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

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

1.1     ! rubenllo    1: /*
        !             2:  *  Combat.c
        !             3:  *  Brogue
        !             4:  *
        !             5:  *  Created by Brian Walker on 6/11/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:
        !            28: /* Combat rules:
        !            29:  * Each combatant has an accuracy rating. This is the percentage of their attacks that will ordinarily hit;
        !            30:  * higher numbers are better for them. Numbers over 100 are permitted.
        !            31:  *
        !            32:  * Each combatant also has a defense rating. The "hit probability" is calculated as given by this formula:
        !            33:  *
        !            34:  *          hit probability = (accuracy) * 0.987 ^ (defense)
        !            35:  *
        !            36:  * when hit determinations are made. Negative numbers and numbers over 100 are permitted.
        !            37:  * The hit is then randomly determined according to this final percentage.
        !            38:  *
        !            39:  * Some environmental factors can modify these numbers. An unaware, sleeping, stuck or paralyzed
        !            40:  * combatant is always hit. An unaware, sleeping or paralyzed combatant also takes treble damage.
        !            41:  *
        !            42:  * If the hit lands, damage is calculated in the range provided. However, the clumping factor affects the
        !            43:  * probability distribution. If the range is 0-10 with a clumping factor of 1, it's a uniform distribution.
        !            44:  * With a clumping factor of 2, it's calculated as 2d5 (with d5 meaing a die numbered from 0 through 5).
        !            45:  * With 3, it's 3d3, and so on. Note that a range not divisible by the clumping factor is defective,
        !            46:  * as it will never be resolved in the top few numbers of the range. In fact, the top
        !            47:  * (rangeWidth % clumpingFactor) will never succeed. Thus we increment the maximum of the first
        !            48:  * (rangeWidth % clumpingFactor) die by 1, so that in fact 0-10 with a CF of 3 would be 1d4 + 2d3. Similarly,
        !            49:  * 0-10 with CF 4 would be 2d3 + 2d2. By playing with the numbers, one can approximate a gaussian
        !            50:  * distribution of any mean and standard deviation.
        !            51:  *
        !            52:  * Player combatants take their base defense value of their actual armor. Their accuracy is a combination of weapon, armor
        !            53:  * and strength.
        !            54:  *
        !            55:  * Players have a base accuracy value of 100 throughout the game. Each point of weapon enchantment (net of
        !            56:  * strength penalty/benefit) increases
        !            57:  */
        !            58:
        !            59: fixpt strengthModifier(item *theItem) {
        !            60:     int difference = (rogue.strength - player.weaknessAmount) - theItem->strengthRequired;
        !            61:     if (difference > 0) {
        !            62:         return difference * FP_FACTOR / 4; // 0.25x
        !            63:     } else {
        !            64:         return difference * FP_FACTOR * 5/2; // 2.5x
        !            65:     }
        !            66: }
        !            67:
        !            68: fixpt netEnchant(item *theItem) {
        !            69:     fixpt retval = theItem->enchant1 * FP_FACTOR;
        !            70:     if (theItem->category & (WEAPON | ARMOR)) {
        !            71:         retval += strengthModifier(theItem);
        !            72:     }
        !            73:     // Clamp all net enchantment values to [-20, 50].
        !            74:     return clamp(retval, -20 * FP_FACTOR, 50 * FP_FACTOR);
        !            75: }
        !            76:
        !            77: fixpt monsterDamageAdjustmentAmount(const creature *monst) {
        !            78:     if (monst == &player) {
        !            79:         // Handled through player strength routines elsewhere.
        !            80:         return FP_FACTOR;
        !            81:     } else {
        !            82:         return damageFraction(monst->weaknessAmount * FP_FACTOR * -3/2);
        !            83:     }
        !            84: }
        !            85:
        !            86: short monsterDefenseAdjusted(const creature *monst) {
        !            87:     short retval;
        !            88:     if (monst == &player) {
        !            89:         // Weakness is already taken into account in recalculateEquipmentBonuses() for the player.
        !            90:         retval = monst->info.defense;
        !            91:     } else {
        !            92:         retval = monst->info.defense - 25 * monst->weaknessAmount;
        !            93:     }
        !            94:     return max(retval, 0);
        !            95: }
        !            96:
        !            97: short monsterAccuracyAdjusted(const creature *monst) {
        !            98:     short retval = monst->info.accuracy * accuracyFraction(monst->weaknessAmount * FP_FACTOR * -3/2) / FP_FACTOR;
        !            99:     return max(retval, 0);
        !           100: }
        !           101:
        !           102: // does NOT account for auto-hit from sleeping or unaware defenders; does account for auto-hit from
        !           103: // stuck or captive defenders and from weapons of slaying.
        !           104: short hitProbability(creature *attacker, creature *defender) {
        !           105:     short accuracy = monsterAccuracyAdjusted(attacker);
        !           106:     short defense = monsterDefenseAdjusted(defender);
        !           107:     short hitProbability;
        !           108:
        !           109:     if (defender->status[STATUS_STUCK] || (defender->bookkeepingFlags & MB_CAPTIVE)) {
        !           110:         return 100;
        !           111:     }
        !           112:     if ((defender->bookkeepingFlags & MB_SEIZED)
        !           113:         && (attacker->bookkeepingFlags & MB_SEIZING)) {
        !           114:
        !           115:         return 100;
        !           116:     }
        !           117:     if (attacker == &player && rogue.weapon) {
        !           118:         if ((rogue.weapon->flags & ITEM_RUNIC)
        !           119:             && rogue.weapon->enchant2 == W_SLAYING
        !           120:             && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) {
        !           121:
        !           122:             return 100;
        !           123:         }
        !           124:         accuracy = player.info.accuracy * accuracyFraction(netEnchant(rogue.weapon)) / FP_FACTOR;
        !           125:     }
        !           126:     hitProbability = accuracy * defenseFraction(defense * FP_FACTOR) / FP_FACTOR;
        !           127:     if (hitProbability > 100) {
        !           128:         hitProbability = 100;
        !           129:     } else if (hitProbability < 0) {
        !           130:         hitProbability = 0;
        !           131:     }
        !           132:     return hitProbability;
        !           133: }
        !           134:
        !           135: boolean attackHit(creature *attacker, creature *defender) {
        !           136:     // automatically hit if the monster is sleeping or captive or stuck in a web
        !           137:     if (defender->status[STATUS_STUCK]
        !           138:         || defender->status[STATUS_PARALYZED]
        !           139:         || (defender->bookkeepingFlags & MB_CAPTIVE)) {
        !           140:
        !           141:         return true;
        !           142:     }
        !           143:
        !           144:     return rand_percent(hitProbability(attacker, defender));
        !           145: }
        !           146:
        !           147: void addMonsterToContiguousMonsterGrid(short x, short y, creature *monst, char grid[DCOLS][DROWS]) {
        !           148:     short newX, newY;
        !           149:     enum directions dir;
        !           150:     creature *tempMonst;
        !           151:
        !           152:     grid[x][y] = true;
        !           153:     for (dir=0; dir<4; dir++) {
        !           154:         newX = x + nbDirs[dir][0];
        !           155:         newY = y + nbDirs[dir][1];
        !           156:
        !           157:         if (coordinatesAreInMap(newX, newY) && !grid[newX][newY]) {
        !           158:             tempMonst = monsterAtLoc(newX, newY);
        !           159:             if (tempMonst && monstersAreTeammates(monst, tempMonst)) {
        !           160:                 addMonsterToContiguousMonsterGrid(newX, newY, monst, grid);
        !           161:             }
        !           162:         }
        !           163:     }
        !           164: }
        !           165:
        !           166: // Splits a monster in half.
        !           167: // The split occurs only if there is a spot adjacent to the contiguous
        !           168: // group of monsters that the monster would not avoid.
        !           169: // The contiguous group is supplemented with the given (x, y) coordinates, if any;
        !           170: // this is so that jellies et al. can spawn behind the player in a hallway.
        !           171: void splitMonster(creature *monst, short x, short y) {
        !           172:     short i, j, b, dir, newX, newY, eligibleLocationCount, randIndex;
        !           173:     char buf[DCOLS * 3];
        !           174:     char monstName[DCOLS];
        !           175:     char monsterGrid[DCOLS][DROWS], eligibleGrid[DCOLS][DROWS];
        !           176:     creature *clone;
        !           177:
        !           178:     zeroOutGrid(monsterGrid);
        !           179:     zeroOutGrid(eligibleGrid);
        !           180:     eligibleLocationCount = 0;
        !           181:
        !           182:     // Add the (x, y) location to the contiguous group, if any.
        !           183:     if (x > 0 && y > 0) {
        !           184:         monsterGrid[x][y] = true;
        !           185:     }
        !           186:
        !           187:     // Find the contiguous group of monsters.
        !           188:     addMonsterToContiguousMonsterGrid(monst->xLoc, monst->yLoc, monst, monsterGrid);
        !           189:
        !           190:     // Find the eligible edges around the group of monsters.
        !           191:     for (i=0; i<DCOLS; i++) {
        !           192:         for (j=0; j<DROWS; j++) {
        !           193:             if (monsterGrid[i][j]) {
        !           194:                 for (dir=0; dir<4; dir++) {
        !           195:                     newX = i + nbDirs[dir][0];
        !           196:                     newY = j + nbDirs[dir][1];
        !           197:                     if (coordinatesAreInMap(newX, newY)
        !           198:                         && !eligibleGrid[newX][newY]
        !           199:                         && !monsterGrid[newX][newY]
        !           200:                         && !(pmap[newX][newY].flags & (HAS_PLAYER | HAS_MONSTER))
        !           201:                         && !monsterAvoids(monst, newX, newY)) {
        !           202:
        !           203:                         eligibleGrid[newX][newY] = true;
        !           204:                         eligibleLocationCount++;
        !           205:                     }
        !           206:                 }
        !           207:             }
        !           208:         }
        !           209:     }
        !           210: //    DEBUG {
        !           211: //        hiliteCharGrid(eligibleGrid, &green, 75);
        !           212: //        hiliteCharGrid(monsterGrid, &blue, 75);
        !           213: //        temporaryMessage("Jelly spawn possibilities (green = eligible, blue = monster):", true);
        !           214: //        displayLevel();
        !           215: //    }
        !           216:
        !           217:     // Pick a random location on the eligibleGrid and add the clone there.
        !           218:     if (eligibleLocationCount) {
        !           219:         randIndex = rand_range(1, eligibleLocationCount);
        !           220:         for (i=0; i<DCOLS; i++) {
        !           221:             for (j=0; j<DROWS; j++) {
        !           222:                 if (eligibleGrid[i][j] && !--randIndex) {
        !           223:                     // Found the spot!
        !           224:
        !           225:                     monsterName(monstName, monst, true);
        !           226:                     monst->currentHP = (monst->currentHP + 1) / 2;
        !           227:                     clone = cloneMonster(monst, false, false);
        !           228:
        !           229:                     // Split monsters don't inherit the learnings of their parents.
        !           230:                     // Sorry, but self-healing jelly armies are too much.
        !           231:                     // Mutation effects can be inherited, however; they're not learned abilities.
        !           232:                     if (monst->mutationIndex >= 0) {
        !           233:                         clone->info.flags           &= (monsterCatalog[clone->info.monsterID].flags | mutationCatalog[monst->mutationIndex].monsterFlags);
        !           234:                         clone->info.abilityFlags    &= (monsterCatalog[clone->info.monsterID].abilityFlags | mutationCatalog[monst->mutationIndex].monsterAbilityFlags);
        !           235:                     } else {
        !           236:                         clone->info.flags           &= monsterCatalog[clone->info.monsterID].flags;
        !           237:                         clone->info.abilityFlags    &= monsterCatalog[clone->info.monsterID].abilityFlags;
        !           238:                     }
        !           239:                     for (b = 0; b < 20; b++) {
        !           240:                         clone->info.bolts[b] = monsterCatalog[clone->info.monsterID].bolts[b];
        !           241:                     }
        !           242:
        !           243:                     if (!(clone->info.flags & MONST_FLIES)
        !           244:                         && clone->status[STATUS_LEVITATING] == 1000) {
        !           245:
        !           246:                         clone->status[STATUS_LEVITATING] = 0;
        !           247:                     }
        !           248:
        !           249:                     clone->xLoc = i;
        !           250:                     clone->yLoc = j;
        !           251:                     pmap[i][j].flags |= HAS_MONSTER;
        !           252:                     clone->ticksUntilTurn = max(clone->ticksUntilTurn, 101);
        !           253:                     fadeInMonster(clone);
        !           254:                     refreshSideBar(-1, -1, false);
        !           255:
        !           256:                     if (canDirectlySeeMonster(monst)) {
        !           257:                         sprintf(buf, "%s splits in two!", monstName);
        !           258:                         message(buf, false);
        !           259:                     }
        !           260:
        !           261:                     return;
        !           262:                 }
        !           263:             }
        !           264:         }
        !           265:     }
        !           266: }
        !           267:
        !           268: short alliedCloneCount(creature *monst) {
        !           269:     short count;
        !           270:     creature *temp;
        !           271:
        !           272:     count = 0;
        !           273:     for (temp = monsters->nextCreature; temp != NULL; temp = temp->nextCreature) {
        !           274:         if (temp != monst
        !           275:             && temp->info.monsterID == monst->info.monsterID
        !           276:             && monstersAreTeammates(temp, monst)) {
        !           277:
        !           278:             count++;
        !           279:         }
        !           280:     }
        !           281:     if (rogue.depthLevel > 1) {
        !           282:         for (temp = levels[rogue.depthLevel - 2].monsters; temp != NULL; temp = temp->nextCreature) {
        !           283:             if (temp != monst
        !           284:                 && temp->info.monsterID == monst->info.monsterID
        !           285:                 && monstersAreTeammates(temp, monst)) {
        !           286:
        !           287:                 count++;
        !           288:             }
        !           289:         }
        !           290:     }
        !           291:     if (rogue.depthLevel < DEEPEST_LEVEL) {
        !           292:         for (temp = levels[rogue.depthLevel].monsters; temp != NULL; temp = temp->nextCreature) {
        !           293:             if (temp != monst
        !           294:                 && temp->info.monsterID == monst->info.monsterID
        !           295:                 && monstersAreTeammates(temp, monst)) {
        !           296:
        !           297:                 count++;
        !           298:             }
        !           299:         }
        !           300:     }
        !           301:     return count;
        !           302: }
        !           303:
        !           304: // This function is called whenever one creature acts aggressively against another in a way that directly causes damage.
        !           305: // This can be things like melee attacks, fire/lightning attacks or throwing a weapon.
        !           306: void moralAttack(creature *attacker, creature *defender) {
        !           307:
        !           308:     if (attacker == &player && canSeeMonster(defender)) {
        !           309:         rogue.featRecord[FEAT_PACIFIST] = false;
        !           310:         if (defender->creatureState != MONSTER_TRACKING_SCENT) {
        !           311:             rogue.featRecord[FEAT_PALADIN] = false;
        !           312:         }
        !           313:     }
        !           314:
        !           315:     if (defender->currentHP > 0
        !           316:         && !(defender->bookkeepingFlags & MB_IS_DYING)) {
        !           317:
        !           318:         if (defender->status[STATUS_PARALYZED]) {
        !           319:             defender->status[STATUS_PARALYZED] = 0;
        !           320:              // Paralyzed creature gets a turn to react before the attacker moves again.
        !           321:             defender->ticksUntilTurn = min(attacker->attackSpeed, 100) - 1;
        !           322:         }
        !           323:         if (defender->status[STATUS_MAGICAL_FEAR]) {
        !           324:             defender->status[STATUS_MAGICAL_FEAR] = 1;
        !           325:         }
        !           326:         defender->status[STATUS_ENTRANCED] = 0;
        !           327:
        !           328:         if (attacker == &player
        !           329:             && defender->creatureState == MONSTER_ALLY
        !           330:             && !defender->status[STATUS_DISCORDANT]
        !           331:             && !attacker->status[STATUS_CONFUSED]
        !           332:             && !(attacker->bookkeepingFlags & MB_IS_DYING)) {
        !           333:
        !           334:             unAlly(defender);
        !           335:         }
        !           336:
        !           337:         if ((attacker == &player || attacker->creatureState == MONSTER_ALLY)
        !           338:             && defender != &player
        !           339:             && defender->creatureState != MONSTER_ALLY) {
        !           340:
        !           341:             alertMonster(defender); // this alerts the monster that you're nearby
        !           342:         }
        !           343:
        !           344:         if ((defender->info.abilityFlags & MA_CLONE_SELF_ON_DEFEND) && alliedCloneCount(defender) < 100) {
        !           345:             if (distanceBetween(defender->xLoc, defender->yLoc, attacker->xLoc, attacker->yLoc) <= 1) {
        !           346:                 splitMonster(defender, attacker->xLoc, attacker->yLoc);
        !           347:             } else {
        !           348:                 splitMonster(defender, 0, 0);
        !           349:             }
        !           350:         }
        !           351:     }
        !           352: }
        !           353:
        !           354: boolean playerImmuneToMonster(creature *monst) {
        !           355:     if (monst != &player
        !           356:         && rogue.armor
        !           357:         && (rogue.armor->flags & ITEM_RUNIC)
        !           358:         && (rogue.armor->enchant2 == A_IMMUNITY)
        !           359:         && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) {
        !           360:
        !           361:         return true;
        !           362:     } else {
        !           363:         return false;
        !           364:     }
        !           365: }
        !           366:
        !           367: void specialHit(creature *attacker, creature *defender, short damage) {
        !           368:     short itemCandidates, randItemIndex, stolenQuantity;
        !           369:     item *theItem = NULL, *itemFromTopOfStack;
        !           370:     char buf[COLS], buf2[COLS], buf3[COLS];
        !           371:
        !           372:     if (!(attacker->info.abilityFlags & SPECIAL_HIT)) {
        !           373:         return;
        !           374:     }
        !           375:
        !           376:     // Special hits that can affect only the player:
        !           377:     if (defender == &player) {
        !           378:         if (playerImmuneToMonster(attacker)) {
        !           379:             return;
        !           380:         }
        !           381:
        !           382:         if (attacker->info.abilityFlags & MA_HIT_DEGRADE_ARMOR
        !           383:             && defender == &player
        !           384:             && rogue.armor
        !           385:             && !(rogue.armor->flags & ITEM_PROTECTED)
        !           386:             && (rogue.armor->enchant1 + rogue.armor->armor/10 > -10)) {
        !           387:
        !           388:             rogue.armor->enchant1--;
        !           389:             equipItem(rogue.armor, true);
        !           390:             itemName(rogue.armor, buf2, false, false, NULL);
        !           391:             sprintf(buf, "your %s weakens!", buf2);
        !           392:             messageWithColor(buf, &itemMessageColor, false);
        !           393:             checkForDisenchantment(rogue.armor);
        !           394:         }
        !           395:         if (attacker->info.abilityFlags & MA_HIT_HALLUCINATE) {
        !           396:             if (!player.status[STATUS_HALLUCINATING]) {
        !           397:                 combatMessage("you begin to hallucinate", 0);
        !           398:             }
        !           399:             if (!player.status[STATUS_HALLUCINATING]) {
        !           400:                 player.maxStatus[STATUS_HALLUCINATING] = 0;
        !           401:             }
        !           402:             player.status[STATUS_HALLUCINATING] += 20;
        !           403:             player.maxStatus[STATUS_HALLUCINATING] = max(player.maxStatus[STATUS_HALLUCINATING], player.status[STATUS_HALLUCINATING]);
        !           404:         }
        !           405:         if (attacker->info.abilityFlags & MA_HIT_BURN
        !           406:              && !defender->status[STATUS_IMMUNE_TO_FIRE]) {
        !           407:
        !           408:             exposeCreatureToFire(defender);
        !           409:         }
        !           410:
        !           411:         if (attacker->info.abilityFlags & MA_HIT_STEAL_FLEE
        !           412:             && !(attacker->carriedItem)
        !           413:             && (packItems->nextItem)
        !           414:             && attacker->currentHP > 0
        !           415:             && !attacker->status[STATUS_CONFUSED] // No stealing from the player if you bump him while confused.
        !           416:             && attackHit(attacker, defender)) {
        !           417:
        !           418:             itemCandidates = numberOfMatchingPackItems(ALL_ITEMS, 0, (ITEM_EQUIPPED), false);
        !           419:             if (itemCandidates) {
        !           420:                 randItemIndex = rand_range(1, itemCandidates);
        !           421:                 for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
        !           422:                     if (!(theItem->flags & (ITEM_EQUIPPED))) {
        !           423:                         if (randItemIndex == 1) {
        !           424:                             break;
        !           425:                         } else {
        !           426:                             randItemIndex--;
        !           427:                         }
        !           428:                     }
        !           429:                 }
        !           430:                 if (theItem) {
        !           431:                     if (theItem->category & WEAPON) { // Monkeys will steal half of a stack of weapons, and one of any other stack.
        !           432:                         if (theItem->quantity > 3) {
        !           433:                             stolenQuantity = (theItem->quantity + 1) / 2;
        !           434:                         } else {
        !           435:                             stolenQuantity = theItem->quantity;
        !           436:                         }
        !           437:                     } else {
        !           438:                         stolenQuantity = 1;
        !           439:                     }
        !           440:                     if (stolenQuantity < theItem->quantity) { // Peel off stolen item(s).
        !           441:                         itemFromTopOfStack = generateItem(ALL_ITEMS, -1);
        !           442:                         *itemFromTopOfStack = *theItem; // Clone the item.
        !           443:                         theItem->quantity -= stolenQuantity;
        !           444:                         itemFromTopOfStack->quantity = stolenQuantity;
        !           445:                         theItem = itemFromTopOfStack; // Redirect pointer.
        !           446:                     } else {
        !           447:                         removeItemFromChain(theItem, packItems);
        !           448:                     }
        !           449:                     theItem->flags &= ~ITEM_PLAYER_AVOIDS; // Explore will seek the item out if it ends up on the floor again.
        !           450:                     attacker->carriedItem = theItem;
        !           451:                     attacker->creatureMode = MODE_PERM_FLEEING;
        !           452:                     attacker->creatureState = MONSTER_FLEEING;
        !           453:                     monsterName(buf2, attacker, true);
        !           454:                     itemName(theItem, buf3, false, true, NULL);
        !           455:                     sprintf(buf, "%s stole %s!", buf2, buf3);
        !           456:                     messageWithColor(buf, &badMessageColor, false);
        !           457:                 }
        !           458:             }
        !           459:         }
        !           460:     }
        !           461:     if ((attacker->info.abilityFlags & MA_POISONS)
        !           462:         && damage > 0
        !           463:         && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
        !           464:
        !           465:         addPoison(defender, damage, 1);
        !           466:     }
        !           467:     if ((attacker->info.abilityFlags & MA_CAUSES_WEAKNESS)
        !           468:         && damage > 0
        !           469:         && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
        !           470:
        !           471:         weaken(defender, 300);
        !           472:     }
        !           473:     if (attacker->info.abilityFlags & MA_ATTACKS_STAGGER) {
        !           474:         processStaggerHit(attacker, defender);
        !           475:     }
        !           476: }
        !           477:
        !           478: boolean forceWeaponHit(creature *defender, item *theItem) {
        !           479:     short oldLoc[2], newLoc[2], forceDamage;
        !           480:     char buf[DCOLS*3], buf2[COLS], monstName[DCOLS];
        !           481:     creature *otherMonster = NULL;
        !           482:     boolean knowFirstMonsterDied = false, autoID = false;
        !           483:     bolt theBolt;
        !           484:
        !           485:     monsterName(monstName, defender, true);
        !           486:
        !           487:     oldLoc[0] = defender->xLoc;
        !           488:     oldLoc[1] = defender->yLoc;
        !           489:     newLoc[0] = defender->xLoc + clamp(defender->xLoc - player.xLoc, -1, 1);
        !           490:     newLoc[1] = defender->yLoc + clamp(defender->yLoc - player.yLoc, -1, 1);
        !           491:     if (canDirectlySeeMonster(defender)
        !           492:         && !cellHasTerrainFlag(newLoc[0], newLoc[1], T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION)
        !           493:         && !(pmap[newLoc[0]][newLoc[1]].flags & (HAS_MONSTER | HAS_PLAYER))) {
        !           494:         sprintf(buf, "you launch %s backward with the force of your blow", monstName);
        !           495:         buf[DCOLS] = '\0';
        !           496:         combatMessage(buf, messageColorFromVictim(defender));
        !           497:         autoID = true;
        !           498:     }
        !           499:     theBolt = boltCatalog[BOLT_BLINKING];
        !           500:     theBolt.magnitude = max(1, netEnchant(theItem) / FP_FACTOR);
        !           501:     zap(oldLoc, newLoc, &theBolt, false);
        !           502:     if (!(defender->bookkeepingFlags & MB_IS_DYING)
        !           503:         && distanceBetween(oldLoc[0], oldLoc[1], defender->xLoc, defender->yLoc) > 0
        !           504:         && distanceBetween(oldLoc[0], oldLoc[1], defender->xLoc, defender->yLoc) < weaponForceDistance(netEnchant(theItem))) {
        !           505:
        !           506:         if (pmap[defender->xLoc + newLoc[0] - oldLoc[0]][defender->yLoc + newLoc[1] - oldLoc[1]].flags & (HAS_MONSTER | HAS_PLAYER)) {
        !           507:             otherMonster = monsterAtLoc(defender->xLoc + newLoc[0] - oldLoc[0], defender->yLoc + newLoc[1] - oldLoc[1]);
        !           508:             monsterName(buf2, otherMonster, true);
        !           509:         } else {
        !           510:             otherMonster = NULL;
        !           511:             strcpy(buf2, tileCatalog[pmap[defender->xLoc + newLoc[0] - oldLoc[0]][defender->yLoc + newLoc[1] - oldLoc[1]].layers[highestPriorityLayer(defender->xLoc + newLoc[0] - oldLoc[0], defender->yLoc + newLoc[1] - oldLoc[1], true)]].description);
        !           512:         }
        !           513:
        !           514:         forceDamage = distanceBetween(oldLoc[0], oldLoc[1], defender->xLoc, defender->yLoc);
        !           515:
        !           516:         if (!(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))
        !           517:             && inflictDamage(NULL, defender, forceDamage, &white, false)) {
        !           518:
        !           519:             if (canDirectlySeeMonster(defender)) {
        !           520:                 knowFirstMonsterDied = true;
        !           521:                 sprintf(buf, "%s %s on impact with %s",
        !           522:                         monstName,
        !           523:                         (defender->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies",
        !           524:                         buf2);
        !           525:                 buf[DCOLS] = '\0';
        !           526:                 combatMessage(buf, messageColorFromVictim(defender));
        !           527:                 autoID = true;
        !           528:             }
        !           529:         } else {
        !           530:             if (canDirectlySeeMonster(defender)) {
        !           531:                 sprintf(buf, "%s slams against %s",
        !           532:                         monstName,
        !           533:                         buf2);
        !           534:                 buf[DCOLS] = '\0';
        !           535:                 combatMessage(buf, messageColorFromVictim(defender));
        !           536:                 autoID = true;
        !           537:             }
        !           538:         }
        !           539:         moralAttack(&player, defender);
        !           540:
        !           541:         if (otherMonster
        !           542:             && !(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))) {
        !           543:
        !           544:             if (inflictDamage(NULL, otherMonster, forceDamage, &white, false)) {
        !           545:                 if (canDirectlySeeMonster(otherMonster)) {
        !           546:                     sprintf(buf, "%s %s%s when %s slams into $HIMHER",
        !           547:                             buf2,
        !           548:                             (knowFirstMonsterDied ? "also " : ""),
        !           549:                             (defender->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies",
        !           550:                             monstName);
        !           551:                     resolvePronounEscapes(buf, otherMonster);
        !           552:                     buf[DCOLS] = '\0';
        !           553:                     combatMessage(buf, messageColorFromVictim(otherMonster));
        !           554:                     autoID = true;
        !           555:                 }
        !           556:             }
        !           557:             if (otherMonster->creatureState != MONSTER_ALLY) {
        !           558:                 // Allies won't defect if you throw another monster at them, even though it hurts.
        !           559:                 moralAttack(&player, otherMonster);
        !           560:             }
        !           561:         }
        !           562:     }
        !           563:     return autoID;
        !           564: }
        !           565:
        !           566: void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) {
        !           567:     char buf[DCOLS*3], monstName[DCOLS], theItemName[DCOLS];
        !           568:
        !           569:     color *effectColors[NUMBER_WEAPON_RUNIC_KINDS] = {&white, &black,
        !           570:         &yellow, &pink, &green, &confusionGasColor, NULL, NULL, &darkRed, &rainbow};
        !           571:     //  W_SPEED, W_QUIETUS, W_PARALYSIS, W_MULTIPLICITY, W_SLOWING, W_CONFUSION, W_FORCE, W_SLAYING, W_MERCY, W_PLENTY
        !           572:     short chance, i;
        !           573:     fixpt enchant;
        !           574:     enum weaponEnchants enchantType = theItem->enchant2;
        !           575:     creature *newMonst;
        !           576:     boolean autoID = false;
        !           577:
        !           578:     // If the defender is already dead, proceed only if the runic is speed or multiplicity.
        !           579:     // (Everything else acts on the victim, which would literally be overkill.)
        !           580:     if ((defender->bookkeepingFlags & MB_IS_DYING)
        !           581:         && theItem->enchant2 != W_SPEED
        !           582:         && theItem->enchant2 != W_MULTIPLICITY) {
        !           583:         return;
        !           584:     }
        !           585:
        !           586:     enchant = netEnchant(theItem);
        !           587:
        !           588:     if (theItem->enchant2 == W_SLAYING) {
        !           589:         chance = (monsterIsInClass(defender, theItem->vorpalEnemy) ? 100 : 0);
        !           590:     } else if (defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) {
        !           591:         chance = 0;
        !           592:     } else {
        !           593:         chance = runicWeaponChance(theItem, false, 0);
        !           594:         if (backstabbed && chance < 100) {
        !           595:             chance = min(chance * 2, (chance + 100) / 2);
        !           596:         }
        !           597:     }
        !           598:     if (chance > 0 && rand_percent(chance)) {
        !           599:         if (!(defender->bookkeepingFlags & MB_SUBMERGED)) {
        !           600:             switch (enchantType) {
        !           601:                 case W_SPEED:
        !           602:                     createFlare(player.xLoc, player.yLoc, SCROLL_ENCHANTMENT_LIGHT);
        !           603:                     break;
        !           604:                 case W_QUIETUS:
        !           605:                     createFlare(defender->xLoc, defender->yLoc, QUIETUS_FLARE_LIGHT);
        !           606:                     break;
        !           607:                 case W_SLAYING:
        !           608:                     createFlare(defender->xLoc, defender->yLoc, SLAYING_FLARE_LIGHT);
        !           609:                     break;
        !           610:                 default:
        !           611:                     flashMonster(defender, effectColors[enchantType], 100);
        !           612:                     break;
        !           613:             }
        !           614:             autoID = true;
        !           615:         }
        !           616:         rogue.disturbed = true;
        !           617:         monsterName(monstName, defender, true);
        !           618:         itemName(theItem, theItemName, false, false, NULL);
        !           619:
        !           620:         switch (enchantType) {
        !           621:             case W_SPEED:
        !           622:                 if (player.ticksUntilTurn != -1) {
        !           623:                     sprintf(buf, "your %s trembles and time freezes for a moment", theItemName);
        !           624:                     buf[DCOLS] = '\0';
        !           625:                     combatMessage(buf, 0);
        !           626:                     player.ticksUntilTurn = -1; // free turn!
        !           627:                     autoID = true;
        !           628:                 }
        !           629:                 break;
        !           630:             case W_SLAYING:
        !           631:             case W_QUIETUS:
        !           632:                 inflictLethalDamage(&player, defender);
        !           633:                 sprintf(buf, "%s suddenly %s",
        !           634:                         monstName,
        !           635:                         (defender->info.flags & MONST_INANIMATE) ? "shatters" : "dies");
        !           636:                 buf[DCOLS] = '\0';
        !           637:                 combatMessage(buf, messageColorFromVictim(defender));
        !           638:                 autoID = true;
        !           639:                 break;
        !           640:             case W_PARALYSIS:
        !           641:                 defender->status[STATUS_PARALYZED] = max(defender->status[STATUS_PARALYZED], weaponParalysisDuration(enchant));
        !           642:                 defender->maxStatus[STATUS_PARALYZED] = defender->status[STATUS_PARALYZED];
        !           643:                 if (canDirectlySeeMonster(defender)) {
        !           644:                     sprintf(buf, "%s is frozen in place", monstName);
        !           645:                     buf[DCOLS] = '\0';
        !           646:                     combatMessage(buf, messageColorFromVictim(defender));
        !           647:                     autoID = true;
        !           648:                 }
        !           649:                 break;
        !           650:             case W_MULTIPLICITY:
        !           651:                 sprintf(buf, "Your %s emits a flash of light, and %sspectral duplicate%s appear%s!",
        !           652:                         theItemName,
        !           653:                         (weaponImageCount(enchant) == 1 ? "a " : ""),
        !           654:                         (weaponImageCount(enchant) == 1 ? "" : "s"),
        !           655:                         (weaponImageCount(enchant) == 1 ? "s" : ""));
        !           656:                 buf[DCOLS] = '\0';
        !           657:
        !           658:                 for (i = 0; i < (weaponImageCount(enchant)); i++) {
        !           659:                     newMonst = generateMonster(MK_SPECTRAL_IMAGE, true, false);
        !           660:                     getQualifyingPathLocNear(&(newMonst->xLoc), &(newMonst->yLoc), defender->xLoc, defender->yLoc, true,
        !           661:                                              T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(newMonst->info)), HAS_PLAYER,
        !           662:                                              avoidedFlagsForMonster(&(newMonst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
        !           663:                     newMonst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED);
        !           664:                     newMonst->bookkeepingFlags &= ~MB_JUST_SUMMONED;
        !           665:                     newMonst->leader = &player;
        !           666:                     newMonst->creatureState = MONSTER_ALLY;
        !           667:                     if (theItem->flags & ITEM_ATTACKS_STAGGER) {
        !           668:                         newMonst->info.attackSpeed *= 2;
        !           669:                         newMonst->info.abilityFlags |= MA_ATTACKS_STAGGER;
        !           670:                     }
        !           671:                     if (theItem->flags & ITEM_ATTACKS_QUICKLY) {
        !           672:                         newMonst->info.attackSpeed /= 2;
        !           673:                     }
        !           674:                     if (theItem->flags & ITEM_ATTACKS_PENETRATE) {
        !           675:                         newMonst->info.abilityFlags |= MA_ATTACKS_PENETRATE;
        !           676:                     }
        !           677:                     if (theItem->flags & ITEM_ATTACKS_ALL_ADJACENT) {
        !           678:                         newMonst->info.abilityFlags |= MA_ATTACKS_ALL_ADJACENT;
        !           679:                     }
        !           680:                     if (theItem->flags & ITEM_ATTACKS_EXTEND) {
        !           681:                         newMonst->info.abilityFlags |= MA_ATTACKS_EXTEND;
        !           682:                     }
        !           683:                     newMonst->ticksUntilTurn = 100;
        !           684:                     newMonst->info.accuracy = player.info.accuracy + (5 * netEnchant(theItem) / FP_FACTOR);
        !           685:                     newMonst->info.damage = player.info.damage;
        !           686:                     newMonst->status[STATUS_LIFESPAN_REMAINING] = newMonst->maxStatus[STATUS_LIFESPAN_REMAINING] = weaponImageDuration(enchant);
        !           687:                     if (strLenWithoutEscapes(theItemName) <= 8) {
        !           688:                         sprintf(newMonst->info.monsterName, "spectral %s", theItemName);
        !           689:                     } else {
        !           690:                         switch (rogue.weapon->kind) {
        !           691:                             case BROADSWORD:
        !           692:                                 strcpy(newMonst->info.monsterName, "spectral sword");
        !           693:                                 break;
        !           694:                             case HAMMER:
        !           695:                                 strcpy(newMonst->info.monsterName, "spectral hammer");
        !           696:                                 break;
        !           697:                             case PIKE:
        !           698:                                 strcpy(newMonst->info.monsterName, "spectral pike");
        !           699:                                 break;
        !           700:                             case WAR_AXE:
        !           701:                                 strcpy(newMonst->info.monsterName, "spectral axe");
        !           702:                                 break;
        !           703:                             default:
        !           704:                                 strcpy(newMonst->info.monsterName, "spectral weapon");
        !           705:                                 break;
        !           706:                         }
        !           707:                     }
        !           708:                     pmap[newMonst->xLoc][newMonst->yLoc].flags |= HAS_MONSTER;
        !           709:                     fadeInMonster(newMonst);
        !           710:                 }
        !           711:                 updateVision(true);
        !           712:
        !           713:                 message(buf, false);
        !           714:                 autoID = true;
        !           715:                 break;
        !           716:             case W_SLOWING:
        !           717:                 slow(defender, weaponSlowDuration(enchant));
        !           718:                 if (canDirectlySeeMonster(defender)) {
        !           719:                     sprintf(buf, "%s slows down", monstName);
        !           720:                     buf[DCOLS] = '\0';
        !           721:                     combatMessage(buf, messageColorFromVictim(defender));
        !           722:                     autoID = true;
        !           723:                 }
        !           724:                 break;
        !           725:             case W_CONFUSION:
        !           726:                 defender->status[STATUS_CONFUSED] = max(defender->status[STATUS_CONFUSED], weaponConfusionDuration(enchant));
        !           727:                 defender->maxStatus[STATUS_CONFUSED] = defender->status[STATUS_CONFUSED];
        !           728:                 if (canDirectlySeeMonster(defender)) {
        !           729:                     sprintf(buf, "%s looks very confused", monstName);
        !           730:                     buf[DCOLS] = '\0';
        !           731:                     combatMessage(buf, messageColorFromVictim(defender));
        !           732:                     autoID = true;
        !           733:                 }
        !           734:                 break;
        !           735:             case W_FORCE:
        !           736:                 autoID = forceWeaponHit(defender, theItem);
        !           737:                 break;
        !           738:             case W_MERCY:
        !           739:                 heal(defender, 50, false);
        !           740:                 if (canSeeMonster(defender)) {
        !           741:                     autoID = true;
        !           742:                 }
        !           743:                 break;
        !           744:             case W_PLENTY:
        !           745:                 newMonst = cloneMonster(defender, true, true);
        !           746:                 if (newMonst) {
        !           747:                     flashMonster(newMonst, effectColors[enchantType], 100);
        !           748:                     if (canSeeMonster(newMonst)) {
        !           749:                         autoID = true;
        !           750:                     }
        !           751:                 }
        !           752:                 break;
        !           753:             default:
        !           754:                 break;
        !           755:         }
        !           756:     }
        !           757:     if (autoID) {
        !           758:         autoIdentify(theItem);
        !           759:     }
        !           760: }
        !           761:
        !           762: void attackVerb(char returnString[DCOLS], creature *attacker, short hitPercentile) {
        !           763:     short verbCount, increment;
        !           764:
        !           765:     if (attacker != &player && (player.status[STATUS_HALLUCINATING] || !canSeeMonster(attacker))) {
        !           766:         strcpy(returnString, "hits");
        !           767:         return;
        !           768:     }
        !           769:
        !           770:     if (attacker == &player && !rogue.weapon) {
        !           771:         strcpy(returnString, "punch");
        !           772:         return;
        !           773:     }
        !           774:
        !           775:     for (verbCount = 0; verbCount < 4 && monsterText[attacker->info.monsterID].attack[verbCount + 1][0] != '\0'; verbCount++);
        !           776:     increment = (100 / (verbCount + 1));
        !           777:     hitPercentile = max(0, min(hitPercentile, increment * (verbCount + 1) - 1));
        !           778:     strcpy(returnString, monsterText[attacker->info.monsterID].attack[hitPercentile / increment]);
        !           779:     resolvePronounEscapes(returnString, attacker);
        !           780: }
        !           781:
        !           782: void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short *damage, boolean melee) {
        !           783:     char armorName[DCOLS], attackerName[DCOLS], monstName[DCOLS], buf[DCOLS * 3];
        !           784:     boolean runicKnown;
        !           785:     boolean runicDiscovered;
        !           786:     short newDamage, dir, newX, newY, count, i;
        !           787:     fixpt enchant;
        !           788:     creature *monst, *hitList[8];
        !           789:
        !           790:     returnString[0] = '\0';
        !           791:
        !           792:     if (!(rogue.armor && rogue.armor->flags & ITEM_RUNIC)) {
        !           793:         return; // just in case
        !           794:     }
        !           795:
        !           796:     enchant = netEnchant(rogue.armor);
        !           797:
        !           798:     runicKnown = rogue.armor->flags & ITEM_RUNIC_IDENTIFIED;
        !           799:     runicDiscovered = false;
        !           800:
        !           801:     itemName(rogue.armor, armorName, false, false, NULL);
        !           802:
        !           803:     monsterName(attackerName, attacker, true);
        !           804:
        !           805:     switch (rogue.armor->enchant2) {
        !           806:         case A_MULTIPLICITY:
        !           807:             if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) && rand_percent(33)) {
        !           808:                 for (i = 0; i < armorImageCount(enchant); i++) {
        !           809:                     monst = cloneMonster(attacker, false, true);
        !           810:                     monst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED);
        !           811:                     monst->info.flags |= MONST_DIES_IF_NEGATED;
        !           812:                     monst->bookkeepingFlags &= ~(MB_JUST_SUMMONED | MB_SEIZED | MB_SEIZING);
        !           813:                     monst->info.abilityFlags &= ~(MA_CAST_SUMMON | MA_DF_ON_DEATH); // No summoning by spectral images. Gotta draw the line!
        !           814:                                                                                     // Also no exploding or infecting by spectral clones.
        !           815:                     monst->leader = &player;
        !           816:                     monst->creatureState = MONSTER_ALLY;
        !           817:                     monst->status[STATUS_DISCORDANT] = 0; // Otherwise things can get out of control...
        !           818:                     monst->ticksUntilTurn = 100;
        !           819:                     monst->info.monsterID = MK_SPECTRAL_IMAGE;
        !           820:                     if (monst->carriedMonster) {
        !           821:                         killCreature(monst->carriedMonster, true); // Otherwise you can get infinite phoenices from a discordant phoenix.
        !           822:                         monst->carriedMonster = NULL;
        !           823:                     }
        !           824:
        !           825:                     // Give it the glowy red light and color.
        !           826:                     monst->info.intrinsicLightType = SPECTRAL_IMAGE_LIGHT;
        !           827:                     monst->info.foreColor = &spectralImageColor;
        !           828:
        !           829:                     // Temporary guest!
        !           830:                     monst->status[STATUS_LIFESPAN_REMAINING] = monst->maxStatus[STATUS_LIFESPAN_REMAINING] = 3;
        !           831:                     monst->currentHP = monst->info.maxHP = 1;
        !           832:                     monst->info.defense = 0;
        !           833:
        !           834:                     if (strLenWithoutEscapes(attacker->info.monsterName) <= 6) {
        !           835:                         sprintf(monst->info.monsterName, "spectral %s", attacker->info.monsterName);
        !           836:                     } else {
        !           837:                         strcpy(monst->info.monsterName, "spectral clone");
        !           838:                     }
        !           839:                     fadeInMonster(monst);
        !           840:                 }
        !           841:                 updateVision(true);
        !           842:
        !           843:                 runicDiscovered = true;
        !           844:                 sprintf(returnString, "Your %s flashes, and spectral images of %s appear!", armorName, attackerName);
        !           845:             }
        !           846:             break;
        !           847:         case A_MUTUALITY:
        !           848:             if (*damage > 0) {
        !           849:                 count = 0;
        !           850:                 for (i=0; i<8; i++) {
        !           851:                     hitList[i] = NULL;
        !           852:                     dir = i % 8;
        !           853:                     newX = player.xLoc + nbDirs[dir][0];
        !           854:                     newY = player.yLoc + nbDirs[dir][1];
        !           855:                     if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & HAS_MONSTER)) {
        !           856:                         monst = monsterAtLoc(newX, newY);
        !           857:                         if (monst
        !           858:                             && monst != attacker
        !           859:                             && monstersAreEnemies(&player, monst)
        !           860:                             && !(monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))
        !           861:                             && !(monst->bookkeepingFlags & MB_IS_DYING)) {
        !           862:
        !           863:                             hitList[i] = monst;
        !           864:                             count++;
        !           865:                         }
        !           866:                     }
        !           867:                 }
        !           868:                 if (count) {
        !           869:                     for (i=0; i<8; i++) {
        !           870:                         if (hitList[i] && !(hitList[i]->bookkeepingFlags & MB_IS_DYING)) {
        !           871:                             monsterName(monstName, hitList[i], true);
        !           872:                             if (inflictDamage(&player, hitList[i], (*damage + count) / (count + 1), &blue, true)
        !           873:                                 && canSeeMonster(hitList[i])) {
        !           874:
        !           875:                                 sprintf(buf, "%s %s", monstName, ((hitList[i]->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies"));
        !           876:                                 combatMessage(buf, messageColorFromVictim(hitList[i]));
        !           877:                             }
        !           878:                         }
        !           879:                     }
        !           880:                     runicDiscovered = true;
        !           881:                     if (!runicKnown) {
        !           882:                         sprintf(returnString, "Your %s pulses, and the damage is shared with %s!",
        !           883:                                 armorName,
        !           884:                                 (count == 1 ? monstName : "the other adjacent enemies"));
        !           885:                     }
        !           886:                     *damage = (*damage + count) / (count + 1);
        !           887:                 }
        !           888:             }
        !           889:             break;
        !           890:         case A_ABSORPTION:
        !           891:             *damage -= rand_range(0, armorAbsorptionMax(enchant));
        !           892:             if (*damage <= 0) {
        !           893:                 *damage = 0;
        !           894:                 runicDiscovered = true;
        !           895:                 if (!runicKnown) {
        !           896:                     sprintf(returnString, "your %s pulses and absorbs the blow!", armorName);
        !           897:                 }
        !           898:             }
        !           899:             break;
        !           900:         case A_REPRISAL:
        !           901:             if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
        !           902:                 newDamage = max(1, armorReprisalPercent(enchant) * (*damage) / 100); // 5% reprisal per armor level
        !           903:                 if (inflictDamage(&player, attacker, newDamage, &blue, true)) {
        !           904:                     if (canSeeMonster(attacker)) {
        !           905:                         sprintf(returnString, "your %s pulses and %s drops dead!", armorName, attackerName);
        !           906:                         runicDiscovered = true;
        !           907:                     }
        !           908:                 } else if (!runicKnown) {
        !           909:                     if (canSeeMonster(attacker)) {
        !           910:                         sprintf(returnString, "your %s pulses and %s shudders in pain!", armorName, attackerName);
        !           911:                         runicDiscovered = true;
        !           912:                     }
        !           913:                 }
        !           914:             }
        !           915:             break;
        !           916:         case A_IMMUNITY:
        !           917:             if (monsterIsInClass(attacker, rogue.armor->vorpalEnemy)) {
        !           918:                 *damage = 0;
        !           919:                 runicDiscovered = true;
        !           920:             }
        !           921:             break;
        !           922:         case A_BURDEN:
        !           923:             if (rand_percent(10)) {
        !           924:                 rogue.armor->strengthRequired++;
        !           925:                 sprintf(returnString, "your %s suddenly feels heavier!", armorName);
        !           926:                 equipItem(rogue.armor, true);
        !           927:                 runicDiscovered = true;
        !           928:             }
        !           929:             break;
        !           930:         case A_VULNERABILITY:
        !           931:             *damage *= 2;
        !           932:             if (!runicKnown) {
        !           933:                 sprintf(returnString, "your %s pulses and you are wracked with pain!", armorName);
        !           934:                 runicDiscovered = true;
        !           935:             }
        !           936:             break;
        !           937:         case A_IMMOLATION:
        !           938:             if (rand_percent(10)) {
        !           939:                 sprintf(returnString, "flames suddenly explode out of your %s!", armorName);
        !           940:                 message(returnString, !runicKnown);
        !           941:                 returnString[0] = '\0';
        !           942:                 spawnDungeonFeature(player.xLoc, player.yLoc, &(dungeonFeatureCatalog[DF_ARMOR_IMMOLATION]), true, false);
        !           943:                 runicDiscovered = true;
        !           944:             }
        !           945:         default:
        !           946:             break;
        !           947:     }
        !           948:
        !           949:     if (runicDiscovered && !runicKnown) {
        !           950:         autoIdentify(rogue.armor);
        !           951:     }
        !           952: }
        !           953:
        !           954: void decrementWeaponAutoIDTimer() {
        !           955:     char buf[COLS*3], buf2[COLS*3];
        !           956:
        !           957:     if (rogue.weapon
        !           958:         && !(rogue.weapon->flags & ITEM_IDENTIFIED)
        !           959:         && !--rogue.weapon->charges) {
        !           960:
        !           961:         rogue.weapon->flags |= ITEM_IDENTIFIED;
        !           962:         updateIdentifiableItems();
        !           963:         messageWithColor("you are now familiar enough with your weapon to identify it.", &itemMessageColor, false);
        !           964:         itemName(rogue.weapon, buf2, true, true, NULL);
        !           965:         sprintf(buf, "%s %s.", (rogue.weapon->quantity > 1 ? "they are" : "it is"), buf2);
        !           966:         messageWithColor(buf, &itemMessageColor, false);
        !           967:     }
        !           968: }
        !           969:
        !           970: void processStaggerHit(creature *attacker, creature *defender) {
        !           971:     if ((defender->info.flags & (MONST_INVULNERABLE | MONST_IMMOBILE | MONST_INANIMATE))
        !           972:         || (defender->bookkeepingFlags & MB_CAPTIVE)
        !           973:         || cellHasTerrainFlag(defender->xLoc, defender->yLoc, T_OBSTRUCTS_PASSABILITY)) {
        !           974:
        !           975:         return;
        !           976:     }
        !           977:     short newX = clamp(defender->xLoc - attacker->xLoc, -1, 1) + defender->xLoc;
        !           978:     short newY = clamp(defender->yLoc - attacker->yLoc, -1, 1) + defender->yLoc;
        !           979:     if (coordinatesAreInMap(newX, newY)
        !           980:         && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
        !           981:         && !(pmap[newX][newY].flags & (HAS_MONSTER | HAS_PLAYER))) {
        !           982:
        !           983:         setMonsterLocation(defender, newX, newY);
        !           984:     }
        !           985: }
        !           986:
        !           987: // returns whether the attack hit
        !           988: boolean attack(creature *attacker, creature *defender, boolean lungeAttack) {
        !           989:     short damage, specialDamage, poisonDamage;
        !           990:     char buf[COLS*2], buf2[COLS*2], attackerName[COLS], defenderName[COLS], verb[DCOLS], explicationClause[DCOLS] = "", armorRunicString[DCOLS*3];
        !           991:     boolean sneakAttack, defenderWasAsleep, defenderWasParalyzed, degradesAttackerWeapon, sightUnseen;
        !           992:
        !           993:     if (attacker == &player && canSeeMonster(defender)) {
        !           994:         rogue.featRecord[FEAT_PURE_MAGE] = false;
        !           995:     }
        !           996:
        !           997:     if (attacker->info.abilityFlags & MA_KAMIKAZE) {
        !           998:         killCreature(attacker, false);
        !           999:         return true;
        !          1000:     }
        !          1001:
        !          1002:     armorRunicString[0] = '\0';
        !          1003:
        !          1004:     poisonDamage = 0;
        !          1005:
        !          1006:     degradesAttackerWeapon = (defender->info.flags & MONST_DEFEND_DEGRADE_WEAPON ? true : false);
        !          1007:
        !          1008:     sightUnseen = !canSeeMonster(attacker) && !canSeeMonster(defender);
        !          1009:
        !          1010:     if (defender->status[STATUS_LEVITATING] && (attacker->info.flags & MONST_RESTRICTED_TO_LIQUID)) {
        !          1011:         return false; // aquatic or other liquid-bound monsters cannot attack flying opponents
        !          1012:     }
        !          1013:
        !          1014:     if ((attacker == &player || defender == &player) && !rogue.blockCombatText) {
        !          1015:         rogue.disturbed = true;
        !          1016:     }
        !          1017:
        !          1018:     defender->status[STATUS_ENTRANCED] = 0;
        !          1019:     if (defender->status[STATUS_MAGICAL_FEAR]) {
        !          1020:         defender->status[STATUS_MAGICAL_FEAR] = 1;
        !          1021:     }
        !          1022:
        !          1023:     if (attacker == &player
        !          1024:         && defender->creatureState != MONSTER_TRACKING_SCENT) {
        !          1025:
        !          1026:         rogue.featRecord[FEAT_PALADIN] = false;
        !          1027:     }
        !          1028:
        !          1029:     if (attacker != &player && defender == &player && attacker->creatureState == MONSTER_WANDERING) {
        !          1030:         attacker->creatureState = MONSTER_TRACKING_SCENT;
        !          1031:     }
        !          1032:
        !          1033:     if (defender->info.flags & MONST_INANIMATE) {
        !          1034:         sneakAttack = false;
        !          1035:         defenderWasAsleep = false;
        !          1036:         defenderWasParalyzed = false;
        !          1037:     } else {
        !          1038:         sneakAttack = (defender != &player && attacker == &player && (defender->creatureState == MONSTER_WANDERING) ? true : false);
        !          1039:         defenderWasAsleep = (defender != &player && (defender->creatureState == MONSTER_SLEEPING) ? true : false);
        !          1040:         defenderWasParalyzed = defender->status[STATUS_PARALYZED] > 0;
        !          1041:     }
        !          1042:
        !          1043:     monsterName(attackerName, attacker, true);
        !          1044:     monsterName(defenderName, defender, true);
        !          1045:
        !          1046:     if ((attacker->info.abilityFlags & MA_SEIZES)
        !          1047:         && (!(attacker->bookkeepingFlags & MB_SEIZING) || !(defender->bookkeepingFlags & MB_SEIZED))
        !          1048:         && (rogue.patchVersion < 2 ||
        !          1049:             (distanceBetween(attacker->xLoc, attacker->yLoc, defender->xLoc, defender->yLoc) == 1
        !          1050:             && !diagonalBlocked(attacker->xLoc, attacker->yLoc, defender->xLoc, defender->yLoc, false)))) {
        !          1051:
        !          1052:         attacker->bookkeepingFlags |= MB_SEIZING;
        !          1053:         defender->bookkeepingFlags |= MB_SEIZED;
        !          1054:         if (canSeeMonster(attacker) || canSeeMonster(defender)) {
        !          1055:             sprintf(buf, "%s seizes %s!", attackerName, (defender == &player ? "your legs" : defenderName));
        !          1056:             messageWithColor(buf, &white, false);
        !          1057:         }
        !          1058:         return false;
        !          1059:     }
        !          1060:
        !          1061:     if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack || attackHit(attacker, defender)) {
        !          1062:         // If the attack hit:
        !          1063:         damage = (defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)
        !          1064:                   ? 0 : randClump(attacker->info.damage) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR);
        !          1065:
        !          1066:         if (sneakAttack || defenderWasAsleep || defenderWasParalyzed) {
        !          1067:             if (defender != &player) {
        !          1068:                 // The non-player defender doesn't hit back this turn because it's still flat-footed.
        !          1069:                 defender->ticksUntilTurn += max(defender->movementSpeed, defender->attackSpeed);
        !          1070:                 if (defender->creatureState != MONSTER_ALLY) {
        !          1071:                     defender->creatureState = MONSTER_TRACKING_SCENT; // Wake up!
        !          1072:                 }
        !          1073:             }
        !          1074:         }
        !          1075:         if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack) {
        !          1076:             if (attacker == &player
        !          1077:                 && rogue.weapon
        !          1078:                 && (rogue.weapon->flags & ITEM_SNEAK_ATTACK_BONUS)) {
        !          1079:
        !          1080:                 damage *= 5; // 5x damage for dagger sneak attacks.
        !          1081:             } else {
        !          1082:                 damage *= 3; // Treble damage for general sneak attacks.
        !          1083:             }
        !          1084:         }
        !          1085:
        !          1086:         if (defender == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC)) {
        !          1087:             applyArmorRunicEffect(armorRunicString, attacker, &damage, true);
        !          1088:         }
        !          1089:
        !          1090:         if (attacker == &player
        !          1091:             && rogue.reaping
        !          1092:             && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
        !          1093:
        !          1094:             specialDamage = min(damage, defender->currentHP) * rogue.reaping; // Maximum reaped damage can't exceed the victim's remaining health.
        !          1095:             if (rogue.reaping > 0) {
        !          1096:                 specialDamage = rand_range(0, specialDamage);
        !          1097:             } else {
        !          1098:                 specialDamage = rand_range(specialDamage, 0);
        !          1099:             }
        !          1100:             if (specialDamage) {
        !          1101:                 rechargeItemsIncrementally(specialDamage);
        !          1102:             }
        !          1103:         }
        !          1104:
        !          1105:         if (damage == 0) {
        !          1106:             sprintf(explicationClause, " but %s no damage", (attacker == &player ? "do" : "does"));
        !          1107:             if (attacker == &player) {
        !          1108:                 rogue.disturbed = true;
        !          1109:             }
        !          1110:         } else if (lungeAttack) {
        !          1111:             strcpy(explicationClause, " with a vicious lunge attack");
        !          1112:         } else if (defenderWasParalyzed) {
        !          1113:             sprintf(explicationClause, " while $HESHE %s paralyzed", (defender == &player ? "are" : "is"));
        !          1114:         } else if (defenderWasAsleep) {
        !          1115:             strcpy(explicationClause, " in $HISHER sleep");
        !          1116:         } else if (sneakAttack) {
        !          1117:             strcpy(explicationClause, ", catching $HIMHER unaware");
        !          1118:         } else if (defender->status[STATUS_STUCK] || defender->bookkeepingFlags & MB_CAPTIVE) {
        !          1119:             sprintf(explicationClause, " while %s dangle%s helplessly",
        !          1120:                     (canSeeMonster(defender) ? "$HESHE" : "it"),
        !          1121:                     (defender == &player ? "" : "s"));
        !          1122:         }
        !          1123:         resolvePronounEscapes(explicationClause, defender);
        !          1124:
        !          1125:         if ((attacker->info.abilityFlags & MA_POISONS) && damage > 0) {
        !          1126:             poisonDamage = damage;
        !          1127:             damage = 1;
        !          1128:         }
        !          1129:
        !          1130:         if (inflictDamage(attacker, defender, damage, &red, false)) { // if the attack killed the defender
        !          1131:             if (defenderWasAsleep || sneakAttack || defenderWasParalyzed || lungeAttack) {
        !          1132:                 sprintf(buf, "%s %s %s%s", attackerName,
        !          1133:                         ((defender->info.flags & MONST_INANIMATE) ? "destroyed" : "dispatched"),
        !          1134:                         defenderName,
        !          1135:                         explicationClause);
        !          1136:             } else {
        !          1137:                 sprintf(buf, "%s %s %s%s",
        !          1138:                         attackerName,
        !          1139:                         ((defender->info.flags & MONST_INANIMATE) ? "destroyed" : "defeated"),
        !          1140:                         defenderName,
        !          1141:                         explicationClause);
        !          1142:             }
        !          1143:             if (sightUnseen) {
        !          1144:                 if (defender->info.flags & MONST_INANIMATE) {
        !          1145:                     combatMessage("you hear something get destroyed in combat", 0);
        !          1146:                 } else {
        !          1147:                     combatMessage("you hear something die in combat", 0);
        !          1148:                 }
        !          1149:             } else {
        !          1150:                 combatMessage(buf, (damage > 0 ? messageColorFromVictim(defender) : &white));
        !          1151:             }
        !          1152:             if (&player == defender) {
        !          1153:                 gameOver(attacker->info.monsterName, false);
        !          1154:                 return true;
        !          1155:             } else if (&player == attacker
        !          1156:                        && defender->info.monsterID == MK_DRAGON) {
        !          1157:
        !          1158:                 rogue.featRecord[FEAT_DRAGONSLAYER] = true;
        !          1159:             }
        !          1160:         } else { // if the defender survived
        !          1161:             if (!rogue.blockCombatText && (canSeeMonster(attacker) || canSeeMonster(defender))) {
        !          1162:                 attackVerb(verb, attacker, max(damage - (attacker->info.damage.lowerBound * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR), 0) * 100
        !          1163:                            / max(1, (attacker->info.damage.upperBound - attacker->info.damage.lowerBound) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR));
        !          1164:                 sprintf(buf, "%s %s %s%s", attackerName, verb, defenderName, explicationClause);
        !          1165:                 if (sightUnseen) {
        !          1166:                     if (!rogue.heardCombatThisTurn) {
        !          1167:                         rogue.heardCombatThisTurn = true;
        !          1168:                         combatMessage("you hear combat in the distance", 0);
        !          1169:                     }
        !          1170:                 } else {
        !          1171:                     combatMessage(buf, messageColorFromVictim(defender));
        !          1172:                 }
        !          1173:             }
        !          1174:             if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_STAGGER)) {
        !          1175:                 processStaggerHit(attacker, defender);
        !          1176:             }
        !          1177:             if (attacker->info.abilityFlags & SPECIAL_HIT) {
        !          1178:                 specialHit(attacker, defender, (attacker->info.abilityFlags & MA_POISONS) ? poisonDamage : damage);
        !          1179:             }
        !          1180:             if (armorRunicString[0]) {
        !          1181:                 message(armorRunicString, false);
        !          1182:                 if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_BURDEN) {
        !          1183:                     strengthCheck(rogue.armor);
        !          1184:                 }
        !          1185:             }
        !          1186:         }
        !          1187:
        !          1188:         moralAttack(attacker, defender);
        !          1189:
        !          1190:         if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_RUNIC)) {
        !          1191:             magicWeaponHit(defender, rogue.weapon, sneakAttack || defenderWasAsleep || defenderWasParalyzed);
        !          1192:         }
        !          1193:
        !          1194:         if (attacker == &player
        !          1195:             && (defender->bookkeepingFlags & MB_IS_DYING)
        !          1196:             && (defender->bookkeepingFlags & MB_HAS_SOUL)) {
        !          1197:
        !          1198:             decrementWeaponAutoIDTimer();
        !          1199:         }
        !          1200:
        !          1201:         if (degradesAttackerWeapon
        !          1202:             && attacker == &player
        !          1203:             && rogue.weapon
        !          1204:             && !(rogue.weapon->flags & ITEM_PROTECTED)
        !          1205:                 // Can't damage a Weapon of Acid Mound Slaying by attacking an acid mound... just ain't right!
        !          1206:             && !((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING && monsterIsInClass(defender, rogue.weapon->vorpalEnemy))
        !          1207:             && rogue.weapon->enchant1 >= -10) {
        !          1208:
        !          1209:             rogue.weapon->enchant1--;
        !          1210:             if (rogue.weapon->quiverNumber) {
        !          1211:                 rogue.weapon->quiverNumber = rand_range(1, 60000);
        !          1212:             }
        !          1213:             equipItem(rogue.weapon, true);
        !          1214:             itemName(rogue.weapon, buf2, false, false, NULL);
        !          1215:             sprintf(buf, "your %s weakens!", buf2);
        !          1216:             messageWithColor(buf, &itemMessageColor, false);
        !          1217:             checkForDisenchantment(rogue.weapon);
        !          1218:         }
        !          1219:
        !          1220:         return true;
        !          1221:     } else { // if the attack missed
        !          1222:         if (!rogue.blockCombatText) {
        !          1223:             if (sightUnseen) {
        !          1224:                 if (!rogue.heardCombatThisTurn) {
        !          1225:                     rogue.heardCombatThisTurn = true;
        !          1226:                     combatMessage("you hear combat in the distance", 0);
        !          1227:                 }
        !          1228:             } else {
        !          1229:                 sprintf(buf, "%s missed %s", attackerName, defenderName);
        !          1230:                 combatMessage(buf, 0);
        !          1231:             }
        !          1232:         }
        !          1233:         return false;
        !          1234:     }
        !          1235: }
        !          1236:
        !          1237: // Gets the length of a string without the four-character color escape sequences, since those aren't displayed.
        !          1238: short strLenWithoutEscapes(const char *str) {
        !          1239:     short i, count;
        !          1240:
        !          1241:     count = 0;
        !          1242:     for (i=0; str[i];) {
        !          1243:         if (str[i] == COLOR_ESCAPE) {
        !          1244:             i += 4;
        !          1245:             continue;
        !          1246:         }
        !          1247:         count++;
        !          1248:         i++;
        !          1249:     }
        !          1250:     return count;
        !          1251: }
        !          1252:
        !          1253: void combatMessage(char *theMsg, color *theColor) {
        !          1254:     char newMsg[COLS * 2];
        !          1255:
        !          1256:     if (theColor == 0) {
        !          1257:         theColor = &white;
        !          1258:     }
        !          1259:
        !          1260:     newMsg[0] = '\0';
        !          1261:     encodeMessageColor(newMsg, 0, theColor);
        !          1262:     strcat(newMsg, theMsg);
        !          1263:
        !          1264:     if (strLenWithoutEscapes(combatText) + strLenWithoutEscapes(newMsg) + 3 > DCOLS) {
        !          1265:         // the "3" is for the semicolon, space and period that get added to conjoined combat texts.
        !          1266:         displayCombatText();
        !          1267:     }
        !          1268:
        !          1269:     if (combatText[0]) {
        !          1270:         strcat(combatText, "; ");
        !          1271:         strcat(combatText, newMsg);
        !          1272:     } else {
        !          1273:         strcpy(combatText, newMsg);
        !          1274:     }
        !          1275: }
        !          1276:
        !          1277: void displayCombatText() {
        !          1278:     char buf[COLS];
        !          1279:
        !          1280:     if (combatText[0]) {
        !          1281:         sprintf(buf, "%s.", combatText);
        !          1282:         combatText[0] = '\0';
        !          1283:         message(buf, rogue.cautiousMode);
        !          1284:         rogue.cautiousMode = false;
        !          1285:     }
        !          1286: }
        !          1287:
        !          1288: void flashMonster(creature *monst, const color *theColor, short strength) {
        !          1289:     if (!theColor) {
        !          1290:         return;
        !          1291:     }
        !          1292:     if (!(monst->bookkeepingFlags & MB_WILL_FLASH) || monst->flashStrength < strength) {
        !          1293:         monst->bookkeepingFlags |= MB_WILL_FLASH;
        !          1294:         monst->flashStrength = strength;
        !          1295:         monst->flashColor = *theColor;
        !          1296:         rogue.creaturesWillFlashThisTurn = true;
        !          1297:     }
        !          1298: }
        !          1299:
        !          1300: boolean canAbsorb(creature *ally, boolean ourBolts[NUMBER_BOLT_KINDS], creature *prey, short **grid) {
        !          1301:     short i;
        !          1302:
        !          1303:     if (ally->creatureState == MONSTER_ALLY
        !          1304:         && ally->newPowerCount > 0
        !          1305:         && (ally->targetCorpseLoc[0] <= 0)
        !          1306:         && !((ally->info.flags | prey->info.flags) & (MONST_INANIMATE | MONST_IMMOBILE))
        !          1307:         && !monsterAvoids(ally, prey->xLoc, prey->yLoc)
        !          1308:         && grid[ally->xLoc][ally->yLoc] <= 10) {
        !          1309:
        !          1310:         if (~(ally->info.abilityFlags) & prey->info.abilityFlags & LEARNABLE_ABILITIES) {
        !          1311:             return true;
        !          1312:         } else if (~(ally->info.flags) & prey->info.flags & LEARNABLE_BEHAVIORS) {
        !          1313:             return true;
        !          1314:         } else {
        !          1315:             for (i = 0; i < NUMBER_BOLT_KINDS; i++) {
        !          1316:                 ourBolts[i] = false;
        !          1317:             }
        !          1318:             for (i = 0; ally->info.bolts[i] != BOLT_NONE; i++) {
        !          1319:                 ourBolts[ally->info.bolts[i]] = true;
        !          1320:             }
        !          1321:
        !          1322:             for (i=0; prey->info.bolts[i] != BOLT_NONE; i++) {
        !          1323:                 if (!(boltCatalog[prey->info.bolts[i]].flags & BF_NOT_LEARNABLE)
        !          1324:                     && !ourBolts[prey->info.bolts[i]]) {
        !          1325:
        !          1326:                     return true;
        !          1327:                 }
        !          1328:             }
        !          1329:         }
        !          1330:     }
        !          1331:     return false;
        !          1332: }
        !          1333:
        !          1334: boolean anyoneWantABite(creature *decedent) {
        !          1335:     short candidates, randIndex, i;
        !          1336:     short **grid;
        !          1337:     creature *ally;
        !          1338:     boolean success = false;
        !          1339:     boolean ourBolts[NUMBER_BOLT_KINDS] = {false};
        !          1340:
        !          1341:     candidates = 0;
        !          1342:     if ((!(decedent->info.abilityFlags & LEARNABLE_ABILITIES)
        !          1343:          && !(decedent->info.flags & LEARNABLE_BEHAVIORS)
        !          1344:          && decedent->info.bolts[0] == BOLT_NONE)
        !          1345:         || (cellHasTerrainFlag(decedent->xLoc, decedent->yLoc, T_PATHING_BLOCKER))
        !          1346:         || decedent->info.monsterID == MK_SPECTRAL_IMAGE
        !          1347:         || (decedent->info.flags & (MONST_INANIMATE | MONST_IMMOBILE))) {
        !          1348:
        !          1349:         return false;
        !          1350:     }
        !          1351:
        !          1352:     grid = allocGrid();
        !          1353:     fillGrid(grid, 0);
        !          1354:     calculateDistances(grid, decedent->xLoc, decedent->yLoc, T_PATHING_BLOCKER, NULL, true, true);
        !          1355:     for (ally = monsters->nextCreature; ally != NULL; ally = ally->nextCreature) {
        !          1356:         if (canAbsorb(ally, ourBolts, decedent, grid)) {
        !          1357:             candidates++;
        !          1358:         }
        !          1359:     }
        !          1360:     if (candidates > 0) {
        !          1361:         randIndex = rand_range(1, candidates);
        !          1362:         for (ally = monsters->nextCreature; ally != NULL; ally = ally->nextCreature) {
        !          1363:             // CanAbsorb() populates ourBolts if it returns true and there are no learnable behaviors or flags:
        !          1364:             if (canAbsorb(ally, ourBolts, decedent, grid) && !--randIndex) {
        !          1365:                 break;
        !          1366:             }
        !          1367:         }
        !          1368:         if (ally) {
        !          1369:             ally->targetCorpseLoc[0] = decedent->xLoc;
        !          1370:             ally->targetCorpseLoc[1] = decedent->yLoc;
        !          1371:             strcpy(ally->targetCorpseName, decedent->info.monsterName);
        !          1372:             ally->corpseAbsorptionCounter = 20; // 20 turns to get there and start eating before he loses interest
        !          1373:
        !          1374:             // Choose a superpower.
        !          1375:             // First, select from among learnable ability or behavior flags, if one is available.
        !          1376:             candidates = 0;
        !          1377:             for (i=0; i<32; i++) {
        !          1378:                 if (Fl(i) & ~(ally->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) {
        !          1379:                     candidates++;
        !          1380:                 }
        !          1381:             }
        !          1382:             for (i=0; i<32; i++) {
        !          1383:                 if (Fl(i) & ~(ally->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) {
        !          1384:                     candidates++;
        !          1385:                 }
        !          1386:             }
        !          1387:             if (candidates > 0) {
        !          1388:                 randIndex = rand_range(1, candidates);
        !          1389:                 for (i=0; i<32; i++) {
        !          1390:                     if ((Fl(i) & ~(ally->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES)
        !          1391:                         && !--randIndex) {
        !          1392:
        !          1393:                         ally->absorptionFlags = Fl(i);
        !          1394:                         ally->absorbBehavior = false;
        !          1395:                         success = true;
        !          1396:                         break;
        !          1397:                     }
        !          1398:                 }
        !          1399:                 for (i=0; i<32 && !success; i++) {
        !          1400:                     if ((Fl(i) & ~(ally->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS)
        !          1401:                         && !--randIndex) {
        !          1402:
        !          1403:                         ally->absorptionFlags = Fl(i);
        !          1404:                         ally->absorbBehavior = true;
        !          1405:                         success = true;
        !          1406:                         break;
        !          1407:                     }
        !          1408:                 }
        !          1409:             } else if (decedent->info.bolts[0] != BOLT_NONE) {
        !          1410:                 // If there are no learnable ability or behavior flags, pick a learnable bolt.
        !          1411:                 candidates = 0;
        !          1412:                 for (i=0; decedent->info.bolts[i] != BOLT_NONE; i++) {
        !          1413:                     if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE)
        !          1414:                         && !ourBolts[decedent->info.bolts[i]]) {
        !          1415:
        !          1416:                         candidates++;
        !          1417:                     }
        !          1418:                 }
        !          1419:                 if (candidates > 0) {
        !          1420:                     randIndex = rand_range(1, candidates);
        !          1421:                     for (i=0; decedent->info.bolts[i] != BOLT_NONE; i++) {
        !          1422:                         if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE)
        !          1423:                             && !ourBolts[decedent->info.bolts[i]]
        !          1424:                             && !--randIndex) {
        !          1425:
        !          1426:                             ally->absorptionBolt = decedent->info.bolts[i];
        !          1427:                             success = true;
        !          1428:                             break;
        !          1429:                         }
        !          1430:                     }
        !          1431:                 }
        !          1432:             }
        !          1433:         }
        !          1434:     }
        !          1435:     freeGrid(grid);
        !          1436:     return success;
        !          1437: }
        !          1438:
        !          1439: #define MIN_FLASH_STRENGTH  50
        !          1440:
        !          1441: void inflictLethalDamage(creature *attacker, creature *defender) {
        !          1442:     inflictDamage(attacker, defender, defender->currentHP, NULL, true);
        !          1443: }
        !          1444:
        !          1445: // returns true if this was a killing stroke; does NOT free the pointer, but DOES remove it from the monster chain
        !          1446: // flashColor indicates the color that the damage will cause the creature to flash
        !          1447: boolean inflictDamage(creature *attacker, creature *defender,
        !          1448:                       short damage, const color *flashColor, boolean ignoresProtectionShield) {
        !          1449:
        !          1450:     boolean killed = false;
        !          1451:     dungeonFeature theBlood;
        !          1452:     short transferenceAmount;
        !          1453:
        !          1454:     if (damage == 0
        !          1455:         || (defender->info.flags & MONST_INVULNERABLE)) {
        !          1456:
        !          1457:         return false;
        !          1458:     }
        !          1459:
        !          1460:     if (!ignoresProtectionShield
        !          1461:         && defender->status[STATUS_SHIELDED]) {
        !          1462:
        !          1463:         if (defender->status[STATUS_SHIELDED] > damage * 10) {
        !          1464:             defender->status[STATUS_SHIELDED] -= damage * 10;
        !          1465:             damage = 0;
        !          1466:         } else {
        !          1467:             damage -= (defender->status[STATUS_SHIELDED] + 9) / 10;
        !          1468:             defender->status[STATUS_SHIELDED] = defender->maxStatus[STATUS_SHIELDED] = 0;
        !          1469:         }
        !          1470:     }
        !          1471:
        !          1472:     defender->bookkeepingFlags &= ~MB_ABSORBING; // Stop eating a corpse if you are getting hurt.
        !          1473:
        !          1474:     // bleed all over the place, proportionately to damage inflicted:
        !          1475:     if (damage > 0 && defender->info.bloodType) {
        !          1476:         theBlood = dungeonFeatureCatalog[defender->info.bloodType];
        !          1477:         theBlood.startProbability = (theBlood.startProbability * (15 + min(damage, defender->currentHP) * 3 / 2) / 100);
        !          1478:         if (theBlood.layer == GAS) {
        !          1479:             theBlood.startProbability *= 100;
        !          1480:         }
        !          1481:         spawnDungeonFeature(defender->xLoc, defender->yLoc, &theBlood, true, false);
        !          1482:     }
        !          1483:
        !          1484:     if (defender != &player && defender->creatureState == MONSTER_SLEEPING) {
        !          1485:         wakeUp(defender);
        !          1486:     }
        !          1487:
        !          1488:     if (defender == &player
        !          1489:         && rogue.easyMode
        !          1490:         && damage > 0) {
        !          1491:         damage = max(1, damage/5);
        !          1492:     }
        !          1493:
        !          1494:     if (((attacker == &player && rogue.transference) || (attacker && attacker != &player && (attacker->info.abilityFlags & MA_TRANSFERENCE)))
        !          1495:         && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
        !          1496:
        !          1497:         transferenceAmount = min(damage, defender->currentHP); // Maximum transferred damage can't exceed the victim's remaining health.
        !          1498:
        !          1499:         if (attacker == &player) {
        !          1500:             transferenceAmount = transferenceAmount * rogue.transference / 20;
        !          1501:             if (transferenceAmount == 0) {
        !          1502:                 transferenceAmount = ((rogue.transference > 0) ? 1 : -1);
        !          1503:             }
        !          1504:         } else if (attacker->creatureState == MONSTER_ALLY) {
        !          1505:             transferenceAmount = transferenceAmount * 4 / 10; // allies get 40% recovery rate
        !          1506:         } else {
        !          1507:             transferenceAmount = transferenceAmount * 9 / 10; // enemies get 90% recovery rate, deal with it
        !          1508:         }
        !          1509:
        !          1510:         attacker->currentHP += transferenceAmount;
        !          1511:
        !          1512:         if (attacker == &player && player.currentHP <= 0) {
        !          1513:             gameOver("Drained by a cursed ring", true);
        !          1514:             return false;
        !          1515:         }
        !          1516:     }
        !          1517:
        !          1518:     if (defender->currentHP <= damage) { // killed
        !          1519:         killCreature(defender, false);
        !          1520:         anyoneWantABite(defender);
        !          1521:         killed = true;
        !          1522:     } else { // survived
        !          1523:         if (damage < 0 && defender->currentHP - damage > defender->info.maxHP) {
        !          1524:             defender->currentHP = max(defender->currentHP, defender->info.maxHP);
        !          1525:         } else {
        !          1526:             defender->currentHP -= damage; // inflict the damage!
        !          1527:             if (defender == &player && damage > 0) {
        !          1528:                 rogue.featRecord[FEAT_INDOMITABLE] = false;
        !          1529:             }
        !          1530:         }
        !          1531:
        !          1532:         if (defender != &player && defender->creatureState != MONSTER_ALLY
        !          1533:             && defender->info.flags & MONST_FLEES_NEAR_DEATH
        !          1534:             && defender->info.maxHP / 4 >= defender->currentHP) {
        !          1535:
        !          1536:             defender->creatureState = MONSTER_FLEEING;
        !          1537:         }
        !          1538:         if (flashColor && damage > 0) {
        !          1539:             flashMonster(defender, flashColor, MIN_FLASH_STRENGTH + (100 - MIN_FLASH_STRENGTH) * damage / defender->info.maxHP);
        !          1540:         }
        !          1541:     }
        !          1542:
        !          1543:     refreshSideBar(-1, -1, false);
        !          1544:     return killed;
        !          1545: }
        !          1546:
        !          1547: void addPoison(creature *monst, short durationIncrement, short concentrationIncrement) {
        !          1548:     extern const color poisonColor;
        !          1549:     if (durationIncrement > 0) {
        !          1550:         if (monst == &player && !player.status[STATUS_POISONED]) {
        !          1551:             combatMessage("scalding poison fills your veins", &badMessageColor);
        !          1552:         }
        !          1553:         if (!monst->status[STATUS_POISONED]) {
        !          1554:             monst->maxStatus[STATUS_POISONED] = 0;
        !          1555:         }
        !          1556:         monst->poisonAmount += concentrationIncrement;
        !          1557:         if (monst->poisonAmount == 0) {
        !          1558:             monst->poisonAmount = 1;
        !          1559:         }
        !          1560:         monst->status[STATUS_POISONED] += durationIncrement;
        !          1561:         monst->maxStatus[STATUS_POISONED] = monst->info.maxHP / monst->poisonAmount;
        !          1562:
        !          1563:         if (canSeeMonster(monst)) {
        !          1564:             flashMonster(monst, &poisonColor, 100);
        !          1565:         }
        !          1566:     }
        !          1567: }
        !          1568:
        !          1569:
        !          1570: // Removes the decedent from the screen and from the monster chain; inserts it into the graveyard chain; does NOT free the memory.
        !          1571: // Or, if the decedent is a player ally at the moment of death, insert it into the purgatory chain for possible future resurrection.
        !          1572: // Use "administrativeDeath" if the monster is being deleted for administrative purposes, as opposed to dying as a result of physical actions.
        !          1573: // AdministrativeDeath means the monster simply disappears, with no messages, dropped item, DFs or other effect.
        !          1574: void killCreature(creature *decedent, boolean administrativeDeath) {
        !          1575:     short x, y;
        !          1576:     char monstName[DCOLS], buf[DCOLS * 3];
        !          1577:
        !          1578:     if (decedent->bookkeepingFlags & MB_IS_DYING) {
        !          1579:         // monster has already been killed; let's avoid overkill
        !          1580:         return;
        !          1581:     }
        !          1582:
        !          1583:     if (decedent != &player) {
        !          1584:         decedent->bookkeepingFlags |= MB_IS_DYING;
        !          1585:     }
        !          1586:
        !          1587:     if (rogue.lastTarget == decedent) {
        !          1588:         rogue.lastTarget = NULL;
        !          1589:     }
        !          1590:     if (rogue.yendorWarden == decedent) {
        !          1591:         rogue.yendorWarden = NULL;
        !          1592:     }
        !          1593:
        !          1594:     if (decedent->carriedItem) {
        !          1595:         if (administrativeDeath) {
        !          1596:             deleteItem(decedent->carriedItem);
        !          1597:             decedent->carriedItem = NULL;
        !          1598:         } else {
        !          1599:             makeMonsterDropItem(decedent);
        !          1600:         }
        !          1601:     }
        !          1602:
        !          1603:     if (!administrativeDeath && (decedent->info.abilityFlags & MA_DF_ON_DEATH)
        !          1604:         && ((rogue.patchVersion < 3) || !(decedent->bookkeepingFlags & MB_IS_FALLING))) {
        !          1605:         spawnDungeonFeature(decedent->xLoc, decedent->yLoc, &dungeonFeatureCatalog[decedent->info.DFType], true, false);
        !          1606:
        !          1607:         if (monsterText[decedent->info.monsterID].DFMessage[0] && canSeeMonster(decedent)) {
        !          1608:             monsterName(monstName, decedent, true);
        !          1609:             snprintf(buf, DCOLS * 3, "%s %s", monstName, monsterText[decedent->info.monsterID].DFMessage);
        !          1610:             resolvePronounEscapes(buf, decedent);
        !          1611:             message(buf, false);
        !          1612:         }
        !          1613:     }
        !          1614:
        !          1615:     if (decedent == &player) { // the player died
        !          1616:         // game over handled elsewhere
        !          1617:     } else {
        !          1618:         if (!administrativeDeath
        !          1619:             && decedent->creatureState == MONSTER_ALLY
        !          1620:             && !canSeeMonster(decedent)
        !          1621:             && !(decedent->info.flags & MONST_INANIMATE)
        !          1622:             && !(decedent->bookkeepingFlags & MB_BOUND_TO_LEADER)
        !          1623:             && !decedent->carriedMonster) {
        !          1624:
        !          1625:             messageWithColor("you feel a sense of loss.", &badMessageColor, false);
        !          1626:         }
        !          1627:         x = decedent->xLoc;
        !          1628:         y = decedent->yLoc;
        !          1629:         if (decedent->bookkeepingFlags & MB_IS_DORMANT) {
        !          1630:             pmap[x][y].flags &= ~HAS_DORMANT_MONSTER;
        !          1631:         } else {
        !          1632:             pmap[x][y].flags &= ~HAS_MONSTER;
        !          1633:         }
        !          1634:         removeMonsterFromChain(decedent, dormantMonsters);
        !          1635:         removeMonsterFromChain(decedent, monsters);
        !          1636:
        !          1637:         if (decedent->leader == &player
        !          1638:             && !(decedent->info.flags & MONST_INANIMATE)
        !          1639:             && (decedent->bookkeepingFlags & MB_HAS_SOUL)
        !          1640:             && !administrativeDeath) {
        !          1641:
        !          1642:             decedent->nextCreature = purgatory->nextCreature;
        !          1643:             purgatory->nextCreature = decedent;
        !          1644:         } else {
        !          1645:             decedent->nextCreature = graveyard->nextCreature;
        !          1646:             graveyard->nextCreature = decedent;
        !          1647:         }
        !          1648:
        !          1649:         if (!administrativeDeath && !(decedent->bookkeepingFlags & MB_IS_DORMANT)) {
        !          1650:             // Was there another monster inside?
        !          1651:             if (decedent->carriedMonster) {
        !          1652:                 // Insert it into the chain.
        !          1653:                 decedent->carriedMonster->nextCreature = monsters->nextCreature;
        !          1654:                 monsters->nextCreature = decedent->carriedMonster;
        !          1655:                 decedent->carriedMonster->xLoc = x;
        !          1656:                 decedent->carriedMonster->yLoc = y;
        !          1657:                 decedent->carriedMonster->ticksUntilTurn = 200;
        !          1658:                 pmap[x][y].flags |= HAS_MONSTER;
        !          1659:                 fadeInMonster(decedent->carriedMonster);
        !          1660:
        !          1661:                 if (canSeeMonster(decedent->carriedMonster)) {
        !          1662:                     monsterName(monstName, decedent->carriedMonster, true);
        !          1663:                     sprintf(buf, "%s appears", monstName);
        !          1664:                     combatMessage(buf, NULL);
        !          1665:                 }
        !          1666:
        !          1667:                 applyInstantTileEffectsToCreature(decedent->carriedMonster);
        !          1668:                 decedent->carriedMonster = NULL;
        !          1669:             }
        !          1670:             refreshDungeonCell(x, y);
        !          1671:         }
        !          1672:     }
        !          1673:     decedent->currentHP = 0;
        !          1674:     demoteMonsterFromLeadership(decedent);
        !          1675:     if (decedent->leader) {
        !          1676:         checkForContinuedLeadership(decedent->leader);
        !          1677:     }
        !          1678: }
        !          1679:
        !          1680: void buildHitList(creature **hitList, const creature *attacker, creature *defender, const boolean sweep) {
        !          1681:     short i, x, y, newX, newY, newestX, newestY;
        !          1682:     enum directions dir, newDir;
        !          1683:
        !          1684:     x = attacker->xLoc;
        !          1685:     y = attacker->yLoc;
        !          1686:     newX = defender->xLoc;
        !          1687:     newY = defender->yLoc;
        !          1688:
        !          1689:     dir = NO_DIRECTION;
        !          1690:     for (i = 0; i < DIRECTION_COUNT; i++) {
        !          1691:         if (nbDirs[i][0] == newX - x
        !          1692:             && nbDirs[i][1] == newY - y) {
        !          1693:
        !          1694:             dir = i;
        !          1695:             break;
        !          1696:         }
        !          1697:     }
        !          1698:
        !          1699:     if (sweep) {
        !          1700:         if (dir == NO_DIRECTION) {
        !          1701:             dir = UP; // Just pick one.
        !          1702:         }
        !          1703:         for (i=0; i<8; i++) {
        !          1704:             newDir = (dir + i) % DIRECTION_COUNT;
        !          1705:             newestX = x + cDirs[newDir][0];
        !          1706:             newestY = y + cDirs[newDir][1];
        !          1707:             if (coordinatesAreInMap(newestX, newestY) && (pmap[newestX][newestY].flags & (HAS_MONSTER | HAS_PLAYER))) {
        !          1708:                 defender = monsterAtLoc(newestX, newestY);
        !          1709:                 if (defender
        !          1710:                     && monsterWillAttackTarget(attacker, defender)
        !          1711:                     && (!cellHasTerrainFlag(defender->xLoc, defender->yLoc, T_OBSTRUCTS_PASSABILITY) || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))) {
        !          1712:
        !          1713:                     hitList[i] = defender;
        !          1714:                 }
        !          1715:             }
        !          1716:         }
        !          1717:     } else {
        !          1718:         hitList[0] = defender;
        !          1719:     }
        !          1720: }

CVSweb