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