From 9fecb788095cc5315ef5f1485e0b736563a21460 Mon Sep 17 00:00:00 2001 From: j-p-e-g Date: Mon, 13 Apr 2009 20:40:39 +0000 Subject: * Add stair repelling as a bad Xom effect. * Experimentally make tension increase the odds for good effects. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@9600 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/docs/changes.stone_soup | 4 +- crawl-ref/source/chardump.cc | 10 + crawl-ref/source/dat/database/godspeak.txt | 10 + crawl-ref/source/decks.cc | 4 +- crawl-ref/source/delay.cc | 2 +- crawl-ref/source/effects.cc | 10 +- crawl-ref/source/fight.cc | 5 +- crawl-ref/source/files.cc | 24 ++- crawl-ref/source/item_use.cc | 88 ++++++--- crawl-ref/source/itemprop.cc | 27 +-- crawl-ref/source/itemprop.h | 3 +- crawl-ref/source/misc.cc | 5 + crawl-ref/source/monstuff.cc | 11 +- crawl-ref/source/monstuff.h | 2 +- crawl-ref/source/output.cc | 12 +- crawl-ref/source/player.cc | 8 + crawl-ref/source/stuff.cc | 2 + crawl-ref/source/terrain.cc | 8 +- crawl-ref/source/xom.cc | 291 +++++++++++++++++++++++++++-- crawl-ref/source/xom.h | 4 +- 20 files changed, 436 insertions(+), 94 deletions(-) diff --git a/crawl-ref/docs/changes.stone_soup b/crawl-ref/docs/changes.stone_soup index 746ecc73e9..e30834b0f6 100644 --- a/crawl-ref/docs/changes.stone_soup +++ b/crawl-ref/docs/changes.stone_soup @@ -115,7 +115,7 @@ Items * Missile enchantment decreases chance of destruction on impact. * Freezing weapons can now slow cold-blooded monsters. * The staff of channeling now trains Evocations. -* Potions of magic no longer increase max. mana. +* Potions of magic no longer increase max. magic points. * Item descriptions now mention enchantment/charging limits. * Randarts now get their known properties autoinscribed. * Randarts are now noted as identified even if the plusses are still unknown. @@ -147,7 +147,7 @@ Gods * Beogh accepts more kinds of kills. * Beogh has a non-cannibalism conduct. * Disallow shapeshifters in orc form to become Beoghites' followers. -* Can see card descriptions when using Nemelex' Triple Draw or Stack Five. +* Offer card descriptions when using Nemelex' Triple Draw or Stack Five. Tiles ----- diff --git a/crawl-ref/source/chardump.cc b/crawl-ref/source/chardump.cc index 4ed7d4b4f3..7d246a7561 100644 --- a/crawl-ref/source/chardump.cc +++ b/crawl-ref/source/chardump.cc @@ -56,6 +56,7 @@ REVISION("$Rev$"); #include "transfor.h" #include "version.h" #include "view.h" +#include "xom.h" struct dump_params; @@ -650,6 +651,15 @@ static void _sdump_religion(dump_params &par) text += " " + verb + " demanding penance.\n"; } } + else + { + if (par.se) + text += "You were "; + else + text += "You are "; + text += describe_xom_favour(false); + text += "\n"; + } } } diff --git a/crawl-ref/source/dat/database/godspeak.txt b/crawl-ref/source/dat/database/godspeak.txt index 86c95b00a7..573990921c 100644 --- a/crawl-ref/source/dat/database/godspeak.txt +++ b/crawl-ref/source/dat/database/godspeak.txt @@ -378,6 +378,16 @@ Xom brings you back to life. Xom smiles on you. %%%% +Xom repel stairs + +Xom alters the dungeon around you. + +Xom changes the scenery. + +Xom pokes at a staircase. + +"Tag, you're it!" +%%%% # Xom laughing # (Currently only used post-game in response to "You die...") Xom laughter diff --git a/crawl-ref/source/decks.cc b/crawl-ref/source/decks.cc index 048c2b3d56..76dec12063 100644 --- a/crawl-ref/source/decks.cc +++ b/crawl-ref/source/decks.cc @@ -1685,9 +1685,9 @@ static void _stairs_card(int power, deck_rarity_type rarity) you.duration[DUR_REPEL_STAIRS_CLIMB] = 0; if (grid_stair_direction(grd(you.pos())) == CMD_NO_CMD) - you.duration[DUR_REPEL_STAIRS_MOVE] = 1000; + you.duration[DUR_REPEL_STAIRS_MOVE] = 1000; else - you.duration[DUR_REPEL_STAIRS_CLIMB] = 1000; + you.duration[DUR_REPEL_STAIRS_CLIMB] = 500; // more annoying std::vector stairs_avail; diff --git a/crawl-ref/source/delay.cc b/crawl-ref/source/delay.cc index 68b662178b..d8a1c7ff85 100644 --- a/crawl-ref/source/delay.cc +++ b/crawl-ref/source/delay.cc @@ -1190,7 +1190,7 @@ static void _finish_delay(const delay_queue_item &delay) if (m) { // One square, a few squares, anywhere... - if (!shift_monster(m) && !monster_blink(m)) + if (!shift_monster(m) && !monster_blink(m, true)) monster_teleport(m, true, true); } diff --git a/crawl-ref/source/effects.cc b/crawl-ref/source/effects.cc index 509fc8c2c8..2bccb6103a 100644 --- a/crawl-ref/source/effects.cc +++ b/crawl-ref/source/effects.cc @@ -3201,15 +3201,15 @@ void handle_time(long time_delta) mutagenic_randart = true; } - // we take off about .5 points per turn + // We take off about .5 points per turn. if (!you.duration[DUR_INVIS] && !you.duration[DUR_HASTE] && coinflip()) added_contamination--; - // only punish if contamination caused by mutagenic randarts - // (haste and invisibility already penalized earlier) + // Only punish if contamination caused by mutagenic randarts. + // (Haste and invisibility already penalized earlier.) contaminate_player( added_contamination, mutagenic_randart ); - // only check for badness once every other turn + // Only check for badness once every other turn. if (coinflip()) { // [ds] Be less harsh with glow mutation; Brent and Mark Mackey note @@ -3255,7 +3255,7 @@ void handle_time(long time_delta) beam.explode(); } - // we want to warp the player, not do good stuff! + // We want to warp the player, not do good stuff! if (one_chance_in(5)) mutate(RANDOM_MUTATION); else diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index f673ba2435..0bbd1a3158 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -66,6 +66,7 @@ REVISION("$Rev$"); #define NOTE_DEBUG_CHAOS_EFFECTS #endif +#define NOTE_DEBUG_CHAOS_EFFECTS #ifdef NOTE_DEBUG_CHAOS_EFFECTS #include "notes.h" #endif @@ -2934,8 +2935,8 @@ int melee_attack::random_chaos_brand() } break; case SPWPN_CONFUSE: - if (defender->holiness() != MH_NONLIVING - && defender->holiness() != MH_PLANT) + if (defender->holiness() == MH_NONLIVING + || defender->holiness() == MH_PLANT) { susceptible = false; } diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc index 107a86b710..d6f163c63d 100644 --- a/crawl-ref/source/files.cc +++ b/crawl-ref/source/files.cc @@ -1371,24 +1371,30 @@ bool load( dungeon_feature_type stair_taken, load_mode_type load_mode, dungeon_feature_type feat = grd(you.pos()); if (feat != DNGN_ENTER_SHOP && grid_stair_direction(feat) != CMD_NO_CMD - && grid_stair_direction(stair_taken) != CMD_NO_CMD - && coinflip() - && slide_feature_over(you.pos(), coord_def(-1, -1), false)) + && grid_stair_direction(stair_taken) != CMD_NO_CMD) { std::string stair_str = feature_description(feat, NUM_TRAPS, false, DESC_CAP_THE, false); std::string verb = stair_climb_verb(feat); - mprf("%s slides away from you right after you %s through it!", - stair_str.c_str(), verb.c_str()); + if (coinflip() + && slide_feature_over(you.pos(), coord_def(-1, -1), false)) + { + mprf("%s slides away from you right after you %s through " + "it!", stair_str.c_str(), verb.c_str()); + } + + if (coinflip()) + { + // Stairs stop fleeing from you now you actually caught one. + mprf("%s settles down.", stair_str.c_str(), verb.c_str()); + you.duration[DUR_REPEL_STAIRS_MOVE] = 0; + you.duration[DUR_REPEL_STAIRS_CLIMB] = 0; + } } } - // Stairs running from you is done now that you actually caught one. - you.duration[DUR_REPEL_STAIRS_MOVE] = 0; - you.duration[DUR_REPEL_STAIRS_CLIMB] = 0; - // If butchering was interrupted by switching levels (banishment) // then switch back from butchering tool if there's no hostiles // nearby. diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc index 0ecc9228cc..9e57ea98e6 100644 --- a/crawl-ref/source/item_use.cc +++ b/crawl-ref/source/item_use.cc @@ -4381,8 +4381,10 @@ static bool _vorpalise_weapon() bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn) { + bool to_hit = (which_stat == ENCHANT_TO_HIT); + // Cannot be enchanted nor uncursed. - if (!is_enchantable_weapon(wpn, true)) + if (!is_enchantable_weapon(wpn, true, to_hit)) { if (!quiet) canned_msg( MSG_NOTHING_HAPPENS ); @@ -4394,14 +4396,17 @@ bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn) // Missiles only have one stat. if (wpn.base_type == OBJ_MISSILES) + { which_stat = ENCHANT_TO_HIT; + to_hit = true; + } - int enchant_level = (which_stat == ENCHANT_TO_HIT) ? wpn.plus - : wpn.plus2; + int enchant_level = (to_hit ? wpn.plus + : wpn.plus2); // Even if not affected, it may be uncursed. - if (!is_enchantable_weapon(wpn, false) - || enchant_level >= 4 && x_chance_in_y(enchant_level, 9)) + if (!is_enchantable_weapon(wpn, false, to_hit) + || enchant_level >= 4 && x_chance_in_y(enchant_level, MAX_WPN_ENCHANT)) { if (is_cursed) { @@ -4419,6 +4424,10 @@ bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn) if (!quiet) canned_msg(MSG_NOTHING_HAPPENS); + // Xom thinks it's funny if enchantment is possible but fails. + if (is_enchantable_weapon(wpn, false, to_hit)) + xom_is_stimulated(32); + return (false); } } @@ -4428,7 +4437,7 @@ bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn) if (wpn.base_type == OBJ_WEAPONS) { - if (which_stat == ENCHANT_TO_HIT) + if (to_hit) { if (!quiet) mprf("%s glows green for a moment.", iname.c_str()); @@ -4457,7 +4466,6 @@ bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn) if (is_cursed) do_uncurse_item(wpn); - xom_is_stimulated(16); return (true); } @@ -4540,6 +4548,10 @@ bool enchant_armour(int &ac_change, bool quiet, item_def &arm) if (!quiet) canned_msg(MSG_NOTHING_HAPPENS); + // Xom thinks it's funny if enchantment is possible but fails. + if (is_enchantable_armour(arm, false)) + xom_is_stimulated(32); + return (false); } } @@ -4557,7 +4569,6 @@ bool enchant_armour(int &ac_change, bool quiet, item_def &arm) if (is_cursed) do_uncurse_item(arm); - xom_is_stimulated(16); return (true); } @@ -4978,38 +4989,57 @@ void read_scroll(int slot) const bool is_cursed = item_cursed(wpn); + if (wpn.base_type != OBJ_WEAPONS && wpn.base_type != OBJ_MISSILES + || !is_cursed + && !is_enchantable_weapon(wpn, true, true) + && !is_enchantable_weapon(wpn, true, false)) + { + canned_msg(MSG_NOTHING_HAPPENS); + id_the_scroll = false; + break; + } + // It's a weapon or stack of missiles that is not an artefact + // and not fully enchanted, or at least needs to be uncursed. + // Get item name now before changing enchantment. std::string iname = wpn.name(DESC_CAP_YOUR); - if (wpn.base_type == OBJ_WEAPONS) - { - mprf("%s glows bright yellow for a while.", iname.c_str()); + // Uncursing is always possible. + bool success = is_cursed; + if (_handle_enchant_weapon(ENCHANT_TO_HIT, true)) + success = true; - _handle_enchant_weapon(ENCHANT_TO_HIT, true); + if (is_enchantable_weapon(wpn, true, true) && coinflip() + && _handle_enchant_weapon(ENCHANT_TO_HIT, true)) + { + success = true; + } - if (coinflip()) - _handle_enchant_weapon(ENCHANT_TO_HIT, true); + // Only weapons use the second stat. + if (wpn.base_type == OBJ_WEAPONS) + { + if (_handle_enchant_weapon(ENCHANT_TO_DAM, true)) + success = true; - _handle_enchant_weapon(ENCHANT_TO_DAM, true); + if (is_enchantable_weapon(wpn, true, false) && coinflip() + && _handle_enchant_weapon(ENCHANT_TO_DAM, true)) + { + success = true; + } + } - if (coinflip()) - _handle_enchant_weapon(ENCHANT_TO_DAM, true); + if (is_cursed) + do_uncurse_item(wpn); - if (is_cursed) - do_uncurse_item(wpn); - } - else if (wpn.base_type == OBJ_MISSILES) + if (success) { mprf("%s glow%s bright yellow for a while.", iname.c_str(), wpn.quantity > 1 ? "" : "s"); - - _handle_enchant_weapon(ENCHANT_TO_HIT, true); - - if (coinflip()) - _handle_enchant_weapon(ENCHANT_TO_HIT, true); - - if (is_cursed) - do_uncurse_item(wpn); + } + else + { + canned_msg(MSG_NOTHING_HAPPENS); + id_the_scroll = false; } } else diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index 829b6ca1b9..21fd25c77c 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -484,6 +484,17 @@ void do_curse_item( item_def &item, bool quiet ) if (item.flags & ISFLAG_CURSED) return; + if (!quiet) + { + mprf("Your %s glows black for a moment.", + item.name(DESC_PLAIN).c_str()); + + // If we get the message, we know the item is cursed now. + item.flags |= ISFLAG_KNOW_CURSE; + } + + item.flags |= ISFLAG_CURSED; + // Xom is amused by the player's items being cursed, especially // if they're worn/equipped. if (in_inventory(item)) @@ -508,17 +519,6 @@ void do_curse_item( item_def &item, bool quiet ) } xom_is_stimulated(amusement); } - - if (!quiet) - { - mprf("Your %s glows black for a moment.", - item.name(DESC_PLAIN).c_str()); - - // If we get the message, we know the item is cursed now. - item.flags |= ISFLAG_KNOW_CURSE; - } - - item.flags |= ISFLAG_CURSED; } void do_uncurse_item( item_def &item ) @@ -1389,7 +1389,7 @@ int wand_charge_value(int type) } } -bool is_enchantable_weapon(const item_def &wpn, bool uncurse) +bool is_enchantable_weapon(const item_def &wpn, bool uncurse, bool first) { if (wpn.base_type != OBJ_WEAPONS && wpn.base_type != OBJ_MISSILES) return (false); @@ -1399,7 +1399,8 @@ bool is_enchantable_weapon(const item_def &wpn, bool uncurse) if (wpn.base_type == OBJ_WEAPONS) { if (is_artefact(wpn) - || wpn.plus >= MAX_WPN_ENCHANT && wpn.plus2 >= MAX_WPN_ENCHANT) + || first && wpn.plus >= MAX_WPN_ENCHANT + || !first && wpn.plus2 >= MAX_WPN_ENCHANT) { return (uncurse && item_cursed(wpn)); } diff --git a/crawl-ref/source/itemprop.h b/crawl-ref/source/itemprop.h index 63a77f7c41..a4b88ea11b 100644 --- a/crawl-ref/source/itemprop.h +++ b/crawl-ref/source/itemprop.h @@ -674,7 +674,8 @@ bool check_armour_shape( const item_def &item, bool quiet ); bool item_is_rechargeable(const item_def &it, bool hide_charged = false, bool weapons = false); int wand_charge_value(int type); -bool is_enchantable_weapon(const item_def &wpn, bool uncurse); +bool is_enchantable_weapon(const item_def &wpn, bool uncurse, + bool first = true); bool is_enchantable_armour(const item_def &arm, bool uncurse, bool unknown = false); diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index 589fdfe492..488babbb43 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -1635,6 +1635,11 @@ static bool _stair_moves_pre(dungeon_feature_type stair) else pct = 50; + // When the effect is still strong, the chance to actually catch a stair + // is smaller. (Assuming the duration starts out at 500.) + const int dur = std::max(0, you.duration[DUR_REPEL_STAIRS_CLIMB] - 200); + pct += dur/20; + if (!x_chance_in_y(pct, 100)) return (false); diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index c5948c9bef..e58056765b 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -2201,13 +2201,16 @@ static coord_def _random_monster_nearby_habitable_space(const monsters& mon, return (target); } -bool monster_blink(monsters *monster) +bool monster_blink(monsters *monster, bool quiet) { coord_def near = _random_monster_nearby_habitable_space(*monster, false, true); if (near == monster->pos()) return (false); + if (!quiet) + simple_monster_message(monster, " blinks!"); + if (!(monster->flags & MF_WAS_IN_VIEW)) monster->seen_context = "thin air"; @@ -5627,10 +5630,7 @@ static bool _handle_special_ability(monsters *monster, bolt & beem) case MONS_KILLER_KLOWN: case MONS_PRINCE_RIBBIT: if (one_chance_in(7) || mons_is_caught(monster) && one_chance_in(3)) - { - simple_monster_message(monster, " blinks."); used = monster_blink(monster); - } break; case MONS_MANTICORE: @@ -6011,7 +6011,6 @@ static bool _handle_scroll(monsters *monster) if (mons_near(monster)) { simple_monster_message(monster, " reads a scroll."); - simple_monster_message(monster, " blinks!"); monster_blink(monster); read = true; ident = ID_KNOWN_TYPE; @@ -6602,8 +6601,6 @@ static bool _handle_spell(monsters *monster, bolt &beem) if (monsterNearby) { mons_cast_noise(monster, beem, spell_cast); - - simple_monster_message(monster, " blinks!"); monster_blink(monster); monster->lose_energy(EUT_SPELL); diff --git a/crawl-ref/source/monstuff.h b/crawl-ref/source/monstuff.h index 9a9975def7..899e304abd 100644 --- a/crawl-ref/source/monstuff.h +++ b/crawl-ref/source/monstuff.h @@ -118,7 +118,7 @@ void monster_drop_ething(monsters *monster, bool mark_item_origins = false, /* *********************************************************************** * called from: fight * *********************************************************************** */ -bool monster_blink(monsters *monster); +bool monster_blink(monsters *monster, bool quiet = false); /* *********************************************************************** diff --git a/crawl-ref/source/output.cc b/crawl-ref/source/output.cc index f4e638d309..7838ce124d 100644 --- a/crawl-ref/source/output.cc +++ b/crawl-ref/source/output.cc @@ -1944,7 +1944,17 @@ static std::vector _get_overview_stats() char god_colour_tag[20]; god_colour_tag[0] = 0; std::string godpowers(god_name(you.religion)); - if (you.religion != GOD_NO_GOD) + if (you.religion == GOD_XOM) + { + snprintf(god_colour_tag, sizeof god_colour_tag, "<%s>", + colour_to_str(god_colour(you.religion)).c_str()); + + if (you.gift_timeout == 0) + godpowers += " - BORED"; + else if (you.gift_timeout == 1) + godpowers += " - getting BORED"; + } + else if (you.religion != GOD_NO_GOD) { if (player_under_penance()) strcpy(god_colour_tag, "*"); diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index ba506a7fa7..3a04aaecda 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -335,6 +335,11 @@ bool move_player_to_grid( const coord_def& p, bool stepped, bool allow_shift, else pct = 50; + // When the effect is still strong, the chance to actually catch + // a stair is smaller. (Assuming the duration starts out at 1000.) + const int dur = std::max(0, you.duration[DUR_REPEL_STAIRS_MOVE] - 700); + pct += dur/10; + if (x_chance_in_y(pct, 100)) { if (slide_feature_over(you.pos(), coord_def(-1, -1), false)) @@ -346,6 +351,9 @@ bool move_player_to_grid( const coord_def& p, bool stepped, bool allow_shift, mprf("%s slides away as you move %s it!", stair_str.c_str(), prep.c_str()); + + if (player_in_a_dangerous_place() && one_chance_in(5)) + xom_is_stimulated(32); } } } diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc index d7dbcd38e1..9413dd1460 100644 --- a/crawl-ref/source/stuff.cc +++ b/crawl-ref/source/stuff.cc @@ -403,9 +403,11 @@ static int follower_tag_radius2() // If only friendlies are adjacent, we set a max radius of 6, otherwise // only adjacent friendlies may follow. for (adjacent_iterator ai; ai; ++ai) + { if (const monsters *mon = monster_at(*ai)) if (!mons_friendly(mon)) return (2); + } return (6 * 6); } diff --git a/crawl-ref/source/terrain.cc b/crawl-ref/source/terrain.cc index 6c83d1f162..09ee06a4f5 100644 --- a/crawl-ref/source/terrain.cc +++ b/crawl-ref/source/terrain.cc @@ -883,12 +883,12 @@ bool slide_feature_over(const coord_def &src, coord_def prefered_dest, else { int squares = 0; - for (radius_iterator ri(src, 1, true, false, true); ri; ++ri) + for (adjacent_iterator ai(src); ai; ++ai) { - if (_ok_dest_grid(orig_actor, orig_feat, *ri)) + if (_ok_dest_grid(orig_actor, orig_feat, *ai) + && one_chance_in(++squares)) { - if (one_chance_in(++squares)) - prefered_dest = *ri; + prefered_dest = *ai; } } } diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc index 52cb556a7a..99e57e31c8 100644 --- a/crawl-ref/source/xom.cc +++ b/crawl-ref/source/xom.cc @@ -164,7 +164,7 @@ static bool _xom_feels_nasty() return (you.penance[GOD_XOM] || _xom_is_bored()); } -bool xom_is_nice() +bool xom_is_nice(int tension) { if (you.penance[GOD_XOM]) return (false); @@ -172,7 +172,14 @@ bool xom_is_nice() if (you.religion == GOD_XOM) { // If you.gift_timeout was 0, then Xom was BORED. He HATES that. - return (you.gift_timeout > 0 && you.piety >= random2(MAX_PIETY+1)); + if (you.gift_timeout == 0) + return (false); + + // At high tension Xom is more likely to be nice. + int tension_bonus = (tension <= 0 ? 0 : random2(tension)); + + // Whether Xom is nice depends largely on his mood (== piety). + return (x_chance_in_y(you.piety + tension_bonus, MAX_PIETY + 1)); } else // CARD_XOM return coinflip(); @@ -1410,10 +1417,11 @@ static bool _xom_give_mutations(bool good) 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; #ifdef NOTE_DEBUG_XOM static char mut_buf[80]; - snprintf(mut_buf, sizeof(mut_buf), "XOM: give %s mutations", - good || !_xom_feels_nasty() ? "good" : "random"); + snprintf(mut_buf, sizeof(mut_buf), "XOM: give %s mutation%s", + good ? "good" : "random", num_tries > 1 ? "s" : ""); take_note(Note(NOTE_MESSAGE, 0, 0, mut_buf), true); #endif @@ -1424,7 +1432,7 @@ static bool _xom_give_mutations(bool good) bool failMsg = true; - for (int i = random2(4); i >= 0; --i) + for (int i = num_tries; i > 0; --i) { if (mutate(good ? RANDOM_GOOD_MUTATION : RANDOM_XOM_MUTATION, failMsg, false, true, false, false, @@ -1491,8 +1499,30 @@ 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!"); @@ -1526,7 +1556,10 @@ static bool _xom_throw_divine_lightning() you.reset_escaped_death(); } #ifdef NOTE_DEBUG_XOM - take_note(Note(NOTE_MESSAGE, 0, 0, "XOM: divine lightning"), true); + static char lightning_buf[80]; + snprintf(lightning_buf, sizeof(lightning_buf), + "XOM: divine lightning%s", protection ? " (protected)" : ""); + take_note(Note(NOTE_MESSAGE, 0, 0, lightning_buf), true); #endif return (true); } @@ -2105,8 +2138,8 @@ static bool _xom_lose_stats() #ifdef NOTE_DEBUG_XOM static char stat_buf[80]; snprintf(stat_buf, sizeof(stat_buf), "XOM: stat loss: -%d %s (%d/%d)", - loss, (stat == STAT_STRENGTH ? " Str" : - stat == STAT_DEXTERITY ? " Dex" : "Int"), + 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 : @@ -2196,6 +2229,210 @@ static bool _xom_player_confusion_effect(int sever) return (rc); } +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); + int adj_count = 0; + for (adjacent_iterator ai(stair_pos); ai; ++ai) + if (grid_stair_direction(grd(*ai)) == CMD_NO_CMD + && one_chance_in(++adj_count)) + { + new_pos = *ai; + } + + 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; + 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); + } + } + + // Should only happen if there are stairs in view. + if (stairs_avail.empty()) + { + mpr("No stairs found!"); + return (false); + } + + god_speaks(GOD_XOM, + _get_xom_speech("repel stairs").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); + } + +#ifdef NOTE_DEBUG_XOM + take_note(Note(NOTE_MESSAGE, 0, 0, "XOM: repel stairs"), true); +#endif + + return (true); +} + static bool _xom_draining_torment_effect(int sever) { const std::string speech = _get_xom_speech("draining or torment"); @@ -2230,8 +2467,8 @@ static bool _xom_draining_torment_effect(int sever) torment_player(0, TORMENT_XOM); #ifdef NOTE_DEBUG_XOM static char torment_buf[80]; - snprintf(torment_buf, sizeof(torment_buf), "XOM: torment (%d/%d)", - you.hp, you.hp_max); + snprintf(torment_buf, sizeof(torment_buf), + "XOM: torment (%d/%d hp)", you.hp, you.hp_max); take_note(Note(NOTE_MESSAGE, 0, 0, torment_buf), true); #endif rc = true; @@ -2386,23 +2623,28 @@ static bool _xom_is_bad(int sever, int tension) done = _xom_polymorph_nearby_monster(false); badness = 3; } + else if ((!nasty || 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(11, sever)) + 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(12, sever)) + 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(13, sever)) + else if (x_chance_in_y(14, sever)) { done = _xom_summon_hostiles(sever); badness = 3 + coinflip(); } - else if (x_chance_in_y(14, sever)) + else if (x_chance_in_y(15, sever)) { _xom_miscast(3, nasty); badness = 4 + coinflip(); @@ -2629,7 +2871,14 @@ void xom_acts(bool niceness, int sever, int tension) const FixedVector orig_mutation = you.mutation; - if (niceness && !one_chance_in(20)) +#ifdef 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 + + if (niceness && !one_chance_in(15)) { // Good stuff. while (!_xom_is_good(sever, tension)) @@ -2637,6 +2886,18 @@ void xom_acts(bool niceness, int sever, int tension) } else { +#ifdef NOTE_DEBUG_XOM + if (_xom_is_bored()) + take_note(Note(NOTE_MESSAGE, 0, 0, "XOM is BORED!"), true); +#ifdef DEBUG_XOM + else if (niceness) + { + take_note(Note(NOTE_MESSAGE, 0, 0, "good act randomly turned bad"), + true); + } +#endif +#endif + // Bad mojo. while (!_xom_is_bad(sever, tension)) ; diff --git a/crawl-ref/source/xom.h b/crawl-ref/source/xom.h index cd1c0a67bb..e17f242d68 100644 --- a/crawl-ref/source/xom.h +++ b/crawl-ref/source/xom.h @@ -26,13 +26,13 @@ void xom_is_stimulated(int maxinterestingness, bool force_message = false); void xom_is_stimulated(int maxinterestingness, const std::string& message, bool force_message = false); -bool xom_is_nice(); +bool xom_is_nice(int tension = -1); void xom_acts(bool niceness, int sever, int tension = -1); const char *describe_xom_favour(bool upper = false); inline void xom_acts(int sever, int tension = -1) { - xom_acts(xom_is_nice(), sever, tension); + xom_acts(xom_is_nice(tension), sever, tension); } void xom_check_lost_item(const item_def& item); -- cgit v1.2.3-54-g00ecf