1 /*
2  *  Items.c
3  *  Brogue
4  *
5  *  Created by Brian Walker on 1/17/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 
25 #include "Rogue.h"
26 #include "IncludeGlobals.h"
27 
initializeItem()28 item *initializeItem() {
29     short i;
30     item *theItem;
31 
32     theItem = (item *) malloc(sizeof(item));
33     memset(theItem, '\0', sizeof(item) );
34 
35     theItem->category = 0;
36     theItem->kind = 0;
37     theItem->flags = 0;
38     theItem->displayChar = '&';
39     theItem->foreColor = &itemColor;
40     theItem->inventoryColor = &white;
41     theItem->inventoryLetter = '\0';
42     theItem->armor = 0;
43     theItem->strengthRequired = 0;
44     theItem->enchant1 = 0;
45     theItem->enchant2 = 0;
46     theItem->timesEnchanted = 0;
47     theItem->vorpalEnemy = 0;
48     theItem->charges = 0;
49     theItem->quantity = 1;
50     theItem->quiverNumber = 0;
51     theItem->originDepth = 0;
52     theItem->inscription[0] = '\0';
53     theItem->lastUsed[0] = 0;
54     theItem->lastUsed[1] = 0;
55     theItem->lastUsed[2] = 0;
56     theItem->nextItem = NULL;
57 
58     for (i=0; i < KEY_ID_MAXIMUM; i++) {
59         theItem->keyLoc[i].x = 0;
60         theItem->keyLoc[i].y = 0;
61         theItem->keyLoc[i].machine = 0;
62         theItem->keyLoc[i].disposableHere = false;
63     }
64     return theItem;
65 }
66 
67 // Allocates space, generates a specified item (or random category/kind if -1)
68 // and returns a pointer to that item. The item is not given a location here
69 // and is not inserted into the item chain!
generateItem(unsigned short theCategory,short theKind)70 item *generateItem(unsigned short theCategory, short theKind) {
71     item *theItem = initializeItem();
72     makeItemInto(theItem, theCategory, theKind);
73     return theItem;
74 }
75 
pickItemCategory(unsigned long theCategory)76 unsigned long pickItemCategory(unsigned long theCategory) {
77     short i, sum, randIndex;
78     short probabilities[13] =                       {50,    42,     52,     3,      3,      10,     8,      2,      3,      2,        0,        0,      0};
79     unsigned short correspondingCategories[13] =    {GOLD,  SCROLL, POTION, STAFF,  WAND,   WEAPON, ARMOR,  FOOD,   RING,   CHARM,    AMULET,   GEM,    KEY};
80 
81     sum = 0;
82 
83     for (i=0; i<13; i++) {
84         if (theCategory <= 0 || theCategory & correspondingCategories[i]) {
85             sum += probabilities[i];
86         }
87     }
88 
89     if (sum == 0) {
90         return theCategory; // e.g. when you pass in AMULET or GEM, since they have no frequency
91     }
92 
93     randIndex = rand_range(1, sum);
94 
95     for (i=0; ; i++) {
96         if (theCategory <= 0 || theCategory & correspondingCategories[i]) {
97             if (randIndex <= probabilities[i]) {
98                 return correspondingCategories[i];
99             }
100             randIndex -= probabilities[i];
101         }
102     }
103 }
104 
105 // Sets an item to the given type and category (or chooses randomly if -1) with all other stats
makeItemInto(item * theItem,unsigned long itemCategory,short itemKind)106 item *makeItemInto(item *theItem, unsigned long itemCategory, short itemKind) {
107     itemTable *theEntry = NULL;
108 
109     if (itemCategory <= 0) {
110         itemCategory = ALL_ITEMS;
111     }
112 
113     itemCategory = pickItemCategory(itemCategory);
114 
115     theItem->category = itemCategory;
116 
117     switch (itemCategory) {
118 
119         case FOOD:
120             if (itemKind < 0) {
121                 itemKind = chooseKind(foodTable, NUMBER_FOOD_KINDS);
122             }
123             theEntry = &foodTable[itemKind];
124             theItem->displayChar = G_FOOD;
125             theItem->flags |= ITEM_IDENTIFIED;
126             break;
127 
128         case WEAPON:
129             if (itemKind < 0) {
130                 itemKind = chooseKind(weaponTable, NUMBER_WEAPON_KINDS);
131             }
132             theEntry = &weaponTable[itemKind];
133             theItem->damage = weaponTable[itemKind].range;
134             theItem->strengthRequired = weaponTable[itemKind].strengthRequired;
135             theItem->displayChar = G_WEAPON;
136 
137             switch (itemKind) {
138                 case DAGGER:
139                     theItem->flags |= ITEM_SNEAK_ATTACK_BONUS;
140                     break;
141                 case MACE:
142                 case HAMMER:
143                     theItem->flags |= ITEM_ATTACKS_STAGGER;
144                     break;
145                 case WHIP:
146                     theItem->flags |= ITEM_ATTACKS_EXTEND;
147                     break;
148                 case RAPIER:
149                     theItem->flags |= (ITEM_ATTACKS_QUICKLY | ITEM_LUNGE_ATTACKS);
150                     break;
151                 case FLAIL:
152                     theItem->flags |= ITEM_PASS_ATTACKS;
153                     break;
154                 case SPEAR:
155                 case PIKE:
156                     theItem->flags |= ITEM_ATTACKS_PENETRATE;
157                     break;
158                 case AXE:
159                 case WAR_AXE:
160                     theItem->flags |= ITEM_ATTACKS_ALL_ADJACENT;
161                     break;
162                 default:
163                     break;
164             }
165 
166             if (rand_percent(40)) {
167                 theItem->enchant1 += rand_range(1, 3);
168                 if (rand_percent(50)) {
169                     // cursed
170                     theItem->enchant1 *= -1;
171                     theItem->flags |= ITEM_CURSED;
172                     if (rand_percent(33)) { // give it a bad runic
173                         theItem->enchant2 = rand_range(NUMBER_GOOD_WEAPON_ENCHANT_KINDS, NUMBER_WEAPON_RUNIC_KINDS - 1);
174                         theItem->flags |= ITEM_RUNIC;
175                     }
176                 } else if (rand_range(3, 10)
177                            * ((theItem->flags & ITEM_ATTACKS_STAGGER) ? 2 : 1)
178                            / ((theItem->flags & ITEM_ATTACKS_QUICKLY) ? 2 : 1)
179                            / ((theItem->flags & ITEM_ATTACKS_EXTEND) ? 2 : 1)
180                            > theItem->damage.lowerBound) {
181                     // give it a good runic; lower damage items are more likely to be runic
182                     theItem->enchant2 = rand_range(0, NUMBER_GOOD_WEAPON_ENCHANT_KINDS - 1);
183                     theItem->flags |= ITEM_RUNIC;
184                     if (theItem->enchant2 == W_SLAYING) {
185                         theItem->vorpalEnemy = chooseVorpalEnemy();
186                     }
187                 } else {
188                     while (rand_percent(10)) {
189                         theItem->enchant1++;
190                     }
191                 }
192             }
193             if (itemKind == DART || itemKind == INCENDIARY_DART || itemKind == JAVELIN) {
194                 if (itemKind == INCENDIARY_DART) {
195                     theItem->quantity = rand_range(3, 6);
196                 } else {
197                     theItem->quantity = rand_range(5, 18);
198                 }
199                 theItem->quiverNumber = rand_range(1, 60000);
200                 theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC); // throwing weapons can't be cursed or runic
201                 theItem->enchant1 = 0; // throwing weapons can't be magical
202             }
203             theItem->charges = WEAPON_KILLS_TO_AUTO_ID; // kill 20 enemies to auto-identify
204             break;
205 
206         case ARMOR:
207             if (itemKind < 0) {
208                 itemKind = chooseKind(armorTable, NUMBER_ARMOR_KINDS);
209             }
210             theEntry = &armorTable[itemKind];
211             theItem->armor = randClump(armorTable[itemKind].range);
212             theItem->strengthRequired = armorTable[itemKind].strengthRequired;
213             theItem->displayChar = G_ARMOR;
214             theItem->charges = ARMOR_DELAY_TO_AUTO_ID; // this many turns until it reveals its enchants and whether runic
215             if (rand_percent(40)) {
216                 theItem->enchant1 += rand_range(1, 3);
217                 if (rand_percent(50)) {
218                     // cursed
219                     theItem->enchant1 *= -1;
220                     theItem->flags |= ITEM_CURSED;
221                     if (rand_percent(33)) { // give it a bad runic
222                         theItem->enchant2 = rand_range(NUMBER_GOOD_ARMOR_ENCHANT_KINDS, NUMBER_ARMOR_ENCHANT_KINDS - 1);
223                         theItem->flags |= ITEM_RUNIC;
224                     }
225                 } else if (rand_range(0, 95) > theItem->armor) { // give it a good runic
226                     theItem->enchant2 = rand_range(0, NUMBER_GOOD_ARMOR_ENCHANT_KINDS - 1);
227                     theItem->flags |= ITEM_RUNIC;
228                     if (theItem->enchant2 == A_IMMUNITY) {
229                         theItem->vorpalEnemy = chooseVorpalEnemy();
230                     }
231                 } else {
232                     while (rand_percent(10)) {
233                         theItem->enchant1++;
234                     }
235                 }
236             }
237             break;
238         case SCROLL:
239             if (itemKind < 0) {
240                 itemKind = chooseKind(scrollTable, NUMBER_SCROLL_KINDS);
241             }
242             theEntry = &scrollTable[itemKind];
243             theItem->displayChar = G_SCROLL;
244             theItem->flags |= ITEM_FLAMMABLE;
245             break;
246         case POTION:
247             if (itemKind < 0) {
248                 itemKind = chooseKind(potionTable, NUMBER_POTION_KINDS);
249             }
250             theEntry = &potionTable[itemKind];
251             theItem->displayChar = G_POTION;
252             break;
253         case STAFF:
254             if (itemKind < 0) {
255                 itemKind = chooseKind(staffTable, NUMBER_STAFF_KINDS);
256             }
257             theEntry = &staffTable[itemKind];
258             theItem->displayChar = G_STAFF;
259             theItem->charges = 2;
260             if (rand_percent(50)) {
261                 theItem->charges++;
262                 if (rand_percent(15)) {
263                     theItem->charges++;
264                     while (rand_percent(10)) {
265                         theItem->charges++;
266                     }
267                 }
268             }
269             theItem->enchant1 = theItem->charges;
270             theItem->enchant2 = (itemKind == STAFF_BLINKING || itemKind == STAFF_OBSTRUCTION ? 1000 : 500); // start with no recharging mojo
271             break;
272         case WAND:
273             if (itemKind < 0) {
274                 itemKind = chooseKind(wandTable, NUMBER_WAND_KINDS);
275             }
276             theEntry = &wandTable[itemKind];
277             theItem->displayChar = G_WAND;
278             theItem->charges = randClump(wandTable[itemKind].range);
279             break;
280         case RING:
281             if (itemKind < 0) {
282                 itemKind = chooseKind(ringTable, NUMBER_RING_KINDS);
283             }
284             theEntry = &ringTable[itemKind];
285             theItem->displayChar = G_RING;
286             theItem->enchant1 = randClump(ringTable[itemKind].range);
287             theItem->charges = RING_DELAY_TO_AUTO_ID; // how many turns of being worn until it auto-identifies
288             if (rand_percent(16)) {
289                 // cursed
290                 theItem->enchant1 *= -1;
291                 theItem->flags |= ITEM_CURSED;
292             } else {
293                 while (rand_percent(10)) {
294                     theItem->enchant1++;
295                 }
296             }
297             break;
298         case CHARM:
299             if (itemKind < 0) {
300                 itemKind = chooseKind(charmTable, NUMBER_CHARM_KINDS);
301             }
302             theItem->displayChar = G_CHARM;
303             theItem->charges = 0; // Charms are initially ready for use.
304             theItem->enchant1 = randClump(charmTable[itemKind].range);
305             while (rand_percent(7)) {
306                 theItem->enchant1++;
307             }
308             theItem->flags |= ITEM_IDENTIFIED;
309             break;
310         case GOLD:
311             theEntry = NULL;
312             theItem->displayChar = G_GOLD;
313             theItem->quantity = rand_range(50 + rogue.depthLevel * 10, 100 + rogue.depthLevel * 15);
314             break;
315         case AMULET:
316             theEntry = NULL;
317             theItem->displayChar = G_AMULET;
318             itemKind = 0;
319             theItem->flags |= ITEM_IDENTIFIED;
320             break;
321         case GEM:
322             theEntry = NULL;
323             theItem->displayChar = G_GEM;
324             itemKind = 0;
325             theItem->flags |= ITEM_IDENTIFIED;
326             break;
327         case KEY:
328             theEntry = NULL;
329             theItem->displayChar = G_KEY;
330             theItem->flags |= ITEM_IDENTIFIED;
331             break;
332         default:
333             theEntry = NULL;
334             message("something has gone terribly wrong!", REQUIRE_ACKNOWLEDGMENT);
335             break;
336     }
337     if (theItem
338         && !(theItem->flags & ITEM_IDENTIFIED)
339         && (!(theItem->category & (POTION | SCROLL) ) || (theEntry && !theEntry->identified))) {
340 
341         theItem->flags |= ITEM_CAN_BE_IDENTIFIED;
342     }
343     theItem->kind = itemKind;
344 
345     return theItem;
346 }
347 
chooseKind(itemTable * theTable,short numKinds)348 short chooseKind(itemTable *theTable, short numKinds) {
349     short i, totalFrequencies = 0, randomFrequency;
350     for (i=0; i<numKinds; i++) {
351         totalFrequencies += max(0, theTable[i].frequency);
352     }
353     randomFrequency = rand_range(1, totalFrequencies);
354     for (i=0; randomFrequency > theTable[i].frequency; i++) {
355         randomFrequency -= max(0, theTable[i].frequency);
356     }
357     return i;
358 }
359 
360 // Places an item at (x,y) if provided or else a random location if they're 0. Inserts item into the floor list.
placeItem(item * theItem,short x,short y)361 item *placeItem(item *theItem, short x, short y) {
362     short loc[2];
363     enum dungeonLayers layer;
364     char theItemName[DCOLS], buf[DCOLS];
365     if (x <= 0 || y <= 0) {
366         randomMatchingLocation(&(loc[0]), &(loc[1]), FLOOR, NOTHING, -1);
367         theItem->xLoc = loc[0];
368         theItem->yLoc = loc[1];
369     } else {
370         theItem->xLoc = x;
371         theItem->yLoc = y;
372     }
373 
374     removeItemFromChain(theItem, floorItems); // just in case; double-placing an item will result in game-crashing loops in the item list
375     addItemToChain(theItem, floorItems);
376     pmap[theItem->xLoc][theItem->yLoc].flags |= HAS_ITEM;
377     if ((theItem->flags & ITEM_MAGIC_DETECTED) && itemMagicPolarity(theItem)) {
378         pmap[theItem->xLoc][theItem->yLoc].flags |= ITEM_DETECTED;
379     }
380     if (cellHasTerrainFlag(x, y, T_IS_DF_TRAP)
381         && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS)
382         && !(pmap[x][y].flags & PRESSURE_PLATE_DEPRESSED)) {
383 
384         pmap[x][y].flags |= PRESSURE_PLATE_DEPRESSED;
385         if (playerCanSee(x, y)) {
386             if (cellHasTMFlag(x, y, TM_IS_SECRET)) {
387                 discover(x, y);
388                 refreshDungeonCell(x, y);
389             }
390             itemName(theItem, theItemName, false, false, NULL);
391             sprintf(buf, "a pressure plate clicks underneath the %s!", theItemName);
392             message(buf, REQUIRE_ACKNOWLEDGMENT);
393         }
394         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
395             if (tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_DF_TRAP) {
396                 spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[tileCatalog[pmap[x][y].layers[layer]].fireType]), true, false);
397                 promoteTile(x, y, layer, false);
398             }
399         }
400     }
401     return theItem;
402 }
403 
fillItemSpawnHeatMap(unsigned short heatMap[DCOLS][DROWS],unsigned short heatLevel,short x,short y)404 void fillItemSpawnHeatMap(unsigned short heatMap[DCOLS][DROWS], unsigned short heatLevel, short x, short y) {
405     enum directions dir;
406     short newX, newY;
407 
408     if (pmap[x][y].layers[DUNGEON] == DOOR) {
409         heatLevel += 10;
410     } else if (pmap[x][y].layers[DUNGEON] == SECRET_DOOR) {
411         heatLevel += 3000;
412     }
413     if (heatMap[x][y] > heatLevel) {
414         heatMap[x][y] = heatLevel;
415     }
416     for (dir = 0; dir < 4; dir++) {
417         newX = x + nbDirs[dir][0];
418         newY = y + nbDirs[dir][1];
419         if (coordinatesAreInMap(newX, newY)
420             && !cellHasTerrainFlag(newX, newY, T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT)
421             && isPassableOrSecretDoor(newX, newY)
422             && heatLevel < heatMap[newX][newY]) {
423 
424             fillItemSpawnHeatMap(heatMap, heatLevel, newX, newY);
425         }
426     }
427 }
428 
coolHeatMapAt(unsigned short heatMap[DCOLS][DROWS],short x,short y,unsigned long * totalHeat)429 void coolHeatMapAt(unsigned short heatMap[DCOLS][DROWS], short x, short y, unsigned long *totalHeat) {
430     short k, l;
431     unsigned short currentHeat;
432 
433     currentHeat = heatMap[x][y];
434     if (currentHeat == 0) {
435         return;
436     }
437     *totalHeat -= heatMap[x][y];
438     heatMap[x][y] = 0;
439 
440     // lower the heat near the chosen location
441     for (k = -5; k <= 5; k++) {
442         for (l = -5; l <= 5; l++) {
443             if (coordinatesAreInMap(x+k, y+l) && heatMap[x+k][y+l] == currentHeat) {
444                 heatMap[x+k][y+l] = max(1, heatMap[x+k][y+l]/10);
445                 *totalHeat -= (currentHeat - heatMap[x+k][y+l]);
446             }
447         }
448     }
449 }
450 
451 // Returns false if no place could be found.
452 // That should happen only if the total heat is zero.
getItemSpawnLoc(unsigned short heatMap[DCOLS][DROWS],short * x,short * y,unsigned long * totalHeat)453 boolean getItemSpawnLoc(unsigned short heatMap[DCOLS][DROWS], short *x, short *y, unsigned long *totalHeat) {
454     unsigned long randIndex;
455     unsigned short currentHeat;
456     short i, j;
457 
458     if (*totalHeat <= 0) {
459         return false;
460     }
461 
462     randIndex = rand_range(1, *totalHeat);
463 
464     //printf("\nrandIndex: %i", randIndex);
465 
466     for (i=0; i<DCOLS; i++) {
467         for (j=0; j<DROWS; j++) {
468             currentHeat = heatMap[i][j];
469             if (randIndex <= currentHeat) { // this is the spot!
470                 *x = i;
471                 *y = j;
472                 return true;
473             }
474             randIndex -= currentHeat;
475         }
476     }
477     brogueAssert(0); // should never get here!
478     return false;
479 }
480 
481 // Generates and places items for the level. Must pass the location of the up-stairway on the level.
populateItems(short upstairsX,short upstairsY)482 void populateItems(short upstairsX, short upstairsY) {
483     if (!ITEMS_ENABLED) {
484         return;
485     }
486     item *theItem;
487     unsigned short itemSpawnHeatMap[DCOLS][DROWS];
488     short i, j, numberOfItems, numberOfGoldPiles, goldBonusProbability, x = 0, y = 0;
489     unsigned long totalHeat;
490     short theCategory, theKind, randomDepthOffset = 0;
491 
492     const int POW_GOLD[] = {
493         // b^3.05, with b from 0 to 25:
494         0, 1, 8, 28, 68, 135, 236, 378, 568, 813, 1122, 1500, 1956, 2497, 3131,
495         3864, 4705, 5660, 6738, 7946, 9292, 10783, 12427, 14232, 16204, 18353};
496 #define aggregateGoldLowerBound(d)  (POW_GOLD[d] + 320 * (d))
497 #define aggregateGoldUpperBound(d)  (POW_GOLD[d] + 420 * (d))
498     const fixpt POW_FOOD[] = {
499         // b^1.35 fixed point, with b from 1 to 50 (for future-proofing):
500         65536, 167059, 288797, 425854, 575558, 736180, 906488, 1085553, 1272645,
501         1467168, 1668630, 1876612, 2090756, 2310749, 2536314, 2767208, 3003211,
502         3244126, 3489773, 3739989, 3994624, 4253540, 4516609, 4783712, 5054741,
503         5329591, 5608167, 5890379, 6176141, 6465373, 6758000, 7053950, 7353155,
504         7655551, 7961076, 8269672, 8581283, 8895856, 9213341, 9533687, 9856849,
505         10182782, 10511443, 10842789, 11176783, 11513384, 11852556, 12194264,
506         12538472, 12885148};
507 
508 #ifdef AUDIT_RNG
509     char RNGmessage[100];
510 #endif
511 
512     if (rogue.depthLevel > AMULET_LEVEL) {
513         if (rogue.depthLevel - AMULET_LEVEL - 1 >= 8) {
514             numberOfItems = 1;
515         } else {
516             const short lumenstoneDistribution[8] = {3, 3, 3, 2, 2, 2, 2, 2};
517             numberOfItems = lumenstoneDistribution[rogue.depthLevel - AMULET_LEVEL - 1];
518         }
519         numberOfGoldPiles = 0;
520     } else {
521         rogue.lifePotionFrequency += 34;
522         rogue.strengthPotionFrequency += 17;
523         rogue.enchantScrollFrequency += 30;
524         numberOfItems = 3;
525         while (rand_percent(60)) {
526             numberOfItems++;
527         }
528         if (rogue.depthLevel <= 2) {
529             numberOfItems += 2; // 4 extra items to kickstart your career as a rogue
530         } else if (rogue.depthLevel <= 4) {
531             numberOfItems++; // and 2 more here
532         }
533 
534         numberOfGoldPiles = min(5, rogue.depthLevel / 4);
535         for (goldBonusProbability = 60;
536              rand_percent(goldBonusProbability) && numberOfGoldPiles <= 10;
537              goldBonusProbability -= 15) {
538 
539             numberOfGoldPiles++;
540         }
541         // Adjust the amount of gold if we're past depth 5 and we were below or above
542         // the production schedule as of the previous depth.
543         if (rogue.depthLevel > 5) {
544             if (rogue.goldGenerated < aggregateGoldLowerBound(rogue.depthLevel - 1)) {
545                 numberOfGoldPiles += 2;
546             } else if (rogue.goldGenerated > aggregateGoldUpperBound(rogue.depthLevel - 1)) {
547                 numberOfGoldPiles -= 2;
548             }
549         }
550     }
551 
552     // Create an item spawn heat map to bias item generation behind secret doors (and, to a lesser
553     // extent, regular doors). This is in terms of the number of secret/regular doors that must be
554     // passed to reach the area when pathing to it from the upward staircase.
555     // This is why there are often several items in well hidden secret rooms. Otherwise,
556     // those rooms are usually empty, which is demoralizing after you take the trouble to find them.
557     for (i=0; i<DCOLS; i++) {
558         for (j=0; j<DROWS; j++) {
559             itemSpawnHeatMap[i][j] = 50000;
560         }
561     }
562     fillItemSpawnHeatMap(itemSpawnHeatMap, 5, upstairsX, upstairsY);
563     totalHeat = 0;
564 
565 #ifdef AUDIT_RNG
566     sprintf(RNGmessage, "\n\nInitial heat map for level %i:\n", rogue.currentTurnNumber);
567     RNGLog(RNGmessage);
568 #endif
569 
570     for (j=0; j<DROWS; j++) {
571         for (i=0; i<DCOLS; i++) {
572             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_ITEMS | T_PATHING_BLOCKER)
573                 || (pmap[i][j].flags & (IS_CHOKEPOINT | IN_LOOP | IS_IN_MACHINE))
574                 || passableArcCount(i, j) > 1) { // Not in walls, hallways, quest rooms, loops or chokepoints, please.
575 
576                 itemSpawnHeatMap[i][j] = 0;
577             } else if (itemSpawnHeatMap[i][j] == 50000) {
578                 itemSpawnHeatMap[i][j] = 0;
579                 pmap[i][j].layers[DUNGEON] = WALL; // due to a bug that created occasional isolated one-cell islands;
580                                                    // not sure if it's still around, but this is a good-enough failsafe
581             }
582 #ifdef AUDIT_RNG
583             sprintf(RNGmessage, "%u%s%s\t%s",
584                     itemSpawnHeatMap[i][j],
585                     ((pmap[i][j].flags & IS_CHOKEPOINT) ? " (C)": ""), // chokepoint
586                     ((pmap[i][j].flags & IN_LOOP) ? " (L)": ""), // loop
587                     (i == DCOLS-1 ? "\n" : ""));
588             RNGLog(RNGmessage);
589 #endif
590             totalHeat += itemSpawnHeatMap[i][j];
591         }
592     }
593 
594     if (D_INSPECT_LEVELGEN) {
595         short **map = allocGrid();
596         for (i=0; i<DCOLS; i++) {
597             for (j=0; j<DROWS; j++) {
598                 map[i][j] = itemSpawnHeatMap[i][j] * -1;
599             }
600         }
601         dumpLevelToScreen();
602         displayGrid(map);
603         freeGrid(map);
604         temporaryMessage("Item spawn heat map:", REQUIRE_ACKNOWLEDGMENT);
605     }
606 
607     if (rogue.depthLevel > 2) {
608         // Include a random factor in food and potion of life generation to make things slightly less predictable.
609         randomDepthOffset = rand_range(-1, 1);
610         randomDepthOffset += rand_range(-1, 1);
611     }
612 
613     for (i=0; i<numberOfItems; i++) {
614         theCategory = ALL_ITEMS & ~GOLD; // gold is placed separately, below, so it's not a punishment
615         theKind = -1;
616 
617         scrollTable[SCROLL_ENCHANTING].frequency = rogue.enchantScrollFrequency;
618         potionTable[POTION_STRENGTH].frequency = rogue.strengthPotionFrequency;
619         potionTable[POTION_LIFE].frequency = rogue.lifePotionFrequency;
620 
621         // Adjust the desired item category if necessary.
622         if ((rogue.foodSpawned + foodTable[RATION].strengthRequired / 3) * 4 * FP_FACTOR
623             <= (POW_FOOD[rogue.depthLevel-1] + (randomDepthOffset * FP_FACTOR)) * foodTable[RATION].strengthRequired * 45/100) {
624             // Guarantee a certain nutrition minimum of the approximate equivalent of one ration every four levels,
625             // with more food on deeper levels since they generally take more turns to complete.
626             theCategory = FOOD;
627             if (rogue.depthLevel > AMULET_LEVEL) {
628                 numberOfItems++; // Food isn't at the expense of lumenstones.
629             }
630         } else if (rogue.depthLevel > AMULET_LEVEL) {
631             theCategory = GEM;
632         } else if (rogue.lifePotionsSpawned * 4 + 3 < rogue.depthLevel + randomDepthOffset) {
633             theCategory = POTION;
634             theKind = POTION_LIFE;
635         }
636 
637         // Generate the item.
638         theItem = generateItem(theCategory, theKind);
639         theItem->originDepth = rogue.depthLevel;
640 
641         if (theItem->category & FOOD) {
642             rogue.foodSpawned += foodTable[theItem->kind].strengthRequired;
643             if (D_MESSAGE_ITEM_GENERATION) printf("\n(:)  Depth %i: generated food", rogue.depthLevel);
644         }
645 
646         // Choose a placement location.
647         if ((theItem->category & FOOD) || ((theItem->category & POTION) && theItem->kind == POTION_STRENGTH)) {
648             do {
649                 randomMatchingLocation(&x, &y, FLOOR, NOTHING, -1); // Food and gain strength don't follow the heat map.
650             } while (passableArcCount(x, y) > 1); // Not in a hallway.
651         } else {
652             getItemSpawnLoc(itemSpawnHeatMap, &x, &y, &totalHeat);
653         }
654         brogueAssert(coordinatesAreInMap(x, y));
655         // Cool off the item spawning heat map at the chosen location:
656         coolHeatMapAt(itemSpawnHeatMap, x, y, &totalHeat);
657 
658         // Regulate the frequency of enchantment scrolls and strength/life potions.
659         if ((theItem->category & SCROLL) && theItem->kind == SCROLL_ENCHANTING) {
660             rogue.enchantScrollFrequency -= 50;
661             if (D_MESSAGE_ITEM_GENERATION) printf("\n(?)  Depth %i: generated an enchant scroll at %i frequency", rogue.depthLevel, rogue.enchantScrollFrequency);
662         } else if (theItem->category & POTION && theItem->kind == POTION_LIFE) {
663             if (D_MESSAGE_ITEM_GENERATION) printf("\n(!l) Depth %i: generated a life potion at %i frequency", rogue.depthLevel, rogue.lifePotionFrequency);
664             rogue.lifePotionFrequency -= 150;
665             rogue.lifePotionsSpawned++;
666         } else if (theItem->category & POTION && theItem->kind == POTION_STRENGTH) {
667             if (D_MESSAGE_ITEM_GENERATION) printf("\n(!s) Depth %i: generated a strength potion at %i frequency", rogue.depthLevel, rogue.strengthPotionFrequency);
668             rogue.strengthPotionFrequency -= 50;
669         }
670 
671         // Place the item.
672         placeItem(theItem, x, y); // Random valid location already obtained according to heat map.
673         brogueAssert(!cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY));
674 
675         if (D_INSPECT_LEVELGEN) {
676             short **map = allocGrid();
677             short i2, j2;
678             for (i2=0; i2<DCOLS; i2++) {
679                 for (j2=0; j2<DROWS; j2++) {
680                     map[i2][j2] = itemSpawnHeatMap[i2][j2] * -1;
681                 }
682             }
683             dumpLevelToScreen();
684             displayGrid(map);
685             freeGrid(map);
686             plotCharWithColor(theItem->displayChar, mapToWindowX(x), mapToWindowY(y), &black, &purple);
687             temporaryMessage("Added an item.", REQUIRE_ACKNOWLEDGMENT);
688         }
689     }
690 
691     // Now generate gold.
692     for (i=0; i<numberOfGoldPiles; i++) {
693         theItem = generateItem(GOLD, -1);
694         getItemSpawnLoc(itemSpawnHeatMap, &x, &y, &totalHeat);
695         coolHeatMapAt(itemSpawnHeatMap, x, y, &totalHeat);
696         placeItem(theItem, x, y);
697         rogue.goldGenerated += theItem->quantity;
698     }
699 
700     if (D_INSPECT_LEVELGEN) {
701         dumpLevelToScreen();
702         temporaryMessage("Added gold.", REQUIRE_ACKNOWLEDGMENT);
703     }
704 
705     scrollTable[SCROLL_ENCHANTING].frequency    = 0;    // No enchant scrolls or strength/life potions can spawn except via initial
706     potionTable[POTION_STRENGTH].frequency      = 0;    // item population or blueprints that create them specifically.
707     potionTable[POTION_LIFE].frequency          = 0;
708 
709     if (D_MESSAGE_ITEM_GENERATION) printf("\n---- Depth %i: %lu gold generated so far.", rogue.depthLevel, rogue.goldGenerated);
710 }
711 
712 // Name of this function is a bit misleading -- basically returns true iff the item will stack without consuming an extra slot
713 // i.e. if it's a throwing weapon with a sibling already in your pack. False for potions and scrolls.
itemWillStackWithPack(item * theItem)714 boolean itemWillStackWithPack(item *theItem) {
715     item *tempItem;
716     if (theItem->category & GEM) {
717         for (tempItem = packItems->nextItem;
718              tempItem != NULL && !((tempItem->category & GEM) && theItem->originDepth == tempItem->originDepth);
719              tempItem = tempItem->nextItem);
720         return (tempItem ? true : false);
721     } else if (!(theItem->quiverNumber)) {
722         return false;
723     } else {
724         for (tempItem = packItems->nextItem;
725              tempItem != NULL && tempItem->quiverNumber != theItem->quiverNumber;
726              tempItem = tempItem->nextItem);
727         return (tempItem ? true : false);
728     }
729 }
730 
removeItemFrom(short x,short y)731 void removeItemFrom(short x, short y) {
732     short layer;
733 
734     pmap[x][y].flags &= ~HAS_ITEM;
735 
736     if (cellHasTMFlag(x, y, TM_PROMOTES_ON_ITEM_PICKUP)) {
737         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
738             if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_ON_ITEM_PICKUP) {
739                 promoteTile(x, y, layer, false);
740             }
741         }
742     }
743 }
744 
745 // adds the item at (x,y) to the pack
pickUpItemAt(short x,short y)746 void pickUpItemAt(short x, short y) {
747     item *theItem;
748     creature *monst;
749     char buf[COLS * 3], buf2[COLS * 3];
750     short guardianX, guardianY;
751 
752     rogue.disturbed = true;
753 
754     // find the item
755     theItem = itemAtLoc(x, y);
756 
757     if (!theItem) {
758         message("Error: Expected item; item not found.", REQUIRE_ACKNOWLEDGMENT);
759         return;
760     }
761 
762     if ((theItem->flags & ITEM_KIND_AUTO_ID)
763         && tableForItemCategory(theItem->category, NULL)
764         && !(tableForItemCategory(theItem->category, NULL)[theItem->kind].identified)) {
765 
766         identifyItemKind(theItem);
767     }
768 
769     if ((theItem->category & WAND)
770         && wandTable[theItem->kind].identified
771         && wandTable[theItem->kind].range.lowerBound == wandTable[theItem->kind].range.upperBound) {
772 
773         theItem->flags |= ITEM_IDENTIFIED;
774     }
775 
776     if (numberOfItemsInPack() < MAX_PACK_ITEMS || (theItem->category & GOLD) || itemWillStackWithPack(theItem)) {
777         // remove from floor chain
778         pmap[x][y].flags &= ~ITEM_DETECTED;
779 
780         if (!removeItemFromChain(theItem, floorItems)) {
781             brogueAssert(false);
782         }
783 
784         if (theItem->category & GOLD) {
785             rogue.gold += theItem->quantity;
786             rogue.featRecord[FEAT_TONE] = false;
787             sprintf(buf, "you found %i pieces of gold.", theItem->quantity);
788             messageWithColor(buf, &itemMessageColor, 0);
789             deleteItem(theItem);
790             removeItemFrom(x, y); // triggers tiles with T_PROMOTES_ON_ITEM_PICKUP
791             return;
792         }
793 
794         if ((theItem->category & AMULET) && numberOfMatchingPackItems(AMULET, 0, 0, false)) {
795             message("you already have the Amulet of Yendor.", 0);
796             deleteItem(theItem);
797             return;
798         }
799 
800         theItem = addItemToPack(theItem);
801 
802         itemName(theItem, buf2, true, true, NULL); // include suffix, article
803 
804         sprintf(buf, "you now have %s (%c).", buf2, theItem->inventoryLetter);
805         messageWithColor(buf, &itemMessageColor, 0);
806 
807         removeItemFrom(x, y); // triggers tiles with T_PROMOTES_ON_ITEM_PICKUP
808 
809         if ((theItem->category & AMULET)
810             && !(rogue.yendorWarden)) {
811             // Identify the amulet guardian, or generate one if there isn't one.
812             for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
813                 creature *monst = nextCreature(&it);
814                 if (monst->info.monsterID == MK_WARDEN_OF_YENDOR) {
815                     rogue.yendorWarden = monst;
816                     break;
817                 }
818             }
819             if (!rogue.yendorWarden) {
820                 getRandomMonsterSpawnLocation(&guardianX, &guardianY);
821                 monst = generateMonster(MK_WARDEN_OF_YENDOR, false, false);
822                 monst->xLoc = guardianX;
823                 monst->yLoc = guardianY;
824                 pmap[guardianX][guardianY].flags |= HAS_MONSTER;
825                 rogue.yendorWarden = monst;
826             }
827         }
828     } else {
829         theItem->flags |= ITEM_PLAYER_AVOIDS; // explore shouldn't try to pick it up more than once.
830         itemName(theItem, buf2, false, true, NULL); // include article
831         sprintf(buf, "Your pack is too full to pick up %s.", buf2);
832         message(buf, 0);
833     }
834 }
835 
conflateItemCharacteristics(item * newItem,item * oldItem)836 void conflateItemCharacteristics(item *newItem, item *oldItem) {
837 
838     // let magic detection and other flags propagate to the new stack...
839     newItem->flags |= (oldItem->flags & (ITEM_MAGIC_DETECTED | ITEM_IDENTIFIED | ITEM_PROTECTED | ITEM_RUNIC
840                                          | ITEM_RUNIC_HINTED | ITEM_CAN_BE_IDENTIFIED | ITEM_MAX_CHARGES_KNOWN));
841 
842     // keep the higher enchantment and lower strength requirement...
843     if (oldItem->enchant1 > newItem->enchant1) {
844         newItem->enchant1 = oldItem->enchant1;
845     }
846     if (oldItem->strengthRequired < newItem->strengthRequired) {
847         newItem->strengthRequired = oldItem->strengthRequired;
848     }
849     // Keep track of origin depth only if every item in the stack has the same origin depth.
850     if (oldItem->originDepth <= 0 || newItem->originDepth != oldItem->originDepth) {
851         newItem->originDepth = 0;
852     }
853 }
854 
stackItems(item * newItem,item * oldItem)855 void stackItems(item *newItem, item *oldItem) {
856     //Increment the quantity of the old item...
857     newItem->quantity += oldItem->quantity;
858 
859     // ...conflate attributes...
860     conflateItemCharacteristics(newItem, oldItem);
861 
862     // ...and delete the new item.
863     deleteItem(oldItem);
864 }
865 
inventoryLetterAvailable(char proposedLetter)866 boolean inventoryLetterAvailable(char proposedLetter) {
867     item *theItem;
868     if (proposedLetter >= 'a'
869         && proposedLetter <= 'z') {
870 
871         for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
872             if (theItem->inventoryLetter == proposedLetter) {
873                 return false;
874             }
875         }
876         return true;
877     }
878     return false;
879 }
880 
addItemToPack(item * theItem)881 item *addItemToPack(item *theItem) {
882     item *previousItem, *tempItem;
883     char itemLetter;
884 
885     // Can the item stack with another in the inventory?
886     if (theItem->category & (FOOD|POTION|SCROLL|GEM)) {
887         for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
888             if (theItem->category == tempItem->category
889                 && theItem->kind == tempItem->kind
890                 && (!(theItem->category & GEM) || theItem->originDepth == tempItem->originDepth)) {
891 
892                 // We found a match!
893                 stackItems(tempItem, theItem);
894 
895                 // Pass back the incremented (old) item. No need to add it to the pack since it's already there.
896                 return tempItem;
897             }
898         }
899     } else if (theItem->category & WEAPON && theItem->quiverNumber > 0) {
900         for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
901             if (theItem->category == tempItem->category && theItem->kind == tempItem->kind
902                 && theItem->quiverNumber == tempItem->quiverNumber) {
903                 // We found a match!
904                 stackItems(tempItem, theItem);
905 
906                 // Pass back the incremented (old) item. No need to add it to the pack since it's already there.
907                 return tempItem;
908             }
909         }
910     }
911 
912     // assign a reference letter to the item
913     if (!inventoryLetterAvailable(theItem->inventoryLetter)) {
914         itemLetter = nextAvailableInventoryCharacter();
915         if (itemLetter) {
916             theItem->inventoryLetter = itemLetter;
917         }
918     }
919 
920     // insert at proper place in pack chain
921     for (previousItem = packItems;
922          previousItem->nextItem != NULL && previousItem->nextItem->category <= theItem->category;
923          previousItem = previousItem->nextItem);
924     theItem->nextItem = previousItem->nextItem;
925     previousItem->nextItem = theItem;
926 
927     return theItem;
928 }
929 
numberOfItemsInPack()930 short numberOfItemsInPack() {
931     short theCount = 0;
932     item *theItem;
933     for(theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
934         theCount += (theItem->category & (WEAPON | GEM) ? 1 : theItem->quantity);
935     }
936     return theCount;
937 }
938 
nextAvailableInventoryCharacter()939 char nextAvailableInventoryCharacter() {
940     boolean charTaken[26];
941     short i;
942     item *theItem;
943     char c;
944     for(i=0; i<26; i++) {
945         charTaken[i] = false;
946     }
947     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
948         c = theItem->inventoryLetter;
949         if (c >= 'a' && c <= 'z') {
950             charTaken[c - 'a'] = true;
951         }
952     }
953     for(i=0; i<26; i++) {
954         if (!charTaken[i]) {
955             return ('a' + i);
956         }
957     }
958     return 0;
959 }
960 
checkForDisenchantment(item * theItem)961 void checkForDisenchantment(item *theItem) {
962     char buf[COLS], buf2[COLS];
963 
964     if ((theItem->flags & ITEM_RUNIC)
965         && (((theItem->category & WEAPON) && theItem->enchant2 < NUMBER_GOOD_WEAPON_ENCHANT_KINDS) || ((theItem->category & ARMOR) && theItem->enchant2 < NUMBER_GOOD_ARMOR_ENCHANT_KINDS))
966         && theItem->enchant1 <= 0) {
967 
968         theItem->enchant2 = 0;
969         theItem->flags &= ~(ITEM_RUNIC | ITEM_RUNIC_HINTED | ITEM_RUNIC_IDENTIFIED);
970 
971         if (theItem->flags & ITEM_IDENTIFIED) {
972             identify(theItem);
973             itemName(theItem, buf2, false, false, NULL);
974             sprintf(buf, "the runes fade from your %s.", buf2);
975             messageWithColor(buf, &itemMessageColor, 0);
976         }
977     }
978     if (theItem->flags & ITEM_CURSED
979         && theItem->enchant1 >= 0) {
980 
981         theItem->flags &= ~ITEM_CURSED;
982     }
983 }
984 
itemIsSwappable(const item * theItem)985 boolean itemIsSwappable(const item *theItem) {
986     if ((theItem->category & CAN_BE_SWAPPED)
987         && theItem->quiverNumber == 0) {
988 
989         return true;
990     } else {
991         return false;
992     }
993 }
994 
swapItemToEnchantLevel(item * theItem,short newEnchant,boolean enchantmentKnown)995 void swapItemToEnchantLevel(item *theItem, short newEnchant, boolean enchantmentKnown) {
996     short x, y, charmPercent;
997     char buf1[COLS * 3], buf2[COLS * 3];
998 
999     if ((theItem->category & STAFF) && newEnchant < 2
1000         || (theItem->category & CHARM) && newEnchant < 1
1001         || (theItem->category & WAND) && newEnchant < 0) {
1002 
1003         itemName(theItem, buf1, false, true, NULL);
1004         sprintf(buf2, "%s shatter%s from the strain!",
1005                 buf1,
1006                 theItem->quantity == 1 ? "s" : "");
1007         x = theItem->xLoc;
1008         y = theItem->yLoc;
1009         removeItemFromChain(theItem, floorItems);
1010         pmap[x][y].flags &= ~(HAS_ITEM | ITEM_DETECTED);
1011         if (pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | DISCOVERED | ITEM_DETECTED)) {
1012             refreshDungeonCell(x, y);
1013         }
1014         if (playerCanSee(x, y)) {
1015             messageWithColor(buf2, &itemMessageColor, 0);
1016         }
1017     } else {
1018         if ((theItem->category & STAFF)
1019             && theItem->charges > newEnchant) {
1020 
1021             theItem->charges = newEnchant;
1022         }
1023         if (theItem->category & CHARM) {
1024             charmPercent = theItem->charges * 100 / charmRechargeDelay(theItem->kind, theItem->enchant1);
1025             theItem->charges = charmPercent * charmRechargeDelay(theItem->kind, newEnchant) / 100;
1026         }
1027         if (enchantmentKnown) {
1028             if (theItem->category & STAFF) {
1029                 theItem->flags |= ITEM_MAX_CHARGES_KNOWN;
1030             }
1031             theItem->flags |= ITEM_IDENTIFIED;
1032         } else {
1033             theItem->flags &= ~(ITEM_MAX_CHARGES_KNOWN | ITEM_IDENTIFIED);
1034             theItem->flags |= ITEM_CAN_BE_IDENTIFIED;
1035             if (theItem->category & WEAPON) {
1036                 theItem->charges = WEAPON_KILLS_TO_AUTO_ID; // kill this many enemies to auto-identify
1037             } else if (theItem->category & ARMOR) {
1038                 theItem->charges = ARMOR_DELAY_TO_AUTO_ID; // this many turns until it reveals its enchants and whether runic
1039             } else if (theItem->category & RING) {
1040                 theItem->charges = RING_DELAY_TO_AUTO_ID; // how many turns of being worn until it auto-identifies
1041             }
1042         }
1043         if (theItem->category & WAND) {
1044             theItem->charges = newEnchant;
1045         } else {
1046             theItem->enchant1 = newEnchant;
1047         }
1048         checkForDisenchantment(theItem);
1049     }
1050 }
1051 
enchantLevelKnown(const item * theItem)1052 boolean enchantLevelKnown(const item *theItem) {
1053     if ((theItem->category & STAFF)
1054         && (theItem->flags & ITEM_MAX_CHARGES_KNOWN)) {
1055 
1056         return true;
1057     } else {
1058         return (theItem->flags & ITEM_IDENTIFIED);
1059     }
1060 }
1061 
effectiveEnchantLevel(const item * theItem)1062 short effectiveEnchantLevel(const item *theItem) {
1063     if (theItem->category & WAND) {
1064         return theItem->charges;
1065     } else {
1066         return theItem->enchant1;
1067     }
1068 }
1069 
swapItemEnchants(const short machineNumber)1070 boolean swapItemEnchants(const short machineNumber) {
1071     item *lockedItem, *tempItem;
1072     short i, j, oldEnchant;
1073     boolean enchantmentKnown;
1074 
1075     lockedItem = NULL;
1076     for (i = 0; i < DCOLS; i++) {
1077         for (j = 0; j < DROWS; j++) {
1078             tempItem = itemAtLoc(i, j);
1079             if (tempItem
1080                 && pmap[i][j].machineNumber == machineNumber
1081                 && cellHasTMFlag(i, j, TM_SWAP_ENCHANTS_ACTIVATION)
1082                 && itemIsSwappable(tempItem)) {
1083 
1084                 if (lockedItem) {
1085                     if (effectiveEnchantLevel(lockedItem) != effectiveEnchantLevel(tempItem)) {
1086                         // Presto change-o!
1087                         oldEnchant = effectiveEnchantLevel(lockedItem);
1088                         enchantmentKnown = enchantLevelKnown(lockedItem);
1089                         swapItemToEnchantLevel(lockedItem, effectiveEnchantLevel(tempItem), enchantLevelKnown(tempItem));
1090                         swapItemToEnchantLevel(tempItem, oldEnchant, enchantmentKnown);
1091                         return true;
1092                     }
1093                 } else {
1094                     lockedItem = tempItem;
1095                 }
1096             }
1097         }
1098     }
1099     return false;
1100 }
1101 
updateFloorItems()1102 void updateFloorItems() {
1103     short x, y, loc[2];
1104     char buf[DCOLS*3], buf2[DCOLS*3];
1105     enum dungeonLayers layer;
1106     item *theItem, *nextItem;
1107 
1108     for (theItem=floorItems->nextItem; theItem != NULL; theItem = nextItem) {
1109         nextItem = theItem->nextItem;
1110         x = theItem->xLoc;
1111         y = theItem->yLoc;
1112         if (rogue.absoluteTurnNumber < theItem->spawnTurnNumber) {
1113             // we are simulating an earlier turn than when the item fell into this level... let's not touch it yet
1114             continue;
1115         }
1116         if (cellHasTerrainFlag(x, y, T_AUTO_DESCENT)) {
1117             if (playerCanSeeOrSense(x, y)) {
1118                 itemName(theItem, buf, false, false, NULL);
1119                 sprintf(buf2, "The %s plunge%s out of sight!", buf, (theItem->quantity > 1 ? "" : "s"));
1120                 messageWithColor(buf2, &itemMessageColor, 0);
1121             }
1122             if (playerCanSee(x, y)) {
1123                 discover(x, y);
1124             }
1125             theItem->flags |= ITEM_PREPLACED;
1126 
1127             // Remove from item chain.
1128             removeItemFromChain(theItem, floorItems);
1129 
1130             pmap[x][y].flags &= ~(HAS_ITEM | ITEM_DETECTED);
1131 
1132             if (theItem->category == POTION || rogue.depthLevel == DEEPEST_LEVEL) {
1133                 // Potions don't survive the fall.
1134                 deleteItem(theItem);
1135             } else {
1136                 // Add to next level's chain.
1137                 theItem->spawnTurnNumber = rogue.absoluteTurnNumber;
1138                 theItem->nextItem = levels[rogue.depthLevel-1 + 1].items;
1139                 levels[rogue.depthLevel-1 + 1].items = theItem;
1140             }
1141             refreshDungeonCell(x, y);
1142             continue;
1143         }
1144         if ((cellHasTerrainFlag(x, y, T_IS_FIRE) && (theItem->flags & ITEM_FLAMMABLE))
1145             || (cellHasTerrainFlag(x, y, T_LAVA_INSTA_DEATH) && !(theItem->category & AMULET))) {
1146 
1147             burnItem(theItem);
1148             continue;
1149         }
1150         if (cellHasTerrainFlag(x, y, T_MOVES_ITEMS)) {
1151             getQualifyingLocNear(loc, x, y, true, 0, (T_OBSTRUCTS_ITEMS | T_OBSTRUCTS_PASSABILITY), (HAS_ITEM), false, false);
1152             removeItemFrom(x, y);
1153             pmap[loc[0]][loc[1]].flags |= HAS_ITEM;
1154             if (pmap[x][y].flags & ITEM_DETECTED) {
1155                 pmap[x][y].flags &= ~ITEM_DETECTED;
1156                 pmap[loc[0]][loc[1]].flags |= ITEM_DETECTED;
1157             }
1158             theItem->xLoc = loc[0];
1159             theItem->yLoc = loc[1];
1160             refreshDungeonCell(x, y);
1161             refreshDungeonCell(loc[0], loc[1]);
1162             continue;
1163         }
1164         if (cellHasTMFlag(x, y, TM_PROMOTES_ON_ITEM)) {
1165             for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1166                 if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_ON_ITEM) {
1167                     promoteTile(x, y, layer, false);
1168                 }
1169             }
1170             continue;
1171         }
1172         if (pmap[x][y].machineNumber
1173             && pmap[x][y].machineNumber == pmap[player.xLoc][player.yLoc].machineNumber
1174             && (theItem->flags & ITEM_KIND_AUTO_ID)) {
1175 
1176             identifyItemKind(theItem);
1177         }
1178         if (cellHasTMFlag(x, y, TM_SWAP_ENCHANTS_ACTIVATION)
1179             && pmap[x][y].machineNumber) {
1180 
1181             while (nextItem != NULL
1182                    && pmap[x][y].machineNumber == pmap[nextItem->xLoc][nextItem->yLoc].machineNumber
1183                    && cellHasTMFlag(nextItem->xLoc, nextItem->yLoc, TM_SWAP_ENCHANTS_ACTIVATION)) {
1184 
1185                 // Skip future items that are also swappable, so that we don't inadvertently
1186                 // destroy the next item and then try to update it.
1187                 nextItem = nextItem->nextItem;
1188             }
1189 
1190             if (!circuitBreakersPreventActivation(pmap[x][y].machineNumber)
1191                 && swapItemEnchants(pmap[x][y].machineNumber)) {
1192 
1193                 activateMachine(pmap[x][y].machineNumber);
1194             }
1195         }
1196     }
1197 }
1198 
inscribeItem(item * theItem)1199 boolean inscribeItem(item *theItem) {
1200     char itemText[30], buf[COLS * 3], nameOfItem[COLS * 3], oldInscription[COLS];
1201 
1202     strcpy(oldInscription, theItem->inscription);
1203     theItem->inscription[0] = '\0';
1204     itemName(theItem, nameOfItem, true, true, NULL);
1205     strcpy(theItem->inscription, oldInscription);
1206 
1207     sprintf(buf, "inscribe: %s \"", nameOfItem);
1208     if (getInputTextString(itemText, buf, min(29, DCOLS - strLenWithoutEscapes(buf) - 1), "", "\"", TEXT_INPUT_NORMAL, false)) {
1209         strcpy(theItem->inscription, itemText);
1210         confirmMessages();
1211         itemName(theItem, nameOfItem, true, true, NULL);
1212         sprintf(buf, "%s %s.", (theItem->quantity > 1 ? "they're" : "it's"), nameOfItem);
1213         messageWithColor(buf, &itemMessageColor, 0);
1214         return true;
1215     } else {
1216         confirmMessages();
1217         return false;
1218     }
1219 }
1220 
itemCanBeCalled(item * theItem)1221 boolean itemCanBeCalled(item *theItem) {
1222     if (theItem->category & (WEAPON|ARMOR|SCROLL|RING|POTION|STAFF|WAND|CHARM)) {
1223         return true;
1224     } else if ((theItem->category & (POTION | SCROLL))
1225                && !tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
1226         return true;
1227     } else {
1228         return false;
1229     }
1230 }
1231 
call(item * theItem)1232 void call(item *theItem) {
1233     char itemText[30], buf[COLS * 3];
1234     short c;
1235     unsigned char command[100];
1236     item *tempItem;
1237 
1238     c = 0;
1239     command[c++] = CALL_KEY;
1240     if (theItem == NULL) {
1241         // Need to gray out known potions and scrolls from inventory selection.
1242         // Hijack the "item can be identified" flag for this purpose,
1243         // and then reset it immediately afterward.
1244         for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
1245             if ((tempItem->category & (POTION | SCROLL))
1246                 && tableForItemCategory(tempItem->category, NULL)[tempItem->kind].identified) {
1247 
1248                 tempItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
1249             } else {
1250                 tempItem->flags |= ITEM_CAN_BE_IDENTIFIED;
1251             }
1252         }
1253         theItem = promptForItemOfType((WEAPON|ARMOR|SCROLL|RING|POTION|STAFF|WAND|CHARM), ITEM_CAN_BE_IDENTIFIED, 0,
1254                                       KEYBOARD_LABELS ? "Call what? (a-z, shift for more info; or <esc> to cancel)" : "Call what?",
1255                                       true);
1256         updateIdentifiableItems(); // Reset the flags.
1257     }
1258     if (theItem == NULL) {
1259         return;
1260     }
1261 
1262     command[c++] = theItem->inventoryLetter;
1263 
1264     confirmMessages();
1265 
1266     if ((theItem->flags & ITEM_IDENTIFIED) || theItem->category & (WEAPON|ARMOR|CHARM|FOOD|GOLD|AMULET|GEM)) {
1267         if (theItem->category & (WEAPON | ARMOR | CHARM | STAFF | WAND | RING)) {
1268             if (inscribeItem(theItem)) {
1269                 command[c++] = '\0';
1270                 strcat((char *) command, theItem->inscription);
1271                 recordKeystrokeSequence(command);
1272                 recordKeystroke(RETURN_KEY, false, false);
1273             }
1274         } else {
1275             message("you already know what that is.", 0);
1276         }
1277         return;
1278     }
1279 
1280     if (theItem->category & (WEAPON | ARMOR | STAFF | WAND | RING)) {
1281         if (tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
1282             if (inscribeItem(theItem)) {
1283                 command[c++] = '\0';
1284                 strcat((char *) command, theItem->inscription);
1285                 recordKeystrokeSequence(command);
1286                 recordKeystroke(RETURN_KEY, false, false);
1287             }
1288             return;
1289         } else if (confirm("Inscribe this particular item instead of all similar items?", true)) {
1290             command[c++] = 'y'; // y means yes, since the recording also needs to negotiate the above confirmation prompt.
1291             if (inscribeItem(theItem)) {
1292                 command[c++] = '\0';
1293                 strcat((char *) command, theItem->inscription);
1294                 recordKeystrokeSequence(command);
1295                 recordKeystroke(RETURN_KEY, false, false);
1296             }
1297             return;
1298         } else {
1299             command[c++] = 'n'; // n means no
1300         }
1301     }
1302 
1303     if (tableForItemCategory(theItem->category, NULL)
1304         && !(tableForItemCategory(theItem->category, NULL)[theItem->kind].identified)) {
1305 
1306         if (getInputTextString(itemText, "call them: \"", 29, "", "\"", TEXT_INPUT_NORMAL, false)) {
1307             command[c++] = '\0';
1308             strcat((char *) command, itemText);
1309             recordKeystrokeSequence(command);
1310             recordKeystroke(RETURN_KEY, false, false);
1311             if (itemText[0]) {
1312                 strcpy(tableForItemCategory(theItem->category, NULL)[theItem->kind].callTitle, itemText);
1313                 tableForItemCategory(theItem->category, NULL)[theItem->kind].called = true;
1314             } else {
1315                 tableForItemCategory(theItem->category, NULL)[theItem->kind].callTitle[0] = '\0';
1316                 tableForItemCategory(theItem->category, NULL)[theItem->kind].called = false;
1317             }
1318             confirmMessages();
1319             itemName(theItem, buf, false, true, NULL);
1320             messageWithColor(buf, &itemMessageColor, 0);
1321         }
1322     } else {
1323         message("you already know what that is.", 0);
1324     }
1325 }
1326 
1327 // Generates the item name and returns it in the "root" string.
1328 // IncludeDetails governs things such as enchantment, charges, strength requirement, times used, etc.
1329 // IncludeArticle governs the article -- e.g. "some" food, "5" darts, "a" pink potion.
1330 // If baseColor is provided, then the suffix will be in gray, flavor portions of the item name (e.g. a "pink" potion,
1331 //  a "sandalwood" staff, a "ruby" ring) will be in dark purple, and the Amulet of Yendor and lumenstones will be in yellow.
1332 //  BaseColor itself will be the color that the name reverts to outside of these colored portions.
itemName(item * theItem,char * root,boolean includeDetails,boolean includeArticle,color * baseColor)1333 void itemName(item *theItem, char *root, boolean includeDetails, boolean includeArticle, color *baseColor) {
1334     char buf[DCOLS * 5], pluralization[10], article[10] = "", runicName[30],
1335     grayEscapeSequence[5], purpleEscapeSequence[5], yellowEscapeSequence[5], baseEscapeSequence[5];
1336     color tempColor;
1337 
1338     strcpy(pluralization, (theItem->quantity > 1 ? "s" : ""));
1339 
1340     grayEscapeSequence[0] = '\0';
1341     purpleEscapeSequence[0] = '\0';
1342     yellowEscapeSequence[0] = '\0';
1343     baseEscapeSequence[0] = '\0';
1344     if (baseColor) {
1345         tempColor = backgroundMessageColor;
1346         applyColorMultiplier(&tempColor, baseColor); // To gray out the purple if necessary.
1347         encodeMessageColor(purpleEscapeSequence, 0, &tempColor);
1348 
1349         tempColor = gray;
1350         //applyColorMultiplier(&tempColor, baseColor);
1351         encodeMessageColor(grayEscapeSequence, 0, &tempColor);
1352 
1353         tempColor = itemMessageColor;
1354         applyColorMultiplier(&tempColor, baseColor);
1355         encodeMessageColor(yellowEscapeSequence, 0, &tempColor);
1356 
1357         encodeMessageColor(baseEscapeSequence, 0, baseColor);
1358     }
1359 
1360     switch (theItem -> category) {
1361         case FOOD:
1362             if (theItem -> kind == FRUIT) {
1363                 sprintf(root, "mango%s", pluralization);
1364             } else {
1365                 if (theItem->quantity == 1) {
1366                     sprintf(article, "some ");
1367                     sprintf(root, "food");
1368                 } else {
1369                     sprintf(root, "ration%s of food", pluralization);
1370                 }
1371             }
1372             break;
1373         case WEAPON:
1374             sprintf(root, "%s%s", weaponTable[theItem->kind].name, pluralization);
1375             if (includeDetails) {
1376                 if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
1377                     sprintf(buf, "%s%i %s", (theItem->enchant1 < 0 ? "" : "+"), theItem->enchant1, root);
1378                     strcpy(root, buf);
1379                 }
1380 
1381                 if (theItem->flags & ITEM_RUNIC) {
1382                     if ((theItem->flags & ITEM_RUNIC_IDENTIFIED) || rogue.playbackOmniscience) {
1383                         itemRunicName(theItem, runicName);
1384                         sprintf(buf, "%s of %s%s", root, runicName, grayEscapeSequence);
1385                         strcpy(root, buf);
1386                     } else if (theItem->flags & (ITEM_IDENTIFIED | ITEM_RUNIC_HINTED)) {
1387                         if (grayEscapeSequence[0]) {
1388                             strcat(root, grayEscapeSequence);
1389                         }
1390                         strcat(root, " (unknown runic)");
1391                     }
1392                 }
1393                 sprintf(buf, "%s%s <%i>", root, grayEscapeSequence, theItem->strengthRequired);
1394                 strcpy(root, buf);
1395             }
1396             break;
1397         case ARMOR:
1398             sprintf(root, "%s", armorTable[theItem->kind].name);
1399             if (includeDetails) {
1400 
1401                 if ((theItem->flags & ITEM_RUNIC)
1402                     && ((theItem->flags & ITEM_RUNIC_IDENTIFIED)
1403                         || rogue.playbackOmniscience)) {
1404                     itemRunicName(theItem, runicName);
1405                     sprintf(buf, "%s of %s%s", root, runicName, grayEscapeSequence);
1406                     strcpy(root, buf);
1407                     }
1408 
1409                 if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
1410                     if (theItem->enchant1 == 0) {
1411                         sprintf(buf, "%s%s [%i]<%i>", root, grayEscapeSequence, theItem->armor/10, theItem->strengthRequired);
1412                     } else {
1413                         sprintf(buf, "%s%i %s%s [%i]<%i>",
1414                                 (theItem->enchant1 < 0 ? "" : "+"),
1415                                 theItem->enchant1,
1416                                 root,
1417                                 grayEscapeSequence,
1418                                 theItem->armor/10 + theItem->enchant1,
1419                                 theItem->strengthRequired);
1420                     }
1421                     strcpy(root, buf);
1422                 } else {
1423                     sprintf(buf, "%s%s <%i>", root, grayEscapeSequence, theItem->strengthRequired);
1424                     strcpy(root, buf);
1425                 }
1426 
1427                 if ((theItem->flags & ITEM_RUNIC)
1428                     && (theItem->flags & (ITEM_IDENTIFIED | ITEM_RUNIC_HINTED))
1429                     && !(theItem->flags & ITEM_RUNIC_IDENTIFIED)
1430                     && !rogue.playbackOmniscience) {
1431                     strcat(root, " (unknown runic)");
1432                 }
1433             }
1434             break;
1435         case SCROLL:
1436             if (scrollTable[theItem->kind].identified || rogue.playbackOmniscience) {
1437                 sprintf(root, "scroll%s of %s", pluralization, scrollTable[theItem->kind].name);
1438             } else if (scrollTable[theItem->kind].called) {
1439                 sprintf(root, "scroll%s called %s%s%s",
1440                         pluralization,
1441                         purpleEscapeSequence,
1442                         scrollTable[theItem->kind].callTitle,
1443                         baseEscapeSequence);
1444             } else {
1445                 sprintf(root, "scroll%s entitled %s\"%s\"%s",
1446                         pluralization,
1447                         purpleEscapeSequence,
1448                         scrollTable[theItem->kind].flavor,
1449                         baseEscapeSequence);
1450             }
1451             break;
1452         case POTION:
1453             if (potionTable[theItem->kind].identified || rogue.playbackOmniscience) {
1454                 sprintf(root, "potion%s of %s", pluralization, potionTable[theItem->kind].name);
1455             } else if (potionTable[theItem->kind].called) {
1456                 sprintf(root, "potion%s called %s%s%s",
1457                         pluralization,
1458                         purpleEscapeSequence,
1459                         potionTable[theItem->kind].callTitle,
1460                         baseEscapeSequence);
1461             } else {
1462                 sprintf(root, "%s%s%s potion%s",
1463                         purpleEscapeSequence,
1464                         potionTable[theItem->kind].flavor,
1465                         baseEscapeSequence,
1466                         pluralization);
1467             }
1468             break;
1469         case WAND:
1470             if (wandTable[theItem->kind].identified || rogue.playbackOmniscience) {
1471                 sprintf(root, "wand%s of %s",
1472                         pluralization,
1473                         wandTable[theItem->kind].name);
1474             } else if (wandTable[theItem->kind].called) {
1475                 sprintf(root, "wand%s called %s%s%s",
1476                         pluralization,
1477                         purpleEscapeSequence,
1478                         wandTable[theItem->kind].callTitle,
1479                         baseEscapeSequence);
1480             } else {
1481                 sprintf(root, "%s%s%s wand%s",
1482                         purpleEscapeSequence,
1483                         wandTable[theItem->kind].flavor,
1484                         baseEscapeSequence,
1485                         pluralization);
1486             }
1487             if (includeDetails) {
1488                 if (theItem->flags & (ITEM_IDENTIFIED | ITEM_MAX_CHARGES_KNOWN) || rogue.playbackOmniscience) {
1489                     sprintf(buf, "%s%s [%i]",
1490                             root,
1491                             grayEscapeSequence,
1492                             theItem->charges);
1493                     strcpy(root, buf);
1494                 } else if (theItem->enchant2 > 2) {
1495                     sprintf(buf, "%s%s (used %i times)",
1496                             root,
1497                             grayEscapeSequence,
1498                             theItem->enchant2);
1499                     strcpy(root, buf);
1500                 } else if (theItem->enchant2) {
1501                     sprintf(buf, "%s%s (used %s)",
1502                             root,
1503                             grayEscapeSequence,
1504                             (theItem->enchant2 == 2 ? "twice" : "once"));
1505                     strcpy(root, buf);
1506                 }
1507             }
1508             break;
1509         case STAFF:
1510             if (staffTable[theItem->kind].identified || rogue.playbackOmniscience) {
1511                 sprintf(root, "staff%s of %s", pluralization, staffTable[theItem->kind].name);
1512             } else if (staffTable[theItem->kind].called) {
1513                 sprintf(root, "staff%s called %s%s%s",
1514                         pluralization,
1515                         purpleEscapeSequence,
1516                         staffTable[theItem->kind].callTitle,
1517                         baseEscapeSequence);
1518             } else {
1519                 sprintf(root, "%s%s%s staff%s",
1520                         purpleEscapeSequence,
1521                         staffTable[theItem->kind].flavor,
1522                         baseEscapeSequence,
1523                         pluralization);
1524             }
1525             if (includeDetails) {
1526                 if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
1527                     sprintf(buf, "%s%s [%i/%i]", root, grayEscapeSequence, theItem->charges, theItem->enchant1);
1528                     strcpy(root, buf);
1529                 } else if (theItem->flags & ITEM_MAX_CHARGES_KNOWN) {
1530                     sprintf(buf, "%s%s [?/%i]", root, grayEscapeSequence, theItem->enchant1);
1531                     strcpy(root, buf);
1532                 }
1533             }
1534             break;
1535         case RING:
1536             if (ringTable[theItem->kind].identified || rogue.playbackOmniscience) {
1537                 sprintf(root, "ring%s of %s", pluralization, ringTable[theItem->kind].name);
1538             } else if (ringTable[theItem->kind].called) {
1539                 sprintf(root, "ring%s called %s%s%s",
1540                         pluralization,
1541                         purpleEscapeSequence,
1542                         ringTable[theItem->kind].callTitle,
1543                         baseEscapeSequence);
1544             } else {
1545                 sprintf(root, "%s%s%s ring%s",
1546                         purpleEscapeSequence,
1547                         ringTable[theItem->kind].flavor,
1548                         baseEscapeSequence,
1549                         pluralization);
1550             }
1551             if (includeDetails && ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience)) {
1552                 sprintf(buf, "%s%i %s", (theItem->enchant1 < 0 ? "" : "+"), theItem->enchant1, root);
1553                 strcpy(root, buf);
1554             }
1555             break;
1556         case CHARM:
1557             sprintf(root, "%s charm%s", charmTable[theItem->kind].name, pluralization);
1558 
1559             if (includeDetails) {
1560                 sprintf(buf, "%s%i %s", (theItem->enchant1 < 0 ? "" : "+"), theItem->enchant1, root);
1561                 strcpy(root, buf);
1562 
1563                 if (theItem->charges) {
1564                     sprintf(buf, "%s %s(%i%%)",
1565                             root,
1566                             grayEscapeSequence,
1567                             (charmRechargeDelay(theItem->kind, theItem->enchant1) - theItem->charges) * 100 / charmRechargeDelay(theItem->kind, theItem->enchant1));
1568                     strcpy(root, buf);
1569                 } else {
1570                     strcat(root, grayEscapeSequence);
1571                     strcat(root, " (ready)");
1572                 }
1573             }
1574             break;
1575         case GOLD:
1576             sprintf(root, "gold piece%s", pluralization);
1577             break;
1578         case AMULET:
1579             sprintf(root, "%sAmulet%s of Yendor%s", yellowEscapeSequence, pluralization, baseEscapeSequence);
1580             break;
1581         case GEM:
1582             sprintf(root, "%slumenstone%s%s from depth %i", yellowEscapeSequence, pluralization, baseEscapeSequence, theItem->originDepth);
1583             break;
1584         case KEY:
1585             if (includeDetails && theItem->originDepth > 0 && theItem->originDepth != rogue.depthLevel) {
1586                 sprintf(root, "%s%s%s from depth %i",
1587                         keyTable[theItem->kind].name,
1588                         pluralization,
1589                         grayEscapeSequence,
1590                         theItem->originDepth);
1591             } else {
1592                 sprintf(root,
1593                         keyTable[theItem->kind].name,
1594                         "%s%s",
1595                         pluralization);
1596             }
1597             break;
1598         default:
1599             sprintf(root, "unknown item%s", pluralization);
1600             break;
1601     }
1602 
1603     if (includeArticle) {
1604         // prepend number if quantity is over 1
1605         if (theItem->quantity > 1) {
1606             sprintf(article, "%i ", theItem->quantity);
1607         } else if (theItem->category & AMULET) {
1608             sprintf(article, "the ");
1609         } else if (!(theItem->category & ARMOR) && !(theItem->category & FOOD && theItem->kind == RATION)) {
1610             // otherwise prepend a/an if the item is not armor and not a ration of food;
1611             // armor gets no article, and "some food" was taken care of above.
1612             sprintf(article, "a%s ", (isVowelish(root) ? "n" : ""));
1613         }
1614     }
1615     // strcat(buf, suffixID);
1616     if (includeArticle) {
1617         sprintf(buf, "%s%s", article, root);
1618         strcpy(root, buf);
1619     }
1620 
1621     if (includeDetails && theItem->inscription[0]) {
1622         sprintf(buf, "%s \"%s\"", root, theItem->inscription);
1623         strcpy(root, buf);
1624     }
1625     return;
1626 }
1627 
itemKindName(item * theItem,char * kindName)1628 void itemKindName(item *theItem, char *kindName) {
1629 
1630     // use lookup table for randomly generated items with more than one kind per category
1631     if (theItem->category & (ARMOR | CHARM | FOOD | POTION | RING | SCROLL | STAFF | WAND | WEAPON)) {
1632         strcpy(kindName, tableForItemCategory(theItem->category, NULL)[theItem->kind].name);
1633     } else {
1634         switch (theItem->category) {
1635             case KEY:
1636                 strcpy(kindName, keyTable[theItem->kind].name); //keys are not randomly generated but have a lookup table
1637                 break;
1638             // these items have only one kind per category and no lookup table
1639             case GOLD:
1640                 strcpy(kindName, "gold pieces");
1641                 break;
1642             case AMULET:
1643                 strcpy(kindName, "amulet of yendor");
1644                 break;
1645             case GEM:
1646                 strcpy(kindName, "lumenstone");
1647                 break;
1648             default:
1649                 strcpy(kindName, "unknown");
1650                 break;
1651         }
1652     }
1653 }
1654 
itemRunicName(item * theItem,char * runicName)1655 void itemRunicName(item *theItem, char *runicName) {
1656     char vorpalEnemyMonsterClass[15] ="";
1657 
1658     if (theItem->flags & ITEM_RUNIC) {
1659         if ((theItem->category == ARMOR && theItem->enchant2 == A_IMMUNITY)
1660         || (theItem->category == WEAPON && theItem->enchant2 == W_SLAYING)) {
1661             sprintf(vorpalEnemyMonsterClass, "%s ", monsterClassCatalog[theItem->vorpalEnemy].name);
1662         }
1663         if (theItem->category == WEAPON) {
1664             sprintf(runicName, "%s%s", vorpalEnemyMonsterClass, weaponRunicNames[theItem->enchant2]);
1665         } else if (theItem->category == ARMOR) {
1666             sprintf(runicName, "%s%s", vorpalEnemyMonsterClass, armorRunicNames[theItem->enchant2]);
1667         }
1668     }
1669 }
1670 
1671 // kindCount is optional
tableForItemCategory(enum itemCategory theCat,short * kindCount)1672 itemTable *tableForItemCategory(enum itemCategory theCat, short *kindCount) {
1673     itemTable *returnedTable;
1674     short returnedCount;
1675     switch (theCat) {
1676         case FOOD:
1677             returnedTable = foodTable;
1678             returnedCount = NUMBER_FOOD_KINDS;
1679             break;
1680         case WEAPON:
1681             returnedTable = weaponTable;
1682             returnedCount = NUMBER_WEAPON_KINDS;
1683             break;
1684         case ARMOR:
1685             returnedTable = armorTable;
1686             returnedCount = NUMBER_ARMOR_KINDS;
1687             break;
1688         case POTION:
1689             returnedTable = potionTable;
1690             returnedCount = NUMBER_POTION_KINDS;
1691             break;
1692         case SCROLL:
1693             returnedTable = scrollTable;
1694             returnedCount = NUMBER_SCROLL_KINDS;
1695             break;
1696         case RING:
1697             returnedTable = ringTable;
1698             returnedCount = NUMBER_RING_KINDS;
1699             break;
1700         case WAND:
1701             returnedTable = wandTable;
1702             returnedCount = NUMBER_WAND_KINDS;
1703             break;
1704         case STAFF:
1705             returnedTable = staffTable;
1706             returnedCount = NUMBER_STAFF_KINDS;
1707             break;
1708         case CHARM:
1709             returnedTable = charmTable;
1710             returnedCount = NUMBER_CHARM_KINDS;
1711             break;
1712         default:
1713             returnedTable = NULL;
1714             returnedCount = 0;
1715             break;
1716     }
1717     if (kindCount) {
1718         *kindCount = returnedCount;
1719     }
1720     return returnedTable;
1721 }
1722 
isVowelish(char * theChar)1723 boolean isVowelish(char *theChar) {
1724     short i;
1725 
1726     while (*theChar == COLOR_ESCAPE) {
1727         theChar += 4;
1728     }
1729     char str[30];
1730     strncpy(str, theChar, 30);
1731     str[29] = '\0';
1732     for (i = 0; i < 29; i++) {
1733         upperCase(&(str[i]));
1734     }
1735     if (stringsMatch(str, "UNI")        // Words that start with "uni" aren't treated like vowels; e.g., "a" unicorn.
1736         || stringsMatch(str, "EU")) {   // Words that start with "eu" aren't treated like vowels; e.g., "a" eucalpytus staff.
1737 
1738         return false;
1739     } else {
1740         return (str[0] == 'A'
1741                 || str[0] == 'E'
1742                 || str[0] == 'I'
1743                 || str[0] == 'O'
1744                 || str[0] == 'U');
1745     }
1746 }
1747 
enchantIncrement(item * theItem)1748 fixpt enchantIncrement(item *theItem) {
1749     if (theItem->category & (WEAPON | ARMOR)) {
1750         if (theItem->strengthRequired == 0) {
1751             return FP_FACTOR;
1752         } else if (rogue.strength - player.weaknessAmount < theItem->strengthRequired) {
1753             return FP_FACTOR * 35 / 10;
1754         } else {
1755             return FP_FACTOR * 125 / 100;
1756         }
1757     } else {
1758         return FP_FACTOR;
1759     }
1760 }
1761 
itemIsCarried(item * theItem)1762 boolean itemIsCarried(item *theItem) {
1763     item *tempItem;
1764 
1765     for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
1766         if (tempItem == theItem) {
1767             return true;
1768         }
1769     }
1770     return false;
1771 }
1772 
effectiveRingEnchant(item * theItem)1773 short effectiveRingEnchant(item *theItem) {
1774     if (theItem->category != RING) {
1775         return 0;
1776     }
1777     if (!(theItem->flags & ITEM_IDENTIFIED)
1778         && theItem->enchant1 > 0) {
1779 
1780         return theItem->timesEnchanted + 1; // Unidentified positive rings act as +1 until identified.
1781     }
1782     return theItem->enchant1;
1783 }
1784 
apparentRingBonus(const enum ringKind kind)1785 short apparentRingBonus(const enum ringKind kind) {
1786     item *rings[2] = {rogue.ringLeft, rogue.ringRight}, *ring;
1787     short retval = 0;
1788     short i;
1789 
1790     if (ringTable[kind].identified) {
1791         for (i = 0; i < 2; i++) {
1792             ring = rings[i];
1793             if (ring && ring->kind == kind) {
1794                 retval += effectiveRingEnchant(ring);
1795             }
1796         }
1797     }
1798     return retval;
1799 }
1800 
itemDetails(char * buf,item * theItem)1801 void itemDetails(char *buf, item *theItem) {
1802     char buf2[1000], buf3[1000], theName[500], goodColorEscape[20], badColorEscape[20], whiteColorEscape[20];
1803     boolean singular, carried;
1804     fixpt enchant;
1805     fixpt currentDamage, newDamage;
1806     short nextLevelState = 0, new, current, accuracyChange, damageChange;
1807     unsigned long turnsSinceLatestUse;
1808     const char weaponRunicEffectDescriptions[NUMBER_WEAPON_RUNIC_KINDS][DCOLS] = {
1809         "time will stop while you take an extra turn",
1810         "the enemy will die instantly",
1811         "the enemy will be paralyzed",
1812         "[multiplicity]", // never used
1813         "the enemy will be slowed",
1814         "the enemy will be confused",
1815         "the enemy will be flung",
1816         "[slaying]", // never used
1817         "the enemy will be healed",
1818         "the enemy will be cloned"
1819     };
1820 
1821     goodColorEscape[0] = badColorEscape[0] = whiteColorEscape[0] = '\0';
1822     encodeMessageColor(goodColorEscape, 0, &goodMessageColor);
1823     encodeMessageColor(badColorEscape, 0, &badMessageColor);
1824     encodeMessageColor(whiteColorEscape, 0, &white);
1825 
1826     singular = (theItem->quantity == 1 ? true : false);
1827     carried = itemIsCarried(theItem);
1828 
1829     // Name
1830     itemName(theItem, theName, true, true, NULL);
1831     buf[0] = '\0';
1832     encodeMessageColor(buf, 0, &itemMessageColor);
1833     upperCase(theName);
1834     strcat(buf, theName);
1835     if (carried) {
1836         sprintf(buf2, " (%c)", theItem->inventoryLetter);
1837         strcat(buf, buf2);
1838     }
1839     encodeMessageColor(buf, strlen(buf), &white);
1840     strcat(buf, "\n\n");
1841 
1842     enchant = netEnchant(theItem);
1843 
1844     itemName(theItem, theName, false, false, NULL);
1845 
1846     // introductory text
1847     if (tableForItemCategory(theItem->category, NULL)
1848         && (tableForItemCategory(theItem->category, NULL)[theItem->kind].identified || rogue.playbackOmniscience)) {
1849 
1850         strcat(buf, tableForItemCategory(theItem->category, NULL)[theItem->kind].description);
1851 
1852         if (theItem->category == POTION && theItem->kind == POTION_LIFE) {
1853             sprintf(buf2, "\n\nIt will increase your maximum health by %s%i%%%s.",
1854                     goodColorEscape,
1855                     (player.info.maxHP + 10) * 100 / player.info.maxHP - 100,
1856                     whiteColorEscape);
1857             strcat(buf, buf2);
1858         }
1859     } else {
1860         switch (theItem->category) {
1861             case POTION:
1862                 sprintf(buf2, "%s flask%s contain%s a swirling %s liquid. Who knows what %s will do when drunk or thrown?",
1863                         (singular ? "This" : "These"),
1864                         (singular ? "" : "s"),
1865                         (singular ? "s" : ""),
1866                         tableForItemCategory(theItem->category, NULL)[theItem->kind].flavor,
1867                         (singular ? "it" : "they"));
1868                 break;
1869             case SCROLL:
1870                 sprintf(buf2, "%s parchment%s %s covered with indecipherable writing, and bear%s a title of \"%s.\" Who knows what %s will do when read aloud?",
1871                         (singular ? "This" : "These"),
1872                         (singular ? "" : "s"),
1873                         (singular ? "is" : "are"),
1874                         (singular ? "s" : ""),
1875                         tableForItemCategory(theItem->category, NULL)[theItem->kind].flavor,
1876                         (singular ? "it" : "they"));
1877                 break;
1878             case STAFF:
1879                 sprintf(buf2, "This gnarled %s staff is warm to the touch. Who knows what it will do when used?",
1880                         tableForItemCategory(theItem->category, NULL)[theItem->kind].flavor);
1881                 break;
1882             case WAND:
1883                 sprintf(buf2, "This thin %s wand is warm to the touch. Who knows what it will do when used?",
1884                         tableForItemCategory(theItem->category, NULL)[theItem->kind].flavor);
1885                 break;
1886             case RING:
1887                 sprintf(buf2, "This metal band is adorned with a%s %s gem that glitters in the darkness. Who knows what effect it has when worn? ",
1888                         isVowelish(tableForItemCategory(theItem->category, NULL)[theItem->kind].flavor) ? "n" : "",
1889                         tableForItemCategory(theItem->category, NULL)[theItem->kind].flavor);
1890                 break;
1891             case CHARM: // Should never be displayed.
1892                 strcat(buf2, "What a perplexing charm!");
1893                 break;
1894             case AMULET:
1895                 strcpy(buf2, "Legends are told about this mysterious golden amulet, and legions of adventurers have perished in its pursuit. Unfathomable riches await anyone with the skill and ambition to carry it into the light of day.");
1896                 break;
1897             case GEM:
1898                 sprintf(buf2, "Faint golden lights swirl and fluoresce beneath the stone%s surface. Lumenstones are said to contain mysterious properties of untold power, but for you, they mean one thing: riches.",
1899                         (singular ? "'s" : "s'"));
1900                 break;
1901             case KEY:
1902                 strcpy(buf2, keyTable[theItem->kind].description);
1903                 break;
1904             case GOLD:
1905                 sprintf(buf2, "A pile of %i shining gold coins.", theItem->quantity);
1906                 break;
1907             default:
1908                 break;
1909         }
1910         strcat(buf, buf2);
1911     }
1912 
1913     if (carried && theItem->originDepth > 0) {
1914         sprintf(buf2, " (You found %s on depth %i.) ",
1915                 singular ? "it" : "them",
1916                 theItem->originDepth);
1917         strcat(buf, buf2);
1918     }
1919 
1920     // detailed description
1921     switch (theItem->category) {
1922 
1923         case FOOD:
1924             sprintf(buf2, "\n\nYou are %shungry enough to fully enjoy a %s.",
1925                     ((STOMACH_SIZE - player.status[STATUS_NUTRITION]) >= foodTable[theItem->kind].strengthRequired ? "" : "not yet "),
1926                     foodTable[theItem->kind].name);
1927             strcat(buf, buf2);
1928             break;
1929 
1930         case WEAPON:
1931         case ARMOR:
1932             // enchanted? strength modifier?
1933             if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
1934                 if (theItem->enchant1) {
1935                     if (theItem->enchant1 > 0) {
1936                         sprintf(buf2, "\n\nThe %s bear%s an intrinsic enchantment of %s+%i%s",
1937                                 theName,
1938                                 (singular ? "s" : ""),
1939                                 goodColorEscape,
1940                                 theItem->enchant1,
1941                                 whiteColorEscape);
1942                     } else {
1943                         sprintf(buf2, "\n\nThe %s bear%s an intrinsic penalty of %s%i%s",
1944                                 theName,
1945                                 (singular ? "s" : ""),
1946                                 badColorEscape,
1947                                 theItem->enchant1,
1948                                 whiteColorEscape);
1949                     }
1950                 } else {
1951                     sprintf(buf2, "\n\nThe %s bear%s no intrinsic enchantment",
1952                             theName,
1953                             (singular ? "s" : ""));
1954                 }
1955                 strcat(buf, buf2);
1956                 if (strengthModifier(theItem)) {
1957                     sprintf(buf2, ", %s %s %s %s%s%+.2f%s because of your %s strength. ",
1958                             (theItem->enchant1 ? "and" : "but"),
1959                             (singular ? "carries" : "carry"),
1960                             (theItem->enchant1 && (theItem->enchant1 > 0) == (strengthModifier(theItem) > 0) ? "an additional" : "a"),
1961                             (strengthModifier(theItem) > 0 ? "bonus of " : "penalty of "),
1962                             (strengthModifier(theItem) > 0 ? goodColorEscape : badColorEscape),
1963                             strengthModifier(theItem) / (double) FP_FACTOR,
1964                             whiteColorEscape,
1965                             (strengthModifier(theItem) > 0 ? "excess" : "inadequate"));
1966                     strcat(buf, buf2);
1967                 } else {
1968                     strcat(buf, ". ");
1969                 }
1970             } else {
1971                 if ((theItem->enchant1 > 0) && (theItem->flags & ITEM_MAGIC_DETECTED)) {
1972                     sprintf(buf2, "\n\nYou can feel an %saura of benevolent magic%s radiating from the %s. ",
1973                             goodColorEscape,
1974                             whiteColorEscape,
1975                             theName);
1976                     strcat(buf, buf2);
1977                 }
1978                 if (strengthModifier(theItem)) {
1979                     sprintf(buf2, "\n\nThe %s %s%s a %s%s%+.2f%s because of your %s strength. ",
1980                             theName,
1981                             ((theItem->enchant1 > 0) && (theItem->flags & ITEM_MAGIC_DETECTED) ? "also " : ""),
1982                             (singular ? "carries" : "carry"),
1983                             (strengthModifier(theItem) > 0 ? "bonus of " : "penalty of "),
1984                             (strengthModifier(theItem) > 0 ? goodColorEscape : badColorEscape),
1985                             strengthModifier(theItem) / (double) FP_FACTOR,
1986                             whiteColorEscape,
1987                             (strengthModifier(theItem) > 0 ? "excess" : "inadequate"));
1988                     strcat(buf, buf2);
1989                 }
1990 
1991                 if (theItem->category & WEAPON) {
1992                     sprintf(buf2, "It will reveal its secrets if you defeat %i%s %s with it. ",
1993                             theItem->charges,
1994                             (theItem->charges == WEAPON_KILLS_TO_AUTO_ID ? "" : " more"),
1995                             (theItem->charges == 1 ? "enemy" : "enemies"));
1996                 } else {
1997                     sprintf(buf2, "It will reveal its secrets if worn for %i%s turn%s. ",
1998                             theItem->charges,
1999                             (theItem->charges == ARMOR_DELAY_TO_AUTO_ID ? "" : " more"),
2000                             (theItem->charges == 1 ? "" : "s"));
2001                 }
2002                 strcat(buf, buf2);
2003             }
2004 
2005             // Display the known percentage by which the armor/weapon will increase/decrease accuracy/damage/defense if not already equipped.
2006             if (!(theItem->flags & ITEM_EQUIPPED)) {
2007                 if (theItem->category & WEAPON) {
2008                     current = player.info.accuracy;
2009                     if (rogue.weapon) {
2010                         currentDamage = (rogue.weapon->damage.lowerBound + rogue.weapon->damage.upperBound) * FP_FACTOR / 2;
2011                         if ((rogue.weapon->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
2012                             current = current * accuracyFraction(netEnchant(rogue.weapon)) / FP_FACTOR;
2013                             currentDamage = currentDamage * damageFraction(netEnchant(rogue.weapon)) / FP_FACTOR;
2014                         } else {
2015                             current = current * accuracyFraction(strengthModifier(rogue.weapon)) / FP_FACTOR;
2016                             currentDamage = currentDamage * damageFraction(strengthModifier(rogue.weapon)) / FP_FACTOR;
2017                         }
2018                     } else {
2019                         currentDamage = (player.info.damage.lowerBound + player.info.damage.upperBound) * FP_FACTOR / 2;
2020                     }
2021 
2022                     new = player.info.accuracy;
2023                     newDamage = (theItem->damage.lowerBound + theItem->damage.upperBound) * FP_FACTOR / 2;
2024                     if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
2025                         new = new * accuracyFraction(netEnchant(theItem)) / FP_FACTOR;
2026                         newDamage = newDamage * damageFraction(netEnchant(theItem)) / FP_FACTOR;
2027                     } else {
2028                         new = new * accuracyFraction(strengthModifier(theItem)) / FP_FACTOR;
2029                         newDamage = newDamage * damageFraction(strengthModifier(theItem)) / FP_FACTOR;
2030                     }
2031                     accuracyChange  = (new * 100 / current) - 100;
2032                     damageChange    = (newDamage * 100 / currentDamage) - 100;
2033                     sprintf(buf2, "Wielding the %s%s will %s your current accuracy by %s%i%%%s, and will %s your current damage by %s%i%%%s. ",
2034                             theName,
2035                             ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) ? "" : ", assuming it has no hidden properties,",
2036                             (((short) accuracyChange) < 0) ? "decrease" : "increase",
2037                             (((short) accuracyChange) < 0) ? badColorEscape : (accuracyChange > 0 ? goodColorEscape : ""),
2038                             abs((short) accuracyChange),
2039                             whiteColorEscape,
2040                             (((short) damageChange) < 0) ? "decrease" : "increase",
2041                             (((short) damageChange) < 0) ? badColorEscape : (damageChange > 0 ? goodColorEscape : ""),
2042                             abs((short) damageChange),
2043                             whiteColorEscape);
2044                 } else {
2045                     new = theItem->armor;
2046                     if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
2047                         new += 10 * netEnchant(theItem) / FP_FACTOR;
2048                     } else {
2049                         new += 10 * strengthModifier(theItem) / FP_FACTOR;
2050                     }
2051                     new = max(0, new);
2052                     new /= 10;
2053                     sprintf(buf2, "Wearing the %s%s will result in an armor rating of %s%i%s. ",
2054                             theName,
2055                             ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) ? "" : ", assuming it has no hidden properties,",
2056                             (new > displayedArmorValue() ? goodColorEscape : (new < displayedArmorValue() ? badColorEscape : whiteColorEscape)),
2057                             new, whiteColorEscape);
2058                 }
2059                 strcat(buf, buf2);
2060             }
2061 
2062             // protected?
2063             if (theItem->flags & ITEM_PROTECTED) {
2064                 sprintf(buf2, "%sThe %s cannot be corroded by acid.%s ",
2065                         goodColorEscape,
2066                         theName,
2067                         whiteColorEscape);
2068                 strcat(buf, buf2);
2069             }
2070 
2071             // heavy armor?
2072             current = armorAggroAdjustment(rogue.armor);
2073             if ((theItem->category & ARMOR)
2074                 && !(theItem->flags & ITEM_EQUIPPED)
2075                 && (current != armorAggroAdjustment(theItem))) {
2076 
2077                 new = armorAggroAdjustment(theItem);
2078                 if (rogue.armor) {
2079                     new -= armorAggroAdjustment(rogue.armor);
2080                 }
2081                 sprintf(buf2, "Equipping the %s will %s%s your stealth range by %i%s. ",
2082                         theName,
2083                         new > 0 ? badColorEscape : goodColorEscape,
2084                         new > 0 ? "increase" : "decrease",
2085                         abs(new),
2086                         whiteColorEscape);
2087                 strcat(buf, buf2);
2088             }
2089 
2090             if (theItem->category & WEAPON) {
2091 
2092                 // runic?
2093                 if (theItem->flags & ITEM_RUNIC) {
2094                     if ((theItem->flags & ITEM_RUNIC_IDENTIFIED) || rogue.playbackOmniscience) {
2095                         sprintf(buf2, "\n\nGlowing runes of %s adorn the %s. ",
2096                                 weaponRunicNames[theItem->enchant2],
2097                                 theName);
2098                         strcat(buf, buf2);
2099                         if (theItem->enchant2 == W_SLAYING) {
2100                             describeMonsterClass(buf3, theItem->vorpalEnemy, false);
2101                             sprintf(buf2, "It will never fail to slay a%s %s in a single stroke. ",
2102                                     (isVowelish(buf3) ? "n" : ""),
2103                                     buf3);
2104                             strcat(buf, buf2);
2105                         } else if (theItem->enchant2 == W_MULTIPLICITY) {
2106                             if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
2107                                 sprintf(buf2, "%i%% of the time that it hits an enemy, %i spectral %s%s will spring into being with accuracy and attack power equal to your own, and will dissipate %i turns later. (If the %s is enchanted, %i image%s will appear %i%% of the time, and will last %i turns.)",
2108                                         runicWeaponChance(theItem, false, 0),
2109                                         weaponImageCount(enchant),
2110                                         theName,
2111                                         (weaponImageCount(enchant) > 1 ? "s" : ""),
2112                                         weaponImageDuration(enchant),
2113                                         theName,
2114                                         weaponImageCount(enchant + enchantIncrement(theItem)),
2115                                         (weaponImageCount(enchant + enchantIncrement(theItem)) > 1 ? "s" : ""),
2116                                         runicWeaponChance(theItem, true, enchant + enchantIncrement(theItem)),
2117                                         weaponImageDuration(enchant + enchantIncrement(theItem)));
2118                             } else {
2119                                 sprintf(buf2, "Sometimes, when it hits an enemy, spectral %ss will spring into being with accuracy and attack power equal to your own, and will dissipate shortly thereafter.",
2120                                         theName);
2121                             }
2122                             strcat(buf, buf2);
2123                         } else {
2124                             if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
2125                                 if (runicWeaponChance(theItem, false, 0) < 2
2126                                     && rogue.strength - player.weaknessAmount < theItem->strengthRequired) {
2127 
2128                                     strcpy(buf2, "Its runic effect will almost never activate because of your inadequate strength, but sometimes, when");
2129                                 } else {
2130                                     sprintf(buf2, "%i%% of the time that",
2131                                             runicWeaponChance(theItem, false, 0));
2132                                 }
2133                                 strcat(buf, buf2);
2134                             } else {
2135                                 strcat(buf, "Sometimes, when");
2136                             }
2137                             sprintf(buf2, " it hits an enemy, %s",
2138                                     weaponRunicEffectDescriptions[theItem->enchant2]);
2139                             strcat(buf, buf2);
2140 
2141                             if ((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience) {
2142                                 switch (theItem->enchant2) {
2143                                     case W_SPEED:
2144                                         strcat(buf, ". ");
2145                                         break;
2146                                     case W_PARALYSIS:
2147                                         sprintf(buf2, " for %i turns. ",
2148                                                 (int) (weaponParalysisDuration(enchant)));
2149                                         strcat(buf, buf2);
2150                                         nextLevelState = (int) (weaponParalysisDuration(enchant + enchantIncrement(theItem)));
2151                                         break;
2152                                     case W_SLOWING:
2153                                         sprintf(buf2, " for %i turns. ",
2154                                                 weaponSlowDuration(enchant));
2155                                         strcat(buf, buf2);
2156                                         nextLevelState = weaponSlowDuration(enchant + enchantIncrement(theItem));
2157                                         break;
2158                                     case W_CONFUSION:
2159                                         sprintf(buf2, " for %i turns. ",
2160                                                 weaponConfusionDuration(enchant));
2161                                         strcat(buf, buf2);
2162                                         nextLevelState = weaponConfusionDuration(enchant + enchantIncrement(theItem));
2163                                         break;
2164                                     case W_FORCE:
2165                                         sprintf(buf2, " up to %i spaces backward. If the enemy hits an obstruction, it (and any monster it hits) will take damage in proportion to the distance it flew. ",
2166                                                 weaponForceDistance(enchant));
2167                                         strcat(buf, buf2);
2168                                         nextLevelState = weaponForceDistance(enchant + enchantIncrement(theItem));
2169                                         break;
2170                                     case W_MERCY:
2171                                         strcpy(buf2, " by 50% of its maximum health. ");
2172                                         strcat(buf, buf2);
2173                                         break;
2174                                     default:
2175                                         strcpy(buf2, ". ");
2176                                         strcat(buf, buf2);
2177                                         break;
2178                                 }
2179 
2180                                 if (((theItem->flags & ITEM_IDENTIFIED) || rogue.playbackOmniscience)
2181                                     && runicWeaponChance(theItem, false, 0) < runicWeaponChance(theItem, true, enchant + enchantIncrement(theItem))){
2182                                     sprintf(buf2, "(If the %s is enchanted, the chance will increase to %i%%",
2183                                             theName,
2184                                             runicWeaponChance(theItem, true, enchant + enchantIncrement(theItem)));
2185                                     strcat(buf, buf2);
2186                                     if (nextLevelState) {
2187                                         if (theItem->enchant2 == W_FORCE) {
2188                                             sprintf(buf2, " and the distance will increase to %i.)",
2189                                                     nextLevelState);
2190                                         } else {
2191                                             sprintf(buf2, " and the duration will increase to %i turns.)",
2192                                                     nextLevelState);
2193                                         }
2194                                     } else {
2195                                         strcpy(buf2, ".)");
2196                                     }
2197                                     strcat(buf, buf2);
2198                                 }
2199                             } else {
2200                                 strcat(buf, ". ");
2201                             }
2202                         }
2203 
2204                     } else if (theItem->flags & ITEM_IDENTIFIED) {
2205                         sprintf(buf2, "\n\nGlowing runes of an indecipherable language run down the length of the %s. ",
2206                                 theName);
2207                         strcat(buf, buf2);
2208                     }
2209                 }
2210 
2211                 // equipped? cursed?
2212                 if (theItem->flags & ITEM_EQUIPPED) {
2213                     sprintf(buf2, "\n\nYou hold the %s at the ready%s. ",
2214                             theName,
2215                             ((theItem->flags & ITEM_CURSED) ? ", and because it is cursed, you are powerless to let go" : ""));
2216                     strcat(buf, buf2);
2217                 } else if (((theItem->flags & (ITEM_IDENTIFIED | ITEM_MAGIC_DETECTED)) || rogue.playbackOmniscience)
2218                            && (theItem->flags & ITEM_CURSED)) {
2219                     sprintf(buf2, "\n\n%sYou can feel a malevolent magic lurking within the %s.%s ",
2220                             badColorEscape,
2221                             theName,
2222                             whiteColorEscape);
2223                     strcat(buf, buf2);
2224                 }
2225 
2226             } else if (theItem->category & ARMOR) {
2227 
2228                 // runic?
2229                 if (theItem->flags & ITEM_RUNIC) {
2230                     if ((theItem->flags & ITEM_RUNIC_IDENTIFIED) || rogue.playbackOmniscience) {
2231                         sprintf(buf2, "\n\nGlowing runes of %s adorn the %s. ",
2232                                 armorRunicNames[theItem->enchant2],
2233                                 theName);
2234                         strcat(buf, buf2);
2235 
2236                         // A_MULTIPLICITY, A_MUTUALITY, A_ABSORPTION, A_REPRISAL, A_IMMUNITY, A_REFLECTION, A_BURDEN, A_VULNERABILITY, A_IMMOLATION
2237                         switch (theItem->enchant2) {
2238                             case A_MULTIPLICITY:
2239                                 sprintf(buf2, "When worn, 33%% of the time that an enemy's attack connects, %i allied spectral duplicate%s of your attacker will appear for 3 turns. ",
2240                                         armorImageCount(enchant),
2241                                         (armorImageCount(enchant) == 1 ? "" : "s"));
2242                                 if (armorImageCount(enchant + enchantIncrement(theItem)) > armorImageCount(enchant)) {
2243                                     sprintf(buf3, "(If the %s is enchanted, the number of duplicates will increase to %i.) ",
2244                                             theName,
2245                                             (armorImageCount(enchant + enchantIncrement(theItem))));
2246                                     strcat(buf2, buf3);
2247                                 }
2248                                 break;
2249                             case A_MUTUALITY:
2250                                 strcpy(buf2, "When worn, the damage that you incur from physical attacks will be split evenly among yourself and all other adjacent enemies. ");
2251                                 break;
2252                             case A_ABSORPTION:
2253                                 if (theItem->flags & ITEM_IDENTIFIED) {
2254                                     sprintf(buf2, "It will reduce the damage of inbound attacks by a random amount between 0 and %i, which is %i%% of your current maximum health. (If the %s is enchanted, this maximum amount will %s %i.) ",
2255                                             (int) armorAbsorptionMax(enchant),
2256                                             (int) (100 * armorAbsorptionMax(enchant) / player.info.maxHP),
2257                                             theName,
2258                                             (armorAbsorptionMax(enchant) == armorAbsorptionMax(enchant + enchantIncrement(theItem)) ? "remain at" : "increase to"),
2259                                             (int) armorAbsorptionMax(enchant + enchantIncrement(theItem)));
2260                                 } else {
2261                                     strcpy(buf2, "It will reduce the damage of inbound attacks by a random amount determined by its enchantment level. ");
2262                                 }
2263                                 break;
2264                             case A_REPRISAL:
2265                                 if (theItem->flags & ITEM_IDENTIFIED) {
2266                                     sprintf(buf2, "Any enemy that attacks you will itself be wounded by %i%% of the damage that it inflicts. (If the %s is enchanted, this percentage will increase to %i%%.) ",
2267                                             armorReprisalPercent(enchant),
2268                                             theName,
2269                                             armorReprisalPercent(enchant + enchantIncrement(theItem)));
2270                                 } else {
2271                                     strcpy(buf2, "Any enemy that attacks you will itself be wounded by a percentage (determined by enchantment level) of the damage that it inflicts. ");
2272                                 }
2273                                 break;
2274                             case A_IMMUNITY:
2275                                 describeMonsterClass(buf3, theItem->vorpalEnemy, false);
2276                                 sprintf(buf2, "It offers complete protection from any attacking %s. ",
2277                                         buf3);
2278                                 break;
2279                             case A_REFLECTION:
2280                                 if (theItem->flags & ITEM_IDENTIFIED) {
2281                                     if (theItem->enchant1 > 0) {
2282                                         short reflectChance = reflectionChance(enchant);
2283                                         short reflectChance2 = reflectionChance(enchant + enchantIncrement(theItem));
2284                                         sprintf(buf2, "When worn, you will deflect %i%% of incoming spells -- including directly back at their source %i%% of the time. (If the armor is enchanted, these will increase to %i%% and %i%%.) ",
2285                                                 reflectChance,
2286                                                 reflectChance * reflectChance / 100,
2287                                                 reflectChance2,
2288                                                 reflectChance2 * reflectChance2 / 100);
2289                                     } else if (theItem->enchant1 < 0) {
2290                                         short reflectChance = reflectionChance(enchant);
2291                                         short reflectChance2 = reflectionChance(enchant + enchantIncrement(theItem));
2292                                         sprintf(buf2, "When worn, %i%% of your own spells will deflect from their target -- including directly back at you %i%% of the time. (If the armor is enchanted, these will decrease to %i%% and %i%%.) ",
2293                                                 reflectChance,
2294                                                 reflectChance * reflectChance / 100,
2295                                                 reflectChance2,
2296                                                 reflectChance2 * reflectChance2 / 100);
2297                                     }
2298                                 } else {
2299                                     strcpy(buf2, "When worn, you will deflect some percentage of incoming spells, determined by enchantment level. ");
2300                                 }
2301                                 break;
2302                             case A_RESPIRATION:
2303                                 strcpy(buf2, "When worn, it will maintain a pocket of fresh air around you, rendering you immune to the effects of steam and all toxic gases. ");
2304                                 break;
2305                             case A_DAMPENING:
2306                                 strcpy(buf2, "When worn, it will safely absorb the concussive impact of any explosions (though you may still be burned). ");
2307                                 break;
2308                             case A_BURDEN:
2309                                 strcpy(buf2, "10% of the time it absorbs a blow, its strength requirement will permanently increase. ");
2310                                 break;
2311                             case A_VULNERABILITY:
2312                                 strcpy(buf2, "While it is worn, inbound attacks will inflict twice as much damage. ");
2313                                 break;
2314                             case A_IMMOLATION:
2315                                 strcpy(buf2, "10% of the time it absorbs a blow, it will explode in flames. ");
2316                                 break;
2317                             default:
2318                                 break;
2319                         }
2320                         strcat(buf, buf2);
2321                     } else if (theItem->flags & ITEM_IDENTIFIED) {
2322                         sprintf(buf2, "\n\nGlowing runes of an indecipherable language spiral around the %s. ",
2323                                 theName);
2324                         strcat(buf, buf2);
2325                     }
2326                 }
2327 
2328                 // equipped? cursed?
2329                 if (theItem->flags & ITEM_EQUIPPED) {
2330                     sprintf(buf2, "\n\nYou are wearing the %s%s. ",
2331                             theName,
2332                             ((theItem->flags & ITEM_CURSED) ? ", and because it is cursed, you are powerless to remove it" : ""));
2333                     strcat(buf, buf2);
2334                 } else if (((theItem->flags & (ITEM_IDENTIFIED | ITEM_MAGIC_DETECTED)) || rogue.playbackOmniscience)
2335                            && (theItem->flags & ITEM_CURSED)) {
2336                     sprintf(buf2, "\n\n%sYou can feel a malevolent magic lurking within the %s.%s ",
2337                             badColorEscape,
2338                             theName,
2339                             whiteColorEscape);
2340                     strcat(buf, buf2);
2341                 }
2342 
2343             }
2344             break;
2345 
2346         case STAFF:
2347 
2348             // charges
2349             new = apparentRingBonus(RING_WISDOM);
2350             if ((theItem->flags & ITEM_IDENTIFIED)  || rogue.playbackOmniscience) {
2351                 sprintf(buf2, "\n\nThe %s has %i charges remaining out of a maximum of %i charges, and%s recovers a charge in approximately %lli turns. ",
2352                         theName,
2353                         theItem->charges,
2354                         theItem->enchant1,
2355                         new == 0 ? "" : ", with your current rings,",
2356                         FP_DIV(staffChargeDuration(theItem), 10 * ringWisdomMultiplier(new * FP_FACTOR)));
2357                 strcat(buf, buf2);
2358             } else if (theItem->flags & ITEM_MAX_CHARGES_KNOWN) {
2359                 sprintf(buf2, "\n\nThe %s has a maximum of %i charges, and%s recovers a charge in approximately %lli turns. ",
2360                         theName,
2361                         theItem->enchant1,
2362                         new == 0 ? "" : ", with your current rings,",
2363                         FP_DIV(staffChargeDuration(theItem), 10 * ringWisdomMultiplier(new * FP_FACTOR)));
2364                 strcat(buf, buf2);
2365             }
2366 
2367             if (theItem->lastUsed[0] > 0 && theItem->lastUsed[1] > 0 && theItem->lastUsed[2] > 0) {
2368                 sprintf(buf2, "You last used it %li, %li and %li turns ago. ",
2369                         rogue.absoluteTurnNumber - theItem->lastUsed[0],
2370                         rogue.absoluteTurnNumber - theItem->lastUsed[1],
2371                         rogue.absoluteTurnNumber - theItem->lastUsed[2]);
2372                 strcat(buf, buf2);
2373             } else if (theItem->lastUsed[0] > 0 && theItem->lastUsed[1] > 0) {
2374                 sprintf(buf2, "You last used it %li and %li turns ago. ",
2375                         rogue.absoluteTurnNumber - theItem->lastUsed[0],
2376                         rogue.absoluteTurnNumber - theItem->lastUsed[1]);
2377                 strcat(buf, buf2);
2378             } else if (theItem->lastUsed[0] > 0) {
2379                 turnsSinceLatestUse = rogue.absoluteTurnNumber - theItem->lastUsed[0];
2380                 sprintf(buf2, "You last used it %li turn%s ago. ",
2381                         turnsSinceLatestUse,
2382                         turnsSinceLatestUse == 1 ? "" : "s");
2383                 strcat(buf, buf2);
2384             }
2385 
2386             // effect description
2387             if (((theItem->flags & (ITEM_IDENTIFIED | ITEM_MAX_CHARGES_KNOWN)) && staffTable[theItem->kind].identified)
2388                 || rogue.playbackOmniscience) {
2389                 switch (theItem->kind) {
2390                     case STAFF_LIGHTNING:
2391                         sprintf(buf2, "This staff deals damage to every creature in its line of fire; nothing is immune. (If the staff is enchanted, its average damage will increase by %i%%.)",
2392                                 (int) (100 * (staffDamageLow(enchant + FP_FACTOR) + staffDamageHigh(enchant + FP_FACTOR)) / (staffDamageLow(enchant) + staffDamageHigh(enchant)) - 100));
2393                         break;
2394                     case STAFF_FIRE:
2395                         sprintf(buf2, "This staff deals damage to any creature that it hits, unless the creature is immune to fire. (If the staff is enchanted, its average damage will increase by %i%%.) It also sets creatures and flammable terrain on fire.",
2396                                 (int) (100 * (staffDamageLow(enchant + FP_FACTOR) + staffDamageHigh(enchant + FP_FACTOR)) / (staffDamageLow(enchant) + staffDamageHigh(enchant)) - 100));
2397                         break;
2398                     case STAFF_POISON:
2399                         sprintf(buf2, "The bolt from this staff will poison any creature that it hits for %i turns. (If the staff is enchanted, this will increase to %i turns.)",
2400                                 staffPoison(enchant),
2401                                 staffPoison(enchant + FP_FACTOR));
2402                         break;
2403                     case STAFF_TUNNELING:
2404                         sprintf(buf2, "The bolt from this staff will dissolve %i layers of obstruction. (If the staff is enchanted, this will increase to %i layers.)",
2405                                 theItem->enchant1,
2406                                 theItem->enchant1 + 1);
2407                         break;
2408                     case STAFF_BLINKING:
2409                         sprintf(buf2, "This staff enables you to teleport up to %i spaces. (If the staff is enchanted, this will increase to %i spaces.)",
2410                                 staffBlinkDistance(enchant),
2411                                 staffBlinkDistance(enchant + FP_FACTOR));
2412                         break;
2413                     case STAFF_ENTRANCEMENT:
2414                         sprintf(buf2, "This staff will compel its target to mirror your movements for %i turns. (If the staff is enchanted, this will increase to %i turns.)",
2415                                 staffEntrancementDuration(enchant),
2416                                 staffEntrancementDuration(enchant + FP_FACTOR));
2417                         break;
2418                     case STAFF_HEALING:
2419                         if (enchant / FP_FACTOR < 10) {
2420                             sprintf(buf2, "This staff will heal its target by %i%% of its maximum health. (If the staff is enchanted, this will increase to %i%%.)",
2421                                     theItem->enchant1 * 10,
2422                                     (theItem->enchant1 + 1) * 10);
2423                         } else {
2424                             strcpy(buf2, "This staff will completely heal its target.");
2425                         }
2426                         break;
2427                     case STAFF_HASTE:
2428                         sprintf(buf2, "This staff will cause its target to move twice as fast for %i turns. (If the staff is enchanted, this will increase to %i turns.)",
2429                                 staffHasteDuration(enchant),
2430                                 staffHasteDuration(enchant + FP_FACTOR));
2431                         break;
2432                     case STAFF_OBSTRUCTION:
2433                         strcpy(buf2, "");
2434                         break;
2435                     case STAFF_DISCORD:
2436                         sprintf(buf2, "This staff will cause discord for %i turns. (If the staff is enchanted, this will increase to %i turns.)",
2437                                 staffDiscordDuration(enchant),
2438                                 staffDiscordDuration(enchant + FP_FACTOR));
2439                         break;
2440                     case STAFF_CONJURATION:
2441                         sprintf(buf2, "%i phantom blades will be called into service. (If the staff is enchanted, this will increase to %i blades.)",
2442                                 staffBladeCount(enchant),
2443                                 staffBladeCount(enchant + FP_FACTOR));
2444                         break;
2445                     case STAFF_PROTECTION:
2446                         sprintf(buf2, "This staff will shield a creature for up to 20 turns against up to %i damage. (If the staff is enchanted, this will increase to %i damage.)",
2447                                 staffProtection(enchant) / 10,
2448                                 staffProtection(enchant + FP_FACTOR) / 10);
2449                         break;
2450                     default:
2451                         strcpy(buf2, "No one knows what this staff does.");
2452                         break;
2453                 }
2454                 if (buf2[0]) {
2455                     strcat(buf, "\n\n");
2456                     strcat(buf, buf2);
2457                 }
2458             }
2459             break;
2460 
2461         case WAND:
2462             strcat(buf, "\n\n");
2463             if ((theItem->flags & (ITEM_IDENTIFIED | ITEM_MAX_CHARGES_KNOWN)) || rogue.playbackOmniscience) {
2464                 if (theItem->charges) {
2465                     sprintf(buf2, "%i charge%s remain%s. Enchanting this wand will add %i charge%s.",
2466                             theItem->charges,
2467                             (theItem->charges == 1 ? "" : "s"),
2468                             (theItem->charges == 1 ? "s" : ""),
2469                             wandTable[theItem->kind].range.lowerBound,
2470                             (wandTable[theItem->kind].range.lowerBound == 1 ? "" : "s"));
2471                 } else {
2472                     sprintf(buf2, "No charges remain.  Enchanting this wand will add %i charge%s.",
2473                             wandTable[theItem->kind].range.lowerBound,
2474                             (wandTable[theItem->kind].range.lowerBound == 1 ? "" : "s"));
2475                 }
2476             } else {
2477                 if (theItem->enchant2) {
2478                     sprintf(buf2, "You have used this wand %i time%s, but do not know how many charges, if any, remain.",
2479                             theItem->enchant2,
2480                             (theItem->enchant2 == 1 ? "" : "s"));
2481                 } else {
2482                     strcpy(buf2, "You have not yet used this wand.");
2483                 }
2484 
2485                 if (wandTable[theItem->kind].identified) {
2486                     strcat(buf, buf2);
2487                     sprintf(buf2, " Wands of this type can be found with %i to %i charges. Enchanting this wand will add %i charge%s.",
2488                             wandTable[theItem->kind].range.lowerBound,
2489                             wandTable[theItem->kind].range.upperBound,
2490                             wandTable[theItem->kind].range.lowerBound,
2491                             (wandTable[theItem->kind].range.lowerBound == 1 ? "" : "s"));
2492                 }
2493             }
2494             strcat(buf, buf2);
2495             break;
2496 
2497         case RING:
2498             if (((theItem->flags & ITEM_IDENTIFIED) && ringTable[theItem->kind].identified) || rogue.playbackOmniscience) {
2499                 if (theItem->enchant1) {
2500                     switch (theItem->kind) {
2501                         case RING_CLAIRVOYANCE:
2502                             if (theItem->enchant1 > 0) {
2503                                 sprintf(buf2, "\n\nThis ring provides magical sight with a radius of %i. (If the ring is enchanted, this will increase to %i.)",
2504                                         theItem->enchant1 + 1,
2505                                         theItem->enchant1 + 2);
2506                             } else {
2507                                 sprintf(buf2, "\n\nThis ring magically blinds you to a radius of %i. (If the ring is enchanted, this will decrease to %i.)",
2508                                         (theItem->enchant1 * -1) + 1,
2509                                         (theItem->enchant1 * -1));
2510                             }
2511                             strcat(buf, buf2);
2512                             break;
2513                         case RING_REGENERATION:
2514                             sprintf(buf2, "\n\nWith this ring equipped, you will regenerate all of your health in %li turns (instead of %li). (If the ring is enchanted, this will decrease to %li turns.)",
2515                                     (long) (turnsForFullRegenInThousandths(enchant) / 1000),
2516                                     (long) TURNS_FOR_FULL_REGEN,
2517                                     (long) (turnsForFullRegenInThousandths(enchant + FP_FACTOR) / 1000));
2518                             strcat(buf, buf2);
2519                             break;
2520                         case RING_TRANSFERENCE:
2521                             sprintf(buf2, "\n\nDealing direct damage to a creature (whether in melee or otherwise) will %s you by %i%% of the damage dealt. (If the ring is enchanted, this will %s to %i%%.)",
2522                                     (theItem->enchant1 >= 0 ? "heal" : "harm"),
2523                                     abs(theItem->enchant1) * 5,
2524                                     (theItem->enchant1 >= 0 ? "increase" : "decrease"),
2525                                     abs(theItem->enchant1 + 1) * 5);
2526                             strcat(buf, buf2);
2527                             break;
2528                         case RING_WISDOM:
2529                             sprintf(buf2, "\n\nWhen worn, your staffs will recharge at %i%% of their normal rate. (If the ring is enchanted, the rate will increase to %i%% of the normal rate.)",
2530                                     (int) (100 * ringWisdomMultiplier(enchant) / FP_FACTOR),
2531                                     (int) (100 * ringWisdomMultiplier(enchant + FP_FACTOR) / FP_FACTOR));
2532                             strcat(buf, buf2);
2533                             break;
2534                         case RING_REAPING:
2535                             sprintf(buf2, "\n\nEach blow that you land with a weapon will %s your staffs and charms by 0-%i turns per point of damage dealt. (If the ring is enchanted, this will %s to 0-%i turns per point of damage.)",
2536                                     (theItem->enchant1 >= 0 ? "recharge" : "drain"),
2537                                     abs(theItem->enchant1),
2538                                     (theItem->enchant1 >= 0 ? "increase" : "decrease"),
2539                                     abs(theItem->enchant1 + 1));
2540                             strcat(buf, buf2);
2541                             break;
2542                         default:
2543                             break;
2544                     }
2545                 }
2546             } else {
2547                 sprintf(buf2, "\n\nIt will reveal its secrets if worn for %i%s turn%s",
2548                         theItem->charges,
2549                         (theItem->charges == RING_DELAY_TO_AUTO_ID ? "" : " more"),
2550                         (theItem->charges == 1 ? "" : "s"));
2551                 strcat(buf, buf2);
2552 
2553                 if (!(theItem->flags & ITEM_IDENTIFIED) && (theItem->charges < RING_DELAY_TO_AUTO_ID || (theItem->flags & ITEM_MAGIC_DETECTED))) {
2554                     sprintf(buf2, ", and until then it will function, at best, as a +%i ring.", theItem->timesEnchanted + 1);
2555                     strcat(buf, buf2);
2556                 } else {
2557                     strcat(buf, ".");
2558                 }
2559             }
2560 
2561             // equipped? cursed?
2562             if (theItem->flags & ITEM_EQUIPPED) {
2563                 sprintf(buf2, "\n\nThe %s is on your finger%s. ",
2564                         theName,
2565                         ((theItem->flags & ITEM_CURSED) ? ", and because it is cursed, you are powerless to remove it" : ""));
2566                 strcat(buf, buf2);
2567             } else if (((theItem->flags & (ITEM_IDENTIFIED | ITEM_MAGIC_DETECTED)) || rogue.playbackOmniscience)
2568                        && (theItem->flags & ITEM_CURSED)) {
2569                 sprintf(buf2, "\n\n%sYou can feel a malevolent magic lurking within the %s.%s ",
2570                         badColorEscape,
2571                         theName,
2572                         whiteColorEscape);
2573                 strcat(buf, buf2);
2574             }
2575             break;
2576         case CHARM:
2577             switch (theItem->kind) {
2578                 case CHARM_HEALTH:
2579                     sprintf(buf2, "\n\nWhen used, the charm will heal %i%% of your health and recharge in %i turns. (If the charm is enchanted, it will heal %i%% of your health and recharge in %i turns.)",
2580                             charmHealing(enchant),
2581                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2582                             charmHealing(enchant + FP_FACTOR),
2583                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2584                     break;
2585                 case CHARM_PROTECTION:
2586                     sprintf(buf2, "\n\nWhen used, the charm will shield you for up to 20 turns for up to %i%% of your total health and recharge in %i turns. (If the charm is enchanted, it will shield up to %i%% of your total health and recharge in %i turns.)",
2587                             100 * charmProtection(enchant) / 10 / player.info.maxHP,
2588                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2589                             100 * charmProtection(enchant + FP_FACTOR) / 10 / player.info.maxHP,
2590                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2591                     break;
2592                 case CHARM_HASTE:
2593                     sprintf(buf2, "\n\nWhen used, the charm will haste you for %i turns and recharge in %i turns. (If the charm is enchanted, the haste will last %i turns and it will recharge in %i turns.)",
2594                             charmEffectDuration(theItem->kind, theItem->enchant1),
2595                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2596                             charmEffectDuration(theItem->kind, theItem->enchant1 + 1),
2597                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2598                     break;
2599                 case CHARM_FIRE_IMMUNITY:
2600                     sprintf(buf2, "\n\nWhen used, the charm will grant you immunity to fire for %i turns and recharge in %i turns. (If the charm is enchanted, the immunity will last %i turns and it will recharge in %i turns.)",
2601                             charmEffectDuration(theItem->kind, theItem->enchant1),
2602                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2603                             charmEffectDuration(theItem->kind, theItem->enchant1 + 1),
2604                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2605                     break;
2606                 case CHARM_INVISIBILITY:
2607                     sprintf(buf2, "\n\nWhen used, the charm will turn you invisible for %i turns and recharge in %i turns. While invisible, monsters more than two spaces away cannot track you. (If the charm is enchanted, the invisibility will last %i turns and it will recharge in %i turns.)",
2608                             charmEffectDuration(theItem->kind, theItem->enchant1),
2609                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2610                             charmEffectDuration(theItem->kind, theItem->enchant1 + 1),
2611                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2612                     break;
2613                 case CHARM_TELEPATHY:
2614                     sprintf(buf2, "\n\nWhen used, the charm will grant you telepathy for %i turns and recharge in %i turns. (If the charm is enchanted, the telepathy will last %i turns and it will recharge in %i turns.)",
2615                             charmEffectDuration(theItem->kind, theItem->enchant1),
2616                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2617                             charmEffectDuration(theItem->kind, theItem->enchant1 + 1),
2618                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2619                     break;
2620                 case CHARM_LEVITATION:
2621                     sprintf(buf2, "\n\nWhen used, the charm will lift you off the ground for %i turns and recharge in %i turns. (If the charm is enchanted, the levitation will last %i turns and it will recharge in %i turns.)",
2622                             charmEffectDuration(theItem->kind, theItem->enchant1),
2623                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2624                             charmEffectDuration(theItem->kind, theItem->enchant1 + 1),
2625                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2626                     break;
2627                 case CHARM_SHATTERING:
2628                     sprintf(buf2, "\n\nWhen used, the charm will dissolve the nearby walls up to %i spaces away, and recharge in %i turns. (If the charm is enchanted, it will reach up to %i spaces and recharge in %i turns.)",
2629                             charmShattering(enchant),
2630                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2631                             charmShattering(enchant + FP_FACTOR),
2632                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2633                     break;
2634                 case CHARM_GUARDIAN:
2635                     sprintf(buf2, "\n\nWhen used, a guardian will materialize for %i turns, and the charm will recharge in %i turns. (If the charm is enchanted, the guardian will last for %i turns and the charm will recharge in %i turns.)",
2636                             charmGuardianLifespan(enchant),
2637                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2638                             charmGuardianLifespan(enchant + FP_FACTOR),
2639                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2640                     break;
2641                 case CHARM_TELEPORTATION:
2642                     sprintf(buf2, "\n\nWhen used, the charm will teleport you elsewhere in the dungeon and recharge in %i turns. (If the charm is enchanted, it will recharge in %i turns.)",
2643                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2644                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2645                     break;
2646                 case CHARM_RECHARGING:
2647                     sprintf(buf2, "\n\nWhen used, the charm will recharge your staffs (though not your wands or charms), after which it will recharge in %i turns. (If the charm is enchanted, it will recharge in %i turns.)",
2648                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2649                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2650                     break;
2651                 case CHARM_NEGATION:
2652                     sprintf(buf2, "\n\nWhen used, the charm will negate all magical effects on the creatures in your field of view and the items on the ground up to %i spaces away, and recharge in %i turns. (If the charm is enchanted, it will reach up to %i spaces and recharge in %i turns.)",
2653                             charmNegationRadius(enchant),
2654                             charmRechargeDelay(theItem->kind, theItem->enchant1),
2655                             charmNegationRadius(enchant + FP_FACTOR),
2656                             charmRechargeDelay(theItem->kind, theItem->enchant1 + 1));
2657                     break;
2658                 default:
2659                     break;
2660             }
2661             strcat(buf, buf2);
2662             break;
2663         default:
2664             break;
2665     }
2666 }
2667 
displayMagicCharForItem(item * theItem)2668 boolean displayMagicCharForItem(item *theItem) {
2669     if (!(theItem->flags & ITEM_MAGIC_DETECTED)
2670         || (theItem->category & PRENAMED_CATEGORY)) {
2671         return false;
2672     } else {
2673         return true;
2674     }
2675 }
2676 
displayInventory(unsigned short categoryMask,unsigned long requiredFlags,unsigned long forbiddenFlags,boolean waitForAcknowledge,boolean includeButtons)2677 char displayInventory(unsigned short categoryMask,
2678                       unsigned long requiredFlags,
2679                       unsigned long forbiddenFlags,
2680                       boolean waitForAcknowledge,
2681                       boolean includeButtons) {
2682     item *theItem;
2683     short i, j, m, maxLength = 0, itemNumber, itemCount, equippedItemCount;
2684     short extraLineCount = 0;
2685     item *itemList[DROWS];
2686     char buf[COLS*3];
2687     char theKey;
2688     rogueEvent theEvent;
2689     boolean magicDetected, repeatDisplay;
2690     short highlightItemLine, itemSpaceRemaining;
2691     cellDisplayBuffer dbuf[COLS][ROWS];
2692     cellDisplayBuffer rbuf[COLS][ROWS];
2693     brogueButton buttons[50] = {{{0}}};
2694     short actionKey = -1;
2695     color darkItemColor;
2696 
2697     char whiteColorEscapeSequence[20],
2698     grayColorEscapeSequence[20],
2699     yellowColorEscapeSequence[20],
2700     darkYellowColorEscapeSequence[20],
2701     goodColorEscapeSequence[20],
2702     badColorEscapeSequence[20];
2703     char *magicEscapePtr;
2704 
2705     assureCosmeticRNG;
2706 
2707     clearCursorPath();
2708     clearDisplayBuffer(dbuf);
2709 
2710     whiteColorEscapeSequence[0] = '\0';
2711     encodeMessageColor(whiteColorEscapeSequence, 0, &white);
2712     grayColorEscapeSequence[0] = '\0';
2713     encodeMessageColor(grayColorEscapeSequence, 0, &gray);
2714     yellowColorEscapeSequence[0] = '\0';
2715     encodeMessageColor(yellowColorEscapeSequence, 0, &itemColor);
2716     darkItemColor = itemColor;
2717     applyColorAverage(&darkItemColor, &black, 50);
2718     darkYellowColorEscapeSequence[0] = '\0';
2719     encodeMessageColor(darkYellowColorEscapeSequence, 0, &darkItemColor);
2720     goodColorEscapeSequence[0] = '\0';
2721     encodeMessageColor(goodColorEscapeSequence, 0, &goodMessageColor);
2722     badColorEscapeSequence[0] = '\0';
2723     encodeMessageColor(badColorEscapeSequence, 0, &badMessageColor);
2724 
2725     if (packItems->nextItem == NULL) {
2726         confirmMessages();
2727         message("Your pack is empty!", 0);
2728         restoreRNG;
2729         return 0;
2730     }
2731 
2732     magicDetected = false;
2733     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
2734         if (displayMagicCharForItem(theItem) && (theItem->flags & ITEM_MAGIC_DETECTED)) {
2735             magicDetected = true;
2736         }
2737     }
2738 
2739     // List the items in the order we want to display them, with equipped items at the top.
2740     itemNumber = 0;
2741     equippedItemCount = 0;
2742     // First, the equipped weapon if any.
2743     if (rogue.weapon) {
2744         itemList[itemNumber] = rogue.weapon;
2745         itemNumber++;
2746         equippedItemCount++;
2747     }
2748     // Now, the equipped armor if any.
2749     if (rogue.armor) {
2750         itemList[itemNumber] = rogue.armor;
2751         itemNumber++;
2752         equippedItemCount++;
2753     }
2754     // Now, the equipped rings, if any.
2755     if (rogue.ringLeft) {
2756         itemList[itemNumber] = rogue.ringLeft;
2757         itemNumber++;
2758         equippedItemCount++;
2759     }
2760     if (rogue.ringRight) {
2761         itemList[itemNumber] = rogue.ringRight;
2762         itemNumber++;
2763         equippedItemCount++;
2764     }
2765     // Now all of the non-equipped items.
2766     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
2767         if (!(theItem->flags & ITEM_EQUIPPED)) {
2768             itemList[itemNumber] = theItem;
2769             itemNumber++;
2770         }
2771     }
2772 
2773     // Initialize the buttons:
2774     for (i=0; i < max(MAX_PACK_ITEMS, ROWS); i++) {
2775         buttons[i].y = mapToWindowY(i + (equippedItemCount && i >= equippedItemCount ? 1 : 0));
2776         buttons[i].buttonColor = black;
2777         buttons[i].opacity = INTERFACE_OPACITY;
2778         buttons[i].flags |= B_DRAW;
2779     }
2780     // Now prepare the buttons.
2781     const char closeParen = KEYBOARD_LABELS ? ')' : ' ';
2782     for (i=0; i<itemNumber; i++) {
2783         theItem = itemList[i];
2784         // Set button parameters for the item:
2785         buttons[i].flags |= (B_DRAW | B_GRADIENT | B_ENABLED);
2786         if (!waitForAcknowledge) {
2787             buttons[i].flags |= B_KEYPRESS_HIGHLIGHT;
2788         }
2789         buttons[i].hotkey[0] = theItem->inventoryLetter;
2790         buttons[i].hotkey[1] = theItem->inventoryLetter + 'A' - 'a';
2791 
2792         if ((theItem->category & categoryMask) &&
2793             !(~(theItem->flags) & requiredFlags) &&
2794             !(theItem->flags & forbiddenFlags)) {
2795 
2796             buttons[i].flags |= (B_HOVER_ENABLED);
2797         }
2798 
2799         // Set the text for the button:
2800         itemName(theItem, buf, true, true, (buttons[i].flags & B_HOVER_ENABLED) ? &white : &gray);
2801         upperCase(buf);
2802 
2803         if ((theItem->flags & ITEM_MAGIC_DETECTED)
2804             && !(theItem->category & AMULET)) { // Won't include food, keys, lumenstones or amulet.
2805 
2806             int polarity = itemMagicPolarity(theItem);
2807             if (polarity == 0) {
2808                 buttons[i].symbol[0] = '-';
2809                 magicEscapePtr = yellowColorEscapeSequence;
2810             } else if (polarity == 1) {
2811                 buttons[i].symbol[0] = G_GOOD_MAGIC;
2812                 magicEscapePtr = goodColorEscapeSequence;
2813             } else {
2814                 buttons[i].symbol[0] = G_BAD_MAGIC;
2815                 magicEscapePtr = badColorEscapeSequence;
2816             }
2817 
2818             // The first '*' is the magic detection symbol, e.g. '-' for non-magical.
2819             // The second '*' is the item character, e.g. ':' for food.
2820             sprintf(buttons[i].text, " %c%c %s* %s* %s%s%s%s",
2821                     KEYBOARD_LABELS ? theItem->inventoryLetter : ' ',
2822                     (theItem->flags & ITEM_PROTECTED ? '}' : closeParen),
2823                     magicEscapePtr,
2824                     (buttons[i].flags & B_HOVER_ENABLED) ? yellowColorEscapeSequence : darkYellowColorEscapeSequence,
2825                     (buttons[i].flags & B_HOVER_ENABLED) ? whiteColorEscapeSequence : grayColorEscapeSequence,
2826                     buf,
2827                     grayColorEscapeSequence,
2828                     (theItem->flags & ITEM_EQUIPPED ? ((theItem->category & WEAPON) ? " (in hand) " : " (worn) ") : ""));
2829             buttons[i].symbol[1] = theItem->displayChar;
2830         } else {
2831             sprintf(buttons[i].text, " %c%c %s%s* %s%s%s%s", // The '*' is the item character, e.g. ':' for food.
2832                     KEYBOARD_LABELS ? theItem->inventoryLetter : ' ',
2833                     (theItem->flags & ITEM_PROTECTED ? '}' : closeParen),
2834                     (magicDetected ? "  " : ""), // For proper spacing when this item is not detected but another is.
2835                     (buttons[i].flags & B_HOVER_ENABLED) ? yellowColorEscapeSequence : darkYellowColorEscapeSequence,
2836                     (buttons[i].flags & B_HOVER_ENABLED) ? whiteColorEscapeSequence : grayColorEscapeSequence,
2837                     buf,
2838                     grayColorEscapeSequence,
2839                     (theItem->flags & ITEM_EQUIPPED ? ((theItem->category & WEAPON) ? " (in hand) " : " (worn) ") : ""));
2840             buttons[i].symbol[0] = theItem->displayChar;
2841         }
2842 
2843         // Keep track of the maximum width needed:
2844         maxLength = max(maxLength, strLenWithoutEscapes(buttons[i].text));
2845 
2846         //      itemList[itemNumber] = theItem;
2847         //
2848         //      itemNumber++;
2849     }
2850     //printf("\nMaxlength: %i", maxLength);
2851     itemCount = itemNumber;
2852     if (!itemNumber) {
2853         confirmMessages();
2854         message("Nothing of that type!", 0);
2855         restoreRNG;
2856         return 0;
2857     }
2858     if (waitForAcknowledge) {
2859         // Add the two extra lines as disabled buttons.
2860         itemSpaceRemaining = MAX_PACK_ITEMS - numberOfItemsInPack();
2861         if (itemSpaceRemaining) {
2862             sprintf(buttons[itemNumber + extraLineCount].text, "%s%s    You have room for %i more item%s.",
2863                     grayColorEscapeSequence,
2864                     (magicDetected ? "  " : ""),
2865                     itemSpaceRemaining,
2866                     (itemSpaceRemaining == 1 ? "" : "s"));
2867         } else {
2868             sprintf(buttons[itemNumber + extraLineCount].text, "%s%s    Your pack is full.",
2869                     grayColorEscapeSequence,
2870                     (magicDetected ? "  " : ""));
2871         }
2872         maxLength = max(maxLength, (strLenWithoutEscapes(buttons[itemNumber + extraLineCount].text)));
2873         extraLineCount++;
2874 
2875         sprintf(buttons[itemNumber + extraLineCount].text,
2876                 KEYBOARD_LABELS ? "%s%s -- press (a-z) for more info -- " : "%s%s -- touch an item for more info -- ",
2877                 grayColorEscapeSequence,
2878                 (magicDetected ? "  " : ""));
2879         maxLength = max(maxLength, (strLenWithoutEscapes(buttons[itemNumber + extraLineCount].text)));
2880         extraLineCount++;
2881     }
2882     if (equippedItemCount) {
2883         // Add a separator button to fill in the blank line between equipped and unequipped items.
2884         sprintf(buttons[itemNumber + extraLineCount].text, "      %s%s---",
2885                 (magicDetected ? "  " : ""),
2886                 grayColorEscapeSequence);
2887         buttons[itemNumber + extraLineCount].y = mapToWindowY(equippedItemCount);
2888         extraLineCount++;
2889     }
2890 
2891     for (i=0; i < itemNumber + extraLineCount; i++) {
2892 
2893         // Position the button.
2894         buttons[i].x = COLS - maxLength;
2895 
2896         // Pad the button label with space, so the button reaches to the right edge of the screen.
2897         m = strlen(buttons[i].text);
2898         for (j=buttons[i].x + strLenWithoutEscapes(buttons[i].text); j < COLS; j++) {
2899             buttons[i].text[m] = ' ';
2900             m++;
2901         }
2902         buttons[i].text[m] = '\0';
2903 
2904         // Display the button. This would be redundant with the button loop,
2905         // except that we want the display to stick around until we get rid of it.
2906         drawButton(&(buttons[i]), BUTTON_NORMAL, dbuf);
2907     }
2908 
2909     // Add invisible previous and next buttons, so up and down arrows can select items.
2910     // Previous
2911     buttons[itemNumber + extraLineCount + 0].flags = B_ENABLED; // clear everything else
2912     buttons[itemNumber + extraLineCount + 0].hotkey[0] = NUMPAD_8;
2913     buttons[itemNumber + extraLineCount + 0].hotkey[1] = UP_ARROW;
2914     // Next
2915     buttons[itemNumber + extraLineCount + 1].flags = B_ENABLED; // clear everything else
2916     buttons[itemNumber + extraLineCount + 1].hotkey[0] = NUMPAD_2;
2917     buttons[itemNumber + extraLineCount + 1].hotkey[1] = DOWN_ARROW;
2918 
2919     overlayDisplayBuffer(dbuf, rbuf);
2920 
2921     do {
2922         repeatDisplay = false;
2923 
2924         // Do the button loop.
2925         highlightItemLine = -1;
2926         overlayDisplayBuffer(rbuf, NULL);   // Remove the inventory display while the buttons are active,
2927                                             // since they look the same and we don't want their opacities to stack.
2928 
2929         highlightItemLine = buttonInputLoop(buttons,
2930                                             itemCount + extraLineCount + 2, // the 2 is for up/down hotkeys
2931                                             COLS - maxLength,
2932                                             mapToWindowY(0),
2933                                             maxLength,
2934                                             itemNumber + extraLineCount,
2935                                             &theEvent);
2936         if (highlightItemLine == itemNumber + extraLineCount + 0) {
2937             // Up key
2938             highlightItemLine = itemNumber - 1;
2939             theEvent.shiftKey = true;
2940         } else if (highlightItemLine == itemNumber + extraLineCount + 1) {
2941             // Down key
2942             highlightItemLine = 0;
2943             theEvent.shiftKey = true;
2944         }
2945 
2946         if (highlightItemLine >= 0) {
2947             theKey = itemList[highlightItemLine]->inventoryLetter;
2948             theItem = itemList[highlightItemLine];
2949         } else {
2950             theKey = ESCAPE_KEY;
2951         }
2952 
2953         // Was an item selected?
2954         if (highlightItemLine > -1 && (waitForAcknowledge || theEvent.shiftKey || theEvent.controlKey)) {
2955 
2956             do {
2957                 // Yes. Highlight the selected item. Do this by changing the button color and re-displaying it.
2958 
2959                 overlayDisplayBuffer(dbuf, NULL);
2960 
2961                 //buttons[highlightItemLine].buttonColor = interfaceBoxColor;
2962                 drawButton(&(buttons[highlightItemLine]), BUTTON_PRESSED, NULL);
2963                 //buttons[highlightItemLine].buttonColor = black;
2964 
2965                 if (theEvent.shiftKey || theEvent.controlKey || waitForAcknowledge) {
2966                     // Display an information window about the item.
2967                     actionKey = printCarriedItemDetails(theItem, max(2, mapToWindowX(DCOLS - maxLength - 42)), mapToWindowY(2), 40, includeButtons, NULL);
2968 
2969                     overlayDisplayBuffer(rbuf, NULL); // remove the item info window
2970 
2971                     if (actionKey == -1) {
2972                         repeatDisplay = true;
2973                         overlayDisplayBuffer(dbuf, NULL); // redisplay the inventory
2974                     } else {
2975                         restoreRNG;
2976                         repeatDisplay = false;
2977                         overlayDisplayBuffer(rbuf, NULL); // restore the original screen
2978                     }
2979 
2980                     switch (actionKey) {
2981                         case APPLY_KEY:
2982                             apply(theItem, true);
2983                             break;
2984                         case EQUIP_KEY:
2985                             equip(theItem);
2986                             break;
2987                         case UNEQUIP_KEY:
2988                             unequip(theItem);
2989                             break;
2990                         case DROP_KEY:
2991                             drop(theItem);
2992                             break;
2993                         case THROW_KEY:
2994                             throwCommand(theItem, false);
2995                             break;
2996                         case RELABEL_KEY:
2997                             relabel(theItem);
2998                             break;
2999                         case CALL_KEY:
3000                             call(theItem);
3001                             break;
3002                         case UP_KEY:
3003                             highlightItemLine = highlightItemLine - 1;
3004                             if (highlightItemLine < 0) {
3005                                 highlightItemLine = itemNumber - 1;
3006                             }
3007                             break;
3008                         case DOWN_KEY:
3009                             highlightItemLine = highlightItemLine + 1;
3010                             if (highlightItemLine >= itemNumber) {
3011                                 highlightItemLine = 0;
3012                             }
3013                             break;
3014                         default:
3015                             break;
3016                     }
3017 
3018                     if (actionKey == UP_KEY || actionKey == DOWN_KEY) {
3019                         theKey = itemList[highlightItemLine]->inventoryLetter;
3020                         theItem = itemList[highlightItemLine];
3021                     } else if (actionKey > -1) {
3022                         // Player took an action directly from the item screen; we're done here.
3023                         restoreRNG;
3024                         return 0;
3025                     }
3026                 }
3027             } while (actionKey == UP_KEY || actionKey == DOWN_KEY);
3028         }
3029     } while (repeatDisplay); // so you can get info on multiple items sequentially
3030 
3031     overlayDisplayBuffer(rbuf, NULL); // restore the original screen
3032 
3033     restoreRNG;
3034     return theKey;
3035 }
3036 
numberOfMatchingPackItems(unsigned short categoryMask,unsigned long requiredFlags,unsigned long forbiddenFlags,boolean displayErrors)3037 short numberOfMatchingPackItems(unsigned short categoryMask,
3038                                 unsigned long requiredFlags, unsigned long forbiddenFlags,
3039                                 boolean displayErrors) {
3040     item *theItem;
3041     short matchingItemCount = 0;
3042 
3043     if (packItems->nextItem == NULL) {
3044         if (displayErrors) {
3045             confirmMessages();
3046             message("Your pack is empty!", 0);
3047         }
3048         return 0;
3049     }
3050 
3051     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
3052 
3053         if (theItem->category & categoryMask &&
3054             !(~(theItem->flags) & requiredFlags) &&
3055             !(theItem->flags & forbiddenFlags)) {
3056 
3057             matchingItemCount++;
3058         }
3059     }
3060 
3061     if (matchingItemCount == 0) {
3062         if (displayErrors) {
3063             confirmMessages();
3064             message("You have nothing suitable.", 0);
3065         }
3066         return 0;
3067     }
3068 
3069     return matchingItemCount;
3070 }
3071 
updateEncumbrance()3072 void updateEncumbrance() {
3073     short moveSpeed, attackSpeed;
3074 
3075     moveSpeed = player.info.movementSpeed;
3076     attackSpeed = player.info.attackSpeed;
3077 
3078     if (player.status[STATUS_HASTED]) {
3079         moveSpeed /= 2;
3080         attackSpeed /= 2;
3081     } else if (player.status[STATUS_SLOWED]) {
3082         moveSpeed *= 2;
3083         attackSpeed *= 2;
3084     }
3085 
3086     player.movementSpeed = moveSpeed;
3087     player.attackSpeed = attackSpeed;
3088 
3089     recalculateEquipmentBonuses();
3090 }
3091 
displayedArmorValue()3092 short displayedArmorValue() {
3093     if (!rogue.armor || (rogue.armor->flags & ITEM_IDENTIFIED)) {
3094         return player.info.defense / 10;
3095     } else {
3096         return ((armorTable[rogue.armor->kind].range.upperBound + armorTable[rogue.armor->kind].range.lowerBound) * FP_FACTOR / 2 / 10
3097                 + strengthModifier(rogue.armor)) / FP_FACTOR;
3098     }
3099 }
3100 
strengthCheck(item * theItem,boolean noisy)3101 void strengthCheck(item *theItem, boolean noisy) {
3102     char buf1[COLS], buf2[COLS*2];
3103     short strengthDeficiency;
3104 
3105     updateEncumbrance();
3106     if (noisy && theItem) {
3107         if (theItem->category & WEAPON && theItem->strengthRequired > rogue.strength - player.weaknessAmount) {
3108             strengthDeficiency = theItem->strengthRequired - max(0, rogue.strength - player.weaknessAmount);
3109             strcpy(buf1, "");
3110             itemName(theItem, buf1, false, false, NULL);
3111             sprintf(buf2, "You can barely lift the %s; %i more strength would be ideal.", buf1, strengthDeficiency);
3112             message(buf2, 0);
3113         }
3114 
3115         if (theItem->category & ARMOR && theItem->strengthRequired > rogue.strength - player.weaknessAmount) {
3116             strengthDeficiency = theItem->strengthRequired - max(0, rogue.strength - player.weaknessAmount);
3117             strcpy(buf1, "");
3118             itemName(theItem, buf1, false, false, NULL);
3119             sprintf(buf2, "You stagger under the weight of the %s; %i more strength would be ideal.",
3120                     buf1, strengthDeficiency);
3121             message(buf2, 0);
3122         }
3123     }
3124 }
3125 
3126 // Will prompt for an item if none is given.
3127 // Equips the item and records input if successful.
3128 // Player's failure to select an item will result in failure.
3129 // Failure does not record input.
equip(item * theItem)3130 void equip(item *theItem) {
3131     unsigned char command[10];
3132     short c = 0;
3133     item *theItem2;
3134 
3135     command[c++] = EQUIP_KEY;
3136     if (!theItem) {
3137         theItem = promptForItemOfType((WEAPON|ARMOR|RING), 0, ITEM_EQUIPPED,
3138                                       KEYBOARD_LABELS ? "Equip what? (a-z, shift for more info; or <esc> to cancel)" : "Equip what?", true);
3139     }
3140     if (theItem == NULL) {
3141         return;
3142     }
3143 
3144     theItem2 = NULL;
3145     command[c++] = theItem->inventoryLetter;
3146 
3147     if (theItem->category & (WEAPON|ARMOR|RING)) {
3148 
3149         if (theItem->category & RING) {
3150             if (theItem->flags & ITEM_EQUIPPED) {
3151                 confirmMessages();
3152                 message("you are already wearing that ring.", 0);
3153                 return;
3154             } else if (rogue.ringLeft && rogue.ringRight) {
3155                 confirmMessages();
3156                 theItem2 = promptForItemOfType((RING), ITEM_EQUIPPED, 0,
3157                                                "You are already wearing two rings; remove which first?", true);
3158                 if (!theItem2 || theItem2->category != RING || !(theItem2->flags & ITEM_EQUIPPED)) {
3159                     if (theItem2) { // No message if canceled or did an inventory action instead.
3160                         message("Invalid entry.", 0);
3161                     }
3162                     return;
3163                 } else {
3164                     command[c++] = theItem2->inventoryLetter;
3165                 }
3166             }
3167         }
3168 
3169         if (theItem->flags & ITEM_EQUIPPED) {
3170             confirmMessages();
3171             message("already equipped.", 0);
3172             return;
3173         }
3174 
3175         if (theItem->category & (WEAPON | ARMOR)) {
3176             // Swapped out rings are handled above
3177             theItem2 = theItem->category & WEAPON ? rogue.weapon : rogue.armor;
3178         }
3179 
3180         if (!equipItem(theItem, false, theItem2)) {
3181             return; // equip failed because current item is cursed
3182         }
3183 
3184         command[c] = '\0';
3185         recordKeystrokeSequence(command);
3186 
3187         // Something is only swapped in if something else swapped out
3188         rogue.swappedOut = theItem2;
3189         rogue.swappedIn = rogue.swappedOut ? theItem : NULL;
3190 
3191         playerTurnEnded();
3192     } else {
3193         confirmMessages();
3194         message("You can't equip that.", 0);
3195     }
3196 }
3197 
3198 // Returns whether the given item is a key that can unlock the given location.
3199 // An item qualifies if:
3200 // (1) it's a key (has ITEM_IS_KEY flag),
3201 // (2) its originDepth matches the depth, and
3202 // (3) either its key (x, y) location matches (x, y), or its machine number matches the machine number at (x, y).
keyMatchesLocation(item * theItem,short x,short y)3203 boolean keyMatchesLocation(item *theItem, short x, short y) {
3204     short i;
3205 
3206     if ((theItem->flags & ITEM_IS_KEY)
3207         && theItem->originDepth == rogue.depthLevel) {
3208 
3209         for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++) {
3210             if (theItem->keyLoc[i].x == x && theItem->keyLoc[i].y == y) {
3211                 return true;
3212             } else if (theItem->keyLoc[i].machine == pmap[x][y].machineNumber) {
3213                 return true;
3214             }
3215         }
3216     }
3217     return false;
3218 }
3219 
keyInPackFor(short x,short y)3220 item *keyInPackFor(short x, short y) {
3221     item *theItem;
3222 
3223     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
3224         if (keyMatchesLocation(theItem, x, y)) {
3225             return theItem;
3226         }
3227     }
3228     return NULL;
3229 }
3230 
keyOnTileAt(short x,short y)3231 item *keyOnTileAt(short x, short y) {
3232     item *theItem;
3233     creature *monst;
3234 
3235     if ((pmap[x][y].flags & HAS_PLAYER)
3236         && player.xLoc == x
3237         && player.yLoc == y
3238         && keyInPackFor(x, y)) {
3239 
3240         return keyInPackFor(x, y);
3241     }
3242     if (pmap[x][y].flags & HAS_ITEM) {
3243         theItem = itemAtLoc(x, y);
3244         if (keyMatchesLocation(theItem, x, y)) {
3245             return theItem;
3246         }
3247     }
3248     if (pmap[x][y].flags & HAS_MONSTER) {
3249         monst = monsterAtLoc(x, y);
3250         if (monst->carriedItem) {
3251             theItem = monst->carriedItem;
3252             if (keyMatchesLocation(theItem, x, y)) {
3253                 return theItem;
3254             }
3255         }
3256     }
3257     return NULL;
3258 }
3259 
3260 // Aggroes out to the given distance.
aggravateMonsters(short distance,short x,short y,const color * flashColor)3261 void aggravateMonsters(short distance, short x, short y, const color *flashColor) {
3262     short i, j, **grid;
3263 
3264     rogue.wpCoordinates[0][0] = x;
3265     rogue.wpCoordinates[0][1] = y;
3266     refreshWaypoint(0);
3267 
3268     grid = allocGrid();
3269     fillGrid(grid, 0);
3270     calculateDistances(grid, x, y, T_PATHING_BLOCKER, NULL, true, false);
3271 
3272     for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
3273         creature *monst = nextCreature(&it);
3274         if (grid[monst->xLoc][monst->yLoc] <= distance) {
3275             if (monst->creatureState == MONSTER_SLEEPING) {
3276                 wakeUp(monst);
3277             }
3278             if (monst->creatureState != MONSTER_ALLY && monst->leader != &player) {
3279                 alertMonster(monst);
3280                 monst->info.flags &= ~MONST_MAINTAINS_DISTANCE;
3281                 monst->info.abilityFlags &= ~MA_AVOID_CORRIDORS;
3282             }
3283         }
3284     }
3285     for (i=0; i<DCOLS; i++) {
3286         for (j=0; j<DROWS; j++) {
3287             if (grid[i][j] >= 0 && grid[i][j] <= distance) {
3288                 scentMap[i][j] = 0;
3289                 addScentToCell(i, j, 2 * grid[i][j]);
3290             }
3291         }
3292     }
3293 
3294     if (player.xLoc == x && player.yLoc == y) {
3295         player.status[STATUS_AGGRAVATING] = player.maxStatus[STATUS_AGGRAVATING] = distance;
3296         rogue.aggroRange = currentAggroValue();
3297     }
3298 
3299     if (grid[player.xLoc][player.yLoc] >= 0 && grid[player.xLoc][player.yLoc] <= distance) {
3300         discover(x, y);
3301         discoverCell(x, y);
3302         colorFlash(flashColor, 0, (DISCOVERED | MAGIC_MAPPED), 10, distance, x, y);
3303         if (!playerCanSee(x, y)) {
3304             message("You hear a piercing shriek; something must have triggered a nearby alarm.", 0);
3305         }
3306     }
3307 
3308     freeGrid(grid);
3309 }
3310 
3311 // Generates a list of coordinates extending in a straight line
3312 // from originLoc (not included in the output), through targetLoc,
3313 // all the way to the edge of the map.
3314 // Any straight line passing through the target cell generates a valid
3315 // path; the function tries several lines and picks the one that works
3316 // best for the specified bolt type.
3317 // The list is terminated by a marker (-1, -1).
3318 // Returns the number of entries in the list (not counting the terminal marker).
getLineCoordinates(short listOfCoordinates[][2],const short originLoc[2],const short targetLoc[2],const bolt * theBolt)3319 short getLineCoordinates(short listOfCoordinates[][2], const short originLoc[2], const short targetLoc[2], const bolt *theBolt) {
3320     fixpt point[2], step[2];
3321     short listLength;
3322     int score, bestScore = 0, offset, bestOffset = 0;
3323 
3324     // Set of candidate waypoints strategically placed within a diamond shape.
3325     // For why they must be within a diamond, google "diamond-exit rule".
3326     const int numOffsets = 21;
3327     const fixpt offsets[][2] = {
3328         {50, 50}, // center of the square first (coordinates are in %)
3329         {40, 40}, {60, 40}, {60, 60}, {40, 60},
3330         {50, 30}, {70, 50}, {50, 70}, {30, 50},
3331         {50, 20}, {80, 50}, {50, 80}, {20, 50},
3332         {50, 10}, {90, 50}, {50, 90}, {10, 50},
3333         {50,  1}, {99, 50}, {50, 99}, { 1, 50} };
3334 
3335     if (originLoc[0] == targetLoc[0] && originLoc[1] == targetLoc[1]) {
3336         listOfCoordinates[0][0] = listOfCoordinates[0][1] = -1;
3337         return 0;
3338     }
3339 
3340     // try all offsets; the last iteration will use the best offset found
3341     for (offset = 0; offset < numOffsets + 1; offset++) {
3342 
3343         listLength = 0;
3344 
3345         for (int i = 0; i <= 1; i++) {
3346             // always shoot from the center of the origin cell
3347             point[i] = originLoc[i] * FP_FACTOR + FP_FACTOR/2;
3348             // vector to target
3349             step[i] = targetLoc[i] * FP_FACTOR + offsets[offset < numOffsets ? offset : bestOffset][i] * FP_FACTOR / 100 - point[i];
3350         }
3351 
3352         // normalize the step, to move exactly one row or column at a time
3353         fixpt m = max(llabs(step[0]), llabs(step[1]));
3354         step[0] = step[0] * FP_FACTOR / m;
3355         step[1] = step[1] * FP_FACTOR / m;
3356 
3357         // move until we exit the map
3358         while (true) {
3359             for (int i = 0; i <= 1; i++) {
3360                 point[i] += step[i];
3361                 listOfCoordinates[listLength][i] = (point[i] < 0 ? -1 : point[i] / FP_FACTOR);
3362             }
3363             if (!coordinatesAreInMap(listOfCoordinates[listLength][0], listOfCoordinates[listLength][1])) break;
3364             listLength++;
3365         };
3366 
3367         // last iteration does not need evaluation, we are returning it anyway
3368         if (offset == numOffsets) break;
3369 
3370         // No bolt means we don't want any tuning. Returning first path (using center of target)
3371         if (theBolt == NULL) break;
3372 
3373         // evaluate this path; we will return the path with the highest score
3374         score = 0;
3375 
3376         for (int i = 0; i < listLength; i++) {
3377             short x = listOfCoordinates[i][0], y = listOfCoordinates[i][1];
3378 
3379             boolean isImpassable = cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY);
3380             boolean isOpaque = cellHasTerrainFlag(x, y, T_OBSTRUCTS_VISION);
3381             boolean targetsEnemies = theBolt->flags & BF_TARGET_ENEMIES;
3382             boolean targetsAllies = theBolt->flags & BF_TARGET_ALLIES;
3383             boolean burningThrough = (theBolt->flags & BF_FIERY) && cellHasTerrainFlag(x, y, T_IS_FLAMMABLE);
3384             boolean isCastByPlayer = (originLoc[0] == player.xLoc && originLoc[1] == player.yLoc);
3385 
3386             creature *caster = monsterAtLoc(originLoc[0], originLoc[1]);
3387             creature *monst = monsterAtLoc(x, y);
3388             boolean isMonster = monst
3389                 && !(monst->bookkeepingFlags & MB_SUBMERGED)
3390                 && !monsterIsHidden(monst, caster);
3391             boolean isEnemyOfCaster = (monst && caster && monstersAreEnemies(monst, caster));
3392             boolean isAllyOfCaster = (monst && caster && monstersAreTeammates(monst, caster));
3393 
3394             // small bonus for making it this far
3395             score += 2;
3396 
3397             // target reached?
3398             if (x == targetLoc[0] && y == targetLoc[1]) {
3399 
3400                 if ((!targetsEnemies && !targetsAllies) ||
3401                     (targetsEnemies && isMonster && isEnemyOfCaster) ||
3402                     (targetsAllies && isMonster && isAllyOfCaster)) {
3403 
3404                     // big bonus for hitting the target
3405                     score += 5000;
3406                 }
3407 
3408                 break; // we don't care about anything beyond the target--if the player did, they would have selected a farther target
3409             }
3410 
3411             // if the caster is the player, undiscovered cells don't count (lest we reveal something about them)
3412             if (isCastByPlayer && !(pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) continue;
3413 
3414             // nothing can get through impregnable obstacles
3415             if (isImpassable && pmap[x][y].flags & IMPREGNABLE) {
3416                 break;
3417             }
3418 
3419             // tunneling goes through everything
3420             if (theBolt->boltEffect == BE_TUNNELING) {
3421                 score += (isImpassable ? 50 : isOpaque ? 10 : 0);
3422                 continue;
3423             }
3424 
3425             // hitting a creature with a bolt meant for enemies
3426             if (isMonster && targetsEnemies) {
3427                 score += isEnemyOfCaster ? 50 : -200;
3428             }
3429 
3430             // hitting a creature with a bolt meant for allies
3431             if (isMonster && targetsAllies) {
3432                 score += isAllyOfCaster ? 50 : -200;
3433             }
3434 
3435             // small penalty for setting terrain on fire (to prefer not to)
3436             if (burningThrough) {
3437                 score -= 1;
3438             }
3439 
3440             // check for obstruction
3441             if (isMonster && (theBolt->flags & BF_PASSES_THRU_CREATURES)) continue;
3442             if (isMonster || isImpassable || (isOpaque && !burningThrough)) break;
3443         }
3444 
3445         if (score > bestScore) {
3446             bestScore = score;
3447             bestOffset = offset;
3448         }
3449     }
3450 
3451     // demarcate the end of the list
3452     listOfCoordinates[listLength][0] = listOfCoordinates[listLength][1] = -1;
3453 
3454     return listLength;
3455 }
3456 
3457 // If a hypothetical bolt were launched from originLoc toward targetLoc,
3458 // with a given max distance and a toggle as to whether it halts at its impact location
3459 // or one space prior, where would it stop?
3460 // Takes into account the caster's knowledge; i.e. won't be blocked by monsters
3461 // that the caster is not aware of.
getImpactLoc(short returnLoc[2],const short originLoc[2],const short targetLoc[2],const short maxDistance,const boolean returnLastEmptySpace,const bolt * theBolt)3462 void getImpactLoc(short returnLoc[2], const short originLoc[2], const short targetLoc[2],
3463                   const short maxDistance, const boolean returnLastEmptySpace, const bolt *theBolt) {
3464     short coords[DCOLS + 1][2];
3465     short i, n;
3466     creature *monst;
3467 
3468     n = getLineCoordinates(coords, originLoc, targetLoc, theBolt);
3469     n = min(n, maxDistance);
3470     for (i=0; i<n; i++) {
3471         monst = monsterAtLoc(coords[i][0], coords[i][1]);
3472         if (monst
3473             && !monsterIsHidden(monst, monsterAtLoc(originLoc[0], originLoc[1]))
3474             && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
3475             // Imaginary bolt hit the player or a monster.
3476             break;
3477         }
3478         if (cellHasTerrainFlag(coords[i][0], coords[i][1], (T_OBSTRUCTS_VISION | T_OBSTRUCTS_PASSABILITY))) {
3479             break;
3480         }
3481     }
3482     if (i == maxDistance) {
3483         returnLoc[0] = coords[i-1][0];
3484         returnLoc[1] = coords[i-1][1];
3485     } else if (returnLastEmptySpace) {
3486         if (i == 0) {
3487             returnLoc[0] = originLoc[0];
3488             returnLoc[1] = originLoc[1];
3489         } else {
3490             returnLoc[0] = coords[i-1][0];
3491             returnLoc[1] = coords[i-1][1];
3492         }
3493     } else {
3494         returnLoc[0] = coords[i][0];
3495         returnLoc[1] = coords[i][1];
3496     }
3497     brogueAssert(coordinatesAreInMap(returnLoc[0], returnLoc[1]));
3498 }
3499 
3500 // Returns true if the two coordinates are unobstructed and diagonally adjacent,
3501 // but their two common neighbors are obstructed and at least one blocks diagonal movement.
impermissibleKinkBetween(short x1,short y1,short x2,short y2)3502 boolean impermissibleKinkBetween(short x1, short y1, short x2, short y2) {
3503     brogueAssert(coordinatesAreInMap(x1, y1));
3504     brogueAssert(coordinatesAreInMap(x2, y2));
3505     if (cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_PASSABILITY)
3506         || cellHasTerrainFlag(x2, y2, T_OBSTRUCTS_PASSABILITY)) {
3507         // One of the two locations is obstructed.
3508         return false;
3509     }
3510     if (abs(x1 - x2) != 1
3511         || abs(y1 - y2) != 1) {
3512         // Not diagonally adjacent.
3513         return false;
3514     }
3515     if (!cellHasTerrainFlag(x2, y1, T_OBSTRUCTS_PASSABILITY)
3516         || !cellHasTerrainFlag(x1, y2, T_OBSTRUCTS_PASSABILITY)) {
3517         // At least one of the common neighbors isn't obstructed.
3518         return false;
3519     }
3520     if (!cellHasTerrainFlag(x2, y1, T_OBSTRUCTS_DIAGONAL_MOVEMENT)
3521         && !cellHasTerrainFlag(x1, y2, T_OBSTRUCTS_DIAGONAL_MOVEMENT)) {
3522         // Neither of the common neighbors obstructs diagonal movement.
3523         return false;
3524     }
3525     return true;
3526 }
3527 
tunnelize(short x,short y)3528 boolean tunnelize(short x, short y) {
3529     enum dungeonLayers layer;
3530     boolean didSomething = false;
3531     creature *monst;
3532     short x2, y2;
3533     enum directions dir;
3534 
3535     if (pmap[x][y].flags & IMPREGNABLE) {
3536         return false;
3537     }
3538     freeCaptivesEmbeddedAt(x, y);
3539     if (x == 0 || x == DCOLS - 1 || y == 0 || y == DROWS - 1) {
3540         pmap[x][y].layers[DUNGEON] = CRYSTAL_WALL; // don't dissolve the boundary walls
3541         didSomething = true;
3542     } else {
3543         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
3544             if (tileCatalog[pmap[x][y].layers[layer]].flags & (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION)) {
3545                 pmap[x][y].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
3546                 didSomething = true;
3547             }
3548         }
3549     }
3550     if (didSomething) {
3551         spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_TUNNELIZE], true, false);
3552         if (pmap[x][y].flags & HAS_MONSTER) {
3553             // Kill turrets and sentinels if you tunnelize them.
3554             monst = monsterAtLoc(x, y);
3555             if (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS) {
3556                 inflictLethalDamage(NULL, monst);
3557             }
3558         }
3559     }
3560     if (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_DIAGONAL_MOVEMENT)
3561         && didSomething) {
3562         // Tunnel out any diagonal kinks between walls.
3563         for (dir = 0; dir < DIRECTION_COUNT; dir++) {
3564             x2 = x + nbDirs[dir][0];
3565             y2 = y + nbDirs[dir][1];
3566             if (coordinatesAreInMap(x2, y2)
3567                 && impermissibleKinkBetween(x, y, x2, y2)) {
3568 
3569                 if ((pmap[x][y2].flags & IMPREGNABLE)
3570                     || (!(pmap[x2][y].flags & IMPREGNABLE) && rand_percent(50))) {
3571 
3572                     tunnelize(x2, y);
3573                 } else {
3574                     tunnelize(x, y2);
3575                 }
3576             }
3577         }
3578     }
3579     return didSomething;
3580 }
3581 /* Negates the given creature. Returns true if there was an effect for the purpose of identifying a wand of negation.
3582  * If the creature was stripped of any traits or abilities, the wasNegated property is set, which is used for display in
3583  * sidebar and the creature's description.
3584  */
negate(creature * monst)3585 boolean negate(creature *monst) {
3586     short i, j;
3587     enum boltType backupBolts[20];
3588     char buf[DCOLS * 3], monstName[DCOLS];
3589     boolean negated = false;
3590 
3591     monsterName(monstName, monst, true);
3592 
3593     if (monst->info.abilityFlags & ~MA_NON_NEGATABLE_ABILITIES) {
3594         monst->info.abilityFlags &= MA_NON_NEGATABLE_ABILITIES; // negated monsters lose all special abilities
3595         negated = true;
3596         monst->wasNegated = true;
3597     }
3598 
3599     if (monst->bookkeepingFlags & MB_SEIZING){
3600         monst->bookkeepingFlags &= ~MB_SEIZING;
3601         negated = true;
3602     }
3603 
3604     if (monst->info.flags & MONST_DIES_IF_NEGATED) {
3605         if (monst->status[STATUS_LEVITATING]) {
3606             sprintf(buf, "%s dissipates into thin air", monstName);
3607         } else if (monst->info.flags & MONST_INANIMATE) {
3608             sprintf(buf, "%s shatters into tiny pieces", monstName);
3609         } else {
3610             sprintf(buf, "%s falls to the ground, lifeless", monstName);
3611         }
3612         killCreature(monst, false);
3613         combatMessage(buf, messageColorFromVictim(monst));
3614         negated = true;
3615     } else if (!(monst->info.flags & MONST_INVULNERABLE)) {
3616         // works on inanimates
3617         if (monst->status[STATUS_IMMUNE_TO_FIRE]) {
3618             monst->status[STATUS_IMMUNE_TO_FIRE] = 0;
3619             negated = true;
3620         }
3621         if (monst->status[STATUS_SLOWED]) {
3622             monst->status[STATUS_SLOWED] = 0;
3623             negated = true;
3624         }
3625         if (monst->status[STATUS_HASTED]) {
3626             monst->status[STATUS_HASTED] = 0;
3627             negated = true;
3628         }
3629         if (monst->status[STATUS_CONFUSED]) {
3630             monst->status[STATUS_CONFUSED] = 0;
3631             negated = true;
3632         }
3633         if (monst->status[STATUS_ENTRANCED]) {
3634             monst->status[STATUS_ENTRANCED] = 0;
3635             negated = true;
3636         }
3637         if (monst->status[STATUS_DISCORDANT]) {
3638             monst->status[STATUS_DISCORDANT] = 0;
3639             negated = true;
3640         }
3641         if (monst->status[STATUS_SHIELDED]) {
3642             monst->status[STATUS_SHIELDED] = 0;
3643             negated = true;
3644         }
3645         if (monst->status[STATUS_INVISIBLE]) {
3646             monst->status[STATUS_INVISIBLE] = 0;
3647             negated = true;
3648         }
3649         if (monst == &player) {
3650             if (monst->status[STATUS_TELEPATHIC] > 1 ) {
3651                 monst->status[STATUS_TELEPATHIC] = 1;
3652                 negated = true;
3653             }
3654             if (monst->status[STATUS_MAGICAL_FEAR] > 1 ) {
3655                 monst->status[STATUS_MAGICAL_FEAR] = 1;
3656                 negated = true;
3657             }
3658             if (monst->status[STATUS_LEVITATING] > 1 ) {
3659                 monst->status[STATUS_LEVITATING] = 1;
3660                 negated = true;
3661             }
3662             if (monst->status[STATUS_DARKNESS]) {
3663                 monst->status[STATUS_DARKNESS] = 0;
3664                 updateMinersLightRadius();
3665                 updateVision(true);
3666                 negated = true;
3667             }
3668         } else {
3669             if (monst->status[STATUS_TELEPATHIC] > 0 ) {
3670                 monst->status[STATUS_TELEPATHIC] = 0;
3671                 negated = true;
3672             }
3673             if (monst->status[STATUS_MAGICAL_FEAR] > 0 ) {
3674                 monst->status[STATUS_MAGICAL_FEAR] = 0;
3675                 negated = true;
3676             }
3677             if (monst->status[STATUS_LEVITATING] > 0 ) {
3678                 monst->status[STATUS_LEVITATING] = 0;
3679                 negated = true;
3680             }
3681         }
3682         if (monst->info.flags & MONST_IMMUNE_TO_FIRE) {
3683             monst->info.flags &= ~MONST_IMMUNE_TO_FIRE;
3684             monst->wasNegated = true;
3685             negated = true;
3686         }
3687         if (monst->movementSpeed != monst->info.movementSpeed) {
3688             monst->movementSpeed = monst->info.movementSpeed;
3689             negated = true;
3690         }
3691         if (monst->attackSpeed != monst->info.attackSpeed) {
3692             monst->attackSpeed = monst->info.attackSpeed;
3693             negated = true;
3694         }
3695 
3696         if (monst != &player && monst->mutationIndex > -1 && mutationCatalog[monst->mutationIndex].canBeNegated) {
3697 
3698             monst->mutationIndex = -1;
3699             negated = true;
3700             monst->wasNegated = true;
3701         }
3702         if (monst != &player && (monst->info.flags & NEGATABLE_TRAITS)) {
3703             if ((monst->info.flags & MONST_FIERY) && monst->status[STATUS_BURNING]) {
3704                 extinguishFireOnCreature(monst);
3705             }
3706             monst->info.flags &= ~NEGATABLE_TRAITS;
3707             negated = true;
3708             monst->wasNegated = true;
3709             refreshDungeonCell(monst->xLoc, monst->yLoc);
3710             refreshSideBar(-1, -1, false);
3711         }
3712         for (i = 0; i < 20; i++) {
3713             backupBolts[i] = monst->info.bolts[i];
3714             monst->info.bolts[i] = BOLT_NONE;
3715             if (monst->info.bolts[i] && !(boltCatalog[monst->info.bolts[i]].flags & BF_NOT_NEGATABLE)) {
3716                 negated = true;
3717             }
3718         }
3719         for (i = 0, j = 0; i < 20 && backupBolts[i]; i++) {
3720             if (boltCatalog[backupBolts[i]].flags & BF_NOT_NEGATABLE) {
3721                 monst->info.bolts[j] = backupBolts[i];
3722                 j++;
3723             }
3724         }
3725         monst->newPowerCount = monst->totalPowerCount; // Allies can re-learn lost ability slots.
3726         applyInstantTileEffectsToCreature(monst); // in case it should immediately die or fall into a chasm
3727     }
3728 
3729     if (negated && monst != &player && !(monst->info.flags & MONST_DIES_IF_NEGATED)) {
3730         sprintf(buf, "%s is stripped of $HISHER special traits", monstName);
3731         resolvePronounEscapes(buf, monst);
3732         combatMessage(buf, messageColorFromVictim(monst));
3733     }
3734 
3735     return negated;
3736 }
3737 
3738 // Adds one to the creature's weakness, sets the weakness status duration to maxDuration.
weaken(creature * monst,short maxDuration)3739 void weaken(creature *monst, short maxDuration) {
3740     if (monst->weaknessAmount < 10) {
3741         monst->weaknessAmount++;
3742     }
3743     monst->status[STATUS_WEAKENED] = max(monst->status[STATUS_WEAKENED], maxDuration);
3744     monst->maxStatus[STATUS_WEAKENED] = max(monst->maxStatus[STATUS_WEAKENED], maxDuration);
3745     if (monst == &player) {
3746         messageWithColor("your muscles weaken as an enervating toxin fills your veins.", &badMessageColor, 0);
3747         strengthCheck(rogue.weapon, true);
3748         strengthCheck(rogue.armor, true);
3749     }
3750 }
3751 
3752 // True if the creature polymorphed; false if not.
polymorph(creature * monst)3753 boolean polymorph(creature *monst) {
3754     short previousDamageTaken, healthFraction, newMonsterIndex;
3755 
3756     if (monst == &player || (monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
3757         return false; // Sorry, this is not Nethack.
3758     }
3759 
3760     if (monst->creatureState == MONSTER_FLEEING
3761         && (monst->info.flags & (MONST_MAINTAINS_DISTANCE | MONST_FLEES_NEAR_DEATH)) || (monst->info.abilityFlags & MA_HIT_STEAL_FLEE)) {
3762 
3763         monst->creatureState = MONSTER_TRACKING_SCENT;
3764         monst->creatureMode = MODE_NORMAL;
3765     }
3766 
3767     unAlly(monst); // Sorry, no cheap dragon allies.
3768     monst->mutationIndex = -1; // Polymorph cures mutation -- basic science.
3769 
3770     // After polymorphing, don't "drop" any creature on death (e.g. phylactery, phoenix egg)
3771     if (monst->carriedMonster) {
3772         freeCreature(monst->carriedMonster);
3773         monst->carriedMonster = NULL;
3774     }
3775 
3776     healthFraction = monst->currentHP * 1000 / monst->info.maxHP;
3777     previousDamageTaken = monst->info.maxHP - monst->currentHP;
3778 
3779     do {
3780         newMonsterIndex = rand_range(1, NUMBER_MONSTER_KINDS - 1);
3781     } while (monsterCatalog[newMonsterIndex].flags & (MONST_INANIMATE | MONST_NO_POLYMORPH) // Can't turn something into an inanimate object or lich/phoenix/warden.
3782              || newMonsterIndex == monst->info.monsterID); // Can't stay the same monster.
3783     monst->info = monsterCatalog[newMonsterIndex]; // Presto change-o!
3784 
3785     monst->info.turnsBetweenRegen *= 1000;
3786     monst->currentHP = max(1, max(healthFraction * monst->info.maxHP / 1000, monst->info.maxHP - previousDamageTaken));
3787 
3788     monst->movementSpeed = monst->info.movementSpeed;
3789     monst->attackSpeed = monst->info.attackSpeed;
3790     if (monst->status[STATUS_HASTED]) {
3791         monst->movementSpeed /= 2;
3792         monst->attackSpeed /= 2;
3793     }
3794     if (monst->status[STATUS_SLOWED]) {
3795         monst->movementSpeed *= 2;
3796         monst->attackSpeed *= 2;
3797     }
3798 
3799     clearStatus(monst);
3800 
3801     if (monst->info.flags & MONST_FIERY) {
3802         monst->status[STATUS_BURNING] = monst->maxStatus[STATUS_BURNING] = 1000; // won't decrease
3803     }
3804     if (monst->info.flags & MONST_FLIES) {
3805         monst->status[STATUS_LEVITATING] = monst->maxStatus[STATUS_LEVITATING] = 1000; // won't decrease
3806     }
3807     if (monst->info.flags & MONST_IMMUNE_TO_FIRE) {
3808         monst->status[STATUS_IMMUNE_TO_FIRE] = monst->maxStatus[STATUS_IMMUNE_TO_FIRE] = 1000; // won't decrease
3809     }
3810     if (monst->info.flags & MONST_INVISIBLE) {
3811         monst->status[STATUS_INVISIBLE] = monst->maxStatus[STATUS_INVISIBLE] = 1000; // won't decrease
3812     }
3813     monst->status[STATUS_NUTRITION] = monst->maxStatus[STATUS_NUTRITION] = 1000;
3814 
3815     if (monst->bookkeepingFlags & MB_CAPTIVE) {
3816         demoteMonsterFromLeadership(monst);
3817         monst->creatureState = MONSTER_TRACKING_SCENT;
3818         monst->bookkeepingFlags &= ~MB_CAPTIVE;
3819     }
3820     monst->bookkeepingFlags &= ~(MB_SEIZING | MB_SEIZED);
3821 
3822     monst->ticksUntilTurn = max(monst->ticksUntilTurn, 101);
3823 
3824     refreshDungeonCell(monst->xLoc, monst->yLoc);
3825     if (boltCatalog[BOLT_POLYMORPH].backColor) {
3826         flashMonster(monst, boltCatalog[BOLT_POLYMORPH].backColor, 100);
3827     }
3828     return true;
3829 }
3830 
slow(creature * monst,short turns)3831 void slow(creature *monst, short turns) {
3832     if (!(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
3833         monst->status[STATUS_SLOWED] = monst->maxStatus[STATUS_SLOWED] = turns;
3834         monst->status[STATUS_HASTED] = 0;
3835         if (monst == &player) {
3836             updateEncumbrance();
3837             message("you feel yourself slow down.", 0);
3838         } else {
3839             monst->movementSpeed = monst->info.movementSpeed * 2;
3840             monst->attackSpeed = monst->info.attackSpeed * 2;
3841         }
3842     }
3843 }
3844 
haste(creature * monst,short turns)3845 void haste(creature *monst, short turns) {
3846     if (monst && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
3847         monst->status[STATUS_SLOWED] = 0;
3848         monst->status[STATUS_HASTED] = monst->maxStatus[STATUS_HASTED] = turns;
3849         if (monst == &player) {
3850             updateEncumbrance();
3851             message("you feel yourself speed up.", 0);
3852         } else {
3853             monst->movementSpeed = monst->info.movementSpeed / 2;
3854             monst->attackSpeed = monst->info.attackSpeed / 2;
3855         }
3856     }
3857 }
3858 
heal(creature * monst,short percent,boolean panacea)3859 void heal(creature *monst, short percent, boolean panacea) {
3860     char buf[COLS], monstName[COLS];
3861     monst->currentHP = min(monst->info.maxHP, monst->currentHP + percent * monst->info.maxHP / 100);
3862     if (panacea) {
3863         if (monst->status[STATUS_HALLUCINATING] > 1) {
3864             monst->status[STATUS_HALLUCINATING] = 1;
3865         }
3866         if (monst->status[STATUS_CONFUSED] > 1) {
3867             monst->status[STATUS_CONFUSED] = 1;
3868         }
3869         if (monst->status[STATUS_NAUSEOUS] > 1) {
3870             monst->status[STATUS_NAUSEOUS] = 1;
3871         }
3872         if (monst->status[STATUS_SLOWED] > 1) {
3873             monst->status[STATUS_SLOWED] = 1;
3874         }
3875         if (monst->status[STATUS_WEAKENED] > 1) {
3876             monst->weaknessAmount = 0;
3877             monst->status[STATUS_WEAKENED] = 0;
3878             updateEncumbrance();
3879         }
3880         if (monst->status[STATUS_POISONED]) {
3881             monst->poisonAmount = 0;
3882             monst->status[STATUS_POISONED] = 0;
3883         }
3884         if (monst->status[STATUS_DARKNESS] > 0) {
3885             monst->status[STATUS_DARKNESS] = 0;
3886             if (monst == &player) {
3887                 updateMinersLightRadius();
3888                 updateVision(true);
3889             }
3890         }
3891     }
3892     if (canDirectlySeeMonster(monst)
3893         && monst != &player
3894         && !panacea) {
3895 
3896         monsterName(monstName, monst, true);
3897         sprintf(buf, "%s looks healthier", monstName);
3898         combatMessage(buf, NULL);
3899     }
3900 }
3901 
makePlayerTelepathic(short duration)3902 void makePlayerTelepathic(short duration) {
3903     player.status[STATUS_TELEPATHIC] = player.maxStatus[STATUS_TELEPATHIC] = duration;
3904     for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
3905         creature *monst = nextCreature(&it);
3906         refreshDungeonCell(monst->xLoc, monst->yLoc);
3907     }
3908     if (!hasNextCreature(iterateCreatures(monsters))) {
3909         message("you can somehow tell that you are alone on this depth at the moment.", 0);
3910     } else {
3911         message("you can somehow feel the presence of other creatures' minds!", 0);
3912     }
3913 }
3914 
rechargeItems(unsigned long categories)3915 void rechargeItems(unsigned long categories) {
3916     item *tempItem;
3917     short x, y, z, i, categoryCount;
3918     char buf[DCOLS * 3];
3919 
3920     x = y = z = 0; // x counts staffs, y counts wands, z counts charms
3921     for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
3922         if (tempItem->category & categories & STAFF) {
3923             x++;
3924             tempItem->charges = tempItem->enchant1;
3925             tempItem->enchant2 = (tempItem->kind == STAFF_BLINKING || tempItem->kind == STAFF_OBSTRUCTION ? 10000 : 5000) / tempItem->enchant1;
3926         }
3927         if (tempItem->category & categories & WAND) {
3928             y++;
3929             tempItem->charges++;
3930         }
3931         if (tempItem->category & categories & CHARM) {
3932             z++;
3933             tempItem->charges = 0;
3934         }
3935     }
3936 
3937     categoryCount = (x ? 1 : 0) + (y ? 1 : 0) + (z ? 1 : 0);
3938 
3939     if (categoryCount) {
3940         i = 0;
3941         strcpy(buf, "a surge of energy courses through your pack, recharging your ");
3942         if (x) {
3943             i++;
3944             strcat(buf, x == 1 ? "staff" : "staffs");
3945             if (i == categoryCount - 1) {
3946                 strcat(buf, " and ");
3947             } else if (i <= categoryCount - 2) {
3948                 strcat(buf, ", ");
3949             }
3950         }
3951         if (y) {
3952             i++;
3953             strcat(buf, y == 1 ? "wand" : "wands");
3954             if (i == categoryCount - 1) {
3955                 strcat(buf, " and ");
3956             } else if (i <= categoryCount - 2) {
3957                 strcat(buf, ", ");
3958             }
3959         }
3960         if (z) {
3961             strcat(buf, z == 1 ? "charm" : "charms");
3962         }
3963         strcat(buf, ".");
3964         message(buf, 0);
3965     } else {
3966         message("a surge of energy courses through your pack, but nothing happens.", 0);
3967     }
3968 }
3969 
3970 //void causeFear(const char *emitterName) {
3971 //    creature *monst;
3972 //    short numberOfMonsters = 0;
3973 //    char buf[DCOLS*3], mName[DCOLS];
3974 //
3975 //    for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
3976 //        if (pmap[monst->xLoc][monst->yLoc].flags & IN_FIELD_OF_VIEW
3977 //            && monst->creatureState != MONSTER_FLEEING
3978 //            && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
3979 //
3980 //            monst->status[STATUS_MAGICAL_FEAR] = monst->maxStatus[STATUS_MAGICAL_FEAR] = rand_range(150, 225);
3981 //            monst->creatureState = MONSTER_FLEEING;
3982 //            if (canSeeMonster(monst)) {
3983 //                numberOfMonsters++;
3984 //                monsterName(mName, monst, true);
3985 //            }
3986 //        }
3987 //    }
3988 //    if (numberOfMonsters > 1) {
3989 //        sprintf(buf, "%s emits a brilliant flash of red light, and the monsters flee!", emitterName);
3990 //    } else if (numberOfMonsters == 1) {
3991 //        sprintf(buf, "%s emits a brilliant flash of red light, and %s flees!", emitterName, mName);
3992 //    } else {
3993 //        sprintf(buf, "%s emits a brilliant flash of red light!", emitterName);
3994 //    }
3995 //    message(buf, 0);
3996 //    colorFlash(&redFlashColor, 0, IN_FIELD_OF_VIEW, 15, DCOLS, player.xLoc, player.yLoc);
3997 //}
3998 
negationBlast(const char * emitterName,const short distance)3999 void negationBlast(const char *emitterName, const short distance) {
4000     item *theItem;
4001     char buf[DCOLS];
4002 
4003     sprintf(buf, "%s emits a numbing torrent of anti-magic!", emitterName);
4004     messageWithColor(buf, &itemMessageColor, 0);
4005     colorFlash(&pink, 0, IN_FIELD_OF_VIEW, 3 + distance / 5, distance, player.xLoc, player.yLoc);
4006     negate(&player);
4007     flashMonster(&player, &pink, 100);
4008     for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
4009         creature *monst = nextCreature(&it);
4010         if ((pmap[monst->xLoc][monst->yLoc].flags & IN_FIELD_OF_VIEW)
4011             && (player.xLoc - monst->xLoc) * (player.xLoc - monst->xLoc) + (player.yLoc - monst->yLoc) * (player.yLoc - monst->yLoc) <= distance * distance) {
4012 
4013             if (canSeeMonster(monst)) {
4014                 flashMonster(monst, &pink, 100);
4015             }
4016             negate(monst); // This can be fatal.
4017         }
4018     }
4019     for (theItem = floorItems; theItem != NULL; theItem = theItem->nextItem) {
4020         if ((pmap[theItem->xLoc][theItem->yLoc].flags & IN_FIELD_OF_VIEW)
4021             && (player.xLoc - theItem->xLoc) * (player.xLoc - theItem->xLoc) + (player.yLoc - theItem->yLoc) * (player.yLoc - theItem->yLoc) <= distance * distance) {
4022 
4023             theItem->flags &= ~(ITEM_MAGIC_DETECTED | ITEM_CURSED);
4024             switch (theItem->category) {
4025                 case WEAPON:
4026                 case ARMOR:
4027                     theItem->enchant1 = theItem->enchant2 = theItem->charges = 0;
4028                     theItem->flags &= ~(ITEM_RUNIC | ITEM_RUNIC_HINTED | ITEM_RUNIC_IDENTIFIED | ITEM_PROTECTED);
4029                     identify(theItem);
4030                     pmap[theItem->xLoc][theItem->yLoc].flags &= ~ITEM_DETECTED;
4031                     refreshDungeonCell(theItem->xLoc, theItem->yLoc);
4032                     break;
4033                 case STAFF:
4034                     theItem->charges = 0;
4035                     break;
4036                 case WAND:
4037                     theItem->charges = 0;
4038                     theItem->flags |= ITEM_MAX_CHARGES_KNOWN;
4039                     break;
4040                 case RING:
4041                     theItem->enchant1 = 0;
4042                     theItem->flags |= ITEM_IDENTIFIED; // Reveal that it is (now) +0, but not necessarily which kind of ring it is.
4043                     updateIdentifiableItems();
4044                     break;
4045                 case CHARM:
4046                     theItem->charges = charmRechargeDelay(theItem->kind, theItem->enchant1);
4047                     break;
4048                 default:
4049                     break;
4050             }
4051         }
4052     }
4053 }
4054 
discordBlast(const char * emitterName,const short distance)4055 void discordBlast(const char *emitterName, const short distance) {
4056     char buf[DCOLS];
4057 
4058     sprintf(buf, "%s emits a wave of unsettling purple radiation!", emitterName);
4059     messageWithColor(buf, &itemMessageColor, 0);
4060     colorFlash(&discordColor, 0, IN_FIELD_OF_VIEW, 3 + distance / 5, distance, player.xLoc, player.yLoc);
4061     for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
4062         creature *monst = nextCreature(&it);
4063         if ((pmap[monst->xLoc][monst->yLoc].flags & IN_FIELD_OF_VIEW)
4064             && (player.xLoc - monst->xLoc) * (player.xLoc - monst->xLoc) + (player.yLoc - monst->yLoc) * (player.yLoc - monst->yLoc) <= distance * distance) {
4065 
4066             if (!(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4067                 if (canSeeMonster(monst)) {
4068                     flashMonster(monst, &discordColor, 100);
4069                 }
4070                 monst->status[STATUS_DISCORDANT] = monst->maxStatus[STATUS_DISCORDANT] = 30;
4071             }
4072         }
4073     }
4074 }
4075 
crystalize(short radius)4076 void crystalize(short radius) {
4077     extern color forceFieldColor;
4078     short i, j;
4079     creature *monst;
4080 
4081     for (i=0; i<DCOLS; i++) {
4082         for (j=0; j < DROWS; j++) {
4083             if ((player.xLoc - i) * (player.xLoc - i) + (player.yLoc - j) * (player.yLoc - j) <= radius * radius
4084                 && !(pmap[i][j].flags & IMPREGNABLE)) {
4085 
4086                 if (i == 0 || i == DCOLS - 1 || j == 0 || j == DROWS - 1) {
4087                     pmap[i][j].layers[DUNGEON] = CRYSTAL_WALL; // don't dissolve the boundary walls
4088                 } else if (tileCatalog[pmap[i][j].layers[DUNGEON]].flags & (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION)) {
4089 
4090                     pmap[i][j].layers[DUNGEON] = FORCEFIELD;
4091                     spawnDungeonFeature(i, j, &dungeonFeatureCatalog[DF_SHATTERING_SPELL], true, false);
4092 
4093                     if (pmap[i][j].flags & HAS_MONSTER) {
4094                         monst = monsterAtLoc(i, j);
4095                         if (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS) {
4096                             inflictLethalDamage(NULL, monst);
4097                         } else {
4098                             freeCaptivesEmbeddedAt(i, j);
4099                         }
4100                     }
4101                 }
4102             }
4103         }
4104     }
4105     updateVision(false);
4106     colorFlash(&forceFieldColor, 0, 0, radius, radius, player.xLoc, player.yLoc);
4107     displayLevel();
4108     refreshSideBar(-1, -1, false);
4109 }
4110 
imbueInvisibility(creature * monst,short duration)4111 boolean imbueInvisibility(creature *monst, short duration) {
4112     boolean autoID = false;
4113 
4114     if (monst && !(monst->info.flags & (MONST_INANIMATE | MONST_INVISIBLE | MONST_INVULNERABLE))) {
4115         if (monst == &player || monst->creatureState == MONSTER_ALLY) {
4116             autoID = true;
4117         } else if (canSeeMonster(monst) && monsterRevealed(monst)) {
4118             autoID = true;
4119         }
4120         monst->status[STATUS_INVISIBLE] = monst->maxStatus[STATUS_INVISIBLE] = duration;
4121         refreshDungeonCell(monst->xLoc, monst->yLoc);
4122         refreshSideBar(-1, -1, false);
4123         if (boltCatalog[BOLT_POLYMORPH].backColor) {
4124             flashMonster(monst, boltCatalog[BOLT_INVISIBILITY].backColor, 100);
4125         }
4126     }
4127     return autoID;
4128 }
4129 
projectileReflects(creature * attacker,creature * defender)4130 boolean projectileReflects(creature *attacker, creature *defender) {
4131     short prob;
4132     fixpt netReflectionLevel;
4133 
4134     // immunity armor always reflects its vorpal enemy's projectiles
4135     if (defender == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_IMMUNITY
4136         && monsterIsInClass(attacker, rogue.armor->vorpalEnemy)
4137         && monstersAreEnemies(attacker, defender)) {
4138 
4139         return true;
4140     }
4141 
4142     if (defender == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_REFLECTION) {
4143         netReflectionLevel = netEnchant(rogue.armor);
4144     } else {
4145         netReflectionLevel = 0;
4146     }
4147 
4148     if (defender && (defender->info.flags & MONST_REFLECT_4)) {
4149         if (defender->info.flags & MONST_ALWAYS_USE_ABILITY) {
4150             return true;
4151         }
4152         netReflectionLevel += 4 * FP_FACTOR;
4153     }
4154 
4155     if (netReflectionLevel <= 0) {
4156         return false;
4157     }
4158 
4159     prob = reflectionChance(netReflectionLevel);
4160 
4161     return rand_percent(prob);
4162 }
4163 
4164 // Alters listOfCoordinates to describe reflected path,
4165 // which diverges from the existing path at kinkCell,
4166 // and then returns the path length of the reflected path.
reflectBolt(short targetX,short targetY,short listOfCoordinates[][2],short kinkCell,boolean retracePath)4167 short reflectBolt(short targetX, short targetY, short listOfCoordinates[][2], short kinkCell, boolean retracePath) {
4168     short k, target[2], origin[2], newPath[DCOLS][2], newPathLength, failsafe, finalLength;
4169     boolean needRandomTarget;
4170 
4171     needRandomTarget = (targetX < 0 || targetY < 0
4172                         || (targetX == listOfCoordinates[kinkCell][0] && targetY == listOfCoordinates[kinkCell][1]));
4173 
4174     if (retracePath) {
4175         // if reflecting back at caster, follow precise trajectory until we reach the caster
4176         for (k = 1; k <= kinkCell && kinkCell + k < MAX_BOLT_LENGTH; k++) {
4177             listOfCoordinates[kinkCell + k][0] = listOfCoordinates[kinkCell - k][0];
4178             listOfCoordinates[kinkCell + k][1] = listOfCoordinates[kinkCell - k][1];
4179         }
4180 
4181         // Calculate a new "extension" path, with an origin at the caster, and a destination at
4182         // the caster's location translated by the vector from the reflection point to the caster.
4183         //
4184         // For example, if the player is at (0,0), and the caster is at (2,3), then the newpath
4185         // is from (2,3) to (4,6):
4186         // (2,3) + ((2,3) - (0,0)) = (4,6).
4187 
4188         origin[0] = listOfCoordinates[2 * kinkCell][0];
4189         origin[1] = listOfCoordinates[2 * kinkCell][1];
4190         target[0] = targetX + (targetX - listOfCoordinates[kinkCell][0]);
4191         target[1] = targetY + (targetY - listOfCoordinates[kinkCell][1]);
4192 
4193         // (NULL because the reflected bolt is not under the caster's control, so its path should not be tuned)
4194         newPathLength = getLineCoordinates(newPath, origin, target, NULL);
4195 
4196         for (k=0; k<=newPathLength; k++) {
4197             listOfCoordinates[2 * kinkCell + k + 1][0] = newPath[k][0];
4198             listOfCoordinates[2 * kinkCell + k + 1][1] = newPath[k][1];
4199         }
4200         finalLength = 2 * kinkCell + newPathLength + 1;
4201     } else {
4202         failsafe = 50;
4203         do {
4204             if (needRandomTarget) {
4205                 // pick random target
4206                 perimeterCoords(target, rand_range(0, 39));
4207                 target[0] += listOfCoordinates[kinkCell][0];
4208                 target[1] += listOfCoordinates[kinkCell][1];
4209             } else {
4210                 target[0] = targetX;
4211                 target[1] = targetY;
4212             }
4213             newPathLength = getLineCoordinates(newPath, listOfCoordinates[kinkCell], target, NULL);
4214             if (newPathLength > 0
4215                 && !cellHasTerrainFlag(newPath[0][0], newPath[0][1], (T_OBSTRUCTS_VISION | T_OBSTRUCTS_PASSABILITY))) {
4216 
4217                 needRandomTarget = false;
4218             }
4219         } while (needRandomTarget && --failsafe);
4220 
4221         for (k = 0; k < newPathLength; k++) {
4222             listOfCoordinates[kinkCell + k + 1][0] = newPath[k][0];
4223             listOfCoordinates[kinkCell + k + 1][1] = newPath[k][1];
4224         }
4225 
4226         finalLength = kinkCell + newPathLength + 1;
4227     }
4228 
4229     listOfCoordinates[finalLength][0] = -1;
4230     listOfCoordinates[finalLength][1] = -1;
4231     return finalLength;
4232 }
4233 
4234 // Update stuff that promotes without keys so players can't abuse item libraries with blinking/haste shenanigans
checkForMissingKeys(short x,short y)4235 void checkForMissingKeys(short x, short y) {
4236     short layer;
4237 
4238     if (cellHasTMFlag(x, y, TM_PROMOTES_WITHOUT_KEY) && !keyOnTileAt(x, y)) {
4239         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
4240             if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_WITHOUT_KEY) {
4241                 promoteTile(x, y, layer, false);
4242             }
4243         }
4244     }
4245 }
4246 
beckonMonster(creature * monst,short x,short y)4247 void beckonMonster(creature *monst, short x, short y) {
4248     short from[2], to[2];
4249     bolt theBolt = boltCatalog[BOLT_BLINKING];
4250 
4251     if (monst->bookkeepingFlags & MB_CAPTIVE) {
4252         freeCaptive(monst);
4253     }
4254     from[0] = monst->xLoc;
4255     from[1] = monst->yLoc;
4256     to[0] = x;
4257     to[1] = y;
4258     theBolt.magnitude = max(1, (distanceBetween(x, y, monst->xLoc, monst->yLoc) - 2) / 2);
4259     zap(from, to, &theBolt, false);
4260     if (monst->ticksUntilTurn < player.attackSpeed+1) {
4261         monst->ticksUntilTurn = player.attackSpeed+1;
4262     }
4263 }
4264 
boltEffectForItem(item * theItem)4265 enum boltEffects boltEffectForItem(item *theItem) {
4266     if (theItem->category & (STAFF | WAND)) {
4267         return boltCatalog[tableForItemCategory(theItem->category, NULL)[theItem->kind].strengthRequired].boltEffect;
4268     } else {
4269         return BE_NONE;
4270     }
4271 }
4272 
boltForItem(item * theItem)4273 enum boltType boltForItem(item *theItem) {
4274     if (theItem->category & (STAFF | WAND)) {
4275         return tableForItemCategory(theItem->category, NULL)[theItem->kind].strengthRequired;
4276     } else {
4277         return 0;
4278     }
4279 }
4280 
4281 // Called on each space of the bolt's flight.
4282 // Returns true if the bolt terminates here.
4283 // Caster can be null.
4284 // Pass in true for boltInView if any part of the bolt is currently visible to the player.
4285 // Pass in true for alreadyReflected if the bolt has already reflected off of something.
4286 // If the effect is visible enough for the player to identify the shooting item,
4287 // *autoID will be set to true. (AutoID can be null.)
4288 // If the effect causes the level's lighting or vision to change, *lightingChanged
4289 // will be set to true. (LightingChanged can be null.)
updateBolt(bolt * theBolt,creature * caster,short x,short y,boolean boltInView,boolean alreadyReflected,boolean * autoID,boolean * lightingChanged)4290 boolean updateBolt(bolt *theBolt, creature *caster, short x, short y,
4291                    boolean boltInView, boolean alreadyReflected,
4292                    boolean *autoID, boolean *lightingChanged) {
4293     char buf[COLS], monstName[COLS];
4294     creature *monst; // Creature being hit by the bolt, if any.
4295     creature *newMonst; // Utility variable for plenty
4296     boolean terminateBolt = false;
4297     boolean negated = false;
4298 
4299     if (lightingChanged) {
4300         *lightingChanged = false;
4301     }
4302 
4303     // Handle collisions with monsters.
4304 
4305     monst = monsterAtLoc(x, y);
4306     if (monst && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
4307         monsterName(monstName, monst, true);
4308 
4309         switch(theBolt->boltEffect) {
4310             case BE_ATTACK:
4311                 if (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)
4312                     || (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
4313 
4314                     attack(caster, monst, false);
4315                     if (autoID) {
4316                         *autoID = true;
4317                     }
4318                 }
4319                 break;
4320             case BE_DAMAGE:
4321                 if (autoID) {
4322                     *autoID = true;
4323                 }
4324                 if (((theBolt->flags & BF_FIERY) && monst->status[STATUS_IMMUNE_TO_FIRE] > 0)
4325                     || (monst->info.flags & MONST_INVULNERABLE)) {
4326 
4327                     if (canSeeMonster(monst)) {
4328                         sprintf(buf, "%s ignore%s %s %s",
4329                                 monstName,
4330                                 (monst == &player ? "" : "s"),
4331                                 canSeeMonster(caster) ? "the" : "a",
4332                                 theBolt->name);
4333                         combatMessage(buf, 0);
4334                     }
4335                 } else if (inflictDamage(caster, monst, staffDamage(theBolt->magnitude * FP_FACTOR), theBolt->backColor, false)) {
4336                     // killed monster
4337                     if (player.currentHP <= 0) {
4338                         if (caster == &player) {
4339                             sprintf(buf, "Killed by a reflected %s", theBolt->name);
4340                             gameOver(buf, true);
4341                         }
4342                         terminateBolt = true;
4343                         return true;
4344                     }
4345                     if (boltInView || canSeeMonster(monst)) {
4346                         sprintf(buf, "%s %s %s %s",
4347                                 canSeeMonster(caster) ? "the" : "a",
4348                                 theBolt->name,
4349                                 ((monst->info.flags & MONST_INANIMATE) ? "destroys" : "kills"),
4350                                 monstName);
4351                         combatMessage(buf, messageColorFromVictim(monst));
4352                     } else {
4353                         sprintf(buf, "you hear %s %s", monstName, ((monst->info.flags & MONST_INANIMATE) ? "get destroyed" : "die"));
4354                         combatMessage(buf, 0);
4355                     }
4356                 } else {
4357                     // monster lives
4358                     if (monst->creatureMode != MODE_PERM_FLEEING
4359                         && monst->creatureState != MONSTER_ALLY
4360                         && (monst->creatureState != MONSTER_FLEEING || monst->status[STATUS_MAGICAL_FEAR])) {
4361 
4362                         monst->creatureState = MONSTER_TRACKING_SCENT;
4363                         monst->status[STATUS_MAGICAL_FEAR] = 0;
4364                     }
4365                     if (boltInView) {
4366                         sprintf(buf, "%s %s hits %s",
4367                                 canSeeMonster(caster) ? "the" : "a",
4368                                 theBolt->name,
4369                                 monstName);
4370                         combatMessage(buf, messageColorFromVictim(monst));
4371                     }
4372                     if (theBolt->flags & BF_FIERY) {
4373                         exposeCreatureToFire(monst);
4374                     }
4375                     if (!alreadyReflected
4376                         || caster != &player) {
4377                         moralAttack(caster, monst);
4378                     }
4379                 }
4380                 if (theBolt->flags & BF_FIERY) {
4381                     exposeTileToFire(x, y, true); // burninate
4382                 }
4383                 break;
4384             case BE_TELEPORT:
4385                 if (!(monst->info.flags & MONST_IMMOBILE)) {
4386                     if (monst->bookkeepingFlags & MB_CAPTIVE) {
4387                         freeCaptive(monst);
4388                     }
4389                     teleport(monst, -1, -1, false);
4390                 }
4391                 break;
4392             case BE_BECKONING:
4393                 if (!(monst->info.flags & MONST_IMMOBILE)
4394                     && caster
4395                     && distanceBetween(caster->xLoc, caster->yLoc, monst->xLoc, monst->yLoc) > 1) {
4396 
4397                     if (canSeeMonster(monst) && autoID) {
4398                         *autoID = true;
4399                     }
4400                     beckonMonster(monst, caster->xLoc, caster->yLoc);
4401                     if (canSeeMonster(monst) && autoID) {
4402                         *autoID = true;
4403                     }
4404                 }
4405                 break;
4406             case BE_SLOW:
4407                 slow(monst, theBolt->magnitude * 5);
4408                 if (boltCatalog[BOLT_SLOW].backColor) {
4409                     flashMonster(monst, boltCatalog[BOLT_SLOW].backColor, 100);
4410                 }
4411                 if (autoID) {
4412                     *autoID = true;
4413                 }
4414                 break;
4415             case BE_HASTE:
4416                 haste(monst, staffHasteDuration(theBolt->magnitude * FP_FACTOR));
4417                 if (boltCatalog[BOLT_HASTE].backColor) {
4418                     flashMonster(monst, boltCatalog[BOLT_HASTE].backColor, 100);
4419                 }
4420                 if (autoID) {
4421                     *autoID = true;
4422                 }
4423                 break;
4424             case BE_POLYMORPH:
4425                 if (polymorph(monst)) {
4426                     if (!monst->status[STATUS_INVISIBLE]) {
4427                         if (autoID) {
4428                             *autoID = true;
4429                         }
4430                     }
4431                 }
4432                 break;
4433             case BE_INVISIBILITY:
4434                 if (imbueInvisibility(monst, 150) && autoID) {
4435                     *autoID = true;
4436                 }
4437                 break;
4438             case BE_DOMINATION:
4439                 if (monst != &player && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4440                     if (rand_percent(wandDominate(monst))) {
4441                         // domination succeeded
4442                         monst->status[STATUS_DISCORDANT] = 0;
4443                         becomeAllyWith(monst);
4444                         //refreshSideBar(-1, -1, false);
4445                         refreshDungeonCell(monst->xLoc, monst->yLoc);
4446                         if (canSeeMonster(monst)) {
4447                             if (autoID) {
4448                                 *autoID = true;
4449                             }
4450                             sprintf(buf, "%s is bound to your will!", monstName);
4451                             message(buf, 0);
4452                             if (boltCatalog[BOLT_DOMINATION].backColor) {
4453                                 flashMonster(monst, boltCatalog[BOLT_DOMINATION].backColor, 100);
4454                             }
4455                         }
4456                     } else if (canSeeMonster(monst)) {
4457                         if (autoID) {
4458                             *autoID = true;
4459                         }
4460                         sprintf(buf, "%s resists the bolt of domination.", monstName);
4461                         message(buf, 0);
4462                     }
4463                 }
4464                 break;
4465             case BE_NEGATION:
4466                 negated = negate(monst);
4467                 if (boltCatalog[BOLT_NEGATION].backColor) {
4468                     flashMonster(monst, boltCatalog[BOLT_NEGATION].backColor, 100);
4469                 }
4470                 if (negated && autoID && canSeeMonster(monst)) {
4471                     *autoID = true;
4472                 }
4473 
4474                 break;
4475             case BE_EMPOWERMENT:
4476                 if (monst != &player
4477                     && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4478 
4479                     empowerMonster(monst);
4480                     createFlare(monst->xLoc, monst->yLoc, EMPOWERMENT_LIGHT);
4481                     if (canSeeMonster(monst) && autoID) {
4482                         *autoID = true;
4483                     }
4484                 }
4485                 break;
4486             case BE_POISON:
4487                 if (!(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4488                     addPoison(monst, staffPoison(theBolt->magnitude * FP_FACTOR), 1);
4489                     if (canSeeMonster(monst)) {
4490                         if (boltCatalog[BOLT_POISON].backColor) {
4491                             flashMonster(monst, boltCatalog[BOLT_POISON].backColor, 100);
4492                         }
4493                         if (autoID) {
4494                             *autoID = true;
4495                         }
4496                         if (monst != &player) {
4497                             sprintf(buf, "%s %s %s sick",
4498                                     monstName,
4499                                     (monst == &player ? "feel" : "looks"),
4500                                     (monst->status[STATUS_POISONED] * monst->poisonAmount >= monst->currentHP && !player.status[STATUS_HALLUCINATING] ? "fatally" : "very"));
4501                             combatMessage(buf, messageColorFromVictim(monst));
4502                         }
4503                     }
4504                 }
4505                 break;
4506             case BE_ENTRANCEMENT:
4507                 if (monst == &player) {
4508                     flashMonster(monst, &confusionGasColor, 100);
4509                     monst->status[STATUS_CONFUSED] = staffEntrancementDuration(theBolt->magnitude * FP_FACTOR);
4510                     monst->maxStatus[STATUS_CONFUSED] = max(monst->status[STATUS_CONFUSED], monst->maxStatus[STATUS_CONFUSED]);
4511                     message("the bolt hits you and you suddenly feel disoriented.", REQUIRE_ACKNOWLEDGMENT);
4512                     if (autoID) {
4513                         *autoID = true;
4514                     }
4515                 } else if (!(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4516                     monst->status[STATUS_ENTRANCED] = monst->maxStatus[STATUS_ENTRANCED] = staffEntrancementDuration(theBolt->magnitude * FP_FACTOR);
4517                     wakeUp(monst);
4518                     if (canSeeMonster(monst)) {
4519                         if (boltCatalog[BOLT_ENTRANCEMENT].backColor) {
4520                             flashMonster(monst, boltCatalog[BOLT_ENTRANCEMENT].backColor, 100);
4521                         }
4522                         if (autoID) {
4523                             *autoID = true;
4524                         }
4525                         sprintf(buf, "%s is entranced!", monstName);
4526                         message(buf, 0);
4527                     }
4528                 }
4529                 break;
4530             case BE_HEALING:
4531                 heal(monst, theBolt->magnitude * 10, false);
4532                 if (canSeeMonster(monst)) {
4533                     if (autoID) {
4534                         *autoID = true;
4535                     }
4536                 }
4537                 break;
4538             case BE_PLENTY:
4539                 if (!(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4540                     newMonst = cloneMonster(monst, true, true);
4541                     if (newMonst) {
4542                         monst->info.maxHP = newMonst->info.maxHP = (monst->info.maxHP + 1) / 2;
4543                         monst->currentHP = newMonst->currentHP = min(monst->currentHP, monst->info.maxHP);
4544                         if (boltCatalog[BOLT_PLENTY].backColor) {
4545                             flashMonster(monst, boltCatalog[BOLT_PLENTY].backColor, 100);
4546                             flashMonster(newMonst, boltCatalog[BOLT_PLENTY].backColor, 100);
4547                         }
4548                         if (autoID) {
4549                             *autoID = true;
4550                         }
4551                     }
4552                 }
4553                 break;
4554             case BE_DISCORD:
4555                 if (!(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
4556                     monst->status[STATUS_DISCORDANT] = monst->maxStatus[STATUS_DISCORDANT] = max(staffDiscordDuration(theBolt->magnitude * FP_FACTOR),
4557                                                                                                  monst->status[STATUS_DISCORDANT]);
4558                     if (canSeeMonster(monst)) {
4559                         if (boltCatalog[BOLT_DISCORD].backColor) {
4560                             flashMonster(monst, boltCatalog[BOLT_DISCORD].backColor, 100);
4561                         }
4562                         if (autoID) {
4563                             *autoID = true;
4564                         }
4565                     }
4566                 }
4567                 break;
4568             case BE_SHIELDING:
4569                 if (staffProtection(theBolt->magnitude * FP_FACTOR) > monst->status[STATUS_SHIELDED]) {
4570                     monst->status[STATUS_SHIELDED] = staffProtection(theBolt->magnitude * FP_FACTOR);
4571                 }
4572                 monst->maxStatus[STATUS_SHIELDED] = monst->status[STATUS_SHIELDED];
4573                 if (boltCatalog[BOLT_SHIELDING].backColor) {
4574                     flashMonster(monst, boltCatalog[BOLT_SHIELDING].backColor, 100);
4575                 }
4576                 if (autoID) {
4577                     *autoID = true;
4578                 }
4579                 break;
4580             default:
4581                 break;
4582         }
4583 
4584         if (!(theBolt->flags & BF_PASSES_THRU_CREATURES)) {
4585             terminateBolt = true;
4586         }
4587     }
4588 
4589     // Handle ordinary bolt updates that aren't dependent on hitting a creature.
4590     switch (theBolt->boltEffect) {
4591         case BE_BLINKING:
4592             if (caster == &player) {
4593                 player.xLoc = x;
4594                 player.yLoc = y;
4595                 if (lightingChanged) {
4596                     *lightingChanged = true;
4597                 }
4598             }
4599             break;
4600         default:
4601             break;
4602     }
4603 
4604     if (theBolt->pathDF) {
4605         spawnDungeonFeature(x, y, &dungeonFeatureCatalog[theBolt->pathDF], true, false);
4606     }
4607 
4608     if ((theBolt->flags & BF_FIERY)
4609         && exposeTileToFire(x, y, true)) {
4610 
4611         if (lightingChanged) {
4612             *lightingChanged = true;
4613         }
4614         if (autoID) {
4615             *autoID = true;
4616         }
4617     }
4618 
4619     if ((theBolt->flags & BF_ELECTRIC)
4620         && exposeTileToElectricity(x, y)) {
4621 
4622         if (lightingChanged) {
4623             *lightingChanged = true;
4624         }
4625         if (autoID) {
4626             *autoID = true;
4627         }
4628     }
4629 
4630     return terminateBolt;
4631 }
4632 
4633 // Called when the bolt hits something.
4634 // Caster can be null.
4635 // Pass in true for alreadyReflected if the bolt has already reflected off of something.
4636 // If the effect is visible enough for the player to identify the shooting item,
4637 // *autoID will be set to true. (AutoID can be null.)
detonateBolt(bolt * theBolt,creature * caster,short x,short y,boolean * autoID)4638 void detonateBolt(bolt *theBolt, creature *caster, short x, short y, boolean *autoID) {
4639     dungeonFeature feat;
4640     short i, x2, y2;
4641     creature *monst;
4642 
4643     const fixpt POW_OBSTRUCTION[] = {
4644         // 0.8^x, with x from 2 to 40:
4645         41943, 33554, 26843, 21474, 17179, 13743, 10995, 8796, 7036, 5629, 4503, 3602,
4646         2882, 2305, 1844, 1475, 1180, 944, 755, 604, 483, 386, 309, 247, 198, 158, 126,
4647         101, 81, 64, 51, 41, 33, 26, 21, 17, 13, 10, 8, 6, 5};
4648 
4649     switch(theBolt->boltEffect) {
4650         case BE_OBSTRUCTION:
4651             feat = dungeonFeatureCatalog[DF_FORCEFIELD];
4652             feat.probabilityDecrement = max(1, 75 * POW_OBSTRUCTION[min(40, theBolt->magnitude) - 2] / FP_FACTOR);
4653             spawnDungeonFeature(x, y, &feat, true, false);
4654             if (autoID) {
4655                 *autoID = true;
4656             }
4657             break;
4658         case BE_CONJURATION:
4659             for (i = 0; i < (staffBladeCount(theBolt->magnitude * FP_FACTOR)); i++) {
4660                 monst = generateMonster(MK_SPECTRAL_BLADE, true, false);
4661                 getQualifyingPathLocNear(&(monst->xLoc), &(monst->yLoc), x, y, true,
4662                                          T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)) & ~T_SPONTANEOUSLY_IGNITES, HAS_PLAYER,
4663                                          avoidedFlagsForMonster(&(monst->info)) & ~T_SPONTANEOUSLY_IGNITES, (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
4664                 monst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER);
4665                 monst->bookkeepingFlags &= ~MB_JUST_SUMMONED;
4666                 monst->leader = &player;
4667                 monst->creatureState = MONSTER_ALLY;
4668                 monst->ticksUntilTurn = monst->info.attackSpeed + 1; // So they don't move before the player's next turn.
4669                 pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
4670                 //refreshDungeonCell(monst->xLoc, monst->yLoc);
4671                 fadeInMonster(monst);
4672             }
4673             updateVision(true);
4674             //refreshSideBar(-1, -1, false);
4675             monst = NULL;
4676             if (autoID) {
4677                 *autoID = true;
4678             }
4679             break;
4680         case BE_BLINKING:
4681             if (pmap[x][y].flags & HAS_MONSTER) { // We're blinking onto an area already occupied by a submerged monster.
4682                                                   // Make sure we don't get the shooting monster by accident.
4683                 caster->xLoc = caster->yLoc = -1; // Will be set back to the destination in a moment.
4684                 monst = monsterAtLoc(x, y);
4685                 findAlternativeHomeFor(monst, &x2, &y2, true);
4686                 if (x2 >= 0) {
4687                     // Found an alternative location.
4688                     monst->xLoc = x2;
4689                     monst->yLoc = y2;
4690                     pmap[x][y].flags &= ~HAS_MONSTER;
4691                     pmap[x2][y2].flags |= HAS_MONSTER;
4692                 } else {
4693                     // No alternative location?? Hard to imagine how this could happen.
4694                     // Just bury the monster and never speak of this incident again.
4695                     killCreature(monst, true);
4696                     pmap[x][y].flags &= ~HAS_MONSTER;
4697                     monst = NULL;
4698                 }
4699             }
4700             caster->bookkeepingFlags &= ~MB_SUBMERGED;
4701             pmap[x][y].flags |= (caster == &player ? HAS_PLAYER : HAS_MONSTER);
4702             caster->xLoc = x;
4703             caster->yLoc = y;
4704             // Always break free on blink
4705             disentangle(caster);
4706             applyInstantTileEffectsToCreature(caster);
4707             if (caster == &player) {
4708                 // increase scent turn number so monsters don't sniff around at the old cell like idiots
4709                 rogue.scentTurnNumber += 30;
4710                 // get any items at the destination location
4711                 if (pmap[player.xLoc][player.yLoc].flags & HAS_ITEM) {
4712                     pickUpItemAt(player.xLoc, player.yLoc);
4713                 }
4714                 updateVision(true);
4715             }
4716             if (autoID) {
4717                 *autoID = true;
4718             }
4719             break;
4720         case BE_TUNNELING:
4721             setUpWaypoints(); // Recompute waypoints based on the new situation.
4722             break;
4723     }
4724 
4725     if (theBolt->targetDF) {
4726         spawnDungeonFeature(x, y, &dungeonFeatureCatalog[theBolt->targetDF], true, false);
4727     }
4728 }
4729 
4730 // returns whether the bolt effect should autoID any staff or wand it came from, if it came from a staff or wand
zap(short originLoc[2],short targetLoc[2],bolt * theBolt,boolean hideDetails)4731 boolean zap(short originLoc[2], short targetLoc[2], bolt *theBolt, boolean hideDetails) {
4732     short listOfCoordinates[MAX_BOLT_LENGTH][2];
4733     short i, j, k, x, y, x2, y2, numCells, blinkDistance = 0, boltLength, initialBoltLength, lights[DCOLS][DROWS][3];
4734     creature *monst = NULL, *shootingMonst;
4735     char buf[COLS], monstName[COLS];
4736     boolean autoID = false;
4737     boolean lightingChanged = false;
4738     boolean fastForward = false;
4739     boolean alreadyReflected = false;
4740     boolean boltInView;
4741     const color *boltColor;
4742     fixpt boltLightRadius;
4743 
4744     enum displayGlyph theChar;
4745     color foreColor, backColor, multColor;
4746 
4747     lightSource boltLights[500];
4748     color boltLightColors[500];
4749 
4750     brogueAssert(originLoc[0] != targetLoc[0] || originLoc[1] != targetLoc[1]);
4751     if (originLoc[0] == targetLoc[0] && originLoc[1] == targetLoc[1]) {
4752         return false;
4753     }
4754 
4755     x = originLoc[0];
4756     y = originLoc[1];
4757 
4758     initialBoltLength = boltLength = 5 * theBolt->magnitude;
4759     numCells = getLineCoordinates(listOfCoordinates, originLoc, targetLoc, (hideDetails ? &boltCatalog[BOLT_NONE] : theBolt));
4760     shootingMonst = monsterAtLoc(originLoc[0], originLoc[1]);
4761 
4762     if (hideDetails) {
4763         boltColor = &gray;
4764     } else {
4765         boltColor = theBolt->backColor;
4766     }
4767 
4768     refreshSideBar(-1, -1, false);
4769     displayCombatText(); // To announce who fired the bolt while the animation plays.
4770 
4771     if (theBolt->boltEffect == BE_BLINKING) {
4772         if (cellHasTerrainFlag(listOfCoordinates[0][0], listOfCoordinates[0][1], (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION))
4773             || ((pmap[listOfCoordinates[0][0]][listOfCoordinates[0][1]].flags & (HAS_PLAYER | HAS_MONSTER))
4774                 && !(monsterAtLoc(listOfCoordinates[0][0], listOfCoordinates[0][1])->bookkeepingFlags & MB_SUBMERGED))) {
4775                 // shooting blink point-blank into an obstruction does nothing.
4776                 return false;
4777             }
4778         theBolt->foreColor = &black;
4779         theBolt->theChar = shootingMonst->info.displayChar;
4780         pmap[originLoc[0]][originLoc[1]].flags &= ~(HAS_PLAYER | HAS_MONSTER);
4781         refreshDungeonCell(originLoc[0], originLoc[1]);
4782         blinkDistance = theBolt->magnitude * 2 + 1;
4783         checkForMissingKeys(originLoc[0], originLoc[1]);
4784     }
4785 
4786     if (boltColor) {
4787         for (i=0; i<initialBoltLength; i++) {
4788             boltLightColors[i] = *boltColor;
4789             boltLights[i] = lightCatalog[BOLT_LIGHT_SOURCE];
4790             boltLights[i].lightColor = &boltLightColors[i];
4791             boltLightRadius = 50LL * ((3 * FP_FACTOR) + (theBolt->magnitude * FP_FACTOR) * 4/3) * (initialBoltLength - i) / initialBoltLength / FP_FACTOR;
4792             boltLights[i].lightRadius.lowerBound = boltLights[i].lightRadius.upperBound = boltLightRadius;
4793             //boltLights[i].lightRadius.lowerBound = boltLights[i].lightRadius.upperBound = 50 * (3 + theBolt->magnitude * 1.33) * (initialBoltLength - i) / initialBoltLength;
4794             //printf("\nStandard: %i, attempted new: %lli", boltLights[i].lightRadius.lowerBound, boltLightRadius);
4795         }
4796     }
4797 
4798     if (theBolt->boltEffect == BE_TUNNELING) {
4799         tunnelize(originLoc[0], originLoc[1]);
4800     }
4801 
4802     backUpLighting(lights);
4803     boltInView = true;
4804     for (i=0; i<numCells; i++) {
4805 
4806         x = listOfCoordinates[i][0];
4807         y = listOfCoordinates[i][1];
4808 
4809         monst = monsterAtLoc(x, y);
4810 
4811         // Handle bolt reflection off of creatures (reflection off of terrain is handled further down).
4812         if (monst
4813             && !(theBolt->flags & BF_NEVER_REFLECTS)
4814             && projectileReflects(shootingMonst, monst)
4815             && i < MAX_BOLT_LENGTH - max(DCOLS, DROWS)) {
4816 
4817             if (projectileReflects(shootingMonst, monst)) { // if it scores another reflection roll, reflect at caster
4818                 numCells = reflectBolt(originLoc[0], originLoc[1], listOfCoordinates, i, !alreadyReflected);
4819             } else {
4820                 numCells = reflectBolt(-1, -1, listOfCoordinates, i, false); // otherwise reflect randomly
4821             }
4822 
4823             alreadyReflected = true;
4824 
4825             if (boltInView) {
4826                 monsterName(monstName, monst, true);
4827                 sprintf(buf, "%s deflect%s the %s",
4828                         monstName,
4829                         (monst == &player ? "" : "s"),
4830                         hideDetails ? "bolt" : theBolt->name);
4831                 combatMessage(buf, 0);
4832             }
4833             if (monst == &player
4834                 && rogue.armor
4835                 && rogue.armor->enchant2 == A_REFLECTION
4836                 && !(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)) {
4837 
4838                 autoIdentify(rogue.armor);
4839             }
4840             continue;
4841         }
4842 
4843         if (updateBolt(theBolt, shootingMonst, x, y, boltInView, alreadyReflected, &autoID, &lightingChanged)) {
4844             break;
4845         }
4846 
4847         if (lightingChanged) {
4848             updateVision(true);
4849             backUpLighting(lights);
4850         }
4851 
4852         // Update the visual effect of the bolt.
4853         // First do lighting. This lighting effect is expensive; do it only if the player can see the bolt.
4854         if (boltInView && boltColor) {
4855             demoteVisibility();
4856             restoreLighting(lights);
4857             for (k = min(i, boltLength + 2); k >= 0; k--) {
4858                 if (k < initialBoltLength) {
4859                     paintLight(&boltLights[k], listOfCoordinates[i-k][0], listOfCoordinates[i-k][1], false, false);
4860                 }
4861             }
4862         }
4863         boltInView = false;
4864         updateFieldOfViewDisplay(false, true);
4865         // Now draw the bolt itself.
4866         for (k = min(i, boltLength + 2); k >= 0; k--) {
4867             x2 = listOfCoordinates[i-k][0];
4868             y2 = listOfCoordinates[i-k][1];
4869             if (playerCanSeeOrSense(x2, y2)) {
4870                 if (!fastForward) {
4871                     getCellAppearance(x2, y2, &theChar, &foreColor, &backColor);
4872                     if (boltColor) {
4873                         applyColorAugment(&foreColor, boltColor, max(0, 100 - k * 100 / (boltLength)));
4874                         applyColorAugment(&backColor, boltColor, max(0, 100 - k * 100 / (boltLength)));
4875                     }
4876                     const boolean displayChar = (k == 0 || (theBolt->flags & BF_DISPLAY_CHAR_ALONG_LENGTH));
4877                     if (displayChar) {
4878                         if (theBolt->foreColor) {
4879                             foreColor = *(theBolt->foreColor);
4880                         }
4881                         if (theBolt->theChar) {
4882                             theChar = theBolt->theChar;
4883                         }
4884                     }
4885                     if (displayChar
4886                         && theBolt->foreColor
4887                         && theBolt->theChar) {
4888 
4889                         colorMultiplierFromDungeonLight(x2, y2, &multColor);
4890                         applyColorMultiplier(&foreColor, &multColor);
4891                         plotCharWithColor(theChar, mapToWindowX(x2), mapToWindowY(y2), &foreColor, &backColor);
4892                     } else if (boltColor) {
4893                         plotCharWithColor(theChar, mapToWindowX(x2), mapToWindowY(y2), &foreColor, &backColor);
4894                     } else if (k == 1
4895                                && theBolt->foreColor
4896                                && theBolt->theChar) {
4897 
4898                         refreshDungeonCell(x2, y2); // Clean up the contrail so it doesn't leave a trail of characters.
4899                     }
4900                 }
4901                 if (playerCanSee(x2, y2)) {
4902                     // Don't want to let omniscience mode affect boltInView; causes OOS.
4903                     boltInView = true;
4904                 }
4905             }
4906         }
4907         if (!fastForward && (boltInView || rogue.playbackOmniscience)) {
4908             fastForward = rogue.playbackFastForward || pauseBrogue(16);
4909         }
4910 
4911         if (theBolt->boltEffect == BE_BLINKING) {
4912             theBolt->magnitude = (blinkDistance - i) / 2 + 1;
4913             boltLength = theBolt->magnitude * 5;
4914             for (j=0; j<i; j++) {
4915                 refreshDungeonCell(listOfCoordinates[j][0], listOfCoordinates[j][1]);
4916             }
4917             if (i >= blinkDistance) {
4918                 break;
4919             }
4920         }
4921 
4922         // Some bolts halt at the square before they hit something.
4923         if ((theBolt->flags & BF_HALTS_BEFORE_OBSTRUCTION)
4924             && i + 1 < numCells) {
4925 
4926             x2 = listOfCoordinates[i+1][0];
4927             y2 = listOfCoordinates[i+1][1];
4928 
4929             if (cellHasTerrainFlag(x2, y2, (T_OBSTRUCTS_VISION | T_OBSTRUCTS_PASSABILITY))) {
4930                 break;
4931             }
4932 
4933             if (!(theBolt->flags & BF_PASSES_THRU_CREATURES)) {
4934                 monst = monsterAtLoc(listOfCoordinates[i+1][0], listOfCoordinates[i+1][1]);
4935                 if (monst && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
4936                     break;
4937                 }
4938             }
4939         }
4940 
4941         // Tunnel if we hit a wall.
4942         if (cellHasTerrainFlag(x, y, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION))
4943             && theBolt->boltEffect == BE_TUNNELING
4944             && tunnelize(x, y)) {
4945 
4946             updateVision(true);
4947             backUpLighting(lights);
4948             autoID = true;
4949             theBolt->magnitude--;
4950             boltLength = theBolt->magnitude * 5;
4951             for (j=0; j<i; j++) {
4952                 refreshDungeonCell(listOfCoordinates[j][0], listOfCoordinates[j][1]);
4953             }
4954             if (theBolt->magnitude <= 0) {
4955                 refreshDungeonCell(listOfCoordinates[i-1][0], listOfCoordinates[i-1][1]);
4956                 refreshDungeonCell(x, y);
4957                 break;
4958             }
4959         }
4960 
4961         // Stop when we hit a wall.
4962         if (cellHasTerrainFlag(x, y, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION))) {
4963             break;
4964         }
4965 
4966         // Does the bolt bounce before hitting a wall?
4967         // Can happen with a cursed deflection ring or a reflective terrain target, or when shooting a tunneling bolt into an impregnable wall.
4968         if (i + 1 < numCells
4969             && !(theBolt->flags & BF_NEVER_REFLECTS)) {
4970 
4971             x2 = listOfCoordinates[i+1][0];
4972             y2 = listOfCoordinates[i+1][1];
4973             if (cellHasTerrainFlag(x2, y2, (T_OBSTRUCTS_VISION | T_OBSTRUCTS_PASSABILITY))
4974                 && (projectileReflects(shootingMonst, NULL)
4975                     || cellHasTMFlag(x2, y2, TM_REFLECTS_BOLTS)
4976                     || (theBolt->boltEffect == BE_TUNNELING && (pmap[x2][y2].flags & IMPREGNABLE)))
4977                 && i < MAX_BOLT_LENGTH - max(DCOLS, DROWS)) {
4978 
4979                 sprintf(buf, "the bolt reflects off of %s", tileText(x2, y2));
4980                 if (projectileReflects(shootingMonst, NULL)) {
4981                     // If it scores another reflection roll, reflect at caster, unless it's already reflected.
4982                     numCells = reflectBolt(originLoc[0], originLoc[1], listOfCoordinates, i, !alreadyReflected);
4983                 } else {
4984                     numCells = reflectBolt(-1, -1, listOfCoordinates, i, false); // Otherwise reflect randomly.
4985                 }
4986                 alreadyReflected = true;
4987                 if (boltInView) {
4988                     combatMessage(buf, 0);
4989                 }
4990             }
4991         }
4992     }
4993 
4994     if (!fastForward) {
4995         refreshDungeonCell(x, y);
4996         if (i > 0) {
4997             refreshDungeonCell(listOfCoordinates[i-1][0], listOfCoordinates[i-1][1]);
4998         }
4999     }
5000 
5001     if (pmap[x][y].flags & (HAS_MONSTER | HAS_PLAYER)) {
5002         monst = monsterAtLoc(x, y);
5003         monsterName(monstName, monst, true);
5004     } else {
5005         monst = NULL;
5006     }
5007 
5008     detonateBolt(theBolt, shootingMonst, x, y, &autoID);
5009 
5010     updateLighting();
5011     backUpLighting(lights);
5012     boltInView = true;
5013     refreshSideBar(-1, -1, false);
5014     if (boltLength > 0) {
5015         if (boltColor) {
5016             // j is where the front tip of the bolt would be if it hadn't collided at i
5017             for (j=i; j < i + boltLength + 2; j++) { // j can imply a bolt tip position that is off the map
5018 
5019                 // dynamic lighting
5020                 if (boltInView) {
5021                     demoteVisibility();
5022                     restoreLighting(lights);
5023 
5024                     // k = j-i;
5025                     // boltLights[k].lightRadius.lowerBound *= 2;
5026                     // boltLights[k].lightRadius.upperBound *= 2;
5027                     // boltLights[k].lightColor = &boltImpactColor;
5028 
5029                     for (k = min(j, boltLength + 2); k >= j-i; k--) {
5030                         if (k < initialBoltLength) {
5031                             paintLight(&boltLights[k], listOfCoordinates[j-k][0], listOfCoordinates[j-k][1], false, false);
5032                         }
5033                     }
5034                     updateFieldOfViewDisplay(false, true);
5035                 }
5036 
5037                 boltInView = false;
5038 
5039                 // beam graphic
5040                 // k iterates from the tail tip of the visible portion of the bolt to the head
5041                 for (k = min(j, boltLength + 2); k >= j-i; k--) {
5042                     if (playerCanSee(listOfCoordinates[j-k][0], listOfCoordinates[j-k][1])) {
5043                         if (boltColor) {
5044                             hiliteCell(listOfCoordinates[j-k][0], listOfCoordinates[j-k][1], boltColor, max(0, 100 - k * 100 / (boltLength)), false);
5045                         }
5046                         boltInView = true;
5047                     }
5048                 }
5049 
5050                 if (!fastForward && boltInView) {
5051                     fastForward = rogue.playbackFastForward || pauseBrogue(16);
5052                 }
5053             }
5054         } else if (theBolt->flags & BF_DISPLAY_CHAR_ALONG_LENGTH) {
5055             for (j = 0; j < i; j++) {
5056                 x2 = listOfCoordinates[j][0];
5057                 y2 = listOfCoordinates[j][1];
5058                 if (playerCanSeeOrSense(x2, y2)) {
5059                     refreshDungeonCell(x2, y2);
5060                 }
5061             }
5062         }
5063     }
5064     return autoID;
5065 }
5066 
5067 // Relies on the sidebar entity list. If one is already selected, select the next qualifying. Otherwise, target the first qualifying.
nextTargetAfter(short * returnX,short * returnY,short targetX,short targetY,boolean targetEnemies,boolean targetAllies,boolean targetItems,boolean targetTerrain,boolean requireOpenPath,boolean reverseDirection)5068 boolean nextTargetAfter(short *returnX,
5069                         short *returnY,
5070                         short targetX,
5071                         short targetY,
5072                         boolean targetEnemies,
5073                         boolean targetAllies,
5074                         boolean targetItems,
5075                         boolean targetTerrain,
5076                         boolean requireOpenPath,
5077                         boolean reverseDirection) {
5078     short i, n, targetCount, newX, newY;
5079     short selectedIndex = 0;
5080     creature *monst;
5081     item *theItem;
5082     short deduplicatedTargetList[ROWS][2];
5083 
5084     targetCount = 0;
5085     for (i=0; i<ROWS; i++) {
5086         if (rogue.sidebarLocationList[i][0] != -1) {
5087             if (targetCount == 0
5088                 || deduplicatedTargetList[targetCount-1][0] != rogue.sidebarLocationList[i][0]
5089                 || deduplicatedTargetList[targetCount-1][1] != rogue.sidebarLocationList[i][1]) {
5090 
5091                 deduplicatedTargetList[targetCount][0] = rogue.sidebarLocationList[i][0];
5092                 deduplicatedTargetList[targetCount][1] = rogue.sidebarLocationList[i][1];
5093                 if (rogue.sidebarLocationList[i][0] == targetX
5094                     && rogue.sidebarLocationList[i][1] == targetY) {
5095                     selectedIndex = targetCount;
5096                 }
5097                 targetCount++;
5098             }
5099         }
5100     }
5101     for (i = reverseDirection ? targetCount - 1 : 0; reverseDirection ? i >= 0 : i < targetCount; reverseDirection ? i-- : i++) {
5102         n = (selectedIndex + i) % targetCount;
5103         newX = deduplicatedTargetList[n][0];
5104         newY = deduplicatedTargetList[n][1];
5105         if ((newX != player.xLoc || newY != player.yLoc)
5106             && (newX != targetX || newY != targetY)
5107             && (!requireOpenPath || openPathBetween(player.xLoc, player.yLoc, newX, newY))) {
5108 
5109             brogueAssert(coordinatesAreInMap(newX, newY));
5110             brogueAssert(n >= 0 && n < targetCount);
5111             monst = monsterAtLoc(newX, newY);
5112             if (monst) {
5113                 if (monstersAreEnemies(&player, monst)) {
5114                     if (targetEnemies) {
5115                         *returnX = newX;
5116                         *returnY = newY;
5117                         return true;
5118                     }
5119                 } else {
5120                     if (targetAllies) {
5121                         *returnX = newX;
5122                         *returnY = newY;
5123                         return true;
5124                     }
5125                 }
5126             }
5127             theItem = itemAtLoc(newX, newY);
5128             if (!monst && theItem && targetItems) {
5129                 *returnX = newX;
5130                 *returnY = newY;
5131                 return true;
5132             }
5133             if (!monst && !theItem && targetTerrain) {
5134                 *returnX = newX;
5135                 *returnY = newY;
5136                 return true;
5137             }
5138         }
5139     }
5140     return false;
5141 }
5142 
5143 // Returns how far it went before hitting something.
hiliteTrajectory(short coordinateList[DCOLS][2],short numCells,boolean eraseHiliting,const bolt * theBolt,const color * hiliteColor)5144 short hiliteTrajectory(short coordinateList[DCOLS][2], short numCells, boolean eraseHiliting, const bolt *theBolt, const color *hiliteColor) {
5145     short x, y, i;
5146     creature *monst;
5147 
5148     boolean isFiery = theBolt && (theBolt->flags & BF_FIERY);
5149     boolean isTunneling = theBolt && (theBolt->boltEffect == BE_TUNNELING);
5150     boolean passThroughMonsters = theBolt && (theBolt->flags & BF_PASSES_THRU_CREATURES);
5151 
5152     for (i=0; i<numCells; i++) {
5153         x = coordinateList[i][0];
5154         y = coordinateList[i][1];
5155         if (eraseHiliting) {
5156             refreshDungeonCell(x, y);
5157         } else {
5158             hiliteCell(x, y, hiliteColor, 20, true);
5159         }
5160 
5161         if (!(pmap[x][y].flags & DISCOVERED)) {
5162             if (isTunneling) {
5163                 continue;
5164             } else {
5165                 break;
5166             }
5167         } else if (!passThroughMonsters && pmap[x][y].flags & (HAS_MONSTER)
5168                    && (playerCanSee(x, y) || player.status[STATUS_TELEPATHIC])) {
5169             monst = monsterAtLoc(x, y);
5170             if (!(monst->bookkeepingFlags & MB_SUBMERGED)
5171                 && !monsterIsHidden(monst, &player)) {
5172 
5173                 i++;
5174                 break;
5175             }
5176         } else if (cellHasTerrainFlag(x, y, T_IS_FLAMMABLE) && isFiery) {
5177             continue;
5178         } else if (isTunneling && cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY) && (pmap[x][y].flags & IMPREGNABLE)
5179                 || !isTunneling && cellHasTerrainFlag(x, y, (T_OBSTRUCTS_VISION | T_OBSTRUCTS_PASSABILITY))) {
5180             i++;
5181             break;
5182         }
5183     }
5184     return i;
5185 }
5186 
5187 // Event is optional. Returns true if the event should be executed by the parent function.
moveCursor(boolean * targetConfirmed,boolean * canceled,boolean * tabKey,short targetLoc[2],rogueEvent * event,buttonState * state,boolean colorsDance,boolean keysMoveCursor,boolean targetCanLeaveMap)5188 boolean moveCursor(boolean *targetConfirmed,
5189                    boolean *canceled,
5190                    boolean *tabKey,
5191                    short targetLoc[2],
5192                    rogueEvent *event,
5193                    buttonState *state,
5194                    boolean colorsDance,
5195                    boolean keysMoveCursor,
5196                    boolean targetCanLeaveMap) {
5197     signed long keystroke;
5198     short moveIncrement;
5199     short buttonInput;
5200     boolean cursorMovementCommand, again, movementKeystroke, sidebarHighlighted;
5201     rogueEvent theEvent;
5202     short oldRNG;
5203 
5204     short *cursor = rogue.cursorLoc; // shorthand
5205 
5206     cursor[0] = targetLoc[0];
5207     cursor[1] = targetLoc[1];
5208 
5209     *targetConfirmed = *canceled = *tabKey = false;
5210     sidebarHighlighted = false;
5211 
5212     do {
5213         again = false;
5214         cursorMovementCommand = false;
5215         movementKeystroke = false;
5216 
5217         oldRNG = rogue.RNG;
5218         rogue.RNG = RNG_COSMETIC;
5219         //assureCosmeticRNG;
5220 
5221         if (state) { // Also running a button loop.
5222 
5223             // Update the display.
5224             overlayDisplayBuffer(state->dbuf, NULL);
5225 
5226             // Get input.
5227             nextBrogueEvent(&theEvent, false, colorsDance, true);
5228 
5229             // Process the input.
5230             buttonInput = processButtonInput(state, NULL, &theEvent);
5231 
5232             if (buttonInput != -1) {
5233                 state->buttonDepressed = state->buttonFocused = -1;
5234                 drawButtonsInState(state);
5235             }
5236 
5237             // Revert the display.
5238             overlayDisplayBuffer(state->rbuf, NULL);
5239 
5240         } else { // No buttons to worry about.
5241             nextBrogueEvent(&theEvent, false, colorsDance, true);
5242         }
5243         restoreRNG;
5244 
5245         if (theEvent.eventType == MOUSE_UP || theEvent.eventType == MOUSE_ENTERED_CELL) {
5246             if (theEvent.param1 >= 0
5247                 && theEvent.param1 < mapToWindowX(0)
5248                 && theEvent.param2 >= 0
5249                 && theEvent.param2 < ROWS - 1
5250                 && rogue.sidebarLocationList[theEvent.param2][0] > -1) {
5251 
5252                 // If the cursor is on an entity in the sidebar.
5253                 cursor[0] = rogue.sidebarLocationList[theEvent.param2][0];
5254                 cursor[1] = rogue.sidebarLocationList[theEvent.param2][1];
5255                 sidebarHighlighted = true;
5256                 cursorMovementCommand = true;
5257                 refreshSideBar(cursor[0], cursor[1], false);
5258                 if (theEvent.eventType == MOUSE_UP) {
5259                     *targetConfirmed = true;
5260                 }
5261             } else if (coordinatesAreInMap(windowToMapX(theEvent.param1), windowToMapY(theEvent.param2))
5262                        || targetCanLeaveMap && theEvent.eventType != MOUSE_UP) {
5263 
5264                 // If the cursor is in the map area, or is allowed to leave the map and it isn't a click.
5265                 if (theEvent.eventType == MOUSE_UP
5266                     && !theEvent.shiftKey
5267                     && (theEvent.controlKey || (cursor[0] == windowToMapX(theEvent.param1) && cursor[1] == windowToMapY(theEvent.param2)))) {
5268 
5269                     *targetConfirmed = true;
5270                 }
5271                 cursor[0] = windowToMapX(theEvent.param1);
5272                 cursor[1] = windowToMapY(theEvent.param2);
5273                 cursorMovementCommand = true;
5274             } else {
5275                 cursorMovementCommand = false;
5276                 again = theEvent.eventType != MOUSE_UP;
5277             }
5278         } else if (theEvent.eventType == KEYSTROKE) {
5279             keystroke = theEvent.param1;
5280             moveIncrement = ( (theEvent.controlKey || theEvent.shiftKey) ? 5 : 1 );
5281             stripShiftFromMovementKeystroke(&keystroke);
5282             switch(keystroke) {
5283                 case LEFT_ARROW:
5284                 case LEFT_KEY:
5285                 case NUMPAD_4:
5286                     if (keysMoveCursor && cursor[0] > 0) {
5287                         cursor[0] -= moveIncrement;
5288                     }
5289                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5290                     break;
5291                 case RIGHT_ARROW:
5292                 case RIGHT_KEY:
5293                 case NUMPAD_6:
5294                     if (keysMoveCursor && cursor[0] < DCOLS - 1) {
5295                         cursor[0] += moveIncrement;
5296                     }
5297                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5298                     break;
5299                 case UP_ARROW:
5300                 case UP_KEY:
5301                 case NUMPAD_8:
5302                     if (keysMoveCursor && cursor[1] > 0) {
5303                         cursor[1] -= moveIncrement;
5304                     }
5305                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5306                     break;
5307                 case DOWN_ARROW:
5308                 case DOWN_KEY:
5309                 case NUMPAD_2:
5310                     if (keysMoveCursor && cursor[1] < DROWS - 1) {
5311                         cursor[1] += moveIncrement;
5312                     }
5313                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5314                     break;
5315                 case UPLEFT_KEY:
5316                 case NUMPAD_7:
5317                     if (keysMoveCursor && cursor[0] > 0 && cursor[1] > 0) {
5318                         cursor[0] -= moveIncrement;
5319                         cursor[1] -= moveIncrement;
5320                     }
5321                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5322                     break;
5323                 case UPRIGHT_KEY:
5324                 case NUMPAD_9:
5325                     if (keysMoveCursor && cursor[0] < DCOLS - 1 && cursor[1] > 0) {
5326                         cursor[0] += moveIncrement;
5327                         cursor[1] -= moveIncrement;
5328                     }
5329                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5330                     break;
5331                 case DOWNLEFT_KEY:
5332                 case NUMPAD_1:
5333                     if (keysMoveCursor && cursor[0] > 0 && cursor[1] < DROWS - 1) {
5334                         cursor[0] -= moveIncrement;
5335                         cursor[1] += moveIncrement;
5336                     }
5337                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5338                     break;
5339                 case DOWNRIGHT_KEY:
5340                 case NUMPAD_3:
5341                     if (keysMoveCursor && cursor[0] < DCOLS - 1 && cursor[1] < DROWS - 1) {
5342                         cursor[0] += moveIncrement;
5343                         cursor[1] += moveIncrement;
5344                     }
5345                     cursorMovementCommand = movementKeystroke = keysMoveCursor;
5346                     break;
5347                 case TAB_KEY:
5348                 case SHIFT_TAB_KEY:
5349                 case NUMPAD_0:
5350                     *tabKey = true;
5351                     break;
5352                 case RETURN_KEY:
5353                     *targetConfirmed = true;
5354                     break;
5355                 case ESCAPE_KEY:
5356                 case ACKNOWLEDGE_KEY:
5357                     *canceled = true;
5358                     break;
5359                 default:
5360                     break;
5361             }
5362         } else if (theEvent.eventType == RIGHT_MOUSE_UP) {
5363             // do nothing
5364         } else {
5365             again = true;
5366         }
5367 
5368         if (sidebarHighlighted
5369             && (!(pmap[cursor[0]][cursor[1]].flags & (HAS_PLAYER | HAS_MONSTER))
5370                 || !canSeeMonster(monsterAtLoc(cursor[0], cursor[1])))
5371             && (!(pmap[cursor[0]][cursor[1]].flags & HAS_ITEM) || !playerCanSeeOrSense(cursor[0], cursor[1]))
5372             && (!cellHasTMFlag(cursor[0], cursor[1], TM_LIST_IN_SIDEBAR) || !playerCanSeeOrSense(cursor[0], cursor[1]))) {
5373 
5374             // The sidebar is highlighted but the cursor is not on a visible item, monster or terrain. Un-highlight the sidebar.
5375             refreshSideBar(-1, -1, false);
5376             sidebarHighlighted = false;
5377         }
5378 
5379         if (targetCanLeaveMap && !movementKeystroke) {
5380             // permit it to leave the map by up to 1 space in any direction if mouse controlled.
5381             cursor[0] = clamp(cursor[0], -1, DCOLS);
5382             cursor[1] = clamp(cursor[1], -1, DROWS);
5383         } else {
5384             cursor[0] = clamp(cursor[0], 0, DCOLS - 1);
5385             cursor[1] = clamp(cursor[1], 0, DROWS - 1);
5386         }
5387     } while (again && (!event || !cursorMovementCommand));
5388 
5389     if (event) {
5390         *event = theEvent;
5391     }
5392 
5393     if (sidebarHighlighted) {
5394         // Don't leave the sidebar highlighted when we exit.
5395         refreshSideBar(-1, -1, false);
5396         sidebarHighlighted = false;
5397     }
5398 
5399     targetLoc[0] = cursor[0];
5400     targetLoc[1] = cursor[1];
5401 
5402     return !cursorMovementCommand;
5403 }
5404 
pullMouseClickDuringPlayback(short loc[2])5405 void pullMouseClickDuringPlayback(short loc[2]) {
5406     rogueEvent theEvent;
5407 
5408     brogueAssert(rogue.playbackMode);
5409     nextBrogueEvent(&theEvent, false, false, false);
5410     loc[0] = windowToMapX(theEvent.param1);
5411     loc[1] = windowToMapY(theEvent.param2);
5412 }
5413 
5414 // Returns whether monst is targetable with thrown items, staves, wands, etc.
5415 // i.e. would the player ever select it?
creatureIsTargetable(creature * monst)5416 static boolean creatureIsTargetable(creature *monst) {
5417     return monst != NULL
5418         && canSeeMonster(monst)
5419         && monst->depth == rogue.depthLevel
5420         && !(monst->bookkeepingFlags & MB_IS_DYING)
5421         && openPathBetween(player.xLoc, player.yLoc, monst->xLoc, monst->yLoc);
5422 }
5423 
5424 // Return true if a target is chosen, or false if canceled.
chooseTarget(short returnLoc[2],short maxDistance,boolean stopAtTarget,boolean autoTarget,boolean targetAllies,const bolt * theBolt,const color * trajectoryColor)5425 boolean chooseTarget(short returnLoc[2],
5426                      short maxDistance,
5427                      boolean stopAtTarget,
5428                      boolean autoTarget,
5429                      boolean targetAllies,
5430                      const bolt *theBolt,
5431                      const color *trajectoryColor) {
5432     short originLoc[2], targetLoc[2], oldTargetLoc[2], coordinates[DCOLS][2], numCells, i, distance, newX, newY;
5433     creature *monst;
5434     boolean canceled, targetConfirmed, tabKey, cursorInTrajectory, focusedOnSomething = false;
5435     rogueEvent event = {0};
5436     short oldRNG;
5437     color trajColor = *trajectoryColor;
5438 
5439     normColor(&trajColor, 100, 10);
5440 
5441     if (rogue.playbackMode) {
5442         // In playback, pull the next event (a mouseclick) and use that location as the target.
5443         pullMouseClickDuringPlayback(returnLoc);
5444         rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
5445         return true;
5446     }
5447 
5448     oldRNG = rogue.RNG;
5449     rogue.RNG = RNG_COSMETIC;
5450     //assureCosmeticRNG;
5451 
5452     originLoc[0] = player.xLoc;
5453     originLoc[1] = player.yLoc;
5454 
5455     targetLoc[0] = oldTargetLoc[0] = player.xLoc;
5456     targetLoc[1] = oldTargetLoc[1] = player.yLoc;
5457 
5458     if (autoTarget) {
5459         if (creatureIsTargetable(rogue.lastTarget) && (targetAllies == (rogue.lastTarget->creatureState == MONSTER_ALLY))) {
5460             monst = rogue.lastTarget;
5461         } else {
5462             //rogue.lastTarget = NULL;
5463             if (nextTargetAfter(&newX, &newY, targetLoc[0], targetLoc[1], !targetAllies, targetAllies, false, false, true, false)) {
5464                 targetLoc[0] = newX;
5465                 targetLoc[1] = newY;
5466             }
5467             monst = monsterAtLoc(targetLoc[0], targetLoc[1]);
5468         }
5469         if (monst) {
5470             targetLoc[0] = monst->xLoc;
5471             targetLoc[1] = monst->yLoc;
5472             refreshSideBar(monst->xLoc, monst->yLoc, false);
5473             focusedOnSomething = true;
5474         }
5475     }
5476 
5477     numCells = getLineCoordinates(coordinates, originLoc, targetLoc, theBolt);
5478     if (maxDistance > 0) {
5479         numCells = min(numCells, maxDistance);
5480     }
5481     if (stopAtTarget) {
5482         numCells = min(numCells, distanceBetween(player.xLoc, player.yLoc, targetLoc[0], targetLoc[1]));
5483     }
5484 
5485     targetConfirmed = canceled = tabKey = false;
5486 
5487     do {
5488         printLocationDescription(targetLoc[0], targetLoc[1]);
5489 
5490         if (canceled) {
5491             refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]);
5492             hiliteTrajectory(coordinates, numCells, true, theBolt, trajectoryColor);
5493             confirmMessages();
5494             rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
5495             restoreRNG;
5496             return false;
5497         }
5498 
5499         if (tabKey) {
5500             if (nextTargetAfter(&newX, &newY, targetLoc[0], targetLoc[1], !targetAllies, targetAllies, false, false, true, event.shiftKey)) {
5501                 targetLoc[0] = newX;
5502                 targetLoc[1] = newY;
5503             }
5504         }
5505 
5506         monst = monsterAtLoc(targetLoc[0], targetLoc[1]);
5507         if (monst != NULL && monst != &player && canSeeMonster(monst)) {
5508             focusedOnSomething = true;
5509         } else if (playerCanSeeOrSense(targetLoc[0], targetLoc[1])
5510                    && (pmap[targetLoc[0]][targetLoc[1]].flags & HAS_ITEM) || cellHasTMFlag(targetLoc[0], targetLoc[1], TM_LIST_IN_SIDEBAR)) {
5511             focusedOnSomething = true;
5512         } else if (focusedOnSomething) {
5513             refreshSideBar(-1, -1, false);
5514             focusedOnSomething = false;
5515         }
5516         if (focusedOnSomething) {
5517             refreshSideBar(targetLoc[0], targetLoc[1], false);
5518         }
5519 
5520         refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]);
5521         hiliteTrajectory(coordinates, numCells, true, theBolt, &trajColor);
5522 
5523         if (!targetConfirmed) {
5524             numCells = getLineCoordinates(coordinates, originLoc, targetLoc, theBolt);
5525             if (maxDistance > 0) {
5526                 numCells = min(numCells, maxDistance);
5527             }
5528 
5529             if (stopAtTarget) {
5530                 numCells = min(numCells, distanceBetween(player.xLoc, player.yLoc, targetLoc[0], targetLoc[1]));
5531             }
5532             distance = hiliteTrajectory(coordinates, numCells, false, theBolt, &trajColor);
5533             cursorInTrajectory = false;
5534             for (i=0; i<distance; i++) {
5535                 if (coordinates[i][0] == targetLoc[0] && coordinates[i][1] == targetLoc[1]) {
5536                     cursorInTrajectory = true;
5537                     break;
5538                 }
5539             }
5540             hiliteCell(targetLoc[0], targetLoc[1], &white, (cursorInTrajectory ? 100 : 35), true);
5541         }
5542 
5543         oldTargetLoc[0] = targetLoc[0];
5544         oldTargetLoc[1] = targetLoc[1];
5545         moveCursor(&targetConfirmed, &canceled, &tabKey, targetLoc, &event, NULL, false, true, false);
5546         if (event.eventType == RIGHT_MOUSE_UP) { // Right mouse cancels.
5547             canceled = true;
5548         }
5549     } while (!targetConfirmed);
5550     if (maxDistance > 0) {
5551         numCells = min(numCells, maxDistance);
5552     }
5553     hiliteTrajectory(coordinates, numCells, true, theBolt, trajectoryColor);
5554     refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]);
5555 
5556     if (originLoc[0] == targetLoc[0] && originLoc[1] == targetLoc[1]) {
5557         confirmMessages();
5558         restoreRNG;
5559         rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
5560         return false;
5561     }
5562 
5563     monst = monsterAtLoc(targetLoc[0], targetLoc[1]);
5564     if (monst && monst != &player && canSeeMonster(monst)) {
5565         rogue.lastTarget = monst;
5566     }
5567 
5568     returnLoc[0] = targetLoc[0];
5569     returnLoc[1] = targetLoc[1];
5570     restoreRNG;
5571     rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
5572     return true;
5573 }
5574 
identifyItemKind(item * theItem)5575 void identifyItemKind(item *theItem) {
5576     itemTable *theTable;
5577     short tableCount, i, lastItem;
5578 
5579     theTable = tableForItemCategory(theItem->category, NULL);
5580     if (theTable) {
5581         theItem->flags &= ~ITEM_KIND_AUTO_ID;
5582 
5583         tableCount = 0;
5584         lastItem = -1;
5585 
5586         switch (theItem->category) {
5587             case SCROLL:
5588                 tableCount = NUMBER_SCROLL_KINDS;
5589                 break;
5590             case POTION:
5591                 tableCount = NUMBER_POTION_KINDS;
5592                 break;
5593             case WAND:
5594                 tableCount = NUMBER_WAND_KINDS;
5595                 break;
5596             case STAFF:
5597                 tableCount = NUMBER_STAFF_KINDS;
5598                 break;
5599             case RING:
5600                 tableCount = NUMBER_RING_KINDS;
5601                 break;
5602             default:
5603                 break;
5604         }
5605         if ((theItem->category & RING)
5606             && theItem->enchant1 <= 0) {
5607 
5608             theItem->flags |= ITEM_IDENTIFIED;
5609         }
5610 
5611         if ((theItem->category & WAND)
5612             && theTable[theItem->kind].range.lowerBound == theTable[theItem->kind].range.upperBound) {
5613 
5614             theItem->flags |= ITEM_IDENTIFIED;
5615         }
5616         if (tableCount) {
5617             theTable[theItem->kind].identified = true;
5618             for (i=0; i<tableCount; i++) {
5619                 if (!(theTable[i].identified)) {
5620                     if (lastItem != -1) {
5621                         return; // At least two unidentified items remain.
5622                     }
5623                     lastItem = i;
5624                 }
5625             }
5626             if (lastItem != -1) {
5627                 // Exactly one unidentified item remains; identify it.
5628                 theTable[lastItem].identified = true;
5629             }
5630         }
5631     }
5632 }
5633 
autoIdentify(item * theItem)5634 void autoIdentify(item *theItem) {
5635     short quantityBackup;
5636     char buf[COLS * 3], oldName[COLS * 3], newName[COLS * 3];
5637 
5638     if (tableForItemCategory(theItem->category, NULL)
5639         && !tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
5640 
5641         identifyItemKind(theItem);
5642         quantityBackup = theItem->quantity;
5643         theItem->quantity = 1;
5644         itemName(theItem, newName, false, true, NULL);
5645         theItem->quantity = quantityBackup;
5646         sprintf(buf, "(It must %s %s.)",
5647                 ((theItem->category & (POTION | SCROLL)) ? "have been" : "be"),
5648                 newName);
5649         messageWithColor(buf, &itemMessageColor, 0);
5650     }
5651 
5652     if ((theItem->category & (WEAPON | ARMOR))
5653         && (theItem->flags & ITEM_RUNIC)
5654         && !(theItem->flags & ITEM_RUNIC_IDENTIFIED)) {
5655 
5656         itemName(theItem, oldName, false, false, NULL);
5657         theItem->flags |= (ITEM_RUNIC_IDENTIFIED | ITEM_RUNIC_HINTED);
5658         itemName(theItem, newName, true, true, NULL);
5659         sprintf(buf, "(Your %s must be %s.)", oldName, newName);
5660         messageWithColor(buf, &itemMessageColor, 0);
5661     }
5662 }
5663 
5664 // returns whether the item disappeared
hitMonsterWithProjectileWeapon(creature * thrower,creature * monst,item * theItem)5665 boolean hitMonsterWithProjectileWeapon(creature *thrower, creature *monst, item *theItem) {
5666     char buf[DCOLS], theItemName[DCOLS], targetName[DCOLS], armorRunicString[DCOLS];
5667     boolean thrownWeaponHit;
5668     item *equippedWeapon;
5669     short damage;
5670 
5671     if (!(theItem->category & WEAPON)) {
5672         return false;
5673     }
5674 
5675     armorRunicString[0] = '\0';
5676 
5677     itemName(theItem, theItemName, false, false, NULL);
5678     monsterName(targetName, monst, true);
5679 
5680     monst->status[STATUS_ENTRANCED] = 0;
5681 
5682     if (monst != &player
5683         && monst->creatureMode != MODE_PERM_FLEEING
5684         && (monst->creatureState != MONSTER_FLEEING || monst->status[STATUS_MAGICAL_FEAR])
5685         && !(monst->bookkeepingFlags & MB_CAPTIVE)
5686         && monst->creatureState != MONSTER_ALLY) {
5687 
5688         monst->creatureState = MONSTER_TRACKING_SCENT;
5689         if (monst->status[STATUS_MAGICAL_FEAR]) {
5690             monst->status[STATUS_MAGICAL_FEAR] = 1;
5691         }
5692     }
5693 
5694     if (thrower == &player) {
5695         equippedWeapon = rogue.weapon;
5696         equipItem(theItem, true, NULL);
5697         thrownWeaponHit = attackHit(&player, monst);
5698         if (equippedWeapon) {
5699             equipItem(equippedWeapon, true, NULL);
5700         } else {
5701             unequipItem(theItem, true);
5702         }
5703     } else {
5704         thrownWeaponHit = attackHit(thrower, monst);
5705     }
5706 
5707     if (thrownWeaponHit) {
5708         damage = monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE) ? 0 :
5709                   (randClump(theItem->damage) * damageFraction(netEnchant(theItem)) / FP_FACTOR);
5710 
5711         if (monst == &player) {
5712             applyArmorRunicEffect(armorRunicString, thrower, &damage, false);
5713         }
5714 
5715         if (inflictDamage(thrower, monst, damage, &red, false)) { // monster killed
5716             sprintf(buf, "the %s %s %s.",
5717                     theItemName,
5718                     (monst->info.flags & MONST_INANIMATE) ? "destroyed" : "killed",
5719                     targetName);
5720             messageWithColor(buf, messageColorFromVictim(monst), 0);
5721         } else {
5722             sprintf(buf, "the %s hit %s.", theItemName, targetName);
5723             if (theItem->flags & ITEM_RUNIC) {
5724                 magicWeaponHit(monst, theItem, false);
5725             }
5726             messageWithColor(buf, messageColorFromVictim(monst), 0);
5727         }
5728         moralAttack(thrower, monst);
5729         if (armorRunicString[0]) {
5730             message(armorRunicString, 0);
5731         }
5732         return true;
5733     } else {
5734         theItem->flags &= ~ITEM_PLAYER_AVOIDS; // Don't avoid thrown weapons that missed.
5735         sprintf(buf, "the %s missed %s.", theItemName, targetName);
5736         message(buf, 0);
5737         return false;
5738     }
5739 }
5740 
throwItem(item * theItem,creature * thrower,short targetLoc[2],short maxDistance)5741 void throwItem(item *theItem, creature *thrower, short targetLoc[2], short maxDistance) {
5742     short listOfCoordinates[MAX_BOLT_LENGTH][2], originLoc[2];
5743     short i, x, y, numCells;
5744     creature *monst = NULL;
5745     char buf[COLS*3], buf2[COLS*3], buf3[COLS*3];
5746     enum displayGlyph displayChar;
5747     color foreColor, backColor, multColor;
5748     short dropLoc[2];
5749     boolean hitSomethingSolid = false, fastForward = false;
5750     enum dungeonLayers layer;
5751 
5752     theItem->flags |= ITEM_PLAYER_AVOIDS; // Avoid thrown items, unless it's a weapon that misses a monster.
5753 
5754     x = originLoc[0] = thrower->xLoc;
5755     y = originLoc[1] = thrower->yLoc;
5756 
5757     // Using BOLT_NONE for throws because all flags are off, which means we'll try to avoid all obstacles in front of the target
5758     numCells = getLineCoordinates(listOfCoordinates, originLoc, targetLoc, &boltCatalog[BOLT_NONE]);
5759 
5760     thrower->ticksUntilTurn = thrower->attackSpeed;
5761 
5762     if (thrower != &player
5763         && (pmap[originLoc[0]][originLoc[1]].flags & IN_FIELD_OF_VIEW)) {
5764 
5765         monsterName(buf2, thrower, true);
5766         itemName(theItem, buf3, false, true, NULL);
5767         sprintf(buf, "%s hurls %s.", buf2, buf3);
5768         message(buf, 0);
5769     }
5770 
5771     for (i=0; i<numCells && i < maxDistance; i++) {
5772         x = listOfCoordinates[i][0];
5773         y = listOfCoordinates[i][1];
5774 
5775         if (pmap[x][y].flags & (HAS_MONSTER | HAS_PLAYER)) {
5776             monst = monsterAtLoc(x, y);
5777             if (!(monst->bookkeepingFlags & MB_SUBMERGED)) {
5778 //          if (projectileReflects(thrower, monst) && i < DCOLS*2) {
5779 //              if (projectileReflects(thrower, monst)) { // if it scores another reflection roll, reflect at caster
5780 //                  numCells = reflectBolt(originLoc[0], originLoc[1], listOfCoordinates, i, true);
5781 //              } else {
5782 //                  numCells = reflectBolt(-1, -1, listOfCoordinates, i, false); // otherwise reflect randomly
5783 //              }
5784 //
5785 //              monsterName(buf2, monst, true);
5786 //              itemName(theItem, buf3, false, false, NULL);
5787 //              sprintf(buf, "%s deflect%s the %s", buf2, (monst == &player ? "" : "s"), buf3);
5788 //              combatMessage(buf, 0);
5789 //              continue;
5790 //          }
5791                 if ((theItem->category & WEAPON)
5792                     && theItem->kind != INCENDIARY_DART
5793                     && hitMonsterWithProjectileWeapon(thrower, monst, theItem)) {
5794                     deleteItem(theItem);
5795                     return;
5796                 }
5797                 break;
5798             }
5799         }
5800 
5801         // We hit something!
5802         if (cellHasTerrainFlag(x, y, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION))) {
5803             if ((theItem->category & WEAPON)
5804                 && (theItem->kind == INCENDIARY_DART)
5805                 && (cellHasTerrainFlag(x, y, T_IS_FLAMMABLE) || (pmap[x][y].flags & (HAS_MONSTER | HAS_PLAYER)))) {
5806                 // Incendiary darts thrown at flammable obstructions (foliage, wooden barricades, doors) will hit the obstruction
5807                 // instead of bursting a cell earlier.
5808             } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)
5809                        && cellHasTMFlag(x, y, TM_PROMOTES_ON_PLAYER_ENTRY)
5810                        && tileCatalog[pmap[x][y].layers[layerWithTMFlag(x, y, TM_PROMOTES_ON_PLAYER_ENTRY)]].flags & T_OBSTRUCTS_PASSABILITY) {
5811                 layer = layerWithTMFlag(x, y, TM_PROMOTES_ON_PLAYER_ENTRY);
5812                 if (tileCatalog[pmap[x][y].layers[layer]].flags & T_OBSTRUCTS_PASSABILITY) {
5813                     message(tileCatalog[pmap[x][y].layers[layer]].flavorText, 0);
5814                     promoteTile(x, y, layer, false);
5815                 }
5816             } else {
5817                 i--;
5818                 if (i >= 0) {
5819                     x = listOfCoordinates[i][0];
5820                     y = listOfCoordinates[i][1];
5821                 } else { // it was aimed point-blank into an obstruction
5822                     x = thrower->xLoc;
5823                     y = thrower->yLoc;
5824                 }
5825             }
5826             hitSomethingSolid = true;
5827             break;
5828         }
5829 
5830         if (playerCanSee(x, y)) { // show the graphic
5831             getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
5832             foreColor = *(theItem->foreColor);
5833             if (playerCanDirectlySee(x, y)) {
5834                 colorMultiplierFromDungeonLight(x, y, &multColor);
5835                 applyColorMultiplier(&foreColor, &multColor);
5836             } else { // clairvoyant visible
5837                 applyColorMultiplier(&foreColor, &clairvoyanceColor);
5838             }
5839             plotCharWithColor(theItem->displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
5840 
5841             if (!fastForward) {
5842                 fastForward = rogue.playbackFastForward || pauseBrogue(25);
5843             }
5844 
5845             refreshDungeonCell(x, y);
5846         }
5847 
5848         if (x == targetLoc[0] && y == targetLoc[1]) { // reached its target
5849             break;
5850         }
5851     }
5852 
5853     if ((theItem->category & POTION) && (hitSomethingSolid || !cellHasTerrainFlag(x, y, T_AUTO_DESCENT))) {
5854         if (theItem->kind == POTION_CONFUSION || theItem->kind == POTION_POISON
5855             || theItem->kind == POTION_PARALYSIS || theItem->kind == POTION_INCINERATION
5856             || theItem->kind == POTION_DARKNESS || theItem->kind == POTION_LICHEN
5857             || theItem->kind == POTION_DESCENT) {
5858             switch (theItem->kind) {
5859                 case POTION_POISON:
5860                     strcpy(buf, "the flask shatters and a deadly purple cloud billows out!");
5861                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_POISON_GAS_CLOUD_POTION], true, false);
5862                     message(buf, 0);
5863                     break;
5864                 case POTION_CONFUSION:
5865                     strcpy(buf, "the flask shatters and a multi-hued cloud billows out!");
5866                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_CONFUSION_GAS_CLOUD_POTION], true, false);
5867                     message(buf, 0);
5868                     break;
5869                 case POTION_PARALYSIS:
5870                     strcpy(buf, "the flask shatters and a cloud of pink gas billows out!");
5871                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_PARALYSIS_GAS_CLOUD_POTION], true, false);
5872                     message(buf, 0);
5873                     break;
5874                 case POTION_INCINERATION:
5875                     strcpy(buf, "the flask shatters and its contents burst violently into flame!");
5876                     message(buf, 0);
5877                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_INCINERATION_POTION], true, false);
5878                     break;
5879                 case POTION_DARKNESS:
5880                     strcpy(buf, "the flask shatters and the lights in the area start fading.");
5881                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_DARKNESS_POTION], true, false);
5882                     message(buf, 0);
5883                     break;
5884                 case POTION_DESCENT:
5885                     strcpy(buf, "as the flask shatters, the ground vanishes!");
5886                     message(buf, 0);
5887                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_HOLE_POTION], true, false);
5888                     break;
5889                 case POTION_LICHEN:
5890                     strcpy(buf, "the flask shatters and deadly spores spill out!");
5891                     message(buf, 0);
5892                     spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_LICHEN_PLANTED], true, false);
5893                     break;
5894             }
5895 
5896             autoIdentify(theItem);
5897 
5898             refreshDungeonCell(x, y);
5899 
5900             //if (pmap[x][y].flags & (HAS_MONSTER | HAS_PLAYER)) {
5901             //  monst = monsterAtLoc(x, y);
5902             //  applyInstantTileEffectsToCreature(monst);
5903             //}
5904         } else {
5905             if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
5906                 strcpy(buf2, "against");
5907             } else if (tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].mechFlags & TM_STAND_IN_TILE) {
5908                 strcpy(buf2, "into");
5909             } else {
5910                 strcpy(buf2, "on");
5911             }
5912             sprintf(buf, "the flask shatters and %s liquid splashes harmlessly %s %s.",
5913                     potionTable[theItem->kind].flavor, buf2, tileText(x, y));
5914             message(buf, 0);
5915             if (theItem->kind == POTION_HALLUCINATION && (theItem->flags & ITEM_MAGIC_DETECTED)) {
5916                 autoIdentify(theItem);
5917             }
5918         }
5919         deleteItem(theItem);
5920         return; // potions disappear when they break
5921     }
5922     if ((theItem->category & WEAPON) && theItem->kind == INCENDIARY_DART) {
5923         spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DF_DART_EXPLOSION], true, false);
5924         if (pmap[x][y].flags & (HAS_MONSTER | HAS_PLAYER)) {
5925             exposeCreatureToFire(monsterAtLoc(x, y));
5926         }
5927         deleteItem(theItem);
5928         return;
5929     }
5930     getQualifyingLocNear(dropLoc, x, y, true, 0, (T_OBSTRUCTS_ITEMS | T_OBSTRUCTS_PASSABILITY), (HAS_ITEM), false, false);
5931     placeItem(theItem, dropLoc[0], dropLoc[1]);
5932     refreshDungeonCell(dropLoc[0], dropLoc[1]);
5933 }
5934 
5935 /*
5936 Called when the player chooses to throw an item. theItem is optional; if it is
5937 NULL, the player is prompted to choose one. If autoThrow is true and the last
5938 targeted creature is still targetable, the item is thrown at it without prompting.
5939 */
throwCommand(item * theItem,boolean autoThrow)5940 void throwCommand(item *theItem, boolean autoThrow) {
5941     item *thrownItem;
5942     char buf[COLS], theName[COLS];
5943     unsigned char command[10];
5944     short maxDistance, zapTarget[2], quantity;
5945 
5946     command[0] = THROW_KEY;
5947 
5948     //
5949     // From inventory, we know item
5950     // Else ask ITEM
5951     //
5952     if (theItem == NULL) {
5953         theItem = promptForItemOfType((ALL_ITEMS), 0, 0,
5954                                       KEYBOARD_LABELS ? "Throw what? (a-z, shift for more info; or <esc> to cancel)" : "Throw what?", true);
5955     }
5956     if (theItem == NULL) {
5957         return;
5958     }
5959 
5960     //
5961     // Change quantity to 1 to generate name of item ("a" and not "some, the, etc")
5962     //
5963     quantity = theItem->quantity;
5964     theItem->quantity = 1;
5965     itemName(theItem, theName, false, false, NULL);
5966     theItem->quantity = quantity;
5967 
5968     command[1] = theItem->inventoryLetter;
5969     confirmMessages();
5970 
5971     //
5972     // If special item (not throw item)
5973     // -> Confirm before throw
5974     if (((theItem->flags & ITEM_EQUIPPED) || theItem->timesEnchanted > 0)
5975         && theItem->quantity <= 1) {
5976 
5977         sprintf(buf, "Are you sure you want to throw your %s?", theName);
5978         if (!confirm(buf, false)) {
5979             return;
5980         }
5981         if (theItem->flags & ITEM_CURSED) {
5982             sprintf(buf, "You cannot unequip your %s; it appears to be cursed.", theName);
5983             messageWithColor(buf, &itemMessageColor, 0);
5984             return;
5985         }
5986     }
5987 
5988     //
5989     // Ask location to throw
5990     //
5991     sprintf(buf, "Throw %s %s where? (<hjklyubn>, mouse, or <tab>)",
5992             (theItem->quantity > 1 ? "a" : "your"),
5993             theName);
5994     temporaryMessage(buf, REFRESH_SIDEBAR);
5995     maxDistance = (12 + 2 * max(rogue.strength - player.weaknessAmount - 12, 2));
5996 
5997     // Automatically pick a target for known bad potions and throwing weapons
5998     boolean autoTarget = false;
5999     switch (theItem->category) {
6000         case WEAPON:
6001             if (theItem->kind == DART || theItem->kind == INCENDIARY_DART || theItem->kind == JAVELIN) {
6002                 autoTarget = true;
6003             }
6004             break;
6005         case POTION:
6006             if ((theItem->flags & ITEM_MAGIC_DETECTED || potionTable[theItem->kind].identified)
6007                     && itemMagicPolarity(theItem) == -1) {
6008                 autoTarget = true;
6009             }
6010             break;
6011         default:
6012             break;
6013     }
6014 
6015     if (autoThrow && creatureIsTargetable(rogue.lastTarget)) {
6016         zapTarget[0] = rogue.lastTarget->xLoc;
6017         zapTarget[1] = rogue.lastTarget->yLoc;
6018     } else if (!chooseTarget(zapTarget, maxDistance, true, autoTarget, false, &boltCatalog[BOLT_NONE], &red)) {
6019         // player doesn't choose a target? return
6020         return;
6021     }
6022 
6023     if ((theItem->flags & ITEM_EQUIPPED) && theItem->quantity <= 1) {
6024         unequipItem(theItem, false);
6025     }
6026     command[2] = '\0';
6027     recordKeystrokeSequence(command);
6028     recordMouseClick(mapToWindowX(zapTarget[0]), mapToWindowY(zapTarget[1]), true, false);
6029 
6030     confirmMessages();
6031 
6032     thrownItem = generateItem(ALL_ITEMS, -1);   // generate item object in memory
6033     *thrownItem = *theItem;                     // clone the item
6034     thrownItem->flags &= ~ITEM_EQUIPPED;        // item not equiped
6035     thrownItem->quantity = 1;                   // item thrown, so quantity == 1
6036 
6037     itemName(thrownItem, theName, false, false, NULL); // update name of the thrown item
6038 
6039     throwItem(thrownItem, &player, zapTarget, maxDistance);
6040 
6041     // Update inventory
6042     // -> Now decrement or delete the thrown item out of the inventory.
6043     // -> Save last item thrown
6044     if (theItem->quantity > 1) {
6045         theItem->quantity--;
6046         rogue.lastItemThrown = theItem;
6047     } else {
6048         rogue.lastItemThrown = NULL;
6049         if (rogue.swappedIn == theItem || rogue.swappedOut == theItem) {
6050             rogue.swappedIn = NULL;
6051             rogue.swappedOut = NULL;
6052         }
6053         removeItemFromChain(theItem, packItems);
6054         deleteItem(theItem);
6055     }
6056     playerTurnEnded();
6057 }
6058 
relabel(item * theItem)6059 void relabel(item *theItem) {
6060     item *oldItem;
6061     char buf[COLS * 3], theName[COLS], newLabel;
6062     unsigned char command[10];
6063 
6064     if (!KEYBOARD_LABELS && !rogue.playbackMode) {
6065         return;
6066     }
6067     if (theItem == NULL) {
6068         theItem = promptForItemOfType((ALL_ITEMS), 0, 0,
6069                                       KEYBOARD_LABELS ? "Relabel what? (a-z, shift for more info; or <esc> to cancel)" : "Relabel what?", true);
6070     }
6071     if (theItem == NULL) {
6072         return;
6073     }
6074     temporaryMessage("New letter? (a-z)", 0);
6075     newLabel = '\0';
6076     do {
6077         newLabel = nextKeyPress(true);
6078     } while (!newLabel);
6079 
6080     if (newLabel >= 'A' && newLabel <= 'Z') {
6081         newLabel += 'a' - 'A'; // lower-case.
6082     }
6083     if (newLabel >= 'a' && newLabel <= 'z') {
6084         if (newLabel != theItem->inventoryLetter) {
6085             command[0] = RELABEL_KEY;
6086             command[1] = theItem->inventoryLetter;
6087             command[2] = newLabel;
6088             command[3] = '\0';
6089             recordKeystrokeSequence(command);
6090 
6091             oldItem = itemOfPackLetter(newLabel);
6092             if (oldItem) {
6093                 oldItem->inventoryLetter = theItem->inventoryLetter;
6094                 itemName(oldItem, theName, true, true, NULL);
6095                 sprintf(buf, "Relabeled %s as (%c);", theName, oldItem->inventoryLetter);
6096                 messageWithColor(buf, &itemMessageColor, 0);
6097             }
6098             theItem->inventoryLetter = newLabel;
6099             itemName(theItem, theName, true, true, NULL);
6100             sprintf(buf, "%selabeled %s as (%c).", oldItem ? " r" : "R", theName, newLabel);
6101             messageWithColor(buf, &itemMessageColor, 0);
6102         } else {
6103             itemName(theItem, theName, true, true, NULL);
6104             sprintf(buf, "%s %s already labeled (%c).",
6105                     theName,
6106                     theItem->quantity == 1 ? "is" : "are",
6107                     theItem->inventoryLetter);
6108             messageWithColor(buf, &itemMessageColor, 0);
6109         }
6110     }
6111 }
6112 
6113 // If the most recently equipped item caused another item to be unequiped, is
6114 // uncursed, and both haven't left the inventory since, swap them back.
swapLastEquipment()6115 void swapLastEquipment() {
6116     item *theItem;
6117     unsigned char command[10];
6118 
6119     if (rogue.swappedIn == NULL || rogue.swappedOut == NULL) {
6120         confirmMessages();
6121         message("You have nothing to swap.", 0);
6122         return;
6123     }
6124 
6125     if (!equipItem(rogue.swappedOut, false, rogue.swappedIn)) {
6126         // Cursed
6127         return;
6128     }
6129 
6130     command[0] = SWAP_KEY;
6131     command[1] = '\0';
6132     recordKeystrokeSequence(command);
6133 
6134     theItem = rogue.swappedIn;
6135     rogue.swappedIn = rogue.swappedOut;
6136     rogue.swappedOut = theItem;
6137 
6138     playerTurnEnded();
6139 }
6140 
6141 // If the blink trajectory lands in lava based on the player's knowledge, abort.
6142 // If the blink trajectory might land in lava based on the player's knowledge,
6143 // prompt for confirmation.
playerCancelsBlinking(const short originLoc[2],const short targetLoc[2],const short maxDistance)6144 boolean playerCancelsBlinking(const short originLoc[2], const short targetLoc[2], const short maxDistance) {
6145     short coordinates[DCOLS][2], impactLoc[2];
6146     short numCells, i, x, y;
6147     boolean certainDeath = false;
6148     boolean possibleDeath = false;
6149     unsigned long tFlags, tmFlags;
6150 
6151     if (rogue.playbackMode) {
6152         return false;
6153     }
6154 
6155     if (player.status[STATUS_IMMUNE_TO_FIRE]
6156         || player.status[STATUS_LEVITATING]) {
6157         return false;
6158     }
6159 
6160     getImpactLoc(impactLoc, originLoc, targetLoc, maxDistance > 0 ? maxDistance : DCOLS, true, &boltCatalog[BOLT_BLINKING]);
6161     getLocationFlags(impactLoc[0], impactLoc[1], &tFlags, &tmFlags, NULL, true);
6162     if (maxDistance > 0) {
6163         if ((pmap[impactLoc[0]][impactLoc[1]].flags & DISCOVERED)
6164             && (tFlags & T_LAVA_INSTA_DEATH)
6165             && !(tFlags & (T_ENTANGLES | T_AUTO_DESCENT))
6166             && !(tmFlags & TM_EXTINGUISHES_FIRE)) {
6167 
6168             certainDeath = possibleDeath = true;
6169         }
6170     } else {
6171         certainDeath = true;
6172         numCells = getLineCoordinates(coordinates, originLoc, targetLoc, &boltCatalog[BOLT_BLINKING]);
6173         for (i = 0; i < numCells; i++) {
6174             x = coordinates[i][0];
6175             y = coordinates[i][1];
6176             if (pmap[x][y].flags & DISCOVERED) {
6177                 getLocationFlags(x, y, &tFlags, NULL, NULL, true);
6178                 if ((tFlags & T_LAVA_INSTA_DEATH)
6179                     && !(tFlags & (T_ENTANGLES | T_AUTO_DESCENT))
6180                     && !(tmFlags & TM_EXTINGUISHES_FIRE)) {
6181 
6182                     possibleDeath = true;
6183                 } else if (i >= staffBlinkDistance(2 * FP_FACTOR) - 1) {
6184                     // Found at least one possible safe landing spot.
6185                     certainDeath = false;
6186                 }
6187             }
6188             if (x == impactLoc[0]
6189                 && y == impactLoc[1]) {
6190 
6191                 break;
6192             }
6193         }
6194     }
6195     if (possibleDeath && certainDeath) {
6196         message("that would be certain death!", 0);
6197         return true;
6198     }
6199     if (possibleDeath
6200         && !confirm("Blink across lava with unknown range?", false)) {
6201         return true;
6202     }
6203     return false;
6204 }
6205 
useStaffOrWand(item * theItem,boolean * commandsRecorded)6206 boolean useStaffOrWand(item *theItem, boolean *commandsRecorded) {
6207     char buf[COLS], buf2[COLS];
6208     unsigned char command[10];
6209     short zapTarget[2], originLoc[2], maxDistance, c;
6210     boolean autoTarget, targetAllies, autoID, boltKnown, confirmedTarget;
6211     bolt theBolt;
6212     color trajectoryHiliteColor;
6213 
6214     c = 0;
6215     command[c++] = APPLY_KEY;
6216     command[c++] = theItem->inventoryLetter;
6217 
6218     if (theItem->charges <= 0 && (theItem->flags & ITEM_IDENTIFIED)) {
6219         itemName(theItem, buf2, false, false, NULL);
6220         sprintf(buf, "Your %s has no charges.", buf2);
6221         messageWithColor(buf, &itemMessageColor, 0);
6222         return false;
6223     }
6224     temporaryMessage("Direction? (<hjklyubn>, mouse, or <tab>; <return> to confirm)", REFRESH_SIDEBAR);
6225     itemName(theItem, buf2, false, false, NULL);
6226     sprintf(buf, "Zapping your %s:", buf2);
6227     printString(buf, mapToWindowX(0), 1, &itemMessageColor, &black, NULL);
6228 
6229     theBolt = boltCatalog[tableForItemCategory(theItem->category, NULL)[theItem->kind].strengthRequired];
6230     if (theItem->category == STAFF) {
6231         theBolt.magnitude = theItem->enchant1;
6232     }
6233 
6234     if ((theItem->category & STAFF) && theItem->kind == STAFF_BLINKING
6235         && theItem->flags & (ITEM_IDENTIFIED | ITEM_MAX_CHARGES_KNOWN)) {
6236 
6237         maxDistance = staffBlinkDistance(netEnchant(theItem));
6238     } else {
6239         maxDistance = -1;
6240     }
6241     if (tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
6242         autoTarget = targetAllies = false;
6243         if (!player.status[STATUS_HALLUCINATING]) {
6244             if (theBolt.flags & (BF_TARGET_ALLIES | BF_TARGET_ENEMIES)) {
6245                 autoTarget = true;
6246             }
6247             if (theBolt.flags & BF_TARGET_ALLIES) {
6248                 targetAllies = true;
6249             }
6250         }
6251     } else {
6252         autoTarget = true;
6253         targetAllies = false;
6254     }
6255     boltKnown = (((theItem->category & WAND) && wandTable[theItem->kind].identified)
6256                  || ((theItem->category & STAFF) && staffTable[theItem->kind].identified));
6257     if (!boltKnown) {
6258         trajectoryHiliteColor = gray;
6259     } else if (theBolt.backColor == NULL) {
6260         trajectoryHiliteColor = red;
6261     } else {
6262         trajectoryHiliteColor = *theBolt.backColor;
6263     }
6264 
6265     originLoc[0] = player.xLoc;
6266     originLoc[1] = player.yLoc;
6267     confirmedTarget = chooseTarget(zapTarget, maxDistance, false, autoTarget,
6268         targetAllies, (boltKnown ? &theBolt : &boltCatalog[BOLT_NONE]), &trajectoryHiliteColor);
6269     if (confirmedTarget
6270         && boltKnown
6271         && theBolt.boltEffect == BE_BLINKING
6272         && playerCancelsBlinking(originLoc, zapTarget, maxDistance)) {
6273 
6274         confirmedTarget = false;
6275     }
6276     if (confirmedTarget) {
6277 
6278         command[c] = '\0';
6279         if (!(*commandsRecorded)) {
6280             recordKeystrokeSequence(command);
6281             recordMouseClick(mapToWindowX(zapTarget[0]), mapToWindowY(zapTarget[1]), true, false);
6282             *commandsRecorded = true;
6283         }
6284         confirmMessages();
6285 
6286         rogue.featRecord[FEAT_PURE_WARRIOR] = false;
6287 
6288         if (theItem->charges > 0) {
6289             autoID = zap(originLoc, zapTarget,
6290                          &theBolt,
6291                          !boltKnown);   // hide bolt details
6292             if (autoID) {
6293                 if (!tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
6294                     itemName(theItem, buf2, false, false, NULL);
6295                     sprintf(buf, "(Your %s must be ", buf2);
6296                     identifyItemKind(theItem);
6297                     itemName(theItem, buf2, false, true, NULL);
6298                     strcat(buf, buf2);
6299                     strcat(buf, ".)");
6300                     messageWithColor(buf, &itemMessageColor, 0);
6301                 }
6302             }
6303         } else {
6304             itemName(theItem, buf2, false, false, NULL);
6305             if (theItem->category == STAFF) {
6306                 sprintf(buf, "Your %s fizzles; it must be out of charges for now.", buf2);
6307             } else {
6308                 sprintf(buf, "Your %s fizzles; it must be depleted.", buf2);
6309             }
6310             messageWithColor(buf, &itemMessageColor, 0);
6311             theItem->flags |= ITEM_MAX_CHARGES_KNOWN;
6312             playerTurnEnded();
6313             return false;
6314         }
6315     } else {
6316         return false;
6317     }
6318     return true;
6319 }
6320 
summonGuardian(item * theItem)6321 void summonGuardian(item *theItem) {
6322     short x = player.xLoc, y = player.yLoc;
6323     creature *monst;
6324 
6325     monst = generateMonster(MK_CHARM_GUARDIAN, false, false);
6326     getQualifyingPathLocNear(&(monst->xLoc), &(monst->yLoc), x, y, true,
6327                              T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)) & ~T_SPONTANEOUSLY_IGNITES, HAS_PLAYER,
6328                              avoidedFlagsForMonster(&(monst->info)) & ~T_SPONTANEOUSLY_IGNITES, (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
6329     monst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER);
6330     monst->bookkeepingFlags &= ~MB_JUST_SUMMONED;
6331     monst->leader = &player;
6332     monst->creatureState = MONSTER_ALLY;
6333     monst->ticksUntilTurn = monst->info.attackSpeed + 1; // So they don't move before the player's next turn.
6334     monst->status[STATUS_LIFESPAN_REMAINING] = monst->maxStatus[STATUS_LIFESPAN_REMAINING] = charmGuardianLifespan(netEnchant(theItem));
6335     pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
6336     fadeInMonster(monst);
6337 }
6338 
useCharm(item * theItem)6339 void useCharm(item *theItem) {
6340     fixpt enchant = netEnchant(theItem);
6341 
6342     rogue.featRecord[FEAT_PURE_WARRIOR] = false;
6343 
6344     switch (theItem->kind) {
6345         case CHARM_HEALTH:
6346             heal(&player, charmHealing(enchant), false);
6347             message("You feel much healthier.", 0);
6348             break;
6349         case CHARM_PROTECTION:
6350             if (charmProtection(enchant) > player.status[STATUS_SHIELDED]) {
6351                 player.status[STATUS_SHIELDED] = charmProtection(enchant);
6352             }
6353             player.maxStatus[STATUS_SHIELDED] = player.status[STATUS_SHIELDED];
6354             if (boltCatalog[BOLT_SHIELDING].backColor) {
6355                 flashMonster(&player, boltCatalog[BOLT_SHIELDING].backColor, 100);
6356             }
6357             message("A shimmering shield coalesces around you.", 0);
6358             break;
6359         case CHARM_HASTE:
6360             haste(&player, charmEffectDuration(theItem->kind, theItem->enchant1));
6361             break;
6362         case CHARM_FIRE_IMMUNITY:
6363             player.status[STATUS_IMMUNE_TO_FIRE] = player.maxStatus[STATUS_IMMUNE_TO_FIRE] = charmEffectDuration(theItem->kind, theItem->enchant1);
6364             if (player.status[STATUS_BURNING]) {
6365                 extinguishFireOnCreature(&player);
6366             }
6367             message("you no longer fear fire.", 0);
6368             break;
6369         case CHARM_INVISIBILITY:
6370             imbueInvisibility(&player, charmEffectDuration(theItem->kind, theItem->enchant1));
6371             message("You shiver as a chill runs up your spine.", 0);
6372             break;
6373         case CHARM_TELEPATHY:
6374             makePlayerTelepathic(charmEffectDuration(theItem->kind, theItem->enchant1));
6375             break;
6376         case CHARM_LEVITATION:
6377             player.status[STATUS_LEVITATING] = player.maxStatus[STATUS_LEVITATING] = charmEffectDuration(theItem->kind, theItem->enchant1);
6378             player.bookkeepingFlags &= ~MB_SEIZED; // break free of holding monsters
6379             message("you float into the air!", 0);
6380             break;
6381         case CHARM_SHATTERING:
6382             messageWithColor("your charm emits a wave of turquoise light that pierces the nearby walls!", &itemMessageColor, 0);
6383             crystalize(charmShattering(enchant));
6384             break;
6385         case CHARM_GUARDIAN:
6386             messageWithColor("your charm flashes and the form of a mythical guardian coalesces!", &itemMessageColor, 0);
6387             summonGuardian(theItem);
6388             break;
6389         case CHARM_TELEPORTATION:
6390             teleport(&player, -1, -1, true);
6391             break;
6392         case CHARM_RECHARGING:
6393             rechargeItems(STAFF);
6394             break;
6395         case CHARM_NEGATION:
6396             negationBlast("your charm", charmNegationRadius(enchant) + 1); // Add 1 because otherwise radius 1 would affect only the player.
6397             break;
6398         default:
6399             break;
6400     }
6401 }
6402 
apply(item * theItem,boolean recordCommands)6403 void apply(item *theItem, boolean recordCommands) {
6404     char buf[COLS * 3], buf2[COLS * 3];
6405     boolean commandsRecorded, revealItemType;
6406     unsigned char command[10] = "";
6407     short c;
6408 
6409     commandsRecorded = !recordCommands;
6410     c = 0;
6411     command[c++] = APPLY_KEY;
6412 
6413     revealItemType = false;
6414 
6415     if (!theItem) {
6416         theItem = promptForItemOfType((SCROLL|FOOD|POTION|STAFF|WAND|CHARM), 0, 0,
6417                                       KEYBOARD_LABELS ? "Apply what? (a-z, shift for more info; or <esc> to cancel)" : "Apply what?",
6418                                       true);
6419     }
6420 
6421     if (theItem == NULL) {
6422         return;
6423     }
6424 
6425     if ((theItem->category == SCROLL || theItem->category == POTION)
6426         && magicCharDiscoverySuffix(theItem->category, theItem->kind) == -1
6427         && ((theItem->flags & ITEM_MAGIC_DETECTED) || tableForItemCategory(theItem->category, NULL)[theItem->kind].identified)) {
6428 
6429         if (tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
6430             sprintf(buf,
6431                     "Really %s a %s of %s?",
6432                     theItem->category == SCROLL ? "read" : "drink",
6433                     theItem->category == SCROLL ? "scroll" : "potion",
6434                     tableForItemCategory(theItem->category, NULL)[theItem->kind].name);
6435         } else {
6436             sprintf(buf,
6437                     "Really %s a cursed %s?",
6438                     theItem->category == SCROLL ? "read" : "drink",
6439                     theItem->category == SCROLL ? "scroll" : "potion");
6440         }
6441         if (!confirm(buf, false)) {
6442             return;
6443         }
6444     }
6445 
6446     command[c++] = theItem->inventoryLetter;
6447     confirmMessages();
6448     switch (theItem->category) {
6449         case FOOD:
6450             if (STOMACH_SIZE - player.status[STATUS_NUTRITION] < foodTable[theItem->kind].strengthRequired) { // Not hungry enough.
6451                 sprintf(buf, "You're not hungry enough to fully enjoy the %s. Eat it anyway?",
6452                         (theItem->kind == RATION ? "food" : "mango"));
6453                 if (!confirm(buf, false)) {
6454                     return;
6455                 }
6456             }
6457             player.status[STATUS_NUTRITION] = min(foodTable[theItem->kind].strengthRequired + player.status[STATUS_NUTRITION], STOMACH_SIZE);
6458             if (theItem->kind == RATION) {
6459                 messageWithColor("That food tasted delicious!", &itemMessageColor, 0);
6460             } else {
6461                 messageWithColor("My, what a yummy mango!", &itemMessageColor, 0);
6462             }
6463             rogue.featRecord[FEAT_ASCETIC] = false;
6464             break;
6465         case POTION:
6466             command[c] = '\0';
6467             if (!commandsRecorded) {
6468                 recordKeystrokeSequence(command);
6469                 commandsRecorded = true;
6470             }
6471             if (!potionTable[theItem->kind].identified) {
6472                 revealItemType = true;
6473             }
6474             drinkPotion(theItem);
6475             break;
6476         case SCROLL:
6477             command[c] = '\0';
6478             if (!commandsRecorded) {
6479                 recordKeystrokeSequence(command);
6480                 commandsRecorded = true; // have to record in case further keystrokes are necessary (e.g. enchant scroll)
6481             }
6482             if (!scrollTable[theItem->kind].identified
6483                 && theItem->kind != SCROLL_ENCHANTING
6484                 && theItem->kind != SCROLL_IDENTIFY) {
6485 
6486                 revealItemType = true;
6487             }
6488             readScroll(theItem);
6489             break;
6490         case STAFF:
6491         case WAND:
6492             if (!useStaffOrWand(theItem, &commandsRecorded)) {
6493                 return;
6494             }
6495             break;
6496         case CHARM:
6497             if (theItem->charges > 0) {
6498                 itemName(theItem, buf2, false, false, NULL);
6499                 sprintf(buf, "Your %s hasn't finished recharging.", buf2);
6500                 messageWithColor(buf, &itemMessageColor, 0);
6501                 return;
6502             }
6503             if (!commandsRecorded) {
6504                 command[c] = '\0';
6505                 recordKeystrokeSequence(command);
6506                 commandsRecorded = true;
6507             }
6508             useCharm(theItem);
6509             break;
6510         default:
6511             itemName(theItem, buf2, false, true, NULL);
6512             sprintf(buf, "you can't apply %s.", buf2);
6513             message(buf, 0);
6514             return;
6515     }
6516 
6517     if (!commandsRecorded) { // to make sure we didn't already record the keystrokes above with staff/wand targeting
6518         command[c] = '\0';
6519         recordKeystrokeSequence(command);
6520         commandsRecorded = true;
6521     }
6522 
6523     // Reveal the item type if appropriate.
6524     if (revealItemType) {
6525         autoIdentify(theItem);
6526     }
6527 
6528     theItem->lastUsed[2] = theItem->lastUsed[1];
6529     theItem->lastUsed[1] = theItem->lastUsed[0];
6530     theItem->lastUsed[0] = rogue.absoluteTurnNumber;
6531 
6532     if (theItem->category & CHARM) {
6533         theItem->charges = charmRechargeDelay(theItem->kind, theItem->enchant1);
6534     } else if (theItem->charges > 0) {
6535         theItem->charges--;
6536         if (theItem->category == WAND) {
6537             theItem->enchant2++; // keeps track of how many times the wand has been discharged for the player's convenience
6538         }
6539     } else if (theItem->quantity > 1) {
6540         theItem->quantity--;
6541     } else {
6542         removeItemFromChain(theItem, packItems);
6543         deleteItem(theItem);
6544     }
6545     playerTurnEnded();
6546 }
6547 
identify(item * theItem)6548 void identify(item *theItem) {
6549     theItem->flags |= ITEM_IDENTIFIED;
6550     theItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
6551     if (theItem->flags & ITEM_RUNIC) {
6552         theItem->flags |= (ITEM_RUNIC_IDENTIFIED | ITEM_RUNIC_HINTED);
6553     }
6554     if (theItem->category & RING) {
6555         updateRingBonuses();
6556     }
6557     identifyItemKind(theItem);
6558 }
6559 /*
6560 enum monsterTypes chooseVorpalEnemy() {
6561     short i, index, possCount = 0, deepestLevel = 0, deepestHorde, chosenHorde, failsafe = 25;
6562     enum monsterTypes candidate;
6563 
6564     for (i=0; i<NUMBER_HORDES; i++) {
6565         if (hordeCatalog[i].minLevel >= rogue.depthLevel && !hordeCatalog[i].flags) {
6566             possCount += hordeCatalog[i].frequency;
6567         }
6568         if (hordeCatalog[i].minLevel > deepestLevel) {
6569             deepestHorde = i;
6570             deepestLevel = hordeCatalog[i].minLevel;
6571         }
6572     }
6573 
6574     do {
6575         if (possCount == 0) {
6576             chosenHorde = deepestHorde;
6577         } else {
6578             index = rand_range(1, possCount);
6579             for (i=0; i<NUMBER_HORDES; i++) {
6580                 if (hordeCatalog[i].minLevel >= rogue.depthLevel && !hordeCatalog[i].flags) {
6581                     if (index <= hordeCatalog[i].frequency) {
6582                         chosenHorde = i;
6583                         break;
6584                     }
6585                     index -= hordeCatalog[i].frequency;
6586                 }
6587             }
6588         }
6589 
6590         index = rand_range(-1, hordeCatalog[chosenHorde].numberOfMemberTypes - 1);
6591         if (index == -1) {
6592             candidate = hordeCatalog[chosenHorde].leaderType;
6593         } else {
6594             candidate = hordeCatalog[chosenHorde].memberType[index];
6595         }
6596     } while (((monsterCatalog[candidate].flags & MONST_NEVER_VORPAL_ENEMY)
6597               || (monsterCatalog[candidate].abilityFlags & MA_NEVER_VORPAL_ENEMY))
6598              && --failsafe > 0);
6599     return candidate;
6600 }*/
6601 
lotteryDraw(short * frequencies,short itemCount)6602 short lotteryDraw(short *frequencies, short itemCount) {
6603     short i, maxFreq, randIndex;
6604     maxFreq = 0;
6605     for (i = 0; i < itemCount; i++) {
6606         maxFreq += frequencies[i];
6607     }
6608     brogueAssert(maxFreq > 0);
6609     randIndex = rand_range(0, maxFreq - 1);
6610     for (i = 0; i < itemCount; i++) {
6611         if (frequencies[i] > randIndex) {
6612             return i;
6613         } else {
6614             randIndex -= frequencies[i];
6615         }
6616     }
6617     brogueAssert(false);
6618     return 0;
6619 }
6620 
chooseVorpalEnemy()6621 short chooseVorpalEnemy() {
6622     short i, frequencies[MONSTER_CLASS_COUNT];
6623     for (i = 0; i < MONSTER_CLASS_COUNT; i++) {
6624         if (monsterClassCatalog[i].maxDepth <= 0
6625             || rogue.depthLevel <= monsterClassCatalog[i].maxDepth) {
6626 
6627             frequencies[i] = monsterClassCatalog[i].frequency;
6628         } else {
6629             frequencies[i] = 0;
6630         }
6631     }
6632     return lotteryDraw(frequencies, MONSTER_CLASS_COUNT);
6633 }
6634 
describeMonsterClass(char * buf,const short classID,boolean conjunctionAnd)6635 void describeMonsterClass(char *buf, const short classID, boolean conjunctionAnd) {
6636     short i;
6637     char buf2[50];
6638 
6639     buf[0] = '\0';
6640     for (i = 0; monsterClassCatalog[classID].memberList[i] != 0; i++) {
6641         strcpy(buf2, monsterCatalog[monsterClassCatalog[classID].memberList[i]].monsterName);
6642         if (monsterClassCatalog[classID].memberList[i + 1] != 0) {
6643             if (monsterClassCatalog[classID].memberList[i + 2] == 0) {
6644                 strcat(buf2, conjunctionAnd ? " and " : " or ");
6645             } else {
6646                 strcat(buf2, ", ");
6647             }
6648         }
6649         strcat(buf, buf2);
6650     }
6651 }
6652 
updateIdentifiableItem(item * theItem)6653 void updateIdentifiableItem(item *theItem) {
6654     if ((theItem->category & SCROLL) && scrollTable[theItem->kind].identified) {
6655         theItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
6656     } else if ((theItem->category & POTION) && potionTable[theItem->kind].identified) {
6657         theItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
6658     } else if ((theItem->category & (RING | STAFF | WAND))
6659                && (theItem->flags & ITEM_IDENTIFIED)
6660                && tableForItemCategory(theItem->category, NULL)[theItem->kind].identified) {
6661 
6662         theItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
6663     } else if ((theItem->category & (WEAPON | ARMOR))
6664                && (theItem->flags & ITEM_IDENTIFIED)
6665                && (!(theItem->flags & ITEM_RUNIC) || (theItem->flags & ITEM_RUNIC_IDENTIFIED))) {
6666 
6667         theItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
6668     } else if (theItem->category & NEVER_IDENTIFIABLE) {
6669         theItem->flags &= ~ITEM_CAN_BE_IDENTIFIED;
6670     }
6671 }
6672 
updateIdentifiableItems()6673 void updateIdentifiableItems() {
6674     item *theItem;
6675     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
6676         updateIdentifiableItem(theItem);
6677     }
6678     for (theItem = floorItems; theItem != NULL; theItem = theItem->nextItem) {
6679         updateIdentifiableItem(theItem);
6680     }
6681 }
6682 
magicMapCell(short x,short y)6683 void magicMapCell(short x, short y) {
6684     pmap[x][y].flags |= MAGIC_MAPPED;
6685     pmap[x][y].rememberedTerrainFlags = tileCatalog[pmap[x][y].layers[DUNGEON]].flags | tileCatalog[pmap[x][y].layers[LIQUID]].flags;
6686     pmap[x][y].rememberedTMFlags = tileCatalog[pmap[x][y].layers[DUNGEON]].mechFlags | tileCatalog[pmap[x][y].layers[LIQUID]].mechFlags;
6687     if (pmap[x][y].layers[LIQUID] && tileCatalog[pmap[x][y].layers[LIQUID]].drawPriority < tileCatalog[pmap[x][y].layers[DUNGEON]].drawPriority) {
6688         pmap[x][y].rememberedTerrain = pmap[x][y].layers[LIQUID];
6689     } else {
6690         pmap[x][y].rememberedTerrain = pmap[x][y].layers[DUNGEON];
6691     }
6692 }
6693 
readScroll(item * theItem)6694 void readScroll(item *theItem) {
6695     short i, j, x, y, numberOfMonsters = 0;
6696     item *tempItem;
6697     creature *monst;
6698     boolean hadEffect = false;
6699     char buf[COLS * 3], buf2[COLS * 3];
6700 
6701     rogue.featRecord[FEAT_ARCHIVIST] = false;
6702 
6703     switch (theItem->kind) {
6704         case SCROLL_IDENTIFY:
6705             identify(theItem);
6706             updateIdentifiableItems();
6707             messageWithColor("this is a scroll of identify.", &itemMessageColor, REQUIRE_ACKNOWLEDGMENT);
6708             if (numberOfMatchingPackItems(ALL_ITEMS, ITEM_CAN_BE_IDENTIFIED, 0, false) == 0) {
6709                 message("everything in your pack is already identified.", 0);
6710                 break;
6711             }
6712             do {
6713                 theItem = promptForItemOfType((ALL_ITEMS), ITEM_CAN_BE_IDENTIFIED, 0,
6714                                               KEYBOARD_LABELS ? "Identify what? (a-z; shift for more info)" : "Identify what?",
6715                                               false);
6716                 if (rogue.gameHasEnded) {
6717                     return;
6718                 }
6719                 if (theItem && !(theItem->flags & ITEM_CAN_BE_IDENTIFIED)) {
6720                     confirmMessages();
6721                     itemName(theItem, buf2, true, true, NULL);
6722                     sprintf(buf, "you already know %s %s.", (theItem->quantity > 1 ? "they're" : "it's"), buf2);
6723                     messageWithColor(buf, &itemMessageColor, 0);
6724                 }
6725             } while (theItem == NULL || !(theItem->flags & ITEM_CAN_BE_IDENTIFIED));
6726             recordKeystroke(theItem->inventoryLetter, false, false);
6727             confirmMessages();
6728             identify(theItem);
6729             itemName(theItem, buf, true, true, NULL);
6730             sprintf(buf2, "%s %s.", (theItem->quantity == 1 ? "this is" : "these are"), buf);
6731             messageWithColor(buf2, &itemMessageColor, 0);
6732             break;
6733         case SCROLL_TELEPORT:
6734             teleport(&player, -1, -1, true);
6735             break;
6736         case SCROLL_REMOVE_CURSE:
6737             for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
6738                 if (tempItem->flags & ITEM_CURSED) {
6739                     hadEffect = true;
6740                     tempItem->flags &= ~ITEM_CURSED;
6741                 }
6742             }
6743             if (hadEffect) {
6744                 message("your pack glows with a cleansing light, and a malevolent energy disperses.", 0);
6745             } else {
6746                 message("your pack glows with a cleansing light, but nothing happens.", 0);
6747             }
6748             break;
6749         case SCROLL_ENCHANTING:
6750             identify(theItem);
6751             messageWithColor("this is a scroll of enchanting.", &itemMessageColor, REQUIRE_ACKNOWLEDGMENT);
6752             if (!numberOfMatchingPackItems((WEAPON | ARMOR | RING | STAFF | WAND | CHARM), 0, 0, false)) {
6753                 confirmMessages();
6754                 message("you have nothing that can be enchanted.", 0);
6755                 break;
6756             }
6757             do {
6758                 theItem = promptForItemOfType((WEAPON | ARMOR | RING | STAFF | WAND | CHARM), 0, 0,
6759                                               KEYBOARD_LABELS ? "Enchant what? (a-z; shift for more info)" : "Enchant what?",
6760                                               false);
6761                 confirmMessages();
6762                 if (theItem == NULL || !(theItem->category & (WEAPON | ARMOR | RING | STAFF | WAND | CHARM))) {
6763                     message("Can't enchant that.", REQUIRE_ACKNOWLEDGMENT);
6764                 }
6765                 if (rogue.gameHasEnded) {
6766                     return;
6767                 }
6768             } while (theItem == NULL || !(theItem->category & (WEAPON | ARMOR | RING | STAFF | WAND | CHARM)));
6769             recordKeystroke(theItem->inventoryLetter, false, false);
6770             confirmMessages();
6771             switch (theItem->category) {
6772                 case WEAPON:
6773                     theItem->strengthRequired = max(0, theItem->strengthRequired - 1);
6774                     theItem->enchant1++;
6775                     if (theItem->quiverNumber) {
6776                         theItem->quiverNumber = rand_range(1, 60000);
6777                     }
6778                     break;
6779                 case ARMOR:
6780                     theItem->strengthRequired = max(0, theItem->strengthRequired - 1);
6781                     theItem->enchant1++;
6782                     break;
6783                 case RING:
6784                     theItem->enchant1++;
6785                     updateRingBonuses();
6786                     if (theItem->kind == RING_CLAIRVOYANCE) {
6787                         updateClairvoyance();
6788                         displayLevel();
6789                     }
6790                     break;
6791                 case STAFF:
6792                     theItem->enchant1++;
6793                     theItem->charges++;
6794                     theItem->enchant2 = 500 / theItem->enchant1;
6795                     break;
6796                 case WAND:
6797                     theItem->charges += wandTable[theItem->kind].range.lowerBound;
6798                     break;
6799                 case CHARM:
6800                     theItem->enchant1++;
6801                     theItem->charges = min(0, theItem->charges); // Enchanting instantly recharges charms.
6802                                                                  //                    theItem->charges = theItem->charges
6803                                                                  //                    * charmRechargeDelay(theItem->kind, theItem->enchant1)
6804                                                                  //                    / charmRechargeDelay(theItem->kind, theItem->enchant1 - 1);
6805 
6806                     break;
6807                 default:
6808                     break;
6809             }
6810             theItem->timesEnchanted++;
6811             if ((theItem->category & (WEAPON | ARMOR | STAFF | RING | CHARM))
6812                 && theItem->enchant1 >= 16) {
6813 
6814                 rogue.featRecord[FEAT_SPECIALIST] = true;
6815             }
6816             if (theItem->flags & ITEM_EQUIPPED) {
6817                 equipItem(theItem, true, NULL);
6818             }
6819             itemName(theItem, buf, false, false, NULL);
6820             sprintf(buf2, "your %s gleam%s briefly in the darkness.", buf, (theItem->quantity == 1 ? "s" : ""));
6821             messageWithColor(buf2, &itemMessageColor, 0);
6822             if (theItem->flags & ITEM_CURSED) {
6823                 sprintf(buf2, "a malevolent force leaves your %s.", buf);
6824                 messageWithColor(buf2, &itemMessageColor, 0);
6825                 theItem->flags &= ~ITEM_CURSED;
6826             }
6827             createFlare(player.xLoc, player.yLoc, SCROLL_ENCHANTMENT_LIGHT);
6828             break;
6829         case SCROLL_RECHARGING:
6830             rechargeItems(STAFF | CHARM);
6831             break;
6832         case SCROLL_PROTECT_ARMOR:
6833             if (rogue.armor) {
6834                 tempItem = rogue.armor;
6835                 tempItem->flags |= ITEM_PROTECTED;
6836                 itemName(tempItem, buf2, false, false, NULL);
6837                 sprintf(buf, "a protective golden light covers your %s.", buf2);
6838                 messageWithColor(buf, &itemMessageColor, 0);
6839                 if (tempItem->flags & ITEM_CURSED) {
6840                     sprintf(buf, "a malevolent force leaves your %s.", buf2);
6841                     messageWithColor(buf, &itemMessageColor, 0);
6842                     tempItem->flags &= ~ITEM_CURSED;
6843                 }
6844             } else {
6845                 message("a protective golden light surrounds you, but it quickly disperses.", 0);
6846             }
6847             createFlare(player.xLoc, player.yLoc, SCROLL_PROTECTION_LIGHT);
6848             break;
6849         case SCROLL_PROTECT_WEAPON:
6850             if (rogue.weapon) {
6851                 tempItem = rogue.weapon;
6852                 tempItem->flags |= ITEM_PROTECTED;
6853                 itemName(tempItem, buf2, false, false, NULL);
6854                 sprintf(buf, "a protective golden light covers your %s.", buf2);
6855                 messageWithColor(buf, &itemMessageColor, 0);
6856                 if (tempItem->flags & ITEM_CURSED) {
6857                     sprintf(buf, "a malevolent force leaves your %s.", buf2);
6858                     messageWithColor(buf, &itemMessageColor, 0);
6859                     tempItem->flags &= ~ITEM_CURSED;
6860                 }
6861                 if (rogue.weapon->quiverNumber) {
6862                     rogue.weapon->quiverNumber = rand_range(1, 60000);
6863                 }
6864             } else {
6865                 message("a protective golden light covers your empty hands, but it quickly disperses.", 0);
6866             }
6867             createFlare(player.xLoc, player.yLoc, SCROLL_PROTECTION_LIGHT);
6868             break;
6869         case SCROLL_SANCTUARY:
6870             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_SACRED_GLYPHS], true, false);
6871             messageWithColor("sprays of color arc to the ground, forming glyphs where they alight.", &itemMessageColor, 0);
6872             break;
6873         case SCROLL_MAGIC_MAPPING:
6874             confirmMessages();
6875             messageWithColor("this scroll has a map on it!", &itemMessageColor, 0);
6876             for (i=0; i<DCOLS; i++) {
6877                 for (j=0; j<DROWS; j++) {
6878                     if (cellHasTMFlag(i, j, TM_IS_SECRET)) {
6879                         discover(i, j);
6880                         magicMapCell(i, j);
6881                         pmap[i][j].flags &= ~(STABLE_MEMORY | DISCOVERED);
6882                     }
6883                 }
6884             }
6885             for (i=0; i<DCOLS; i++) {
6886                 for (j=0; j<DROWS; j++) {
6887                     if (!(pmap[i][j].flags & DISCOVERED) && pmap[i][j].layers[DUNGEON] != GRANITE) {
6888                         magicMapCell(i, j);
6889                     }
6890                 }
6891             }
6892             colorFlash(&magicMapFlashColor, 0, MAGIC_MAPPED, 15, DCOLS + DROWS, player.xLoc, player.yLoc);
6893             break;
6894         case SCROLL_AGGRAVATE_MONSTER:
6895             aggravateMonsters(DCOLS + DROWS, player.xLoc, player.yLoc, &gray);
6896             message("the scroll emits a piercing shriek that echoes throughout the dungeon!", 0);
6897             break;
6898         case SCROLL_SUMMON_MONSTER:
6899             for (j=0; j<25 && numberOfMonsters < 3; j++) {
6900                 for (i=0; i<8; i++) {
6901                     x = player.xLoc + nbDirs[i][0];
6902                     y = player.yLoc + nbDirs[i][1];
6903                     if (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY) && !(pmap[x][y].flags & HAS_MONSTER)
6904                         && rand_percent(10) && (numberOfMonsters < 3)) {
6905                         monst = spawnHorde(0, x, y, (HORDE_LEADER_CAPTIVE | HORDE_NO_PERIODIC_SPAWN | HORDE_IS_SUMMONED | HORDE_MACHINE_ONLY), 0);
6906                         if (monst) {
6907                             // refreshDungeonCell(x, y);
6908                             // monst->creatureState = MONSTER_TRACKING_SCENT;
6909                             // monst->ticksUntilTurn = player.movementSpeed;
6910                             wakeUp(monst);
6911                             fadeInMonster(monst);
6912                             numberOfMonsters++;
6913                         }
6914                     }
6915                 }
6916             }
6917             if (numberOfMonsters > 1) {
6918                 message("the fabric of space ripples, and monsters appear!", 0);
6919             } else if (numberOfMonsters == 1) {
6920                 message("the fabric of space ripples, and a monster appears!", 0);
6921             } else {
6922                 message("the fabric of space boils violently around you, but nothing happens.", 0);
6923             }
6924             break;
6925         case SCROLL_NEGATION:
6926             negationBlast("the scroll", DCOLS);
6927             break;
6928         case SCROLL_SHATTERING:
6929             messageWithColor("the scroll emits a wave of turquoise light that pierces the nearby walls!", &itemMessageColor, 0);
6930             crystalize(9);
6931             break;
6932         case SCROLL_DISCORD:
6933             discordBlast("the scroll", DCOLS);
6934             break;
6935     }
6936 }
6937 
detectMagicOnItem(item * theItem)6938 void detectMagicOnItem(item *theItem) {
6939     theItem->flags |= ITEM_MAGIC_DETECTED;
6940     if ((theItem->category & (WEAPON | ARMOR))
6941         && theItem->enchant1 == 0
6942         && !(theItem->flags & ITEM_RUNIC)) {
6943 
6944         identify(theItem);
6945     }
6946 }
6947 
drinkPotion(item * theItem)6948 void drinkPotion(item *theItem) {
6949     item *tempItem = NULL;
6950     boolean hadEffect = false;
6951     boolean hadEffect2 = false;
6952     char buf[1000] = "";
6953 
6954     brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
6955 
6956     rogue.featRecord[FEAT_ARCHIVIST] = false;
6957 
6958     switch (theItem->kind) {
6959         case POTION_LIFE:
6960             sprintf(buf, "%syour maximum health increases by %i%%.",
6961                     ((player.currentHP < player.info.maxHP) ? "you heal completely and " : ""),
6962                     (player.info.maxHP + 10) * 100 / player.info.maxHP - 100);
6963 
6964             player.info.maxHP += 10;
6965             heal(&player, 100, true);
6966             updatePlayerRegenerationDelay();
6967             messageWithColor(buf, &advancementMessageColor, 0);
6968             break;
6969         case POTION_HALLUCINATION:
6970             player.status[STATUS_HALLUCINATING] = player.maxStatus[STATUS_HALLUCINATING] = 300;
6971             message("colors are everywhere! The walls are singing!", 0);
6972             break;
6973         case POTION_INCINERATION:
6974             //colorFlash(&darkOrange, 0, IN_FIELD_OF_VIEW, 4, 4, player.xLoc, player.yLoc);
6975             message("as you uncork the flask, it explodes in flame!", 0);
6976             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_INCINERATION_POTION], true, false);
6977             exposeCreatureToFire(&player);
6978             break;
6979         case POTION_DARKNESS:
6980             player.status[STATUS_DARKNESS] = max(400, player.status[STATUS_DARKNESS]);
6981             player.maxStatus[STATUS_DARKNESS] = max(400, player.maxStatus[STATUS_DARKNESS]);
6982             updateMinersLightRadius();
6983             updateVision(true);
6984             message("your vision flickers as a cloak of darkness settles around you!", 0);
6985             break;
6986         case POTION_DESCENT:
6987             colorFlash(&darkBlue, 0, IN_FIELD_OF_VIEW, 3, 3, player.xLoc, player.yLoc);
6988             message("vapor pours out of the flask and causes the floor to disappear!", 0);
6989             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_HOLE_POTION], true, false);
6990             if (!player.status[STATUS_LEVITATING]) {
6991                 player.bookkeepingFlags |= MB_IS_FALLING;
6992             }
6993             break;
6994         case POTION_STRENGTH:
6995             rogue.strength++;
6996             if (player.status[STATUS_WEAKENED]) {
6997                 player.status[STATUS_WEAKENED] = 1;
6998             }
6999             updateEncumbrance();
7000             messageWithColor("newfound strength surges through your body.", &advancementMessageColor, 0);
7001             createFlare(player.xLoc, player.yLoc, POTION_STRENGTH_LIGHT);
7002             break;
7003         case POTION_POISON:
7004             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_POISON_GAS_CLOUD_POTION], true, false);
7005             message("caustic gas billows out of the open flask!", 0);
7006             break;
7007         case POTION_PARALYSIS:
7008             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_PARALYSIS_GAS_CLOUD_POTION], true, false);
7009             message("your muscles stiffen as a cloud of pink gas bursts from the open flask!", 0);
7010             break;
7011         case POTION_TELEPATHY:
7012             makePlayerTelepathic(300);
7013             break;
7014         case POTION_LEVITATION:
7015             player.status[STATUS_LEVITATING] = player.maxStatus[STATUS_LEVITATING] = 100;
7016             player.bookkeepingFlags &= ~MB_SEIZED; // break free of holding monsters
7017             message("you float into the air!", 0);
7018             break;
7019         case POTION_CONFUSION:
7020             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_CONFUSION_GAS_CLOUD_POTION], true, false);
7021             message("a shimmering cloud of rainbow-colored gas billows out of the open flask!", 0);
7022             break;
7023         case POTION_LICHEN:
7024             message("a handful of tiny spores burst out of the open flask!", 0);
7025             spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_LICHEN_PLANTED], true, false);
7026             break;
7027         case POTION_DETECT_MAGIC:
7028             hadEffect = false;
7029             hadEffect2 = false;
7030             for (tempItem = floorItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
7031                 if (tempItem->category & CAN_BE_DETECTED) {
7032                     detectMagicOnItem(tempItem);
7033                     if (itemMagicPolarity(tempItem)) {
7034                         pmap[tempItem->xLoc][tempItem->yLoc].flags |= ITEM_DETECTED;
7035                         hadEffect = true;
7036                         refreshDungeonCell(tempItem->xLoc, tempItem->yLoc);
7037                     }
7038                 }
7039             }
7040             for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
7041                 creature *monst = nextCreature(&it);
7042                 if (monst->carriedItem && (monst->carriedItem->category & CAN_BE_DETECTED)) {
7043                     detectMagicOnItem(monst->carriedItem);
7044                     if (itemMagicPolarity(monst->carriedItem)) {
7045                         hadEffect = true;
7046                         refreshDungeonCell(monst->xLoc, monst->yLoc);
7047                     }
7048                 }
7049             }
7050             for (tempItem = packItems->nextItem; tempItem != NULL; tempItem = tempItem->nextItem) {
7051                 if (tempItem->category & CAN_BE_DETECTED) {
7052                     detectMagicOnItem(tempItem);
7053                     if (itemMagicPolarity(tempItem)) {
7054                         if (tempItem->flags & ITEM_MAGIC_DETECTED) {
7055                             hadEffect2 = true;
7056                         }
7057                     }
7058                 }
7059             }
7060             if (hadEffect || hadEffect2) {
7061                 if (hadEffect && hadEffect2) {
7062                     message("you can somehow feel the presence of magic on the level and in your pack.", 0);
7063                 } else if (hadEffect) {
7064                     message("you can somehow feel the presence of magic on the level.", 0);
7065                 } else {
7066                     message("you can somehow feel the presence of magic in your pack.", 0);
7067                 }
7068             } else {
7069                 message("you can somehow feel the absence of magic on the level and in your pack.", 0);
7070             }
7071             break;
7072         case POTION_HASTE_SELF:
7073             haste(&player, 25);
7074             break;
7075         case POTION_FIRE_IMMUNITY:
7076             player.status[STATUS_IMMUNE_TO_FIRE] = player.maxStatus[STATUS_IMMUNE_TO_FIRE] = 150;
7077             if (player.status[STATUS_BURNING]) {
7078                 extinguishFireOnCreature(&player);
7079             }
7080             message("a comforting breeze envelops you, and you no longer fear fire.", 0);
7081             break;
7082         case POTION_INVISIBILITY:
7083             player.status[STATUS_INVISIBLE] = player.maxStatus[STATUS_INVISIBLE] = 75;
7084             message("you shiver as a chill runs up your spine.", 0);
7085             break;
7086         default:
7087             message("you feel very strange, as though your body doesn't know how to react!", REQUIRE_ACKNOWLEDGMENT);
7088     }
7089 }
7090 
7091 // Used for the Discoveries screen. Returns a number: 1 == good, -1 == bad, 0 == could go either way.
magicCharDiscoverySuffix(short category,short kind)7092 short magicCharDiscoverySuffix(short category, short kind) {
7093     short result = 0;
7094 
7095     switch (category) {
7096         case SCROLL:
7097             switch (kind) {
7098                 case SCROLL_AGGRAVATE_MONSTER:
7099                 case SCROLL_SUMMON_MONSTER:
7100                     result = -1;
7101                     break;
7102                 default:
7103                     result = 1;
7104                     break;
7105             }
7106             break;
7107         case POTION:
7108             switch (kind) {
7109                 case POTION_HALLUCINATION:
7110                 case POTION_INCINERATION:
7111                 case POTION_DESCENT:
7112                 case POTION_POISON:
7113                 case POTION_PARALYSIS:
7114                 case POTION_CONFUSION:
7115                 case POTION_LICHEN:
7116                 case POTION_DARKNESS:
7117                     result = -1;
7118                     break;
7119                 default:
7120                     result = 1;
7121                     break;
7122             }
7123             break;
7124         case WAND:
7125         case STAFF:
7126             if (boltCatalog[tableForItemCategory(category, NULL)[kind].strengthRequired].flags & (BF_TARGET_ALLIES)) {
7127                 result = -1;
7128             } else {
7129                 result = 1;
7130             }
7131             break;
7132         case RING:
7133             result = 0;
7134             break;
7135         case CHARM:
7136             result = 1;
7137             break;
7138     }
7139     return result;
7140 }
7141 
7142 /* Returns
7143 -1 if the item is of bad magic
7144  0 if it is neutral
7145  1 if it is of good magic */
itemMagicPolarity(item * theItem)7146 int itemMagicPolarity(item *theItem) {
7147     switch (theItem->category) {
7148         case WEAPON:
7149         case ARMOR:
7150             if ((theItem->flags & ITEM_CURSED) || theItem->enchant1 < 0) {
7151                 return -1;
7152             } else if (theItem->enchant1 > 0) {
7153                 return 1;
7154             }
7155             return 0;
7156             break;
7157         case SCROLL:
7158             switch (theItem->kind) {
7159                 case SCROLL_AGGRAVATE_MONSTER:
7160                 case SCROLL_SUMMON_MONSTER:
7161                     return -1;
7162                 default:
7163                     return 1;
7164             }
7165         case POTION:
7166             switch (theItem->kind) {
7167                 case POTION_HALLUCINATION:
7168                 case POTION_INCINERATION:
7169                 case POTION_DESCENT:
7170                 case POTION_POISON:
7171                 case POTION_PARALYSIS:
7172                 case POTION_CONFUSION:
7173                 case POTION_LICHEN:
7174                 case POTION_DARKNESS:
7175                     return -1;
7176                 default:
7177                     return 1;
7178             }
7179         case WAND:
7180             if (theItem->charges == 0) {
7181                 return 0;
7182             }
7183         case STAFF:
7184             if (boltCatalog[tableForItemCategory(theItem->category, NULL)[theItem->kind].strengthRequired].flags & (BF_TARGET_ALLIES)) {
7185                 return -1;
7186             } else {
7187                 return 1;
7188             }
7189         case RING:
7190             if (theItem->flags & ITEM_CURSED || theItem->enchant1 < 0) {
7191                 return -1;
7192             } else if (theItem->enchant1 > 0) {
7193                 return 1;
7194             } else {
7195                 return 0;
7196             }
7197         case CHARM:
7198             return 1;
7199         case AMULET:
7200             return 1;
7201     }
7202     return 0;
7203 }
7204 
unequip(item * theItem)7205 void unequip(item *theItem) {
7206     char buf[COLS * 3], buf2[COLS * 3];
7207     unsigned char command[3];
7208 
7209     command[0] = UNEQUIP_KEY;
7210     if (theItem == NULL) {
7211         theItem = promptForItemOfType(ALL_ITEMS, ITEM_EQUIPPED, 0,
7212                                       KEYBOARD_LABELS ? "Remove (unequip) what? (a-z or <esc> to cancel)" : "Remove (unequip) what?",
7213                                       true);
7214     }
7215     if (theItem == NULL) {
7216         return;
7217     }
7218 
7219     command[1] = theItem->inventoryLetter;
7220     command[2] = '\0';
7221 
7222     if (!(theItem->flags & ITEM_EQUIPPED)) {
7223         itemName(theItem, buf2, false, false, NULL);
7224         sprintf(buf, "your %s %s not equipped.",
7225                 buf2,
7226                 theItem->quantity == 1 ? "was" : "were");
7227         confirmMessages();
7228         messageWithColor(buf, &itemMessageColor, 0);
7229         return;
7230     } else {
7231         if (!unequipItem(theItem, false)) {
7232             return; // cursed
7233         }
7234         recordKeystrokeSequence(command);
7235         itemName(theItem, buf2, true, true, NULL);
7236         if (strLenWithoutEscapes(buf2) > 52) {
7237             itemName(theItem, buf2, false, true, NULL);
7238         }
7239         confirmMessages();
7240         sprintf(buf, "you are no longer %s %s.", (theItem->category & WEAPON ? "wielding" : "wearing"), buf2);
7241         messageWithColor(buf, &itemMessageColor, 0);
7242     }
7243     playerTurnEnded();
7244 }
7245 
canDrop()7246 boolean canDrop() {
7247     if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_ITEMS)) {
7248         return false;
7249     }
7250     return true;
7251 }
7252 
drop(item * theItem)7253 void drop(item *theItem) {
7254     char buf[COLS * 3], buf2[COLS * 3];
7255     unsigned char command[3];
7256 
7257     command[0] = DROP_KEY;
7258     if (theItem == NULL) {
7259         theItem = promptForItemOfType(ALL_ITEMS, 0, 0,
7260                                       KEYBOARD_LABELS ? "Drop what? (a-z, shift for more info; or <esc> to cancel)" : "Drop what?",
7261                                       true);
7262     }
7263     if (theItem == NULL) {
7264         return;
7265     }
7266     command[1] = theItem->inventoryLetter;
7267     command[2] = '\0';
7268 
7269     if ((theItem->flags & ITEM_EQUIPPED) && (theItem->flags & ITEM_CURSED)) {
7270         itemName(theItem, buf2, false, false, NULL);
7271         sprintf(buf, "you can't; your %s appears to be cursed.", buf2);
7272         confirmMessages();
7273         messageWithColor(buf, &itemMessageColor, 0);
7274     } else if (canDrop()) {
7275         recordKeystrokeSequence(command);
7276         if (theItem->flags & ITEM_EQUIPPED) {
7277             unequipItem(theItem, false);
7278         }
7279         theItem = dropItem(theItem); // This is where it gets dropped.
7280         theItem->flags |= ITEM_PLAYER_AVOIDS; // Try not to pick up stuff you've already dropped.
7281         itemName(theItem, buf2, true, true, NULL);
7282         sprintf(buf, "You dropped %s.", buf2);
7283         messageWithColor(buf, &itemMessageColor, 0);
7284         playerTurnEnded();
7285     } else {
7286         confirmMessages();
7287         message("There is already something there.", 0);
7288     }
7289 }
7290 
promptForItemOfType(unsigned short category,unsigned long requiredFlags,unsigned long forbiddenFlags,char * prompt,boolean allowInventoryActions)7291 item *promptForItemOfType(unsigned short category,
7292                           unsigned long requiredFlags,
7293                           unsigned long forbiddenFlags,
7294                           char *prompt,
7295                           boolean allowInventoryActions) {
7296     char keystroke;
7297     item *theItem;
7298 
7299     if (!numberOfMatchingPackItems(ALL_ITEMS, requiredFlags, forbiddenFlags, true)) {
7300         return NULL;
7301     }
7302 
7303     temporaryMessage(prompt, 0);
7304 
7305     keystroke = displayInventory(category, requiredFlags, forbiddenFlags, false, allowInventoryActions);
7306 
7307     if (!keystroke) {
7308         // This can happen if the player does an action with an item directly from the inventory screen via a button.
7309         return NULL;
7310     }
7311 
7312     if (keystroke < 'a' || keystroke > 'z') {
7313         confirmMessages();
7314         if (keystroke != ESCAPE_KEY && keystroke != ACKNOWLEDGE_KEY) {
7315             message("Invalid entry.", 0);
7316         }
7317         return NULL;
7318     }
7319 
7320     theItem = itemOfPackLetter(keystroke);
7321     if (theItem == NULL) {
7322         confirmMessages();
7323         message("No such item.", 0);
7324         return NULL;
7325     }
7326 
7327     return theItem;
7328 }
7329 
itemOfPackLetter(char letter)7330 item *itemOfPackLetter(char letter) {
7331     item *theItem;
7332     for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
7333         if (theItem->inventoryLetter == letter) {
7334             return theItem;
7335         }
7336     }
7337     return NULL;
7338 }
7339 
itemAtLoc(short x,short y)7340 item *itemAtLoc(short x, short y) {
7341     item *theItem;
7342 
7343     if (!(pmap[x][y].flags & HAS_ITEM)) {
7344         return NULL; // easy optimization
7345     }
7346     for (theItem = floorItems->nextItem; theItem != NULL && (theItem->xLoc != x || theItem->yLoc != y); theItem = theItem->nextItem);
7347     if (theItem == NULL) {
7348         pmap[x][y].flags &= ~HAS_ITEM;
7349         hiliteCell(x, y, &white, 75, true);
7350         rogue.automationActive = false;
7351         message("ERROR: An item was supposed to be here, but I couldn't find it.", REQUIRE_ACKNOWLEDGMENT);
7352         refreshDungeonCell(x, y);
7353     }
7354     return theItem;
7355 }
7356 
dropItem(item * theItem)7357 item *dropItem(item *theItem) {
7358     item *itemFromTopOfStack, *itemOnFloor;
7359 
7360     if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_ITEMS)) {
7361         return NULL;
7362     }
7363 
7364     itemOnFloor = itemAtLoc(player.xLoc, player.yLoc);
7365 
7366     if (theItem->quantity > 1 && !(theItem->category & (WEAPON | GEM))) { // peel off the top item and drop it
7367         itemFromTopOfStack = generateItem(ALL_ITEMS, -1);
7368         *itemFromTopOfStack = *theItem; // clone the item
7369         theItem->quantity--;
7370         itemFromTopOfStack->quantity = 1;
7371         if (itemOnFloor) {
7372             itemOnFloor->inventoryLetter = theItem->inventoryLetter; // just in case all letters are taken
7373             pickUpItemAt(player.xLoc, player.yLoc);
7374         }
7375         placeItem(itemFromTopOfStack, player.xLoc, player.yLoc);
7376         return itemFromTopOfStack;
7377     } else { // drop the entire item
7378         if (rogue.swappedIn == theItem || rogue.swappedOut == theItem) {
7379             rogue.swappedIn = NULL;
7380             rogue.swappedOut = NULL;
7381         }
7382         removeItemFromChain(theItem, packItems);
7383         if (itemOnFloor) {
7384             itemOnFloor->inventoryLetter = theItem->inventoryLetter;
7385             pickUpItemAt(player.xLoc, player.yLoc);
7386         }
7387         placeItem(theItem, player.xLoc, player.yLoc);
7388         return theItem;
7389     }
7390 }
7391 
recalculateEquipmentBonuses()7392 void recalculateEquipmentBonuses() {
7393     fixpt enchant;
7394     item *theItem;
7395     if (rogue.weapon) {
7396         theItem = rogue.weapon;
7397         enchant = netEnchant(theItem);
7398         player.info.damage = theItem->damage;
7399         player.info.damage.lowerBound = player.info.damage.lowerBound * damageFraction(enchant) / FP_FACTOR;
7400         player.info.damage.upperBound = player.info.damage.upperBound * damageFraction(enchant) / FP_FACTOR;
7401         if (player.info.damage.lowerBound < 1) {
7402             player.info.damage.lowerBound = 1;
7403         }
7404         if (player.info.damage.upperBound < 1) {
7405             player.info.damage.upperBound = 1;
7406         }
7407     }
7408 
7409     if (rogue.armor) {
7410         theItem = rogue.armor;
7411         enchant = netEnchant(theItem);
7412         enchant -= player.status[STATUS_DONNING] * FP_FACTOR;
7413         player.info.defense = (theItem->armor * FP_FACTOR + enchant * 10) / FP_FACTOR;
7414         if (player.info.defense < 0) {
7415             player.info.defense = 0;
7416         }
7417     }
7418 }
7419 
7420 // Returns true on success, false otherwise (for example, if failing to remove
7421 // a cursed item) If something must be first unequipped and it is not clear
7422 // what, unequipHint will be used if passed.
equipItem(item * theItem,boolean force,item * unequipHint)7423 boolean equipItem(item *theItem, boolean force, item *unequipHint) {
7424     char buf1[COLS * 3], buf2[COLS * 3], buf3[COLS * 3];
7425     item *previouslyEquippedItem = NULL;
7426 
7427     if ((theItem->category & RING) && (theItem->flags & ITEM_EQUIPPED)) {
7428         return false;
7429     }
7430 
7431     if (theItem->category & WEAPON) {
7432         previouslyEquippedItem = rogue.weapon;
7433     } else if (theItem->category & ARMOR) {
7434         previouslyEquippedItem = rogue.armor;
7435     } else if (theItem->category & RING
7436               && unequipHint && rogue.ringLeft && rogue.ringRight
7437               && (unequipHint == rogue.ringLeft || unequipHint == rogue.ringRight)) {
7438         previouslyEquippedItem = unequipHint;
7439     }
7440 
7441     if (previouslyEquippedItem && !unequipItem(previouslyEquippedItem, force)) {
7442         return false; // already using a cursed item
7443     }
7444     if (theItem->category & WEAPON) {
7445         rogue.weapon = theItem;
7446         strengthCheck(theItem, !force);
7447     } else if (theItem->category & ARMOR) {
7448         if (!force) {
7449             player.status[STATUS_DONNING] = player.maxStatus[STATUS_DONNING] = theItem->armor / 10;
7450         }
7451         rogue.armor = theItem;
7452         strengthCheck(theItem, !force);
7453     } else if (theItem->category & RING) {
7454         if (rogue.ringLeft && rogue.ringRight) {
7455             return false; // no available ring slot and no hint, see equip()
7456         }
7457         if (rogue.ringLeft) {
7458             rogue.ringRight = theItem;
7459         } else {
7460             rogue.ringLeft = theItem;
7461         }
7462         updateRingBonuses();
7463         if (theItem->kind == RING_CLAIRVOYANCE) {
7464             updateClairvoyance();
7465             displayLevel();
7466             identifyItemKind(theItem);
7467         } else if (theItem->kind == RING_LIGHT
7468                    || theItem->kind == RING_STEALTH) {
7469             identifyItemKind(theItem);
7470         }
7471         updateEncumbrance();
7472     }
7473     theItem->flags |= ITEM_EQUIPPED;
7474 
7475     itemName(theItem, buf2, true, true, NULL);
7476 
7477     if (!force) {
7478         if (previouslyEquippedItem) {
7479             itemName(previouslyEquippedItem, buf3, false, false, NULL);
7480             sprintf(buf1, "Now %s %s instead of your %s.", (theItem->category & WEAPON ? "wielding" : "wearing"), buf2, buf3);
7481         } else {
7482             sprintf(buf1, "Now %s %s.", (theItem->category & WEAPON ? "wielding" : "wearing"), buf2);
7483         }
7484 
7485         confirmMessages();
7486         messageWithColor(buf1, &itemMessageColor, false);
7487 
7488         if (theItem->flags & ITEM_CURSED) {
7489             itemName(theItem, buf2, false, false, NULL);
7490             switch(theItem->category) {
7491                 case WEAPON:
7492                     sprintf(buf1, "you wince as your grip involuntarily tightens around your %s.", buf2);
7493                     break;
7494                 case ARMOR:
7495                     sprintf(buf1, "your %s constricts around you painfully.", buf2);
7496                     break;
7497                 case RING:
7498                     sprintf(buf1, "your %s tightens around your finger painfully.", buf2);
7499                     break;
7500                 default:
7501                     sprintf(buf1, "your %s seizes you with a malevolent force.", buf2);
7502                     break;
7503             }
7504             messageWithColor(buf1, &itemMessageColor, 0);
7505         }
7506     }
7507 
7508     return true;
7509 }
7510 
7511 // Returns true on success, false otherwise (for example, if cursed and not forced)
unequipItem(item * theItem,boolean force)7512 boolean unequipItem(item *theItem, boolean force) {
7513     char buf[COLS * 3], buf2[COLS * 3];
7514 
7515     if (theItem == NULL || !(theItem->flags & ITEM_EQUIPPED)) {
7516         return false;
7517     }
7518     if ((theItem->flags & ITEM_CURSED) && !force) {
7519         itemName(theItem, buf2, false, false, NULL);
7520         sprintf(buf, "you can't; your %s appear%s to be cursed.",
7521                 buf2,
7522                 theItem->quantity == 1 ? "s" : "");
7523         confirmMessages();
7524         messageWithColor(buf, &itemMessageColor, 0);
7525         return false;
7526     }
7527     theItem->flags &= ~ITEM_EQUIPPED;
7528     if (theItem->category & WEAPON) {
7529         player.info.damage.lowerBound = 1;
7530         player.info.damage.upperBound = 2;
7531         player.info.damage.clumpFactor = 1;
7532         rogue.weapon = NULL;
7533     }
7534     if (theItem->category & ARMOR) {
7535         player.info.defense = 0;
7536         rogue.armor = NULL;
7537         player.status[STATUS_DONNING] = 0;
7538     }
7539     if (theItem->category & RING) {
7540         if (rogue.ringLeft == theItem) {
7541             rogue.ringLeft = NULL;
7542         } else if (rogue.ringRight == theItem) {
7543             rogue.ringRight = NULL;
7544         }
7545         updateRingBonuses();
7546         if (theItem->kind == RING_CLAIRVOYANCE) {
7547             updateClairvoyance();
7548             updateFieldOfViewDisplay(false, false);
7549             updateClairvoyance(); // Yes, we have to call this a second time.
7550             displayLevel();
7551         }
7552     }
7553     updateEncumbrance();
7554     return true;
7555 }
7556 
updateRingBonuses()7557 void updateRingBonuses() {
7558     short i;
7559     item *rings[2] = {rogue.ringLeft, rogue.ringRight};
7560 
7561     rogue.clairvoyance = rogue.stealthBonus = rogue.transference
7562     = rogue.awarenessBonus = rogue.regenerationBonus = rogue.wisdomBonus = rogue.reaping = 0;
7563     rogue.lightMultiplier = 1;
7564 
7565     for (i=0; i<= 1; i++) {
7566         if (rings[i]) {
7567             switch (rings[i]->kind) {
7568                 case RING_CLAIRVOYANCE:
7569                     rogue.clairvoyance += effectiveRingEnchant(rings[i]);
7570                     break;
7571                 case RING_STEALTH:
7572                     rogue.stealthBonus += effectiveRingEnchant(rings[i]);
7573                     break;
7574                 case RING_REGENERATION:
7575                     rogue.regenerationBonus += effectiveRingEnchant(rings[i]);
7576                     break;
7577                 case RING_TRANSFERENCE:
7578                     rogue.transference += effectiveRingEnchant(rings[i]);
7579                     break;
7580                 case RING_LIGHT:
7581                     rogue.lightMultiplier += effectiveRingEnchant(rings[i]);
7582                     break;
7583                 case RING_AWARENESS:
7584                     rogue.awarenessBonus += 20 * effectiveRingEnchant(rings[i]);
7585                     break;
7586                 case RING_WISDOM:
7587                     rogue.wisdomBonus += effectiveRingEnchant(rings[i]);
7588                     break;
7589                 case RING_REAPING:
7590                     rogue.reaping += effectiveRingEnchant(rings[i]);
7591                     break;
7592             }
7593         }
7594     }
7595 
7596     if (rogue.lightMultiplier <= 0) {
7597         rogue.lightMultiplier--; // because it starts at positive 1 instead of 0
7598     }
7599 
7600     updateMinersLightRadius();
7601     updatePlayerRegenerationDelay();
7602 
7603     if (rogue.stealthBonus < 0) {
7604         rogue.stealthBonus *= 4;
7605     }
7606 }
7607 
updatePlayerRegenerationDelay()7608 void updatePlayerRegenerationDelay() {
7609     short maxHP;
7610     long turnsForFull; // In thousandths of a turn.
7611     maxHP = player.info.maxHP;
7612     turnsForFull = turnsForFullRegenInThousandths(rogue.regenerationBonus * FP_FACTOR);
7613 
7614     player.regenPerTurn = 0;
7615     while (maxHP > turnsForFull / 1000) {
7616         player.regenPerTurn++;
7617         maxHP -= turnsForFull / 1000;
7618     }
7619 
7620     player.info.turnsBetweenRegen = (turnsForFull / maxHP);
7621     // DEBUG printf("\nTurnsForFull: %i; regenPerTurn: %i; (thousandths of) turnsBetweenRegen: %i", turnsForFull, player.regenPerTurn, player.info.turnsBetweenRegen);
7622 }
7623 
removeItemFromChain(item * theItem,item * theChain)7624 boolean removeItemFromChain(item *theItem, item *theChain) {
7625     item *previousItem;
7626 
7627     for (previousItem = theChain;
7628          previousItem->nextItem;
7629          previousItem = previousItem->nextItem) {
7630         if (previousItem->nextItem == theItem) {
7631             previousItem->nextItem = theItem->nextItem;
7632             return true;
7633         }
7634     }
7635     return false;
7636 }
7637 
addItemToChain(item * theItem,item * theChain)7638 void addItemToChain(item *theItem, item *theChain) {
7639     theItem->nextItem = theChain->nextItem;
7640     theChain->nextItem = theItem;
7641 }
7642 
deleteItem(item * theItem)7643 void deleteItem(item *theItem) {
7644     free(theItem);
7645 }
7646 
resetItemTableEntry(itemTable * theEntry)7647 void resetItemTableEntry(itemTable *theEntry) {
7648     theEntry->identified = false;
7649     theEntry->called = false;
7650     theEntry->callTitle[0] = '\0';
7651 }
7652 
shuffleFlavors()7653 void shuffleFlavors() {
7654     short i, j, randIndex, randNumber;
7655     char buf[COLS];
7656 
7657     for (i=0; i<NUMBER_POTION_KINDS; i++) {
7658         resetItemTableEntry(potionTable + i);
7659     }
7660     for (i=0; i<NUMBER_STAFF_KINDS; i++) {
7661         resetItemTableEntry(staffTable+ i);
7662     }
7663     for (i=0; i<NUMBER_WAND_KINDS; i++) {
7664         resetItemTableEntry(wandTable + i);
7665     }
7666     for (i=0; i<NUMBER_SCROLL_KINDS; i++) {
7667         resetItemTableEntry(scrollTable + i);
7668     }
7669     for (i=0; i<NUMBER_RING_KINDS; i++) {
7670         resetItemTableEntry(ringTable + i);
7671     }
7672 
7673     for (i=0; i<NUMBER_ITEM_COLORS; i++) {
7674         strcpy(itemColors[i], itemColorsRef[i]);
7675     }
7676     for (i=0; i<NUMBER_ITEM_COLORS; i++) {
7677         randIndex = rand_range(0, NUMBER_ITEM_COLORS - 1);
7678         if (randIndex != i) {
7679             strcpy(buf, itemColors[i]);
7680             strcpy(itemColors[i], itemColors[randIndex]);
7681             strcpy(itemColors[randIndex], buf);
7682         }
7683     }
7684 
7685     for (i=0; i<NUMBER_ITEM_WOODS; i++) {
7686         strcpy(itemWoods[i], itemWoodsRef[i]);
7687     }
7688     for (i=0; i<NUMBER_ITEM_WOODS; i++) {
7689         randIndex = rand_range(0, NUMBER_ITEM_WOODS - 1);
7690         if (randIndex != i) {
7691             strcpy(buf, itemWoods[i]);
7692             strcpy(itemWoods[i], itemWoods[randIndex]);
7693             strcpy(itemWoods[randIndex], buf);
7694         }
7695     }
7696 
7697     for (i=0; i<NUMBER_ITEM_GEMS; i++) {
7698         strcpy(itemGems[i], itemGemsRef[i]);
7699     }
7700     for (i=0; i<NUMBER_ITEM_GEMS; i++) {
7701         randIndex = rand_range(0, NUMBER_ITEM_GEMS - 1);
7702         if (randIndex != i) {
7703             strcpy(buf, itemGems[i]);
7704             strcpy(itemGems[i], itemGems[randIndex]);
7705             strcpy(itemGems[randIndex], buf);
7706         }
7707     }
7708 
7709     for (i=0; i<NUMBER_ITEM_METALS; i++) {
7710         strcpy(itemMetals[i], itemMetalsRef[i]);
7711     }
7712     for (i=0; i<NUMBER_ITEM_METALS; i++) {
7713         randIndex = rand_range(0, NUMBER_ITEM_METALS - 1);
7714         if (randIndex != i) {
7715             strcpy(buf, itemMetals[i]);
7716             strcpy(itemMetals[i], itemMetals[randIndex]);
7717             strcpy(itemMetals[randIndex], buf);
7718         }
7719     }
7720 
7721     for (i=0; i<NUMBER_SCROLL_KINDS; i++) {
7722         itemTitles[i][0] = '\0';
7723         randNumber = rand_range(3, 4);
7724         for (j=0; j<randNumber; j++) {
7725             randIndex = rand_range(0, NUMBER_TITLE_PHONEMES - 1);
7726             strcpy(buf, itemTitles[i]);
7727             sprintf(itemTitles[i], "%s%s%s", buf, ((rand_percent(50) && j>0) ? " " : ""), titlePhonemes[randIndex]);
7728         }
7729     }
7730 }
7731 
itemValue(item * theItem)7732 unsigned long itemValue(item *theItem) {
7733     switch (theItem->category) {
7734         case AMULET:
7735             return 35000;
7736             break;
7737         case GEM:
7738             return 5000 * theItem->quantity;
7739             break;
7740         default:
7741             return 0;
7742             break;
7743     }
7744 }
7745