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

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

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

CVSweb