/* * File: xom.cc * Summary: All things Xomly * Written by: Zooko * * Modified for Crawl Reference by $Author$ on $Date$ */ #include "AppHdr.h" REVISION("$Rev$"); #include #include "beam.h" #include "branch.h" #include "database.h" #include "delay.h" #include "effects.h" #include "it_use2.h" #include "items.h" #include "Kills.h" #include "makeitem.h" #include "message.h" #include "misc.h" #include "mon-util.h" #include "monplace.h" #include "monstuff.h" #include "mutation.h" #include "notes.h" #include "ouch.h" #include "player.h" #include "randart.h" #include "religion.h" #include "spells1.h" #include "spells2.h" #include "spells3.h" #include "spl-book.h" #include "spl-cast.h" #include "spl-mis.h" #include "spl-util.h" #include "stash.h" #include "state.h" #include "stuff.h" #include "transfor.h" #include "traps.h" #include "view.h" #include "xom.h" #ifdef DEBUG_XOM # define DEBUG_RELIGION 1 # define NOTE_DEBUG_XOM 1 #endif #ifdef DEBUG_RELIGION # define DEBUG_DIAGNOSTICS 1 # define DEBUG_GIFTS 1 #endif // Which spells? First I copied all spells from your_spells(), and then // I filtered some out, especially conjurations. Then I sorted them in // roughly ascending order of power. // Spells to be cast at tension 0 (no or only low-level monsters around), // mostly flavour. static const spell_type _xom_nontension_spells[] = { SPELL_MAGIC_MAPPING, SPELL_DETECT_ITEMS, SPELL_SUMMON_BUTTERFLIES, SPELL_DETECT_CREATURES, SPELL_FLY, SPELL_SPIDER_FORM, SPELL_STATUE_FORM, SPELL_ICE_FORM, SPELL_DRAGON_FORM, SPELL_NECROMUTATION }; // Spells to be cast at tension > 0, i.e. usually in battle situations. static const spell_type _xom_tension_spells[] = { SPELL_BLINK, SPELL_CONFUSING_TOUCH, SPELL_CAUSE_FEAR, SPELL_MASS_SLEEP, SPELL_DISPERSAL, SPELL_STONESKIN, SPELL_RING_OF_FLAMES, SPELL_OLGREBS_TOXIC_RADIANCE, SPELL_TUKIMAS_VORPAL_BLADE, SPELL_MAXWELLS_SILVER_HAMMER, SPELL_FIRE_BRAND, SPELL_FREEZING_AURA, SPELL_POISON_WEAPON, SPELL_STONEMAIL, SPELL_LETHAL_INFUSION, SPELL_EXCRUCIATING_WOUNDS, SPELL_WARP_BRAND, SPELL_TUKIMAS_DANCE, SPELL_RECALL, SPELL_SUMMON_BUTTERFLIES, SPELL_SUMMON_SMALL_MAMMALS, SPELL_SUMMON_SCORPIONS, SPELL_SUMMON_SWARM, SPELL_FLY, SPELL_SPIDER_FORM, SPELL_STATUE_FORM, SPELL_ICE_FORM, SPELL_DRAGON_FORM, SPELL_ANIMATE_DEAD, SPELL_SUMMON_WRAITHS, SPELL_SHADOW_CREATURES, SPELL_SUMMON_HORRIBLE_THINGS, SPELL_CALL_CANINE_FAMILIAR, SPELL_SUMMON_ICE_BEAST, SPELL_SUMMON_UGLY_THING, SPELL_CONJURE_BALL_LIGHTNING, SPELL_SUMMON_DRAGON, SPELL_DEATH_CHANNEL, SPELL_NECROMUTATION }; static const char *_xom_message_arrays[NUM_XOM_MESSAGE_TYPES][6] = { // XM_NORMAL { "Xom is interested.", "Xom is mildly amused.", "Xom is amused.", "Xom is highly amused!", "Xom thinks this is hilarious!", "Xom roars with laughter!" }, // XM_INTRIGUED { "Xom is interested.", "Xom is very interested.", "Xom is extremely interested.", "Xom is intrigued!", "Xom is very intrigued!", "Xom is fascinated!" } }; const char *describe_xom_favour(bool upper) { std::string favour; if (you.religion != GOD_XOM) favour = "a very buggy toy of Xom."; else if (you.gift_timeout < 1) favour = "a BORING thing."; else { favour = (you.piety > 180) ? "Xom's teddy bear." : (you.piety > 150) ? "a beloved toy of Xom." : (you.piety > 120) ? "a favourite toy of Xom." : (you.piety > 80) ? "a toy of Xom." : (you.piety > 50) ? "a plaything of Xom." : (you.piety > 20) ? "a special plaything of Xom." : "a very special plaything of Xom."; } if (upper) favour = uppercase_first(favour); return (favour.c_str()); } static std::string _get_xom_speech(const std::string key) { std::string result = getSpeakString("Xom " + key); if (result.empty()) result = getSpeakString("Xom general effect"); if (result.empty()) return ("Xom makes something happen."); return (result); } static bool _xom_is_bored() { return (you.religion == GOD_XOM && you.gift_timeout == 0); } static bool _xom_feels_nasty() { // Xom will only directly kill you with a bad effect if you're under // penance from him, or if he's bored. return (you.penance[GOD_XOM] || _xom_is_bored()); } bool xom_is_nice(int tension) { if (you.penance[GOD_XOM]) return (false); if (you.religion == GOD_XOM) { // If you.gift_timeout was 0, then Xom was BORED. He HATES that. if (you.gift_timeout == 0) return (false); // At high tension Xom is more likely to be nice, // at zero tension the opposite. const int tension_bonus = (tension == -1 ? 0 : tension == 0 ? -std::min(abs(HALF_MAX_PIETY - you.piety) / 2, you.piety / 10) : std::min((MAX_PIETY - you.piety) / 2, random2(tension))); const int effective_piety = you.piety + tension_bonus; ASSERT(effective_piety >= 0 && effective_piety <= MAX_PIETY); #ifdef DEBUG_XOM mprf(MSGCH_DIAGNOSTICS, "Xom: tension: %d, piety: %d -> tension bonus = %d, eff. piety: %d", tension, you.piety, tension_bonus, effective_piety); #endif // Whether Xom is nice depends largely on his mood (== piety). return (x_chance_in_y(effective_piety, MAX_PIETY)); } else // CARD_XOM return coinflip(); } static void _xom_is_stimulated(int maxinterestingness, const char *message_array[], bool force_message) { if (you.religion != GOD_XOM || maxinterestingness <= 0) return; // Xom is not directly stimulated by his own acts. if (crawl_state.which_god_acting() == GOD_XOM) return; int interestingness = random2(maxinterestingness); interestingness = std::min(255, interestingness); #if defined(DEBUG_RELIGION) || defined(DEBUG_GIFTS) || defined(DEBUG_XOM) mprf(MSGCH_DIAGNOSTICS, "Xom: gift_timeout: %d, maxinterestingness = %d, interestingness = %d", you.gift_timeout, maxinterestingness, interestingness); #endif bool was_stimulated = false; if (interestingness > you.gift_timeout && interestingness >= 12) { you.gift_timeout = interestingness; was_stimulated = true; } if (was_stimulated || force_message) { god_speaks(GOD_XOM, ((interestingness > 200) ? message_array[5] : (interestingness > 100) ? message_array[4] : (interestingness > 75) ? message_array[3] : (interestingness > 50) ? message_array[2] : (interestingness > 25) ? message_array[1] : message_array[0])); } } void xom_is_stimulated(int maxinterestingness, xom_message_type message_type, bool force_message) { _xom_is_stimulated(maxinterestingness, _xom_message_arrays[message_type], force_message); } void xom_is_stimulated(int maxinterestingness, const std::string& message, bool force_message) { if (you.religion != GOD_XOM) return; const char *message_array[6]; for (int i = 0; i < 6; ++i) message_array[i] = message.c_str(); _xom_is_stimulated(maxinterestingness, message_array, force_message); } void xom_tick() { // Xom semi-randomly drifts your piety. const std::string old_xom_favour = describe_xom_favour(); const bool good = (you.piety == HALF_MAX_PIETY? coinflip() : you.piety > HALF_MAX_PIETY); int size = abs(you.piety - HALF_MAX_PIETY); // Piety slowly drifts towards the extremes. int delta = (x_chance_in_y(511, 1000) ? 1 : -1); size += delta; if (size > HALF_MAX_PIETY) size = HALF_MAX_PIETY; you.piety = HALF_MAX_PIETY + (good ? size : -size); std::string new_xom_favour = describe_xom_favour(); if (old_xom_favour != new_xom_favour) { // If we entered another favour state, take a big step into // the new territory to avoid oscillating favour announcements // every few turns. size += delta * 8; if (size > HALF_MAX_PIETY) size = HALF_MAX_PIETY; // If size was 0 to begin with it may become negative but that // doesn't really matter. you.piety = HALF_MAX_PIETY + (good ? size : -size); } #ifdef DEBUG_DIAGNOSTICS snprintf(info, INFO_SIZE, "xom_tick(), delta: %d, piety: %d", delta, you.piety); take_note(Note(NOTE_MESSAGE, 0, 0, info), true); #endif // ...but he gets bored... if (you.gift_timeout > 0 && coinflip()) you.gift_timeout--; new_xom_favour = describe_xom_favour(); if (old_xom_favour != new_xom_favour) { const std::string msg = "You are now " + new_xom_favour; god_speaks(you.religion, msg.c_str()); } if (you.gift_timeout == 1) simple_god_message(" is getting BORED."); if (one_chance_in(3)) { const int tension = get_tension(GOD_XOM); const int chance = (tension == 0 ? 1 : tension <= 5 ? 2 : tension <= 10 ? 3 : tension <= 20 ? 4 : 5); // If Xom is bored the chances for Xom acting are reversed. if (you.gift_timeout == 0 && x_chance_in_y(5-chance,5)) { xom_acts(abs(you.piety - HALF_MAX_PIETY), tension); return; } else if (you.gift_timeout <= 1 && chance > 0 && x_chance_in_y(chance-1, 4)) { // During tension, Xom may briefly forget about being bored. const int interest = random2(chance*15); if (interest > 0) { if (interest < 25) simple_god_message(" is interested."); else simple_god_message(" is intrigued."); you.gift_timeout += interest; #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM) mprf(MSGCH_DIAGNOSTICS, "tension %d (chance: %d) -> increase interest to %d", tension, chance, you.gift_timeout); #endif } } if (x_chance_in_y(chance, 5)) xom_acts(abs(you.piety - HALF_MAX_PIETY), tension); } } // Picks 100 random grids from the level and checks whether they've been // marked as seen (explored) or known (mapped). If seen_only is true // grids only "seen" via magic mapping don't count. // Returns the estimated percentage value of exploration. static int _exploration_estimate(bool seen_only = false) { int seen = 0; int total = 0; int tries = 0; do { tries++; coord_def pos = random_in_bounds(); if (!seen_only && is_terrain_known(pos) || is_terrain_seen(pos)) { seen++; total++; continue; } bool open = true; if (grid_is_solid(grd(pos)) && grd(pos) != DNGN_CLOSED_DOOR) { open = false; for (adjacent_iterator ai(pos); ai; ++ai) { if (map_bounds(*ai) && (!grid_is_opaque(grd(*ai)) || grd(*ai) == DNGN_CLOSED_DOOR)) { open = true; break; } } } if (open) total++; } while (total < 100 && tries < 1000); #ifdef DEBUG_XOM mprf(MSGCH_DIAGNOSTICS, "exploration estimate (%s): %d out of %d grids seen", seen_only ? "explored" : "mapped", seen, total); #endif // If we didn't get any qualifying grids, there are probably so few // of them you've already seen them all. if (total == 0) return 100; if (total < 100) seen *= 100/total; return (seen); } static bool _spell_weapon_check(const spell_type spell) { switch (spell) { case SPELL_TUKIMAS_DANCE: // Requires a wielded weapon. return player_weapon_wielded(); case SPELL_TUKIMAS_VORPAL_BLADE: case SPELL_MAXWELLS_SILVER_HAMMER: case SPELL_FIRE_BRAND: case SPELL_FREEZING_AURA: case SPELL_POISON_WEAPON: case SPELL_LETHAL_INFUSION: case SPELL_EXCRUCIATING_WOUNDS: case SPELL_WARP_BRAND: { if (!player_weapon_wielded()) return (false); // The wielded weapon must be a non-branded non-launcher non-artefact! const item_def& weapon = *you.weapon(); return (!is_artefact(weapon) && !is_range_weapon(weapon) && get_weapon_brand(weapon) == SPWPN_NORMAL); } default: return (true); } } static bool _transformation_check(const spell_type spell) { transformation_type tran = TRAN_NONE; switch (spell) { case SPELL_SPIDER_FORM: tran = TRAN_SPIDER; break; case SPELL_STATUE_FORM: tran = TRAN_STATUE; break; case SPELL_ICE_FORM: tran = TRAN_ICE_BEAST; break; case SPELL_DRAGON_FORM: tran = TRAN_DRAGON; break; case SPELL_NECROMUTATION: tran = TRAN_LICH; break; default: break; } if (tran == TRAN_NONE) return (true); // Check whether existing enchantments/transformations, cursed equipment, // or potential stat loss interfere with this transformation. return transform(0, tran, true, true); } static bool _xom_makes_you_cast_random_spell(int sever, int tension) { int spellenum = std::max(1, sever); god_acting gdact(GOD_XOM); spell_type spell; if (tension > 0) { const int nxomspells = ARRAYSZ(_xom_tension_spells); spellenum = std::min(nxomspells, spellenum); spell = _xom_tension_spells[random2(spellenum)]; // Don't attempt to cast spells that are guaranteed to fail. // You may still get results such as "The spell fizzles" or // "Nothing appears to happen", but those should be rarer now. if (!_spell_weapon_check(spell)) return (false); } else { const int nxomspells = ARRAYSZ(_xom_nontension_spells); // spellenum will be at least 3, so we don't run into infinite loops // for Detect Creatures/Magic Mapping in fully explored levels. spellenum = std::min(nxomspells, std::max(3 + coinflip(), spellenum)); spell = _xom_nontension_spells[random2(spellenum)]; if (spell == SPELL_MAGIC_MAPPING || spell == SPELL_DETECT_ITEMS) { // If the level is already mostly explored, there's // a chance we might try something else. const int explored = _exploration_estimate(); if (explored > 80 && x_chance_in_y(explored, 100)) return (false); } } // Don't attempt to cast spells the undead cannot memorise. if (you_cannot_memorise(spell)) return (false); // Don't attempt to transform the player if the transformation will fail. if (!_transformation_check(spell)) return (false); god_speaks(GOD_XOM, _get_xom_speech("spell effect").c_str()); #if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM) mprf(MSGCH_DIAGNOSTICS, "_xom_makes_you_cast_random_spell(); spell: %d, spellenum: %d", spell, spellenum); #endif static char spell_buf[100]; snprintf(spell_buf, sizeof(spell_buf), "cast spell '%s'", spell_title(spell)); take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, spell_buf), true); your_spells(spell, sever, false); return (true); } static void _try_brand_switch(const int item_index) { if (item_index == NON_ITEM) return; item_def &item(mitm[item_index]); // Only apply it to melee weapons for the player. if (item.base_type != OBJ_WEAPONS || is_range_weapon(item)) return; if (is_unrandom_artefact(item) || is_fixed_artefact(item)) return; // Only do it some of the time. if (one_chance_in(5)) return; int brand; if (item.base_type == OBJ_WEAPONS) { // Only switch already branded items. if (get_weapon_brand(item) == SPWPN_NORMAL) return; brand = (int) SPWPN_CHAOS; } else { // Only switch already branded items. if (get_ammo_brand(item) == SPWPN_NORMAL) return; brand = (int) SPMSL_CHAOS; } if (is_random_artefact(item)) randart_set_property(item, RAP_BRAND, brand); else item.special = brand; } static void _xom_make_item(object_class_type base, int subtype, int power) { god_acting gdact(GOD_XOM); int thing_created = items(true, base, subtype, true, power, MAKE_ITEM_RANDOM_RACE, 0, 0, GOD_XOM); if (grid_destroys_items(grd(you.pos()))) { if (!silenced(you.pos())) mprf(MSGCH_SOUND, grid_item_destruction_message(grd(you.pos()))); simple_god_message(" snickers.", GOD_XOM); destroy_item(thing_created, true); thing_created = NON_ITEM; } if (thing_created == NON_ITEM) { god_speaks(GOD_XOM, "\"No, never mind.\""); return; } _try_brand_switch(thing_created); static char gift_buf[100]; snprintf(gift_buf, sizeof(gift_buf), "god gift: %s", mitm[thing_created].name(DESC_PLAIN).c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, gift_buf), true); move_item_to_grid(&thing_created, you.pos()); mitm[thing_created].inscription = "god gift"; canned_msg(MSG_SOMETHING_APPEARS); stop_running(); } static void _xom_acquirement(object_class_type force_class) { if (grid_destroys_items(grd(you.pos()))) return; god_acting gdact(GOD_XOM); int item_index = NON_ITEM; if (!acquirement(force_class, GOD_XOM, false, &item_index) || item_index == NON_ITEM) { god_speaks(GOD_XOM, "\"No, never mind.\""); return; } _try_brand_switch(item_index); static char gift_buf[100]; snprintf(gift_buf, sizeof(gift_buf), "god gift: %s", mitm[item_index].name(DESC_PLAIN).c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, gift_buf), true); stop_running(); } static object_class_type _get_unrelated_wield_class(object_class_type ref) { object_class_type objtype = OBJ_WEAPONS; if (ref == OBJ_WEAPONS) { if (one_chance_in(10)) objtype = OBJ_MISCELLANY; else objtype = OBJ_STAVES; } else if (ref == OBJ_STAVES) { if (one_chance_in(10)) objtype = OBJ_MISCELLANY; else objtype = OBJ_WEAPONS; } else { const int temp_rand = random2(3); objtype = (temp_rand == 0) ? OBJ_WEAPONS : (temp_rand == 1) ? OBJ_STAVES : OBJ_MISCELLANY; } return (objtype); } static bool _xom_annoyance_gift(int power) { god_acting gdact(GOD_XOM); if (coinflip() && player_in_a_dangerous_place()) { const item_def *weapon = you.weapon(); // Xom has a sense of humour. if (coinflip() && weapon && weapon->cursed()) { // If you are wielding a cursed item then Xom will give you // an item of that same type. Ha ha! god_speaks(GOD_XOM, _get_xom_speech("cursed gift").c_str()); if (coinflip()) // For added humour, give the same sub-type. _xom_make_item(weapon->base_type, weapon->sub_type, power * 3); else _xom_acquirement(weapon->base_type); return (true); } const item_def *gloves = you.slot_item(EQ_GLOVES); if (coinflip() && gloves && gloves->cursed()) { // If you are wearing cursed gloves, then Xom will give you // a ring. Ha ha! god_speaks(GOD_XOM, _get_xom_speech("cursed gift").c_str()); _xom_make_item(OBJ_JEWELLERY, get_random_ring_type(), power * 3); return (true); }; const item_def *amulet = you.slot_item(EQ_AMULET); if (coinflip() && amulet && amulet->cursed()) { // If you are wearing a cursed amulet, then Xom will give // you an amulet. Ha ha! god_speaks(GOD_XOM, _get_xom_speech("cursed gift").c_str()); _xom_make_item(OBJ_JEWELLERY, get_random_amulet_type(), power * 3); return (true); }; const item_def *left_ring = you.slot_item(EQ_LEFT_RING); const item_def *right_ring = you.slot_item(EQ_RIGHT_RING); if (coinflip() && ((left_ring && left_ring->cursed()) || (right_ring && right_ring->cursed()))) { // If you are wearing a cursed ring, then Xom will give you // a ring. Ha ha! god_speaks(GOD_XOM, _get_xom_speech("ring gift").c_str()); _xom_make_item(OBJ_JEWELLERY, get_random_ring_type(), power * 3); return (true); } if (one_chance_in(5) && weapon) { // Xom will give you a wielded item of a type different from // what you are currently wielding. god_speaks(GOD_XOM, _get_xom_speech("weapon gift").c_str()); const object_class_type objtype = _get_unrelated_wield_class(weapon->base_type); if (x_chance_in_y(power, 256)) _xom_acquirement(objtype); else _xom_make_item(objtype, OBJ_RANDOM, power * 3); return (true); } } return (false); } static bool _xom_give_item(int power) { if (_xom_annoyance_gift(power)) return (true); const item_def *cloak = you.slot_item(EQ_CLOAK); if (coinflip() && cloak && cloak->cursed()) { // If you are wearing a cursed cloak, then Xom will give you a // cloak or body armour. Ha ha! god_speaks(GOD_XOM, _get_xom_speech("armour gift").c_str()); _xom_make_item(OBJ_ARMOUR, one_chance_in(10) ? ARM_CLOAK : get_random_body_armour_type(power * 2), power * 3); return (true); } god_speaks(GOD_XOM, _get_xom_speech("general gift").c_str()); // There are two kinds of Xom gifts: acquirement and random object. // The result from acquirement is very good (usually as good or // better than random object), and it is sometimes tuned to the // player's skills and nature. Being tuned to the player's skills // and nature is not very Xomlike... if (x_chance_in_y(power, 256)) { const object_class_type types[] = { OBJ_WEAPONS, OBJ_ARMOUR, OBJ_JEWELLERY, OBJ_BOOKS, OBJ_STAVES, OBJ_FOOD, OBJ_MISCELLANY, OBJ_GOLD }; god_acting gdact(GOD_XOM); _xom_acquirement(RANDOM_ELEMENT(types)); } else { // Random-type random object. _xom_make_item(OBJ_RANDOM, OBJ_RANDOM, power * 3); } more(); return (true); } static bool _choose_mutatable_monster(const monsters* mon) { return (mon->alive() && mon->can_safely_mutate() && !mons_is_submerged(mon)); } static bool _is_chaos_upgradeable(const item_def &item, const monsters* mon) { // Since Xom is a god he is capable of changing randarts, but not // other artifacts. if (is_artefact(item) && !is_random_artefact(item)) return (false); // Only upgrade permanent items, since the player should get a // chance to use the item if s/he can defeat the monster. if (item.flags & ISFLAG_SUMMONED) return (false); // Blessed blades are protected, being gifts from good gods. if (is_blessed_blade(item)) return (false); // God gifts from good gods are protected. Also, Beogh hates all // the other gods, so he'll protect his gifts as well. if (item.orig_monnum < 0) { god_type iorig = static_cast(-item.orig_monnum); if ((iorig > GOD_NO_GOD && iorig < NUM_GODS) && (is_good_god(iorig) || iorig == GOD_BEOGH)) { return (false); } } // Leave branded items alone, since this is supposed to be an // upgrade. if (item.base_type == OBJ_MISSILES) { // Don't make boulders or throwing nets of chaos. if (item.sub_type == MI_LARGE_ROCK || item.sub_type == MI_THROWING_NET) { return (false); } if (get_ammo_brand(item) == SPMSL_NORMAL) return (true); } else { // If the weapon is a launcher, and the monster is either out // of ammo or is carrying javelins, then don't bother upgrading // the launcher. if (is_range_weapon(item) && (mon->inv[MSLOT_MISSILE] == NON_ITEM || !has_launcher(mitm[mon->inv[MSLOT_MISSILE]]))) { return (false); } if (get_weapon_brand(item) == SPWPN_NORMAL) return (true); } return (false); } static bool _choose_chaos_upgrade(const monsters* mon) { // Only choose monsters that will attack. if (!mon->alive() || mons_attitude(mon) != ATT_HOSTILE || mons_is_fleeing(mon) || mons_is_panicking(mon)) { return (false); } if (mons_itemuse(mon) < MONUSE_STARTING_EQUIPMENT) return (false); // Holy beings are presumably protected by another god, unless // they're gifts from a chaotic god. if (mons_is_holy(mon) && !is_chaotic_god(mon->god)) return (false); // God gifts from good gods will be protected by their god from // being given chaos weapons, while other gods won't mind the help // in their servants' killing the player. if (is_good_god(mon->god)) return (false); // Beogh presumably doesn't want Xom messing with his orcs, even if // it would give them a better weapon. if (mons_species(mon->type) == MONS_ORC && (mons_class_flag(mon->type, M_PRIEST) || coinflip())) { return (false); } mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE}; // NOTE: Code assumes that the monster will only be carrying one // missile launcher at a time. bool special_launcher = false; for (int i = 0; i < 3; ++i) { const mon_inv_type slot = slots[i]; const int midx = mon->inv[slot]; if (midx == NON_ITEM) continue; const item_def &item(mitm[midx]); // The monster already has a chaos weapon. Give the upgrade to // a different monster. if (is_chaotic_item(item)) return (false); if (_is_chaos_upgradeable(item, mon)) { if (item.base_type != OBJ_MISSILES) return (true); // If, for some weird reason, a monster is carrying a bow // and javelins, then branding the javelins is okay, since // they won't be fired by the bow. if (!special_launcher || !has_launcher(item)) return (true); } if (is_range_weapon(item)) { // If the launcher alters its ammo, then branding the // monster's ammo won't be an upgrade. int brand = get_weapon_brand(item); if (brand == SPWPN_FLAME || brand == SPWPN_FROST || brand == SPWPN_VENOM) { special_launcher = true; } } } return (false); } static void _do_chaos_upgrade(item_def &item, const monsters* mon) { ASSERT(item.base_type == OBJ_MISSILES || item.base_type == OBJ_WEAPONS); ASSERT(!is_unrandom_artefact(item) && !is_fixed_artefact(item)); bool seen = false; if (mon && you.can_see(mon) && item.base_type == OBJ_WEAPONS) { seen = true; description_level_type desc = mons_friendly(mon) ? DESC_CAP_YOUR : DESC_CAP_THE; std::string msg = apostrophise(mon->name(desc)); msg += " "; msg += item.name(DESC_PLAIN, false, false, false); msg += " is briefly surrounded by a scintillating aura of " "random colours."; mpr(msg.c_str()); } const int brand = (item.base_type == OBJ_WEAPONS) ? (int) SPWPN_CHAOS : (int) SPMSL_CHAOS; if (is_random_artefact(item)) { randart_set_property(item, RAP_BRAND, brand); if (seen) randart_wpn_learn_prop(item, RAP_BRAND); } else { item.special = brand; if (seen) set_ident_flags(item, ISFLAG_KNOW_TYPE); // Make sure it's visibly special. if (!(item.flags & ISFLAG_COSMETIC_MASK)) item.flags |= ISFLAG_GLOWING; // Make the pluses more like a randomly generated ego item. item.plus += random2(5); item.plus2 += random2(5); } } static monster_type _xom_random_demon(int sever, bool use_greater_demons = true) { const int roll = random2(1000 - (MAX_PIETY - sever) * 3); #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "_xom_random_demon(); sever = %d, roll: %d", sever, roll); #endif const demon_class_type dct = (roll >= 850) ? DEMON_GREATER : (roll >= 340) ? DEMON_COMMON : DEMON_LESSER; monster_type demon = MONS_PROGRAM_BUG; // Sometimes, send a holy warrior instead. if (dct == DEMON_GREATER && coinflip()) demon = summon_any_holy_being(HOLY_BEING_WARRIOR); else { const demon_class_type dct2 = (!use_greater_demons && dct == DEMON_GREATER) ? DEMON_COMMON : dct; if (dct2 == DEMON_COMMON && one_chance_in(10)) demon = MONS_CHAOS_SPAWN; else demon = summon_any_demon(dct2); } return (demon); } static bool _feat_is_deadly(dungeon_feature_type feat) { if (you.airborne()) return (false); return (feat == DNGN_LAVA || feat == DNGN_DEEP_WATER && !you.can_swim()); } static bool _player_is_dead() { return (you.hp <= 0 || you.strength <= 0 || you.dex <= 0 || you.intel <= 0 || _feat_is_deadly(grd(you.pos())) || you.did_escape_death()); } static bool _xom_do_potion() { potion_type pot = POT_HEALING; while (true) { pot = static_cast( random_choose(POT_HEALING, POT_HEAL_WOUNDS, POT_MAGIC, POT_SPEED, POT_MIGHT, POT_INVISIBILITY, POT_BERSERK_RAGE, POT_EXPERIENCE, -1)); if (pot == POT_EXPERIENCE && !one_chance_in(6)) pot = POT_BERSERK_RAGE; bool has_effect = true; // Don't pick something that won't have an effect. // Extending an existing effect is okay, though. switch (pot) { case POT_HEALING: if (you.rotting || you.disease || you.duration[DUR_CONF] || you.duration[DUR_POISONING]) { break; } // else fall through case POT_HEAL_WOUNDS: if (you.hp == you.hp_max && player_rotted() == 0) has_effect = false; break; case POT_MAGIC: if (you.magic_points == you.max_magic_points) has_effect = false; break; case POT_BERSERK_RAGE: if (!you.can_go_berserk(false)) has_effect = false; break; case POT_EXPERIENCE: if (you.experience_level == 27) has_effect = false; break; default: break; } if (has_effect) break; } god_speaks(GOD_XOM, _get_xom_speech("potion effect").c_str()); if (pot == POT_BERSERK_RAGE) you.berserk_penalty = NO_BERSERK_PENALTY; // Take a note. std::string potion_msg = "potion effect "; switch (pot) { case POT_HEALING: potion_msg += "(healing)"; break; case POT_HEAL_WOUNDS: potion_msg += "(heal wounds)"; break; case POT_MAGIC: potion_msg += "(magic)"; break; case POT_SPEED: potion_msg += "(speed)"; break; case POT_MIGHT: potion_msg += "(might)"; break; case POT_INVISIBILITY: potion_msg += "(invisibility)"; break; case POT_BERSERK_RAGE: potion_msg += "(berserk)"; break; case POT_EXPERIENCE: potion_msg += "(experience)"; break; default: potion_msg += "(other)"; break; } take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, potion_msg.c_str()), true); potion_effect(pot, 150); return (true); } static bool _xom_confuse_monsters(int sever) { bool rc = false; monsters *monster; for (unsigned i = 0; i < MAX_MONSTERS; ++i) { monster = &menv[i]; if (monster->type == -1 || !mons_near(monster) || mons_wont_attack(monster) || !mons_class_is_confusable(monster->type) || one_chance_in(20)) { continue; } if (monster->add_ench(mon_enchant(ENCH_CONFUSION, 0, KC_FRIENDLY, random2(sever)))) { // Only give this message once. if (!rc) god_speaks(GOD_XOM, _get_xom_speech("confusion").c_str()); simple_monster_message(monster, " looks rather confused."); rc = true; } } if (rc == true) { take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "confuse monster(s)"), true); } return (rc); } static bool _xom_send_allies(int sever) { bool rc = false; // The number of allies is dependent on severity, though heavily // randomized. int numdemons = sever; for (int i = 0; i < 3; i++) numdemons = random2(numdemons + 1); numdemons = std::min(numdemons + 2, 16); int numdifferent = 0; // If we have a mix of demons and non-demons, there's a chance // that one or both of the factions may be hostile. int hostiletype = random_choose_weighted(3, 0, // both friendly 4, 1, // one hostile 4, 2, // other hostile 1, 3, // both hostile 0); std::vector is_demonic(numdemons, false); std::vector summons(numdemons); int num_actually_summoned = 0; for (int i = 0; i < numdemons; ++i) { monster_type monster = _xom_random_demon(sever); summons[i] = create_monster( mgen_data(monster, BEH_FRIENDLY, 3, MON_SUMM_AID, you.pos(), MHITYOU, MG_FORCE_BEH, GOD_XOM)); if (summons[i] != -1) { num_actually_summoned++; is_demonic[i] = (mons_class_holiness(monster) == MH_DEMONIC); // If it's not a demon, Xom got it someplace else, so use // different messages below. if (!is_demonic[i]) numdifferent++; } } if (num_actually_summoned) { const bool only_holy = (numdifferent == num_actually_summoned); const bool only_demonic = (numdifferent == 0); if (only_holy) { god_speaks(GOD_XOM, _get_xom_speech("multiple holy summons").c_str()); } else if (only_demonic) { god_speaks(GOD_XOM, _get_xom_speech("multiple summons").c_str()); } else { god_speaks(GOD_XOM, _get_xom_speech("multiple mixed summons").c_str()); } // If we have only non-demons, there's a chance that they // may be hostile. if (only_holy && one_chance_in(4)) hostiletype = 2; // If we have only demons, they'll always be friendly. else if (only_demonic) hostiletype = 0; for (int i = 0; i < numdemons; ++i) { if (summons[i] == -1) continue; monsters *mon = &menv[summons[i]]; if (hostiletype != 0) { // Mark factions hostile as appropriate. if (hostiletype == 3 || (is_demonic[i] && hostiletype == 1) || (!is_demonic[i] && hostiletype == 2)) { mon->attitude = ATT_HOSTILE; behaviour_event(mon, ME_ALERT, MHITYOU); } } player_angers_monster(mon); } // Take a note. static char summ_buf[80]; snprintf(summ_buf, sizeof(summ_buf), "summons %d %s%s%s", num_actually_summoned, hostiletype == 0 ? "friendly " : hostiletype == 3 ? "hostile " : "", only_demonic ? "demon" : "monster", num_actually_summoned > 1 ? "s" : ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); rc = true; } return (rc); } static bool _xom_send_one_ally(int sever) { bool rc = false; const monster_type mon = _xom_random_demon(sever); const bool is_demonic = (mons_class_holiness(mon) == MH_DEMONIC); // If we have a non-demon, Xom got it someplace else, so use // different messages below. bool different = !is_demonic; beh_type beha = BEH_FRIENDLY; // There's a chance that a non-demon may be hostile. if (different && one_chance_in(4)) beha = BEH_HOSTILE; const int summons = create_monster( mgen_data(mon, beha, 6, MON_SUMM_AID, you.pos(), MHITYOU, MG_FORCE_BEH, GOD_XOM)); if (summons != -1) { if (different) god_speaks(GOD_XOM, _get_xom_speech("single holy summon").c_str()); else god_speaks(GOD_XOM, _get_xom_speech("single summon").c_str()); player_angers_monster(&menv[summons]); // Take a note. static char summ_buf[80]; snprintf(summ_buf, sizeof(summ_buf), "summons %s %s", beha == BEH_FRIENDLY ? "friendly" : "hostile", menv[summons].name(DESC_PLAIN).c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); rc = true; } return (rc); } static bool _xom_polymorph_nearby_monster(bool helpful) { bool rc = false; if (there_are_monsters_nearby(false, false)) { monsters *mon = choose_random_nearby_monster(0, _choose_mutatable_monster); if (mon) { const char* lookup = (helpful ? "good monster polymorph" : "bad monster polymorph"); god_speaks(GOD_XOM, _get_xom_speech(lookup).c_str()); bool see_old = you.can_see(mon); std::string old_name = mon->full_name(DESC_PLAIN); if (one_chance_in(8) && !mons_is_shapeshifter(mon) && mons_holiness(mon) == MH_NATURAL) { mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER : ENCH_SHAPESHIFTER); } const bool powerup = !(mons_wont_attack(mon) ^ helpful); monster_polymorph(mon, RANDOM_MONSTER, powerup ? PPT_MORE : PPT_LESS); bool see_new = you.can_see(mon); if (see_old || see_new) { std::string new_name = mon->full_name(DESC_PLAIN); if (!see_old) old_name = "something unseen"; else if (!see_new) new_name = "something unseen"; // Take a note. static char poly_buf[120]; snprintf(poly_buf, sizeof(poly_buf), "polymorph %s -> %s", old_name.c_str(), new_name.c_str()); std::string poly = poly_buf; #ifdef NOTE_DEBUG_XOM poly += " ("; poly += (powerup ? "upgrade" : "downgrade"); poly += ")"; #endif take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, poly.c_str()), true); } rc = true; } } return (rc); } static void _confuse_monster(monsters mons, int sever) { if (!mons_class_is_confusable(mons.type)) return; const bool was_confused = mons.confused(); if (mons.add_ench(mon_enchant(ENCH_CONFUSION, 0, KC_FRIENDLY, random2(sever)))) { if (was_confused) simple_monster_message(&mons, " looks rather more confused."); else simple_monster_message(&mons, " looks rather confused."); } } static bool _swap_monsters(monsters *m1, monsters *m2) { monsters& mon1(*m1); monsters& mon2(*m2); const bool mon1_caught = mons_is_caught(&mon1); const bool mon2_caught = mons_is_caught(&mon2); const coord_def mon1_pos = mon1.pos(); const coord_def mon2_pos = mon2.pos(); if (!mon2.is_habitable(mon1_pos) || !mon1.is_habitable(mon2_pos)) return (false); // Make submerged monsters unsubmerge. mon1.del_ench(ENCH_SUBMERGED); mon2.del_ench(ENCH_SUBMERGED); mgrd(mon1_pos) = mon2.mindex(); mon1.moveto(mon2_pos); mgrd(mon2_pos) = mon1.mindex(); mon2.moveto(mon1_pos); if (mon1_caught && !mon2_caught) { check_net_will_hold_monster(&mon2); mon1.del_ench(ENCH_HELD, true); } else if (mon2_caught && !mon1_caught) { check_net_will_hold_monster(&mon1); mon2.del_ench(ENCH_HELD, true); } return (true); } // Swap places with a random monster and, depending on severity, also // between monsters. This can be pretty bad if there are a lot of // hostile monsters around. static bool _xom_rearrange_pieces(int sever) { if (player_stair_delay()) return (false); std::vector mons; for (unsigned i = 0; i < MAX_MONSTERS; ++i) { monsters* m = &menv[i]; if (!m->alive()) continue; if (!see_grid(m->pos())) continue; mons.push_back(m); } if (mons.empty()) return (false); god_speaks(GOD_XOM, _get_xom_speech("rearrange the pieces").c_str()); const int num_mons = mons.size(); // Swap places with a random monster. monsters *mon = mons[random2(num_mons)]; swap_with_monster(mon); // Sometimes confuse said monster. if (coinflip()) _confuse_monster(*mon, sever); if (num_mons > 1 && x_chance_in_y(sever, 70)) { bool did_message = false; const int max_repeats = std::min(num_mons / 2, 8); const int repeats = std::min(random2(sever / 10) + 1, max_repeats); for (int i = 0; i < repeats; ++i) { const int mon1 = random2(num_mons); int mon2 = mon1; while (mon1 == mon2) mon2 = random2(num_mons); if (_swap_monsters(mons[mon1], mons[mon2])) { if (!did_message) { mpr("Some monsters swap places."); did_message = true; } if (one_chance_in(3)) _confuse_monster(*mons[mon1], sever); if (one_chance_in(3)) _confuse_monster(*mons[mon2], sever); } } } take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "swap monsters"), true); return (true); } static bool _xom_give_mutations(bool good) { bool rc = false; if (you.can_safely_mutate()) { const char* lookup = (good ? "good mutations" : "random mutations"); god_speaks(GOD_XOM, _get_xom_speech(lookup).c_str()); const int num_tries = random2(4) + 1; static char mut_buf[80]; snprintf(mut_buf, sizeof(mut_buf), "give %smutation%s", #ifdef NOTE_DEBUG_XOM good ? "good " : "random ", #else "", #endif num_tries > 1 ? "s" : ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, mut_buf), true); mpr("Your body is suffused with distortional energy."); set_hp(1 + random2(you.hp), false); deflate_hp(you.hp_max / 2, true); bool failMsg = true; for (int i = num_tries; i > 0; --i) { if (mutate(good ? RANDOM_GOOD_MUTATION : RANDOM_XOM_MUTATION, failMsg, false, true, false, false, good || !_xom_feels_nasty())) { rc = true; } else failMsg = false; } } return (rc); } // Summons a permanent ally. static bool _xom_send_major_ally(int sever) { bool rc = false; const monster_type mon = _xom_random_demon(sever); const bool is_demonic = (mons_class_holiness(mon) == MH_DEMONIC); beh_type beha = BEH_FRIENDLY; // There's a chance that a non-demon may be hostile. if (!is_demonic && one_chance_in(4)) beha = BEH_HOSTILE; const int summons = create_monster( mgen_data(_xom_random_demon(sever, one_chance_in(8)), beha, 0, 0, you.pos(), MHITYOU, MG_FORCE_BEH, GOD_XOM)); if (summons != -1) { if (is_demonic) { god_speaks(GOD_XOM, _get_xom_speech("single major demon summon").c_str()); } else { god_speaks(GOD_XOM, _get_xom_speech("single major holy summon").c_str()); } player_angers_monster(&menv[summons]); // Take a note. static char summ_buf[80]; snprintf(summ_buf, sizeof(summ_buf), "sends permanent %s %s", beha == BEH_FRIENDLY ? "friendly" : "hostile", menv[summons].name(DESC_PLAIN).c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); rc = true; } return (rc); } static bool _xom_throw_divine_lightning() { if (!player_in_a_dangerous_place()) return (false); // Make sure there's at least one enemy within the lightning radius. bool found_hostile = false; for (radius_iterator ri(you.pos(), 2, true, true, true); ri; ++ri) { if (monsters* mon = monster_at(*ri)) { if (!mons_wont_attack(mon)) { found_hostile = true; break; } } } // No hostiles within radius. if (!found_hostile) return (false); bool protection = false; if (you.hp <= random2(201)) { you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 1; protection = true; } god_speaks(GOD_XOM, "The area is suffused with divine lightning!"); bolt beam; beam.flavour = BEAM_ELECTRICITY; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.damage = dice_def(3, 30); beam.target = you.pos(); beam.name = "blast of lightning"; beam.colour = LIGHTCYAN; beam.thrower = KILL_MISC; beam.beam_source = NON_MONSTER; beam.aux_source = "Xom's lightning strike"; beam.ex_size = 2; beam.is_explosion = true; beam.explode(); if (you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION]) { mpr("Your divine protection wanes."); you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 0; } // Don't accidentally kill the player when doing a good act. if (you.escaped_death_cause == KILLED_BY_WILD_MAGIC && you.escaped_death_aux == "Xom's lightning strike") { you.hp = 1; you.reset_escaped_death(); } // Take a note. static char lightning_buf[80]; snprintf(lightning_buf, sizeof(lightning_buf), "divine lightning%s", protection ? " (protected)" : ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, lightning_buf), true); return (true); } // The nicer stuff. Note: these things are not necessarily nice. static bool _xom_is_good(int sever, int tension) { bool done = false; // Did Xom (already) kill the player? if (_player_is_dead()) return (true); god_acting gdact(GOD_XOM); // This series of random calls produces a poisson-looking // distribution: initial hump, plus a long-ish tail. // Don't make the player go berserk etc. if there's no danger. if (tension > random2(3) && x_chance_in_y(2, sever)) done = _xom_do_potion(); else if (x_chance_in_y(3, sever)) { // There are a lot less non-tension spells than tension ones, // so use them more rarely. if (tension > 0 || one_chance_in(3)) done = _xom_makes_you_cast_random_spell(sever, tension); } else if (x_chance_in_y(4, sever)) done = _xom_confuse_monsters(sever); // It's pointless to send in help if there's no danger. else if (tension > random2(5) && x_chance_in_y(5, sever)) done = _xom_send_one_ally(sever); else if (x_chance_in_y(6, sever)) { _xom_give_item(sever); done = true; } // It's pointless to send in help if there's no danger. else if (tension > random2(10) && x_chance_in_y(7, sever)) done = _xom_send_allies(sever); else if (x_chance_in_y(8, sever)) done = _xom_polymorph_nearby_monster(true); else if (tension > 0 && x_chance_in_y(9, sever)) done = _xom_rearrange_pieces(sever); else if (random2(tension) < 15 && x_chance_in_y(10, sever)) { _xom_give_item(sever); done = true; } else if (x_chance_in_y(11, sever) && you.level_type != LEVEL_ABYSS) { // Try something else if teleportation is impossible. if (scan_randarts(RAP_PREVENT_TELEPORTATION)) return (false); // This is not very interesting if the level is already fully // explored (presumably cleared). Even then, it may occasionally // happen. const int explored = _exploration_estimate(true); if (explored >= 80 && x_chance_in_y(explored, 120)) return (false); // The Xom teleportation train takes you on instant teleportation // to a few random areas, stopping randomly but most likely in // an area that is not dangerous to you. god_speaks(GOD_XOM, _get_xom_speech("teleportation journey").c_str()); int count = 0; do { count++; you_teleport_now(false); more(); if (one_chance_in(10)) break; } while (x_chance_in_y(3, 4) || count > 7 + random2(5) || player_in_a_dangerous_place()); maybe_update_stashes(); // Take a note. static char tele_buf[80]; snprintf(tele_buf, sizeof(tele_buf), "%d-stop teleportation journey%s", count, #ifdef NOTE_DEBUG_XOM player_in_a_dangerous_place() ? " (dangerous)" : // see below #endif ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, tele_buf), true); done = true; } else if (random2(tension) < 5 && x_chance_in_y(12, sever)) { // This can fail with radius 1, or in open areas. if (vitrify_area(random2avg(sever/4,2) + 1)) { god_speaks(GOD_XOM, _get_xom_speech("vitrification").c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, "vitrification"), true); done = true; } } else if (random2(tension) < 5 && x_chance_in_y(13, sever) && x_chance_in_y(16, how_mutated())) { done = _xom_give_mutations(true); } // It's pointless to send in help if there's no danger. else if (tension > random2(15) && x_chance_in_y(14, sever)) done = _xom_send_major_ally(sever); else if (tension > 0 && x_chance_in_y(15, sever)) done = _xom_throw_divine_lightning(); return (done); } // Is the equipment type usable, and is the slot empty? static bool _could_wear_eq(equipment_type eq) { if (!you_tran_can_wear(eq, true)) return (false); return (you.slot_item(eq) == NULL); } static item_def* _tran_get_eq(equipment_type eq) { if (you_tran_can_wear(eq, true)) return (you.slot_item(eq)); return (NULL); } // Which types of dungeon features are in view? static void _get_in_view(FixedVector& in_view) { in_view.init(false); for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) in_view[grd(*ri)] = true; } static void _xom_zero_miscast() { std::vector messages; std::vector priority; std::vector inv_items; for (int i = 0; i < ENDOFPACK; ++i) { const item_def &item(you.inv[i]); if (is_valid_item(item) && !item_is_equipped(item) && !item_is_critical(item)) { inv_items.push_back(i); } } // Assure that the messages vector has at least one element. messages.push_back("Nothing appears to happen... Ominous!"); /////////////////////////////////// // Dungeon feature dependent stuff. FixedVector in_view; _get_in_view(in_view); if (in_view[DNGN_LAVA]) messages.push_back("The lava spits out sparks!"); if (in_view[DNGN_SHALLOW_WATER] || in_view[DNGN_DEEP_WATER]) { messages.push_back("The water briefly bubbles."); messages.push_back("The water briefly swirls."); messages.push_back("The water briefly glows."); } if (in_view[DNGN_DEEP_WATER]) { messages.push_back("From the corner of your eye you spot something " "lurking in the deep water."); } if (in_view[DNGN_ORCISH_IDOL]) { if (you.species == SP_HILL_ORC) priority.push_back("The idol of Beogh turns to glare at you."); else priority.push_back("The orcish idol turns to glare at you."); } if (in_view[DNGN_GRANITE_STATUE]) priority.push_back("The granite statue turns to stare at you."); if (in_view[DNGN_WAX_WALL]) priority.push_back("The wax wall pulsates ominously."); if (in_view[DNGN_CLEAR_ROCK_WALL] || in_view[DNGN_CLEAR_STONE_WALL] || in_view[DNGN_CLEAR_PERMAROCK_WALL]) { messages.push_back("Dim shapes swim through the translucent wall."); } if (in_view[DNGN_GREEN_CRYSTAL_WALL]) messages.push_back("Dim shapes swim through the green crystal wall."); if (in_view[DNGN_METAL_WALL]) messages.push_back("Tendrils of electricity crawl over the metal " "wall!"); if (in_view[DNGN_FOUNTAIN_BLUE] || in_view[DNGN_FOUNTAIN_SPARKLING]) { priority.push_back("The water in the fountain briefly bubbles."); priority.push_back("The water in the fountain briefly swirls."); priority.push_back("The water in the fountain briefly glows."); } if (in_view[DNGN_DRY_FOUNTAIN_BLUE] || in_view[DNGN_DRY_FOUNTAIN_SPARKLING] || in_view[DNGN_PERMADRY_FOUNTAIN]) { priority.push_back("Water briefly sprays from the dry fountain."); priority.push_back("Dust puffs up from the dry fountain."); } if (in_view[DNGN_STONE_ARCH]) priority.push_back("The stone arch briefly shows a sunny meadow on " "the other side."); const dungeon_feature_type feat = grd(you.pos()); if (!grid_is_solid(feat) && grid_stair_direction(feat) == CMD_NO_CMD && !grid_is_trap(feat) && feat != DNGN_STONE_ARCH && feat != DNGN_OPEN_DOOR && feat != DNGN_ABANDONED_SHOP) { const std::string feat_name = feature_description(you.pos(), false, DESC_CAP_THE, false); if (you.airborne()) { // Kenku fly a lot, so don't put airborne messages into the // priority vector for them. std::vector* vec; if (you.species == SP_KENKU) vec = &messages; else vec = &priority; vec->push_back(feat_name + " seems to fall away from under you!"); vec->push_back(feat_name + " seems to rush up at you!"); if (grid_is_water(feat)) { priority.push_back("Something invisible splashes into the " "water beneath you!"); } } else if (grid_is_water(feat)) { priority.push_back("The water briefly recedes away from you."); priority.push_back("Something invisible splashes into the water " "beside you!"); } } if (!grid_destroys_items(feat) && !grid_is_solid(feat) && inv_items.size() > 0) { int idx = inv_items[random2(inv_items.size())]; const item_def &item(you.inv[idx]); std::string name; if (item.quantity == 1) name = item.name(DESC_CAP_YOUR, false, false, false); else { name = "One of "; name += item.name(DESC_NOCAP_YOUR, false, false, false); } messages.push_back(name + " falls out of your pack, then " "immediately jumps back in!"); } //////////////////////////////////////////// // Body, player spcies, transformations, etc const int transform = you.attribute[ATTR_TRANSFORMATION]; if (you.species == SP_MUMMY && you_tran_can_wear(EQ_BODY_ARMOUR)) { messages.push_back("You briefly get tangled in your bandages."); if (!you.airborne() && !you.swimming()) messages.push_back("You trip over your bandages."); } if (transform != TRAN_SPIDER) { std::string str = "A monocle briefly appears over your "; str += coinflip() ? "right" : "left"; str += " eye."; messages.push_back(str); } if (!player_genus(GENPC_DRACONIAN) && you.species != SP_MUMMY && (transform == TRAN_NONE || transform == TRAN_BLADE_HANDS)) { messages.push_back("Your eyebrows briefly feel incredibly bushy."); messages.push_back("Your eyebrows wriggle."); } if (you.species != SP_NAGA && (you.species != SP_MERFOLK || !player_is_swimming()) && !you.airborne()) { messages.push_back("You do an impromptu tapdance."); } /////////////////////////// // Equipment related stuff. item_def* item; if (_could_wear_eq(EQ_WEAPON)) { std::string str = "A fancy cane briefly appears in your "; str += you.hand_name(false); str += "."; messages.push_back(str); } if (_tran_get_eq(EQ_CLOAK) != NULL) messages.push_back("Your cloak billows in an unfelt wind."); if ((item = _tran_get_eq(EQ_HELMET))) { std::string str = "Your "; str += item->name(DESC_BASENAME, false, false, false); str += " leaps into the air, briefly spins, then lands back on " "your head!"; messages.push_back(str); } if ((item = _tran_get_eq(EQ_BOOTS)) && item->sub_type == ARM_BOOTS && !you.cannot_act()) { std::string name = item->name(DESC_BASENAME, false, false, false); name = replace_all(name, "pair of ", ""); std::string str = "You compulsively click the heels of your "; str += name; str += " together three times."; } if ((item = _tran_get_eq(EQ_SHIELD))) { std::string str = "Your "; str += item->name(DESC_BASENAME, false, false, false); str += " spins!"; messages.push_back(str); str = "Your "; str += item->name(DESC_BASENAME, false, false, false); str += " briefly flashes a lurid colour!"; messages.push_back(str); } if ((item = _tran_get_eq(EQ_BODY_ARMOUR))) { std::string str; std::string name = item->name(DESC_BASENAME, false, false, false); if (name.find("dragon") != std::string::npos) { str = "The scales on your "; str += name; str += " wiggle briefly."; } else if (item->sub_type == ARM_ANIMAL_SKIN) { str = "The fur on your "; str += name; str += " grows longer at an alarming rate, then retracts back " "to normal."; } else if (item->sub_type == ARM_LEATHER_ARMOUR) { str = "Your "; str += name; str += " briefly grows fur, then returns to normal."; } else if (item->sub_type == ARM_ROBE) { str = "You briefly become tangled in your "; str += pluralise(name); str += "."; } else if (item->sub_type >= ARM_RING_MAIL && item->sub_type <= ARM_PLATE_MAIL) { str = "Your "; str += name; str += " briefly appears rusty."; } if (!str.empty()) messages.push_back(str); } //////// // Misc. if (inv_items.size() > 0) { int idx = inv_items[random2(inv_items.size())]; item = &you.inv[idx]; std::string name = item->name(DESC_CAP_YOUR, false, false, false); std::string verb = coinflip() ? "glow" : "vibrate"; if (item->quantity == 1) verb += "s"; messages.push_back(name + " briefly " + verb + "."); } if (!priority.empty() && coinflip()) mpr(priority[random2(priority.size())].c_str()); else mpr(messages[random2(messages.size())].c_str()); } static void _get_hand_type(std::string &hand, bool &can_plural) { hand = ""; can_plural = true; const int transform = you.attribute[ATTR_TRANSFORMATION]; std::vector hand_vec; std::vector plural_vec; bool plural; hand_vec.push_back(you.hand_name(false, &plural)); plural_vec.push_back(plural); if (you.species != SP_NAGA || transform_changed_physiology()) { item_def* item; if ((item = _tran_get_eq(EQ_BOOTS)) && item->sub_type == ARM_BOOTS) { hand_vec.push_back("boot"); plural = true; } else hand_vec.push_back(you.foot_name(false, &plural)); plural_vec.push_back(plural); } if (transform == TRAN_SPIDER) { hand_vec.push_back("mandible"); plural_vec.push_back(true); } else if (you.species != SP_MUMMY && !player_mutation_level(MUT_BEAK) || transform_changed_physiology()) { hand_vec.push_back("nose"); plural_vec.push_back(false); } if (transform == TRAN_BAT || you.species != SP_MUMMY && !transform_changed_physiology()) { hand_vec.push_back("ear"); plural_vec.push_back(true); } if (!transform_changed_physiology()) { hand_vec.push_back("elbow"); plural_vec.push_back(true); } ASSERT(hand_vec.size() == plural_vec.size()); ASSERT(hand_vec.size() > 0); const unsigned int choice = random2(hand_vec.size()); hand = hand_vec[choice]; can_plural = plural_vec[choice]; } static void _xom_miscast(const int max_level, const bool nasty) { ASSERT(max_level >= 0 && max_level <= 3); const char* speeches[4] = { "zero miscast effect", "minor miscast effect", "medium miscast effect", "major miscast effect" }; const char* causes[4] = { "the mischief of Xom", "the capriciousness of Xom", "the capriciousness of Xom", "the severe capriciousness of Xom" }; const char* speech_str = speeches[max_level]; const char* cause_str = causes[max_level]; const int level = random2(max_level + 1); // Take a note. std::string desc = "miscast effect"; #ifdef NOTE_DEBUG_XOM static char level_buf[20]; snprintf(level_buf, sizeof(level_buf), " level %d%s", level, (nasty ? " (nasty)" : "")); desc += level_buf; #endif take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, desc.c_str()), true); if (level == 0 && one_chance_in(20)) { god_speaks(GOD_XOM, _get_xom_speech(speech_str).c_str()); _xom_zero_miscast(); return; } std::string hand_str; bool can_plural; _get_hand_type(hand_str, can_plural); // If Xom's not being nasty, then prevent spell miscasts from // killing the player. const int lethality_margin = nasty ? 0 : random_range(1, 4); god_speaks(GOD_XOM, _get_xom_speech(speech_str).c_str()); MiscastEffect(&you, -GOD_XOM, SPTYP_RANDOM, level, cause_str, NH_DEFAULT, lethality_margin, hand_str, can_plural); } static bool _xom_lose_stats() { stat_type stat = STAT_RANDOM; int max = 3; // Don't kill the player unless Xom is being nasty. if (!_xom_feels_nasty()) { // Make sure not to lower strength so much that the player // will die once might wears off. char vals[3] = {you.strength - (you.duration[DUR_MIGHT] ? 5 : 0), you.dex, you.intel}; stat_type types[3] = {STAT_STRENGTH, STAT_DEXTERITY, STAT_INTELLIGENCE}; int tries = 0; do { int idx = random2(3); stat = types[idx]; max = std::min(3, vals[idx] - 1); } while (max < 2 && (++tries < 30)); if (tries >= 30) return (false); } god_speaks(GOD_XOM, _get_xom_speech("lose stats").c_str()); const int loss = 1 + random2(max); lose_stat(stat, loss, true, "the vengeance of Xom" ); // Take a note. static char stat_buf[80]; snprintf(stat_buf, sizeof(stat_buf), "stat loss: -%d %s (%d/%d)", loss, (stat == STAT_STRENGTH ? "Str" : stat == STAT_DEXTERITY ? "Dex" : "Int"), (stat == STAT_STRENGTH ? you.strength : stat == STAT_DEXTERITY ? you.dex : you.intel), (stat == STAT_STRENGTH ? you.max_strength : stat == STAT_DEXTERITY ? you.max_dex : you.max_intel)); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, stat_buf), true); return (true); } static bool _xom_chaos_upgrade_nearby_monster() { bool rc = false; monsters *mon = choose_random_nearby_monster(0, _choose_chaos_upgrade); if (!mon) return (false); god_speaks(GOD_XOM, _get_xom_speech("chaos upgrade").c_str()); mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE}; for (int i = 0; i < 3 && !rc; ++i) { item_def* const item = mon->mslot_item(slots[i]); if (item && _is_chaos_upgradeable(*item, mon)) { _do_chaos_upgrade(*item, mon); rc = true; } } ASSERT(rc); // Wake the monster up. behaviour_event(mon, ME_ALERT, MHITYOU); if (rc) take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "chaos upgrade"), true); return (rc); } static bool _xom_player_confusion_effect(int sever) { if (!_xom_feels_nasty()) { // Don't confuse the player if standing next to lava or deep water. for (adjacent_iterator ai; ai; ++ai) if (in_bounds(*ai) && is_grid_dangerous(grd(*ai))) return (false); } bool rc = false; // Looks like this will *always* succeed? if (confuse_player(random2(sever) + 1, false)) { // FIXME: Message order is a bit off here. god_speaks(GOD_XOM, _get_xom_speech("confusion").c_str()); rc = true; // Sometimes Xom gets carried away and starts confusing // other creatures too. bool mons_too = false; if (coinflip()) { for (unsigned i = 0; i < MAX_MONSTERS; ++i) { monsters* const monster = &menv[i]; if (!monster->alive() || !mons_near(monster) || !mons_class_is_confusable(monster->type) || one_chance_in(20)) { continue; } if (monster->add_ench(mon_enchant(ENCH_CONFUSION, 0, KC_FRIENDLY, random2(sever)))) { simple_monster_message(monster, " looks rather confused."); } mons_too = true; } } // Take a note. std::string conf_msg = "confusion"; if (mons_too) conf_msg += " (+ monsters)"; take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, conf_msg.c_str()), true); } return (rc); } static bool _valid_floor_grid(coord_def pos) { if (!in_bounds(pos)) return (false); return (grd(pos) == DNGN_FLOOR); } static bool _move_stair(coord_def stair_pos, bool away) { dungeon_feature_type feat = grd(stair_pos); ASSERT(grid_stair_direction(feat) != CMD_NO_CMD); coord_def begin, towards; bool stairs_moved = false; if (away) { // If the staircase starts out under the player, first shove it // onto a neighbouring grid. if (stair_pos == you.pos()) { coord_def new_pos(stair_pos); // Loop twice through all adjacent grids. In the first round, // only consider grids whose next neighbour in the direction // away from the player is also of type floor. If we didn't // find any matching grid, try again without that restriction. for (int tries = 0; tries < 2; ++tries) { int adj_count = 0; for (adjacent_iterator ai(stair_pos); ai; ++ai) if (grd(*ai) == DNGN_FLOOR && (tries || _valid_floor_grid(*ai + *ai - stair_pos)) && one_chance_in(++adj_count)) { new_pos = *ai; } if (!tries && new_pos != stair_pos) break; } if (new_pos == stair_pos) return (false); if (!slide_feature_over(stair_pos, new_pos)) return (false); stair_pos = new_pos; stairs_moved = true; } begin = you.pos(); towards = stair_pos; } else { // Can't move towards player if it's already adjacent. if (adjacent(you.pos(), stair_pos)) return (false); begin = stair_pos; towards = you.pos(); } ray_def ray; if (!find_ray(begin, towards, true, ray, 0, true)) { mpr("Couldn't find ray between player and stairs.", MSGCH_ERROR); return (stairs_moved); } // Don't start off under the player. if (away) ray.advance(); bool found_stairs = false; int past_stairs = 0; while (in_bounds(ray.pos()) && see_grid(ray.pos()) && !grid_is_solid(ray.pos()) && ray.pos() != you.pos()) { if (ray.pos() == stair_pos) found_stairs = true; if (found_stairs) past_stairs++; ray.advance(); } past_stairs--; if (!away && grid_is_solid(ray.pos())) { // Transparent wall between stair and player. return (stairs_moved); } if (away && !found_stairs) { if (grid_is_solid(ray.pos())) { // Transparent wall between stair and player. return (stairs_moved); } mpr("Ray didn't cross stairs.", MSGCH_ERROR); } if (away && past_stairs <= 0) { // Stairs already at edge, can't move further away. return (stairs_moved); } if (!in_bounds(ray.pos()) || ray.pos() == you.pos()) ray.regress(); while (!see_grid(ray.pos()) || grd(ray.pos()) != DNGN_FLOOR) { ray.regress(); if (!in_bounds(ray.pos()) || ray.pos() == you.pos() || ray.pos() == stair_pos) { // No squares in path are a plain floor. return (stairs_moved); } } ASSERT(stair_pos != ray.pos()); std::string stair_str = feature_description(stair_pos, false, DESC_CAP_THE, false); mprf("%s slides %s you!", stair_str.c_str(), away ? "away from" : "towards"); // Animate stair moving. const feature_def &feat_def = get_feature_def(feat); bolt beam; beam.range = INFINITE_DISTANCE; beam.flavour = BEAM_VISUAL; beam.type = feat_def.symbol; beam.colour = feat_def.colour; beam.source = stair_pos; beam.target = ray.pos(); beam.name = "STAIR BEAM"; beam.draw_delay = 50; // Make beam animation slower than normal. beam.aimed_at_spot = true; beam.fire(); // Clear out "missile trails" viewwindow(true, false); if (!swap_features(stair_pos, ray.pos(), false, false)) { mprf(MSGCH_ERROR, "_move_stair(): failed to move %s", stair_str.c_str()); return (stairs_moved); } return (true); } static bool _repel_stairs() { // Repeating the effect while it's still active is boring. if (you.duration[DUR_REPEL_STAIRS_MOVE] || you.duration[DUR_REPEL_STAIRS_CLIMB]) { return (false); } std::vector stairs_avail; bool real_stairs = false; for (radius_iterator ri(you.pos(), LOS_RADIUS, false, true); ri; ++ri) { dungeon_feature_type feat = grd(*ri); if (grid_stair_direction(feat) != CMD_NO_CMD && feat != DNGN_ENTER_SHOP) { stairs_avail.push_back(*ri); if (grid_is_staircase(feat)) real_stairs = true; } } // Should only happen if there are stairs in view. if (stairs_avail.empty()) return (false); // Don't mention staircases if there aren't any nearby. std::string stair_msg = _get_xom_speech("repel stairs"); if (stair_msg.find("@staircase@") != std::string::npos) { std::string feat_name; if (!real_stairs) { if (grid_is_escape_hatch(grd(stairs_avail[0]))) feat_name = "escape hatch"; else feat_name = "gate"; } else feat_name = "staircase"; stair_msg = replace_all(stair_msg, "@staircase@", feat_name); } god_speaks(GOD_XOM, stair_msg.c_str()); you.duration[DUR_REPEL_STAIRS_MOVE] = 1000; if (one_chance_in(5) || grid_stair_direction(grd(you.pos())) != CMD_NO_CMD && grd(you.pos()) != DNGN_ENTER_SHOP) { you.duration[DUR_REPEL_STAIRS_CLIMB] = 500; } std::random_shuffle(stairs_avail.begin(), stairs_avail.end()); int count_moved = 0; for (unsigned int i = 0; i < stairs_avail.size(); i++) if (_move_stair(stairs_avail[i], true)) count_moved++; if (!count_moved) { if (one_chance_in(8)) mpr("Nothing appears to happen... Ominous!"); else canned_msg(MSG_NOTHING_HAPPENS); } else take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "repel stairs"), true); return (true); } static bool _xom_draining_torment_effect(int sever) { const std::string speech = _get_xom_speech("draining or torment"); const bool nasty = _xom_feels_nasty(); bool rc = false; if (one_chance_in(4)) { // XP drain effect (25%). if (player_prot_life() < 3 && (nasty || you.experience > 0)) { god_speaks(GOD_XOM, speech.c_str()); drain_exp(); if (random2(sever) > 3 && (nasty || you.experience > 0)) drain_exp(); if (random2(sever) > 3 && (nasty || you.experience > 0)) drain_exp(); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "draining"), true); rc = true; } } else { // Torment effect (75%). if (!player_res_torment()) { god_speaks(GOD_XOM, speech.c_str()); torment_player(0, TORMENT_XOM); // Take a note. static char torment_buf[80]; snprintf(torment_buf, sizeof(torment_buf), "torment (%d/%d hp)", you.hp, you.hp_max); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, torment_buf), true); rc = true; } } return (rc); } static bool _xom_summon_hostiles(int sever) { bool rc = false; const std::string speech = _get_xom_speech("hostile monster"); // Nasty, but fun. if (player_weapon_wielded() && one_chance_in(4)) { const item_def& weapon = *you.weapon(); const std::string wep_name = weapon.name(DESC_PLAIN); rc = cast_tukimas_dance(100, GOD_XOM, true); if (rc) { static char wpn_buf[80]; snprintf(wpn_buf, sizeof(wpn_buf), "animates weapon (%s)", wep_name.c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, wpn_buf), true); } } else { // The number of demons is dependent on severity, though heavily // randomized. int numdemons = sever; for (int i = 0; i < 3; i++) numdemons = random2(numdemons+1); numdemons = std::min(numdemons+1,14); int num_summoned = 0; for (int i = 0; i < numdemons; ++i) { if (create_monster( mgen_data::hostile_at( _xom_random_demon(sever), you.pos(), 4, 0, true, GOD_XOM, MON_SUMM_WRATH)) != -1) { num_summoned++; } } if (num_summoned > 0) { static char summ_buf[80]; snprintf(summ_buf, sizeof(summ_buf), "summons %d hostile demon%s", num_summoned, num_summoned > 1 ? "s" : ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); rc = true; } } if (rc) god_speaks(GOD_XOM, speech.c_str()); return (rc); } static bool _xom_is_bad(int sever, int tension) { bool done = false; bool nasty = (sever >= 5 && _xom_feels_nasty()); god_acting gdact(GOD_XOM); // Rough estimate of how bad a Xom effect hits the player, // scaled between 1 (harmless) and 5 (disastrous). int badness = 1; while (!done) { // Did Xom kill the player? if (_player_is_dead()) return (true); if (!nasty && x_chance_in_y(3, sever)) { _xom_miscast(0, nasty); done = true; } else if (!nasty && x_chance_in_y(4, sever)) { _xom_miscast(1, nasty); done = true; } else if (x_chance_in_y(5, sever)) { done = _xom_lose_stats(); badness = 2; } else if (x_chance_in_y(6, sever)) { _xom_miscast(2, nasty); badness = 2; done = true; } else if (x_chance_in_y(7, sever) && you.level_type != LEVEL_ABYSS) { // Try something else if teleportation is impossible. if (scan_randarts(RAP_PREVENT_TELEPORTATION)) return (false); // This is not particularly exciting if the level is already // fully explored (presumably cleared). If Xom is feeling // nasty, this is likelier to happen if the level is // unexplored. const int explored = _exploration_estimate(true); if (nasty && explored >= 50 && coinflip() || explored >= 80 + random2(20)) { done = false; continue; } // The Xom teleportation train takes you on instant // teleportation to a few random areas, stopping if either // an area is dangerous to you or randomly. god_speaks(GOD_XOM, _get_xom_speech("teleportation journey").c_str()); int count = 0; do { count++; you_teleport_now(false); more(); } while (count > 7 + random2(5) || x_chance_in_y(3, 4) && !player_in_a_dangerous_place()); badness = player_in_a_dangerous_place() ? 3 : 1; maybe_update_stashes(); // Take a note. static char tele_buf[80]; snprintf(tele_buf, sizeof(tele_buf), "%d-stop teleportation journey%s", count, #ifdef NOTE_DEBUG_XOM badness == 3 ? " (dangerous)" : ""); #else ""); #endif take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, tele_buf), true); done = true; } else if (x_chance_in_y(8, sever)) { done = _xom_chaos_upgrade_nearby_monster(); badness = 2 + coinflip(); } else if (random2(tension) < 10 && x_chance_in_y(9, sever)) { done = _xom_give_mutations(false); badness = 3; } else if (x_chance_in_y(10, sever)) { done = _xom_polymorph_nearby_monster(false); badness = 3; } else if (tension > 0 && x_chance_in_y(11, sever)) { done = _repel_stairs(); badness = (you.duration[DUR_REPEL_STAIRS_CLIMB] ? 3 : 2); } // It's pointless to confuse player if there's no danger nearby. else if (tension > 0 && x_chance_in_y(12, sever)) { done = _xom_player_confusion_effect(sever); badness = (random2(tension) > 5 ? 2 : 1); } else if (x_chance_in_y(13, sever)) { done = _xom_draining_torment_effect(sever); badness = (random2(tension) > 5 ? 3 : 2); } else if (x_chance_in_y(14, sever)) { done = _xom_summon_hostiles(sever); badness = 3 + coinflip(); } else if (x_chance_in_y(15, sever)) { _xom_miscast(3, nasty); badness = 4 + coinflip(); done = true; } else if (one_chance_in(sever) && you.level_type != LEVEL_ABYSS) { god_speaks(GOD_XOM, _get_xom_speech("banishment").c_str()); // handles note taking banished(DNGN_ENTER_ABYSS, "Xom"); badness = 5; done = true; } } // If we got here because Xom was bored, reset gift timeout according // to the badness of the effect. if (done && _xom_is_bored()) { const int interest = random2avg(badness*60, 2); you.gift_timeout = std::min(interest, 255); #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM) mprf(MSGCH_DIAGNOSTICS, "badness: %d, new interest: %d", badness, you.gift_timeout); #endif } return (done); } static char* _stat_ptrs[3] = {&you.strength, &you.intel, &you.dex}; static void _handle_accidental_death(const int orig_hp, const char orig_stats[], const FixedVector &orig_mutation) { // Did ouch() return early because the player died from the Xom // effect, even though neither is the player under penance nor is // Xom bored? if (!you.did_escape_death() && you.escaped_death_aux.empty() && !_player_is_dead()) { // The player is fine. return; } std::string speech_type = "accidental homicide"; const dungeon_feature_type feat = grd(you.pos()); switch (you.escaped_death_cause) { case NUM_KILLBY: case KILLED_BY_LEAVING: case KILLED_BY_WINNING: case KILLED_BY_QUITTING: speech_type = "weird death"; break; case KILLED_BY_LAVA: case KILLED_BY_WATER: if (!_feat_is_deadly(feat)) speech_type = "weird death"; break; case KILLED_BY_STUPIDITY: if (you.intel > 0) speech_type = "weird death"; break; case KILLED_BY_WEAKNESS: if (you.strength > 0) speech_type = "weird death"; break; case KILLED_BY_CLUMSINESS: if (you.dex > 0) speech_type = "weird death"; break; default: if (_feat_is_deadly(feat)) speech_type = "weird death"; if (you.strength <= 0 || you.intel <= 0 || you.dex <= 0) speech_type = "weird death"; break; } mpr("You die..."); god_speaks(GOD_XOM, _get_xom_speech(speech_type).c_str()); god_speaks(GOD_XOM, _get_xom_speech("resurrection").c_str()); if (you.hp <= 0) you.hp = std::min(orig_hp, you.hp_max); mutation_type dex_muts[5] = {MUT_GREY2_SCALES, MUT_METALLIC_SCALES, MUT_YELLOW_SCALES, MUT_RED2_SCALES, MUT_STRONG_STIFF}; for (int i = 0; i < 5; ++i) { mutation_type bad = dex_muts[i]; while (you.dex <= 0 && you.mutation[bad] > orig_mutation[bad]) delete_mutation(bad, true, true, true); } while (you.dex <= 0 && you.mutation[MUT_FLEXIBLE_WEAK] < orig_mutation[MUT_FLEXIBLE_WEAK]) { mutate(MUT_FLEXIBLE_WEAK, true, true, true); } while (you.strength <= 0 && you.mutation[MUT_FLEXIBLE_WEAK] > orig_mutation[MUT_FLEXIBLE_WEAK]) { delete_mutation(MUT_FLEXIBLE_WEAK, true, true, true); } while (you.strength <= 0 && you.mutation[MUT_STRONG_STIFF] < orig_mutation[MUT_STRONG_STIFF]) { mutate(MUT_STRONG_STIFF, true, true, true); } mutation_type bad_muts[3] = {MUT_WEAK, MUT_DOPEY, MUT_CLUMSY}; mutation_type good_muts[3] = {MUT_STRONG, MUT_CLEVER, MUT_AGILE}; for (int i = 0; i < 3; ++i) { while (*(_stat_ptrs[i]) <= 0) { mutation_type good = good_muts[i]; mutation_type bad = bad_muts[i]; if (you.mutation[bad] > orig_mutation[bad] || you.mutation[good] < orig_mutation[good]) { mutate(good, true, true, true); } else { *(_stat_ptrs[i]) = orig_stats[i]; break; } } } you.max_strength = std::max(you.max_strength, you.strength); you.max_intel = std::max(you.max_intel, you.intel); you.max_dex = std::max(you.max_dex, you.dex); if (_feat_is_deadly(feat)) you.teleport(true); } void xom_acts(bool niceness, int sever, int tension) { #if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM) mprf(MSGCH_DIAGNOSTICS, "xom_acts(%u, %d, %d); piety: %u, interest: %u\n", niceness, sever, tension, you.piety, you.gift_timeout); #endif #ifdef WIZARD if (_player_is_dead()) { // This should only happen if the player used wizard mode to // escape from death via stat loss, or if the player used wizard // mode to escape death from deep water or lava. ASSERT(you.wizard && !you.did_escape_death()); if (_feat_is_deadly(grd(you.pos()))) { mpr("Player is standing in deadly terrain, skipping Xom act.", MSGCH_DIAGNOSTICS); } else { mpr("Player is already dead, skipping Xom act.", MSGCH_DIAGNOSTICS); } return; } #else ASSERT(!_player_is_dead()); #endif entry_cause_type old_entry_cause = you.entry_cause; sever = std::max(1, sever); god_type which_god = GOD_XOM; // Drawing the Xom card from Nemelex's decks of oddities or punishment. if (crawl_state.is_god_acting() && crawl_state.which_god_acting() != GOD_XOM) { which_god = crawl_state.which_god_acting(); if (crawl_state.is_god_retribution()) { niceness = false; simple_god_message(" asks Xom for help in punishing you, and " "Xom happily agrees.", which_god); } else { niceness = true; simple_god_message(" calls in a favour from Xom.", which_god); } } if (tension == -1) tension = get_tension(which_god); #if defined(DEBUG_RELIGION) || defined(DEBUG_XOM) || defined(DEBUG_TENSION) mprf(MSGCH_DIAGNOSTICS, "Xom tension: %d", tension); #endif const int orig_hp = you.hp; const char orig_stats[3] = {*(_stat_ptrs[0]), *(_stat_ptrs[1]), *(_stat_ptrs[2])}; const FixedVector orig_mutation = you.mutation; #ifdef NOTE_DEBUG_XOM static char xom_buf[100]; snprintf(xom_buf, sizeof(xom_buf), "xom_acts(%s, %d, %d), mood: %d", (niceness ? "true" : "false"), sever, tension, you.piety); take_note(Note(NOTE_MESSAGE, 0, 0, xom_buf), true); #endif const bool was_bored = _xom_is_bored(); if (niceness && !one_chance_in(20)) { // Good stuff. while (!_xom_is_good(sever, tension)) ; } else { if (was_bored && Options.note_xom_effects) take_note(Note(NOTE_MESSAGE, 0, 0, "XOM is BORED!"), true); #ifdef NOTE_DEBUG_XOM else if (niceness) { take_note(Note(NOTE_MESSAGE, 0, 0, "good act randomly turned bad"), true); } #endif // Bad mojo. while (!_xom_is_bad(sever, tension)) ; } _handle_accidental_death(orig_hp, orig_stats, orig_mutation); // Drawing the Xom card from Nemelex's decks of oddities or punishment. if (crawl_state.is_god_acting() && crawl_state.which_god_acting() != GOD_XOM) { if (old_entry_cause != you.entry_cause && you.entry_cause_god == GOD_XOM) { you.entry_cause_god = crawl_state.which_god_acting(); } } if (you.religion == GOD_XOM && one_chance_in(5)) { const std::string old_xom_favour = describe_xom_favour(); you.piety = random2(MAX_PIETY + 1); const std::string new_xom_favour = describe_xom_favour(); if (was_bored || old_xom_favour != new_xom_favour) { const std::string msg = "You are now " + new_xom_favour; god_speaks(you.religion, msg.c_str()); } #ifdef NOTE_DEBUG_XOM snprintf(info, INFO_SIZE, "reroll piety: %d", you.piety); take_note(Note(NOTE_MESSAGE, 0, 0, info), true); #endif } else if (was_bored) { // If we didn't reroll at least mention the new favour // now it's not "BORING thing" anymore. const std::string new_xom_favour = describe_xom_favour(); const std::string msg = "You are now " + new_xom_favour; god_speaks(you.religion, msg.c_str()); } } static void _xom_check_less_runes(int runes_gone) { if (player_in_branch(BRANCH_HALL_OF_ZOT) || !(branches[BRANCH_HALL_OF_ZOT].branch_flags & BFLAG_HAS_ORB)) { return; } int runes_avail = you.attribute[ATTR_UNIQUE_RUNES] + you.attribute[ATTR_DEMONIC_RUNES] + you.attribute[ATTR_ABYSSAL_RUNES]; int was_avail = runes_avail + runes_gone; // No longer enough available runes to get into Zot. if (was_avail >= NUMBER_OF_RUNES_NEEDED && runes_avail < NUMBER_OF_RUNES_NEEDED) { xom_is_stimulated(128, "Xom snickers.", true); } } void xom_check_lost_item(const item_def& item) { if (item.base_type == OBJ_ORBS) xom_is_stimulated(255, "Xom laughs nastily.", true); else if (is_fixed_artefact(item)) xom_is_stimulated(128, "Xom snickers.", true); else if (is_rune(item)) { // If you'd dropped it, check if that means you'd dropped your // third rune, and now you don't have enough to get into Zot. if (item.flags & ISFLAG_BEEN_IN_INV) _xom_check_less_runes(item.quantity); if (is_unique_rune(item)) xom_is_stimulated(255, "Xom snickers loudly.", true); else if (you.entry_cause == EC_SELF_EXPLICIT && !(item.flags & ISFLAG_BEEN_IN_INV)) { // Player voluntarily entered Pan or the Abyss looking for // runes, yet never found them. if (item.plus == RUNE_ABYSSAL && you.attribute[ATTR_ABYSSAL_RUNES] == 0) { // Ignore Abyss area shifts. if (you.level_type != LEVEL_ABYSS) { // Abyssal runes are a lot more trouble to find than // demonic runes, so they get twice the stimulation. xom_is_stimulated(128, "Xom snickers.", true); } } else if (item.plus == RUNE_DEMONIC && you.attribute[ATTR_DEMONIC_RUNES] == 0) { xom_is_stimulated(64, "Xom snickers softly.", true); } } } } void xom_check_destroyed_item(const item_def& item, int cause) { int amusement = 0; if (item.base_type == OBJ_ORBS) { xom_is_stimulated(255, "Xom laughs nastily.", true); return; } else if (is_fixed_artefact(item)) xom_is_stimulated(128, "Xom snickers.", true); else if (is_rune(item)) { _xom_check_less_runes(item.quantity); if (is_unique_rune(item) || item.plus == RUNE_ABYSSAL) amusement = 255; else amusement = 64 * item.quantity; } xom_is_stimulated(amusement, (amusement > 128) ? "Xom snickers loudly." : (amusement > 64) ? "Xom snickers." : "Xom snickers softly.", true); } static bool _death_is_funny(const kill_method_type killed_by) { switch (killed_by) { // The less original deaths are considered boring. case KILLED_BY_MONSTER: case KILLED_BY_BEAM: case KILLED_BY_CLOUD: case KILLED_BY_FREEZING: case KILLED_BY_BURNING: case KILLED_BY_SELF_AIMED: case KILLED_BY_SOMETHING: case KILLED_BY_TRAP: return (false); default: // All others are fun (says Xom). return (true); } } void xom_death_message(const kill_method_type killed_by) { if (you.religion != GOD_XOM && (!you.worshipped[GOD_XOM] || coinflip())) return; const int death_tension = get_tension(GOD_XOM, false); // "Normal" deaths with only down to -2 hp and comparatively low tension // are considered particularly boring. if (!_death_is_funny(killed_by) && you.hp >= -1 * random2(3) && death_tension <= random2(10)) { god_speaks(GOD_XOM, _get_xom_speech("boring death").c_str()); } // Unusual methods of dying, really low hp, or high tension make // for funny deaths. else if (_death_is_funny(killed_by) || you.hp <= -10 || death_tension >= 20) { god_speaks(GOD_XOM, _get_xom_speech("laughter").c_str()); } // All others just get ignored by Xom. }