/**
 * @file
 * @brief Implementing the actor interface for player.
**/

#include "AppHdr.h"

#include "player.h"

#include <math.h>

#include "act-iter.h"
#include "areas.h"
#include "art-enum.h"
#include "artefact.h"
#include "coordit.h"
#include "dgnevent.h"
#include "env.h"
#include "fight.h"
#include "food.h"
#include "godconduct.h"
#include "goditem.h"
#include "hints.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "item_use.h"
#include "libutil.h"
#include "message.h"
#include "misc.h"
#include "monster.h"
#include "mon-util.h" // for decline_pronoun
#include "player-stats.h"
#include "religion.h"
#include "spl-damage.h"
#include "state.h"
#include "terrain.h"
#include "transform.h"
#include "traps.h"
#include "viewgeom.h"

int player::mindex() const
{
    return MHITYOU;
}

kill_category player::kill_alignment() const
{
    return KC_YOU;
}

god_type player::deity() const
{
    return religion;
}

bool player::alive() const
{
    // Simplistic, but if the player dies the game is over anyway, so
    // nobody can ask further questions.
    return !crawl_state.game_is_arena();
}

bool player::is_summoned(int* _duration, int* summon_type) const
{
    if (_duration != NULL)
        *_duration = -1;
    if (summon_type != NULL)
        *summon_type = 0;

    return false;
}

void player::moveto(const coord_def &c, bool clear_net)
{
    if (clear_net && c != pos())
        clear_trapping_net();

    crawl_view.set_player_at(c);
    set_position(c);

    clear_far_constrictions();
    end_searing_ray();
}

bool player::move_to_pos(const coord_def &c, bool clear_net)
{
    actor *target = actor_at(c);
    if (!target || target->submerged())
    {
        moveto(c, clear_net);
        return true;
    }
    return false;
}

void player::apply_location_effects(const coord_def &oldpos,
                                    killer_type killer,
                                    int killernum)
{
    moveto_location_effects(env.grid(oldpos));
}

void player::set_position(const coord_def &c)
{
    ASSERT(!crawl_state.game_is_arena());

    const bool real_move = (c != pos());
    actor::set_position(c);

    if (real_move)
    {
        reset_prev_move();

        if (duration[DUR_QUAD_DAMAGE])
            invalidate_agrid(true);

        if (player_has_orb())
        {
            env.orb_pos = c;
            invalidate_agrid(true);
        }

        dungeon_events.fire_position_event(DET_PLAYER_MOVED, c);
    }
}

bool player::swimming() const
{
    return in_water() && can_swim();
}

bool player::submerged() const
{
    return false;
}

bool player::floundering() const
{
    return in_water() && !can_swim() && !extra_balanced();
}

bool player::extra_balanced() const
{
    const dungeon_feature_type grid = grd(pos());
    return species == SP_GREY_DRACONIAN
              || form == TRAN_TREE
              || grid == DNGN_SHALLOW_WATER
                  && (species == SP_NAGA // tails, not feet
                      || body_size(PSIZE_BODY) >= SIZE_LARGE)
                  && (form == TRAN_LICH || form == TRAN_STATUE
                      || form == TRAN_SHADOW
                      || !form_changed_physiology());
}

int player::get_hit_dice() const
{
    return experience_level;
}

int player::get_experience_level() const
{
    return experience_level;
}

bool player::can_pass_through_feat(dungeon_feature_type grid) const
{
    return !feat_is_solid(grid) && grid != DNGN_MALIGN_GATEWAY;
}

bool player::is_habitable_feat(dungeon_feature_type actual_grid) const
{
    if (!can_pass_through_feat(actual_grid))
        return false;

    if (airborne()
#if TAG_MAJOR_VERSION == 34
            || species == SP_DJINNI
#endif
            )
    {
        return true;
    }

    if (
#if TAG_MAJOR_VERSION == 34
        actual_grid == DNGN_LAVA && species != SP_LAVA_ORC ||
#endif
        actual_grid == DNGN_DEEP_WATER && !can_swim())
    {
        return false;
    }

    return true;
}

size_type player::body_size(size_part_type psize, bool base) const
{
    if (base)
        return species_size(species, psize);
    else
    {
        size_type tf_size = transform_size(form, psize);
        return tf_size == SIZE_CHARACTER ? species_size(species, psize)
                                         : tf_size;
    }
}

