/* * File: items.cc * Summary: Misc (mostly) inventory related functions. * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <9> 7/08/01 MV Added messages for chunks/corpses rotting * <8> 8/07/99 BWR Added Rune stacking * <7> 6/13/99 BWR Added auto staff detection * <6> 6/12/99 BWR Fixed time system. * <5> 6/9/99 DML Autopickup * <4> 5/26/99 JDJ Drop will attempt to take off armour. * <3> 5/21/99 BWR Upped armour skill learning slightly. * <2> 5/20/99 BWR Added assurance that against inventory count being wrong. * <1> -/--/-- LRH Created */ #include "AppHdr.h" #include "items.h" #include "clua.h" #include #include #include #include #ifdef DOS #include #endif #include "externs.h" #include "beam.h" #include "cloud.h" #include "debug.h" #include "delay.h" #include "effects.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemname.h" #include "itemprop.h" #include "misc.h" #include "monplace.h" #include "monstuff.h" #include "mstuff2.h" #include "mon-util.h" #include "mutation.h" #include "player.h" #include "randart.h" #include "religion.h" #include "shopping.h" #include "skills.h" #include "spl-cast.h" #include "stuff.h" #include "stash.h" static void autopickup(void); static bool is_stackable_item( const item_def &item ); static bool invisible_to_player( const item_def& item ); static void autoinscribe_item( item_def& item ); static void autoinscribe_items( void ); // Used to be called "unlink_items", but all it really does is make // sure item coordinates are correct to the stack they're in. -- bwr void fix_item_coordinates(void) { int x,y,i; // nails all items to the ground (i.e. sets x,y) for (x = 0; x < GXM; x++) { for (y = 0; y < GYM; y++) { i = igrd[x][y]; while (i != NON_ITEM) { mitm[i].x = x; mitm[i].y = y; i = mitm[i].link; } } } } // This function uses the items coordinates to relink all the igrd lists. void link_items(void) { int i,j; // first, initailize igrd array for (i = 0; i < GXM; i++) { for (j = 0; j < GYM; j++) igrd[i][j] = NON_ITEM; } // link all items on the grid, plus shop inventory, // DON'T link the huge pile of monster items at (0,0) for (i = 0; i < MAX_ITEMS; i++) { if (!is_valid_item(mitm[i]) || (mitm[i].x == 0 && mitm[i].y == 0)) { // item is not assigned, or is monster item. ignore. mitm[i].link = NON_ITEM; continue; } // link to top mitm[i].link = igrd[ mitm[i].x ][ mitm[i].y ]; igrd[ mitm[i].x ][ mitm[i].y ] = i; } } // end link_items() static bool item_ok_to_clean(int item) { // 5. never clean food or Orbs if (mitm[item].base_type == OBJ_FOOD || mitm[item].base_type == OBJ_ORBS) return false; // never clean runes if (mitm[item].base_type == OBJ_MISCELLANY && mitm[item].sub_type == MISC_RUNE_OF_ZOT) { return false; } return true; } // returns index number of first available space, or NON_ITEM for // unsuccessful cleanup (should be exceedingly rare!) int cull_items(void) { // XXX: Not the prettiest of messages, but the player // deserves to know whenever this kicks in. -- bwr mpr( "Too many items on level, removing some.", MSGCH_WARN ); /* rules: 1. Don't cleanup anything nearby the player 2. Don't cleanup shops 3. Don't cleanup monster inventory 4. Clean 15% of items 5. never remove food, orbs, runes 7. uniques weapons are moved to the abyss 8. randarts are simply lost 9. unrandarts are 'destroyed', but may be generated again */ int x,y, item, next; int first_cleaned = NON_ITEM; // 2. avoid shops by avoiding (0,5..9) // 3. avoid monster inventory by iterating over the dungeon grid for (x = 5; x < GXM; x++) { for (y = 5; y < GYM; y++) { // 1. not near player! if (x > you.x_pos - 9 && x < you.x_pos + 9 && y > you.y_pos - 9 && y < you.y_pos + 9) { continue; } // iterate through the grids list of items: for (item = igrd[x][y]; item != NON_ITEM; item = next) { next = mitm[item].link; // in case we can't get it later. if (item_ok_to_clean(item) && random2(100) < 15) { if (is_fixed_artefact( mitm[item] )) { // 7. move uniques to abyss set_unique_item_status( OBJ_WEAPONS, mitm[item].special, UNIQ_LOST_IN_ABYSS ); } else if (is_unrandom_artefact( mitm[item] )) { // 9. unmark unrandart int z = find_unrandart_index(item); if (z >= 0) set_unrandart_exist(z, 0); } // POOF! destroy_item( item ); if (first_cleaned == NON_ITEM) first_cleaned = item; } } // end for item } // end y } // end x return (first_cleaned); } // Note: This function is to isolate all the checks to see if // an item is valid (often just checking the quantity). // // It shouldn't be used a a substitute for those cases // which actually want to check the quantity (as the // rules for unused objects might change). bool is_valid_item( const item_def &item ) { return (item.base_type != OBJ_UNASSIGNED && item.quantity > 0); } // Reduce quantity of an inventory item, do cleanup if item goes away. // // Returns true if stack of items no longer exists. bool dec_inv_item_quantity( int obj, int amount ) { bool ret = false; if (you.equip[EQ_WEAPON] == obj) you.wield_change = true; if (you.inv[obj].quantity <= amount) { for (int i = 0; i < NUM_EQUIP; i++) { if (you.equip[i] == obj) { you.equip[i] = -1; if (i == EQ_WEAPON) { unwield_item( obj ); canned_msg( MSG_EMPTY_HANDED ); } } } you.inv[obj].base_type = OBJ_UNASSIGNED; you.inv[obj].quantity = 0; ret = true; } else { you.inv[obj].quantity -= amount; } burden_change(); return (ret); } // Reduce quantity of a monster/grid item, do cleanup if item goes away. // // Returns true if stack of items no longer exists. bool dec_mitm_item_quantity( int obj, int amount ) { if (mitm[obj].quantity <= amount) { destroy_item( obj ); return (true); } mitm[obj].quantity -= amount; return (false); } void inc_inv_item_quantity( int obj, int amount ) { if (you.equip[EQ_WEAPON] == obj) you.wield_change = true; you.inv[obj].quantity += amount; burden_change(); } void inc_mitm_item_quantity( int obj, int amount ) { mitm[obj].quantity += amount; } void init_item( int item ) { if (item == NON_ITEM) return; mitm[item].base_type = OBJ_UNASSIGNED; mitm[item].sub_type = 0; mitm[item].plus = 0; mitm[item].plus2 = 0; mitm[item].special = 0; mitm[item].quantity = 0; mitm[item].colour = 0; mitm[item].flags = 0; mitm[item].x = 0; mitm[item].y = 0; mitm[item].link = NON_ITEM; } // Returns an unused mitm slot, or NON_ITEM if none available. // The reserve is the number of item slots to not check. // Items may be culled if a reserve <= 10 is specified. int get_item_slot( int reserve ) { ASSERT( reserve >= 0 ); int item = NON_ITEM; for (item = 0; item < (MAX_ITEMS - reserve); item++) { if (!is_valid_item( mitm[item] )) break; } if (item >= MAX_ITEMS - reserve) { item = (reserve <= 10) ? cull_items() : NON_ITEM; if (item == NON_ITEM) return (NON_ITEM); } ASSERT( item != NON_ITEM ); init_item( item ); return (item); } void unlink_item( int dest ) { int c = 0; int cy = 0; // Don't destroy non-items, may be called after an item has been // reduced to zero quantity however. if (dest == NON_ITEM || !is_valid_item( mitm[dest] )) return; if (mitm[dest].x == 0 && mitm[dest].y == 0) { // (0,0) is where the monster items are (and they're unlinked by igrd), // although it also contains items that are not linked in yet. // // Check if a monster has it: for (c = 0; c < MAX_MONSTERS; c++) { struct monsters *monster = &menv[c]; if (monster->type == -1) continue; for (cy = 0; cy < NUM_MONSTER_SLOTS; cy++) { if (monster->inv[cy] == dest) { monster->inv[cy] = NON_ITEM; mitm[dest].x = 0; mitm[dest].y = 0; mitm[dest].link = NON_ITEM; // This causes problems when changing levels. -- bwr // if (monster->type == MONS_DANCING_WEAPON) // monster_die(monster, KILL_RESET, 0); return; } } } // Always return because this item might just be temporary. return; } else { // Linked item on map: // // Use the items (x,y) to access the list (igrd[x][y]) where // the item should be linked. // First check the top: if (igrd[ mitm[dest].x ][ mitm[dest].y ] == dest) { // link igrd to the second item igrd[ mitm[dest].x ][ mitm[dest].y ] = mitm[dest].link; mitm[dest].x = 0; mitm[dest].y = 0; mitm[dest].link = NON_ITEM; return; } // Okay, item is buried, find item that's on top of it: for (c = igrd[ mitm[dest].x ][ mitm[dest].y ]; c != NON_ITEM; c = mitm[c].link) { // find item linking to dest item if (is_valid_item( mitm[c] ) && mitm[c].link == dest) { // unlink dest mitm[c].link = mitm[dest].link; mitm[dest].x = 0; mitm[dest].y = 0; mitm[dest].link = NON_ITEM; return; } } } #if DEBUG // Okay, the sane ways are gone... let's warn the player: mpr( "BUG WARNING: Problems unlinking item!!!", MSGCH_DANGER ); // Okay, first we scan all items to see if we have something // linked to this item. We're not going to return if we find // such a case... instead, since things are already out of // alignment, let's assume there might be multiple links as well. bool linked = false; int old_link = mitm[dest].link; // used to try linking the first // clean the relevant parts of the object: mitm[dest].base_type = OBJ_UNASSIGNED; mitm[dest].quantity = 0; mitm[dest].x = 0; mitm[dest].y = 0; mitm[dest].link = NON_ITEM; // Look through all items for links to this item. for (c = 0; c < MAX_ITEMS; c++) { if (is_valid_item( mitm[c] ) && mitm[c].link == dest) { // unlink item mitm[c].link = old_link; if (!linked) { old_link = NON_ITEM; linked = true; } } } // Now check the grids to see if it's linked as a list top. for (c = 2; c < (GXM - 1); c++) { for (cy = 2; cy < (GYM - 1); cy++) { if (igrd[c][cy] == dest) { igrd[c][cy] = old_link; if (!linked) { old_link = NON_ITEM; // cleaned after the first linked = true; } } } } // Okay, finally warn player if we didn't do anything. if (!linked) mpr("BUG WARNING: Item didn't seem to be linked at all.", MSGCH_DANGER); #endif } // end unlink_item() static void item_cleanup(item_def &item) { item.base_type = OBJ_UNASSIGNED; item.quantity = 0; item.orig_place = 0; item.orig_monnum = 0; item.inscription.clear(); } void destroy_item( int dest ) { // Don't destroy non-items, but this function may be called upon // to remove items reduced to zero quantity, so we allow "invalid" // objects in. if (dest == NON_ITEM || !is_valid_item( mitm[dest] )) return; unlink_item( dest ); item_cleanup(mitm[dest]); } void destroy_item_stack( int x, int y ) { int o = igrd[x][y]; igrd[x][y] = NON_ITEM; while (o != NON_ITEM) { int next = mitm[o].link; if (is_valid_item( mitm[o] )) { if (mitm[o].base_type == OBJ_ORBS) { set_unique_item_status( OBJ_ORBS, mitm[o].sub_type, UNIQ_LOST_IN_ABYSS ); } else if (is_fixed_artefact( mitm[o] )) { set_unique_item_status( OBJ_WEAPONS, mitm[o].special, UNIQ_LOST_IN_ABYSS ); } mitm[o].base_type = OBJ_UNASSIGNED; mitm[o].quantity = 0; } o = next; } } static void describe_floor() { const int grid = grd[you.x_pos][you.y_pos]; if (grid >= DNGN_ENTER_HELL && grid <= DNGN_PERMADRY_FOUNTAIN) { if (grid >= DNGN_STONE_STAIRS_DOWN_I && grid <= DNGN_ROCK_STAIRS_DOWN) { snprintf( info, INFO_SIZE, "There is a %s staircase leading down here.", (grid == DNGN_ROCK_STAIRS_DOWN) ? "rock" : "stone" ); mpr(info); } else if (grid >= DNGN_STONE_STAIRS_UP_I && grid <= DNGN_ROCK_STAIRS_UP) { snprintf( info, INFO_SIZE, "There is a %s staircase leading upwards here.", (grid == DNGN_ROCK_STAIRS_UP) ? "rock" : "stone" ); mpr(info); } else { switch (grid) { case DNGN_ENTER_HELL: mpr("There is a gateway to Hell here."); break; case DNGN_ENTER_GEHENNA: mpr("There is a gateway to Gehenna here."); break; case DNGN_ENTER_COCYTUS: mpr("There is a gateway to the frozen wastes of Cocytus here."); break; case DNGN_ENTER_TARTARUS: mpr("There is a gateway to Tartarus here."); break; case DNGN_ENTER_DIS: mpr("There is a gateway to the Iron City of Dis here."); break; case DNGN_ENTER_SHOP: snprintf( info, INFO_SIZE, "There is an entrance to %s here.", shop_name(you.x_pos, you.y_pos)); mpr(info); break; case DNGN_ENTER_LABYRINTH: mpr("There is an entrance to a labyrinth here."); mpr("Beware, for starvation awaits!"); break; case DNGN_ENTER_ABYSS: mpr("There is a one-way gate to the infinite horrors of the Abyss here."); break; case DNGN_STONE_ARCH: mpr("There is an empty stone archway here."); break; case DNGN_EXIT_ABYSS: mpr("There is a gateway leading out of the Abyss here."); break; case DNGN_ENTER_PANDEMONIUM: mpr("There is a gate leading to the halls of Pandemonium here."); break; case DNGN_EXIT_PANDEMONIUM: mpr("There is a gate leading out of Pandemonium here."); break; case DNGN_TRANSIT_PANDEMONIUM: mpr("There is a gate leading to another region of Pandemonium here."); break; case DNGN_ENTER_ORCISH_MINES: mpr("There is a staircase to the Orcish Mines here."); break; case DNGN_ENTER_HIVE: mpr("There is a staircase to the Hive here."); break; case DNGN_ENTER_LAIR: mpr("There is a staircase to the Lair here."); break; case DNGN_ENTER_SLIME_PITS: mpr("There is a staircase to the Slime Pits here."); break; case DNGN_ENTER_VAULTS: mpr("There is a staircase to the Vaults here."); break; case DNGN_ENTER_CRYPT: mpr("There is a staircase to the Crypt here."); break; case DNGN_ENTER_HALL_OF_BLADES: mpr("There is a staircase to the Hall of Blades here."); break; case DNGN_ENTER_ZOT: mpr("There is a gate to the Realm of Zot here."); break; case DNGN_ENTER_TEMPLE: mpr("There is a staircase to the Ecumenical Temple here."); break; case DNGN_ENTER_SNAKE_PIT: mpr("There is a staircase to the Snake Pit here."); break; case DNGN_ENTER_ELVEN_HALLS: mpr("There is a staircase to the Elven Halls here."); break; case DNGN_ENTER_TOMB: mpr("There is a staircase to the Tomb here."); break; case DNGN_ENTER_SWAMP: mpr("There is a staircase to the Swamp here."); break; case DNGN_RETURN_FROM_ORCISH_MINES: case DNGN_RETURN_FROM_HIVE: case DNGN_RETURN_FROM_LAIR: case DNGN_RETURN_FROM_VAULTS: case DNGN_RETURN_FROM_TEMPLE: mpr("There is a staircase back to the Dungeon here."); break; case DNGN_RETURN_FROM_SLIME_PITS: case DNGN_RETURN_FROM_SNAKE_PIT: case DNGN_RETURN_FROM_SWAMP: mpr("There is a staircase back to the Lair here."); break; case DNGN_RETURN_FROM_CRYPT: case DNGN_RETURN_FROM_HALL_OF_BLADES: mpr("There is a staircase back to the Vaults here."); break; case DNGN_RETURN_FROM_TOMB: mpr("There is a staircase back to the Crypt here."); break; case DNGN_RETURN_FROM_ELVEN_HALLS: mpr("There is a staircase back to the Mines here."); break; case DNGN_RETURN_FROM_ZOT: mpr("There is a gate leading back out of this place here."); break; case DNGN_ALTAR_ZIN: mpr("There is a glowing white marble altar of Zin here."); break; case DNGN_ALTAR_SHINING_ONE: mpr("There is a glowing golden altar of the Shining One here."); break; case DNGN_ALTAR_KIKUBAAQUDGHA: mpr("There is an ancient bone altar of Kikubaaqudgha here."); break; case DNGN_ALTAR_YREDELEMNUL: mpr("There is a basalt altar of Yredelemnul here."); break; case DNGN_ALTAR_XOM: mpr("There is a shimmering altar of Xom here."); break; case DNGN_ALTAR_VEHUMET: mpr("There is a shining altar of Vehumet here."); break; case DNGN_ALTAR_OKAWARU: mpr("There is an iron altar of Okawaru here."); break; case DNGN_ALTAR_MAKHLEB: mpr("There is a burning altar of Makhleb here."); break; case DNGN_ALTAR_SIF_MUNA: mpr("There is a deep blue altar of Sif Muna here."); break; case DNGN_ALTAR_TROG: mpr("There is a bloodstained altar of Trog here."); break; case DNGN_ALTAR_NEMELEX_XOBEH: mpr("There is a sparkling altar of Nemelex Xobeh here."); break; case DNGN_ALTAR_ELYVILON: mpr("There is a silver altar of Elyvilon here."); break; case DNGN_BLUE_FOUNTAIN: mpr("There is a fountain here (q to drink)."); break; case DNGN_SPARKLING_FOUNTAIN: mpr("There is a sparkling fountain here (q to drink)."); break; case DNGN_DRY_FOUNTAIN_I: case DNGN_DRY_FOUNTAIN_II: case DNGN_DRY_FOUNTAIN_IV: case DNGN_DRY_FOUNTAIN_VI: case DNGN_DRY_FOUNTAIN_VIII: case DNGN_PERMADRY_FOUNTAIN: mpr("There is a dry fountain here."); break; } } } } static bool invisible_to_player( const item_def& item ) { return strstr(item.inscription.c_str(), "=k") != 0; } /* * Takes keyin as an argument because it will only display a long list of items * if ; is pressed. */ void item_check(char keyin) { char item_show[50][50]; char temp_quant[10]; int counter = 0; int counter_max = 0; describe_floor(); if (igrd[you.x_pos][you.y_pos] == NON_ITEM && keyin == ';') { mpr("There are no items here."); return; } autoinscribe_items(); autopickup(); origin_set(you.x_pos, you.y_pos); for ( int objl = igrd[you.x_pos][you.y_pos]; objl != NON_ITEM; objl = mitm[objl].link ) { if ( invisible_to_player(mitm[objl]) ) continue; counter++; if (counter > 45) { strcpy(item_show[counter], "Too many items."); break; } if (mitm[objl].base_type == OBJ_GOLD) { itoa(mitm[objl].quantity, temp_quant, 10); strcpy(item_show[counter], temp_quant); strcat(item_show[counter], " gold piece"); if (mitm[objl].quantity > 1) strcat(item_show[counter], "s"); } else { char str_pass[ ITEMNAME_SIZE ]; it_name(objl, DESC_NOCAP_A, str_pass); strcpy(item_show[counter], str_pass); } } counter_max = counter; counter = 0; if (counter_max == 1) { strcpy(info, "You see here "); // remember 'an'. strcat(info, item_show[counter_max]); strcat(info, "."); mpr(info); counter++; counter_max = 0; // to skip next part. } if ((counter_max > 0 && counter_max < 6) || (counter_max > 1 && keyin == ';')) { mpr("Things that are here:"); while (counter < counter_max) { // this is before the strcpy because item_show start at 1, not 0. counter++; mpr(item_show[counter]); } } if (counter_max > 5 && keyin != ';') mpr("There are several objects here."); } void show_items() { const int o = igrd[you.x_pos][you.y_pos]; if (o == NON_ITEM) { mpr("There are no items here."); } else { std::vector items; for (int i = o; i != NON_ITEM; i = mitm[i].link) if ( !invisible_to_player(mitm[i]) ) items.push_back( &mitm[i] ); select_items( items, "Things that are here:", true ); redraw_screen(); } describe_floor(); } void pickup_menu(int item_link) { std::vector items; for (int i = item_link; i != NON_ITEM; i = mitm[i].link) if (!invisible_to_player(mitm[i])) items.push_back( &mitm[i] ); std::vector selected = select_items( items, "Select items to pick up" ); redraw_screen(); for (int i = 0, count = selected.size(); i < count; ++i) { for (int j = item_link; j != NON_ITEM; j = mitm[j].link) { if (&mitm[j] == selected[i].item) { if (j == item_link) item_link = mitm[j].link; unsigned long oldflags = mitm[j].flags; mitm[j].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); int result = move_item_to_player( j, selected[i].quantity ); // If we cleared any flags on the items, but the pickup was // partial, reset the flags for the items that remain on the // floor. if (is_valid_item(mitm[j])) mitm[j].flags = oldflags; if (result == 0) { mpr("You can't carry that much weight."); return; } else if (result == -1) { mpr("You can't carry that many items."); return; } break; } } } } bool origin_known(const item_def &item) { return (item.orig_place != 0); } // We have no idea where the player found this item. void origin_set_unknown(item_def &item) { if (!origin_known(item)) { item.orig_place = 0xFFFF; item.orig_monnum = 0; } } // This item is starting equipment. void origin_set_startequip(item_def &item) { if (!origin_known(item)) { item.orig_place = 0xFFFF; item.orig_monnum = -1; } } void origin_set_monster(item_def &item, const monsters *monster) { if (!origin_known(item)) { if (!item.orig_monnum) item.orig_monnum = monster->type + 1; item.orig_place = get_packed_place(); } } void origin_purchased(item_def &item) { // We don't need to check origin_known if it's a shop purchase item.orig_place = get_packed_place(); // Hackiness item.orig_monnum = -1; } void origin_acquired(item_def &item, int agent) { // We don't need to check origin_known if it's a divine gift item.orig_place = get_packed_place(); // Hackiness item.orig_monnum = -2 - agent; } void origin_set_inventory(void (*oset)(item_def &item)) { for (int i = 0; i < ENDOFPACK; ++i) { if (is_valid_item(you.inv[i])) oset(you.inv[i]); } } static int first_corpse_monnum(int x, int y) { // We could look for a corpse on this square and assume that the // items belonged to it, but that is unsatisfactory. return (0); } void origin_set(int x, int y) { int monnum = first_corpse_monnum(x, y); unsigned short pplace = get_packed_place(); for (int link = igrd[x][y]; link != NON_ITEM; link = mitm[link].link) { item_def &item = mitm[link]; if (origin_known(item)) continue; if (!item.orig_monnum) item.orig_monnum = static_cast( monnum ); item.orig_place = pplace; } } void origin_set_monstercorpse(item_def &item, int x, int y) { item.orig_monnum = first_corpse_monnum(x, y); } void origin_freeze(item_def &item, int x, int y) { if (!origin_known(item)) { if (!item.orig_monnum && x != -1 && y != -1) origin_set_monstercorpse(item, x, y); item.orig_place = get_packed_place(); } } static std::string origin_monster_name(const item_def &item) { const int monnum = item.orig_monnum - 1; if (monnum == MONS_PLAYER_GHOST) return ("a player ghost"); else if (monnum == MONS_PANDEMONIUM_DEMON) return ("a demon"); char monnamebuf[ITEMNAME_SIZE]; // Le sigh. moname(monnum, true, DESC_NOCAP_A, monnamebuf); return (monnamebuf); } static std::string origin_monster_desc(const item_def &item) { return (origin_monster_name(item)); } static std::string origin_place_desc(const item_def &item) { return prep_branch_level_name(item.orig_place); } bool is_rune(const item_def &item) { return (item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT); } bool origin_describable(const item_def &item) { return (origin_known(item) && (item.orig_place != 0xFFFFU || item.orig_monnum == -1) && (!is_stackable_item(item) || is_rune(item)) && item.quantity == 1 && item.base_type != OBJ_CORPSES && (item.base_type != OBJ_FOOD || item.sub_type != FOOD_CHUNK) // Portable altars cannot be tracked meaningfully with Crawl's // current handling for portable altars. && (item.base_type != OBJ_MISCELLANY || item.sub_type != MISC_PORTABLE_ALTAR_OF_NEMELEX)); } std::string article_it(const item_def &item) { /* bool them = false; if (item.quantity > 1) them = true; else if (item.base_type == OBJ_ARMOUR && item.sub_type == ARM_BOOTS) { if (item.plus2 != TBOOT_NAGA_BARDING && item.plus2 != TBOOT_CENTAUR_BARDING) them = true; } else if (item.base_type == OBJ_ARMOUR && item.sub_type == ARM_GLOVES) { them = true; } return them? "them" : "it"; */ // "it" is always correct, since gloves and boots also come in pairs. return "it"; } bool origin_is_original_equip(const item_def &item) { return (item.orig_place == 0xFFFFU && item.orig_monnum == -1); } std::string origin_desc(const item_def &item) { if (!origin_describable(item)) return (""); if (origin_is_original_equip(item)) return "Original Equipment"; std::string desc; if (item.orig_monnum) { if (item.orig_monnum < 0) { int iorig = -item.orig_monnum - 2; switch (iorig) { case -1: desc += "You bought " + article_it(item) + " in a shop "; break; case AQ_SCROLL: desc += "You acquired " + article_it(item) + " "; break; case AQ_CARD_ACQUISITION: desc += "You drew \"Acquisition\" "; break; case AQ_CARD_VIOLENCE: desc += "You drew \"Violence\" "; break; case AQ_CARD_PROTECTION: desc += "You drew \"Protection\" "; break; case AQ_CARD_KNOWLEDGE: desc += "You drew \"Knowledge\" "; break; case AQ_WIZMODE: desc += "Your wizardly powers created " + article_it(item) + " "; break; default: if (iorig > GOD_NO_GOD && iorig < NUM_GODS) desc += std::string(god_name(iorig)) + " gifted " + article_it(item) + " to you "; else // Bug really. desc += "You stumbled upon " + article_it(item) + " "; break; } } else if (item.orig_monnum - 1 == MONS_DANCING_WEAPON) desc += "You subdued it "; else desc += "You took " + article_it(item) + " off " + origin_monster_desc(item) + " "; } else desc += "You found " + article_it(item) + " "; desc += origin_place_desc(item); return (desc); } bool pickup_single_item(int link, int qty) { if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR && you.duration[DUR_TRANSFORMATION] > 0) { mpr("You can't pick up anything in this form!"); return (false); } if (player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT)) { mpr("You can't reach the floor from up here."); return (false); } if (qty < 1 || qty > mitm[link].quantity) qty = mitm[link].quantity; unsigned long oldflags = mitm[link].flags; mitm[link].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); int num = move_item_to_player( link, qty ); if (is_valid_item(mitm[link])) mitm[link].flags = oldflags; if (num == -1) { mpr("You can't carry that many items."); return (false); } else if (num == 0) { mpr("You can't carry that much weight."); return (false); } return (true); } void pickup(void) { int o = 0; int m = 0; unsigned char keyin = 0; int next; char str_pass[ ITEMNAME_SIZE ]; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR && you.duration[DUR_TRANSFORMATION] > 0) { mpr("You can't pick up anything in this form!"); return; } if (player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT)) { mpr("You can't reach the floor from up here."); return; } // Fortunately, the player is prevented from testing their // portable altar in the Ecumenical Temple. -- bwr if (grd[you.x_pos][you.y_pos] == DNGN_ALTAR_NEMELEX_XOBEH && !player_in_branch( BRANCH_ECUMENICAL_TEMPLE )) { if (inv_count() >= ENDOFPACK) { mpr("There is a portable altar here, but you can't carry anything else."); return; } if (yesno("There is a portable altar here. Pick it up?")) { for (m = 0; m < ENDOFPACK; m++) { if (!is_valid_item( you.inv[m] )) { you.inv[m].base_type = OBJ_MISCELLANY; you.inv[m].sub_type = MISC_PORTABLE_ALTAR_OF_NEMELEX; you.inv[m].plus = 0; you.inv[m].plus2 = 0; you.inv[m].special = 0; you.inv[m].colour = LIGHTMAGENTA; you.inv[m].quantity = 1; set_ident_flags( you.inv[m], ISFLAG_IDENT_MASK ); you.inv[m].x = -1; you.inv[m].y = -1; you.inv[m].link = m; burden_change(); in_name( m, DESC_INVENTORY_EQUIP, str_pass ); strcpy( info, str_pass ); mpr( info ); break; } } grd[you.x_pos][you.y_pos] = DNGN_FLOOR; } } o = igrd[you.x_pos][you.y_pos]; if (o == NON_ITEM) { mpr("There are no items here."); } else if (mitm[o].link == NON_ITEM) // just one item? { // deliberately allowing the player to pick up // a killed item here pickup_single_item(o, mitm[o].quantity); } // end of if items_here else { mpr("There are several objects here."); while (o != NON_ITEM) { next = mitm[o].link; if ( invisible_to_player( mitm[o] ) ) { o = next; continue; } if (keyin != 'a') { strcpy(info, "Pick up "); if (mitm[o].base_type == OBJ_GOLD) { char st_prn[20]; itoa(mitm[o].quantity, st_prn, 10); strcat(info, st_prn); strcat(info, " gold piece"); if (mitm[o].quantity > 1) strcat(info, "s"); } else { it_name(o, DESC_NOCAP_A, str_pass); strcat(info, str_pass); } strcat(info, "\? (y,n,a,*,q)"); mpr( info, MSGCH_PROMPT ); keyin = get_ch(); } if (keyin == '*' || keyin == '?' || keyin == ',') { pickup_menu(o); break; } if (keyin == 'q') break; if (keyin == 'y' || keyin == 'a') { mitm[o].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); int result = move_item_to_player( o, mitm[o].quantity ); if (result == 0) { mpr("You can't carry that much weight."); keyin = 'x'; // resets from 'a' } else if (result == -1) { mpr("You can't carry that many items."); break; } } o = next; } } } // end pickup() static bool is_stackable_item( const item_def &item ) { if (!is_valid_item( item )) return (false); if (item.base_type == OBJ_MISSILES || (item.base_type == OBJ_FOOD && item.sub_type != FOOD_CHUNK) || item.base_type == OBJ_SCROLLS || item.base_type == OBJ_POTIONS || item.base_type == OBJ_UNKNOWN_II || (item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT)) { return (true); } return (false); } bool items_stack( const item_def &item1, const item_def &item2 ) { // both items must be stackable if (!is_stackable_item( item1 ) || !is_stackable_item( item2 )) return (false); // base and sub-types must always be the same to stack if (item1.base_type != item2.base_type || item1.sub_type != item2.sub_type) return (false); // These classes also require pluses and special if (item1.base_type == OBJ_MISSILES || item1.base_type == OBJ_MISCELLANY) // only runes { if (item1.plus != item2.plus || item1.plus2 != item2.plus2 || item1.special != item2.special) { return (false); } } // Check the flags, food/scrolls/potions don't care about the item's // ident status (scrolls and potions are known by identifying any // one of them, the individual status might not be the same). if (item1.base_type == OBJ_FOOD || item1.base_type == OBJ_SCROLLS || item1.base_type == OBJ_POTIONS) { if ((item1.flags & ~ISFLAG_IDENT_MASK) != (item2.flags & ~ISFLAG_IDENT_MASK)) { return (false); } // Thanks to mummy cursing, we can have potions of decay // that don't look alike... so we don't stack potions // if either isn't identified and they look different. -- bwr if (item1.base_type == OBJ_POTIONS && item1.special != item2.special && (!item_ident( item1, ISFLAG_KNOW_TYPE ) || !item_ident( item2, ISFLAG_KNOW_TYPE ))) { return (false); } } else if (item1.flags != item2.flags) { return (false); } return (true); } static int userdef_find_free_slot(const item_def &i) { #ifdef CLUA_BINDINGS int slot = -1; if (!clua.callfn("c_assign_invletter", "u>d", &i, &slot)) return (-1); return (slot); #else return -1; #endif } int find_free_slot(const item_def &i) { #define slotisfree(s) \ ((s) >= 0 && (s) < ENDOFPACK && !is_valid_item(you.inv[s])) bool searchforward = false; // If we're doing Lua, see if there's a Lua function that can give // us a free slot. int slot = userdef_find_free_slot(i); if (slot == -2 || Options.assign_item_slot == SS_FORWARD) searchforward = true; if (slotisfree(slot)) return slot; // See if the item remembers where it's been. Lua code can play with // this field so be extra careful. if ((i.slot >= 'a' && i.slot <= 'z') || (i.slot >= 'A' && i.slot <= 'Z')) slot = letter_to_index(i.slot); if (slotisfree(slot)) return slot; if (searchforward) { // Return first free slot for (slot = 0; slot < ENDOFPACK; ++slot) { if (!is_valid_item(you.inv[slot])) return slot; } } else { // This is the new default free slot search. We look for the last // available slot that does not leave a gap in the inventory. bool accept_empty = false; for (slot = ENDOFPACK - 1; slot >= 0; --slot) { if (is_valid_item(you.inv[slot])) { if (!accept_empty && slot + 1 < ENDOFPACK && !is_valid_item(you.inv[slot + 1])) return (slot + 1); accept_empty = true; } else if (accept_empty) { return slot; } } } return (-1); #undef slotisfree } // Returns quantity of items moved into player's inventory and -1 if // the player's inventory is full. int move_item_to_player( int obj, int quant_got, bool quiet ) { int imass = 0; int unit_mass = 0; int retval = quant_got; char brek = 0; bool partialPickup = false; int m = 0; // Gold has no mass, so we handle it first. if (mitm[obj].base_type == OBJ_GOLD) { you.gold += quant_got; dec_mitm_item_quantity( obj, quant_got ); you.redraw_gold = 1; if (!quiet) { snprintf( info, INFO_SIZE, "You pick up %d gold piece%s.", quant_got, (quant_got > 1) ? "s" : "" ); mpr(info); } you.turn_is_over = 1; return (retval); } unit_mass = item_mass( mitm[obj] ); if (quant_got > mitm[obj].quantity || quant_got <= 0) quant_got = mitm[obj].quantity; imass = unit_mass * quant_got; brek = 0; // multiply both constants * 10 if ((int) you.burden + imass > carrying_capacity()) { // calculate quantity we can actually pick up int part = (carrying_capacity() - (int)you.burden) / unit_mass; if (part < 1) return (0); // only pickup 'part' items quant_got = part; partialPickup = true; retval = part; } if (is_stackable_item( mitm[obj] )) { for (m = 0; m < ENDOFPACK; m++) { if (items_stack( you.inv[m], mitm[obj] )) { if (!quiet && partialPickup) mpr("You can only carry some of what is here."); inc_inv_item_quantity( m, quant_got ); dec_mitm_item_quantity( obj, quant_got ); burden_change(); if (!quiet) { in_name( m, DESC_INVENTORY, info ); mpr(info); } you.turn_is_over = 1; return (retval); } } // end of for m loop. } // can't combine, check for slot space if (inv_count() >= ENDOFPACK) return (-1); if (!quiet && partialPickup) mpr("You can only carry some of what is here."); int freeslot = find_free_slot(mitm[obj]); if (freeslot < 0 || freeslot >= ENDOFPACK || is_valid_item(you.inv[freeslot])) { // Something is terribly wrong return (-1); } item_def &item = you.inv[freeslot]; // copy item item = mitm[obj]; item.x = -1; item.y = -1; item.link = freeslot; /*** HP CHANGE: do autoinscribe ***/ autoinscribe_item( item ); origin_freeze(item, you.x_pos, you.y_pos); item.quantity = quant_got; dec_mitm_item_quantity( obj, quant_got ); burden_change(); if (!quiet) { in_name( freeslot, DESC_INVENTORY, info ); mpr(info); } if (item.base_type == OBJ_ORBS && you.char_direction == DIR_DESCENDING) { if (!quiet) mpr("Now all you have to do is get back out of the dungeon!"); you.char_direction = DIR_ASCENDING; } you.turn_is_over = 1; return (retval); } // end move_item_to_player() // Moves mitm[obj] to (x,y)... will modify the value of obj to // be the index of the final object (possibly different). // // Done this way in the hopes that it will be obvious from // calling code that "obj" is possibly modified. void move_item_to_grid( int *const obj, int x, int y ) { // must be a valid reference to a valid object if (*obj == NON_ITEM || !is_valid_item( mitm[*obj] )) return; // If it's a stackable type... if (is_stackable_item( mitm[*obj] )) { // Look for similar item to stack: for (int i = igrd[x][y]; i != NON_ITEM; i = mitm[i].link) { // check if item already linked here -- don't want to unlink it if (*obj == i) return; if (items_stack( mitm[*obj], mitm[i] )) { // Add quantity to item already here, and dispose // of obj, while returning the found item. -- bwr inc_mitm_item_quantity( i, mitm[*obj].quantity ); destroy_item( *obj ); *obj = i; return; } } } ASSERT( *obj != NON_ITEM ); // Need to actually move object, so first unlink from old position. unlink_item( *obj ); // move item to coord: mitm[*obj].x = x; mitm[*obj].y = y; // link item to top of list. mitm[*obj].link = igrd[x][y]; igrd[x][y] = *obj; return; } void move_item_stack_to_grid( int x, int y, int targ_x, int targ_y ) { // Tell all items in stack what the new coordinate is. for (int o = igrd[x][y]; o != NON_ITEM; o = mitm[o].link) { mitm[o].x = targ_x; mitm[o].y = targ_y; } igrd[targ_x][targ_y] = igrd[x][y]; igrd[x][y] = NON_ITEM; } // returns quantity dropped bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos, int quant_drop, bool mark_dropped ) { if (quant_drop == 0) return (false); // default quant_drop == -1 => drop all if (quant_drop < 0) quant_drop = item.quantity; // loop through items at current location if (is_stackable_item( item )) { for (int i = igrd[x_plos][y_plos]; i != NON_ITEM; i = mitm[i].link) { if (items_stack( item, mitm[i] )) { inc_mitm_item_quantity( i, quant_drop ); // If the items on the floor already have a nonzero slot, // leave it as such, otherwise set the slot. if (mark_dropped && !mitm[i].slot) mitm[i].slot = index_to_letter(item.link); return (true); } } } // item not found in current stack, add new item to top. int new_item = get_item_slot(10); if (new_item == NON_ITEM) return (false); // copy item mitm[new_item] = item; // set quantity, and set the item as unlinked mitm[new_item].quantity = quant_drop; mitm[new_item].x = 0; mitm[new_item].y = 0; mitm[new_item].link = NON_ITEM; if (mark_dropped) { mitm[new_item].slot = index_to_letter(item.link); mitm[new_item].flags |= ISFLAG_DROPPED; mitm[new_item].flags &= ~ISFLAG_THROWN; origin_set_unknown(mitm[new_item]); } move_item_to_grid( &new_item, x_plos, y_plos ); return (true); } // end copy_item_to_grid() //--------------------------------------------------------------- // // move_top_item -- moves the top item of a stack to a new // location. // //--------------------------------------------------------------- bool move_top_item( int src_x, int src_y, int dest_x, int dest_y ) { int item = igrd[ src_x ][ src_y ]; if (item == NON_ITEM) return (false); // Now move the item to its new possition... move_item_to_grid( &item, dest_x, dest_y ); return (true); } //--------------------------------------------------------------- // // drop_gold // //--------------------------------------------------------------- static void drop_gold(unsigned int amount) { const unsigned long BIGGEST_QUANT_VALUE = ((1L << (sizeof(mitm[0].quantity)*8 - 1)) - 1000); if (you.gold > 0) { if (amount > you.gold) amount = you.gold; snprintf( info, INFO_SIZE, "You drop %d gold piece%s.", amount, (amount > 1) ? "s" : "" ); mpr(info); // loop through items at grid location, look for gold int i = igrd[you.x_pos][you.y_pos]; while(i != NON_ITEM) { if (mitm[i].base_type == OBJ_GOLD) { if ( mitm[i].quantity + amount > BIGGEST_QUANT_VALUE ) { amount = BIGGEST_QUANT_VALUE - mitm[i].quantity; snprintf(info, INFO_SIZE, "But there's only room for %d.", amount); mpr(info); } inc_mitm_item_quantity( i, amount ); you.gold -= amount; you.redraw_gold = 1; you.turn_is_over = 1; return; } // follow link i = mitm[i].link; } // place on top. i = get_item_slot(10); if (i == NON_ITEM) { mpr( "Too many items on this level, not dropping the gold." ); return; } if (amount > BIGGEST_QUANT_VALUE) { amount = BIGGEST_QUANT_VALUE; snprintf(info, INFO_SIZE, "But there's only room for %d.", amount); mpr(info); } mitm[i].base_type = OBJ_GOLD; mitm[i].quantity = amount; mitm[i].flags = 0; move_item_to_grid( &i, you.x_pos, you.y_pos ); you.gold -= amount; you.redraw_gold = 1; you.turn_is_over = 1; } else { mpr("You don't have any money."); } } // end drop_gold() bool drop_item( int item_dropped, int quant_drop ) { if (item_dropped == PROMPT_GOT_SPECIAL) // ie '$' for gold { // drop gold if (quant_drop < 0 || quant_drop > static_cast< int >( you.gold )) quant_drop = you.gold; drop_gold( quant_drop ); return (true); } if (quant_drop < 0 || quant_drop > you.inv[item_dropped].quantity) quant_drop = you.inv[item_dropped].quantity; if (item_dropped == you.equip[EQ_LEFT_RING] || item_dropped == you.equip[EQ_RIGHT_RING] || item_dropped == you.equip[EQ_AMULET]) { mpr("You will have to take that off first."); return (false); } if (item_dropped == you.equip[EQ_WEAPON] && you.inv[item_dropped].base_type == OBJ_WEAPONS && item_cursed( you.inv[item_dropped] )) { mpr("That object is stuck to you!"); return (false); } for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++) { if (item_dropped == you.equip[i] && you.equip[i] != -1) { if (!Options.easy_armour) { mpr("You will have to take that off first."); } else { // If we take off the item, cue up the item being dropped if (takeoff_armour( item_dropped )) { start_delay( DELAY_DROP_ITEM, 1, item_dropped, 1 ); you.turn_is_over = 0; // turn happens later } } // Regardless, we want to return here because either we're // aborting the drop, or the drop is delayed until after // the armour is removed. -- bwr return (false); } } // Unwield needs to be done before copy in order to clear things // like temporary brands. -- bwr if (item_dropped == you.equip[EQ_WEAPON]) { unwield_item(item_dropped); you.equip[EQ_WEAPON] = -1; canned_msg( MSG_EMPTY_HANDED ); } const unsigned char my_grid = grd[you.x_pos][you.y_pos]; if ( !grid_destroys_items(my_grid) && !copy_item_to_grid( you.inv[item_dropped], you.x_pos, you.y_pos, quant_drop, true )) { mpr( "Too many items on this level, not dropping the item." ); return (false); } char str_pass[ ITEMNAME_SIZE ]; quant_name( you.inv[item_dropped], quant_drop, DESC_NOCAP_A, str_pass ); snprintf( info, INFO_SIZE, "You drop %s.", str_pass ); mpr(info); if ( grid_destroys_items(my_grid) ) { mprf(MSGCH_SOUND, grid_item_destruction_message(my_grid)); } dec_inv_item_quantity( item_dropped, quant_drop ); you.turn_is_over = 1; return (true); } static std::string drop_menu_title( int menuflags, const std::string &oldt ) { std::string res = oldt; res.erase(0, res.find_first_not_of(" \n\t")); if (menuflags & MF_MULTISELECT) res = "[Multidrop] " + res; return " " + res; } int get_equip_slot(const item_def *item) { int worn = -1; if (item && in_inventory(*item)) { for (int i = 0; i < NUM_EQUIP; ++i) { if (you.equip[i] == item->link) { worn = i; break; } } } return worn; } static std::string drop_selitem_text( const std::vector *s ) { char buf[130]; bool extraturns = false; if (!s->size()) return ""; for (int i = 0, size = s->size(); i < size; ++i) { const item_def *item = static_cast( (*s)[i]->data ); int eq = get_equip_slot(item); if (eq > EQ_WEAPON && eq < NUM_EQUIP) { extraturns = true; break; } } snprintf( buf, sizeof buf, " (%lu%s turn%s)", (unsigned long) (s->size()), extraturns? "+" : "", s->size() > 1? "s" : "" ); return buf; } //--------------------------------------------------------------- // // drop // // Prompts the user for an item to drop // //--------------------------------------------------------------- void drop(void) { if (inv_count() < 1 && you.gold == 0) { canned_msg(MSG_NOTHING_CARRIED); you.activity = ACT_NONE; return; } static std::vector selected; if (!you.activity || selected.empty()) selected = prompt_invent_items( "Drop which item?", -1, drop_menu_title, true, true, '$', &Options.drop_filter, drop_selitem_text ); if (selected.empty()) { canned_msg( MSG_OK ); you.activity = ACT_NONE; return; } // Throw away invalid items; items usually go invalid because // of chunks rotting away. while (!selected.empty() // Don't look for gold in inventory && selected[0].slot != PROMPT_GOT_SPECIAL && !is_valid_item(you.inv[ selected[0].slot ])) selected.erase( selected.begin() ); // Did the defunct item filter above deprive us of a drop target? if (!selected.empty()) { drop_item( selected[0].slot, selected[0].quantity ); // Forget the item we just dropped selected.erase( selected.begin() ); } // If we still have items that want to be dropped, start the multiturn // activity you.activity = selected.empty()? ACT_NONE : ACT_MULTIDROP; } // end drop() //--------------------------------------------------------------- // // shift_monster // // Moves a monster to approximately (x,y) and returns true // if monster was moved. // //--------------------------------------------------------------- static bool shift_monster( struct monsters *mon, int x, int y ) { bool found_move = false; int i, j; int tx, ty; int nx = 0, ny = 0; int count = 0; if (x == 0 && y == 0) { // try and find a random floor space some distance away for (i = 0; i < 50; i++) { tx = 5 + random2( GXM - 10 ); ty = 5 + random2( GYM - 10 ); int dist = grid_distance(x, y, tx, ty); if (grd[tx][ty] == DNGN_FLOOR && dist > 10) break; } if (i == 50) return (false); } for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { tx = x + i; ty = y + j; if (tx < 5 || tx > GXM - 5 || ty < 5 || ty > GXM - 5) continue; // won't drop on anything but vanilla floor right now if (grd[tx][ty] != DNGN_FLOOR) continue; if (mgrd[tx][ty] != NON_MONSTER) continue; if (tx == you.x_pos && ty == you.y_pos) continue; count++; if (one_chance_in(count)) { nx = tx; ny = ty; found_move = true; } } } if (found_move) { const int mon_index = mgrd[mon->x][mon->y]; mgrd[mon->x][mon->y] = NON_MONSTER; mgrd[nx][ny] = mon_index; mon->x = nx; mon->y = ny; } return (found_move); } //--------------------------------------------------------------- // // update_corpses // // Update all of the corpses and food chunks on the floor. (The // elapsed time is a double because this is called when we re- // enter a level and a *long* time may have elapsed). // //--------------------------------------------------------------- void update_corpses(double elapsedTime) { int cx, cy; if (elapsedTime <= 0.0) return; const long rot_time = (long) (elapsedTime / 20.0); for (int c = 0; c < MAX_ITEMS; c++) { if (mitm[c].quantity < 1) continue; if (mitm[c].base_type != OBJ_CORPSES && mitm[c].base_type != OBJ_FOOD) { continue; } if (mitm[c].base_type == OBJ_CORPSES && mitm[c].sub_type > CORPSE_SKELETON) { continue; } if (mitm[c].base_type == OBJ_FOOD && mitm[c].sub_type != FOOD_CHUNK) { continue; } if (rot_time >= mitm[c].special) { if (mitm[c].base_type == OBJ_FOOD) { destroy_item(c); } else { if (mitm[c].sub_type == CORPSE_SKELETON || !mons_skeleton( mitm[c].plus )) { destroy_item(c); } else { mitm[c].sub_type = CORPSE_SKELETON; mitm[c].special = 200; mitm[c].colour = LIGHTGREY; } } } else { ASSERT(rot_time < 256); mitm[c].special -= rot_time; } } int fountain_checks = (int)(elapsedTime / 1000.0); if (random2(1000) < (int)(elapsedTime) % 1000) fountain_checks += 1; // dry fountains may start flowing again if (fountain_checks > 0) { for (cx=0; cx DNGN_SPARKLING_FOUNTAIN && grd[cx][cy] < DNGN_PERMADRY_FOUNTAIN) { for (int i=0; i DNGN_SPARKLING_FOUNTAIN) grd[cx][cy]--; } } } } } } } static bool remove_enchant_levels( struct monsters *mon, int slot, int min, int levels ) { const int new_level = mon->enchantment[slot] - levels; if (new_level < min) { mons_del_ench( mon, mon->enchantment[slot], mon->enchantment[slot], true ); return (true); } else { mon->enchantment[slot] = new_level; } return (false); } //--------------------------------------------------------------- // // update_enchantments // // Update a monster's enchantments when the player returns // to the level. // // Management for enchantments... problems with this are the oddities // (monster dying from poison several thousands of turns later), and // game balance. // // Consider: Poison/Sticky Flame a monster at range and leave, monster // dies but can't leave level to get to player (implied game balance of // the delayed damage is that the monster could be a danger before // it dies). This could be fixed by keeping some monsters active // off level and allowing them to take stairs (a very serious change). // // Compare this to the current abuse where the player gets // effectively extended duration of these effects (although only // the actual effects only occur on level, the player can leave // and heal up without having the effect disappear). // // This is a simple compromise between the two... the enchantments // go away, but the effects don't happen off level. -- bwr // //--------------------------------------------------------------- static void update_enchantments( struct monsters *mon, int levels ) { int i; for (i = 0; i < NUM_MON_ENCHANTS; i++) { switch (mon->enchantment[i]) { case ENCH_YOUR_POISON_I: case ENCH_YOUR_POISON_II: case ENCH_YOUR_POISON_III: case ENCH_YOUR_POISON_IV: remove_enchant_levels( mon, i, ENCH_YOUR_POISON_I, levels ); break; case ENCH_YOUR_SHUGGOTH_I: case ENCH_YOUR_SHUGGOTH_II: case ENCH_YOUR_SHUGGOTH_III: case ENCH_YOUR_SHUGGOTH_IV: remove_enchant_levels( mon, i, ENCH_YOUR_SHUGGOTH_I, levels ); break; case ENCH_YOUR_ROT_I: case ENCH_YOUR_ROT_II: case ENCH_YOUR_ROT_III: case ENCH_YOUR_ROT_IV: remove_enchant_levels( mon, i, ENCH_YOUR_ROT_I, levels ); break; case ENCH_BACKLIGHT_I: case ENCH_BACKLIGHT_II: case ENCH_BACKLIGHT_III: case ENCH_BACKLIGHT_IV: remove_enchant_levels( mon, i, ENCH_BACKLIGHT_I, levels ); break; case ENCH_YOUR_STICKY_FLAME_I: case ENCH_YOUR_STICKY_FLAME_II: case ENCH_YOUR_STICKY_FLAME_III: case ENCH_YOUR_STICKY_FLAME_IV: remove_enchant_levels( mon, i, ENCH_YOUR_STICKY_FLAME_I, levels ); break; case ENCH_POISON_I: case ENCH_POISON_II: case ENCH_POISON_III: case ENCH_POISON_IV: remove_enchant_levels( mon, i, ENCH_POISON_I, levels ); break; case ENCH_STICKY_FLAME_I: case ENCH_STICKY_FLAME_II: case ENCH_STICKY_FLAME_III: case ENCH_STICKY_FLAME_IV: remove_enchant_levels( mon, i, ENCH_STICKY_FLAME_I, levels ); break; case ENCH_FRIEND_ABJ_I: case ENCH_FRIEND_ABJ_II: case ENCH_FRIEND_ABJ_III: case ENCH_FRIEND_ABJ_IV: case ENCH_FRIEND_ABJ_V: case ENCH_FRIEND_ABJ_VI: if (remove_enchant_levels( mon, i, ENCH_FRIEND_ABJ_I, levels )) { monster_die( mon, KILL_RESET, 0 ); } break; case ENCH_ABJ_I: case ENCH_ABJ_II: case ENCH_ABJ_III: case ENCH_ABJ_IV: case ENCH_ABJ_V: case ENCH_ABJ_VI: if (remove_enchant_levels( mon, i, ENCH_ABJ_I, levels )) { monster_die( mon, KILL_RESET, 0 ); } break; case ENCH_SHORT_LIVED: monster_die( mon, KILL_RESET, 0 ); break; case ENCH_TP_I: case ENCH_TP_II: case ENCH_TP_III: case ENCH_TP_IV: monster_teleport( mon, true ); break; case ENCH_CONFUSION: monster_blink( mon ); break; case ENCH_GLOWING_SHAPESHIFTER: case ENCH_SHAPESHIFTER: case ENCH_CREATED_FRIENDLY: case ENCH_SUBMERGED: default: // don't touch these break; case ENCH_SLOW: case ENCH_HASTE: case ENCH_FEAR: case ENCH_INVIS: case ENCH_CHARM: case ENCH_SLEEP_WARY: // delete enchantment (using function to get this done cleanly) mons_del_ench(mon, mon->enchantment[i], mon->enchantment[i], true); break; } } } //--------------------------------------------------------------- // // update_level // // Update the level when the player returns to it. // //--------------------------------------------------------------- void update_level( double elapsedTime ) { int m, i; int turns = (int) (elapsedTime / 10.0); #if DEBUG_DIAGNOSTICS int mons_total = 0; snprintf( info, INFO_SIZE, "turns: %d", turns ); mpr( info, MSGCH_DIAGNOSTICS ); #endif update_corpses( elapsedTime ); for (m = 0; m < MAX_MONSTERS; m++) { struct monsters *mon = &menv[m]; if (mon->type == -1) continue; #if DEBUG_DIAGNOSTICS mons_total++; #endif // following monsters don't get movement if (mon->flags & MF_JUST_SUMMONED) continue; // XXX: Allow some spellcasting (like Healing and Teleport)? -- bwr // const bool healthy = (mon->hit_points * 2 > mon->max_hit_points); // This is the monster healing code, moved here from tag.cc: if (monster_descriptor( mon->type, MDSC_REGENERATES ) || mon->type == MONS_PLAYER_GHOST) { heal_monster( mon, turns, false ); } else { heal_monster( mon, (turns / 10), false ); } if (turns >= 10) update_enchantments( mon, turns / 10 ); // Don't move water or lava monsters around if (monster_habitat( mon->type ) != DNGN_FLOOR) continue; // Let sleeping monsters lie if (mon->behaviour == BEH_SLEEP) continue; const int range = (turns * mon->speed) / 10; const int moves = (range > 50) ? 50 : range; // const bool short_time = (range >= 5 + random2(10)); const bool long_time = (range >= (500 + roll_dice( 2, 500 ))); const bool ranged_attack = (mons_has_ranged_spell( mon ) || mons_has_ranged_attack( mon )); #if DEBUG_DIAGNOSTICS // probably too annoying even for DEBUG_DIAGNOSTICS snprintf( info, INFO_SIZE, "mon #%d: range %d; long %d; pos (%d,%d); targ %d(%d,%d); flags %d", m, range, long_time, mon->x, mon->y, mon->foe, mon->target_x, mon->target_y, mon->flags ); mpr( info, MSGCH_DIAGNOSTICS ); #endif if (range <= 0) continue; if (long_time && (mon->behaviour == BEH_FLEE || mon->behaviour == BEH_CORNERED || testbits( mon->flags, MF_BATTY ) || ranged_attack || coinflip())) { if (mon->behaviour != BEH_WANDER) { mon->behaviour = BEH_WANDER; mon->foe = MHITNOT; mon->target_x = 10 + random2( GXM - 10 ); mon->target_y = 10 + random2( GYM - 10 ); } else { // monster will be sleeping after we move it mon->behaviour = BEH_SLEEP; } } else if (ranged_attack) { // if we're doing short time movement and the monster has a // ranged attack (missile or spell), then the monster will // flee to gain distance if its "too close", else it will // just shift its position rather than charge the player. -- bwr if (grid_distance(mon->x, mon->y, mon->target_x, mon->target_y) < 3) { mon->behaviour = BEH_FLEE; // if the monster is on the target square, fleeing won't work if (mon->x == mon->target_x && mon->y == mon->target_y) { if (you.x_pos != mon->x || you.y_pos != mon->y) { // flee from player's position if different mon->target_x = you.x_pos; mon->target_y = you.y_pos; } else { // randomize the target so we have a direction to flee mon->target_x += (random2(3) - 1); mon->target_y += (random2(3) - 1); } } #if DEBUG_DIAGNOSTICS mpr( "backing off...", MSGCH_DIAGNOSTICS ); #endif } else { shift_monster( mon, mon->x, mon->y ); #if DEBUG_DIAGNOSTICS snprintf(info, INFO_SIZE, "shifted to (%d,%d)", mon->x, mon->y); mpr( info, MSGCH_DIAGNOSTICS ); #endif continue; } } int pos_x = mon->x, pos_y = mon->y; // dirt simple movement: for (i = 0; i < moves; i++) { int mx = (pos_x > mon->target_x) ? -1 : (pos_x < mon->target_x) ? 1 : 0; int my = (pos_y > mon->target_y) ? -1 : (pos_y < mon->target_y) ? 1 : 0; if (mon->behaviour == BEH_FLEE) { mx *= -1; my *= -1; } if (pos_x + mx < 0 || pos_x + mx >= GXM) mx = 0; if (pos_y + my < 0 || pos_y + my >= GXM) my = 0; if (mx == 0 && my == 0) break; if (grd[pos_x + mx][pos_y + my] < DNGN_FLOOR) break; pos_x += mx; pos_y += my; } if (!shift_monster( mon, pos_x, pos_y )) shift_monster( mon, mon->x, mon->y ); #if DEBUG_DIAGNOSTICS snprintf( info, INFO_SIZE, "moved to (%d,%d)", mon->x, mon->y ); mpr( info, MSGCH_DIAGNOSTICS ); #endif } #if DEBUG_DIAGNOSTICS snprintf( info, INFO_SIZE, "total monsters on level = %d", mons_total ); mpr( info, MSGCH_DIAGNOSTICS ); #endif for (i = 0; i < MAX_CLOUDS; i++) delete_cloud( i ); } //--------------------------------------------------------------- // // handle_time // // Do various time related actions... // This function is called about every 20 turns. // //--------------------------------------------------------------- void handle_time( long time_delta ) { int temp_rand; // probability determination {dlb} // so as not to reduplicate f(x) calls {dlb} unsigned int which_miscast = SPTYP_RANDOM; bool summon_instead; // for branching within a single switch {dlb} int which_beastie = MONS_PROGRAM_BUG; // error trapping {dlb} unsigned char i; // loop variable {dlb} bool new_rotting_item = false; //mv: becomes true when some new item becomes rotting // BEGIN - Nasty things happen to people who spend too long in Hell: if (player_in_hell() && coinflip()) { temp_rand = random2(17); mpr((temp_rand == 0) ? "\"You will not leave this place.\"" : (temp_rand == 1) ? "\"Die, mortal!\"" : (temp_rand == 2) ? "\"We do not forgive those who trespass against us!\"" : (temp_rand == 3) ? "\"Trespassers are not welcome here!\"" : (temp_rand == 4) ? "\"You do not belong in this place!\"" : (temp_rand == 5) ? "\"Leave now, before it is too late!\"" : (temp_rand == 6) ? "\"We have you now!\"" : (temp_rand == 7) ? "You feel a terrible foreboding..." : (temp_rand == 8) ? "You hear words spoken in a strange and terrible language..." : (temp_rand == 9) ? ((you.species != SP_MUMMY) ? "You smell brimstone." : "Brimstone rains from above.") : (temp_rand == 10) ? "Something frightening happens." : (temp_rand == 11) ? "You sense an ancient evil watching you..." : (temp_rand == 12) ? "You feel lost and a long, long way from home..." : (temp_rand == 13) ? "You suddenly feel all small and vulnerable." : (temp_rand == 14) ? "A gut-wrenching scream fills the air!" : (temp_rand == 15) ? "You shiver with fear." : (temp_rand == 16) ? "You sense a hostile presence." : "You hear diabolical laughter!", MSGCH_TALK); temp_rand = random2(27); if (temp_rand > 17) // 9 in 27 odds {dlb} { temp_rand = random2(8); if (temp_rand > 3) // 4 in 8 odds {dlb} which_miscast = SPTYP_NECROMANCY; else if (temp_rand > 1) // 2 in 8 odds {dlb} which_miscast = SPTYP_SUMMONING; else if (temp_rand > 0) // 1 in 8 odds {dlb} which_miscast = SPTYP_CONJURATION; else // 1 in 8 odds {dlb} which_miscast = SPTYP_ENCHANTMENT; miscast_effect( which_miscast, 4 + random2(6), random2avg(97, 3), 100, "the effects of Hell" ); } else if (temp_rand > 7) // 10 in 27 odds {dlb} { // 60:40 miscast:summon split {dlb} summon_instead = (random2(5) > 2); switch (you.where_are_you) { case BRANCH_DIS: if (summon_instead) which_beastie = summon_any_demon(DEMON_GREATER); else which_miscast = SPTYP_EARTH; break; case BRANCH_GEHENNA: if (summon_instead) which_beastie = MONS_FIEND; else which_miscast = SPTYP_FIRE; break; case BRANCH_COCYTUS: if (summon_instead) which_beastie = MONS_ICE_FIEND; else which_miscast = SPTYP_ICE; break; case BRANCH_TARTARUS: if (summon_instead) which_beastie = MONS_SHADOW_FIEND; else which_miscast = SPTYP_NECROMANCY; break; default: // this is to silence gcc compiler warnings {dlb} if (summon_instead) which_beastie = MONS_FIEND; else which_miscast = SPTYP_NECROMANCY; break; } if (summon_instead) { create_monster( which_beastie, 0, BEH_HOSTILE, you.x_pos, you.y_pos, MHITYOU, 250 ); } else { miscast_effect( which_miscast, 4 + random2(6), random2avg(97, 3), 100, "the effects of Hell" ); } } // NB: no "else" - 8 in 27 odds that nothing happens through // first chain {dlb} // also note that the following is distinct from and in // addition to the above chain: // try to summon at least one and up to five random monsters {dlb} if (one_chance_in(3)) { create_monster( RANDOM_MONSTER, 0, BEH_HOSTILE, you.x_pos, you.y_pos, MHITYOU, 250 ); for (i = 0; i < 4; i++) { if (one_chance_in(3)) { create_monster( RANDOM_MONSTER, 0, BEH_HOSTILE, you.x_pos, you.y_pos, MHITYOU, 250 ); } } } } // END - special Hellish things... // Adjust the player's stats if s/he's diseased (or recovering). if (!you.disease) { if (you.strength < you.max_strength && one_chance_in(100)) { mpr("You feel your strength returning.", MSGCH_RECOVERY); you.strength++; you.redraw_strength = 1; } if (you.dex < you.max_dex && one_chance_in(100)) { mpr("You feel your dexterity returning.", MSGCH_RECOVERY); you.dex++; you.redraw_dexterity = 1; } if (you.intel < you.max_intel && one_chance_in(100)) { mpr("You feel your intelligence returning.", MSGCH_RECOVERY); you.intel++; you.redraw_intelligence = 1; } } else { if (one_chance_in(30)) { mpr("Your disease is taking its toll.", MSGCH_WARN); lose_stat(STAT_RANDOM, 1); } } // Adjust the player's stats if s/he has the deterioration mutation if (you.mutation[MUT_DETERIORATION] && random2(200) <= you.mutation[MUT_DETERIORATION] * 5 - 2) { lose_stat(STAT_RANDOM, 1); } int added_contamination = 0; // Account for mutagenic radiation. Invis and haste will give the // player about .1 points per turn, mutagenic randarts will give // about 1.5 points on average, so they can corrupt the player // quite quickly. Wielding one for a short battle is OK, which is // as things should be. -- GDL if (you.invis && random2(10) < 6) added_contamination++; if (you.haste && !you.berserker && random2(10) < 6) added_contamination++; // randarts are usually about 20x worse than running around invisible // or hasted.. this seems OK. added_contamination += random2(1 + scan_randarts(RAP_MUTAGENIC)); // we take off about .5 points per turn if (!you.invis && !you.haste && coinflip()) added_contamination -= 1; contaminate_player( added_contamination ); // only check for badness once every other turn if (coinflip()) { if (you.magic_contamination >= 5 /* && random2(150) <= you.magic_contamination */) { mpr("Your body shudders with the violent release of wild energies!", MSGCH_WARN); // for particularly violent releases, make a little boom if (you.magic_contamination > 25 && one_chance_in(3)) { struct bolt boom; boom.type = SYM_BURST; boom.colour = BLACK; boom.flavour = BEAM_RANDOM; boom.target_x = you.x_pos; boom.target_y = you.y_pos; boom.damage = dice_def( 3, (you.magic_contamination / 2) ); boom.thrower = KILL_MISC; boom.aux_source = "a magical explosion"; boom.beam_source = NON_MONSTER; boom.is_beam = false; boom.is_tracer = false; boom.is_explosion = true; strcpy(boom.beam_name, "magical storm"); boom.ench_power = (you.magic_contamination * 5); boom.ex_size = (you.magic_contamination / 15); if (boom.ex_size > 9) boom.ex_size = 9; explosion(boom); } // we want to warp the player, not do good stuff! if (one_chance_in(5)) mutate(100); else give_bad_mutation(coinflip()); // we're meaner now, what with explosions and whatnot, but // we dial down the contamination a little faster if its actually // mutating you. -- GDL contaminate_player( -(random2(you.magic_contamination / 4) + 1) ); } } // Random chance to identify staff in hand based off of Spellcasting // and an appropriate other spell skill... is 1/20 too fast? if (you.equip[EQ_WEAPON] != -1 && you.inv[you.equip[EQ_WEAPON]].base_type == OBJ_STAVES && !item_ident( you.inv[you.equip[EQ_WEAPON]], ISFLAG_KNOW_TYPE ) && one_chance_in(20)) { int total_skill = you.skills[SK_SPELLCASTING]; switch (you.inv[you.equip[EQ_WEAPON]].sub_type) { case STAFF_WIZARDRY: case STAFF_ENERGY: total_skill += you.skills[SK_SPELLCASTING]; break; case STAFF_FIRE: if (you.skills[SK_FIRE_MAGIC] > you.skills[SK_ICE_MAGIC]) total_skill += you.skills[SK_FIRE_MAGIC]; else total_skill += you.skills[SK_ICE_MAGIC]; break; case STAFF_COLD: if (you.skills[SK_ICE_MAGIC] > you.skills[SK_FIRE_MAGIC]) total_skill += you.skills[SK_ICE_MAGIC]; else total_skill += you.skills[SK_FIRE_MAGIC]; break; case STAFF_AIR: if (you.skills[SK_AIR_MAGIC] > you.skills[SK_EARTH_MAGIC]) total_skill += you.skills[SK_AIR_MAGIC]; else total_skill += you.skills[SK_EARTH_MAGIC]; break; case STAFF_EARTH: if (you.skills[SK_EARTH_MAGIC] > you.skills[SK_AIR_MAGIC]) total_skill += you.skills[SK_EARTH_MAGIC]; else total_skill += you.skills[SK_AIR_MAGIC]; break; case STAFF_POISON: total_skill += you.skills[SK_POISON_MAGIC]; break; case STAFF_DEATH: total_skill += you.skills[SK_NECROMANCY]; break; case STAFF_CONJURATION: total_skill += you.skills[SK_CONJURATIONS]; break; case STAFF_ENCHANTMENT: total_skill += you.skills[SK_ENCHANTMENTS]; break; case STAFF_SUMMONING: total_skill += you.skills[SK_SUMMONINGS]; break; } if (random2(100) < total_skill) { set_ident_flags( you.inv[you.equip[EQ_WEAPON]], ISFLAG_IDENT_MASK ); char str_pass[ ITEMNAME_SIZE ]; in_name(you.equip[EQ_WEAPON], DESC_NOCAP_A, str_pass); snprintf( info, INFO_SIZE, "You are wielding %s.", str_pass ); mpr(info); more(); you.wield_change = true; } } // Check to see if an upset god wants to do something to the player // jmf: moved huge thing to religion.cc handle_god_time(); // If the player has the lost mutation forget portions of the map if (you.mutation[MUT_LOST]) { if (random2(100) <= you.mutation[MUT_LOST] * 5) forget_map(5 + random2(you.mutation[MUT_LOST] * 10)); } // Update all of the corpses and food chunks on the floor update_corpses(time_delta); // Update all of the corpses and food chunks in the player's // inventory {should be moved elsewhere - dlb} for (i = 0; i < ENDOFPACK; i++) { if (you.inv[i].quantity < 1) continue; if (you.inv[i].base_type != OBJ_CORPSES && you.inv[i].base_type != OBJ_FOOD) continue; if (you.inv[i].base_type == OBJ_CORPSES && you.inv[i].sub_type > CORPSE_SKELETON) { continue; } if (you.inv[i].base_type == OBJ_FOOD && you.inv[i].sub_type != FOOD_CHUNK) continue; if ((time_delta / 20) >= you.inv[i].special) { if (you.inv[i].base_type == OBJ_FOOD) { if (you.equip[EQ_WEAPON] == i) { unwield_item(you.equip[EQ_WEAPON]); you.equip[EQ_WEAPON] = -1; you.wield_change = true; } mpr( "Your equipment suddenly weighs less.", MSGCH_ROTTEN_MEAT ); you.inv[i].quantity = 0; burden_change(); continue; } if (you.inv[i].sub_type == CORPSE_SKELETON) continue; // carried skeletons are not destroyed if (!mons_skeleton( you.inv[i].plus )) { if (you.equip[EQ_WEAPON] == i) { unwield_item(you.equip[EQ_WEAPON]); you.equip[EQ_WEAPON] = -1; } you.inv[i].quantity = 0; burden_change(); continue; } you.inv[i].sub_type = 1; you.inv[i].special = 0; you.inv[i].colour = LIGHTGREY; you.wield_change = true; continue; } you.inv[i].special -= (time_delta / 20); if (you.inv[i].special < 100 && (you.inv[i].special + (time_delta / 20)>=100)) { new_rotting_item = true; } } //mv: messages when chunks/corpses become rotten if (new_rotting_item) { switch (you.species) { // XXX: should probably still notice? case SP_MUMMY: // no smell case SP_TROLL: // stupid, living in mess - doesn't care about it break; case SP_GHOUL: //likes it temp_rand = random2(8); mpr( ((temp_rand < 5) ? "You smell something rotten." : (temp_rand == 5) ? "Smell of rotting flesh makes you more hungry." : (temp_rand == 6) ? "You smell decay. Yum-yum." : "Wow! There is something tasty in your inventory."), MSGCH_ROTTEN_MEAT ); break; case SP_KOBOLD: //mv: IMO these race aren't so "touchy" case SP_OGRE: case SP_MINOTAUR: case SP_HILL_ORC: temp_rand = random2(8); mpr( ((temp_rand < 5) ? "You smell something rotten." : (temp_rand == 5) ? "You smell rotting flesh." : (temp_rand == 6) ? "You smell decay." : "There is something rotten in your inventory."), MSGCH_ROTTEN_MEAT ); break; default: temp_rand = random2(8); mpr( ((temp_rand < 5) ? "You smell something rotten." : (temp_rand == 5) ? "Smell of rotting flesh makes you sick." : (temp_rand == 6) ? "You smell decay. Yuk..." : "Ugh! There is something really disgusting in your inventory."), MSGCH_ROTTEN_MEAT ); break; } } // exercise armour *xor* stealth skill: {dlb} if (!player_light_armour()) { if (random2(1000) <= item_mass( you.inv[you.equip[EQ_BODY_ARMOUR]] )) { return; } if (one_chance_in(6)) // lowered random roll from 7 to 6 -- bwross exercise(SK_ARMOUR, 1); } else // exercise stealth skill: { if (you.burden_state != BS_UNENCUMBERED || you.berserker) return; if (you.special_wield == SPWLD_SHADOW) return; if (you.equip[EQ_BODY_ARMOUR] != -1 && random2( item_mass( you.inv[you.equip[EQ_BODY_ARMOUR]] )) >= 100) { return; } if (one_chance_in(18)) exercise(SK_STEALTH, 1); } return; } // end handle_time() int autopickup_on = 1; static void autoinscribe_item( item_def& item ) { char name[ITEMNAME_SIZE]; item_name(item, DESC_INVENTORY, name, false); std::string iname = name; /* if there's an inscription already do nothing */ if ( item.inscription.size() > 0 ) return; for ( unsigned i = 0; i < Options.autoinscriptions.size(); ++i ) { if ( Options.autoinscriptions[i].first.matches(iname) ) { item.inscription += Options.autoinscriptions[i].second; } } } static bool is_banned(const item_def &item) { static char name[ITEMNAME_SIZE]; item_name(item, DESC_INVENTORY, name, false); std::string iname = name; for (unsigned i = 0; i < Options.banned_objects.size(); ++i) { if (Options.banned_objects[i].matches(iname)) return true; } return false; } static void autoinscribe_items() { int o, next; o = igrd[you.x_pos][you.y_pos]; while (o != NON_ITEM) { next = mitm[o].link; autoinscribe_item( mitm[o] ); o = next; } } static void autopickup(void) { //David Loewenstern 6/99 int result, o, next; bool did_pickup = false; if (autopickup_on == 0 || Options.autopickups == 0L) return; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR && you.duration[DUR_TRANSFORMATION] > 0) { return; } if (player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT)) return; if ( Options.safe_autopickup && !i_feel_safe() ) return; o = igrd[you.x_pos][you.y_pos]; while (o != NON_ITEM) { next = mitm[o].link; if ( (strstr(mitm[o].inscription.c_str(), "=g") != 0) || ( ((mitm[o].flags & ISFLAG_THROWN) && Options.pickup_thrown) || ( (Options.autopickups & (1L << mitm[o].base_type) #ifdef CLUA_BINDINGS || clua.callbooleanfn(false, "ch_autopickup", "u", &mitm[o]) #endif ) && (Options.pickup_dropped || !(mitm[o].flags & ISFLAG_DROPPED)) && !is_banned(mitm[o])))) { mitm[o].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); result = move_item_to_player( o, mitm[o].quantity); if (result == 0) { mpr("You can't carry any more."); break; } else if (result == -1) { mpr("Your pack is full."); break; } did_pickup = true; } o = next; } // move_item_to_player should leave things linked. -- bwr // relink_cell(you.x_pos, you.y_pos); if (did_pickup) { you.turn_is_over = 1; start_delay( DELAY_AUTOPICKUP, 1 ); } } int inv_count(void) { int count=0; for(int i=0; i< ENDOFPACK; i++) { if (is_valid_item( you.inv[i] )) count += 1; } return count; } #ifdef ALLOW_DESTROY_ITEM_COMMAND // Started with code from AX-crawl, although its modified to fix some // serious problems. -- bwr // // Issues to watch for here: // - no destroying things from the ground since that includes corpses // which might be animated by monsters (butchering takes a few turns). // This code provides a quicker way to get rid of a corpse, but // the player has to be able to lift it first... something that was // a valid preventative method before (although this allow the player // to get rid of the mass on the next action). // // - artefacts can be destroyed // // - equipment cannot be destroyed... not only is this the more accurate // than testing for curse status (to prevent easy removal of cursed items), // but the original code would leave all the equiped items properties // (including weight) which would cause a bit of a mess to state. // // - no item does anything for just carrying it... if that changes then // this code will have to deal with that. // // - Do we want the player to be able to remove items from the game? // This would make things considerably easier to keep weapons (esp // those of distortion) from falling into the hands of monsters. // Right now the player has to carry them to a safe area, or otherwise // ingeniously dispose of them... do we care about this gameplay aspect? // // - Prompt for number to destroy? // void cmd_destroy_item( void ) { int i; char str_pass[ ITEMNAME_SIZE ]; // ask the item to destroy int item = prompt_invent_item( "Destroy which item? ", -1, true, false ); if (item == PROMPT_ABORT) return; // Used to check for cursed... but that's not the real problem -- bwr for (i = 0; i < NUM_EQUIP; i++) { if (you.equip[i] == item) { mesclr( true ); mpr( "You cannot destroy equipped items!" ); return; } } // ask confirmation // quant_name(you.inv[item], you.inv[item].quantity, DESC_NOCAP_A, str_pass ); item_name( you.inv[item], DESC_NOCAP_THE, str_pass ); snprintf( info, INFO_SIZE, "Destroy %s? ", str_pass ); if (yesno( info, true )) { //destroy it!! snprintf( info, INFO_SIZE, "You destroy %s.", str_pass ); mpr( info ); dec_inv_item_quantity( item, you.inv[item].quantity ); burden_change(); } } #endif