Instead of storing age in item.special they now use a dynamic vector (item.props, like decks do), so that a stack of potions doesn't have to coagulate all at once.
Rather than counting down the timers every 20 turns or so, the time-out turn is calculated at creation, and comparison is done against the current turncount. Any action changing a stack (quaffing, firing, Evaporate, picking up, dropping) will always extract the oldest values from the vector, which is likely to be what the player wants.
Blood potions now last about 2000 turns (a bit more if drawn from fresh corpses), and coagulate 500 turns before rotting away.
I ran a lot of tests in wiz mode and out, but of course there may still be problems. I've added methods to calculate the new timers from old style age counters (item.special), but I'm not sure that they actually work… Oh well… if worst comes to worst, this commit breaks saves.
Also:
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@4228 c06c8d41-db1a-0410-9941-cceddc491573
QCUMH3C7GXV7ZW444WT5SFAXQOJKJSE2YCQCEHMRYXCWF4QI7UMAC 2O3C3MTT2ZBYIFGPJ4MF5R4AXBYUHOEN62KAUWFWF6JWHIIVLRNQC 75M6AVUSS3G5EJECJJRB67V5UYDOIV26FZNB2FFCMBZ33EK7FVIQC 67AF33NTJG4WEAIFQX4R5XZTC5XZ5IBVHSFLYTGOXQJIXCZVXKDQC K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC TFNFP2YQA4EOVE4VIXBEQSGACZSXHWIQ2T4TIPQ46R2MJW2C4B5AC VXSORUQOM2VZA4CAZDC6KPAY373NQIN3UT7CXQXTRCYXO2WM62DAC RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC SVY2PTCLXR3KNPQAWXVXTTGCC5DR334HOAKHYO3VDDRWM2BWMALAC 6LT6USGJOTDMRJGXLAN2NSZXK2GKWEXDKKUV6SVV7ZC6WI6EKMDQC Q3XHNSHW6FI4JCXSEABATRFBJPMCF7PXNG2K6ZQTRRPVMIZFHUBQC P2ZCF3BBG523ZEOD6XQA4X5YEHBTWH3IM33YVHXP2SQ5POXZIH4QC 2WVP47RBNL5OVYMAZH7TKRYD7F2TGSZ5X74PWVGAYCQP26G3JUHQC DTO3EUKWHZ5RJNGNCFYXSOVTIPVXPP637F2W7WFGYKJ7JK7VNKNQC Q3DNEB5OOJ34P5ML4CMK3L6SCP7RLW7DDOZEG24KZBX3C7BJRQDAC M2HMYLYYXSP5XGX6MCI77IT6UW5K4YSEUJWHPQPYXVSW5L36BJ2AC 25CH7HH4LKXFIZ75YNMXS3TSXO6O27DYSOPLOD45K4OCNFWLS4LQC ND3T5LCZATC63EVQ6SLI7XXMSUL7XICJDNLH3UCYUDEFWTA3N5MQC XI7U6RL6MFTH6SZQMLO4XFWGXGIFJBCRT6ZFCJFR2B2MGRSKDDCAC OXHOOJE4XEQKGI3JKURNSITG6CRQFAOFQ2EKKG6M5DCLN7LS4GUAC YOZHWGKGPWZGHQYNMMBHNGDO2UEVIHPOR6UF3SEZPIYWVMBGWBOAC SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC CIPVRZGLOZHCERK6YPOBV3P2E4IAB4H6D5EHLRQE2O5E4P4VCBUAC 77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC BDFIS53HAIHOCXQ5BE7WCO2MEOFCUQPFY4JGUWVLWY6JO3IFMEKQC DMLFJIPOE4ZXUFQ25VVEZCMURP2UPJBBWDGQS3DPQVUNVBBQ4GDQC SWT4O2TCOAQOVFA6WRA7MCU3KMTMJWFEMIHO64N4PWL5FNHDPADAC HIRKGUMNJPWKSVTR6TVBPD3MWNA63CEHCLCIPWEMGDFHVB3NPLDQC LT4XSC3B5CREP5LGIG3ER3SZFZFQAHX3UAHTGCMNR2EXVOYFTTMAC APCC5AQ77EHT57NWJA4SRRQOFNTIZDDUJRAFAXJS75Q235TTPDDQC TPZWAV3USKO7RX4IGHLZKVPRN36K33PJPSZYL6FZMX4XBHTYOQYAC OEISFRW2B7E4YRJSWXNXBH2IAJO4O3LHNYFPFD3MBY57LYVRHMZQC TRZAZJJA5VQBJ65SO5H5DNSILIZEJHIZCBYEBHEATCT6ZSMWIFKQC VCG3BRIYRTNNWYC3LOXD6KFGXOX37HAFW2HNV7WXVG2V7EUHLDZQC G5WLU3B4MR3ZDJLGR6OHNIMLZXGQS2EWBJ5YYY5J2GWM6DTD44BAC BMHUBADDGIOZRVN4P3O5QKIDUYD4RFWBS7MP5X6LZWAYHUBRVD2QC 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 );
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/coagulatingbreak;}timer.pop_back();if (current <= rot_limit)
#ifdef DEBUG_BLOOD_POTIONSmprf(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
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 potionsint 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 ";elsemsg = "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 messagesbool 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 handlingint rot_limit = you.num_turns;int coag_limit = you.num_turns + 500; // check 500 turns later// first count whether coagulating is even necessaryint 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/coagulatingbreak;}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_POTIONSmprf(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();#endifif (!coag_count) // some potions rotted away{blood.quantity -= rot_count;if (blood.quantity < 1)destroy_item(blood);elseASSERT(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 necessaryif (!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 bloodfor (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 subtypeif (rot_count + coag_count == blood.quantity){ASSERT(timer.empty());// update subtypeblood.sub_type = POT_BLOOD_COAGULATED;item_colour(blood);// re-fill vectorlong 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_countASSERT(timer.size() == blood.quantity);if (!knew_coag)mpr(blood.name(DESC_INVENTORY).c_str());return (rot_count > 0);}// else, create new stack in inventoryint 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;
// no space in inventory, check floorint 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 stackCrawlHashTable &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!
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;
// 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)
// used whenever copies of blood potions have to be cleaned upvoid remove_newest_blood_potion(item_def &stack, int quant)
ASSERT(quant > 0);// entire stack was dropped?if (!is_valid_item(you.inv[item]))
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 onesint 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 potionsrepeats = quant - repeats;for (int i = 0; i < repeats; i++)timer.pop_back();// and re-sort_long_sort(timer);}// 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){if (!is_valid_item(stack))
// 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)
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 !bloodinit_stack_blood_potions(item, (item.special - 100) * 15 + 500);
// try to merge into existing stacks of decayed potionsfor (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;}}
// try to merge into existing stacks of decayed potionsfor (int m = 0; m < ENDOFPACK; m++)
// else, create new stack in inventoryint freeslot = find_free_slot(you.inv[obj]);if (freeslot >= 0 && freeslot < ENDOFPACK&& !is_valid_item(you.inv[freeslot]))
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);
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 floorint 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;
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 stackfor (int i = 0; i < quant_got; i++)remove_oldest_blood_potion(mitm[obj]);// ... and newest ones from picked up stackremove_newest_blood_potion(item);}}
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 onesremove_newest_blood_potion(mitm[new_item]);}
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 droppedfor (int i = 0; i < quant_drop; i++)remove_oldest_blood_potion(you.inv[item_dropped]);}
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 stacklong val = remove_oldest_blood_potion(you.inv[throw_2]);val -= you.num_turns;item.props.clear();init_stack_blood_potions(item, val);}
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 ))
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 coagulateelse{if (congealed_blood_num == 1)msg += "One of ";elsemsg += "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 necessaryif (!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 (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];}
#ifdef DEBUG_BLOOD_POTIONS// list content of timer vector for blood potionsif (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()) << " ";}#endifcase OBJ_SCROLLS:
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;