int player::body_weight(bool base) const
{
    int weight = actor::body_weight(base);

    if (base)
        return weight;

    switch (form)
    {
    case TRAN_STATUE:
        weight *= 2;
        break;
    case TRAN_LICH:
    case TRAN_SHADOW:
        weight /= 2;
        break;
    default:
        break;
    }

    return weight;
}

int player::damage_type(int)
{
    if (const item_def* wp = weapon())
        return get_vorpal_type(*wp);
    else if (form == TRAN_BLADE_HANDS)
        return DVORP_SLICING;
    else if (has_usable_claws())
        return DVORP_CLAWING;
    else if (has_usable_tentacles())
        return DVORP_TENTACLE;

    return DVORP_CRUSHING;
}

brand_type player::damage_brand(int)
{
    brand_type ret = SPWPN_NORMAL;
    const int wpn = equip[EQ_WEAPON];

    if (wpn != -1 && !melded[EQ_WEAPON])
    {
        if (!is_range_weapon(inv[wpn]))
            ret = get_weapon_brand(inv[wpn]);
    }
    else if (duration[DUR_CONFUSING_TOUCH] || form == TRAN_FUNGUS)
        ret = SPWPN_CONFUSE;
    else
    {
        switch (form)
        {
        case TRAN_SPIDER:
            ret = SPWPN_VENOM;
            break;

        case TRAN_ICE_BEAST:
            ret = SPWPN_FREEZING;
            break;

        case TRAN_LICH:
            ret = SPWPN_DRAINING;
            break;

        case TRAN_BAT:
            if (species == SP_VAMPIRE && one_chance_in(8))
                ret = SPWPN_VAMPIRISM;
            break;

        default:
            break;
        }
    }

    return ret;
}


/**
 * Return the delay caused by attacking with the provided weapon & projectile.
 *
 * @param weap          The weapon to be used; may be null.
 * @param projectile    The projectile to be fired/thrown; may be null.
 * @param random        Whether to randomize delay, or provide a fixed value
 *                      for display.
 * @param scaled        Whether to apply special delay modifiers (finesse)
 * @return              The time taken by an attack with the given weapon &
 *                      projectile, in aut.
 */
random_var player::attack_delay(const item_def *weap,
                                const item_def *projectile, bool random,
                                bool scaled) const
{
    random_var attk_delay = constant(15);
    const int armour_penalty = adjusted_body_armour_penalty(20);
    const int base_shield_penalty = adjusted_shield_penalty(20);

    bool check_weapon = (!projectile && !!weap)
                        || projectile
                            && (is_launched(this, weap, *projectile)
                                != LRET_THROWN);

    if (!check_weapon)
    {
        if (form_uses_xl())
        {
            attk_delay =
                constant(10)
                - div_rand_round(constant(you.experience_level * 10), 54);
        }
        else
        {
            attk_delay =
                rv::max(constant(10),
                        (rv::roll_dice(1, 10) +
                         div_rand_round(
                             rv::roll_dice(2, armour_penalty),
                             20)));

            skill_type sk = projectile ? SK_THROWING : SK_UNARMED_COMBAT;
            attk_delay -= div_rand_round(constant(you.skill(sk, 10)), 54);

            // Bats are faster (for what good it does them).
            if (you.form == TRAN_BAT && !projectile)
                attk_delay = div_rand_round(attk_delay * constant(3), 5);
        }
    }
    else
    {
        if (weap && is_weapon(*weap)
            && (!projectile && !is_range_weapon(*weap)
                || projectile
                   && is_launched(this, weap, *projectile) == LRET_LAUNCHED))
        {
            const skill_type wpn_skill = item_attack_skill(*weap);
            attk_delay = constant(property(*weap, PWPN_SPEED));
            attk_delay -=
                div_rand_round(constant(you.skill(wpn_skill, 10)), 20);

            // apply minimum to weapon skill modification
            attk_delay = rv::max(attk_delay, weapon_min_delay(*weap));

            if (weap->base_type == OBJ_WEAPONS
                && get_weapon_brand(*weap) == SPWPN_SPEED)
            {
                attk_delay = div_rand_round(constant(2) * attk_delay, 3);
            }
        }
    }

    // At the moment it never gets this low anyway.
    attk_delay = rv::max(attk_delay, constant(3));

    // Calculate this separately to avoid overflowing the weights in
    // the random_var.
    random_var shield_penalty = constant(0);
    if (base_shield_penalty)
    {
        shield_penalty =
            div_rand_round(rv::min(rv::roll_dice(1, base_shield_penalty),
                                   rv::roll_dice(1, base_shield_penalty)),
                           20);
    }
    // Give unarmed shield-users a slight penalty always.
    if (!weap && player_wearing_slot(EQ_SHIELD))
        shield_penalty += rv::random2(2);

    int final_delay = random ? attk_delay.roll() + shield_penalty.roll()
                             : attk_delay.expected() + shield_penalty.expected();
    // Stop here if we just want the unmodified value.
    if (!scaled)
        return final_delay;

    const int scaling = finesse_adjust_delay(you.time_taken);
    return max(2, div_rand_round(scaling * final_delay, 10));
}

