diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-09-18 15:08:25 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-09-18 15:08:25 +0000 |
commit | a4d4f3ecccb29c3f5fc1ce55579119106c399911 (patch) | |
tree | 5677ea04b4dfdadd961c01ba3baf7502f8d6e0d0 /stone_soup/crawl-ref/source/items.cc | |
parent | 571501e1135989d3b9dc44e3d332562a7cf78b35 (diff) | |
download | crawl-ref-a4d4f3ecccb29c3f5fc1ce55579119106c399911.tar.gz crawl-ref-a4d4f3ecccb29c3f5fc1ce55579119106c399911.zip |
Updated stone_soup-0.1b1 tag to include fix for Poison Arrow of Doom.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/tags/stone_soup-0.1b1@49 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'stone_soup/crawl-ref/source/items.cc')
-rw-r--r-- | stone_soup/crawl-ref/source/items.cc | 3070 |
1 files changed, 3070 insertions, 0 deletions
diff --git a/stone_soup/crawl-ref/source/items.cc b/stone_soup/crawl-ref/source/items.cc new file mode 100644 index 0000000000..7c7a822343 --- /dev/null +++ b/stone_soup/crawl-ref/source/items.cc @@ -0,0 +1,3070 @@ +/* + * File: items.cc + * Summary: Misc (mostly) inventory related functions. + * Written by: Linley Henzell + * + * 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 <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#ifdef DOS +#include <conio.h> +#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 ); + +// 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 x = find_unrandart_index(item); + if (x >= 0) + set_unrandart_exist(x, 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; +} + +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; + } +} + + +/* + * 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; + + 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; + } + } + } + + if (igrd[you.x_pos][you.y_pos] == NON_ITEM && keyin == ';') + { + mpr("There are no items here."); + return; + } + + autopickup(); + + origin_set(you.x_pos, you.y_pos); + + int objl = igrd[you.x_pos][you.y_pos]; + + while (objl != NON_ITEM) + { + 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); + } + + objl = mitm[objl].link; + } + + 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 pickup_menu(int item_link) +{ + std::vector<item_def*> items; + + for (int i = item_link; i != NON_ITEM; i = mitm[i].link) + items.push_back( &mitm[i] ); + + std::vector<SelItem> 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<short>( 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) +{ + std::string place = branch_level_name(item.orig_place); + if (place.length() && place != "Pandemonium") + place[0] = tolower(place[0]); + return (place.find("level") == 0? + "on " + place + : "in " + 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? + { + 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 (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; + + 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) +{ + 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) + { + 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; + } + + 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 ); + } + + if (!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); + + 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<MenuEntry*> *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<item_def *>( (*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<SelItem> 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<GXM; cx++) + { + for (cy=0; cy<GYM; cy++) + { + if (grd[cx][cy] > DNGN_SPARKLING_FOUNTAIN + && grd[cx][cy] < DNGN_PERMADRY_FOUNTAIN) + { + for (int i=0; i<fountain_checks; i++) + { + if (one_chance_in(100)) + { + if (grd[cx][cy] > 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 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 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; + + o = igrd[you.x_pos][you.y_pos]; + + while (o != NON_ITEM) + { + next = mitm[o].link; + + if ( ((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 |