/* * File: effects.cc * Summary: Misc stuff. * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <1> -/--/-- LRH Created */ #include "AppHdr.h" #include "effects.h" #include #include #include #include #include "externs.h" #include "beam.h" #include "cloud.h" #include "decks.h" #include "delay.h" #include "describe.h" #include "directn.h" #include "dgnevent.h" #include "food.h" #include "hiscores.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "makeitem.h" #include "message.h" #include "misc.h" #include "monplace.h" #include "monstuff.h" #include "mon-util.h" #include "mutation.h" #include "newgame.h" #include "notes.h" #include "ouch.h" #include "player.h" #include "randart.h" #include "religion.h" #include "skills.h" #include "skills2.h" #include "spells2.h" #include "spells3.h" #include "spl-book.h" #include "spl-cast.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "terrain.h" #include "traps.h" #include "tutorial.h" #include "view.h" #include "xom.h" int holy_word_player(int pow, int caster) { if (!you.is_undead && you.species != SP_DEMONSPAWN) return 0; int hploss = std::max(0, you.hp / 2 - 1); if (!hploss) return 0; mpr("You are blasted by holy energy!"); const char *aux = "holy word"; if (caster < 0) { switch (caster) { case HOLY_WORD_SCROLL: aux = "scroll of holy word"; break; case HOLY_WORD_ZIN: aux = "Zin's holy word"; break; case HOLY_WORD_SHINING_ONE: aux = "The Shining One's holy word"; break; } caster = HOLY_WORD_GENERIC; } ouch(hploss, caster, (caster != HOLY_WORD_GENERIC) ? KILLED_BY_MONSTER : KILLED_BY_SOMETHING, aux); return 1; } int holy_word_monsters(int x, int y, int pow, int caster) { int retval = 0; // doubt this will ever happen, but it's here as a safety -- bwr pow = std::min(300, pow); // Is the player in this cell? if (x == you.x_pos && y == you.y_pos) retval = holy_word_player(pow, caster); // Is a monster in this cell? int mon = mgrd[x][y]; if (mon == NON_MONSTER) return retval; monsters *monster = &menv[mon]; if (invalid_monster(monster) || !mons_is_unholy(monster) || (is_good_god(you.religion) && (is_follower(monster) || mons_neutral(monster)))) { return retval; } int hploss = std::max(0, roll_dice(2, 15) + (random2(pow) / 3)); // Currently, holy word annoys the monsters it affects because it // can kill them, and because hostile monsters don't use it. behaviour_event(monster, ME_ANNOY, MHITYOU); hurt_monster(monster, hploss); if (hploss) { retval = 1; if (!monster->alive()) { monster_die(monster, KILL_YOU, 0); return retval; } } simple_monster_message(monster, " convulses!"); if (monster->speed_increment >= 25) { retval = 1; monster->speed_increment -= 20; } if (monster->add_ench(ENCH_FEAR)) retval = 1; return retval; } int holy_word(int pow, int caster, int x, int y, bool silent) { if (!silent) mpr("You speak a Word of immense power!"); return apply_area_within_radius(holy_word_monsters, x, y, pow, 8, caster); } int torment_player(int pow, int caster) { UNUSED(pow); // [dshaligram] Switched to using ouch() instead of dec_hp() so that // notes can also track torment and activities can be interrupted // correctly. int hploss = 0; if (!player_res_torment()) { // Negative energy resistance can alleviate torment. hploss = std::max(0, you.hp * (50 - player_prot_life() * 5) / 100 - 1); } if (!hploss) { mpr("You feel a surge of unholy energy."); return 0; } mpr("Your body is wracked with pain!"); const char *aux = "torment"; if (caster < 0) { switch (caster) { case TORMENT_CARDS: case TORMENT_SPELL: aux = "Symbol of Torment"; break; case TORMENT_SPWLD: // XXX: If we ever make any other weapon / randart eligible // to torment, this will be incorrect. aux = "Sceptre of Torment"; break; case TORMENT_SCROLL: aux = "scroll of torment"; break; case TORMENT_XOM: aux = "Xom's torment"; break; } caster = TORMENT_GENERIC; } ouch(hploss, caster, (caster != TORMENT_GENERIC) ? KILLED_BY_MONSTER : KILLED_BY_SOMETHING, aux); return 1; } // torment_monsters() is called with power 0 because torment is // UNRESISTABLE except for having torment resistance! Even if we used // maximum power of 1000, high level monsters and characters would save // too often. (GDL) int torment_monsters(int x, int y, int pow, int caster) { UNUSED(pow); int retval = 0; // Is the player in this cell? if (x == you.x_pos && y == you.y_pos) retval = torment_player(0, caster); // Is a monster in this cell? int mon = mgrd[x][y]; if (mon == NON_MONSTER) return retval; monsters *monster = &menv[mon]; if (invalid_monster(monster) || mons_res_negative_energy(monster) == 3) return retval; int hploss = std::max(0, monster->hit_points / 2 - 1); // Currently, torment doesn't annoy the monsters it affects because // it can't kill them, and because hostile monsters use it. hurt_monster(monster, hploss); if (hploss) { retval = 1; if (!monster->alive()) { monster_die(monster, KILL_YOU, 0); return retval; } } simple_monster_message(monster, " convulses!"); return retval; } int torment(int caster, int x, int y) { return apply_area_within_radius(torment_monsters, x, y, 0, 8, caster); } static std::string _who_banished(const std::string &who) { return (who.empty()? who : " (" + who + ")"); } void banished(dungeon_feature_type gate_type, const std::string &who) { #ifdef DGL_MILESTONES if (gate_type == DNGN_ENTER_ABYSS) { mark_milestone("abyss.enter", "is cast into the Abyss!" + _who_banished(who)); } else if (gate_type == DNGN_EXIT_ABYSS) { mark_milestone("abyss.exit", "escaped from the Abyss!" + _who_banished(who)); } #endif std::string cast_into; switch (gate_type) { case DNGN_ENTER_ABYSS: if (you.level_type == LEVEL_ABYSS) { mpr("You feel trapped."); return; } cast_into = "the Abyss"; break; case DNGN_EXIT_ABYSS: if (you.level_type != LEVEL_ABYSS) { mpr("You feel dizzy for a moment."); return; } break; case DNGN_ENTER_PANDEMONIUM: if (you.level_type == LEVEL_PANDEMONIUM) { mpr("You feel trapped."); return; } cast_into = "Pandemonium"; break; case DNGN_TRANSIT_PANDEMONIUM: if (you.level_type != LEVEL_PANDEMONIUM) { banished(DNGN_ENTER_PANDEMONIUM, who); return; } break; case DNGN_EXIT_PANDEMONIUM: if (you.level_type != LEVEL_PANDEMONIUM) { mpr("You feel dizzy for a moment."); return; } break; case DNGN_ENTER_LABYRINTH: if (you.level_type == LEVEL_LABYRINTH) { mpr("You feel trapped."); return; } cast_into = "a Labyrinth"; break; case DNGN_ENTER_HELL: case DNGN_ENTER_DIS: case DNGN_ENTER_GEHENNA: case DNGN_ENTER_COCYTUS: case DNGN_ENTER_TARTARUS: if (player_in_hell() || player_in_branch(BRANCH_VESTIBULE_OF_HELL)) { mpr("You feel dizzy for a moment."); return; } cast_into = "Hell"; break; default: mprf(MSGCH_DIAGNOSTICS, "Invalid banished() gateway %d", static_cast(gate_type)); ASSERT(false); } // Now figure out how we got here. if (crawl_state.is_god_acting()) { // down_stairs() will take care of setting things. you.entry_cause = EC_UNKNOWN; } else if (who.find("self") != std::string::npos || who == you.your_name || who == "you" || who == "You") { you.entry_cause = EC_SELF_EXPLICIT; } else if (who.find("distortion") != std::string::npos) { if (who.find("wield") != std::string::npos) { if (who.find("unknowing") != std::string::npos) you.entry_cause = EC_SELF_ACCIDENT; else you.entry_cause = EC_SELF_RISKY; } else if (who.find("affixation") != std::string::npos) you.entry_cause = EC_SELF_ACCIDENT; else if (who.find("branding") != std::string::npos) you.entry_cause = EC_SELF_RISKY; else you.entry_cause = EC_MONSTER; } else if (who == "drawing a card") you.entry_cause = EC_SELF_RISKY; else if (who.find("miscast") != std::string::npos) you.entry_cause = EC_MISCAST; else if (who == "wizard command") you.entry_cause = EC_SELF_EXPLICIT; else you.entry_cause = EC_MONSTER; if (!crawl_state.is_god_acting()) you.entry_cause_god = GOD_NO_GOD; if (!cast_into.empty() && you.entry_cause != EC_SELF_EXPLICIT) { const std::string what = "Cast into " + cast_into + _who_banished(who); take_note(Note(NOTE_MESSAGE, 0, 0, what.c_str()), true); } // No longer held in net. clear_trapping_net(); down_stairs(you.your_level, gate_type, you.entry_cause); // heh heh } bool forget_spell(void) { if (!you.spell_no) return (false); // find a random spell to forget: int slot = -1; int num = 0; for (int i = 0; i < 25; i++) { if (you.spells[i] != SPELL_NO_SPELL) { num++; if (one_chance_in( num )) slot = i; } } if (slot == -1) // should never happen though return (false); del_spell_from_memory_by_slot( slot ); return (true); } // end forget_spell() // use player::decrease_stats() instead iff: // (a) player_sust_abil() should not factor in; and // (b) there is no floor to the final stat values {dlb} bool lose_stat(unsigned char which_stat, unsigned char stat_loss, bool force, const char *cause, bool see_source) { bool statLowered = false; // must initialize to false {dlb} char *ptr_stat = NULL; bool *ptr_redraw = NULL; char newValue = 0; // holds new value, for comparison to old {dlb} kill_method_type kill_type = NUM_KILLBY; // begin outputing message: {dlb} std::string msg = "You feel "; // set pointers to appropriate variables: {dlb} if (which_stat == STAT_RANDOM) which_stat = random2(NUM_STATS); switch (which_stat) { case STAT_STRENGTH: msg += "weakened"; ptr_stat = &you.strength; ptr_redraw = &you.redraw_strength; kill_type = KILLED_BY_WEAKNESS; break; case STAT_DEXTERITY: msg += "clumsy"; ptr_stat = &you.dex; ptr_redraw = &you.redraw_dexterity; kill_type = KILLED_BY_CLUMSINESS; break; case STAT_INTELLIGENCE: msg += "dopey"; ptr_stat = &you.intel; ptr_redraw = &you.redraw_intelligence; kill_type = KILLED_BY_STUPIDITY; break; } // scale modifier by player_sust_abil() - right-shift // permissible because stat_loss is unsigned: {dlb} if (!force) stat_loss >>= player_sust_abil(); // newValue is current value less modifier: {dlb} newValue = *ptr_stat - stat_loss; // conceivable that stat was already *at* three // or stat_loss zeroed by player_sust_abil(): {dlb} // // Actually, that code was somewhat flawed. Several race-class combos // can start with a stat lower than three, and this block (which // used to say '!=' would actually cause stat gain with the '< 3' // check that used to be above. Crawl has stat-death code and I // don't see why we shouldn't be using it here. -- bwr if (newValue < *ptr_stat) { *ptr_stat = newValue; *ptr_redraw = 1; // handle burden change, where appropriate: {dlb} if (ptr_stat == &you.strength) burden_change(); statLowered = true; // that is, stat was lowered (not just changed) } // a warning to player that s/he cut it close: {dlb} if (!statLowered) msg += " for a moment"; // finish outputting message: {dlb} msg += "."; mpr(msg.c_str()); if (newValue < 1) { if (cause == NULL) ouch(INSTANT_DEATH, 0, kill_type); else ouch(INSTANT_DEATH, 0, kill_type, cause, see_source); } return (statLowered); } // end lose_stat() bool lose_stat(unsigned char which_stat, unsigned char stat_loss, bool force, const std::string cause, bool see_source) { return lose_stat(which_stat, stat_loss, force, cause.c_str(), see_source); } bool lose_stat(unsigned char which_stat, unsigned char stat_loss, const monsters* cause, bool force) { if (cause == NULL || invalid_monster(cause)) return lose_stat(which_stat, stat_loss, force, NULL, true); bool vis = mons_near(cause) && player_monster_visible(cause); std::string name = cause->name(DESC_NOCAP_A, true); if (cause->has_ench(ENCH_SHAPESHIFTER)) name += " (shapeshifter)"; else if (cause->has_ench(ENCH_GLOWING_SHAPESHIFTER)) name += " (glowing shapeshifter)"; return lose_stat(which_stat, stat_loss, force, name, vis); } bool lose_stat(unsigned char which_stat, unsigned char stat_loss, const item_def &cause, bool removed, bool force) { std::string name = cause.name(DESC_NOCAP_THE, false, true, false, false, ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES); std::string verb; switch(cause.base_type) { case OBJ_ARMOUR: case OBJ_JEWELLERY: if (removed) verb = "removing"; else verb = "wearing"; break; case OBJ_WEAPONS: case OBJ_STAVES: if (removed) verb = "unwielding"; else verb = "wielding"; break; case OBJ_WANDS: verb = "zapping"; break; case OBJ_FOOD: verb = "eating"; break; case OBJ_SCROLLS: verb = "reading"; break; case OBJ_POTIONS: verb = "drinking"; break; default: verb = "using"; } return lose_stat(which_stat, stat_loss, force, verb + " " + name, true); } void direct_effect(struct bolt &pbolt) { int damage_taken = 0; monsters* source = NULL; if (pbolt.beam_source != NON_MONSTER) source = &menv[pbolt.beam_source]; switch (pbolt.type) { case DMNBM_HELLFIRE: pbolt.aux_source = "burst of hellfire"; pbolt.name = "hellfire"; pbolt.ex_size = 1; pbolt.flavour = BEAM_HELLFIRE; pbolt.is_explosion = true; pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP); pbolt.colour = RED; pbolt.thrower = KILL_MON_MISSILE; pbolt.aux_source.clear(); pbolt.is_beam = false; pbolt.is_tracer = false; pbolt.hit = 20; pbolt.damage = dice_def( 3, 20 ); explosion( pbolt ); break; case DMNBM_SMITING: mpr( "Something smites you!" ); pbolt.name = "smiting"; pbolt.aux_source = "by divine providence"; damage_taken = 7 + random2avg(11, 2); break; case DMNBM_BRAIN_FEED: // lose_stat() must come last {dlb} if (one_chance_in(3) && lose_stat(STAT_INTELLIGENCE, 1, source)) { mpr("Something feeds on your intellect!"); xom_is_stimulated(50); } else mpr("Something tries to feed on your intellect!"); break; } // apply damage and handle death, where appropriate {dlb} if (damage_taken > 0) { ouch(damage_taken, pbolt.beam_source, KILLED_BY_BEAM, pbolt.aux_source.c_str()); } return; } // end direct_effect() // monster-to-monster void mons_direct_effect(struct bolt &pbolt, int i) { // note the translation here - important {dlb} int o = menv[i].foe; monsters *monster = &menv[o]; int damage_taken = 0; // annoy the target behaviour_event(monster, ME_ANNOY, i); switch (pbolt.type) { case DMNBM_HELLFIRE: simple_monster_message(monster, " is engulfed in hellfire."); pbolt.name = "hellfire"; pbolt.flavour = BEAM_LAVA; damage_taken = 5 + random2(10) + random2(5); damage_taken = mons_adjust_flavoured(monster, pbolt, damage_taken); break; case DMNBM_SMITING: simple_monster_message(monster, " is smitten."); pbolt.name = "smiting"; pbolt.flavour = BEAM_MISSILE; damage_taken += 7 + random2avg(11, 2); break; case DMNBM_BRAIN_FEED: // Not implemented here (nor, probably, can be). break; case DMNBM_MUTATION: if (mons_holiness(monster) != MH_NATURAL || mons_immune_magic(monster)) { simple_monster_message(monster, " is unaffected."); } else if (check_mons_resist_magic( monster, pbolt.ench_power )) simple_monster_message(monster, " resists."); else monster_polymorph(monster, RANDOM_MONSTER); break; } // Apply damage and handle death, where appropriate {dlb} if (damage_taken > 0) { hurt_monster(monster, damage_taken); if (monster->hit_points < 1) monster_die(monster, KILL_MON_MISSILE, i); } return; } void random_uselessness(int scroll_slot) { int temp_rand = random2(8); // If this isn't from a scroll, skip the first two possibilities. if (scroll_slot == -1) temp_rand = 2 + random2(6); switch (temp_rand) { case 0: mprf("The dust glows %s!", weird_glowing_colour().c_str()); break; case 1: mpr("The scroll reassembles itself in your hand!"); inc_inv_item_quantity(scroll_slot, 1); break; case 2: if (you.equip[EQ_WEAPON] != -1) { mprf("%s glows %s for a moment.", you.inv[you.equip[EQ_WEAPON]].name(DESC_CAP_YOUR).c_str(), weird_glowing_colour().c_str()); } else { mprf("Your %s glow %s for a moment.", your_hand(true).c_str(), weird_glowing_colour().c_str()); } break; case 3: if (player_can_smell()) mprf("You smell %s.", weird_smell().c_str()); else if (you.species == SP_MUMMY) mpr("Your bandages flutter."); else canned_msg(MSG_NOTHING_HAPPENS); break; case 4: mpr("You experience a momentary feeling of inescapable doom!"); break; case 5: temp_rand = random2(3); mprf("Your %s", (temp_rand == 0) ? "ears itch." : (temp_rand == 1) ? "brain hurts!" : "nose twitches suddenly!"); break; case 6: mpr("You hear the tinkle of a tiny bell.", MSGCH_SOUND); cast_summon_butterflies(100); break; case 7: mprf(MSGCH_SOUND, "You hear %s.", weird_sound().c_str()); break; } } static armour_type _random_nonbody_armour_type() { const armour_type at = static_cast( random_choose(ARM_SHIELD, ARM_CLOAK, ARM_HELMET, ARM_GLOVES, ARM_BOOTS, -1)); return (at); } static int _find_acquirement_subtype(object_class_type class_wanted, int &quantity) { ASSERT(class_wanted != OBJ_RANDOM); int type_wanted = OBJ_RANDOM; int iteration = 0; const int max_has_value = 100; FixedVector< int, max_has_value > already_has; skill_type best_spell = SK_NONE; skill_type best_any = SK_NONE; already_has.init(0); int spell_skills = 0; for (int i = SK_SPELLCASTING; i <= SK_POISON_MAGIC; i++) spell_skills += you.skills[i]; for (int acqc = 0; acqc < ENDOFPACK; acqc++) { if (is_valid_item( you.inv[acqc] ) && you.inv[acqc].base_type == class_wanted) { ASSERT( you.inv[acqc].sub_type < max_has_value ); already_has[you.inv[acqc].sub_type] += you.inv[acqc].quantity; } } if (class_wanted == OBJ_FOOD) { // food is a little less predictable now -- bwr if (you.species == SP_GHOUL) { type_wanted = one_chance_in(10) ? FOOD_ROYAL_JELLY : FOOD_CHUNK; } else if (you.species == SP_VAMPIRE) { // Vampires really don't want any OBJ_FOOD but OBJ_CORPSES // but it's easier to just give them a potion of blood // class type is set elsewhere type_wanted = POT_BLOOD; quantity = 2 + random2(4); } else { // Meat is better than bread (except for herbivores), and // by choosing it as the default we don't have to worry // about special cases for carnivorous races (e.g. kobolds) type_wanted = FOOD_MEAT_RATION; if (player_mutation_level(MUT_HERBIVOROUS)) type_wanted = FOOD_BREAD_RATION; // If we have some regular rations, then we're probably more // interested in faster foods (especially royal jelly)... // otherwise the regular rations should be a good enough offer. if (already_has[FOOD_MEAT_RATION] + already_has[FOOD_BREAD_RATION] >= 2 || coinflip()) { type_wanted = one_chance_in(5) ? FOOD_HONEYCOMB : FOOD_ROYAL_JELLY; } } quantity = 3 + random2(5); // giving more of the lower food value items if (type_wanted == FOOD_HONEYCOMB || type_wanted == FOOD_CHUNK) { quantity += random2avg(10, 2); } } else if (class_wanted == OBJ_WEAPONS) { // Now asking for a weapon is biased towards your skills, // although launchers are right out for now. -- bwr int count = 0; int skill = SK_FIGHTING; // Can't do much with launchers, so we'll avoid them for now -- bwr for (int i = SK_SHORT_BLADES; i < SK_DARTS; i++) { if (i == SK_UNUSED_1) continue; // Adding a small constant allows for the occasional // weapon in an untrained skill. const int weight = you.skills[i] + 1; count += weight; if (x_chance_in_y(weight, count)) skill = i; } count = 0; item_def item_considered; item_considered.base_type = OBJ_WEAPONS; for (int i = 0; i < NUM_WEAPONS; ++i) { item_considered.sub_type = i; const int acqweight = property(item_considered, PWPN_ACQ_WEIGHT); if (!acqweight) continue; int wskill = range_skill(OBJ_WEAPONS, i); if (wskill == SK_THROWING) wskill = weapon_skill(OBJ_WEAPONS, i); if (wskill == skill && x_chance_in_y(acqweight, count += acqweight)) type_wanted = i; } } else if (class_wanted == OBJ_MISSILES) { int count = 0; int skill = SK_THROWING; for (int i = SK_SLINGS; i <= SK_DARTS; i++) { if (you.skills[i]) { count += you.skills[i]; if (x_chance_in_y(you.skills[i], count)) skill = i; } } switch (skill) { case SK_SLINGS: type_wanted = MI_STONE; break; case SK_BOWS: type_wanted = MI_ARROW; break; case SK_CROSSBOWS: type_wanted = MI_DART; for (int i = 0; i < ENDOFPACK; i++) { // Assuming that crossbow in inventory means that they // want bolts for it (not darts for a hand crossbow)... // perhaps we should check for both and compare ammo // amounts on hand? if (is_valid_item( you.inv[i] ) && you.inv[i].base_type == OBJ_WEAPONS && you.inv[i].sub_type == WPN_CROSSBOW) { type_wanted = MI_BOLT; break; } } break; case SK_DARTS: type_wanted = MI_DART; for (int i = 0; i < ENDOFPACK; i++) { if (is_valid_item( you.inv[i] ) && you.inv[i].base_type == OBJ_WEAPONS && you.inv[i].sub_type == WPN_BLOWGUN) { // Assuming that blowgun in inventory means that they // may want needles for it (but darts might also be // wanted). Maybe expand this... see above comment. if (coinflip()) type_wanted = MI_NEEDLE; break; } } break; default: type_wanted = MI_DART; break; } } else if (class_wanted == OBJ_ARMOUR) { // Increasing the representation of the non-body armour // slots here to make up for the fact that there's one // one type of item for most of them. -- bwr // // OBJ_RANDOM is body armour and handled below type_wanted = (coinflip())? OBJ_RANDOM : static_cast(_random_nonbody_armour_type()); // Some species specific fitting problems. switch (you.species) { case SP_OGRE: case SP_OGRE_MAGE: case SP_TROLL: case SP_RED_DRACONIAN: case SP_WHITE_DRACONIAN: case SP_GREEN_DRACONIAN: case SP_YELLOW_DRACONIAN: case SP_GREY_DRACONIAN: case SP_BLACK_DRACONIAN: case SP_PURPLE_DRACONIAN: case SP_MOTTLED_DRACONIAN: case SP_PALE_DRACONIAN: case SP_BASE_DRACONIAN: case SP_SPRIGGAN: if (type_wanted == ARM_GLOVES || type_wanted == ARM_BOOTS || type_wanted == ARM_CENTAUR_BARDING || type_wanted == ARM_NAGA_BARDING) { type_wanted = ARM_ROBE; // no heavy armour } else if (type_wanted == ARM_SHIELD) { if (you.species == SP_SPRIGGAN) type_wanted = ARM_BUCKLER; else if (coinflip()) // giant races: 50/50 shield/large shield type_wanted = ARM_LARGE_SHIELD; } else if (type_wanted == OBJ_RANDOM) { type_wanted = ARM_ROBE; // no heavy armour, see below } break; case SP_NAGA: if (type_wanted == ARM_BOOTS || type_wanted == ARM_CENTAUR_BARDING) type_wanted = ARM_NAGA_BARDING; break; case SP_CENTAUR: if (type_wanted == ARM_BOOTS || type_wanted == ARM_NAGA_BARDING) type_wanted = ARM_CENTAUR_BARDING; break; default: if (type_wanted == ARM_CENTAUR_BARDING || type_wanted == ARM_NAGA_BARDING) { type_wanted = ARM_BOOTS; } break; } // Mutation specific problems (horns allow caps). if (type_wanted == ARM_BOOTS && !player_has_feet() || type_wanted == ARM_GLOVES && you.has_claws(false) >= 3) { type_wanted = OBJ_RANDOM; } // Do this here, before acquirement()'s call to can_wear_armour(), // so that caps will be just as common as helmets for those // that can't wear helmets. // We could use player_mutation_level for the horns, but let's just // check for the mutation directly to avoid acquirement fiddles. if (type_wanted == ARM_HELMET && ((you.species >= SP_OGRE && you.species <= SP_OGRE_MAGE) || player_genus(GENPC_DRACONIAN) || you.species == SP_KENKU || you.species == SP_SPRIGGAN || you.mutation[MUT_HORNS])) { type_wanted = coinflip()? ARM_CAP : ARM_WIZARD_HAT; } // Now we'll randomly pick a body armour (light only in the // case of ARM_ROBE). Unlike before, now we're only giving // out the finished products here, never the hides. -- bwr if (type_wanted == OBJ_RANDOM || type_wanted == ARM_ROBE) { // start with normal base armour if (type_wanted == ARM_ROBE) type_wanted = coinflip() ? ARM_ROBE : ARM_ANIMAL_SKIN; else { type_wanted = ARM_ROBE + random2(8); if (one_chance_in(10) && you.skills[SK_ARMOUR] >= 10) type_wanted = ARM_CRYSTAL_PLATE_MAIL; if (one_chance_in(10)) type_wanted = ARM_ANIMAL_SKIN; } // everyone can wear things made from hides if (one_chance_in(20)) { int rnd = random2(20); type_wanted = (rnd < 4) ? ARM_TROLL_LEATHER_ARMOUR : // 20% (rnd < 8) ? ARM_STEAM_DRAGON_ARMOUR : // 20% (rnd < 11) ? ARM_MOTTLED_DRAGON_ARMOUR : // 15% (rnd < 14) ? ARM_SWAMP_DRAGON_ARMOUR : // 15% (rnd < 16) ? ARM_DRAGON_ARMOUR : // 10% (rnd < 18) ? ARM_ICE_DRAGON_ARMOUR : // 10% (rnd < 19) ? ARM_STORM_DRAGON_ARMOUR // 5% : ARM_GOLD_DRAGON_ARMOUR; // 5% } } } else if (class_wanted != OBJ_GOLD) { do { unsigned char i; switch (class_wanted) { case OBJ_JEWELLERY: // Try for a base type the player hasn't identified for (i = 0; i < 10; i++) { type_wanted = random2(24); if (one_chance_in(3)) type_wanted = AMU_RAGE + random2(10); if (get_ident_type(OBJ_JEWELLERY, type_wanted) == ID_UNKNOWN_TYPE) { break; } } break; case OBJ_BOOKS: // Remember, put rarer books higher in the list. iteration = 1; type_wanted = NUM_BOOKS; best_spell = best_skill( SK_SPELLCASTING, (NUM_SKILLS - 1), best_spell ); which_book: #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "acquirement: iteration = %d, best_spell = %d", iteration, best_spell ); #endif switch (best_spell) { default: case SK_SPELLCASTING: if (you.skills[SK_SPELLCASTING] <= 3 && !you.had_book[BOOK_CANTRIPS]) { // Handful of level one spells, very useful for the // new spellcaster who's asking for a book -- bwr type_wanted = BOOK_CANTRIPS; } else if (!you.had_book[BOOK_MINOR_MAGIC_I]) type_wanted = BOOK_MINOR_MAGIC_I + random2(3); else if (!you.had_book[BOOK_WIZARDRY]) type_wanted = BOOK_WIZARDRY; else if (!you.had_book[BOOK_CONTROL]) type_wanted = BOOK_CONTROL; else if (!you.had_book[BOOK_POWER]) type_wanted = BOOK_POWER; break; case SK_POISON_MAGIC: if (!you.had_book[BOOK_YOUNG_POISONERS]) type_wanted = BOOK_YOUNG_POISONERS; else if (!you.had_book[BOOK_ENVENOMATIONS]) type_wanted = BOOK_ENVENOMATIONS; break; case SK_EARTH_MAGIC: if (!you.had_book[BOOK_GEOMANCY]) type_wanted = BOOK_GEOMANCY; else if (!you.had_book[BOOK_EARTH]) type_wanted = BOOK_EARTH; break; case SK_AIR_MAGIC: // removed the book of clouds... all the other elements // (and most other spell skills) only get two. if (!you.had_book[BOOK_AIR]) type_wanted = BOOK_AIR; else if (!you.had_book[BOOK_SKY]) type_wanted = BOOK_SKY; break; case SK_ICE_MAGIC: if (!you.had_book[BOOK_FROST]) type_wanted = BOOK_FROST; else if (!you.had_book[BOOK_ICE]) type_wanted = BOOK_ICE; break; case SK_FIRE_MAGIC: if (!you.had_book[BOOK_FLAMES]) type_wanted = BOOK_FLAMES; else if (!you.had_book[BOOK_FIRE]) type_wanted = BOOK_FIRE; break; case SK_SUMMONINGS: if (!you.had_book[BOOK_CALLINGS]) type_wanted = BOOK_CALLINGS; else if (!you.had_book[BOOK_SUMMONINGS]) type_wanted = BOOK_SUMMONINGS; // now a Vehumet special -- bwr // else if (!you.had_book[BOOK_DEMONOLOGY]) // type_wanted = BOOK_DEMONOLOGY; break; case SK_ENCHANTMENTS: best_any = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99); // So many enchantment books! I really can't feel // guilty at all for dividing out the fighting // books and forcing the player to raise a fighting // skill (or enchantments in the case of Crusaders) // to get the remaining books... enchantments are // much too good (most spells, lots of books here, // id wand charges, gives magic resistance), // something will eventually have to be done. -- bwr if (best_any >= SK_FIGHTING && best_any <= SK_STAVES) { // Fighter mages get the fighting enchantment books if (!you.had_book[BOOK_WAR_CHANTS]) type_wanted = BOOK_WAR_CHANTS; else if (!you.had_book[BOOK_TUKIMA]) type_wanted = BOOK_TUKIMA; } else if (!you.had_book[BOOK_CHARMS]) type_wanted = BOOK_CHARMS; else if (!you.had_book[BOOK_HINDERANCE]) type_wanted = BOOK_HINDERANCE; else if (!you.had_book[BOOK_ENCHANTMENTS]) type_wanted = BOOK_ENCHANTMENTS; break; case SK_CONJURATIONS: if (!you.had_book[BOOK_CONJURATIONS_I]) type_wanted = give_first_conjuration_book(); else if (!you.had_book[BOOK_TEMPESTS]) type_wanted = BOOK_TEMPESTS; // now a Vehumet special -- bwr // else if (!you.had_book[BOOK_ANNIHILATIONS]) // type_wanted = BOOK_ANNIHILATIONS; break; case SK_NECROMANCY: if (!you.had_book[BOOK_NECROMANCY]) type_wanted = BOOK_NECROMANCY; else if (!you.had_book[BOOK_DEATH]) type_wanted = BOOK_DEATH; else if (!you.had_book[BOOK_UNLIFE]) type_wanted = BOOK_UNLIFE; // now a Kikubaaqudgha special -- bwr // else if (!you.had_book[BOOK_NECRONOMICON]) // type_wanted = BOOK_NECRONOMICON; break; case SK_TRANSLOCATIONS: if (!you.had_book[BOOK_SPATIAL_TRANSLOCATIONS]) type_wanted = BOOK_SPATIAL_TRANSLOCATIONS; else if (!you.had_book[BOOK_WARP]) type_wanted = BOOK_WARP; break; case SK_TRANSMIGRATION: if (!you.had_book[BOOK_CHANGES]) type_wanted = BOOK_CHANGES; else if (!you.had_book[BOOK_TRANSFIGURATIONS]) type_wanted = BOOK_TRANSFIGURATIONS; else if (!you.had_book[BOOK_MUTATIONS]) type_wanted = BOOK_MUTATIONS; break; case SK_DIVINATIONS: //jmf: added 24mar2000 if (!you.had_book[BOOK_SURVEYANCES]) type_wanted = BOOK_SURVEYANCES; else if (!you.had_book[BOOK_DIVINATIONS]) type_wanted = BOOK_DIVINATIONS; break; } /* if (type_wanted == 99 && glof == best_skill(SK_SPELLCASTING, (NUM_SKILLS - 1), 99)) */ if (type_wanted == NUM_BOOKS && iteration == 1) { best_spell = best_skill( SK_SPELLCASTING, NUM_SKILLS - 1, best_skill(SK_SPELLCASTING, NUM_SKILLS - 1, 99) ); iteration++; goto which_book; } // If we don't have a book, try and get a new one. if (type_wanted == NUM_BOOKS) { do { type_wanted = random2(NUM_BOOKS); if (one_chance_in(500)) break; } while (you.had_book[type_wanted]); } // If the book is invalid find any valid one. while (book_rarity(type_wanted) == 100 || type_wanted == BOOK_DESTRUCTION || type_wanted == BOOK_MANUAL) { type_wanted = random2(NUM_BOOKS); } break; case OBJ_STAVES: type_wanted = random2(13); if (type_wanted >= 10) type_wanted = STAFF_AIR + type_wanted - 10; // Elemental preferences -- bwr if (type_wanted == STAFF_FIRE || type_wanted == STAFF_COLD) { if (you.skills[SK_FIRE_MAGIC] > you.skills[SK_ICE_MAGIC]) type_wanted = STAFF_FIRE; else if (you.skills[SK_FIRE_MAGIC] != you.skills[SK_ICE_MAGIC]) type_wanted = STAFF_COLD; } else if (type_wanted == STAFF_AIR || type_wanted == STAFF_EARTH) { if (you.skills[SK_AIR_MAGIC] > you.skills[SK_EARTH_MAGIC]) type_wanted = STAFF_AIR; else if (you.skills[SK_AIR_MAGIC] != you.skills[SK_EARTH_MAGIC]) type_wanted = STAFF_EARTH; } best_spell = best_skill( SK_SPELLCASTING, (NUM_SKILLS-1), 99 ); // If we're going to give out an enhancer staff, // we should at least bias things towards the // best spell skill. -- bwr switch (best_spell) { case SK_FIRE_MAGIC: if (!already_has[STAFF_FIRE]) type_wanted = STAFF_FIRE; break; case SK_ICE_MAGIC: if (!already_has[STAFF_COLD]) type_wanted = STAFF_COLD; break; case SK_AIR_MAGIC: if (!already_has[STAFF_AIR]) type_wanted = STAFF_AIR; break; case SK_EARTH_MAGIC: if (!already_has[STAFF_EARTH]) type_wanted = STAFF_EARTH; break; case SK_POISON_MAGIC: if (!already_has[STAFF_POISON]) type_wanted = STAFF_POISON; break; case SK_NECROMANCY: if (!already_has[STAFF_DEATH]) type_wanted = STAFF_DEATH; break; case SK_CONJURATIONS: if (!already_has[STAFF_CONJURATION]) type_wanted = STAFF_CONJURATION; break; case SK_ENCHANTMENTS: if (!already_has[STAFF_ENCHANTMENT]) type_wanted = STAFF_ENCHANTMENT; break; case SK_SUMMONINGS: if (!already_has[STAFF_SUMMONING]) type_wanted = STAFF_SUMMONING; break; case SK_EVOCATIONS: if (!one_chance_in(4)) type_wanted = random_rod_subtype(); break; default: // Invocations and leftover spell schools. switch (random2(5)) { case 0: type_wanted = STAFF_WIZARDRY; break; case 1: type_wanted = STAFF_POWER; break; case 2: type_wanted = STAFF_ENERGY; break; case 3: type_wanted = STAFF_CHANNELING; break; case 4: break; } break; } // Increased chance of getting a rod for new or // non-spellcasters. -- bwr if (one_chance_in(20) || (spell_skills <= 1 // short on spells && type_wanted < STAFF_SMITING && !one_chance_in(4))) { type_wanted = coinflip() ? STAFF_STRIKING : random_rod_subtype(); } break; case OBJ_MISCELLANY: do { type_wanted = random2(NUM_MISCELLANY); } while (type_wanted == MISC_HORN_OF_GERYON || type_wanted == MISC_RUNE_OF_ZOT || type_wanted == MISC_CRYSTAL_BALL_OF_FIXATION || type_wanted == MISC_EMPTY_EBONY_CASKET); break; default: break; } ASSERT( type_wanted < max_has_value ); } while (already_has[type_wanted] && !one_chance_in(200)); } return (type_wanted); } bool acquirement(object_class_type class_wanted, int agent, bool quiet, int* item_index) { int thing_created = NON_ITEM; if (item_index == NULL) item_index = &thing_created; *item_index = NON_ITEM; while (class_wanted == OBJ_RANDOM) { ASSERT(!quiet); mesclr(); mpr( "[a] Weapon [b] Armour [c] Jewellery [d] Book" ); mpr( "[e] Staff [f] Food [g] Miscellaneous [h] Gold" ); mpr("What kind of item would you like to acquire? ", MSGCH_PROMPT); const int keyin = tolower( get_ch() ); switch (keyin) { case 'a': class_wanted = OBJ_WEAPONS; break; case 'b': class_wanted = OBJ_ARMOUR; break; case 'c': class_wanted = OBJ_JEWELLERY; break; case 'd': class_wanted = OBJ_BOOKS; break; case 'e': class_wanted = OBJ_STAVES; break; case 'f': class_wanted = OBJ_FOOD; break; case 'g': class_wanted = OBJ_MISCELLANY; break; case 'h': class_wanted = OBJ_GOLD; break; default: break; } } if (grid_destroys_items(grd(you.pos()))) { // How sad (and stupid). if (!silenced(you.pos()) && !quiet) mprf(MSGCH_SOUND, grid_item_destruction_message(grd(you.pos()))); item_was_destroyed(mitm[igrd(you.pos())], NON_MONSTER); *item_index = NON_ITEM; } else { int quant = 1; for (int item_tries = 0; item_tries < 40; item_tries++) { int type_wanted = _find_acquirement_subtype(class_wanted, quant); // Clobber class_wanted for vampires. if (you.species == SP_VAMPIRE && class_wanted == OBJ_FOOD) class_wanted = OBJ_POTIONS; thing_created = items( 1, class_wanted, type_wanted, true, MAKE_GOOD_ITEM, MAKE_ITEM_RANDOM_RACE ); if (thing_created == NON_ITEM) continue; const item_def &doodad(mitm[thing_created]); if (doodad.base_type == OBJ_WEAPONS && !can_wield(&doodad, false, true) || doodad.base_type == OBJ_ARMOUR && !can_wear_armour(doodad, false, true)) { destroy_item(thing_created, true); thing_created = NON_ITEM; continue; } // Only TSO gifts blessed blades, and currently not through // acquirement, but make sure of this anyway. if (agent != GOD_SHINING_ONE && is_blessed_blade(doodad)) { destroy_item(thing_created, true); thing_created = NON_ITEM; continue; } // Trog does not gift the Wrath of Trog, nor weapons of pain // (which work together with Necromantic magic). if (agent == GOD_TROG) { int brand = get_weapon_brand(doodad); if (brand == SPWPN_PAIN || is_fixed_artefact(doodad) && (doodad.special == SPWPN_WRATH_OF_TROG || doodad.special == SPWPN_STAFF_OF_WUCAD_MU)) { destroy_item(thing_created, true); thing_created = NON_ITEM; continue; } } // MT - Check: god-gifted weapons and armor shouldn't kill you. // Except Xom. if ((agent == GOD_TROG || agent == GOD_OKAWARU) && is_random_artefact(doodad)) { randart_properties_t proprt; randart_wpn_properties( doodad, proprt ); // Check vs stats. positive stats will automatically fall // through. As will negative stats that won't kill you. if (-proprt[RAP_STRENGTH] >= you.strength || -proprt[RAP_INTELLIGENCE] >= you.intel || -proprt[RAP_DEXTERITY] >= you.dex) { // Try again. destroy_item(thing_created); thing_created = NON_ITEM; continue; } } break; } if (thing_created == NON_ITEM) { if (!quiet) mpr("The demon of the infinite void smiles upon you."); *item_index = NON_ITEM; return (false); } // Easier to read this way. item_def& thing(mitm[thing_created]); // Give some more gold. if (class_wanted == OBJ_GOLD) thing.quantity += 150; else if (quant > 1) thing.quantity = quant; if (is_blood_potion(thing)) init_stack_blood_potions(thing); // Remove curse flag from item. do_uncurse_item( thing ); if (thing.base_type == OBJ_BOOKS) { mark_had_book(thing.sub_type); } else if (thing.base_type == OBJ_JEWELLERY) { switch (thing.sub_type) { case RING_SLAYING: // Make sure plus to damage is >= 1. thing.plus2 = abs( thing.plus2 ); if (thing.plus2 == 0) thing.plus2 = 1; // fall through... case RING_PROTECTION: case RING_STRENGTH: case RING_INTELLIGENCE: case RING_DEXTERITY: case RING_EVASION: // Make sure plus is >= 1. thing.plus = abs( thing.plus ); if (thing.plus == 0) thing.plus = 1; break; case RING_HUNGER: case AMU_INACCURACY: // These are the only truly bad pieces of jewellery. if (!one_chance_in(9)) make_item_randart( thing ); break; default: break; } } else if (thing.base_type == OBJ_WEAPONS && !is_fixed_artefact( thing ) && !is_unrandom_artefact( thing )) { // HACK: Make unwieldable weapons wieldable. // Note: messing with fixed artefacts is probably very bad. switch (you.species) { case SP_DEMONSPAWN: case SP_MUMMY: case SP_GHOUL: case SP_VAMPIRE: { int brand = get_weapon_brand( thing ); if (brand == SPWPN_HOLY_WRATH) { if (!is_random_artefact( thing )) { set_item_ego_type( thing, OBJ_WEAPONS, SPWPN_VORPAL ); } else { // Keep resetting seed until it's good. for (; brand == SPWPN_HOLY_WRATH; brand = get_weapon_brand(thing)) { make_item_randart( thing ); } } } } break; case SP_HALFLING: case SP_GNOME: case SP_KOBOLD: case SP_SPRIGGAN: switch (thing.sub_type) { case WPN_LONGBOW: thing.sub_type = WPN_BOW; break; case WPN_GREAT_SWORD: case WPN_TRIPLE_SWORD: thing.sub_type = (coinflip() ? WPN_FALCHION : WPN_LONG_SWORD); break; case WPN_GREAT_MACE: case WPN_DIRE_FLAIL: thing.sub_type = (coinflip() ? WPN_MACE : WPN_FLAIL); break; case WPN_BATTLEAXE: case WPN_EXECUTIONERS_AXE: thing.sub_type = (coinflip() ? WPN_HAND_AXE : WPN_WAR_AXE); break; case WPN_HALBERD: case WPN_GLAIVE: case WPN_SCYTHE: case WPN_BARDICHE: thing.sub_type = (coinflip() ? WPN_SPEAR : WPN_TRIDENT); break; } break; default: break; } int plusmod = random2(4); if (agent == GOD_TROG) { // More damage, less accuracy. thing.plus -= plusmod; thing.plus2 += plusmod; if (!is_random_artefact(thing)) thing.plus = std::max(static_cast(thing.plus), 0); } else if (agent == GOD_OKAWARU) { // More accuracy, less damage. thing.plus += plusmod; thing.plus2 -= plusmod; if (!is_random_artefact(thing)) thing.plus2 = std::max(static_cast(thing.plus2), 0); } } if (agent > AQ_SCROLL && agent == you.religion) { thing.inscription = "god gift"; if (is_random_artefact(thing)) { origin_acquired(mitm[thing_created], agent); // give another name that takes god gift into account thing.props["randart_name"].get_string() = randart_name(thing, false); } } move_item_to_grid( &thing_created, you.x_pos, you.y_pos ); // This should never actually be NON_ITEM because of the way // move_item_to_grid works (doesn't create a new item ever), // but we're checking it anyways. -- bwr if (thing_created != NON_ITEM) { if (!quiet) canned_msg(MSG_SOMETHING_APPEARS); origin_acquired(mitm[thing_created], agent); } *item_index = thing_created; } // Well, the item may have fallen in the drink, but the intent is // that acquirement happened. -- bwr return (true); } // end acquirement() bool recharge_wand(int item_slot) { if (item_slot == -1) { item_slot = prompt_invent_item( "Charge which item?", MT_INVLIST, OSEL_RECHARGE, true, true, false ); } if (prompt_failed(item_slot)) return (false); item_def &wand = you.inv[ item_slot ]; // Weapons of electrocution can be "charged", i.e. gain +1 damage. if (wand.base_type == OBJ_WEAPONS && get_weapon_brand(wand) == SPWPN_ELECTROCUTION) { // Might fail because of already high enchantment. if (enchant_weapon( ENCHANT_TO_DAM, false, wand )) { you.wield_change = true; if (!item_ident(wand, ISFLAG_KNOW_TYPE)) set_ident_flags(wand, ISFLAG_KNOW_TYPE); return (true); } return (false); } if (wand.base_type != OBJ_WANDS && !item_is_rod(wand)) return (false); int charge_gain = 0; if (wand.base_type == OBJ_WANDS) { switch (wand.sub_type) { case WAND_INVISIBILITY: case WAND_FIREBALL: case WAND_TELEPORTATION: case WAND_HEALING: case WAND_HASTING: charge_gain = 3; break; case WAND_LIGHTNING: case WAND_DRAINING: charge_gain = 4; break; case WAND_FIRE: case WAND_COLD: charge_gain = 5; break; default: charge_gain = 8; break; } // Don't display zap counts any more. wand.plus2 = ZAPCOUNT_UNKNOWN; const int new_charges = std::max( wand.plus, std::min(charge_gain * 3, wand.plus + 1 + random2avg( ((charge_gain - 1) * 3) + 1, 3 ))); const bool charged = new_charges > wand.plus; mprf("%s %s for a moment.", wand.name(DESC_CAP_YOUR).c_str(), charged? "glows" : "flickers"); wand.plus = new_charges; } else // It's a rod. { bool work = false; if (wand.plus2 <= MAX_ROD_CHARGE * ROD_CHARGE_MULT) { wand.plus2 += ROD_CHARGE_MULT; if (wand.plus2 > MAX_ROD_CHARGE * ROD_CHARGE_MULT) wand.plus2 = MAX_ROD_CHARGE * ROD_CHARGE_MULT; work = true; } if (wand.plus < wand.plus2) { wand.plus = wand.plus2; work = true; } if (!work) return (false); mprf("%s glows for a moment.", wand.name(DESC_CAP_YOUR).c_str()); } you.wield_change = true; return (true); } // end recharge_wand() // Sets foe target of friendly monsters. // If allow_patrol is true, patrolling monsters get MHITNOT instead. static void _set_friendly_foes(bool allow_patrol = false) { for (int i = 0; i < MAX_MONSTERS; ++i) { monsters *mon(&menv[i]); if (!mon->alive() || !mons_near(mon) || !mons_friendly(mon)) continue; mon->foe = (allow_patrol && mon->is_patrolling() ? MHITNOT : you.pet_target); } } static void _set_allies_patrol_point(bool clear = false) { for (int i = 0; i < MAX_MONSTERS; ++i) { monsters *mon(&menv[i]); if (!mon->alive() || !mons_near(mon) || !mons_friendly(mon)) continue; mon->patrol_point = (clear ? coord_def(0, 0) : coord_def(mon->x, mon->y)); if (!clear) mon->behaviour = BEH_WANDER; } } void yell(bool force) { bool targ_prev = false; int mons_targd = MHITNOT; struct dist targ; const std::string shout_verb = you.shout_verb(); std::string cap_shout = shout_verb; cap_shout[0] = toupper(cap_shout[0]); int noise_level = 12; // "shout" // Tweak volume for different kinds of vocalisation. if (shout_verb == "roar") noise_level = 18; else if (shout_verb == "hiss") noise_level = 8; else if (shout_verb == "squeak") noise_level = 4; else if (shout_verb == "__NONE") noise_level = 0; else if (shout_verb == "yell") noise_level = 14; else if (shout_verb == "scream") noise_level = 16; if (silenced(you.x_pos, you.y_pos) || you.cannot_speak()) noise_level = 0; if (noise_level == 0) { if (force) { if (shout_verb == "__NONE" || you.paralysed()) { mprf("You feel a strong urge to %s, but " "you are unable to make a sound!", shout_verb == "__NONE" ? "scream" : shout_verb.c_str()); } else { mprf("You feel a %s rip itself from your throat, " "but you make no sound!", shout_verb.c_str()); } } else mpr("You are unable to make a sound!"); return; } if (force) { mprf("A %s rips itself from your throat!", shout_verb.c_str()); noisy(noise_level, you.x_pos, you.y_pos); return; } mpr("What do you say?", MSGCH_PROMPT); mprf(" t - %s!", cap_shout.c_str()); if (!you.duration[DUR_BERSERKER]) { std::string previous = ""; if (!(you.prev_targ == MHITNOT || you.prev_targ == MHITYOU)) { monsters *target = &menv[you.prev_targ]; if (target->alive() && mons_near(target) && player_monster_visible(target)) { previous = " p - Attack previous target."; targ_prev = true; } } mprf("Orders for allies: a - Attack new target.%s", previous.c_str()); mpr( " s - Stop attacking."); mpr( " w - Wait here. f - Follow me."); } mprf(" Anything else - Stay silent%s.", one_chance_in(20)? " (and be thought a fool)" : ""); unsigned char keyn = get_ch(); mesclr(); switch (keyn) { case '!': // for players using the old keyset case 't': mprf(MSGCH_SOUND, "You %s for attention!", shout_verb.c_str()); noisy(noise_level, you.x_pos, you.y_pos); you.turn_is_over = true; return; case 'f': case 's': mons_targd = MHITYOU; if (keyn == 'f') { // Don't reset patrol points for 'Stop fighting!' _set_allies_patrol_point(true); mpr("Follow me!"); } else mpr("Stop fighting!"); break; case 'w': mpr("Wait here!"); mons_targd = MHITNOT; _set_allies_patrol_point(); break; case 'p': if (you.duration[DUR_BERSERKER]) { canned_msg(MSG_TOO_BERSERK); return; } if (targ_prev) { mons_targd = you.prev_targ; break; } // fall through case 'a': if (you.duration[DUR_BERSERKER]) { canned_msg(MSG_TOO_BERSERK); return; } if (env.sanctuary_time > 0) { if (!yesno("An ally attacking under your orders might violate " "sanctuary; order anyway?", false, 'n')) { canned_msg(MSG_OK); return; } } mpr("Gang up on whom?", MSGCH_PROMPT); direction( targ, DIR_TARGET, TARG_ENEMY, -1, false, false ); if (targ.isCancel) { canned_msg(MSG_OK); return; } if (!targ.isValid || mgrd[targ.tx][targ.ty] == NON_MONSTER || !player_monster_visible(&env.mons[mgrd[targ.tx][targ.ty]])) { mpr("Yeah, whatever."); return; } mons_targd = mgrd[targ.tx][targ.ty]; break; default: mpr("Okely-dokely."); return; } you.pet_target = mons_targd; // Allow patrolling for "Stop fighting!" and "Wait here!" _set_friendly_foes(keyn == 's' || keyn == 'w'); if (mons_targd != MHITNOT && mons_targd != MHITYOU) mpr("Attack!"); noisy(10, you.x_pos, you.y_pos); } // end yell() bool forget_inventory(bool quiet) { int items_forgotten = 0; for (int i = 0; i < ENDOFPACK; i++) { item_def& item(you.inv[i]); if (!is_valid_item(item) || item_is_equipped(item)) continue; unsigned long orig_flags = item.flags; unset_ident_flags(item, ISFLAG_KNOW_CURSE); // Don't forget times used or uses left for wands or decks. if (item.base_type != OBJ_WANDS && item.base_type != OBJ_MISCELLANY) unset_ident_flags(item, ISFLAG_KNOW_PLUSES); if (!is_artefact(item)) { switch (item.base_type) { case OBJ_WEAPONS: case OBJ_ARMOUR: case OBJ_BOOKS: case OBJ_STAVES: case OBJ_MISCELLANY: // Don't forget identity of decks if the player has // used any of its cards, or knows how many are left. if (!is_deck(item) || item.plus2 == 0) unset_ident_flags(item, ISFLAG_KNOW_TYPE); break; default: break; } } // Non-jewellery artefacts can easily be re-identified by // equipping them. else if (item.base_type != OBJ_JEWELLERY) unset_ident_flags(item, ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES); if (item.flags != orig_flags) items_forgotten++; } if (items_forgotten > 0) mpr("Wait, did you forget something?"); return (items_forgotten > 0); } // Returns true if there was a visible change. bool vitrify_area(int radius) { if (radius < 2) return (false); const int radius2 = radius * radius; // This hinges on clear wall types having the same order as non-clear ones! const int clear_plus = DNGN_CLEAR_ROCK_WALL - DNGN_ROCK_WALL; bool something_happened = false; for (int x = X_BOUND_1; x <= X_BOUND_2; ++x) for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y) { if (distance(x,y,you.x_pos,you.y_pos) < radius2) { dungeon_feature_type grid = grd[x][y]; if (grid == DNGN_ROCK_WALL || grid == DNGN_STONE_WALL || grid == DNGN_PERMAROCK_WALL ) { grd[x][y] = static_cast(grid + clear_plus); something_happened = true; } } } return (something_happened); } static void _hell_effects() { if (is_sanctuary(you.x_pos, you.y_pos)) { mpr("Zin's power protects you from Hell's scourges!", MSGCH_GOD); return; } int temp_rand = random2(17); spschool_flag_type which_miscast = SPTYP_RANDOM; bool summon_instead = false; monster_type which_beastie = MONS_PROGRAM_BUG; 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!\"" : // plain messages (temp_rand == 7) ? (player_can_smell()) ? "You smell brimstone." : "Brimstone rains from above." : (temp_rand == 8) ? "You feel lost and a long, long way from home..." : (temp_rand == 9) ? "You shiver with fear." : // warning (temp_rand == 10) ? "You feel a terrible foreboding..." : (temp_rand == 11) ? "Something frightening happens." : (temp_rand == 12) ? "You sense an ancient evil watching you..." : (temp_rand == 13) ? "You suddenly feel all small and vulnerable." : (temp_rand == 14) ? "You sense a hostile presence." : // sounds (temp_rand == 15) ? "A gut-wrenching scream fills the air!" : (temp_rand == 16) ? "You hear words spoken in a strange and terrible language..." : "You hear diabolical laughter!", (temp_rand < 7 ? MSGCH_TALK : temp_rand < 10 ? MSGCH_PLAIN : temp_rand < 15 ? MSGCH_WARN : MSGCH_SOUND)); 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 = x_chance_in_y(2, 5); 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( mgen_data::hostile_at(which_beastie, you.pos(), 0, 0, true)); } 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)) { mgen_data mg; mg.pos = you.pos(); mg.foe = MHITYOU; create_monster(mg); for (int i = 0; i < 4; ++i) if (one_chance_in(3)) create_monster(mg); } } static bool _food_item_needs_time_check(item_def &item) { if (!is_valid_item(item)) return (false); if (item.base_type != OBJ_CORPSES && item.base_type != OBJ_FOOD && item.base_type != OBJ_POTIONS) { return (false); } if (item.base_type == OBJ_CORPSES && item.sub_type > CORPSE_SKELETON) { return (false); } if (item.base_type == OBJ_FOOD && item.sub_type != FOOD_CHUNK) return (false); if (item.base_type == OBJ_POTIONS && !is_blood_potion(item)) return (false); return (true); } 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; std::vector rotten_items; for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].quantity < 1) continue; 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) { if (you.equip[EQ_WEAPON] == i) unwield_item(); destroy_item(you.inv[i]); burden_changed_by_rot = true; continue; } // Carried skeletons are not destroyed. if (you.inv[i].sub_type == CORPSE_SKELETON) continue; if (!mons_skeleton( you.inv[i].plus )) { if (you.equip[EQ_WEAPON] == i) unwield_item(); destroy_item(you.inv[i]); burden_changed_by_rot = true; continue; } turn_corpse_into_skeleton(you.inv[i]); you.wield_change = true; burden_changed_by_rot = true; continue; } // if it hasn't disappeared, reduce the rotting timer you.inv[i].special -= (time_delta / 20); if (food_is_rotten(you.inv[i]) && (you.inv[i].special + (time_delta / 20) >= 100 )) { rotten_items.push_back(index_to_letter( i )); } } //mv: messages when chunks/corpses become rotten if (!rotten_items.empty()) { std::string msg = ""; // Races that can't smell don't care, and trolls are stupid and // don't care. if (player_can_smell() && you.species != SP_TROLL) { int temp_rand = 0; // Grr. int level = player_mutation_level(MUT_SAPROVOROUS); if (!level && you.species == SP_VAMPIRE) level = 1; switch (level) { // level 1 and level 2 saprovores, as well as vampires, aren't so touchy case 1: case 2: temp_rand = random2(8); msg = (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."; break; // level 3 saprovores like it case 3: temp_rand = random2(8); msg = (temp_rand < 5) ? "You smell something rotten." : (temp_rand == 5) ? "The smell of rotting flesh makes you hungry." : (temp_rand == 6) ? "You smell decay. Yum-yum." : "Wow! There is something tasty in your inventory."; break; default: temp_rand = random2(8); msg = (temp_rand < 5) ? "You smell something rotten." : (temp_rand == 5) ? "The smell of rotting flesh makes you sick." : (temp_rand == 6) ? "You smell decay. Yuck!" : "Ugh! There is something really disgusting in your inventory."; break; } } else if (Options.list_rotten) msg = "Something in your inventory has become rotten."; if (Options.list_rotten) { mprf(MSGCH_ROTTEN_MEAT, "%s (slot%s %s)", msg.c_str(), rotten_items.size() > 1 ? "s" : "", comma_separated_line(rotten_items.begin(), rotten_items.end()).c_str()); } else if (!msg.empty()) mpr(msg.c_str(), MSGCH_ROTTEN_MEAT); learned_something_new(TUT_ROTTEN_FOOD); } if (burden_changed_by_rot) { mpr("Your equipment suddenly weighs less.", MSGCH_ROTTEN_MEAT); burden_change(); } } // Do various time related actions... // This function is called about every 20 turns. void handle_time(long time_delta) { // Nasty things happen to people who spend too long in Hell. if (player_in_hell() && coinflip()) _hell_effects(); // 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)) restore_stat(STAT_STRENGTH, 0, false, true); if (you.intel < you.max_intel && one_chance_in(100)) restore_stat(STAT_INTELLIGENCE, 0, false, true); if (you.dex < you.max_dex && one_chance_in(100)) restore_stat(STAT_DEXTERITY, 0, false, true); } else { if (one_chance_in(30)) { mpr("Your disease is taking its toll.", MSGCH_WARN); lose_stat(STAT_RANDOM, 1, false, "disease"); } } // Adjust the player's stats if s/he has the deterioration mutation. if (player_mutation_level(MUT_DETERIORATION) && x_chance_in_y(player_mutation_level(MUT_DETERIORATION) * 5 - 1, 200)) { lose_stat(STAT_RANDOM, 1, false, "deterioration mutation"); } 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.duration[DUR_INVIS] && x_chance_in_y(6, 10)) added_contamination++; if (you.duration[DUR_HASTE] && !you.duration[DUR_BERSERKER] && x_chance_in_y(6, 10)) { added_contamination++; } bool mutagenic_randart = false; if (const int randart_glow = scan_randarts(RAP_MUTAGENIC)) { // Reduced randart glow. Note that one randart will contribute // 2 - 5 units of glow to randart_glow. A randart with a mutagen // index of 2 does about 0.58 points of contamination per turn. // A randart with a mutagen index of 5 does about 0.7 points of // contamination per turn. const int mean_glow = 500 + randart_glow * 40; const int actual_glow = mean_glow / 2 + random2(mean_glow); added_contamination += div_rand_round(actual_glow, 1000); mutagenic_randart = true; } // we take off about .5 points per turn if (!you.duration[DUR_INVIS] && !you.duration[DUR_HASTE] && coinflip()) added_contamination--; // only punish if contamination caused by mutagenic randarts // (haste and invisibility already penalized earlier) contaminate_player( added_contamination, mutagenic_randart ); // only check for badness once every other turn if (coinflip()) { // [ds] Be less harsh with glow mutation; Brent and Mark Mackey note // that the commented out random2(X) <= MC check was a bug. I've // uncommented it but dropped the roll sharply from 150. (Brent used // the original roll of 150 for 4.1.2, but I think players are // sufficiently used to beta 26's unkindness that we can use a lower // roll.) if (is_sanctuary(you.x_pos, you.y_pos) && you.magic_contamination >= 5 && x_chance_in_y(you.magic_contamination + 1, 25)) { mpr("Your body momentarily shudders from a surge of wild " "energies until Zin's power calms it.", MSGCH_GOD); } else if (you.magic_contamination >= 5 && x_chance_in_y(you.magic_contamination + 1, 25)) { 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 >= 10 && coinflip()) { struct bolt boom; boom.type = dchar_glyph(DCHAR_FIRED_BURST); boom.colour = BLACK; boom.flavour = BEAM_RANDOM; boom.target_x = you.x_pos; boom.target_y = you.y_pos; // Undead enjoy extra contamination explosion damage because // the magical contamination has a harder time dissipating // through non-living flesh. :-) boom.damage = dice_def(3, you.magic_contamination * (you.is_undead ? 4 : 2) / 4); 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; boom.name = "magical storm"; boom.ench_power = (you.magic_contamination * 5); boom.ex_size = std::min(9, you.magic_contamination / 15); explosion(boom); } // we want to warp the player, not do good stuff! if (one_chance_in(5)) mutate(RANDOM_MUTATION); else give_bad_mutation(true, 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_type_known( you.inv[you.equip[EQ_WEAPON]] ) && 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 (x_chance_in_y(total_skill, 100)) { item_def& item = you.inv[you.equip[EQ_WEAPON]]; set_ident_type( OBJ_STAVES, item.sub_type, ID_KNOWN_TYPE ); set_ident_flags( item, ISFLAG_IDENT_MASK ); mprf("You are wielding %s.", item.name(DESC_NOCAP_A).c_str()); more(); you.wield_change = true; } } // Check to see if an upset god wants to do something to the player. handle_god_time(); if (player_mutation_level(MUT_SCREAM) && x_chance_in_y(3 + player_mutation_level(MUT_SCREAM) * 3, 100)) { yell(true); } // Update all of the corpses, food chunks and potions of blood on // the floor. update_corpses(time_delta); _rot_inventory_food(time_delta); // Exercise armour *xor* stealth skill: {dlb} if (!player_light_armour(true)) { // lowered random roll from 7 to 6 -- bwross if (random2(1000) > item_mass(you.inv[you.equip[EQ_BODY_ARMOUR]]) && one_chance_in(6)) { exercise(SK_ARMOUR, 1); } } // Exercise stealth skill: else if (you.burden_state == BS_UNENCUMBERED && !you.duration[DUR_BERSERKER] && you.special_wield != SPWLD_SHADOW) { // Diminishing returns for stealth training by waiting. if ((you.equip[EQ_BODY_ARMOUR] == -1 || you.equip[EQ_BODY_ARMOUR] != -1 && random2(item_mass(you.inv[you.equip[EQ_BODY_ARMOUR]])) < 100) && you.skills[SK_STEALTH] <= 2 + random2(3) && one_chance_in(18)) { exercise(SK_STEALTH, 1); } } spawn_random_monsters(); } // Move monsters around to fake them walking around while player was // off-level. static void _catchup_monster_moves(monsters *mon, int turns) { // Summoned monsters might have disappeared. if (!mon->alive()) return; // Don't move non-land or stationary monsters around. if (mons_habitat(mon) != HT_LAND || mons_is_zombified(mon) && mons_habitat_by_type(mon->base_monster) != HT_LAND || mons_is_stationary(mon)) { return; } // Let sleeping monsters lie. if (mons_is_sleeping(mon) || mons_is_paralysed(mon)) return; 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 mprf(MSGCH_DIAGNOSTICS, "mon #%d: range %d; long %d; " "pos (%d,%d); targ %d(%d,%d); flags %ld", monster_index(mon), range, long_time, mon->x, mon->y, mon->foe, mon->target_x, mon->target_y, mon->flags ); #endif if (range <= 0) return; if (long_time && (mons_is_fleeing(mon) || mons_is_cornered(mon) || mons_is_batty(mon) || ranged_attack || coinflip())) { if (!mons_is_wandering(mon)) { 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 mprf(MSGCH_DIAGNOSTICS, "shifted to (%d,%d)", mon->x, mon->y); #endif return; } } coord_def pos(mon->pos()); // dirt simple movement: for (int i = 0; i < moves; i++) { coord_def inc(mon->target_pos() - pos); inc = coord_def(sgn(inc.x), sgn(inc.y)); if (mons_is_fleeing(mon)) inc *= -1; if (pos.x + inc.x < 0 || pos.x + inc.x >= GXM) inc.x = 0; if (pos.y + inc.y < 0 || pos.y + inc.y >= GYM) inc.y = 0; if (inc.origin()) break; const coord_def next(pos + inc); const dungeon_feature_type feat = grd(next); if (grid_is_solid(feat) || mgrd(next) != NON_MONSTER || !monster_habitable_grid(mon, feat)) break; pos = next; } if (!shift_monster( mon, pos.x, pos.y )) shift_monster( mon, mon->x, mon->y ); #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "moved to (%d,%d)", mon->x, mon->y ); #endif } //--------------------------------------------------------------- // // update_level // // Update the level when the player returns to it. // //--------------------------------------------------------------- void update_level(double elapsedTime) { const int turns = static_cast(elapsedTime / 10.0); #if DEBUG_DIAGNOSTICS int mons_total = 0; mprf(MSGCH_DIAGNOSTICS, "turns: %d", turns ); #endif update_corpses(elapsedTime); if (env.sanctuary_time) { if (turns >= env.sanctuary_time) remove_sanctuary(); else env.sanctuary_time -= turns; } dungeon_events.fire_event( dgn_event(DET_TURN_ELAPSED, coord_def(0, 0), turns * 10)); for (int m = 0; m < MAX_MONSTERS; m++) { monsters *mon = &menv[m]; if (!mon->alive()) continue; #if DEBUG_DIAGNOSTICS mons_total++; #endif // Pacified monsters often leave the level now. if (mons_is_pacified(mon) && turns > random2(40) + 21) { make_mons_leave_level(mon); continue; } // 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 if (!mons_class_flag(mon->type, M_NO_REGEN)) { // Set a lower ceiling of 0.1 on the regen rate. const int regen_rate = std::max(mons_natural_regen_rate(mon) * 2, 5); heal_monster(mon, div_rand_round(turns * regen_rate, 50), false); } _catchup_monster_moves(mon, turns); if (turns >= 10 && mon->alive()) mon->timeout_enchantments(turns / 10); } #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "total monsters on level = %d", mons_total ); #endif for (int i = 0; i < MAX_CLOUDS; i++) delete_cloud(i); } 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++) { if (!one_chance_in(100)) continue; // Make it start flowing again. grd[x][y] = static_cast (grid - (DNGN_DRY_FOUNTAIN_BLUE - DNGN_FOUNTAIN_BLUE)); if (is_terrain_seen(coord_def(x,y))) set_envmap_obj(x, y, grd[x][y]); // Clean bloody floor. if (is_bloodcovered(x,y)) env.map[x][y].property = FPROP_NONE; // Chance of cleaning adjacent squares. for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { if (is_bloodcovered(x+i,y+j) && one_chance_in(5)) { env.map[x+i][y+j].property = FPROP_NONE; } } return; } } //--------------------------------------------------------------- // // 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 = static_cast(elapsedTime / 20.0); for (int c = 0; c < MAX_ITEMS; c++) { item_def &it = mitm[c]; 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) { destroy_item(c); } else { if (it.sub_type == CORPSE_SKELETON || !mons_skeleton( it.plus )) { destroy_item(c); } else turn_corpse_into_skeleton(it); } } else { it.special -= rot_time; } } int fountain_checks = static_cast(elapsedTime / 1000.0); if (x_chance_in_y(static_cast(elapsedTime) % 1000, 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_DRY_FOUNTAIN_BLUE && grd[cx][cy] < DNGN_PERMADRY_FOUNTAIN) { _maybe_restart_fountain_flow(cx, cy, fountain_checks); } } } }