// Returns the item in the given equipment slot, NULL if the slot is empty.
// eq must be in [EQ_WEAPON, EQ_RING_AMULET], or bad things will happen.
item_def *player::slot_item(equipment_type eq, bool include_melded) const
{
    ASSERT_RANGE(eq, EQ_WEAPON, NUM_EQUIP);

    const int item = equip[eq];
    if (item == -1 || !include_melded && melded[eq])
        return NULL;
    return const_cast<item_def *>(&inv[item]);
}

// Returns the item in the player's weapon slot.
item_def *player::weapon(int /* which_attack */) const
{
    if (melded[EQ_WEAPON])
        return NULL;

    return slot_item(EQ_WEAPON, false);
}

// Give hands required to wield weapon.
hands_reqd_type player::hands_reqd(const item_def &item) const
{
    if (species == SP_FORMICID)
        return HANDS_ONE;
    else
        return actor::hands_reqd(item);
}

bool player::can_wield(const item_def& item, bool ignore_curse,
                       bool ignore_brand, bool ignore_shield,
                       bool ignore_transform) const
{
    if (equip[EQ_WEAPON] != -1 && !ignore_curse)
    {
        if (inv[equip[EQ_WEAPON]].cursed())
            return false;
    }

    // Unassigned means unarmed combat.
    const bool two_handed = item.base_type == OBJ_UNASSIGNED
                            || hands_reqd(item) == HANDS_TWO;

    if (two_handed && !ignore_shield && player_wearing_slot(EQ_SHIELD))
        return false;

    return could_wield(item, ignore_brand, ignore_transform);
}

/**
 * Checks whether the player could ever wield the given weapon, regardless of
 * what they're currently wielding, transformed into, or any other state.
 *
 * @param item              The item to wield.
 * @param ignore_brand      Whether to disregard the weapon's brand.
 * @return                  Whether the player could potentially wield the
 *                          item.
 */
bool player::could_wield(const item_def &item, bool ignore_brand,
                         bool ignore_transform, bool quiet) const
{
    const size_type bsize = body_size(PSIZE_TORSO, ignore_transform);

    // Only ogres and trolls can wield large rocks (for sandblast).
    if (bsize < SIZE_LARGE
        && item.base_type == OBJ_MISSILES && item.sub_type == MI_LARGE_ROCK)
    {
        if (!quiet)
            mpr("That's too large and heavy for you to wield.");
        return false;
    }

    // Most non-weapon objects can be wielded, though there's rarely a point
    if (!is_weapon(item))
    {
        if (item.base_type == OBJ_ARMOUR || item.base_type == OBJ_JEWELLERY)
        {
            if (!quiet)
                mprf("You can't wield %s.", base_type_string(item).c_str());
            return false;
        }

        return true;
    }
    else if (species == SP_FELID)
    {
        if (!quiet)
            mpr("You can't use weapons.");
        return false;
    }

    // Small species wielding large weapons...
    if (!is_weapon_wieldable(item, bsize))
    {
        if (!quiet)
            mpr("That's too large for you to wield.");
        return false;
    }

    // don't let undead/demonspawn wield holy weapons/scrolls (out of spite)
    if (!ignore_brand && undead_or_demonic() && is_holy_item(item))
    {
        if (!quiet)
            mpr("This weapon is holy and will not allow you to wield it.");
        return false;
    }

    return true;
}

// Returns the shield the player is wearing, or NULL if none.
item_def *player::shield() const
{
    return slot_item(EQ_SHIELD, false);
}

void player::make_hungry(int hunger_increase, bool silent)
{
    if (hunger_increase > 0)
        ::make_hungry(hunger_increase, silent);
    else if (hunger_increase < 0)
        ::lessen_hunger(-hunger_increase, silent);
}

