[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

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