summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/player-reacts.cc
diff options
context:
space:
mode:
authorreaver <address.auto@gmail.com>2014-04-12 23:41:56 -0400
committerSteve Melenchuk <smelenchuk@gmail.com>2014-04-14 22:25:16 -0600
commit6d8a42cd9ae10e2f4931a80236c1b3e001368953 (patch)
treee9af4cd9aac560a89258a907e08a0ec048532303 /crawl-ref/source/player-reacts.cc
parent961a08fe896de355d603cf447009c4dac0f8a2ec (diff)
downloadcrawl-ref-6d8a42cd9ae10e2f4931a80236c1b3e001368953.tar.gz
crawl-ref-6d8a42cd9ae10e2f4931a80236c1b3e001368953.zip
Move some main.cc functions to a new file
Specifically player_reacts, decrement_durations, and various helper functions. [Committer's note: updated the xcode and MSVC project files too; I don't guarantee that they work.] Signed-off-by: Steve Melenchuk <smelenchuk@gmail.com>
Diffstat (limited to 'crawl-ref/source/player-reacts.cc')
-rw-r--r--crawl-ref/source/player-reacts.cc1349
1 files changed, 1349 insertions, 0 deletions
diff --git a/crawl-ref/source/player-reacts.cc b/crawl-ref/source/player-reacts.cc
new file mode 100644
index 0000000000..4cb25f623d
--- /dev/null
+++ b/crawl-ref/source/player-reacts.cc
@@ -0,0 +1,1349 @@
+/**
+ * @file player_reacts.cc
+ * @brief Player functions called every turn, mostly handling enchantment durations/expirations.
+ **/
+
+#include "AppHdr.h"
+
+#include "player-reacts.h"
+
+// Later #includes are copy-pasted from main.cc
+// since I didn't have an automated include-
+// what-you-use program when I wrote this. -reaverb
+
+#include <string>
+#include <algorithm>
+
+#include <errno.h>
+#ifndef TARGET_OS_WINDOWS
+# ifndef __ANDROID__
+# include <langinfo.h>
+# endif
+#endif
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <list>
+#include <sstream>
+#include <iostream>
+
+#ifdef USE_UNIX_SIGNALS
+#include <signal.h>
+#endif
+
+#include "ability.h"
+#include "abyss.h"
+#include "acquire.h"
+#include "act-iter.h"
+#include "areas.h"
+#include "art-enum.h"
+#include "artefact.h"
+#include "arena.h"
+#include "beam.h"
+#include "branch.h"
+#include "chardump.h"
+#include "cio.h"
+#include "cloud.h"
+#include "clua.h"
+#include "colour.h"
+#include "command.h"
+#include "coord.h"
+#include "coordit.h"
+#include "crash.h"
+#include "dactions.h"
+#include "database.h"
+#include "dbg-scan.h"
+#include "dbg-util.h"
+#include "delay.h"
+#include "describe.h"
+#include "dgn-overview.h"
+#include "dgn-shoals.h"
+#include "dlua.h"
+#include "dungeon.h"
+#include "effects.h"
+#include "env.h"
+#include "errors.h"
+#include "exercise.h"
+#include "externs.h"
+#include "goditem.h"
+#include "map_knowledge.h"
+#include "fprop.h"
+#include "fight.h"
+#include "files.h"
+#include "fineff.h"
+#include "food.h"
+#include "godabil.h"
+#include "godcompanions.h"
+#include "godpassive.h"
+#include "godprayer.h"
+#include "hiscores.h"
+#include "initfile.h"
+#include "invent.h"
+#include "item_use.h"
+#include "evoke.h"
+#include "itemname.h"
+#include "itemprop.h"
+#include "items.h"
+#include "libutil.h"
+#include "luaterp.h"
+#include "macro.h"
+#include "makeitem.h"
+#include "mapmark.h"
+#include "maps.h"
+#include "melee_attack.h"
+#include "message.h"
+#include "misc.h"
+#include "mon-act.h"
+#include "mon-abil.h"
+#include "mon-cast.h"
+#include "mon-stuff.h"
+#include "mon-transit.h"
+#include "mon-util.h"
+#include "mutation.h"
+#include "notes.h"
+#include "options.h"
+#include "ouch.h"
+#include "output.h"
+#include "player.h"
+#include "player-equip.h"
+#include "player-stats.h"
+#include "quiver.h"
+#include "random.h"
+#include "religion.h"
+#include "godconduct.h"
+#include "shopping.h"
+#include "skills.h"
+#include "skills2.h"
+#include "species.h"
+#include "spl-book.h"
+#include "spl-cast.h"
+#include "spl-clouds.h"
+#include "spl-damage.h"
+#include "spl-goditem.h"
+#include "spl-other.h"
+#include "spl-selfench.h"
+#include "spl-summoning.h"
+#include "spl-transloc.h"
+#include "spl-util.h"
+#include "stairs.h"
+#include "stash.h"
+#include "state.h"
+#include "stuff.h"
+#include "startup.h"
+#include "tags.h"
+#include "target.h"
+#include "terrain.h"
+#include "throw.h"
+#include "transform.h"
+#include "traps.h"
+#include "travel.h"
+#include "hints.h"
+#include "shout.h"
+#include "stash.h"
+#include "uncancel.h"
+#include "version.h"
+#include "view.h"
+#include "viewchar.h"
+#include "viewgeom.h"
+#include "viewmap.h"
+#include "wiz-dgn.h"
+#include "wiz-fsim.h"
+#include "wiz-item.h"
+#include "wiz-mon.h"
+#include "wiz-you.h"
+#include "xom.h"
+#include "zotdef.h"
+
+#ifdef USE_TILE
+#include "tiledef-dngn.h"
+#include "tilepick.h"
+#endif
+
+#ifdef DGL_SIMPLE_MESSAGING
+#include "dgl-message.h"
+#endif
+
+/**
+ * Decrement a duration by the given delay.
+
+ * The midloss value should be either 0 or a number of turns where the delay
+ * from those turns at normal speed is less than the duration's midpoint. The
+ * use of midloss prevents the player from knowing the exact remaining duration
+ * when the midpoint message is displayed.
+ *
+ * @param dur The duration type to be decremented.
+ * @param delay The delay aut amount by which to decrement the duration.
+ * @param endmsg The message to be displayed when the duration ends.
+ * @param midloss A number of normal-speed turns by which to further decrement
+ * the duration if we cross the duration's midpoint.
+ * @param endmsg The message to be displayed when the duration is decremented
+ * to a value under its midpoint.
+ * @param chan The channel where the endmsg will be printed if the duration
+ * ends.
+ *
+ * @returns True if the duration ended, false otherwise.
+ */
+
+static bool _decrement_a_duration(duration_type dur, int delay,
+ const char* endmsg = nullptr,
+ int midloss = 0,
+ const char* midmsg = nullptr,
+ msg_channel_type chan = MSGCH_DURATION)
+{
+ ASSERT(you.duration[dur] >= 0);
+ if (you.duration[dur] == 0)
+ return false;
+
+ ASSERT(!midloss || midmsg != nullptr);
+ const int midpoint = get_expiration_threshold(dur);
+ ASSERTM(!midloss || midloss * BASELINE_DELAY < midpoint,
+ "midpoint delay loss %d not less than duration midpoint %d",
+ midloss * BASELINE_DELAY, midpoint);
+
+ int old_dur = you.duration[dur];
+ you.duration[dur] -= delay;
+
+ // If we cross the midpoint, handle midloss and print the midpoint message.
+ if (you.duration[dur] <= midpoint && old_dur > midpoint)
+ {
+ you.duration[dur] -= midloss * BASELINE_DELAY;
+ if (midmsg)
+ {
+ // Make sure the player has a turn to react to the midpoint
+ // message.
+ if (you.duration[dur] <= 0)
+ you.duration[dur] = 1;
+ if (need_expiration_warning(dur))
+ mprf(MSGCH_DANGER, "Careful! %s", midmsg);
+ else
+ mprf(chan, "%s", midmsg);
+ }
+ }
+
+ if (you.duration[dur] <= 0)
+ {
+ you.duration[dur] = 0;
+ if (endmsg)
+ mprf(chan, "%s", endmsg);
+ return true;
+ }
+
+ return false;
+}
+
+
+static void _decrement_petrification(int delay)
+{
+ if (_decrement_a_duration(DUR_PETRIFIED, delay) && !you.paralysed())
+ {
+ you.redraw_evasion = true;
+ mprf(MSGCH_DURATION, "You turn to %s and can move again.",
+ you.form == TRAN_LICH ? "bone" :
+ you.form == TRAN_ICE_BEAST ? "ice" :
+ "flesh");
+ }
+
+ if (you.duration[DUR_PETRIFYING])
+ {
+ int &dur = you.duration[DUR_PETRIFYING];
+ int old_dur = dur;
+ if ((dur -= delay) <= 0)
+ {
+ dur = 0;
+ // If we'd kill the player when active flight stops, this will
+ // need to pass the killer. Unlike monsters, almost all flight is
+ // magical, inluding tengu, as there's no flapping of wings. Should
+ // we be nasty to dragon and bat forms? For now, let's not instakill
+ // them even if it's inconsistent.
+ you.fully_petrify(NULL);
+ }
+ else if (dur < 15 && old_dur >= 15)
+ mpr("Your limbs are stiffening.");
+ }
+}
+
+static void _decrement_paralysis(int delay)
+{
+ _decrement_a_duration(DUR_PARALYSIS_IMMUNITY, delay);
+
+ if (you.duration[DUR_PARALYSIS])
+ {
+ _decrement_a_duration(DUR_PARALYSIS, delay);
+
+ if (!you.duration[DUR_PARALYSIS] && !you.petrified())
+ {
+ mprf(MSGCH_DURATION, "You can move again.");
+ you.redraw_evasion = true;
+ you.duration[DUR_PARALYSIS_IMMUNITY] = roll_dice(1, 3)
+ * BASELINE_DELAY;
+ if (you.props.exists("paralysed_by"))
+ you.props.erase("paralysed_by");
+ }
+ }
+}
+
+/**
+ * Player reactions after monster and cloud activities in the turn are finished.
+ */
+void player_reacts_to_monsters()
+{
+ // In case Maurice managed to steal a needed item for example.
+ if (!you_are_delayed())
+ update_can_train();
+
+ if (you.duration[DUR_FIRE_SHIELD] > 0)
+ manage_fire_shield(you.time_taken);
+
+ check_monster_detect();
+
+ if ((you_worship(GOD_ASHENZARI) && !player_under_penance())
+ || you.mutation[MUT_JELLY_GROWTH])
+ {
+ detect_items(-1);
+ }
+
+ if (you.duration[DUR_TELEPATHY])
+ detect_creatures(1 + you.duration[DUR_TELEPATHY] /
+ (2 * BASELINE_DELAY), true);
+
+ // We have to do the messaging here, because a simple wand of flame will
+ // call _maybe_melt_player_enchantments twice. It also avoid duplicate
+ // messages when melting because of several heating sources.
+ string what;
+ if (you.props.exists(MELT_ARMOUR_KEY))
+ {
+ what = "armour";
+ you.props.erase(MELT_ARMOUR_KEY);
+ }
+
+ if (you.props.exists("melt_shield"))
+ {
+ if (what != "")
+ what += " and ";
+ what += "shield";
+ you.props.erase("melt_shield");
+ }
+
+ if (what != "")
+ mprf(MSGCH_DURATION, "The heat melts your icy %s.", what.c_str());
+
+ handle_starvation();
+ _decrement_paralysis(you.time_taken);
+ _decrement_petrification(you.time_taken);
+ if (_decrement_a_duration(DUR_SLEEP, you.time_taken))
+ you.awake();
+}
+
+static bool _check_recite()
+{
+ if (you.hp*2 < you.attribute[ATTR_RECITE_HP]
+ || silenced(you.pos())
+ || you.paralysed()
+ || you.confused()
+ || you.asleep()
+ || you.petrified()
+ || you.berserk())
+ {
+ zin_recite_interrupt();
+ return false;
+ }
+ return true;
+}
+
+
+static int _zin_recite_to_monsters(coord_def where, int prayertype, int, actor *)
+{
+ ASSERT_RANGE(prayertype, 0, NUM_RECITE_TYPES);
+ return zin_recite_to_single_monster(where, (recite_type)prayertype);
+}
+
+
+static void _handle_recitation(int step)
+{
+ mprf("\"%s\"",
+ zin_recite_text(you.attribute[ATTR_RECITE_SEED],
+ you.attribute[ATTR_RECITE_TYPE], step).c_str());
+
+ if (apply_area_visible(_zin_recite_to_monsters,
+ you.attribute[ATTR_RECITE_TYPE], &you))
+ viewwindow();
+
+ // Recite trains more than once per use, because it has a
+ // long timer in between uses and actually takes up multiple
+ // turns.
+ practise(EX_USED_ABIL, ABIL_ZIN_RECITE);
+
+ noisy(you.shout_volume(), you.pos());
+
+ if (step == 0)
+ {
+ string speech = zin_recite_text(you.attribute[ATTR_RECITE_SEED],
+ you.attribute[ATTR_RECITE_TYPE], -1);
+ speech += ".";
+ if (one_chance_in(9))
+ {
+ const string closure = getSpeakString("recite_closure");
+ if (!closure.empty() && one_chance_in(3))
+ {
+ speech += " ";
+ speech += closure;
+ }
+ }
+ mprf(MSGCH_DURATION, "You finish reciting %s", speech.c_str());
+ mpr("You feel short of breath.");
+ you.increase_duration(DUR_BREATH_WEAPON, random2(10) + random2(30));
+ }
+}
+
+
+// Perhaps we should write functions like: update_liquid_flames(), etc.
+// Even better, we could have a vector of callback functions (or
+// objects) which get installed at some point.
+
+/**
+ * Decrement player durations based on how long the player's turn lasted in aut.
+ */
+static void _decrement_durations()
+{
+ int delay = you.time_taken;
+
+ if (you.gourmand())
+ {
+ // Innate gourmand is always fully active.
+ if (player_mutation_level(MUT_GOURMAND) > 0)
+ you.duration[DUR_GOURMAND] = GOURMAND_MAX;
+ else if (you.duration[DUR_GOURMAND] < GOURMAND_MAX && coinflip())
+ you.duration[DUR_GOURMAND] += delay;
+ }
+ else
+ you.duration[DUR_GOURMAND] = 0;
+
+ if (you.duration[DUR_ICEMAIL_DEPLETED] > 0)
+ {
+ if (delay > you.duration[DUR_ICEMAIL_DEPLETED])
+ you.duration[DUR_ICEMAIL_DEPLETED] = 0;
+ else
+ you.duration[DUR_ICEMAIL_DEPLETED] -= delay;
+
+ if (!you.duration[DUR_ICEMAIL_DEPLETED])
+ mprf(MSGCH_DURATION, "Your icy envelope is restored.");
+
+ you.redraw_armour_class = true;
+ }
+
+ if (you.duration[DUR_DEMONIC_GUARDIAN] > 0)
+ {
+ if (delay > you.duration[DUR_DEMONIC_GUARDIAN])
+ you.duration[DUR_DEMONIC_GUARDIAN] = 0;
+ else
+ you.duration[DUR_DEMONIC_GUARDIAN] -= delay;
+ }
+
+ // Must come before berserk.
+ if (_decrement_a_duration(DUR_BUILDING_RAGE, delay))
+ go_berserk(false);
+
+ if (you.duration[DUR_LIQUID_FLAMES])
+ dec_napalm_player(delay);
+
+ const bool melted = you.props.exists(MELT_ARMOUR_KEY);
+ if (_decrement_a_duration(DUR_ICY_ARMOUR, delay,
+ "Your icy armour evaporates.",
+ melted ? 0 : coinflip(),
+ melted ? nullptr
+ : "Your icy armour starts to melt."))
+ {
+ you.redraw_armour_class = true;
+ }
+
+ // Possible reduction of silence radius.
+ if (you.duration[DUR_SILENCE])
+ invalidate_agrid();
+ // and liquefying radius.
+ if (you.duration[DUR_LIQUEFYING])
+ invalidate_agrid();
+
+ _decrement_a_duration(DUR_SILENCE, delay, "Your hearing returns.");
+
+ if (_decrement_a_duration(DUR_TROGS_HAND, delay,
+ NULL, coinflip(),
+ "You feel the effects of Trog's Hand fading."))
+ {
+ trog_remove_trogs_hand();
+ }
+
+ _decrement_a_duration(DUR_REGENERATION, delay,
+ "Your skin stops crawling.",
+ coinflip(),
+ "Your skin is crawling a little less now.");
+
+ _decrement_a_duration(DUR_VEHUMET_GIFT, delay);
+
+ _decrement_a_duration(DUR_JELLY_PRAYER, delay, "Your prayer is over.");
+
+ if (you.duration[DUR_DIVINE_SHIELD] > 0)
+ {
+ if (you.duration[DUR_DIVINE_SHIELD] > 1)
+ {
+ you.duration[DUR_DIVINE_SHIELD] -= delay;
+ if (you.duration[DUR_DIVINE_SHIELD] <= 1)
+ {
+ you.duration[DUR_DIVINE_SHIELD] = 1;
+ mprf(MSGCH_DURATION, "Your divine shield starts to fade.");
+ }
+ }
+
+ if (you.duration[DUR_DIVINE_SHIELD] == 1 && !one_chance_in(3))
+ {
+ you.redraw_armour_class = true;
+ if (--you.attribute[ATTR_DIVINE_SHIELD] == 0)
+ {
+ you.duration[DUR_DIVINE_SHIELD] = 0;
+ mprf(MSGCH_DURATION, "Your divine shield fades away.");
+ }
+ }
+ }
+
+ //jmf: More flexible weapon branding code.
+ int last_value = you.duration[DUR_WEAPON_BRAND];
+
+ if (last_value > 0)
+ {
+ you.duration[DUR_WEAPON_BRAND] -= delay;
+
+ if (you.duration[DUR_WEAPON_BRAND] <= 0)
+ {
+ you.duration[DUR_WEAPON_BRAND] = 0;
+ item_def& weapon = *you.weapon();
+ const int temp_effect = get_weapon_brand(weapon);
+
+ set_item_ego_type(weapon, OBJ_WEAPONS, SPWPN_NORMAL);
+ const char *msg = nullptr;
+
+ switch (temp_effect)
+ {
+ case SPWPN_VORPAL:
+ if (get_vorpal_type(weapon) == DVORP_SLICING)
+ msg = " seems blunter.";
+ else
+ msg = " feels lighter.";
+ break;
+ case SPWPN_FLAME:
+ case SPWPN_FLAMING:
+ msg = " goes out.";
+ break;
+ case SPWPN_FREEZING:
+ msg = " stops glowing.";
+ break;
+ case SPWPN_FROST:
+ msg = "'s frost melts away.";
+ break;
+ case SPWPN_VENOM:
+ msg = " stops dripping with poison.";
+ break;
+ case SPWPN_DRAINING:
+ msg = " stops crackling.";
+ break;
+ case SPWPN_DISTORTION:
+ msg = " seems straighter.";
+ break;
+ case SPWPN_PAIN:
+ msg = " seems less pained.";
+ break;
+ case SPWPN_CHAOS:
+ msg = " seems more stable.";
+ break;
+ case SPWPN_ELECTROCUTION:
+ msg = " stops emitting sparks.";
+ break;
+ case SPWPN_HOLY_WRATH:
+ msg = "'s light goes out.";
+ break;
+ case SPWPN_ANTIMAGIC:
+ msg = " stops repelling magic.";
+ calc_mp();
+ break;
+ default:
+ msg = " seems inexplicably less special.";
+ break;
+ }
+
+ mprf(MSGCH_DURATION, "%s%s", weapon.name(DESC_YOUR).c_str(), msg);
+ you.wield_change = true;
+ }
+ }
+
+ // FIXME: [ds] Remove this once we've ensured durations can never go < 0?
+ if (you.duration[DUR_TRANSFORMATION] <= 0
+ && you.form != TRAN_NONE)
+ {
+ you.duration[DUR_TRANSFORMATION] = 1;
+ }
+
+ // Vampire bat transformations are permanent (until ended).
+ if (you.species != SP_VAMPIRE || you.form != TRAN_BAT
+ || you.duration[DUR_TRANSFORMATION] <= 5 * BASELINE_DELAY)
+ {
+ if (_decrement_a_duration(DUR_TRANSFORMATION, delay, NULL, random2(3),
+ "Your transformation is almost over."))
+ {
+ untransform();
+ }
+ }
+
+ // Must come after transformation duration.
+ _decrement_a_duration(DUR_BREATH_WEAPON, delay,
+ "You have got your breath back.", 0, NULL,
+ MSGCH_RECOVERY);
+
+ if (you.attribute[ATTR_SWIFTNESS] >= 0)
+ {
+ if (_decrement_a_duration(DUR_SWIFTNESS, delay,
+ "You feel sluggish.", coinflip(),
+ "You start to feel a little slower."))
+ {
+ // Start anti-swiftness.
+ you.duration[DUR_SWIFTNESS] = you.attribute[ATTR_SWIFTNESS];
+ you.attribute[ATTR_SWIFTNESS] = -1;
+ }
+ }
+ else
+ {
+ if (_decrement_a_duration(DUR_SWIFTNESS, delay,
+ "You no longer feel sluggish.", coinflip(),
+ "You start to feel a little faster."))
+ {
+ you.attribute[ATTR_SWIFTNESS] = 0;
+ }
+ }
+
+ _decrement_a_duration(DUR_RESISTANCE, delay,
+ "Your resistance to elements expires.", coinflip(),
+ "You start to feel less resistant.");
+
+ if (_decrement_a_duration(DUR_PHASE_SHIFT, delay,
+ "You are firmly grounded in the material plane once more.",
+ coinflip(),
+ "You feel closer to the material plane."))
+ {
+ you.redraw_evasion = true;
+ }
+
+ _decrement_a_duration(DUR_POWERED_BY_DEATH, delay,
+ "You feel less regenerative.");
+
+ _decrement_a_duration(DUR_TELEPATHY, delay, "You feel less empathic.");
+
+ if (_decrement_a_duration(DUR_CONDENSATION_SHIELD, delay,
+ "Your icy shield evaporates.",
+ coinflip(),
+ "Your icy shield starts to melt."))
+ {
+ you.redraw_armour_class = true;
+ }
+
+ if (_decrement_a_duration(DUR_MAGIC_SHIELD, delay,
+ "Your magical shield disappears."))
+ {
+ you.redraw_armour_class = true;
+ }
+
+ if (_decrement_a_duration(DUR_STONESKIN, delay, "Your skin feels tender."))
+ you.redraw_armour_class = true;
+
+ if (_decrement_a_duration(DUR_TELEPORT, delay))
+ {
+ you_teleport_now(true);
+ untag_followers();
+ }
+
+ _decrement_a_duration(DUR_CONTROL_TELEPORT, delay,
+ "You feel uncertain.", coinflip(),
+ "You start to feel a little uncertain.");
+
+ if (_decrement_a_duration(DUR_DEATH_CHANNEL, delay,
+ "Your unholy channel expires.", coinflip(),
+ "Your unholy channel is weakening."))
+ {
+ you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = 0;
+ }
+
+ _decrement_a_duration(DUR_STEALTH, delay, "You feel less stealthy.");
+
+ if (_decrement_a_duration(DUR_INVIS, delay, NULL,
+ coinflip(), "You flicker for a moment."))
+ {
+ if (you.invisible())
+ mprf(MSGCH_DURATION, "You feel more conspicuous.");
+ else
+ mprf(MSGCH_DURATION, "You flicker back into view.");
+ you.attribute[ATTR_INVIS_UNCANCELLABLE] = 0;
+ }
+
+ _decrement_a_duration(DUR_CONF, delay, "You feel less confused.");
+ _decrement_a_duration(DUR_LOWERED_MR, delay, "You feel less vulnerable to hostile enchantments.");
+ _decrement_a_duration(DUR_SLIMIFY, delay, "You feel less slimy.",
+ coinflip(), "Your slime is starting to congeal.");
+ if (_decrement_a_duration(DUR_QUAD_DAMAGE, delay, NULL, 0,
+ "Quad Damage is wearing off."))
+ {
+ invalidate_agrid(true);
+ }
+ _decrement_a_duration(DUR_MIRROR_DAMAGE, delay,
+ "Your dark mirror aura disappears.");
+ if (_decrement_a_duration(DUR_HEROISM, delay,
+ "You feel like a meek peon again."))
+ {
+ you.redraw_evasion = true;
+ you.redraw_armour_class = true;
+ }
+ _decrement_a_duration(DUR_FINESSE, delay, "Your hands slow down.");
+
+ _decrement_a_duration(DUR_CONFUSING_TOUCH, delay,
+ ((string("Your ") + you.hand_name(true)) +
+ " stop glowing.").c_str());
+
+ _decrement_a_duration(DUR_SURE_BLADE, delay,
+ "The bond with your blade fades away.");
+
+ _decrement_a_duration(DUR_FORESTED, delay,
+ "Space becomes stable.");
+
+ if (_decrement_a_duration(DUR_MESMERISED, delay,
+ "You break out of your daze.",
+ 0, NULL, MSGCH_RECOVERY))
+ {
+ you.clear_beholders();
+ }
+
+ _decrement_a_duration(DUR_MESMERISE_IMMUNE, delay);
+
+ if (_decrement_a_duration(DUR_AFRAID, delay,
+ "Your fear fades away.",
+ 0, NULL, MSGCH_RECOVERY))
+ {
+ you.clear_fearmongers();
+ }
+
+ _decrement_a_duration(DUR_FROZEN, delay,
+ "The ice encasing you melts away.",
+ 0, NULL, MSGCH_RECOVERY);
+
+ dec_slow_player(delay);
+ dec_exhaust_player(delay);
+ dec_haste_player(delay);
+
+ if (you.duration[DUR_LIQUEFYING] && !you.stand_on_solid_ground())
+ you.duration[DUR_LIQUEFYING] = 1;
+
+ if (_decrement_a_duration(DUR_LIQUEFYING, delay,
+ "The ground is no longer liquid beneath you."))
+ {
+ invalidate_agrid();
+ }
+
+ if (_decrement_a_duration(DUR_MIGHT, delay,
+ "You feel a little less mighty now."))
+ {
+ notify_stat_change(STAT_STR, -5, true, "might running out");
+ }
+
+ if (_decrement_a_duration(DUR_AGILITY, delay,
+ "You feel a little less agile now."))
+ {
+ notify_stat_change(STAT_DEX, -5, true, "agility running out");
+ }
+
+ if (_decrement_a_duration(DUR_BRILLIANCE, delay,
+ "You feel a little less clever now."))
+ {
+ notify_stat_change(STAT_INT, -5, true, "brilliance running out");
+ }
+
+ if (you.duration[DUR_BERSERK]
+ && (_decrement_a_duration(DUR_BERSERK, delay)
+ || you.hunger + 100 <= HUNGER_STARVING + BERSERK_NUTRITION))
+ {
+ mpr("You are no longer berserk.");
+ you.duration[DUR_BERSERK] = 0;
+
+ // Sometimes berserk leaves us physically drained.
+ //
+ // Chance of passing out:
+ // - mutation gives a large plus in order to try and
+ // avoid the mutation being a "death sentence" to
+ // certain characters.
+
+ if (you.berserk_penalty != NO_BERSERK_PENALTY
+ && one_chance_in(10 + player_mutation_level(MUT_BERSERK) * 25))
+ {
+ // Note the beauty of Trog! They get an extra save that's at
+ // the very least 20% and goes up to 100%.
+ if (you_worship(GOD_TROG)
+ && !player_under_penance()
+ && x_chance_in_y(you.piety, piety_breakpoint(5)))
+ {
+ mpr("Trog's vigour flows through your veins.");
+ }
+ else
+ {
+ mprf(MSGCH_WARN, "You pass out from exhaustion.");
+ you.increase_duration(DUR_PARALYSIS, roll_dice(1,4));
+ you.stop_constricting_all();
+ }
+ }
+
+ if (!you.duration[DUR_PARALYSIS] && !you.petrified())
+ mprf(MSGCH_WARN, "You are exhausted.");
+
+ if (you.species == SP_LAVA_ORC)
+ mpr("You feel less hot-headed.");
+
+ // This resets from an actual penalty or from NO_BERSERK_PENALTY.
+ you.berserk_penalty = 0;
+
+ int dur = 12 + roll_dice(2, 12);
+ // For consistency with slow give exhaustion 2 times the nominal
+ // duration.
+ you.increase_duration(DUR_EXHAUSTED, dur * 2);
+
+ notify_stat_change(STAT_STR, -5, true, "berserk running out");
+
+ // Don't trigger too many hints mode messages.
+ const bool hints_slow = Hints.hints_events[HINT_YOU_ENCHANTED];
+ Hints.hints_events[HINT_YOU_ENCHANTED] = false;
+
+ slow_player(dur);
+
+ make_hungry(BERSERK_NUTRITION, true);
+ you.hunger = max(HUNGER_STARVING - 100, you.hunger);
+
+ // 1KB: No berserk healing.
+ set_hp((you.hp + 1) * 2 / 3);
+ calc_hp();
+
+ learned_something_new(HINT_POSTBERSERK);
+ Hints.hints_events[HINT_YOU_ENCHANTED] = hints_slow;
+ you.redraw_quiver = true; // Can throw again.
+ }
+
+ if (_decrement_a_duration(DUR_CORONA, delay) && !you.backlit())
+ mprf(MSGCH_DURATION, "You are no longer glowing.");
+
+ // Leak piety from the piety pool into actual piety.
+ // Note that changes of religious status without corresponding actions
+ // (killing monsters, offering items, ...) might be confusing for characters
+ // of other religions.
+ // For now, though, keep information about what happened hidden.
+ if (you.piety < MAX_PIETY && you.duration[DUR_PIETY_POOL] > 0
+ && one_chance_in(5))
+ {
+ you.duration[DUR_PIETY_POOL]--;
+ gain_piety(1, 1, true);
+
+#if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_SACRIFICE) || defined(DEBUG_PIETY)
+ mprf(MSGCH_DIAGNOSTICS, "Piety increases by 1 due to piety pool.");
+
+ if (you.duration[DUR_PIETY_POOL] == 0)
+ mprf(MSGCH_DIAGNOSTICS, "Piety pool is now empty.");
+#endif
+ }
+
+ if (you.duration[DUR_DISJUNCTION])
+ {
+ disjunction();
+ _decrement_a_duration(DUR_DISJUNCTION, delay,
+ "The translocation energy dissipates.");
+ if (!you.duration[DUR_DISJUNCTION])
+ invalidate_agrid(true);
+ }
+
+ if (_decrement_a_duration(DUR_TORNADO_COOLDOWN, delay,
+ "The winds around you calm down."))
+ {
+ remove_tornado_clouds(MID_PLAYER);
+ }
+ // Should expire before flight.
+ if (you.duration[DUR_TORNADO])
+ {
+ tornado_damage(&you, min(delay, you.duration[DUR_TORNADO]));
+ _decrement_a_duration(DUR_TORNADO, delay,
+ "The winds around you start to calm down.");
+ if (!you.duration[DUR_TORNADO])
+ you.duration[DUR_TORNADO_COOLDOWN] = random_range(25, 35);
+ }
+
+ if (you.duration[DUR_FLIGHT])
+ {
+ if (!you.permanent_flight())
+ {
+ if (_decrement_a_duration(DUR_FLIGHT, delay, nullptr, random2(6),
+ "You are starting to lose your buoyancy."))
+ {
+ land_player();
+ }
+ }
+ else if ((you.duration[DUR_FLIGHT] -= delay) <= 0)
+ {
+ // Just time out potions/spells/miscasts.
+ you.attribute[ATTR_FLIGHT_UNCANCELLABLE] = 0;
+ you.duration[DUR_FLIGHT] = 0;
+ }
+ }
+
+ if (you.rotting > 0)
+ {
+ // XXX: Mummies have an ability (albeit an expensive one) that
+ // can fix rotted HPs now... it's probably impossible for them
+ // to even start rotting right now, but that could be changed. - bwr
+ // It's not normal biology, so Cheibriados won't help.
+ if (you.species == SP_MUMMY)
+ you.rotting = 0;
+ else if (x_chance_in_y(you.rotting, 20)
+ && !you.duration[DUR_DEATHS_DOOR])
+ {
+ mprf(MSGCH_WARN, "You feel your flesh rotting away.");
+ rot_hp(1);
+ you.rotting--;
+ }
+ }
+
+ // ghoul rotting is special, but will deduct from you.rotting
+ // if it happens to be positive - because this is placed after
+ // the "normal" rotting check, rotting attacks can be somewhat
+ // more painful on ghouls - reversing order would make rotting
+ // attacks somewhat less painful, but that seems wrong-headed {dlb}:
+ if (you.species == SP_GHOUL)
+ {
+ int resilience = 400;
+
+ if (you_worship(GOD_CHEIBRIADOS) && you.piety >= piety_breakpoint(0))
+ resilience = resilience * 3 / 2;
+
+ // Faster rotting when hungry.
+ if (you.hunger_state < HS_SATIATED)
+ resilience >>= HS_SATIATED - you.hunger_state;
+
+ if (one_chance_in(resilience))
+ {
+ dprf("rot rate: 1/%d", resilience);
+ mprf(MSGCH_WARN, "You feel your flesh rotting away.");
+ rot_hp(1);
+ if (you.rotting > 0)
+ you.rotting--;
+ }
+ }
+
+ if (you.duration[DUR_DEATHS_DOOR])
+ {
+ if (you.hp > allowed_deaths_door_hp())
+ {
+ set_hp(allowed_deaths_door_hp());
+ you.redraw_hit_points = true;
+ }
+
+ if (_decrement_a_duration(DUR_DEATHS_DOOR, delay,
+ "Your life is in your own hands again!",
+ random2(6),
+ "Your time is quickly running out!"))
+ {
+ you.increase_duration(DUR_EXHAUSTED, roll_dice(1,3));
+ }
+ }
+
+ if (_decrement_a_duration(DUR_DIVINE_STAMINA, delay))
+ zin_remove_divine_stamina();
+
+ if (_decrement_a_duration(DUR_DIVINE_VIGOUR, delay))
+ elyvilon_remove_divine_vigour();
+
+ _decrement_a_duration(DUR_REPEL_STAIRS_MOVE, delay);
+ _decrement_a_duration(DUR_REPEL_STAIRS_CLIMB, delay);
+
+ _decrement_a_duration(DUR_COLOUR_SMOKE_TRAIL, 1);
+
+ if (_decrement_a_duration(DUR_SCRYING, delay,
+ "Your astral sight fades away."))
+ {
+ you.xray_vision = false;
+ }
+
+ _decrement_a_duration(DUR_LIFESAVING, delay,
+ "Your divine protection fades away.");
+
+ if (_decrement_a_duration(DUR_DARKNESS, delay,
+ "The ambient light returns to normal.")
+ || (you.duration[DUR_DARKNESS] && you.haloed()))
+ {
+ if (you.duration[DUR_DARKNESS])
+ {
+ you.duration[DUR_DARKNESS] = 0;
+ mpr("The divine light dispels your darkness!");
+ }
+ update_vision_range();
+ }
+
+ _decrement_a_duration(DUR_SHROUD_OF_GOLUBRIA, delay,
+ "Your shroud unravels.",
+ 0,
+ "Your shroud begins to fray at the edges.");
+
+ _decrement_a_duration(DUR_INFUSION, delay,
+ "Your attacks are no longer magically infused.",
+ 0,
+ "You are feeling less magically infused.");
+
+ _decrement_a_duration(DUR_SONG_OF_SLAYING, delay,
+ "Your song has ended.",
+ 0,
+ "Your song is almost over.");
+
+ _decrement_a_duration(DUR_SENTINEL_MARK, delay,
+ "The sentinel's mark upon you fades away.");
+
+ _decrement_a_duration(DUR_WEAK, delay,
+ "Your attacks no longer feel as feeble.");
+
+ _decrement_a_duration(DUR_DIMENSION_ANCHOR, delay,
+ "You are no longer firmly anchored in space.");
+
+ _decrement_a_duration(DUR_SICKENING, delay);
+
+ _decrement_a_duration(DUR_SAP_MAGIC, delay,
+ "Your magic seems less tainted.");
+
+ if (!you.duration[DUR_SAP_MAGIC])
+ {
+ _decrement_a_duration(DUR_MAGIC_SAPPED, delay,
+ "You feel more in control of your magic.");
+ }
+
+ _decrement_a_duration(DUR_ANTIMAGIC, delay,
+ "You regain control over your magic.");
+
+ _decrement_a_duration(DUR_WATER_HOLD_IMMUNITY, delay);
+ if (you.duration[DUR_WATER_HOLD])
+ handle_player_drowning(delay);
+
+ if (you.duration[DUR_FLAYED])
+ {
+ bool near_ghost = false;
+ for (monster_iterator mi; mi; ++mi)
+ {
+ if (mi->type == MONS_FLAYED_GHOST && !mi->wont_attack()
+ && you.see_cell(mi->pos()))
+ {
+ near_ghost = true;
+ break;
+ }
+ }
+ if (!near_ghost)
+ {
+ if (_decrement_a_duration(DUR_FLAYED, delay))
+ heal_flayed_effect(&you);
+ }
+ else if (you.duration[DUR_FLAYED] < 80)
+ you.duration[DUR_FLAYED] += div_rand_round(50, delay);
+ }
+
+ _decrement_a_duration(DUR_RETCHING, delay, "Your fit of retching subsides.");
+
+ if (you.duration[DUR_TOXIC_RADIANCE])
+ {
+ int ticks = (you.duration[DUR_TOXIC_RADIANCE] / 10)
+ - ((you.duration[DUR_TOXIC_RADIANCE] - delay) / 10);
+ toxic_radiance_effect(&you, ticks);
+ _decrement_a_duration(DUR_TOXIC_RADIANCE, delay,
+ "Your toxic aura wanes.");
+ }
+
+ if (you.duration[DUR_RECITE] && _check_recite())
+ {
+ const int old_recite =
+ (you.duration[DUR_RECITE] + BASELINE_DELAY - 1) / BASELINE_DELAY;
+ _decrement_a_duration(DUR_RECITE, delay);
+ const int new_recite =
+ (you.duration[DUR_RECITE] + BASELINE_DELAY - 1) / BASELINE_DELAY;
+ if (old_recite != new_recite)
+ _handle_recitation(new_recite);
+ }
+
+ if (you.duration[DUR_GRASPING_ROOTS])
+ check_grasping_roots(&you);
+
+ if (you.attribute[ATTR_NEXT_RECALL_INDEX] > 0)
+ do_recall(delay);
+
+ _decrement_a_duration(DUR_SLEEP_IMMUNITY, delay);
+
+ _decrement_a_duration(DUR_FIRE_VULN, delay,
+ "You feel less vulnerable to fire.");
+
+ _decrement_a_duration(DUR_POISON_VULN, delay,
+ "You feel less vulnerable to poison.");
+
+ if (_decrement_a_duration(DUR_PORTAL_PROJECTILE, delay,
+ "You are no longer teleporting projectiles to their destination."))
+ {
+ you.attribute[ATTR_PORTAL_PROJECTILE] = 0;
+ }
+
+ _decrement_a_duration(DUR_DRAGON_CALL_COOLDOWN, delay,
+ "You can once more reach out to the dragon horde.");
+
+ if (you.duration[DUR_DRAGON_CALL])
+ {
+ do_dragon_call(delay);
+ if (_decrement_a_duration(DUR_DRAGON_CALL, delay,
+ "The roar of the dragon horde subsides."))
+ {
+ you.duration[DUR_DRAGON_CALL_COOLDOWN] = random_range(150, 250);
+ }
+
+ }
+
+ if (you.duration[DUR_ABJURATION_AURA])
+ {
+ do_aura_of_abjuration(delay);
+ _decrement_a_duration(DUR_ABJURATION_AURA, delay,
+ "Your aura of abjuration expires.");
+ }
+
+ dec_elixir_player(delay);
+
+ if (!env.sunlight.empty())
+ process_sunlights();
+}
+
+
+// For worn items; weapons do this on melee attacks.
+static void _check_equipment_conducts()
+{
+ if (you_worship(GOD_DITHMENOS) && one_chance_in(10))
+ {
+ bool illuminating = false, fiery = false;
+ const item_def* item;
+ for (int i = EQ_MIN_ARMOUR; i < NUM_EQUIP; i++)
+ {
+ item = you.slot_item(static_cast<equipment_type>(i));
+ if (!item)
+ continue;
+ if (is_illuminating_item(*item))
+ illuminating = true;
+ else if (is_fiery_item(*item))
+ fiery = true;
+ if (illuminating && fiery)
+ break;
+ }
+ if (illuminating)
+ did_god_conduct(DID_ILLUMINATE, 1, true);
+ else if (fiery)
+ did_god_conduct(DID_FIRE, 1, true);
+ }
+}
+
+
+// cjo: Handles player hp and mp regeneration. If the counter you.hit_points_regeneration
+// is over 100, a loop restores 1 hp and decreases the counter by 100 (so you can regen
+// more than 1 hp per turn). If the counter is below 100, it is increased by a variable
+// calculated from delay, BASELINE_DELAY, and your regeneration rate. MP regeneration happens
+// similarly, but the countup depends on delay, BASELINE_DELAY, and you.max_magic_points
+static void _regenerate_hp_and_mp(int delay)
+{
+ if (crawl_state.disables[DIS_PLAYER_REGEN])
+ return;
+
+ // XXX: using an int tmp to fix the fact that hit_points_regeneration
+ // is only an unsigned char and is thus likely to overflow. -- bwr
+ int tmp = you.hit_points_regeneration;
+
+ if (you.hp < you.hp_max && !you.duration[DUR_DEATHS_DOOR])
+ {
+ const int base_val = player_regen();
+ tmp += div_rand_round(base_val * delay, BASELINE_DELAY);
+ }
+
+ while (tmp >= 100)
+ {
+ // at low mp, "mana link" restores mp in place of hp
+ if (you.mutation[MUT_MANA_LINK]
+ && !x_chance_in_y(you.magic_points, you.max_magic_points))
+ {
+ inc_mp(1);
+ }
+ else // standard hp regeneration
+ inc_hp(1);
+ tmp -= 100;
+ }
+
+ ASSERT_RANGE(tmp, 0, 100);
+ you.hit_points_regeneration = tmp;
+
+ // XXX: Don't let DD use guardian spirit for free HP, since their
+ // damage shaving is enough. (due, dpeg)
+ if (you.spirit_shield() && you.species == SP_DEEP_DWARF)
+ return;
+
+ // XXX: Doing the same as the above, although overflow isn't an
+ // issue with magic point regeneration, yet. -- bwr
+ tmp = you.magic_points_regeneration;
+
+ if (you.magic_points < you.max_magic_points)
+ {
+ const int base_val = 7 + you.max_magic_points / 2;
+ int mp_regen_countup = div_rand_round(base_val * delay, BASELINE_DELAY);
+ if (you.mutation[MUT_MANA_REGENERATION])
+ mp_regen_countup *= 2;
+ tmp += mp_regen_countup;
+ }
+
+ while (tmp >= 100)
+ {
+ inc_mp(1);
+ tmp -= 100;
+ }
+
+ ASSERT_RANGE(tmp, 0, 100);
+ you.magic_points_regeneration = tmp;
+}
+
+void player_reacts()
+{
+ extern int stealth; // defined in main.cc
+
+ search_around();
+
+ stealth = check_stealth();
+
+#ifdef DEBUG_STEALTH
+ // Too annoying for regular diagnostics.
+ mprf(MSGCH_DIAGNOSTICS, "stealth: %d", stealth);
+#endif
+
+ if (you.attribute[ATTR_SHADOWS])
+ shadow_lantern_effect();
+
+ if (you.species == SP_LAVA_ORC)
+ temperature_check();
+
+ if (player_mutation_level(MUT_DEMONIC_GUARDIAN))
+ check_demonic_guardian();
+
+ _check_equipment_conducts();
+
+ if (you.unrand_reacts != 0)
+ unrand_reacts();
+
+ // Handle sound-dependent effects that are silenced
+ if (silenced(you.pos()))
+ {
+ if (you.duration[DUR_SONG_OF_SLAYING])
+ {
+ mpr("The silence causes your song to end.");
+ _decrement_a_duration(DUR_SONG_OF_SLAYING, you.duration[DUR_SONG_OF_SLAYING]);
+ }
+ }
+
+ // Singing makes a continuous noise
+ if (you.duration[DUR_SONG_OF_SLAYING])
+ noisy(8, you.pos());
+
+ if (one_chance_in(10))
+ {
+ const int teleportitis_level = player_teleport();
+ // this is instantaneous
+ if (teleportitis_level > 0 && one_chance_in(100 / teleportitis_level))
+ {
+ if (teleportitis_level >= 8)
+ you_teleport_now(true);
+ else
+ you_teleport_now(true, false, teleportitis_level * 5);
+ }
+ else if (player_in_branch(BRANCH_ABYSS) && one_chance_in(80)
+ && (!map_masked(you.pos(), MMT_VAULT) || one_chance_in(3)))
+ {
+ mprf(MSGCH_BANISHMENT, "You are suddenly pulled into a different region of the Abyss!");
+ you_teleport_now(false); // to new area of the Abyss
+
+ // It's effectively a new level, make a checkpoint save so eventual
+ // crashes lose less of the player's progress (and fresh new bad
+ // mutations).
+ if (!crawl_state.disables[DIS_SAVE_CHECKPOINTS])
+ save_game(false);
+ }
+ else if (you.form == TRAN_WISP && !you.stasis())
+ random_blink(false);
+ }
+
+ actor_apply_cloud(&you);
+
+ if (env.level_state & LSTATE_SLIMY_WALL)
+ slime_wall_damage(&you, you.time_taken);
+
+ // Icy shield and armour melt over lava.
+ if (grd(you.pos()) == DNGN_LAVA)
+ expose_player_to_element(BEAM_LAVA);
+
+ you.update_beholders();
+ you.update_fearmongers();
+
+ _decrement_durations();
+ you.handle_constriction();
+
+ // increment constriction durations
+ you.accum_has_constricted();
+
+ int capped_time = you.time_taken;
+ if (you.walking && capped_time > BASELINE_DELAY)
+ capped_time = BASELINE_DELAY;
+
+ int food_use = player_hunger_rate();
+ food_use = div_rand_round(food_use * capped_time, BASELINE_DELAY);
+
+ if (food_use > 0 && you.hunger > 0)
+ {
+ make_hungry(food_use, true);
+ if (you.duration[DUR_AMBROSIA])
+ {
+ if (food_use > you.duration[DUR_AMBROSIA])
+ food_use = you.duration[DUR_AMBROSIA];
+ you.duration[DUR_AMBROSIA] -= food_use;
+ inc_mp(food_use);
+ }
+ }
+
+ _regenerate_hp_and_mp(capped_time);
+
+ dec_disease_player(capped_time);
+ if (you.duration[DUR_POISONING])
+ handle_player_poison(capped_time);
+
+ recharge_rods(you.time_taken, false);
+
+ // Reveal adjacent mimics.
+ for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
+ discover_mimic(*ai);
+
+ // Player stealth check.
+ seen_monsters_react();
+
+ update_stat_zero();
+
+ // XOM now ticks from here, to increase his reaction time to tension.
+ if (you_worship(GOD_XOM))
+ xom_tick();
+}
+
+void extract_manticore_spikes(const char* endmsg)
+{
+ if (_decrement_a_duration(DUR_BARBS, you.time_taken, endmsg))
+ {
+ // Note: When this is called in _move player(), ATTR_BARBS_POW
+ // has already been used to calculated damage for the player.
+ // Otherwise, this prevents the damage.
+
+ you.attribute[ATTR_BARBS_POW] = 0;
+ }
+}