string player::name(description_level_type dt, bool) const
{
    switch (dt)
    {
    case DESC_NONE:
        return "";
    case DESC_A: case DESC_THE:
    default:
        return "you";
    case DESC_YOUR:
    case DESC_ITS:
        return "your";
    }
}

string player::pronoun(pronoun_type pro, bool /*force_visible*/) const
{
    return decline_pronoun(GENDER_YOU, pro);
}

string player::conj_verb(const string &verb) const
{
    return verb;
}

string player::hand_name(bool plural, bool *can_plural) const
{
    bool _can_plural;
    if (can_plural == NULL)
        can_plural = &_can_plural;
    *can_plural = true;

    string str;

    if (form == TRAN_BAT || form == TRAN_DRAGON)
        str = "foreclaw";
    else if (form == TRAN_PIG)
        str = "front trotter";
    else if (form == TRAN_SPIDER || form == TRAN_PORCUPINE)
        str = "front leg";
    else if (form == TRAN_ICE_BEAST)
        str = "paw";
    else if (form == TRAN_BLADE_HANDS)
        str = "scythe-like blade";
    else if (form == TRAN_TREE)
        str = "branch";
    else if (form == TRAN_WISP)
        str = "misty tendril";
    else if (form == TRAN_LICH || form == TRAN_STATUE
             || form == TRAN_SHADOW
             || !form_changed_physiology())
    {
        if (species == SP_FELID)
            str = "paw";
        else if (has_usable_claws())
            str = "claw";
        else if (has_usable_tentacles())
            str = "tentacle";
    }

    if (str.empty())
        return plural ? "hands" : "hand";

    if (plural && *can_plural)
        str = pluralise(str);

    return str;
}

string player::foot_name(bool plural, bool *can_plural) const
{
    bool _can_plural;
    if (can_plural == NULL)
        can_plural = &_can_plural;
    *can_plural = true;

    string str;

    if (form == TRAN_SPIDER)
        str = "hind leg";
    else if (form == TRAN_PIG)
        str = "trotter";
    else if (form == TRAN_TREE)
        str = "root";
    else if (form == TRAN_WISP)
        str = "strand";
    else if (form == TRAN_LICH || form == TRAN_STATUE
             || form == TRAN_SHADOW
             || !form_changed_physiology())
    {
        if (player_mutation_level(MUT_HOOVES) >= 3)
            str = "hoof";
        else if (has_usable_talons())
            str = "talon";
        else if (has_usable_tentacles())
        {
            str         = "tentacles";
            *can_plural = false;
        }
        else if (species == SP_NAGA)
        {
            str         = "underbelly";
            *can_plural = false;
        }
        else if (species == SP_FELID)
            str = "paw";
        else if (fishtail)
        {
            str         = "tail";
            *can_plural = false;
        }
    }

    if (str.empty())
        return plural ? "feet" : "foot";

    if (plural && *can_plural)
        str = pluralise(str);

    return str;
}

string player::arm_name(bool plural, bool *can_plural) const
{
    if (form_changed_physiology())
        return hand_name(plural, can_plural);

    if (can_plural != NULL)
        *can_plural = true;

    string adj;
    string str = "arm";

    if (player_genus(GENPC_DRACONIAN) || species == SP_NAGA)
        adj = "scaled";
    else if (species == SP_TENGU)
        adj = "feathered";
    else if (species == SP_MUMMY)
        adj = "bandage-wrapped";
    else if (species == SP_OCTOPODE)
        str = "tentacle";

    if (form == TRAN_LICH)
        adj = "bony";
    else if (form == TRAN_SHADOW)
        adj = "shadowy";

    if (!adj.empty())
        str = adj + " " + str;

    if (plural)
        str = pluralise(str);

    return str;
}

string player::unarmed_attack_name() const
{
    string text = "Nothing wielded"; // Default

    if (species == SP_FELID)
        text = "Teeth and claws";
    else if (has_usable_claws(true))
        text = "Claws";
    else if (has_usable_tentacles(true))
        text = "Tentacles";

    switch (form)
    {
    case TRAN_SPIDER:
        text = "Fangs (venom)";
        break;
    case TRAN_BLADE_HANDS:
        text = "Blade " + blade_parts(true);
        break;
    case TRAN_STATUE:
        if (has_usable_claws(true))
            text = "Stone claws";
        else if (has_usable_tentacles(true))
            text = "Stone tentacles";
        else
            text = "Stone fists";
        break;
    case TRAN_ICE_BEAST:
        text = "Ice fists (freeze)";
        break;
    case TRAN_DRAGON:
        text = "Teeth and claws";
        break;
    case TRAN_LICH:
        text += " (drain)";
        break;
    case TRAN_BAT:
    case TRAN_PIG:
    case TRAN_PORCUPINE:
        text = "Teeth";
        break;
    case TRAN_TREE:
        text = "Branches";
        break;
    case TRAN_NONE:
    case TRAN_APPENDAGE:
    default:
        break;
    }
    return text;
}

