From fa1f442ef7999d3e84dadd3e9c0823ceb937dab2 Mon Sep 17 00:00:00 2001 From: David Lawrence Ramsey Date: Sun, 11 Oct 2009 18:11:30 -0500 Subject: Apply jpeg's patches in [2876380] (automate Xom testing) and [2876688] (new Xom effects), tweaked to fit into current trunk. --- crawl-ref/source/acr.cc | 2 +- crawl-ref/source/command.cc | 3 +- crawl-ref/source/dat/database/godspeak.txt | 23 + crawl-ref/source/items.cc | 12 +- crawl-ref/source/items.h | 1 + crawl-ref/source/kills.cc | 3 + crawl-ref/source/xom.cc | 1097 +++++++++++++++++++++++----- crawl-ref/source/xom.h | 56 +- 8 files changed, 1006 insertions(+), 191 deletions(-) (limited to 'crawl-ref/source') diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index 02a5e42ce8..988da0c579 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -464,7 +464,7 @@ static void _do_wizard_command(int wiz_command, bool silent_fail) case CONTROL('G'): save_ghost(true); break; case CONTROL('H'): wizard_set_hunger_state(); break; case CONTROL('I'): debug_item_statistics(); break; - case CONTROL('X'): wizard_set_xl(); break; + case CONTROL('L'): wizard_set_xl(); break; case 'O': debug_test_explore(); break; case 'S': wizard_set_skill_level(); break; diff --git a/crawl-ref/source/command.cc b/crawl-ref/source/command.cc index 211ff61a2f..2e022f4200 100644 --- a/crawl-ref/source/command.cc +++ b/crawl-ref/source/command.cc @@ -2380,7 +2380,7 @@ int list_wizard_commands(bool do_redraw_screen) "s : gain 20000 skill points\n" "S : set skill to level\n" "x : gain an experience level\n" - "Ctrl-X : change experience level\n" + "Ctrl-L : change experience level\n" "$ : get 1000 gold\n" "] : get a mutation\n" "[ : get a demonspawn mutation\n" @@ -2442,6 +2442,7 @@ int list_wizard_commands(bool do_redraw_screen) "F : combat stats with fsim_kit\n" "Ctrl-F : combat stats (monster vs PC)\n" "Ctrl-I : item generation stats\n" + "Ctrl-X : Xom effect stats\n" "O : measure exploration time\n" "\n" "? : list wizard commands\n", diff --git a/crawl-ref/source/dat/database/godspeak.txt b/crawl-ref/source/dat/database/godspeak.txt index df4689dbe7..c5045dda27 100644 --- a/crawl-ref/source/dat/database/godspeak.txt +++ b/crawl-ref/source/dat/database/godspeak.txt @@ -176,6 +176,21 @@ Xom tricks other gods for their servants. Xom momentarily opens a gate. %%%% +# changing the scenery +Xom scenery + +Xom briefly touches your surroundings. + +Xom adds some personal touches to your environment. + +Xom complains about the scenery! + +Xom alters the dungeon around you. + +"There, this looks better." + +"Just a minor improvement..." +%%%% ################## # Xom nastiness ################## @@ -238,6 +253,10 @@ Xom decides to rearrange the pieces. "This might be better!" %%%% +Xom swap weapons + +@Xom rearrange the pieces@ +%%%% Xom teleportation journey "Whee!" @@ -278,6 +297,10 @@ Xom hostile monster You hear Xom's maniacal laughter. %%%% +Xom animate monster weapon + +@Xom hostile monster@ +%%%% Xom banishment "You have grown too comfortable in your little world, mortal!" diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index faf4f7168e..2e0d7a7015 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -1475,6 +1475,13 @@ static void _got_item(item_def& item, int quant) _check_note_item(item); } +void note_inscribe_item(item_def &item) +{ + _autoinscribe_item(item); + _origin_freeze(item, you.pos()); + _check_note_item(item); +} + // Returns quantity of items moved into player's inventory and -1 if // the player's inventory is full. int move_item_to_player( int obj, int quant_got, bool quiet, @@ -1637,10 +1644,7 @@ int move_item_to_player( int obj, int quant_got, bool quiet, if (!item.slot) item.slot = index_to_letter(item.link); - _autoinscribe_item( item ); - - _origin_freeze(item, you.pos()); - _check_note_item(item); + note_inscribe_item(item); item.quantity = quant_got; if (is_blood_potion(mitm[obj])) diff --git a/crawl-ref/source/items.h b/crawl-ref/source/items.h index fc33f88062..aa70cee2f7 100644 --- a/crawl-ref/source/items.h +++ b/crawl-ref/source/items.h @@ -38,6 +38,7 @@ void inc_mitm_item_quantity(int obj, int amount); bool move_item_to_grid( int *const obj, const coord_def& p ); void move_item_stack_to_grid( const coord_def& from, const coord_def& to ); +void note_inscribe_item(item_def &item); int move_item_to_player( int obj, int quant_got, bool quiet = false, bool ignore_burden = false ); void mark_items_non_pickup_at(const coord_def &pos); diff --git a/crawl-ref/source/kills.cc b/crawl-ref/source/kills.cc index 6539edfb6c..6b7aef270d 100644 --- a/crawl-ref/source/kills.cc +++ b/crawl-ref/source/kills.cc @@ -396,6 +396,9 @@ std::string apostrophise(const std::string &name) if (name.empty()) return (name); + if (name == "it" || name == "It") + return (name + "s"); + const char lastc = name[name.length() - 1]; return (name + (lastc == 's' ? "'" : "'s")); } diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc index 3704537647..0fdc3465f1 100644 --- a/crawl-ref/source/xom.cc +++ b/crawl-ref/source/xom.cc @@ -28,6 +28,11 @@ REVISION("$Rev$"); #include "mutation.h" #include "notes.h" #include "ouch.h" +#ifdef WIZARD +#include "item_use.h" // for safe_to_remove_or_wear() +#include "output.h" // for the monster list +#include "quiver.h" // to update the quiver if necessary +#endif #include "player.h" #include "religion.h" #include "spells2.h" @@ -111,6 +116,17 @@ static const char *_xom_message_arrays[NUM_XOM_MESSAGE_TYPES][6] = } }; +static const char *describe_xom_mood() +{ + return (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."; +} + const char *describe_xom_favour(bool upper) { std::string favour; @@ -119,15 +135,7 @@ const char *describe_xom_favour(bool upper) 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."; - } + favour = describe_xom_mood(); if (upper) favour = uppercase_first(favour); @@ -465,7 +473,8 @@ static bool _transformation_check(const spell_type spell) return transform(0, tran, true, true); } -static bool _xom_makes_you_cast_random_spell(int sever, int tension) +static int _xom_makes_you_cast_random_spell(int sever, int tension, + bool debug = false) { int spellenum = std::max(1, sever); @@ -482,7 +491,7 @@ static bool _xom_makes_you_cast_random_spell(int sever, int tension) // 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); + return (XOM_DID_NOTHING); } else { @@ -498,7 +507,7 @@ static bool _xom_makes_you_cast_random_spell(int sever, int tension) // a chance we might try something else. const int explored = _exploration_estimate(); if (explored > 80 && x_chance_in_y(explored, 100)) - return (false); + return (XOM_DID_NOTHING); } } @@ -506,7 +515,10 @@ static bool _xom_makes_you_cast_random_spell(int sever, int tension) if (spell == FAKE_SPELL_MAGIC_MAPPING) { if (you.level_type == LEVEL_PANDEMONIUM) - return (false); + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_GOOD_MAPPING); god_speaks(GOD_XOM, _get_xom_speech("spell effect").c_str()); @@ -522,16 +534,22 @@ static bool _xom_makes_you_cast_random_spell(int sever, int tension) const int power = stepdown_value( sever, 10, 10, 40, 45 ); magic_mapping( 5 + power, 50 + random2avg( power * 2, 2 ), false ); - return (true); + return (XOM_GOOD_MAPPING); } // Don't attempt to cast spells the undead cannot memorise. if (you_cannot_memorise(spell)) - return (false); + return (XOM_DID_NOTHING); // Don't attempt to transform the player if the transformation will fail. if (!_transformation_check(spell)) - return (false); + return (XOM_DID_NOTHING); + + const int result = (tension > 0 ? XOM_GOOD_SPELL_TENSION + : XOM_GOOD_SPELL_CALM); + + if (debug) + return (result); god_speaks(GOD_XOM, _get_xom_speech("spell effect").c_str()); @@ -547,7 +565,7 @@ static bool _xom_makes_you_cast_random_spell(int sever, int tension) take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, spell_buf), true); your_spells(spell, sever, false); - return (true); + return (result); } static void _try_brand_switch(const int item_index) @@ -681,7 +699,9 @@ static object_class_type _get_unrelated_wield_class(object_class_type ref) return (objtype); } -static bool _xom_annoyance_gift(int power) +// Gift an item the player can't currently use. It can still be really +// good or even acquirement level. +static bool _xom_annoyance_gift(int power, bool debug = false) { god_acting gdact(GOD_XOM); @@ -692,6 +712,9 @@ static bool _xom_annoyance_gift(int power) // Xom has a sense of humour. if (coinflip() && weapon && weapon->cursed()) { + if (debug) + return (true); + // 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()); @@ -706,6 +729,9 @@ static bool _xom_annoyance_gift(int power) const item_def *gloves = you.slot_item(EQ_GLOVES); if (coinflip() && gloves && gloves->cursed()) { + if (debug) + return (true); + // 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()); @@ -716,6 +742,9 @@ static bool _xom_annoyance_gift(int power) const item_def *amulet = you.slot_item(EQ_AMULET); if (coinflip() && amulet && amulet->cursed()) { + if (debug) + return (true); + // 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()); @@ -728,6 +757,9 @@ static bool _xom_annoyance_gift(int power) if (coinflip() && ((left_ring && left_ring->cursed()) || (right_ring && right_ring->cursed()))) { + if (debug) + return (true); + // 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()); @@ -737,6 +769,9 @@ static bool _xom_annoyance_gift(int power) if (one_chance_in(5) && weapon) { + if (debug) + return (true); + // 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()); @@ -752,14 +787,6 @@ static bool _xom_annoyance_gift(int power) } } - 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()) { @@ -773,7 +800,16 @@ static bool _xom_give_item(int power) return (true); } - god_speaks(GOD_XOM, _get_xom_speech("general gift").c_str()); + return (false); +} + +static int _xom_give_item(int power, bool debug = false) +{ + if (_xom_annoyance_gift(power, debug)) + return (XOM_GOOD_ANNOYANCE_GIFT); + + if (!debug) + 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 @@ -782,6 +818,9 @@ static bool _xom_give_item(int power) // and nature is not very Xomlike... if (x_chance_in_y(power, 256)) { + if (debug) + return (XOM_GOOD_ACQUIREMENT); + const object_class_type types[] = { OBJ_WEAPONS, OBJ_ARMOUR, OBJ_JEWELLERY, OBJ_BOOKS, OBJ_STAVES, OBJ_WANDS, OBJ_MISCELLANY, OBJ_FOOD, OBJ_GOLD @@ -791,12 +830,15 @@ static bool _xom_give_item(int power) } else { + if (debug) + return (XOM_GOOD_RANDOM_ITEM); + // Random-type random object. _xom_make_item(OBJ_RANDOM, OBJ_RANDOM, power * 3); } more(); - return (true); + return (XOM_GOOD_RANDOM_ITEM); } static bool _choose_mutatable_monster(const monsters* mon) @@ -827,7 +869,7 @@ static bool _is_chaos_upgradeable(const item_def &item, if (item.orig_monnum < 0) { god_type iorig = static_cast(-item.orig_monnum); - if ((iorig > GOD_NO_GOD && iorig < NUM_GODS) + if (iorig > GOD_NO_GOD && iorig < NUM_GODS && (is_good_god(iorig) || iorig == GOD_BEOGH)) { return (false); @@ -1043,8 +1085,11 @@ static bool _player_is_dead() || you.did_escape_death()); } -static bool _xom_do_potion() +static int _xom_do_potion(bool debug = false) { + if (debug) + return (XOM_GOOD_POTION); + potion_type pot = POT_HEALING; while (true) { @@ -1117,10 +1162,10 @@ static bool _xom_do_potion() potion_effect(pot, 150); - return (true); + return (XOM_GOOD_POTION); } -static bool _xom_confuse_monsters(int sever) +static int _xom_confuse_monsters(int sever, bool debug = false) { bool rc = false; monsters *monster; @@ -1136,6 +1181,9 @@ static bool _xom_confuse_monsters(int sever) continue; } + if (debug) + return (XOM_GOOD_CONFUSION); + if (monster->add_ench(mon_enchant(ENCH_CONFUSION, 0, KC_FRIENDLY, random2(sever)))) { @@ -1148,17 +1196,20 @@ static bool _xom_confuse_monsters(int sever) } } - if (rc == true) + if (rc) { take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "confuse monster(s)"), true); + return (XOM_GOOD_CONFUSION); } - return (rc); + return (XOM_DID_NOTHING); } -static bool _xom_send_allies(int sever) +static int _xom_send_allies(int sever, bool debug = false) { - bool rc = false; + if (debug) + return (XOM_GOOD_ALLIES); + // The number of allies is dependent on severity, though heavily // randomised. int numdemons = sever; @@ -1263,17 +1314,19 @@ static bool _xom_send_allies(int sever) 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 (XOM_GOOD_ALLIES); } - return (rc); + return (XOM_DID_NOTHING); } -static bool _xom_send_one_ally(int sever) +static int _xom_send_one_ally(int sever, bool debug = false) { - bool rc = false; + if (debug) + return (XOM_GOOD_SINGLE_ALLY); const monster_type mon = _xom_random_demon(sever); const bool is_demonic = (mons_class_holiness(mon) == MH_DEMONIC); @@ -1309,21 +1362,23 @@ static bool _xom_send_one_ally(int sever) menv[summons].name(DESC_PLAIN).c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); - rc = true; + return (XOM_GOOD_SINGLE_ALLY); } - return (rc); + return (XOM_DID_NOTHING); } -static bool _xom_polymorph_nearby_monster(bool helpful) +static int _xom_polymorph_nearby_monster(bool helpful, bool debug = false) { - bool rc = false; if (there_are_monsters_nearby(false, false)) { monsters *mon = choose_random_nearby_monster(0, _choose_mutatable_monster); if (mon) { + if (debug) + return (helpful ? XOM_GOOD_POLYMORPH : XOM_BAD_POLYMORPH); + const char* lookup = (helpful ? "good monster polymorph" : "bad monster polymorph"); god_speaks(GOD_XOM, _get_xom_speech(lookup).c_str()); @@ -1368,11 +1423,11 @@ static bool _xom_polymorph_nearby_monster(bool helpful) take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, poly.c_str()), true); } - rc = true; + return (helpful ? XOM_GOOD_POLYMORPH : XOM_BAD_POLYMORPH); } } - return (rc); + return (XOM_DID_NOTHING); } static void _confuse_monster(monsters mons, int sever) @@ -1429,13 +1484,168 @@ static bool _swap_monsters(monsters *m1, monsters *m2) return (true); } +static bool _art_is_safe(item_def item) +{ + int prop_str = artefact_wpn_property(item, ARTP_STRENGTH); + int prop_int = artefact_wpn_property(item, ARTP_INTELLIGENCE); + int prop_dex = artefact_wpn_property(item, ARTP_DEXTERITY); + + return (prop_str >= 0 && prop_int >= 0 && prop_dex >= 0); +} + +static int _xom_swap_weapons(bool debug = false) +{ + if (player_stair_delay()) + return (XOM_DID_NOTHING); + + item_def *wpn = you.weapon(); + + if (!wpn) + return (XOM_DID_NOTHING); + + if (you.duration[DUR_BERSERKER] + || wpn->base_type != OBJ_WEAPONS + || get_weapon_brand(*wpn) == SPWPN_DISTORTION + || !safe_to_remove_or_wear(*wpn, true, true)) + { + return (XOM_DID_NOTHING); + } + + std::vector mons_wpn; + for (unsigned i = 0; i < MAX_MONSTERS; ++i) + { + monsters* m = &menv[i]; + + if (!m->alive()) + continue; + + if (!see_grid(m->pos())) + continue; + + if (!wpn || mons_wont_attack(m) || mons_is_summoned(m) + || mons_itemuse(m) < MONUSE_STARTING_EQUIPMENT + || (m->flags & MF_HARD_RESET)) + { + continue; + } + + const int mweap = m->inv[MSLOT_WEAPON]; + if (mweap == NON_ITEM) + continue; + + const item_def weapon = mitm[mweap]; + + // Let's be nice about this. + if (weapon.base_type == OBJ_WEAPONS + && !(weapon.flags & ISFLAG_SUMMONED) + && you.can_wield(weapon, true) && m->can_wield(*wpn, true) + && !get_weapon_brand(weapon) != SPWPN_DISTORTION + && (!is_artefact(weapon) || _art_is_safe(weapon))) + { + mons_wpn.push_back(m); + } + } + if (mons_wpn.empty()) + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_BAD_SWAP_WEAPONS); + + god_speaks(GOD_XOM, _get_xom_speech("swap weapons").c_str()); + + const int num_mons = mons_wpn.size(); + // Pick a random monster... + monsters *mon = mons_wpn[random2(num_mons)]; + + // ...and get its weapon. + int monwpn = mon->inv[MSLOT_WEAPON]; + int mywpn = you.equip[EQ_WEAPON]; + ASSERT (monwpn != NON_ITEM); + ASSERT (mywpn != -1); + + unwield_item(); + + item_def &myweapon = you.inv[mywpn]; + + int index = get_item_slot(10); + if (index == NON_ITEM) + return (XOM_DID_NOTHING); + + // Move monster's old item to player's inventory as last step. + mon->unequip(*(mon->mslot_item(MSLOT_WEAPON)), MSLOT_WEAPON, 0, true); + mon->inv[MSLOT_WEAPON] = NON_ITEM; + mitm[index] = myweapon; + + unwind_var save_speedinc(mon->speed_increment); + if (!mon->pickup_item(mitm[index], false, true)) + { + mpr("Monster wouldn't take item.", MSGCH_ERROR); + mon->inv[MSLOT_WEAPON] = monwpn; + mon->equip(mitm[monwpn], MSLOT_WEAPON, 0); + unlink_item(index); + destroy_item(myweapon); + return (XOM_DID_NOTHING); + } + // Mark the weapon as thrown, so that we'll autograb it once the + // monster is dead. + mitm[index].flags |= ISFLAG_THROWN; + + mprf("%s wields %s!", + mon->name(DESC_CAP_THE).c_str(), + myweapon.name(DESC_NOCAP_YOUR).c_str()); + mon->equip(myweapon, MSLOT_WEAPON, 0); + + // Item is gone from player's inventory. + dec_inv_item_quantity(mywpn, myweapon.quantity); + + mitm[monwpn].pos.reset(); + mitm[monwpn].link = NON_ITEM; + + int freeslot = find_free_slot(mitm[monwpn]); + if (freeslot < 0 || freeslot >= ENDOFPACK + || is_valid_item(you.inv[freeslot])) + { + // Something is terribly wrong. + return (XOM_DID_NOTHING); + } + + item_def &myitem = you.inv[freeslot]; + // Copy item. + myitem = mitm[monwpn]; + myitem.link = freeslot; + myitem.pos.set(-1, -1); + // Remove "dropped by ally" flag. + myitem.flags &= ~(ISFLAG_DROPPED_BY_ALLY); + + if (!myitem.slot) + myitem.slot = index_to_letter(myitem.link); + + origin_set_monster(myitem, mon); + note_inscribe_item(myitem); + dec_mitm_item_quantity(monwpn, myitem.quantity); + you.m_quiver->on_inv_quantity_changed(freeslot, myitem.quantity); + burden_change(); + + mprf("You wield %s %s!", + mon->name(DESC_NOCAP_ITS).c_str(), + you.inv[freeslot].name(DESC_PLAIN).c_str()); + + you.equip[EQ_WEAPON] = freeslot; + wield_effects(freeslot, true); + + you.wield_change = true; + you.m_quiver->on_weapon_changed(); + + return (XOM_BAD_SWAP_WEAPONS); +} + // 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) +static int _xom_rearrange_pieces(int sever, bool debug = false) { if (player_stair_delay()) - return (false); + return (XOM_DID_NOTHING); std::vector mons; for (unsigned i = 0; i < MAX_MONSTERS; ++i) @@ -1451,7 +1661,10 @@ static bool _xom_rearrange_pieces(int sever) mons.push_back(m); } if (mons.empty()) - return (false); + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_GOOD_SWAP_MONSTERS); god_speaks(GOD_XOM, _get_xom_speech("rearrange the pieces").c_str()); @@ -1493,15 +1706,95 @@ static bool _xom_rearrange_pieces(int sever) } take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "swap monsters"), true); - return (true); + return (XOM_GOOD_SWAP_MONSTERS); } -static bool _xom_give_mutations(bool good) +static int _xom_animate_monster_weapon(int sever, bool debug = false) { - bool rc = false; + std::vector mons_wpn; + for (unsigned i = 0; i < MAX_MONSTERS; ++i) + { + monsters* m = &menv[i]; + + if (!m->alive()) + continue; + if (!see_grid(m->pos())) + continue; + + if (mons_wont_attack(m) || mons_is_summoned(m) + || mons_itemuse(m) < MONUSE_STARTING_EQUIPMENT + || (m->flags & MF_HARD_RESET)) + { + continue; + } + + const int mweap = m->inv[MSLOT_WEAPON]; + if (mweap == NON_ITEM) + continue; + + const item_def weapon = mitm[mweap]; + + if (weapon.base_type == OBJ_WEAPONS + && !(weapon.flags & ISFLAG_SUMMONED) + && weapon.quantity == 1 + && !is_range_weapon(weapon) + && !is_special_unrandom_artefact(weapon) + && !get_weapon_brand(weapon) != SPWPN_DISTORTION) + { + mons_wpn.push_back(m); + } + } + if (mons_wpn.empty()) + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_GOOD_ANIMATE_MON_WPN); + + god_speaks(GOD_XOM, _get_xom_speech("animate monster weapon").c_str()); + + const int num_mons = mons_wpn.size(); + // Pick a random monster... + monsters *mon = mons_wpn[random2(num_mons)]; + + // ...and get its weapon. + const int wpn = mon->inv[MSLOT_WEAPON]; + ASSERT(wpn != NON_ITEM); + + const int dur = std::min(2 + (random2(sever) / 5), 6); + const int monster = create_monster( + mgen_data(MONS_DANCING_WEAPON, BEH_FRIENDLY, + dur, SPELL_TUKIMAS_DANCE, + mon->pos(), mon->mindex(), 0, GOD_XOM)); + + if (monster == -1) + return (XOM_DID_NOTHING); + + // Make the monster unwield its weapon. + mon->unequip(*(mon->mslot_item(MSLOT_WEAPON)), MSLOT_WEAPON, 0, true); + mon->inv[MSLOT_WEAPON] = NON_ITEM; + + mprf("%s %s dances into the air!", + apostrophise(mon->name(DESC_CAP_THE)).c_str(), + mitm[wpn].name(DESC_PLAIN).c_str()); + + destroy_item(menv[monster].inv[MSLOT_WEAPON]); + + menv[monster].inv[MSLOT_WEAPON] = wpn; + mitm[wpn].set_holding_monster(monster); + menv[monster].colour = mitm[wpn].colour; + + return (XOM_GOOD_ANIMATE_MON_WPN); +} + +static int _xom_give_mutations(bool good, bool debug = false) +{ + bool rc = false; if (you.can_safely_mutate()) { + if (debug) + return (good ? XOM_GOOD_MUTATION : XOM_BAD_MUTATION); + const char* lookup = (good ? "good mutations" : "random mutations"); god_speaks(GOD_XOM, _get_xom_speech(lookup).c_str()); @@ -1538,13 +1831,18 @@ static bool _xom_give_mutations(bool good) } } - return (rc); + if (rc) + return (good ? XOM_GOOD_MUTATION : XOM_BAD_MUTATION); + + return (XOM_DID_NOTHING); } // Summons a permanent ally. -static bool _xom_send_major_ally(int sever) +static int _xom_send_major_ally(int sever, bool debug = false) { - bool rc = false; + if (debug) + return (XOM_GOOD_MAJOR_ALLY); + const monster_type mon = _xom_random_demon(sever); const bool is_demonic = (mons_class_holiness(mon) == MH_DEMONIC); @@ -1581,16 +1879,16 @@ static bool _xom_send_major_ally(int sever) menv[summons].name(DESC_PLAIN).c_str()); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); - rc = true; + return (XOM_GOOD_MAJOR_ALLY); } - return (rc); + return (XOM_DID_NOTHING); } -static bool _xom_throw_divine_lightning() +static int _xom_throw_divine_lightning(bool debug = false) { if (!player_in_a_dangerous_place()) - return (false); + return (XOM_DID_NOTHING); // Make sure there's at least one enemy within the lightning radius. bool found_hostile = false; @@ -1608,7 +1906,10 @@ static bool _xom_throw_divine_lightning() // No hostiles within radius. if (!found_hostile) - return (false); + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_GOOD_LIGHTNING); bool protection = false; if (you.hp <= random2(201)) @@ -1655,17 +1956,188 @@ static bool _xom_throw_divine_lightning() "divine lightning%s", protection ? " (protected)" : ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, lightning_buf), true); - return (true); + return (XOM_GOOD_LIGHTNING); +} + +static int _xom_change_scenery(bool debug = false) +{ + std::vector candidates; + for (radius_iterator ri(you.pos(), LOS_RADIUS, false, true); ri; ++ri) + { + if (!in_bounds(*ri) || !see_grid(*ri)) + continue; + + dungeon_feature_type feat = grd(*ri); + if (feat >= DNGN_FOUNTAIN_BLUE && feat <= DNGN_DRY_FOUNTAIN_BLOOD) + candidates.push_back(*ri); + else if (feat >= DNGN_CLOSED_DOOR && feat <= DNGN_SECRET_DOOR + || feat == DNGN_OPEN_DOOR && you.pos() != *ri) + { + candidates.push_back(*ri); + } + } + + const std::string speech = _get_xom_speech("scenery"); + if (candidates.empty()) + { + if (!one_chance_in(8)) + return (XOM_DID_NOTHING); + + // Place one or more altars to Xom. + coord_def place; + bool success = false; + const int max_altars = std::max(1, random2(random2(14))); + for (int tries = max_altars; tries > 0; --tries) + { + if ((random_near_space(you.pos(), place) + || random_near_space(you.pos(), place, true)) + && grd(place) == DNGN_FLOOR && see_grid(place)) + { + if (debug) + return (XOM_GOOD_SCENERY); + + grd(place) = DNGN_ALTAR_XOM; + success = true; + } + } + + if (success) + { + god_speaks(GOD_XOM, speech.c_str()); + return (XOM_GOOD_SCENERY); + } + return (XOM_DID_NOTHING); + } + + if (debug) + return (XOM_GOOD_SCENERY); + + const int fountain_diff = (DNGN_DRY_FOUNTAIN_BLUE - DNGN_FOUNTAIN_BLUE); + + std::random_shuffle(candidates.begin(), candidates.end()); + int doors_open = 0; + int doors_close = 0; + int fountains_flow = 0; + int fountains_blood = 0; + for (unsigned int i = 0; i < candidates.size(); ++i) + { + coord_def pos = candidates[i]; + switch (grd(pos)) + { + case DNGN_CLOSED_DOOR: + case DNGN_DETECTED_SECRET_DOOR: + case DNGN_SECRET_DOOR: + grd(pos) = DNGN_OPEN_DOOR; + doors_open++; + break; + case DNGN_OPEN_DOOR: + grd(pos) = DNGN_CLOSED_DOOR; + doors_close++; + break; + case DNGN_DRY_FOUNTAIN_BLUE: + case DNGN_DRY_FOUNTAIN_SPARKLING: + case DNGN_DRY_FOUNTAIN_BLOOD: + { + if (x_chance_in_y(fountains_flow, 5)) + continue; + + grd(pos) = (dungeon_feature_type) (grd(pos) - fountain_diff); + fountains_flow++; + break; + } + case DNGN_FOUNTAIN_BLUE: + if (x_chance_in_y(fountains_blood, 3)) + continue; + + grd(pos) = DNGN_FOUNTAIN_BLOOD; + fountains_blood++; + break; + default: + break; + } + } + if (!doors_open && !doors_close && !fountains_flow && !fountains_blood) + return (XOM_DID_NOTHING); + + god_speaks(GOD_XOM, speech.c_str()); + + std::vector effects; + if (doors_open > 0) + { + snprintf(info, INFO_SIZE, "%s door%s burst%s open", + doors_open == 1 ? "A" : + doors_open == 2 ? "Two" + : "Several", + doors_open == 1 ? "" : "s", + doors_open == 1 ? "s" : ""); + effects.push_back(info); + } + if (doors_close > 0) + { + snprintf(info, INFO_SIZE, "%s%s door%s slam%s shut", + doors_close == 1 ? "a" : + doors_close == 2 ? "two" + : "several", + doors_open > 0 ? (doors_close == 1 ? "nother" : " other") + : "", + doors_close == 1 ? "" : "s", + doors_close == 1 ? "s" : ""); + std::string closed = info; + if (effects.empty()) + closed = uppercase_first(closed); + effects.push_back(closed); + } + if (!effects.empty()) + { + mprf("%s!", + comma_separated_line(effects.begin(), effects.end(), + ", and ").c_str()); + effects.clear(); + } + + if (fountains_flow > 0) + { + snprintf(info, INFO_SIZE, + "%s fountain%s start%s reflowing", + fountains_flow == 1 ? "A" : "Some", + fountains_flow == 1 ? "" : "s", + fountains_flow == 1 ? "s" : ""); + effects.push_back(info); + } + if (fountains_blood > 0) + { + snprintf(info, INFO_SIZE, + "%s%s fountain%s start%s gushing blood", + fountains_blood == 1 ? "a" : "some", + fountains_flow > 0 ? (fountains_blood == 1 ? "nother" + : " other") + : "", + fountains_blood == 1 ? "" : "s", + fountains_blood == 1 ? "s" : ""); + + std::string fountains = info; + if (effects.empty()) + fountains = uppercase_first(fountains); + effects.push_back(fountains); + } + if (!effects.empty()) + { + mprf("%s!", + comma_separated_line(effects.begin(), effects.end(), + ", and ").c_str()); + } + + return (XOM_GOOD_SCENERY); } // The nicer stuff. Note: these things are not necessarily nice. -static bool _xom_is_good(int sever, int tension) +static int _xom_is_good(int sever, int tension, bool debug = false) { - bool done = false; + int done = XOM_DID_NOTHING; // Did Xom (already) kill the player? if (_player_is_dead()) - return (true); + return (XOM_PLAYER_DEAD); god_acting gdact(GOD_XOM); @@ -1674,48 +2146,49 @@ static bool _xom_is_good(int sever, int tension) // 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(); + done = _xom_do_potion(debug); 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); + done = _xom_makes_you_cast_random_spell(sever, tension, debug); } - else if (x_chance_in_y(4, sever)) - done = _xom_confuse_monsters(sever); + else if (tension > 0 && x_chance_in_y(4, sever)) + done = _xom_confuse_monsters(sever, debug); // 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; - } + done = _xom_send_one_ally(sever, debug); + else if (tension < random2(5) && x_chance_in_y(6, sever)) + done = _xom_change_scenery(debug); + else if (x_chance_in_y(7, sever)) + done = _xom_give_item(sever, debug); // 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) + else if (tension > random2(10) && x_chance_in_y(8, sever)) + done = _xom_send_allies(sever, debug); + else if (tension > random2(8) && x_chance_in_y(9, sever)) + done = _xom_animate_monster_weapon(sever, debug); + else if (x_chance_in_y(10, sever)) + done = _xom_polymorph_nearby_monster(true, debug); + else if (tension > 0 && x_chance_in_y(11, sever)) + done = _xom_rearrange_pieces(sever, debug); + else if (random2(tension) < 15 && x_chance_in_y(12, sever)) + done = _xom_give_item(sever, debug); + else if (x_chance_in_y(13, sever) && you.level_type != LEVEL_ABYSS) { // Try something else if teleportation is impossible. if (scan_artefacts(ARTP_PREVENT_TELEPORTATION)) - return (false); + return (XOM_DID_NOTHING); // 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); + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_GOOD_TELEPORT); // The Xom teleportation train takes you on instant teleportation // to a few random areas, stopping randomly but most likely in @@ -1742,29 +2215,32 @@ static bool _xom_is_good(int sever, int tension) #endif ""); take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, tele_buf), true); - done = true; + done = XOM_GOOD_TELEPORT; } - else if (random2(tension) < 5 && x_chance_in_y(12, sever)) + else if (random2(tension) < 5 && x_chance_in_y(14, sever)) { + if (debug) + return (XOM_GOOD_VITRIFY); + // This can fail with radius 1, or in open areas. - if (vitrify_area(random2avg(sever/4,2) + 1)) + 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; + done = XOM_GOOD_VITRIFY; } } - else if (random2(tension) < 5 && x_chance_in_y(13, sever) + else if (random2(tension) < 5 && x_chance_in_y(15, sever) && x_chance_in_y(16, how_mutated())) { - done = _xom_give_mutations(true); + done = _xom_give_mutations(true, debug); } // 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(); + else if (tension > random2(15) && x_chance_in_y(16, sever)) + done = _xom_send_major_ally(sever, debug); + else if (tension > 0 && x_chance_in_y(17, sever)) + done = _xom_throw_divine_lightning(debug); return (done); } @@ -1860,8 +2336,10 @@ static void _xom_zero_miscast() 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]) { @@ -2150,7 +2628,8 @@ static void _get_hand_type(std::string &hand, bool &can_plural) can_plural = plural_vec[choice]; } -static void _xom_miscast(const int max_level, const bool nasty) +static int _xom_miscast(const int max_level, const bool nasty, + bool debug = false) { ASSERT(max_level >= 0 && max_level <= 3); @@ -2171,7 +2650,19 @@ static void _xom_miscast(const int max_level, const bool nasty) const char* speech_str = speeches[max_level]; const char* cause_str = causes[max_level]; - const int level = random2(max_level + 1); + const int level = (nasty ? 1 + random2(max_level) + : random2(max_level + 1)); + + if (debug) + { + switch (level) + { + case 0: return (XOM_BAD_MISCAST_PSEUDO); + case 1: return (XOM_BAD_MISCAST_MINOR); + case 2: return (XOM_BAD_MISCAST_MAJOR); + case 3: return (XOM_BAD_MISCAST_NASTY); + } + } // Take a note. std::string desc = "miscast effect"; @@ -2187,7 +2678,7 @@ static void _xom_miscast(const int max_level, const bool nasty) { god_speaks(GOD_XOM, _get_xom_speech(speech_str).c_str()); _xom_zero_miscast(); - return; + return (XOM_BAD_MISCAST_PSEUDO); } std::string hand_str; @@ -2203,10 +2694,16 @@ static void _xom_miscast(const int max_level, const bool nasty) MiscastEffect(&you, -GOD_XOM, SPTYP_RANDOM, level, cause_str, NH_DEFAULT, lethality_margin, hand_str, can_plural); + + // Not worth distinguishing unless debugging. + return (XOM_BAD_MISCAST_MAJOR); } -static bool _xom_lose_stats() +static int _xom_lose_stats(bool debug = false) { + if (debug) + return (XOM_BAD_STATLOSS); + stat_type stat = STAT_RANDOM; int max = 3; @@ -2232,7 +2729,7 @@ static bool _xom_lose_stats() while (max < 2 && (++tries < 30)); if (tries >= 30) - return (false); + return (XOM_DID_NOTHING); } god_speaks(GOD_XOM, _get_xom_speech("lose stats").c_str()); @@ -2251,22 +2748,24 @@ static bool _xom_lose_stats() take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, stat_buf), true); - return (true); + return (XOM_BAD_STATLOSS); } -static bool _xom_chaos_upgrade_nearby_monster() +static int _xom_chaos_upgrade_nearby_monster(bool debug = false) { - bool rc = false; - monsters *mon = choose_random_nearby_monster(0, _choose_chaos_upgrade); if (!mon) - return (false); + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_BAD_CHAOS_UPGRADE); god_speaks(GOD_XOM, _get_xom_speech("chaos upgrade").c_str()); mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE}; + bool rc = false; for (int i = 0; i < 3 && !rc; ++i) { item_def* const item = mon->mslot_item(slots[i]); @@ -2282,21 +2781,27 @@ static bool _xom_chaos_upgrade_nearby_monster() behaviour_event(mon, ME_ALERT, MHITYOU); if (rc) + { take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "chaos upgrade"), true); + return (XOM_BAD_CHAOS_UPGRADE); + } - return (rc); + return (XOM_DID_NOTHING); } -static bool _xom_player_confusion_effect(int sever) +static int _xom_player_confusion_effect(int sever, bool debug = false) { 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); + return (XOM_DID_NOTHING); } + if (debug) + return (XOM_BAD_CONFUSION); + bool rc = false; // Looks like this will *always* succeed? @@ -2340,7 +2845,7 @@ static bool _xom_player_confusion_effect(int sever) take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, conf_msg.c_str()), true); } - return (rc); + return (rc ? XOM_BAD_CONFUSION : XOM_DID_NOTHING); } static bool _valid_floor_grid(coord_def pos) @@ -2507,13 +3012,13 @@ static bool _move_stair(coord_def stair_pos, bool away) return (true); } -static bool _repel_stairs() +static int _xom_repel_stairs(bool debug = false) { // 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); + return (XOM_DID_NOTHING); } std::vector stairs_avail; @@ -2532,7 +3037,10 @@ static bool _repel_stairs() // Should only happen if there are stairs in view. if (stairs_avail.empty()) - return (false); + return (XOM_DID_NOTHING); + + if (debug) + return (XOM_BAD_STAIRS); // Don't mention staircases if there aren't any nearby. std::string stair_msg = _get_xom_speech("repel stairs"); @@ -2578,20 +3086,22 @@ static bool _repel_stairs() else take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "repel stairs"), true); - return (true); + return (XOM_BAD_STAIRS); } -static bool _xom_draining_torment_effect(int sever) +static int _xom_draining_torment_effect(int sever, bool debug = false) { const std::string speech = _get_xom_speech("draining or torment"); const bool nasty = _xom_feels_nasty(); - bool rc = false; + int rc = XOM_DID_NOTHING; if (one_chance_in(4)) { // XP drain effect (25%). if (player_prot_life() < 3 && (nasty || you.experience > 0)) { + if (debug) + return (XOM_BAD_DRAINING); god_speaks(GOD_XOM, speech.c_str()); drain_exp(); @@ -2601,7 +3111,7 @@ static bool _xom_draining_torment_effect(int sever) drain_exp(); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "draining"), true); - rc = true; + return (XOM_BAD_DRAINING); } } else @@ -2609,6 +3119,9 @@ static bool _xom_draining_torment_effect(int sever) // Torment effect (75%). if (!player_res_torment()) { + if (debug) + return (XOM_BAD_TORMENT); + god_speaks(GOD_XOM, speech.c_str()); torment_player(0, TORMENT_XOM); @@ -2618,20 +3131,24 @@ static bool _xom_draining_torment_effect(int sever) "torment (%d/%d hp)", you.hp, you.hp_max); take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, torment_buf), true); - rc = true; + return (XOM_BAD_TORMENT); } } return (rc); } -static bool _xom_summon_hostiles(int sever) +static int _xom_summon_hostiles(int sever, bool debug = false) { bool rc = false; const std::string speech = _get_xom_speech("hostile monster"); + int result = XOM_DID_NOTHING; // Nasty, but fun. if (player_weapon_wielded() && one_chance_in(4)) { + if (debug) + return (XOM_BAD_ANIMATE_WPN); + const item_def& weapon = *you.weapon(); const std::string wep_name = weapon.name(DESC_PLAIN); rc = cast_tukimas_dance(100, GOD_XOM, true); @@ -2642,16 +3159,20 @@ static bool _xom_summon_hostiles(int sever) 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); + result = XOM_BAD_ANIMATE_WPN; } } else { + if (debug) + return (XOM_BAD_SUMMON_DEMONS); + // The number of demons is dependent on severity, though heavily // randomised. int numdemons = sever; - for (int i = 0; i < 3; i++) - numdemons = random2(numdemons+1); - numdemons = std::min(numdemons+1,14); + 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) @@ -2675,18 +3196,19 @@ static bool _xom_summon_hostiles(int sever) take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true); rc = true; + result = XOM_BAD_SUMMON_DEMONS; } } if (rc) god_speaks(GOD_XOM, speech.c_str()); - return (rc); + return (result); } -static bool _xom_is_bad(int sever, int tension) +static int _xom_is_bad(int sever, int tension, bool debug = false) { - bool done = false; + int done = XOM_DID_NOTHING; bool nasty = (sever >= 5 && _xom_feels_nasty()); god_acting gdact(GOD_XOM); @@ -2694,51 +3216,60 @@ static bool _xom_is_bad(int sever, int tension) // Rough estimate of how bad a Xom effect hits the player, // scaled between 1 (harmless) and 5 (disastrous). int badness = 1; - while (!done) + while (done == XOM_DID_NOTHING) { // Did Xom kill the player? if (_player_is_dead()) - return (true); + return (XOM_PLAYER_DEAD); if (!nasty && x_chance_in_y(3, sever)) - { - _xom_miscast(0, nasty); - done = true; - } + done = _xom_miscast(0, nasty, debug); else if (!nasty && x_chance_in_y(4, sever)) + done = _xom_miscast(1, nasty, debug); + // It's pointless to confuse player if there's no danger nearby. + else if (tension > 0 && x_chance_in_y(5, sever)) { - _xom_miscast(1, nasty); - done = true; + done = _xom_player_confusion_effect(sever, debug); + badness = (random2(tension) > 5 ? 2 : 1); } - else if (x_chance_in_y(5, sever)) + else if (x_chance_in_y(6, sever)) { - done = _xom_lose_stats(); + done = _xom_lose_stats(debug); badness = 2; } - else if (x_chance_in_y(6, sever)) + else if (tension > 0 && x_chance_in_y(7, sever)) + done = _xom_swap_weapons(debug); + else if (x_chance_in_y(8, sever)) { - _xom_miscast(2, nasty); + done = _xom_miscast(2, nasty, debug); badness = 2; - done = true; } - else if (x_chance_in_y(7, sever) && you.level_type != LEVEL_ABYSS) + else if (x_chance_in_y(9, sever)) + { + done = _xom_chaos_upgrade_nearby_monster(debug); + badness = 2 + coinflip(); + } + else if (x_chance_in_y(10, sever) && you.level_type != LEVEL_ABYSS) { // Try something else if teleportation is impossible. if (scan_artefacts(ARTP_PREVENT_TELEPORTATION)) - return (false); + return (XOM_DID_NOTHING); // 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)) + if (nasty && (explored >= 40 || tension > 10) + || explored >= 60 + random2(40)) { - done = false; + done = XOM_DID_NOTHING; continue; } + if (debug) + return (XOM_BAD_TELEPORT); + // 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. @@ -2768,63 +3299,54 @@ static bool _xom_is_bad(int sever, int tension) #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; + done = XOM_BAD_TELEPORT; } - else if (x_chance_in_y(10, sever)) + else if (x_chance_in_y(11, sever)) { - done = _xom_polymorph_nearby_monster(false); + done = _xom_polymorph_nearby_monster(false, debug); badness = 3; } - else if (tension > 0 && x_chance_in_y(11, sever)) + else if (tension > 0 && x_chance_in_y(12, sever)) { - done = _repel_stairs(); + done = _xom_repel_stairs(debug); 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)) + else if (random2(tension) < 11 && x_chance_in_y(13, sever)) { - done = _xom_player_confusion_effect(sever); - badness = (random2(tension) > 5 ? 2 : 1); + done = _xom_give_mutations(false, debug); + badness = 3; } - else if (x_chance_in_y(13, sever)) + else if (x_chance_in_y(14, sever)) { - done = _xom_draining_torment_effect(sever); + done = _xom_draining_torment_effect(sever, debug); badness = (random2(tension) > 5 ? 3 : 2); } - else if (x_chance_in_y(14, sever)) + else if (x_chance_in_y(15, sever)) { - done = _xom_summon_hostiles(sever); + done = _xom_summon_hostiles(sever, debug); badness = 3 + coinflip(); } - else if (x_chance_in_y(15, sever)) + else if (x_chance_in_y(16, sever)) { - _xom_miscast(3, nasty); + done = _xom_miscast(3, nasty, debug); 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 (!debug) + { + god_speaks(GOD_XOM, _get_xom_speech("banishment").c_str()); + // handles note taking + banished(DNGN_ENTER_ABYSS, "Xom"); + badness = 5; + } + done = XOM_BAD_BANISHMENT; } } // If we got here because Xom was bored, reset gift timeout according // to the badness of the effect. - if (done && _xom_is_bored()) + if (done && !debug && _xom_is_bored()) { const int interest = random2avg(badness*60, 2); you.gift_timeout = std::min(interest, 255); @@ -2963,7 +3485,7 @@ static void _handle_accidental_death(const int orig_hp, you.teleport(true); } -void xom_acts(bool niceness, int sever, int tension) +int xom_acts(bool niceness, int sever, int tension, bool debug) { #if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM) mprf(MSGCH_DIAGNOSTICS, "xom_acts(%u, %d, %d); piety: %u, interest: %u\n", @@ -2987,7 +3509,7 @@ void xom_acts(bool niceness, int sever, int tension) mpr("Player is already dead, skipping Xom act.", MSGCH_DIAGNOSTICS); } - return; + return (XOM_PLAYER_DEAD); } #else ASSERT(!_player_is_dead()); @@ -3039,15 +3561,19 @@ void xom_acts(bool niceness, int sever, int tension) #endif const bool was_bored = _xom_is_bored(); + int result = XOM_DID_NOTHING; if (niceness && !one_chance_in(20)) { // Good stuff. - while (!_xom_is_good(sever, tension)) - ; + while (result == XOM_DID_NOTHING) + result = _xom_is_good(sever, tension, debug); + + if (debug) + return (result); } else { - if (was_bored && Options.note_xom_effects) + if (!debug && was_bored && Options.note_xom_effects) take_note(Note(NOTE_MESSAGE, 0, 0, "XOM is BORED!"), true); #ifdef NOTE_DEBUG_XOM else if (niceness) @@ -3058,8 +3584,11 @@ void xom_acts(bool niceness, int sever, int tension) #endif // Bad mojo. - while (!_xom_is_bad(sever, tension)) - ; + while (result == XOM_DID_NOTHING) + result = _xom_is_bad(sever, tension, debug); + + if (debug) + return (result); } _handle_accidental_death(orig_hp, orig_stats, orig_mutation); @@ -3098,6 +3627,9 @@ void xom_acts(bool niceness, int sever, int tension) const std::string msg = "You are now " + new_xom_favour; god_speaks(you.religion, msg.c_str()); } + + // Not true, but also not important now. + return (result); } static void _xom_check_less_runes(int runes_gone) @@ -3234,3 +3766,200 @@ void xom_death_message(const kill_method_type killed_by) // All others just get ignored by Xom. } + +#ifdef WIZARD +struct xom_effect_count +{ + std::string effect; + int count; + + xom_effect_count(std::string e, int c) : effect(e), count(c) {}; +}; + +static bool _sort_xom_effects(const xom_effect_count &a, + const xom_effect_count &b) +{ + if (a.count == b.count) + return (a.effect < b.effect); + + return (a.count > b.count); +} + +static const char* _xom_effect_to_name(int effect) +{ + ASSERT(effect < XOM_PLAYER_DEAD); + + // See xom.h + static const char* _xom_effect_names[] = + { + "nothing", + // good acts + "potion", "spell (tension)", "spell (no tension)", "mapping", + "confuse monsters", "single ally", "animate monster weapon", + "annoyance gift", "random item gift", "acquirement", "summon allies", + "polymorph", "swap monsters", "teleportation", "vitrification", + "mutation", "permanent ally", "lightning", "change scenery", + // bad acts + "miscast (pseudo)", "miscast (minor)", "miscast (major)", + "miscast (nasty)", "stat loss", "teleportation", "swap weapons", + "chaos upgrade", "mutation", "polymorph", "repel stairs", "confusion", + "draining", "torment", "animate weapon", "summon demons", "banishment" + }; + + std::string result = ""; + if (effect > XOM_DID_NOTHING && effect < XOM_PLAYER_DEAD) + { + if (effect <= XOM_LAST_GOOD_ACT) + result = "GOOD: "; + else + result = "BAD: "; + } + result += _xom_effect_names[effect]; + + return (result.c_str()); +} + +// Loops over the entire piety spectrum and calls xom_acts() multiple +// times for each value, then prints the results into a file. +// TODO: Allow specification of niceness, tension, boredness, and repeats. +void debug_xom_effects() +{ + // Repeat N times. + const int N = 10; + + FILE *ostat = fopen("xom_debug.stat", "a"); + if (!ostat) + { + mprf(MSGCH_ERROR, "Can't write 'xom_debug.stat'. Aborting."); + return; + } + + const int real_piety = you.piety; + const god_type real_god = you.religion; + you.religion = GOD_XOM; + const int tension = get_tension(GOD_XOM); + + fprintf(ostat, "---- STARTING XOM DEBUG TESTING ----\n"); + fprintf(ostat, "%s\n", dump_overview_screen(false).c_str()); + fprintf(ostat, screenshot().c_str()); + fprintf(ostat, "\n%s\n", mpr_monster_list().c_str()); + fprintf(ostat, " --> Tension: %d\n", tension); + + if (you.penance[GOD_XOM]) + fprintf(ostat, "You are under Xom's penance!\n"); + else if (_xom_is_bored()) + fprintf(ostat, "Xom is BORED.\n"); + + std::vector mood_effects; + std::vector > all_effects; + std::vector moods; + std::vector mood_good_acts; + + std::string old_mood = ""; + std::string mood = ""; + + // Add an empty list to later add all effects to. + all_effects.push_back(mood_effects); + moods.push_back("total"); + mood_good_acts.push_back(0); // count total good acts + + int mood_good = 0; + for (int p = 0; p <= MAX_PIETY; ++p) + { + you.piety = p; + int sever = abs(p - HALF_MAX_PIETY); + mood = describe_xom_mood(); + if (old_mood != mood) + { + if (old_mood != "") + { + all_effects.push_back(mood_effects); + mood_effects.clear(); + mood_good_acts.push_back(mood_good); + mood_good_acts[0] += mood_good; + mood_good = 0; + } + moods.push_back(mood); + old_mood = mood; + } + + // Repeat N times. + for (int i = 0; i < N; ++i) + { + bool niceness = xom_is_nice(tension); + int result = xom_acts(niceness, sever, tension, true); + mood_effects.push_back(result); + all_effects[0].push_back(result); + + if (result <= XOM_LAST_GOOD_ACT) + mood_good++; + } + } + all_effects.push_back(mood_effects); + mood_effects.clear(); + mood_good_acts.push_back(mood_good); + mood_good_acts[0] += mood_good; + + const int num_moods = moods.size(); + std::vector xom_ec_pairs; + for (int i = 0; i < num_moods; ++i) + { + mood_effects = all_effects[i]; + const int total = mood_effects.size(); + + if (i == 0) + fprintf(ostat, "\nTotal effects (all piety ranges)\n"); + else + fprintf(ostat, "\nMood: You are %s\n", moods[i].c_str()); + + fprintf(ostat, "GOOD%7.2f%%\n", + (100.0 * (float) mood_good_acts[i] / (float) total)); + fprintf(ostat, "BAD %7.2f%%\n", + (100.0 * (float) (total - mood_good_acts[i]) / (float) total)); + + std::sort(mood_effects.begin(), mood_effects.end()); + + xom_ec_pairs.clear(); + int old_effect = XOM_DID_NOTHING; + int count = 0; + for (int k = 0; k < total; ++k) + { + if (mood_effects[k] != old_effect) + { + if (count > 0) + { + std::string name = _xom_effect_to_name(old_effect); + xom_effect_count xec = xom_effect_count(name, count); + xom_ec_pairs.push_back(xec); + } + old_effect = mood_effects[k]; + count = 1; + } + else + count++; + } + + if (count > 0) + { + std::string name = _xom_effect_to_name(old_effect); + xom_effect_count xec = xom_effect_count(name, count); + xom_ec_pairs.push_back(xec); + } + + std::sort(xom_ec_pairs.begin(), xom_ec_pairs.end(), _sort_xom_effects); + for (unsigned int k = 0; k < xom_ec_pairs.size(); ++k) + { + xom_effect_count xec = xom_ec_pairs[k]; + fprintf(ostat, "%7.2f%% %s\n", + (100.0 * (float) xec.count / (float) total), + xec.effect.c_str()); + } + } + fprintf(ostat, "---- FINISHED XOM DEBUG TESTING ----\n"); + fclose(ostat); + mpr("Results written into 'xom_debug.stat'."); + + you.piety = real_piety; + you.religion = real_god; +} +#endif diff --git a/crawl-ref/source/xom.h b/crawl-ref/source/xom.h index ab0e13d66a..047f5196b5 100644 --- a/crawl-ref/source/xom.h +++ b/crawl-ref/source/xom.h @@ -18,6 +18,56 @@ enum xom_message_type NUM_XOM_MESSAGE_TYPES }; +enum xom_event_type +{ + XOM_DID_NOTHING = 0, + + // good acts + XOM_GOOD_POTION, + XOM_GOOD_SPELL_TENSION, + XOM_GOOD_SPELL_CALM, + XOM_GOOD_MAPPING, + XOM_GOOD_CONFUSION, // 5 + XOM_GOOD_SINGLE_ALLY, + XOM_GOOD_ANIMATE_MON_WPN, + XOM_GOOD_ANNOYANCE_GIFT, + XOM_GOOD_RANDOM_ITEM, + XOM_GOOD_ACQUIREMENT, // 10 + XOM_GOOD_ALLIES, + XOM_GOOD_POLYMORPH, + XOM_GOOD_SWAP_MONSTERS, + XOM_GOOD_TELEPORT, + XOM_GOOD_VITRIFY, // 15 + XOM_GOOD_MUTATION, + XOM_GOOD_MAJOR_ALLY, + XOM_GOOD_LIGHTNING, + XOM_GOOD_SCENERY, + XOM_LAST_GOOD_ACT = XOM_GOOD_SCENERY, // 19 + + // bad acts + XOM_BAD_MISCAST_PSEUDO, // 20 + XOM_BAD_MISCAST_MINOR, + XOM_BAD_MISCAST_MAJOR, + XOM_BAD_MISCAST_NASTY, + XOM_BAD_STATLOSS, + XOM_BAD_TELEPORT, // 25 + XOM_BAD_SWAP_WEAPONS, + XOM_BAD_CHAOS_UPGRADE, + XOM_BAD_MUTATION, + XOM_BAD_POLYMORPH, + XOM_BAD_STAIRS, // 30 + XOM_BAD_CONFUSION, + XOM_BAD_DRAINING, + XOM_BAD_TORMENT, + XOM_BAD_ANIMATE_WPN, + XOM_BAD_SUMMON_DEMONS, // 35 + XOM_BAD_BANISHMENT, + XOM_LAST_BAD_ACT = XOM_BAD_BANISHMENT, // 36 + + XOM_PLAYER_DEAD = 100, // player already dead (shouldn't happen) + NUM_XOM_EVENTS +}; + void xom_tick(); void xom_is_stimulated(int maxinterestingness, xom_message_type message_type = XM_NORMAL, @@ -25,7 +75,7 @@ void xom_is_stimulated(int maxinterestingness, void xom_is_stimulated(int maxinterestingness, const std::string& message, bool force_message = false); bool xom_is_nice(int tension = -1); -void xom_acts(bool niceness, int sever, int tension = -1); +int xom_acts(bool niceness, int sever, int tension = -1, bool debug = false); const char *describe_xom_favour(bool upper = false); inline void xom_acts(int sever, int tension = -1) @@ -36,4 +86,8 @@ inline void xom_acts(int sever, int tension = -1) void xom_check_lost_item(const item_def& item); void xom_check_destroyed_item(const item_def& item, int cause = -1); void xom_death_message(const kill_method_type killed_by); +#ifdef WIZARD +void debug_xom_effects(); +#endif + #endif -- cgit v1.2.3-54-g00ecf