diff options
author | reaver <address.auto@gmail.com> | 2014-04-12 23:41:56 -0400 |
---|---|---|
committer | Steve Melenchuk <smelenchuk@gmail.com> | 2014-04-14 22:25:16 -0600 |
commit | 6d8a42cd9ae10e2f4931a80236c1b3e001368953 (patch) | |
tree | e9af4cd9aac560a89258a907e08a0ec048532303 /crawl-ref/source/player-reacts.cc | |
parent | 961a08fe896de355d603cf447009c4dac0f8a2ec (diff) | |
download | crawl-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.cc | 1349 |
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; + } +} |