bool player::fumbles_attack(bool verbose)
{
    bool did_fumble = false;

    // Fumbling in shallow water.
    if (floundering() || liquefied_ground())
    {
        if (x_chance_in_y(4, dex()) || one_chance_in(5))
        {
            if (verbose)
                mpr("Your unstable footing causes you to fumble your attack.");
            did_fumble = true;
        }
        if (floundering())
            learned_something_new(HINT_FUMBLING_SHALLOW_WATER);
    }
    return did_fumble;
}

bool player::cannot_fight() const
{
    return false;
}

void player::attacking(actor *other, bool ranged)
{
    ASSERT(!crawl_state.game_is_arena());

    if (!other)
        return;

    if (other->is_monster())
    {
        const monster* mon = other->as_monster();
        if (!mon->friendly() && !mon->neutral())
            pet_target = mon->mindex();
    }

    if (ranged || mons_is_firewood((monster*) other))
        return;

    const int chance = pow(3, player_mutation_level(MUT_BERSERK) - 1);
    if (player_mutation_level(MUT_BERSERK) && x_chance_in_y(chance, 100))
        go_berserk(false);
}

/**
 * Check to see if Chei slows down the berserking player.
 * @param intentional If true, this was initiated by the player, and additional
 *                    messages can be printed if we can't berserk.
 * @return            True if Chei will slow the player, false otherwise.
 */
static bool _chei_prevents_berserk_haste(bool intentional)
{
    if (!you_worship(GOD_CHEIBRIADOS))
        return false;

    // Chei makes berserk not speed you up.
    // Unintentional would be forgiven "just this once" every time.
    // Intentional could work as normal, but that would require storing
    // whether you transgressed to start it -- so we just consider this
    // a part of your penance.
    if (!intentional)
    {
        simple_god_message(" protects you from inadvertent hurry.");
        return true;
    }

    did_god_conduct(DID_HASTY, 8);
    // Let's see if you've lost your religion...
    if (!you_worship(GOD_CHEIBRIADOS))
        return false;

    simple_god_message(" forces you to slow down.");
    return true;
}

/**
 * Make the player go berserk!
 * @param intentional If true, this was initiated by the player, and additional
 *                    messages can be printed if we can't berserk.
 * @param potion      If true, this was caused by the player quaffing !berserk;
 *                    and we get the same additional messages as when
 *                    intentional is true.
 * @return            True if we went berserk, false otherwise.
 */
bool player::go_berserk(bool intentional, bool potion)
{
    ASSERT(!crawl_state.game_is_arena());

    if (!you.can_go_berserk(intentional, potion))
        return false;

    if (stasis_blocks_effect(true,
                             "%s thrums violently and saps your rage.",
                             3,
                             "%s vibrates violently and saps your rage."))
    {
        return false;
    }

    if (crawl_state.game_is_hints())
        Hints.hints_berserk_counter++;

    mpr("A red film seems to cover your vision as you go berserk!");

    if (you.duration[DUR_FINESSE] > 0)
    {
        you.duration[DUR_FINESSE] = 0; // Totally incompatible.
        mpr("Your finesse ends abruptly.");
    }

    if (!_chei_prevents_berserk_haste(intentional))
        mpr("You feel yourself moving faster!");

    mpr("You feel mighty!");

    int berserk_duration = (20 + random2avg(19,2)) / 2;

    you.increase_duration(DUR_BERSERK, berserk_duration);

    calc_hp();
    set_hp(you.hp * 3 / 2);

    deflate_hp(you.hp_max, false);

    if (!you.duration[DUR_MIGHT])
        notify_stat_change(STAT_STR, 5, true, "going berserk");

    if (you.berserk_penalty != NO_BERSERK_PENALTY)
        you.berserk_penalty = 0;

    you.redraw_quiver = true; // Account for no firing.

#if TAG_MAJOR_VERSION == 34
    if (you.species == SP_LAVA_ORC)
    {
        mpr("You burn with rage!");
        // This will get sqrt'd later, so.
        you.temperature = TEMP_MAX;
    }
#endif

    if (player_equip_unrand(UNRAND_JIHAD))
        for (monster_near_iterator mi(you.pos(), LOS_NO_TRANS); mi; ++mi)
            if (mi->friendly())
                mi->go_berserk(false);

    return true;
}

