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