diff options
-rw-r--r-- | crawl-ref/source/dat/database/monspeak.txt | 2 | ||||
-rw-r--r-- | crawl-ref/source/debug.cc | 14 | ||||
-rw-r--r-- | crawl-ref/source/describe.cc | 20 | ||||
-rw-r--r-- | crawl-ref/source/effects.cc | 107 | ||||
-rw-r--r-- | crawl-ref/source/item_use.cc | 17 | ||||
-rw-r--r-- | crawl-ref/source/items.cc | 54 | ||||
-rw-r--r-- | crawl-ref/source/libgui.cc | 19 | ||||
-rw-r--r-- | crawl-ref/source/makeitem.cc | 9 | ||||
-rw-r--r-- | crawl-ref/source/misc.cc | 606 | ||||
-rw-r--r-- | crawl-ref/source/misc.h | 11 | ||||
-rw-r--r-- | crawl-ref/source/newgame.cc | 5 | ||||
-rw-r--r-- | crawl-ref/source/player.cc | 4 | ||||
-rw-r--r-- | crawl-ref/source/spells4.cc | 11 |
13 files changed, 664 insertions, 215 deletions
diff --git a/crawl-ref/source/dat/database/monspeak.txt b/crawl-ref/source/dat/database/monspeak.txt index b67de8e3c9..6680635431 100644 --- a/crawl-ref/source/dat/database/monspeak.txt +++ b/crawl-ref/source/dat/database/monspeak.txt @@ -2241,7 +2241,7 @@ orc sorcerer @_wizard_@ w:1 -@_generic_orc_@ +@_generic_orc_speech@ w:1 __NONE diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index b685550733..0f0509b730 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -1244,12 +1244,14 @@ void create_spec_object() break; case OBJ_POTIONS: - if (mitm[thing_created].sub_type == POT_BLOOD) - mitm[thing_created].special = 1200; - else if (mitm[thing_created].sub_type == POT_BLOOD_COAGULATED) - mitm[thing_created].special = 200; - // intentional fall-through - + mitm[thing_created].quantity = 12; + if (mitm[thing_created].sub_type == POT_BLOOD + || mitm[thing_created].sub_type == POT_BLOOD_COAGULATED) + { + init_stack_blood_potions(mitm[thing_created]); + } + break; + case OBJ_FOOD: case OBJ_SCROLLS: mitm[thing_created].quantity = 12; diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 0c2bbdfc24..2f487034f4 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -1645,8 +1645,26 @@ std::string get_item_description( const item_def &item, bool verbose, description << "$This book is beyond your current level of understanding."; break; - case OBJ_SCROLLS: case OBJ_POTIONS: +#ifdef DEBUG_BLOOD_POTIONS + // list content of timer vector for blood potions + if (item.sub_type == POT_BLOOD + || item.sub_type == POT_BLOOD_COAGULATED) + { + item_def stack = static_cast<item_def>(item); + CrawlHashTable &props = stack.props; + ASSERT(props.exists("timer")); + CrawlVector &timer = props["timer"].get_vector(); + ASSERT(!timer.empty()); + + description << "$Quantity: " << stack.quantity + << " Timer size: " << (int) timer.size(); + description << "$Timers:$"; + for (int i = 0; i < timer.size(); i++) + description << (timer[i].get_long()) << " "; + } +#endif + case OBJ_SCROLLS: case OBJ_ORBS: case OBJ_CORPSES: case OBJ_GOLD: diff --git a/crawl-ref/source/effects.cc b/crawl-ref/source/effects.cc index e050246104..d77f3c5a5d 100644 --- a/crawl-ref/source/effects.cc +++ b/crawl-ref/source/effects.cc @@ -1547,12 +1547,11 @@ bool acquirement(object_class_type class_wanted, int agent, else if (quant > 1) thing.quantity = quant; - if (thing.base_type == OBJ_POTIONS) + if (thing.base_type == OBJ_POTIONS + && (thing.sub_type == POT_BLOOD + || thing.sub_type == POT_BLOOD_COAGULATED)) { - if (thing.sub_type == POT_BLOOD) - thing.special = 1200; - else if (thing.sub_type == POT_BLOOD_COAGULATED) - thing.special = 200; + init_stack_blood_potions(thing); } // remove curse flag from item @@ -2213,9 +2212,6 @@ static void rot_inventory_food(long time_delta) // Update all of the corpses and food chunks in the player's // inventory {should be moved elsewhere - dlb} bool burden_changed_by_rot = false; - int num_total_blood = 0, num_blood_coagulates = 0; - int affected_potion = -1; // stack of coagulating blood potions - // (only one possible at a time) std::vector<char> rotten_items; for (int i = 0; i < ENDOFPACK; i++) { @@ -2225,11 +2221,18 @@ static void rot_inventory_food(long time_delta) if (!food_item_needs_time_check(you.inv[i])) continue; + if (you.inv[i].base_type == OBJ_POTIONS) + { + // also handles messaging + if (maybe_coagulate_blood_potions_inv(you.inv[i])) + burden_changed_by_rot = true; + continue; + } + // food item timed out -> make it disappear if ((time_delta / 20) >= you.inv[i].special) { - if (you.inv[i].base_type == OBJ_FOOD - || you.inv[i].base_type == OBJ_POTIONS) + if (you.inv[i].base_type == OBJ_FOOD) { if (you.equip[EQ_WEAPON] == i) unwield_item(); @@ -2263,18 +2266,8 @@ static void rot_inventory_food(long time_delta) // if it hasn't disappeared, reduce the rotting timer you.inv[i].special -= (time_delta / 20); - if (you.inv[i].base_type == OBJ_POTIONS - && you.inv[i].sub_type == POT_BLOOD) - { - num_total_blood += you.inv[i].quantity; - if (you.inv[i].special < 200) - { - num_blood_coagulates += you.inv[i].quantity; - affected_potion = i; - } - } - else if (food_is_rotten(you.inv[i]) - && (you.inv[i].special + (time_delta / 20) >= 100 )) + if (food_is_rotten(you.inv[i]) + && (you.inv[i].special + (time_delta / 20) >= 100 )) { rotten_items.push_back(index_to_letter( i )); } @@ -2339,50 +2332,6 @@ static void rot_inventory_food(long time_delta) learned_something_new(TUT_ROTTEN_FOOD); } - if (num_blood_coagulates) - { - ASSERT(affected_potion != -1); - - std::string msg = ""; - - if (num_total_blood == num_blood_coagulates) - msg += you.inv[affected_potion].name(DESC_CAP_YOUR, false); -/* - // this is for later, when part of a stack can coagulate - else - { - if (congealed_blood_num == 1) - msg += "One of "; - else - msg += "Some of "; - - msg += you.inv[affected_potion].name(DESC_NOCAP_YOUR, false); - } -*/ - msg += " coagulate"; - if (num_blood_coagulates == 1) - msg += "s"; - msg += "."; - - mpr(msg.c_str(), MSGCH_ROTTEN_MEAT); - - const bool known_blood = item_type_known(you.inv[affected_potion]); - you.inv[affected_potion].sub_type = POT_BLOOD_COAGULATED; - you.inv[affected_potion].plus - = you.item_description[IDESC_POTIONS][POT_BLOOD_COAGULATED]; - const bool known_coag_blood = item_type_known(you.inv[affected_potion]); - - // identify both blood and coagulated blood, if necessary - if (!known_blood) - set_ident_type( OBJ_POTIONS, POT_BLOOD, ID_KNOWN_TYPE ); - if (!known_coag_blood) - { - set_ident_flags( you.inv[affected_potion], ISFLAG_IDENT_MASK ); - set_ident_type( OBJ_POTIONS, POT_BLOOD_COAGULATED, ID_KNOWN_TYPE ); - mpr(you.inv[affected_potion].name(DESC_INVENTORY, false).c_str()); - } - } - if (burden_changed_by_rot) { mpr("Your equipment suddenly weighs less.", MSGCH_ROTTEN_MEAT); @@ -2879,10 +2828,9 @@ static void _maybe_restart_fountain_flow(const int x, const int y, const int tries) { dungeon_feature_type grid = grd[x][y]; + if (grid < DNGN_DRY_FOUNTAIN_BLUE || grid > DNGN_DRY_FOUNTAIN_BLOOD) - { return; - } int t = 0; while (tries > t++) @@ -2941,9 +2889,15 @@ void update_corpses(double elapsedTime) if (!food_item_needs_time_check(it)) continue; + if (it.base_type == OBJ_POTIONS) + { + maybe_coagulate_blood_potions_floor(c); + continue; + } + if (rot_time >= it.special && !is_being_butchered(it)) { - if (it.base_type == OBJ_FOOD || it.base_type == OBJ_POTIONS) + if (it.base_type == OBJ_FOOD) { destroy_item(c); } @@ -2964,22 +2918,7 @@ void update_corpses(double elapsedTime) } else { - // potions of blood have a longer rot time - if (it.base_type != OBJ_POTIONS) - ASSERT(rot_time < 256); - it.special -= rot_time; - - if (it.base_type == OBJ_POTIONS - && it.sub_type == POT_BLOOD - && it.special < 200) - { - ASSERT(rot_time < 1200); - it.sub_type = POT_BLOOD_COAGULATED; - - it.plus - = you.item_description[IDESC_POTIONS][POT_BLOOD_COAGULATED]; - } } } diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc index 482291666d..ed787ab125 100644 --- a/crawl-ref/source/item_use.cc +++ b/crawl-ref/source/item_use.cc @@ -1755,6 +1755,17 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, item.quantity = 1; item.slot = index_to_letter(item.link); origin_set_unknown(item); + if (item.base_type == OBJ_POTIONS + && (item.sub_type == POT_BLOOD + || item.sub_type == POT_BLOOD_COAGULATED) + && you.inv[throw_2].quantity > 1) + { + // initialize thrown potion with oldest potion in stack + long val = remove_oldest_blood_potion(you.inv[throw_2]); + val -= you.num_turns; + item.props.clear(); + init_stack_blood_potions(item, val); + } if (you.duration[DUR_CONF]) { @@ -3497,6 +3508,12 @@ void drink( int slot ) xom_is_stimulated(255); } + if (you.inv[item_slot].sub_type == POT_BLOOD + || you.inv[item_slot].sub_type == POT_BLOOD_COAGULATED) + { + // always drink oldest potion + remove_oldest_blood_potion(you.inv[item_slot]); + } dec_inv_item_quantity( item_slot, 1 ); you.turn_is_over = true; diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index f7aa0a111b..06ccc9d944 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -1530,10 +1530,7 @@ int move_item_to_player( int obj, int quant_got, bool quiet ) && (mitm[obj].sub_type == POT_BLOOD || mitm[obj].sub_type == POT_BLOOD_COAGULATED)) { - // use average age - int age = you.inv[m].special * you.inv[m].quantity - + mitm[obj].special * quant_got; - you.inv[m].special = age / (you.inv[m].quantity + quant_got); + pick_up_blood_potions_stack(mitm[obj], quant_got); } inc_inv_item_quantity( m, quant_got ); dec_mitm_item_quantity( obj, quant_got ); @@ -1586,20 +1583,34 @@ int move_item_to_player( int obj, int quant_got, bool quiet ) check_note_item(item); item.quantity = quant_got; + if (mitm[obj].base_type == OBJ_POTIONS + && (mitm[obj].sub_type == POT_BLOOD + || mitm[obj].sub_type == POT_BLOOD_COAGULATED)) + { + if (quant_got != mitm[obj].quantity) + { + // remove oldest timers from original stack + for (int i = 0; i < quant_got; i++) + remove_oldest_blood_potion(mitm[obj]); + + // ... and newest ones from picked up stack + remove_newest_blood_potion(item); + } + } dec_mitm_item_quantity( obj, quant_got ); you.m_quiver->on_inv_quantity_changed(freeslot, quant_got); burden_change(); if (!quiet) mpr( you.inv[freeslot].name(DESC_INVENTORY).c_str() ); - + if (Options.tutorial_left) { taken_new_item(item.base_type); if (is_artefact(item) || get_equip_desc( item ) != ISFLAG_NO_DESC) learned_something_new(TUT_SEEN_RANDART); } - + if (item.base_type == OBJ_ORBS && you.char_direction == GDT_DESCENDING) { @@ -1737,17 +1748,16 @@ bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos, { if (items_stack( item, mitm[i] )) { + inc_mitm_item_quantity( i, quant_drop ); + if (item.base_type == OBJ_POTIONS && (item.sub_type == POT_BLOOD || item.sub_type == POT_BLOOD_COAGULATED)) { - // calculate average age - int age = mitm[i].special * mitm[i].quantity - + item.special * quant_drop; - mitm[i].special = age / (mitm[i].quantity + quant_drop); + item_def help = item; + drop_blood_potions_stack(help, quant_drop, x_plos, y_plos); } - 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) @@ -1780,6 +1790,15 @@ bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos, } move_item_to_grid( &new_item, x_plos, y_plos ); + if (item.base_type == OBJ_POTIONS + && (item.sub_type == POT_BLOOD + || item.sub_type == POT_BLOOD_COAGULATED) + && item.quantity != quant_drop) // partial drop only + { + // since only the oldest potions have been dropped, + // remove the newest ones + remove_newest_blood_potion(mitm[new_item]); + } return (true); } // end copy_item_to_grid() @@ -1893,7 +1912,16 @@ bool drop_item( int item_dropped, int quant_drop, bool try_offer ) } else if (strstr(you.inv[item_dropped].inscription.c_str(), "=s") != 0) StashTrack.add_stash(); - + + if (you.inv[item_dropped].base_type == OBJ_POTIONS + && (you.inv[item_dropped].sub_type == POT_BLOOD + || you.inv[item_dropped].sub_type == POT_BLOOD_COAGULATED) + && you.inv[item_dropped].quantity != quant_drop) + { + // oldest potions have been dropped + for (int i = 0; i < quant_drop; i++) + remove_oldest_blood_potion(you.inv[item_dropped]); + } dec_inv_item_quantity( item_dropped, quant_drop ); you.turn_is_over = true; diff --git a/crawl-ref/source/libgui.cc b/crawl-ref/source/libgui.cc index 73a83f25be..6b005cb167 100644 --- a/crawl-ref/source/libgui.cc +++ b/crawl-ref/source/libgui.cc @@ -29,6 +29,7 @@ #include "externs.h" #include "guic.h" #include "message.h" +#include "misc.h" #include "mon-util.h" #include "newgame.h" #include "player.h" @@ -367,19 +368,19 @@ void GmapUpdate(int x, int y, int what, bool upd_tile) c = _gmap_to_colour(gmap_col[what & 0xff]); break; } - if (c == Options.tile_monster_col && mgrd[x][y] != NON_MONSTER - && upd_tile) + + if (c == Options.tile_monster_col && mgrd[x][y] != NON_MONSTER) { const int grid = mgrd[x][y]; - if (mons_friendly(&menv[grid])) + if (mons_friendly(&menv[grid]) && upd_tile) c = Options.tile_friendly_col; // colour friendly monsters - else if (mons_neutral(&menv[grid]) + else if (mons_neutral(&menv[grid]) && upd_tile && Options.tile_neutral_col != Options.tile_monster_col) { c = Options.tile_neutral_col; // colour neutral monsters } else if (mons_class_flag( menv[grid].type, M_NO_EXP_GAIN )) - c = Options.tile_plant_col; + c = Options.tile_plant_col; // colour zero xp monsters } if (c == Options.tile_floor_col || c == Options.tile_item_col) @@ -1345,8 +1346,12 @@ static int _handle_mouse_motion(int mouse_x, int mouse_y, bool init) && item.sub_type != CORPSE_SKELETON && !food_is_rotten(item)) { - // TODO: Differentiate bottle/butcher for vampires. - desc += EOL "[Shift-L-Click] Chop up (c)"; + desc += EOL "[Shift-L-Click] "; + if (can_bottle_blood_from_corpse( item.plus)) + desc += "Bottle blood"; + else + desc += "Chop up"; + desc += " (c)"; if (you.species == SP_VAMPIRE) desc += EOL "[Shift-R-Click] Drink blood (e)"; diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index 586f9b3aa3..efb4afbe4f 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -19,6 +19,7 @@ #include "itemname.h" #include "itemprop.h" #include "items.h" +#include "misc.h" #include "mon-util.h" #include "player.h" #include "randart.h" @@ -2313,11 +2314,9 @@ static void generate_potion_item(item_def& item, int force_type, int item_level) } item.sub_type = stype; } - - if (item.sub_type == POT_BLOOD) - item.special = 1200; - else - item.special = 0; + + if (item.sub_type == POT_BLOOD || item.sub_type == POT_BLOOD_COAGULATED) + init_stack_blood_potions(item); } static void generate_scroll_item(item_def& item, int force_type, diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index bd124f5d9d..2a64821b6d 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -163,18 +163,32 @@ void turn_corpse_into_chunks( item_def &item ) void init_stack_blood_potions(item_def &stack, int age) { ASSERT(stack.base_type == OBJ_POTIONS); - ASSERT(stack.sub_type == POT_BLOOD); + ASSERT(stack.sub_type == POT_BLOOD + || stack.sub_type == POT_BLOOD_COAGULATED); CrawlHashTable &props = stack.props; + props.clear(); // sanity measure props.set_default_flags(SFLAG_CONST_TYPE); props["timer"].new_vector(SV_LONG); - CrawlVector &timer = props["timer"]; + CrawlVector &timer = props["timer"].get_vector(); + if (age == -1) + { + if (stack.sub_type == POT_BLOOD) + age = 2000; + else // coagulated blood + age = 500; + } // for a newly created stack, all potions use the same timer - const long max_age = you.num_turns + (age == -1 ? 1200 : age); + const long max_age = you.num_turns + age; +#ifdef DEBUG_BLOOD_POTIONS + mprf(MSGCH_DIAGNOSTICS, "newly created stack will time out at turn %d", + max_age); +#endif for (int i = 0; i < stack.quantity; i++) timer.push_back(max_age); + stack.special = 0; ASSERT(timer.size() == stack.quantity); props.assert_validity(); } @@ -184,21 +198,23 @@ static void _long_sort(CrawlVector &vec) { std::vector<long> help; while (!vec.empty()) - help.push_back(vec.pop_back()); + { + help.push_back(vec[vec.size()-1].get_long()); + vec.pop_back(); + } std::sort(help.begin(), help.end()); - long val; while (!help.empty()) { - val = help[help.size() - 1]; + vec.push_back(help[help.size()-1]); help.pop_back(); - vec.push_back(val); } } -void maybe_coagulate_blood_floor(item_def &blood) +void maybe_coagulate_blood_potions_floor(int obj) { + item_def &blood = mitm[obj]; ASSERT(is_valid_item(blood)); ASSERT(blood.base_type == OBJ_POTIONS); @@ -206,46 +222,61 @@ void maybe_coagulate_blood_floor(item_def &blood) || blood.sub_type == POT_BLOOD_COAGULATED); CrawlHashTable &props = blood.props; + if (!props.exists("timer")) + init_stack_blood_potions(blood, blood.special); + ASSERT(props.exists("timer")); - CrawlVector &timer = props["timer"]; - ASSERT(timer.size() == blood.quantity); + CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); + ASSERT(timer.size() == blood.quantity); // blood.sub_type could be POT_BLOOD or POT_BLOOD_COAGULATED // -> need different handling int rot_limit = you.num_turns; - int coag_limit = you.num_turns + 200; // check 200 turns later + int coag_limit = you.num_turns + 500; // check 500 turns later // first count whether coagulating is even necessary int rot_count = 0; int coag_count = 0; std::vector<long> age_timer; long current; - const int size = timer.size(); - for (int i = 0; i < size; i++) + while (!timer.empty()) { - current = timer.pop_back(); - if (rot_limit >= current) + current = timer[timer.size()-1].get_long(); + if (current > coag_limit + || blood.sub_type == POT_BLOOD_COAGULATED && current > rot_limit) + { + // still some time until rotting/coagulating + break; + } + + timer.pop_back(); + if (current <= rot_limit) rot_count++; - else if (blood.sub_type == POT_BLOOD && coag_limit >= current) + else if (blood.sub_type == POT_BLOOD && current <= coag_limit) { coag_count++; age_timer.push_back(current); } - else // still some time until rotting/coagulating - { - timer.push_back(current); - _long_sort(timer); - break; - } } if (!rot_count && !coag_count) return; // nothing to be done +#ifdef DEBUG_BLOOD_POTIONS + mprf(MSGCH_DIAGNOSTICS, "in maybe_coagulate_blood_potions_FLOOR " + "(turns: %d)", you.num_turns); + + mprf(MSGCH_DIAGNOSTICS, "Something happened at pos (%d, %d)!", + blood.x, blood.y); + mprf(MSGCH_DIAGNOSTICS, "coagulated: %d, rotted: %d, total: %d", + coag_count, rot_count, blood.quantity); + more(); +#endif + if (!coag_count) // some potions rotted away { - dec_mitm_item_quantity(blood.link, rot_count); + dec_mitm_item_quantity(obj, rot_count); // timer is already up to date return; } @@ -254,16 +285,19 @@ void maybe_coagulate_blood_floor(item_def &blood) ASSERT(blood.sub_type == POT_BLOOD); // now that coagulating is necessary, check square for !coagulated blood - int o = igrd[blood.x][blood.y]; - while (o != NON_ITEM) + ASSERT(blood.x >= 0 && blood.y >= 0); + for (int o = igrd[blood.x][blood.y]; o != NON_ITEM; o = mitm[o].link) { if (mitm[o].base_type == OBJ_POTIONS && mitm[o].sub_type == POT_BLOOD_COAGULATED) { // merge with existing stack CrawlHashTable &props2 = mitm[o].props; + if (!props2.exists("timer")) + init_stack_blood_potions(mitm[o], mitm[o].special); + ASSERT(props2.exists("timer")); - CrawlVector &timer2 = props2["timer"]; + CrawlVector &timer2 = props2["timer"].get_vector(); ASSERT(timer2.size() == mitm[o].quantity); // update timer -> push(pop) @@ -277,10 +311,9 @@ void maybe_coagulate_blood_floor(item_def &blood) _long_sort(timer2); inc_mitm_item_quantity(o, coag_count); ASSERT(timer2.size() == mitm[o].quantity); - dec_mitm_item_quantity(blood.link, rot_count + coag_count); + dec_mitm_item_quantity(obj, rot_count + coag_count); return; } - o = mitm[o].link; } // If we got here nothing was found! @@ -289,9 +322,11 @@ void maybe_coagulate_blood_floor(item_def &blood) if (rot_count + coag_count == blood.quantity) { ASSERT(timer.empty()); + // update subtype blood.sub_type = POT_BLOOD_COAGULATED; item_colour(blood); + // re-fill vector long val; while (!age_timer.empty()) @@ -300,14 +335,322 @@ void maybe_coagulate_blood_floor(item_def &blood) age_timer.pop_back(); timer.push_back(val); } - dec_mitm_item_quantity(blood.link, rot_count); + dec_mitm_item_quantity(obj, rot_count); ASSERT(timer.size() == blood.quantity); + return; + } + + // else, create a new stack of potions + int o = get_item_slot( 20 ); + if (o == NON_ITEM) + return; + + item_def &item = mitm[o]; + item.base_type = OBJ_POTIONS; + item.sub_type = POT_BLOOD_COAGULATED; + item.quantity = coag_count; + item.plus = 0; + item.plus2 = 0; + item.special = 0; + item.flags = 0; + item_colour(item); + + CrawlHashTable &props_new = item.props; + props_new.set_default_flags(SFLAG_CONST_TYPE); + props_new["timer"].new_vector(SV_LONG); + CrawlVector &timer_new = props_new["timer"].get_vector(); + + long val; + while (!age_timer.empty()) + { + val = age_timer[age_timer.size() - 1]; + age_timer.pop_back(); + timer_new.push_back(val); + } + + ASSERT(timer_new.size() == coag_count); + props_new.assert_validity(); + move_item_to_grid( &o, blood.x, blood.y ); + + dec_mitm_item_quantity(obj, rot_count + coag_count); + ASSERT(timer.size() == blood.quantity); +} + +static void _coagulating_blood_message(item_def &blood, int num_coagulated) +{ + ASSERT(num_coagulated > 0); + + std::string msg; + if (blood.quantity == num_coagulated) + msg = blood.name(DESC_CAP_YOUR, false); + else + { + if (num_coagulated == 1) + msg = "One of "; + else + msg = "Some of "; + + msg += blood.name(DESC_NOCAP_YOUR, false); } + msg += " coagulate"; + if (num_coagulated == 1) + msg += "s"; + msg += "."; + + mpr(msg.c_str(), MSGCH_ROTTEN_MEAT); +} + +// returns true if "equipment weighs less" message needed +// also handles coagulation messages +bool maybe_coagulate_blood_potions_inv(item_def &blood) +{ + ASSERT(is_valid_item(blood)); + ASSERT(blood.base_type == OBJ_POTIONS); + + ASSERT(blood.sub_type == POT_BLOOD + || blood.sub_type == POT_BLOOD_COAGULATED); + + CrawlHashTable &props = blood.props; + if (!props.exists("timer")) + init_stack_blood_potions(blood, blood.special); + + ASSERT(props.exists("timer")); + CrawlVector &timer = props["timer"].get_vector(); + ASSERT(timer.size() == blood.quantity); + ASSERT(!timer.empty()); + + // blood.sub_type could be POT_BLOOD or POT_BLOOD_COAGULATED + // -> need different handling + int rot_limit = you.num_turns; + int coag_limit = you.num_turns + 500; // check 500 turns later + + // first count whether coagulating is even necessary + int rot_count = 0; + int coag_count = 0; + std::vector<long> age_timer; + long current; + const int size = timer.size(); + for (int i = 0; i < size; i++) + { + current = timer[timer.size()-1].get_long(); + if (current > coag_limit + || blood.sub_type == POT_BLOOD_COAGULATED && current > rot_limit) + { + // still some time until rotting/coagulating + break; + } + + timer.pop_back(); + if (current <= rot_limit) + rot_count++; + else if (blood.sub_type == POT_BLOOD && current <= coag_limit) + { + coag_count++; + age_timer.push_back(current); + } + } + + if (!rot_count && !coag_count) + return false; // nothing to be done + +#ifdef DEBUG_BLOOD_POTIONS + mprf(MSGCH_DIAGNOSTICS, "in maybe_coagulate_blood_potions_INV " + "(turns: %d)", you.num_turns); + + mprf(MSGCH_DIAGNOSTICS, "coagulated: %d, rotted: %d, total: %d", + coag_count, rot_count, blood.quantity); + more(); +#endif + + if (!coag_count) // some potions rotted away + { + blood.quantity -= rot_count; + if (blood.quantity < 1) + destroy_item(blood); + else + ASSERT(blood.quantity == timer.size()); + + return true; + } + + // coagulated blood cannot coagulate any further... + ASSERT(blood.sub_type == POT_BLOOD); + + bool knew_blood = get_ident_type(OBJ_POTIONS, POT_BLOOD) == ID_KNOWN_TYPE; + bool knew_coag = (get_ident_type(OBJ_POTIONS, POT_BLOOD_COAGULATED) + == ID_KNOWN_TYPE); + + _coagulating_blood_message(blood, coag_count); + + // identify both blood and coagulated blood, if necessary + if (!knew_blood) + set_ident_type( OBJ_POTIONS, POT_BLOOD, ID_KNOWN_TYPE ); + + if (!knew_coag) + set_ident_type( OBJ_POTIONS, POT_BLOOD_COAGULATED, ID_KNOWN_TYPE ); + + // now that coagulating is necessary, check inventory for !coagulated blood + for (int m = 0; m < ENDOFPACK; m++) + { + if (!is_valid_item(you.inv[m])) + continue; + + if (you.inv[m].base_type == OBJ_POTIONS + && you.inv[m].sub_type == POT_BLOOD_COAGULATED) + { + CrawlHashTable &props2 = you.inv[m].props; + if (!props2.exists("timer")) + init_stack_blood_potions(you.inv[m], you.inv[m].special); + + ASSERT(props2.exists("timer")); + CrawlVector &timer2 = props2["timer"].get_vector(); + + blood.quantity -= coag_count + rot_count; + if (blood.quantity < 1) + destroy_item(blood); + else + { + ASSERT(timer.size() == blood.quantity); + if (!knew_blood) + mpr(blood.name(DESC_INVENTORY).c_str()); + } + + // update timer -> push(pop) + long val; + while (!age_timer.empty()) + { + val = age_timer[age_timer.size() - 1]; + age_timer.pop_back(); + timer2.push_back(val); + } + + you.inv[m].quantity += coag_count; + ASSERT(timer2.size() == you.inv[m].quantity); + if (!knew_coag) + mpr(you.inv[m].name(DESC_INVENTORY).c_str()); + + // re-sort timer + _long_sort(timer2); + + return (rot_count > 0); + } + } + + // if entire stack has coagulated, simply change subtype + if (rot_count + coag_count == blood.quantity) + { + ASSERT(timer.empty()); + // update subtype + blood.sub_type = POT_BLOOD_COAGULATED; + item_colour(blood); + + // re-fill vector + long val; + while (!age_timer.empty()) + { + val = age_timer[age_timer.size() - 1]; + age_timer.pop_back(); + timer.push_back(val); + } + blood.quantity -= rot_count; + // stack still exists because of coag_count + ASSERT(timer.size() == blood.quantity); + + if (!knew_coag) + mpr(blood.name(DESC_INVENTORY).c_str()); + + return (rot_count > 0); + } + + // else, create new stack in inventory + int freeslot = find_free_slot(blood); + if (freeslot >= 0 && freeslot < ENDOFPACK + && !is_valid_item(you.inv[freeslot])) + { + item_def &item = you.inv[freeslot]; + item.link = freeslot; + item.slot = index_to_letter(item.link); + item.base_type = OBJ_POTIONS; + item.sub_type = POT_BLOOD_COAGULATED; + item.quantity = coag_count; + item.x = -1; + item.y = -1; + item.plus = 0; + item.plus2 = 0; + item.special = 0; + item.flags = 0; + item_colour(item); + + CrawlHashTable &props_new = item.props; + props_new.set_default_flags(SFLAG_CONST_TYPE); + props_new["timer"].new_vector(SV_LONG); + CrawlVector &timer_new = props_new["timer"].get_vector(); + + long val; + while (!age_timer.empty()) + { + val = age_timer[age_timer.size() - 1]; + age_timer.pop_back(); + timer_new.push_back(val); + } + + ASSERT(timer_new.size() == coag_count); + props_new.assert_validity(); + + blood.quantity -= coag_count + rot_count; + ASSERT(timer.size() == blood.quantity); + + if (!knew_blood) + mpr(blood.name(DESC_INVENTORY).c_str()); + if (!knew_coag) + mpr(item.name(DESC_INVENTORY).c_str()); + + return (rot_count > 0); + } + + // no space in inventory, check floor + int o = igrd[you.x_pos][you.y_pos]; + while (o != NON_ITEM) + { + if (mitm[o].base_type == OBJ_POTIONS + && mitm[o].sub_type == POT_BLOOD_COAGULATED) + { + // merge with existing stack + CrawlHashTable &props2 = mitm[o].props; + if (!props2.exists("timer")) + init_stack_blood_potions(mitm[o], mitm[o].special); + + ASSERT(props2.exists("timer")); + CrawlVector &timer2 = props2["timer"].get_vector(); + ASSERT(timer2.size() == mitm[o].quantity); + + // update timer -> push(pop) + long val; + while (!age_timer.empty()) + { + val = age_timer[age_timer.size() - 1]; + age_timer.pop_back(); + timer2.push_back(val); + } + _long_sort(timer2); + + inc_mitm_item_quantity(o, coag_count); + ASSERT(timer2.size() == mitm[o].quantity); + dec_inv_item_quantity(blood.link, rot_count + coag_count); + ASSERT(timer.size() == blood.quantity); + if (!knew_blood) + mpr(blood.name(DESC_INVENTORY).c_str()); + return true; + } + o = mitm[o].link; + } + // If we got here nothing was found! + // create a new stack of potions o = get_item_slot( 100 + random2(200) ); if (o == NON_ITEM) - return; + return false; // these values are common to all: {dlb} mitm[o].base_type = OBJ_POTIONS; @@ -316,13 +659,13 @@ void maybe_coagulate_blood_floor(item_def &blood) mitm[o].plus = 0; mitm[o].plus2 = 0; mitm[o].special = 0; - mitm[o].flags = 0; + mitm[o].flags = ~(ISFLAG_THROWN | ISFLAG_DROPPED); item_colour(mitm[o]); CrawlHashTable &props_new = mitm[o].props; props_new.set_default_flags(SFLAG_CONST_TYPE); props_new["timer"].new_vector(SV_LONG); - CrawlVector &timer_new = props_new["timer"]; + CrawlVector &timer_new = props_new["timer"].get_vector(); long val; while (!age_timer.empty()) @@ -334,14 +677,22 @@ void maybe_coagulate_blood_floor(item_def &blood) ASSERT(timer_new.size() == coag_count); props_new.assert_validity(); - move_item_to_grid( &o, blood.x, blood.y ); + move_item_to_grid( &o, you.x_pos, you.y_pos ); - dec_mitm_item_quantity(blood.link, rot_count + coag_count); - ASSERT(timer.size() == blood.quantity); + blood.quantity -= rot_count + coag_count; + if (blood.quantity < 1) + destroy_item(blood); + else + { + ASSERT(timer.size() == blood.quantity); + if (!knew_blood) + mpr(blood.name(DESC_INVENTORY).c_str()); + } + return true; } -// used for (q)uaff, (f)ire, and Evaporate -void remove_oldest_potion_inv(item_def &stack) +// mostly used for (q)uaff, (f)ire, and Evaporate +long remove_oldest_blood_potion(item_def &stack) { ASSERT(is_valid_item(stack)); ASSERT(stack.base_type == OBJ_POTIONS); @@ -350,56 +701,99 @@ void remove_oldest_potion_inv(item_def &stack) || stack.sub_type == POT_BLOOD_COAGULATED); CrawlHashTable &props = stack.props; + if (!props.exists("timer")) + init_stack_blood_potions(stack, stack.special); ASSERT(props.exists("timer")); - CrawlVector &timer = props["timer"]; - ASSERT(timer.size() == stack.quantity); + CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); // assuming already sorted, and first (oldest) potion valid + const long val = timer[timer.size() - 1].get_long(); timer.pop_back(); // the quantity will be decreased elsewhere + return val; +} + +// used whenever copies of blood potions have to be cleaned up +void remove_newest_blood_potion(item_def &stack, int quant) +{ + ASSERT(is_valid_item(stack)); + ASSERT(stack.base_type == OBJ_POTIONS); + + ASSERT (stack.sub_type == POT_BLOOD + || stack.sub_type == POT_BLOOD_COAGULATED); + + CrawlHashTable &props = stack.props; + if (!props.exists("timer")) + init_stack_blood_potions(stack, stack.special); + ASSERT(props.exists("timer")); + CrawlVector &timer = props["timer"].get_vector(); + ASSERT(!timer.empty()); + + if (quant == -1) + quant = timer.size() - stack.quantity; + + // overwrite newest potions with oldest ones + int repeats = stack.quantity; + if (repeats > quant) + repeats = quant; + + for (int i = 0; i < repeats; i++) + { + timer[i] = timer[timer.size() - 1]; + timer.pop_back(); + } + + // now remove remaining oldest potions + repeats = quant - repeats; + for (int i = 0; i < repeats; i++) + timer.pop_back(); + + // and re-sort + _long_sort(timer); } -// Should be called *after* drop_thing (and only if this returns true) -// unless the stack has been dropped in its entirety. -void drop_blood_potions_stack(int item, int quant) +// Called from copy_item_to_grid. +// Quantities are set afterwards, so don't ASSERT for those. +void drop_blood_potions_stack(item_def &stack, int quant, int x, int y) { - ASSERT(quant > 0); - // entire stack was dropped? - if (!is_valid_item(you.inv[item])) + if (!is_valid_item(stack)) return; - item_def &stack = you.inv[item]; + ASSERT(quant > 0 && quant <= stack.quantity); ASSERT(stack.base_type == OBJ_POTIONS); ASSERT(stack.sub_type == POT_BLOOD || stack.sub_type == POT_BLOOD_COAGULATED); CrawlHashTable &props = stack.props; + if (!props.exists("timer")) + init_stack_blood_potions(stack, stack.special); ASSERT(props.exists("timer")); - CrawlVector &timer = props["timer"]; + CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); // first check whether we can merge with an existing stack on the floor - int o = igrd[you.x_pos][you.y_pos]; + int o = igrd[x][y]; while (o != NON_ITEM) { if (mitm[o].base_type == OBJ_POTIONS && mitm[o].sub_type == stack.sub_type) { CrawlHashTable &props2 = mitm[o].props; + if (!props2.exists("timer")) + init_stack_blood_potions(mitm[o], mitm[o].special); ASSERT(props2.exists("timer")); - CrawlVector &timer2 = props2["timer"]; + CrawlVector &timer2 = props2["timer"].get_vector(); // update timer -> push(pop) for (int i = 0; i < quant; i++) - timer2.push_back(timer.pop_back()); + { + timer2.push_back(timer[timer.size() - 1].get_long()); + timer.pop_back(); + } - ASSERT(timer2.size() == mitm[o].quantity); // re-sort timer _long_sort(timer2); - - // now the stack timer should be correct again - ASSERT(timer.size() == stack.quantity); return; } o = mitm[o].link; @@ -410,27 +804,25 @@ void drop_blood_potions_stack(int item, int quant) // have to reduce the timer vector anyway. while (!timer.empty() && quant-- > 0) timer.pop_back(); - - ASSERT(stack.quantity == timer.size()); } -// Should be called *after* move_item_to_player -// unless the stack has been picked up in its entirety. -void pick_up_blood_potions_stack(int item, int quant) +// Called from move_item_to_player. +// Quantities are set afterwards, so don't ASSERT for those. +void pick_up_blood_potions_stack(item_def &stack, int quant) { - ASSERT(quant > 0); - // entire stack was taken? - if (!is_valid_item(mitm[item])) + ASSERT(quant > 0 && quant <= stack.quantity); + if (!is_valid_item(stack)) return; - item_def &stack = mitm[item]; ASSERT(stack.base_type == OBJ_POTIONS); ASSERT(stack.sub_type == POT_BLOOD || stack.sub_type == POT_BLOOD_COAGULATED); CrawlHashTable &props = stack.props; + if (!props.exists("timer")) + init_stack_blood_potions(stack, stack.special); ASSERT(props.exists("timer")); - CrawlVector &timer = props["timer"]; + CrawlVector &timer = props["timer"].get_vector(); ASSERT(!timer.empty()); // first check whether we can merge with an existing stack in inventory @@ -443,25 +835,24 @@ void pick_up_blood_potions_stack(int item, int quant) && you.inv[m].sub_type == stack.sub_type) { CrawlHashTable &props2 = you.inv[m].props; + if (!props2.exists("timer")) + init_stack_blood_potions(you.inv[m], you.inv[m].special); ASSERT(props2.exists("timer")); - CrawlVector &timer2 = props2["timer"]; + CrawlVector &timer2 = props2["timer"].get_vector(); // update timer -> push(pop) for (int i = 0; i < quant; i++) - timer2.push_back(timer.pop_back()); + { + timer2.push_back(timer[timer.size() - 1].get_long()); + timer.pop_back(); + } - ASSERT(timer2.size() == you.inv[m].quantity); // re-sort timer _long_sort(timer2); - - // now the stack timer should be correct again - ASSERT(timer.size() == stack.quantity); return; } } - // If we got here nothing was found. Huh? - ASSERT(stack.quantity == timer.size()); } // Deliberately don't check for rottenness here, so this check @@ -494,8 +885,8 @@ void turn_corpse_into_blood_potions( item_def &item ) item.base_type = OBJ_POTIONS; item.sub_type = POT_BLOOD; - item.colour = RED; - item.special = (item.special - 80) * 10; // potion's age + item_colour(item); + item.flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); // max. amount is about one third of the max. amount for chunks const int max_chunks = mons_weight( mons_class ) / 150; @@ -511,7 +902,10 @@ void turn_corpse_into_blood_potions( item_def &item ) item.quantity = 1; } - item.flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); + // initialize timer depending on corpse age + // almost rotting: age = 100 --> potion timer = 500 --> will coagulate soon + // freshly killed: age = 200 --> potion timer = 2000 --> fresh !blood + init_stack_blood_potions(item, (item.special - 100) * 15 + 500); // happens after the blood has been bottled if (monster_descriptor(mons_class, MDSC_LEAVES_HIDE) && !one_chance_in(3)) @@ -534,6 +928,23 @@ void split_blood_potions_into_decay( int obj, int amount ) if (amount <= 0) amount = random2(potion.quantity) + 1; + // We're being nice here, and only decay the *oldest* potions. + for (int i = 0; i < amount; i++) + remove_oldest_blood_potion(potion); + + // try to merge into existing stacks of decayed potions + for (int m = 0; m < ENDOFPACK; m++) + { + if (you.inv[m].base_type == OBJ_POTIONS + && you.inv[m].sub_type == POT_DECAY + && you.inv[m].colour == potion.colour) + { + you.inv[obj].quantity -= amount; + you.inv[m].quantity += amount; + return; + } + } + // if entire stack affected just change subtype if (amount == potion.quantity) { @@ -541,17 +952,42 @@ void split_blood_potions_into_decay( int obj, int amount ) return; } - // try to merge into existing stacks of decayed potions - for (int m = 0; m < ENDOFPACK; m++) - { - if (you.inv[m].base_type == OBJ_POTIONS - && you.inv[m].sub_type == POT_DECAY) - { - inc_inv_item_quantity( m, amount ); - dec_inv_item_quantity( obj, amount); + // else, create new stack in inventory + int freeslot = find_free_slot(you.inv[obj]); + if (freeslot >= 0 && freeslot < ENDOFPACK + && !is_valid_item(you.inv[freeslot])) + { + item_def &item = you.inv[freeslot]; + item.link = freeslot; + item.slot = index_to_letter(item.link); + item.base_type = OBJ_POTIONS; + item.sub_type = POT_DECAY; + item.quantity = amount; + item.x = -1; + item.y = -1; + item.plus = 0; + item.plus2 = 0; + item.special = 0; + item.flags = 0; + + you.inv[obj].quantity -= amount; + return; + } + // Okay, inventory is full. - return; - } + // check whether we can merge with an existing stack on the floor + int o = igrd[you.x_pos][you.y_pos]; + while (o != NON_ITEM) + { + if (mitm[o].base_type == OBJ_POTIONS + && mitm[o].sub_type == POT_DECAY + && mitm[o].colour == you.inv[obj].colour) + { + dec_inv_item_quantity(obj, amount); + inc_mitm_item_quantity(o, amount); + return; + } + o = mitm[o].link; } // only bother creating a distinct stack of potions diff --git a/crawl-ref/source/misc.h b/crawl-ref/source/misc.h index f4cffde844..4c588fc42c 100644 --- a/crawl-ref/source/misc.h +++ b/crawl-ref/source/misc.h @@ -69,10 +69,13 @@ void trackers_init_new_level(bool transit); void turn_corpse_into_chunks( item_def &item ); void init_stack_blood_potions( item_def &stack, int age = -1 ); -void maybe_coagulate_blood_potions_floor( item_def &blood ); -void remove_oldest_potion_inv( item_def &stack ); -void drop_blood_potions_stack( int item, int quant ); -void pick_up_blood_potions_stack( int item, int quant ); +void maybe_coagulate_blood_potions_floor( int obj ); +bool maybe_coagulate_blood_potions_inv( item_def &blood ); +long remove_oldest_blood_potion( item_def &stack ); +void remove_newest_blood_potion( item_def &stack, int quant = -1 ); +void drop_blood_potions_stack( item_def &stack, int quant, int x = you.x_pos, + int y = you.y_pos ); +void pick_up_blood_potions_stack( item_def &stack, int quant ); bool can_bottle_blood_from_corpse( int mons_type ); void turn_corpse_into_blood_potions ( item_def &item ); diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index 8f8f841d28..51138dd2a1 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -86,6 +86,7 @@ #include "macro.h" #include "makeitem.h" #include "menu.h" +#include "misc.h" #include "place.h" #include "player.h" #include "randart.h" @@ -852,6 +853,7 @@ static void _give_starting_food() return; item_def item; + item.quantity = 1; if ( you.species == SP_SPRIGGAN ) { item.base_type = OBJ_POTIONS; @@ -861,7 +863,7 @@ static void _give_starting_food() { item.base_type = OBJ_POTIONS; item.sub_type = POT_BLOOD; - item.special = 1200; + init_stack_blood_potions(item); } else { @@ -874,7 +876,6 @@ static void _give_starting_food() else item.sub_type = FOOD_BREAD_RATION; } - item.quantity = 1; const int slot = find_free_slot(item); you.inv[slot] = item; // will ASSERT if couldn't find free slot diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index e192b705a4..044e59ee2d 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -1243,10 +1243,7 @@ int player_res_fire(bool calc_unid, bool temp) // species: if (you.species == SP_MUMMY - || you.species == SP_VAMPIRE && you.hunger_state <= HS_NEAR_STARVING) - { rf--; - } // mutations: rf += you.mutation[MUT_HEAT_RESISTANCE]; @@ -3664,7 +3661,6 @@ void display_char_status() { case HS_STARVING: attrib.push_back("resist poison"); - attrib.push_back("are susceptible to fire"); attrib.push_back("significantly resist cold"); attrib.push_back("strongly resist negative energy"); attrib.push_back("resist torment"); diff --git a/crawl-ref/source/spells4.cc b/crawl-ref/source/spells4.cc index cbb771dfaa..d1e5192f24 100644 --- a/crawl-ref/source/spells4.cc +++ b/crawl-ref/source/spells4.cc @@ -1702,16 +1702,21 @@ bool cast_evaporate(int pow, bolt& beem, int potion) fire_beam(beem); // both old and new code use up a potion: + if (you.inv[potion].sub_type == POT_BLOOD + || you.inv[potion].sub_type == POT_BLOOD_COAGULATED) + { + remove_oldest_blood_potion(you.inv[potion]); + } dec_inv_item_quantity( potion, 1 ); return (true); } // end cast_evaporate() -// The intent of this spell isn't to produce helpful potions +// The intent of this spell isn't to produce helpful potions // for drinking, but rather to provide ammo for the Evaporate -// spell out of corpses, thus potentially making it useful. +// spell out of corpses, thus potentially making it useful. // Producing helpful potions would break game balance here... -// and producing more than one potion from a corpse, or not +// and producing more than one potion from a corpse, or not // using up the corpse might also lead to game balance problems. -- bwr void cast_fulsome_distillation( int powc ) { |