bool player::can_go_berserk() const
{
    return can_go_berserk(false);
}

bool player::can_go_berserk(bool intentional, bool potion, bool quiet) const
{
    const bool verbose = (intentional || potion) && !quiet;

    if (berserk())
    {
        if (verbose)
            mpr("You're already berserk!");
        // or else you won't notice -- no message here.
        return false;
    }

    if (duration[DUR_EXHAUSTED])
    {
        if (verbose)
            mpr("You're too exhausted to go berserk.");
        // or else they won't notice -- no message here
        return false;
    }

    if (duration[DUR_DEATHS_DOOR])
    {
        if (verbose)
            mpr("Your body is effectively dead; that's not a shape for a blood rage.");
        return false;
    }

    if (beheld() && !player_equip_unrand(UNRAND_DEMON_AXE))
    {
        if (verbose)
            mpr("You are too mesmerised to rage.");
        // or else they won't notice -- no message here
        return false;
    }

    if (afraid())
    {
        if (verbose)
            mpr("You are too terrified to rage.");

        return false;
    }

#if TAG_MAJOR_VERSION == 34
    if (you.species == SP_DJINNI)
    {
        if (verbose)
            mpr("Only creatures of flesh and blood can berserk.");

        return false;
    }

#endif
    if (is_lifeless_undead())
    {
        if (verbose)
            mpr("You cannot raise a blood rage in your lifeless body.");

        return false;
    }

    // Stasis, but only for identified amulets; unided amulets will
    // trigger when the player attempts to activate berserk,
    // auto-iding at that point, but also killing the berserk and
    // wasting a turn.
    if (stasis(false))
    {
        if (verbose)
            mpr("You cannot go berserk while under stasis.");
        return false;
    }

    if (!intentional && !potion && clarity())
    {
        if (verbose)
            mpr("You're too calm and focused to rage.");
        return false;
    }

    COMPILE_CHECK(HUNGER_STARVING - 100 + BERSERK_NUTRITION < HUNGER_VERY_HUNGRY);
    if (hunger <= HUNGER_VERY_HUNGRY)
    {
        if (verbose)
            mpr("You're too hungry to go berserk.");
        return false;
    }

    return true;
}

bool player::can_jump(bool quiet) const
{
    if (duration[DUR_EXHAUSTED])
    {
        if (!quiet)
            mpr("You're too exhausted to jump.");
        return false;
    }
    if (in_water())
    {
        if (!quiet)
            mpr("You can't jump while in water.");
        return false;
    }
    if (in_lava())
    {
        if (!quiet)
            mpr("You can't jump while standing in lava.");
        return false;
    }
    if (liquefied_ground())
    {
        if (!quiet)
            mpr("You can't jump while stuck in this mess.");
        return false;
    }
    if (is_constricted())
    {
        if (!quiet)
            mpr("You can't jump while being constricted.");
        return false;
    }
    if (caught())
    {
        if (!quiet)
            mprf("You can't jump while %s.", held_status());
        return false;
    }
    if (form == TRAN_TREE || form == TRAN_WISP)
    {
        if (!quiet)
            canned_msg(MSG_PRESENT_FORM);
        return false;
    }
    return true;
}

bool player::can_jump() const
{
    return can_jump(false);
}

bool player::berserk() const
{
    return duration[DUR_BERSERK];
}

bool player::can_cling_to_walls() const
{
    return false;
}

bool player::is_web_immune() const
{
    // Spider form
    return form == TRAN_SPIDER;
}

bool player::shove(const char* feat_name)
{
    for (distance_iterator di(pos()); di; ++di)
        if (in_bounds(*di) && !actor_at(*di) && !is_feat_dangerous(grd(*di))
            && can_pass_through_feat(grd(*di)))
        {
            moveto(*di);
            if (*feat_name)
                mprf("You are pushed out of the %s.", feat_name);
            dprf("Moved to (%d, %d).", pos().x, pos().y);
            return true;
        }
    return false;
}

int player::constriction_damage() const
{
    return roll_dice(2, div_rand_round(strength(), 5));
}
