/*
* File: player.cc
* Summary: Player related functions.
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "player.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sstream>
#include <algorithm>
#include "artefact.h"
#include "branch.h"
#include "cloud.h"
#include "clua.h"
#include "coord.h"
#include "coordit.h"
#include "delay.h"
#include "dgnevent.h"
#include "directn.h"
#include "effects.h"
#include "env.h"
#include "map_knowledge.h"
#include "fight.h"
#include "food.h"
#include "godabil.h"
#include "goditem.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "item_use.h"
#include "it_use2.h"
#include "kills.h"
#include "macro.h"
#include "message.h"
#include "misc.h"
#include "mon-util.h"
#include "mutation.h"
#include "notes.h"
#include "options.h"
#include "ouch.h"
#include "output.h"
#include "quiver.h"
#include "random.h"
#include "religion.h"
#include "shopping.h"
#include "skills.h"
#include "skills2.h"
#include "species.h"
#include "spells1.h"
#include "spells3.h"
#include "spells4.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "areas.h"
#include "terrain.h"
#include "transform.h"
#include "traps.h"
#include "travel.h"
#include "tutorial.h"
#include "view.h"
#include "shout.h"
#include "viewgeom.h"
#include "xom.h"
#ifdef USE_TILE
#include "tiles.h"
#include "tiledef-player.h"
dolls_data::dolls_data()
{
parts = new int[TILEP_PART_MAX];
memset(parts, 0, TILEP_PART_MAX * sizeof(int));
}
dolls_data::dolls_data(const dolls_data& _orig)
{
parts = new int[TILEP_PART_MAX];
memcpy(parts, _orig.parts, TILEP_PART_MAX * sizeof(int));
}
const dolls_data& dolls_data::operator=(const dolls_data& other)
{
memcpy(parts, other.parts, TILEP_PART_MAX * sizeof(int));
return (*this);
}
dolls_data::~dolls_data()
{
delete[] parts;
parts = NULL;
}
#endif
std::string pronoun_you(description_level_type desc)
{
switch (desc)
{
case DESC_NONE:
return "";
case DESC_CAP_A: case DESC_CAP_THE:
return "You";
case DESC_NOCAP_A: case DESC_NOCAP_THE:
default:
return "you";
case DESC_CAP_YOUR:
return "Your";
case DESC_NOCAP_YOUR:
case DESC_NOCAP_ITS:
return "your";
}
}
// Contains functions which return various player state vars, and other
// stuff related to the player.
static void _attribute_increase();
// Use this function whenever the player enters (or lands and thus re-enters)
// a grid.
//
// stepped - normal walking moves
// allow_shift - allowed to scramble in any direction out of lava/water
// force - ignore safety checks, move must happen (traps, lava/water).
// swapping - player is swapping with a monster at (x,y)
bool move_player_to_grid( const coord_def& p, bool stepped, bool allow_shift,
bool force, bool swapping )
{
ASSERT(!crawl_state.arena);
ASSERT( in_bounds( p ) );
// assuming that entering the same square means coming from above (levitate)
const bool from_above = (you.pos() == p);
const dungeon_feature_type old_grid =
(from_above) ? DNGN_FLOOR : grd(you.pos());
const dungeon_feature_type new_grid = grd(p);
// Really must be clear.
ASSERT( you.can_pass_through_feat( new_grid ) );
// Better not be an unsubmerged monster either.
ASSERT(swapping && monster_at(p)
|| !swapping && (!monster_at(p)
|| monster_at(p)->submerged()
|| fedhas_passthrough(monster_at(p))));
// Don't prompt if force is true.
if (!force)
{
const int cloud = env.cgrid(p);
if (cloud != EMPTY_CLOUD && !you.confused())
{
const cloud_type ctype = env.cloud[ cloud ].type;
// Don't prompt if already in a cloud of the same type.
if (is_damaging_cloud(ctype, true)
&& (env.cgrid(you.pos()) == EMPTY_CLOUD
|| ctype != env.cloud[ env.cgrid(you.pos()) ].type))
{
std::string prompt = make_stringf(
"Really step into that cloud of %s?",
cloud_name(cloud).c_str());
if (!yesno(prompt.c_str(), false, 'n'))
{
canned_msg(MSG_OK);
you.turn_is_over = false;
return (false);
}
}
}
// If we're walking along, give a chance to avoid traps.
if (stepped && !you.confused())
{
if (new_grid == DNGN_UNDISCOVERED_TRAP)
{
const int skill = 4 + you.skills[SK_TRAPS_DOORS]
+ player_mutation_level(MUT_ACUTE_VISION)
- 2 * player_mutation_level(MUT_BLURRY_VISION);
if (random2(skill) > 6)
{
if (trap_def* ptrap = find_trap(p))
ptrap->reveal();
viewwindow(false);
mprf(MSGCH_WARN,
"Wait a moment, %s! Do you really want to step there?",
you.your_name.c_str());
if (!you.running.is_any_travel())
more();
exercise(SK_TRAPS_DOORS, 3);
print_stats();
you.turn_is_over = false;
return (false);
}
}
else if (new_grid == DNGN_TRAP_MAGICAL
#ifdef CLUA_BINDINGS
|| new_grid == DNGN_TRAP_MECHANICAL
&& Options.trap_prompt
#endif
|| new_grid == DNGN_TRAP_NATURAL)
{
const trap_type type = get_trap_type(p);
if (type == TRAP_ZOT)
{
if (!yes_or_no("Do you really want to step into the Zot trap"))
{
canned_msg(MSG_OK);
stop_running();
you.turn_is_over = false;
return (false);
}
}
else if (new_grid != DNGN_TRAP_MAGICAL && you.airborne())
{
// No prompt (shaft and mechanical traps ineffective, if flying)
}
else
#ifdef CLUA_BINDINGS
// Prompt for any trap where you might not have enough hp
// as defined in init.txt (see trapwalk.lua)
if (type != TRAP_SHAFT // Known shafts aren't traps
&& (new_grid != DNGN_TRAP_MECHANICAL
|| !clua.callbooleanfn(false, "ch_cross_trap",
"s", trap_name(p))))
#endif
{
std::string prompt = make_stringf(
"Really step %s that %s?",
(type == TRAP_ALARM) ? "onto" : "into",
feature_description(new_grid, type,
false, DESC_BASENAME, false).c_str());
if (!yesno(prompt.c_str(), true, 'n'))
{
canned_msg(MSG_OK);
stop_running();
you.turn_is_over = false;
return (false);
}
}
}
}
}
#ifdef USE_TILE
bool need_doll_update = false;
#endif
// Only consider terrain if player is not levitating.
if (!you.airborne())
{
bool merfolk_check = false;
if (you.species == SP_MERFOLK)
{
if (feat_is_water(new_grid))
merfolk_check = true;
// Safer water effects.
if (feat_is_water(new_grid) && !feat_is_water(old_grid))
{
// Check for fatal stat loss due to transforming.
// Also handles the warning message.
if (!merfolk_change_is_safe())
{
stop_running();
you.turn_is_over = false;
return (false);
}
if (stepped)
mpr("Your legs become a tail as you enter the water.");
else
mpr("Your legs become a tail as you dive into the water.");
merfolk_start_swimming();
#ifdef USE_TILE
need_doll_update = true;
#endif
}
else if (!feat_is_water(new_grid) && feat_is_water(old_grid)
&& !is_feat_dangerous(new_grid))
{
unmeld_one_equip(EQ_BOOTS);
you.redraw_evasion = true;
#ifdef USE_TILE
need_doll_update = true;
#endif
}
}
if (!merfolk_check)
{
// XXX: at some point we're going to need to fix the swimming
// code to handle burden states.
if (is_feat_dangerous(new_grid))
{
// Lava and dangerous deep water (ie not merfolk).
const coord_def entry = (stepped) ? you.pos() : p;
if (stepped && !force && !you.confused())
{
canned_msg(MSG_UNTHINKING_ACT);
return (false);
}
// Have to move now so fall_into_a_pool will work.
you.moveto(p);
viewwindow(false);
// If true, we were shifted and so we're done.
if (fall_into_a_pool( entry, allow_shift, new_grid ))
return (true);
}
else if (new_grid == DNGN_SHALLOW_WATER && !player_likes_water())
{
if (!stepped)
noisy(8, you.pos(), "Splash!");
you.time_taken *= 13 + random2(8);
you.time_taken /= 10;
if (old_grid != DNGN_SHALLOW_WATER)
{
mprf( "You %s the shallow water.",
(stepped ? "enter" : "fall into") );
mpr("Moving in this stuff is going to be slow.");
if (you.invisible())
mpr( "... and don't expect to remain undetected." );
}
}
}
}
// Move the player to new location.
you.moveto(p);
#ifdef USE_TILE
if (need_doll_update)
init_player_doll();
#endif
viewwindow(false);
// Other Effects:
// Clouds -- do we need this? (always seems to double up with main.cc call)
// if (is_cloud( you.x_pos, you.y_pos ))
// in_a_cloud();
// Icy shield goes down over lava.
if (new_grid == DNGN_LAVA)
expose_player_to_element( BEAM_LAVA );
// Traps go off.
if (trap_def* ptrap = find_trap(you.pos()))
ptrap->trigger(you, !stepped); // blinking makes it hard to evade
command_type stair_dir = feat_stair_direction(new_grid);
if (stepped && stair_dir != CMD_NO_CMD
&& new_grid != DNGN_ENTER_SHOP
&& you.duration[DUR_REPEL_STAIRS_MOVE])
{
int pct;
if (you.duration[DUR_REPEL_STAIRS_CLIMB])
pct = 29;
else
pct = 50;
// When the effect is still strong, the chance to actually catch
// a stair is smaller. (Assuming the duration starts out at 1000.)
const int dur = std::max(0, you.duration[DUR_REPEL_STAIRS_MOVE] - 700);
pct += dur/10;
if (x_chance_in_y(pct, 100))
{
if (slide_feature_over(you.pos(), coord_def(-1, -1), false))
{
std::string stair_str =
feature_description(new_grid, NUM_TRAPS, false,
DESC_CAP_THE, false);
std::string prep = feat_preposition(new_grid, true, &you);
mprf("%s slides away as you move %s it!", stair_str.c_str(),
prep.c_str());
if (player_in_a_dangerous_place() && one_chance_in(5))
xom_is_stimulated(32);
}
}
}
return (true);
}
bool is_feat_dangerous(dungeon_feature_type grid)
{
return (!you.airborne()
&& (grid == DNGN_LAVA
|| (grid == DNGN_DEEP_WATER && !player_likes_water())));
}
bool player_in_mappable_area(void)
{
return (!testbits(env.level_flags, LFLAG_NOT_MAPPABLE)
&& !testbits(get_branch_flags(), BFLAG_NOT_MAPPABLE));
}
bool player_in_branch(int branch)
{
return (you.level_type == LEVEL_DUNGEON && you.where_are_you == branch);
}
bool player_in_hell(void)
{
// No real reason except to draw someone's attention here if they
// mess with the branch enum.
COMPILE_CHECK(BRANCH_FIRST_HELL == BRANCH_DIS, a);
COMPILE_CHECK(BRANCH_LAST_HELL == BRANCH_TARTARUS, b);
return (you.level_type == LEVEL_DUNGEON
&& you.where_are_you >= BRANCH_FIRST_HELL
&& you.where_are_you <= BRANCH_LAST_HELL
&& you.where_are_you != BRANCH_VESTIBULE_OF_HELL);
}
bool player_likes_water(bool permanently)
{
return (you.can_swim() || (!permanently && beogh_water_walk()));
}
bool player_in_bat_form()
{
return (you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT);
}
bool player_can_open_doors()
{
// Bats and pigs can't open/close doors.
return (you.attribute[ATTR_TRANSFORMATION] != TRAN_BAT
&& you.attribute[ATTR_TRANSFORMATION] != TRAN_PIG);
}
bool player_under_penance(void)
{
if (you.religion != GOD_NO_GOD)
return (you.penance[you.religion]);
else
return (false);
}
// TODO: get rid of this.
bool player_genus(genus_type which_genus, species_type species)
{
if (species == SP_UNKNOWN)
species = you.species;
return (species_genus(species) == which_genus);
}
// If transform is true, compare with current transformation instead
// of (or in addition to) underlying species.
// (See mon-data.h for species/genus use.)
bool is_player_same_species(const int mon, bool transform)
{
if (transform)
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
// Unique monsters.
case TRAN_BAT:
return (mon == MONS_GIANT_BAT);
case TRAN_ICE_BEAST:
return (mon == MONS_ICE_BEAST);
// Compare with monster *species*.
case TRAN_LICH:
return (mons_species(mon) == MONS_LICH);
// Compare with monster *genus*.
case TRAN_SPIDER:
return (mons_genus(mon) == MONS_WOLF_SPIDER);
case TRAN_DRAGON:
return (mons_genus(mon) == MONS_DRAGON); // Includes all drakes.
case TRAN_PIG:
return (mons_genus(mon) == MONS_HOG);
default:
break; // Check real (non-transformed) form.
}
}
switch (you.species)
{
case SP_HUMAN:
if (mons_genus(mon) == MONS_HUMAN)
return (true);
return (false);
case SP_CENTAUR:
if (mons_genus(mon) == MONS_CENTAUR)
return (true);
return (false);
case SP_OGRE:
if (mons_genus(mon) == MONS_OGRE)
return (true);
return (false);
case SP_TROLL:
if (mons_genus(mon) == MONS_TROLL)
return (true);
return (false);
case SP_MUMMY:
if (mons_genus(mon) == MONS_MUMMY)
return (true);
return (false);
case SP_GHOUL: // Genus would include necrophage and rotting hulk.
if (mons_species(mon) == MONS_GHOUL)
return (true);
return (false);
case SP_VAMPIRE:
if (mons_genus(mon) == MONS_VAMPIRE)
return (true);
return (false);
case SP_MINOTAUR:
if (mons_genus(mon) == MONS_MINOTAUR)
return (true);
return (false);
case SP_NAGA:
if (mons_genus(mon) == MONS_NAGA)
return (true);
return (false);
case SP_HILL_ORC:
if (mons_genus(mon) == MONS_ORC)
return (true);
return (false);
case SP_MERFOLK:
if (mons_genus(mon) == MONS_MERFOLK
|| mons_genus(mon) == MONS_MERMAID)
{
return (true);
}
return (false);
case SP_HIGH_ELF:
case SP_DEEP_ELF:
case SP_SLUDGE_ELF:
if (mons_genus(mon) == MONS_ELF)
return (true);
return (false);
case SP_RED_DRACONIAN:
case SP_WHITE_DRACONIAN:
case SP_GREEN_DRACONIAN:
case SP_YELLOW_DRACONIAN:
case SP_GREY_DRACONIAN:
case SP_BLACK_DRACONIAN:
case SP_PURPLE_DRACONIAN:
case SP_MOTTLED_DRACONIAN:
case SP_PALE_DRACONIAN:
if (mons_genus(mon) == MONS_DRACONIAN)
return (true);
return (false);
case SP_KOBOLD:
if (mons_genus(mon) == MONS_KOBOLD)
return (true);
return (false);
default: // no monster equivalent
return (false);
}
}
// Checks whether the player's current species can
// use (usually wear) a given piece of equipment.
// Note that EQ_BODY_ARMOUR and EQ_HELMET only check
// the ill-fitting variant (i.e. not caps and robes).
// If special_armour is set to true, special cases
// such as bardings, light armour and caps are
// considered. Otherwise these simply return false.
// -------------------------------------------------
bool you_can_wear(int eq, bool special_armour)
{
// These can be used by all.
if (eq == EQ_LEFT_RING || eq == EQ_RIGHT_RING || eq == EQ_AMULET
|| eq == EQ_WEAPON || eq == EQ_CLOAK)
{
return (true);
}
// Anyone can wear caps/hats and robes and at least one of buckler/shield.
if (special_armour
&& (eq == EQ_HELMET || eq == EQ_BODY_ARMOUR || eq == EQ_SHIELD))
{
return (true);
}
if (eq == EQ_BOOTS && (you.species == SP_NAGA || you.species == SP_CENTAUR))
return (special_armour);
// Of the remaining items, these races can't wear anything.
if (you.species == SP_TROLL || you.species == SP_SPRIGGAN
|| player_genus(GENPC_OGRE) || player_genus(GENPC_DRACONIAN))
{
return (false);
}
if (you.species == SP_KENKU && (eq == EQ_HELMET || eq == EQ_BOOTS))
return (false);
if (eq == EQ_HELMET && (player_mutation_level(MUT_HORNS)
|| player_mutation_level(MUT_BEAK)))
{
return (special_armour);
}
// Else, no problems.
return (true);
}
bool player_has_feet()
{
if (you.species == SP_NAGA || player_genus(GENPC_DRACONIAN))
return (false);
if (player_mutation_level(MUT_HOOVES) || player_mutation_level(MUT_TALONS))
return (false);
return (true);
}
bool player_wearing_slot(int eq)
{
if (you.equip[eq] == -1)
return (false);
return (you_tran_can_wear(you.inv[you.equip[eq]]));
}
bool you_tran_can_wear(const item_def &item)
{
switch (item.base_type)
{
case OBJ_WEAPONS:
return you_tran_can_wear(EQ_WEAPON);
case OBJ_JEWELLERY:
return you_tran_can_wear(jewellery_is_amulet(item) ? EQ_AMULET
: EQ_LEFT_RING);
case OBJ_ARMOUR:
if (item.sub_type == ARM_NAGA_BARDING)
return (you.species == SP_NAGA && you_tran_can_wear(EQ_BOOTS));
else if (item.sub_type == ARM_CENTAUR_BARDING)
return (you.species == SP_CENTAUR && you_tran_can_wear(EQ_BOOTS));
if (get_armour_slot(item) == EQ_HELMET
&& !is_hard_helmet(item)
&& (you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST))
{
return (true);
}
if (fit_armour_size(item, you.body_size()) != 0)
return (false);
return you_tran_can_wear(get_armour_slot(item), true);
default:
return (true);
}
}
bool you_tran_can_wear(int eq, bool check_mutation)
{
if (eq == EQ_NONE)
return (true);
if (eq == EQ_STAFF)
eq = EQ_WEAPON;
else if (eq >= EQ_RINGS && eq <= EQ_RINGS_PLUS2)
eq = EQ_LEFT_RING;
// Everybody can wear at least some type of armour.
if (eq == EQ_ALL_ARMOUR)
return (true);
// Not a transformation, but also temporary -> check first.
if (check_mutation)
{
if (eq == EQ_GLOVES && you.has_claws(false) >= 3)
return (false);
if (eq == EQ_BOOTS
&& (you.swimming() && you.species == SP_MERFOLK
|| player_mutation_level(MUT_HOOVES)
|| player_mutation_level(MUT_TALONS)))
{
return (false);
}
}
const int transform = you.attribute[ATTR_TRANSFORMATION];
// No further restrictions.
if (transform == TRAN_NONE || transform == TRAN_LICH)
return (true);
// Bats and pigs cannot wear anything except amulets.
if ((transform == TRAN_BAT || transform == TRAN_PIG) && eq != EQ_AMULET)
return (false);
// Everyone else can wear jewellery, at least.
if (eq == EQ_LEFT_RING || eq == EQ_RIGHT_RING || eq == EQ_AMULET)
return (true);
// These cannot use anything but jewellery.
if (transform == TRAN_SPIDER || transform == TRAN_DRAGON)
return (false);
if (transform == TRAN_BLADE_HANDS)
{
if (eq == EQ_WEAPON || eq == EQ_GLOVES || eq == EQ_SHIELD)
return (false);
return (true);
}
if (transform == TRAN_ICE_BEAST)
{
if (eq != EQ_CLOAK)
return (false);
return (true);
}
if (transform == TRAN_STATUE)
{
if (eq == EQ_WEAPON || eq == EQ_CLOAK || eq == EQ_HELMET)
return (true);
return (false);
}
return (true);
}
bool player_weapon_wielded()
{
const int wpn = you.equip[EQ_WEAPON];
if (wpn == -1)
return (false);
if (you.inv[wpn].base_type != OBJ_WEAPONS
&& you.inv[wpn].base_type != OBJ_STAVES)
{
return (false);
}
return (true);
}
// Returns false if the player is wielding a weapon inappropriate for Berserk.
bool berserk_check_wielded_weapon()
{
if (!you.weapon())
return (true);
const item_def weapon = *you.weapon();
if (weapon.is_valid() && weapon.base_type != OBJ_STAVES
&& (weapon.base_type != OBJ_WEAPONS || is_range_weapon(weapon))
|| you.attribute[ATTR_WEAPON_SWAP_INTERRUPTED])
{
std::string prompt = "Do you really want to go berserk while "
"wielding " + weapon.name(DESC_NOCAP_YOUR)
+ "? ";
if (!yesno(prompt.c_str(), true, 'n'))
{
canned_msg(MSG_OK);
return (false);
}
you.attribute[ATTR_WEAPON_SWAP_INTERRUPTED] = 0;
}
return (true);
}
// Looks in equipment "slot" to see if there is an equipped "sub_type".
// Returns number of matches (in the case of rings, both are checked)
int player_equip( equipment_type slot, int sub_type, bool calc_unid )
{
if (!you_tran_can_wear(slot))
return (0);
int ret = 0;
switch (slot)
{
case EQ_WEAPON:
// Hands can have more than just weapons.
if (you.weapon()
&& you.weapon()->base_type == OBJ_WEAPONS
&& you.weapon()->sub_type == sub_type)
{
ret++;
}
break;
case EQ_STAFF:
// Like above, but must be magical staff.
if (you.weapon()
&& you.weapon()->base_type == OBJ_STAVES
&& you.weapon()->sub_type == sub_type
&& (calc_unid || item_type_known(*you.weapon())))
{
ret++;
}
break;
case EQ_RINGS:
if (you.equip[EQ_LEFT_RING] != -1
&& you.inv[you.equip[EQ_LEFT_RING]].sub_type == sub_type
&& (calc_unid
|| item_type_known( you.inv[you.equip[EQ_LEFT_RING]] )))
{
ret++;
}
if (you.equip[EQ_RIGHT_RING] != -1
&& you.inv[you.equip[EQ_RIGHT_RING]].sub_type == sub_type
&& (calc_unid
|| item_type_known( you.inv[you.equip[EQ_RIGHT_RING]] )))
{
ret++;
}
break;
case EQ_RINGS_PLUS:
if (you.equip[EQ_LEFT_RING] != -1
&& you.inv[you.equip[EQ_LEFT_RING]].sub_type == sub_type)
{
ret += you.inv[you.equip[EQ_LEFT_RING]].plus;
}
if (you.equip[EQ_RIGHT_RING] != -1
&& you.inv[you.equip[EQ_RIGHT_RING]].sub_type == sub_type)
{
ret += you.inv[you.equip[EQ_RIGHT_RING]].plus;
}
break;
case EQ_RINGS_PLUS2:
if (you.equip[EQ_LEFT_RING] != -1
&& you.inv[you.equip[EQ_LEFT_RING]].sub_type == sub_type)
{
ret += you.inv[you.equip[EQ_LEFT_RING]].plus2;
}
if (you.equip[EQ_RIGHT_RING] != -1
&& you.inv[you.equip[EQ_RIGHT_RING]].sub_type == sub_type)
{
ret += you.inv[you.equip[EQ_RIGHT_RING]].plus2;
}
break;
case EQ_ALL_ARMOUR:
// Doesn't make much sense here... be specific. -- bwr
break;
default:
if (you.equip[slot] != -1
&& you.inv[you.equip[slot]].sub_type == sub_type
&& (calc_unid || item_type_known(you.inv[you.equip[slot]])))
{
ret++;
}
break;
}
return (ret);
}
// Looks in equipment "slot" to see if equipped item has "special" ego-type
// Returns number of matches (jewellery returns zero -- no ego type).
// [ds] There's no equivalent of calc_unid or req_id because as of now, weapons
// and armour type-id on wield/wear.
int player_equip_ego_type( int slot, int special )
{
if (!you_tran_can_wear(slot))
return (0);
int ret = 0;
int wpn;
switch (slot)
{
case EQ_WEAPON:
// Hands can have more than just weapons.
wpn = you.equip[EQ_WEAPON];
if (wpn != -1
&& you.inv[wpn].base_type == OBJ_WEAPONS
&& get_weapon_brand( you.inv[wpn] ) == special)
{
ret++;
}
break;
case EQ_LEFT_RING:
case EQ_RIGHT_RING:
case EQ_AMULET:
case EQ_STAFF:
case EQ_RINGS:
case EQ_RINGS_PLUS:
case EQ_RINGS_PLUS2:
// no ego types for these slots
break;
case EQ_ALL_ARMOUR:
// Check all armour slots:
for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++)
{
// ... but skip ones you can't currently use!
if (!you_tran_can_wear(i))
continue;
if (you.equip[i] != -1
&& get_armour_ego_type( you.inv[you.equip[i]] ) == special)
{
ret++;
}
}
break;
default:
// Check a specific armour slot for an ego type:
if (you.equip[slot] != -1
&& get_armour_ego_type( you.inv[you.equip[slot]] ) == special)
{
ret++;
}
break;
}
return (ret);
}
// Return's true if the indicated unrandart is equipped
// [ds] There's no equivalent of calc_unid or req_id because as of now, weapons
// and armour type-id on wield/wear.
bool player_equip_unrand(int unrand_index)
{
unrandart_entry* entry = get_unrand_entry(unrand_index);
equipment_type slot = get_item_slot(entry->base_type,
entry->sub_type);
if (!you_tran_can_wear(slot))
return (false);
int it;
switch (slot)
{
case EQ_WEAPON:
// Hands can have more than just weapons.
it = you.equip[EQ_WEAPON];
if (it != -1
&& you.inv[it].base_type == OBJ_WEAPONS
&& is_unrandom_artefact(you.inv[it])
&& you.inv[it].special == unrand_index)
{
return (true);
}
break;
case EQ_RINGS:
it = you.equip[EQ_LEFT_RING];
if (it != -1
&& is_unrandom_artefact(you.inv[it])
&& you.inv[it].special == unrand_index)
{
return (true);
}
it = you.equip[EQ_RIGHT_RING];
if (it != -1
&& is_unrandom_artefact(you.inv[it])
&& you.inv[it].special == unrand_index)
{
return (true);
}
break;
case EQ_NONE:
case EQ_STAFF:
case EQ_LEFT_RING:
case EQ_RIGHT_RING:
case EQ_RINGS_PLUS:
case EQ_RINGS_PLUS2:
case EQ_ALL_ARMOUR:
// no unrandarts for these slots.
break;
default:
// Check a specific slot.
it = you.equip[slot];
if (it != -1
&& is_unrandom_artefact(you.inv[it])
&& you.inv[it].special == unrand_index)
{
return (true);
}
break;
}
return (false);
}
// Given an adjacent monster, returns true if the player can hit it (the
// monster should not be submerged, or be submerged in shallow water if
// the player has a polearm).
bool player_can_hit_monster(const monsters *mon)
{
if (!mon->submerged())
return (true);
if (grd(mon->pos()) != DNGN_SHALLOW_WATER)
return (false);
const item_def *weapon = you.weapon();
return (weapon && weapon_skill(*weapon) == SK_POLEARMS);
}
int player_teleport(bool calc_unid)
{
ASSERT(!crawl_state.arena);
int tp = 0;
// rings
tp += 8 * player_equip( EQ_RINGS, RING_TELEPORTATION, calc_unid );
// mutations
tp += player_mutation_level(MUT_TELEPORT) * 3;
// randart weapons only
if (you.weapon()
&& you.weapon()->base_type == OBJ_WEAPONS
&& is_artefact(*you.weapon()))
{
tp += scan_artefacts(ARTP_CAUSE_TELEPORTATION, calc_unid);
}
return tp;
}
int player_regen()
{
int rr = you.hp_max / 3;
if (rr > 20)
rr = 20 + ((rr - 20) / 2);
// Rings.
rr += 40 * player_equip(EQ_RINGS, RING_REGENERATION);
// Spell.
if (you.duration[DUR_REGENERATION]
&& !you.attribute[ATTR_DIVINE_REGENERATION])
{
rr += 100;
}
// Troll leather (except for trolls).
if (player_equip(EQ_BODY_ARMOUR, ARM_TROLL_LEATHER_ARMOUR)
&& you.species != SP_TROLL)
{
rr += 30;
}
// Fast heal mutation.
rr += player_mutation_level(MUT_REGENERATION) * 20;
// Before applying other effects, make sure that there's something
// to heal.
rr = std::max(1, rr);
// Healing depending on satiation.
// The better-fed you are, the faster you heal.
if (you.species == SP_VAMPIRE)
{
if (you.hunger_state == HS_STARVING)
// No regeneration for starving vampires.
rr = 0;
else if (you.hunger_state == HS_ENGORGED)
// More bonus regeneration for engorged vampires.
rr += 20;
else if (you.hunger_state < HS_SATIATED)
// Halved regeneration for hungry vampires.
rr /= 2;
else if (you.hunger_state >= HS_FULL)
// Bonus regeneration for full vampires.
rr += 10;
}
// Slow heal mutation. Each level reduces your natural healing by
// one third.
if (player_mutation_level(MUT_SLOW_HEALING) > 0)
{
rr *= 3 - player_mutation_level(MUT_SLOW_HEALING);
rr /= 3;
}
// Trog's Hand. This circumvents the slow healing effect.
if (you.attribute[ATTR_DIVINE_REGENERATION])
rr += 100;
return (rr);
}
int player_hunger_rate(void)
{
int hunger = 3;
if (player_in_bat_form())
return 1;
if (you.species == SP_TROLL)
hunger += 3; // in addition to the +3 for fast metabolism
if (you.duration[DUR_REGENERATION] && you.hp < you.hp_max)
hunger += 4;
// If Cheibriados has slowed your life processes, you will hunger less.
if (GOD_CHEIBRIADOS == you.religion
&& you.piety >= piety_breakpoint(0))
hunger--;
// Moved here from main.cc... maintaining the >= 40 behaviour.
if (you.hunger >= 40)
{
if (you.duration[DUR_INVIS] > 0)
hunger += 5;
// Berserk has its own food penalty -- excluding berserk haste.
// Doubling the hunger cost for haste so that the per turn hunger
// is consistent now that a hasted turn causes 50% the normal hunger
// -cao
if (you.duration[DUR_HASTE] > 0 && !you.berserk())
hunger += 10;
}
if (you.species == SP_VAMPIRE)
{
switch (you.hunger_state)
{
case HS_STARVING:
case HS_NEAR_STARVING:
hunger -= 3;
break;
case HS_VERY_HUNGRY:
hunger -= 2;
break;
case HS_HUNGRY:
hunger--;
break;
case HS_SATIATED:
break;
case HS_FULL:
hunger++;
break;
case HS_VERY_FULL:
hunger += 2;
break;
case HS_ENGORGED:
hunger += 3;
}
}
else
{
hunger += player_mutation_level(MUT_FAST_METABOLISM);
if (player_mutation_level(MUT_SLOW_METABOLISM) > 2)
hunger -= 2;
else if (player_mutation_level(MUT_SLOW_METABOLISM) > 0)
hunger--;
}
// rings
if (you.hp < you.hp_max)
hunger += 3 * player_equip( EQ_RINGS, RING_REGENERATION );
hunger += 4 * player_equip( EQ_RINGS, RING_HUNGER );
hunger -= 2 * player_equip( EQ_RINGS, RING_SUSTENANCE );
// weapon ego types
if (you.species != SP_VAMPIRE)
{
if (player_equip_ego_type( EQ_WEAPON, SPWPN_VAMPIRICISM ))
hunger += 6;
}
else
{
if (player_equip_ego_type( EQ_WEAPON, SPWPN_VAMPIRICISM ))
hunger += 1;
}
// troll leather armour
if (you.species != SP_TROLL && you.hp < you.hp_max)
if (player_equip( EQ_BODY_ARMOUR, ARM_TROLL_LEATHER_ARMOUR ))
hunger += coinflip() ? 2 : 1;
// randarts
hunger += scan_artefacts(ARTP_METABOLISM);
// burden
hunger += you.burden_state;
if (hunger < 1)
hunger = 1;
return (hunger);
}
int player_spell_levels(void)
{
int sl = (you.experience_level - 1) + (you.skills[SK_SPELLCASTING] * 2);
bool fireball = false;
bool delayed_fireball = false;
if (sl > 99)
sl = 99;
for (int i = 0; i < 25; i++)
{
if (you.spells[i] == SPELL_FIREBALL)
fireball = true;
else if (you.spells[i] == SPELL_DELAYED_FIREBALL)
delayed_fireball = true;
if (you.spells[i] != SPELL_NO_SPELL)
sl -= spell_difficulty(you.spells[i]);
}
// Fireball is free for characters with delayed fireball
if (fireball && delayed_fireball)
sl += spell_difficulty( SPELL_FIREBALL );
// Note: This can happen because of level drain. Maybe we should
// force random spells out when that happens. -- bwr
if (sl < 0)
sl = 0;
return (sl);
}
bool player_likes_chunks(bool permanently)
{
return (player_mutation_level(MUT_GOURMAND) > 0
|| player_mutation_level(MUT_CARNIVOROUS) > 0
|| (!permanently && wearing_amulet(AMU_THE_GOURMAND)));
}
// If temp is set to false, temporary sources or resistance won't be counted.
int player_res_fire(bool calc_unid, bool temp, bool items)
{
int rf = 0;
if (items)
{
// rings of fire resistance/fire
rf += player_equip( EQ_RINGS, RING_PROTECTION_FROM_FIRE, calc_unid );
rf += player_equip( EQ_RINGS, RING_FIRE, calc_unid );
// rings of ice
rf -= player_equip( EQ_RINGS, RING_ICE, calc_unid );
// Staves
rf += player_equip( EQ_STAFF, STAFF_FIRE, calc_unid );
// body armour:
rf += 2 * player_equip( EQ_BODY_ARMOUR, ARM_DRAGON_ARMOUR );
rf += player_equip( EQ_BODY_ARMOUR, ARM_GOLD_DRAGON_ARMOUR );
rf -= player_equip( EQ_BODY_ARMOUR, ARM_ICE_DRAGON_ARMOUR );
// ego armours
rf += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_FIRE_RESISTANCE );
rf += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_RESISTANCE );
// randart weapons:
rf += scan_artefacts(ARTP_FIRE, calc_unid);
}
// species:
if (you.species == SP_MUMMY)
rf--;
// mutations:
rf += player_mutation_level(MUT_HEAT_RESISTANCE);
// spells:
if (temp)
{
if (you.duration[DUR_RESIST_FIRE] > 0)
rf++;
if (you.duration[DUR_FIRE_SHIELD])
rf += 2;
// transformations:
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_ICE_BEAST:
rf--;
break;
case TRAN_DRAGON:
rf += 2;
break;
}
}
if (rf < -3)
rf = -3;
else if (rf > 3)
rf = 3;
return (rf);
}
int player_res_steam(bool calc_unid, bool temp, bool items)
{
int res = 0;
if (you.species == SP_PALE_DRACONIAN && you.experience_level > 5)
res += 2;
if (items && player_equip(EQ_BODY_ARMOUR, ARM_STEAM_DRAGON_ARMOUR))
res += 2;
return (res + player_res_fire(calc_unid, temp, items) / 2);
}
int player_res_cold(bool calc_unid, bool temp, bool items)
{
int rc = 0;
if (temp)
{
// spells:
if (you.duration[DUR_RESIST_COLD] > 0)
rc++;
if (you.duration[DUR_FIRE_SHIELD])
rc -= 2;
// transformations:
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_ICE_BEAST:
rc += 3;
break;
case TRAN_DRAGON:
rc--;
break;
case TRAN_LICH:
rc++;
break;
}
if (you.species == SP_VAMPIRE)
{
if (you.hunger_state <= HS_NEAR_STARVING)
rc += 2;
else if (you.hunger_state < HS_SATIATED)
rc++;
}
}
if (items)
{
// rings of cold resistance/ice
rc += player_equip( EQ_RINGS, RING_PROTECTION_FROM_COLD, calc_unid );
rc += player_equip( EQ_RINGS, RING_ICE, calc_unid );
// rings of fire
rc -= player_equip( EQ_RINGS, RING_FIRE, calc_unid );
// Staves
rc += player_equip( EQ_STAFF, STAFF_COLD, calc_unid );
// body armour:
rc += 2 * player_equip( EQ_BODY_ARMOUR, ARM_ICE_DRAGON_ARMOUR );
rc += player_equip( EQ_BODY_ARMOUR, ARM_GOLD_DRAGON_ARMOUR );
rc -= player_equip( EQ_BODY_ARMOUR, ARM_DRAGON_ARMOUR );
// ego armours
rc += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_COLD_RESISTANCE );
rc += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_RESISTANCE );
// randart weapons:
rc += scan_artefacts(ARTP_COLD, calc_unid);
}
// mutations:
rc += player_mutation_level(MUT_COLD_RESISTANCE);
if (player_mutation_level(MUT_SHAGGY_FUR) == 3)
rc++;
if (rc < -3)
rc = -3;
else if (rc > 3)
rc = 3;
return (rc);
}
int player_res_acid(bool calc_unid, bool items)
{
int res = 0;
if (!transform_changed_physiology())
{
if (you.species == SP_YELLOW_DRACONIAN && you.experience_level > 6)
res += 2;
res += player_mutation_level(MUT_YELLOW_SCALES) * 2 / 3;
}
if (items)
{
if (wearing_amulet(AMU_RESIST_CORROSION, calc_unid))
res++;
if (player_equip_ego_type(EQ_CLOAK, SPARM_PRESERVATION))
res++;
}
if (you.religion == GOD_JIYVA && x_chance_in_y(you.piety, MAX_PIETY))
res++;
return (res);
}
// Returns a factor X such that post-resistance acid damage can be calculated
// as pre_resist_damage * X / 100.
int player_acid_resist_factor()
{
int res = player_res_acid();
int factor = 100;
if (res == 1)
factor = 50;
else if (res == 2)
factor = 34;
else if (res > 2)
{
factor = 30;
while (res-- > 2 && factor >= 20)
factor = factor * 90 / 100;
}
return (factor);
}
int player_res_electricity(bool calc_unid, bool temp, bool items)
{
int re = 0;
if (temp)
{
if (you.duration[DUR_INSULATION])
re++;
// transformations:
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_STATUE)
re += 1;
if (you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION])
re = 3;
else if (re > 1)
re = 1;
}
if (items)
{
// staff
re += player_equip( EQ_STAFF, STAFF_AIR, calc_unid );
// body armour:
re += player_equip( EQ_BODY_ARMOUR, ARM_STORM_DRAGON_ARMOUR );
// randart weapons:
re += scan_artefacts(ARTP_ELECTRICITY, calc_unid);
}
// species:
if (you.species == SP_BLACK_DRACONIAN && you.experience_level > 17)
re++;
// mutations:
if (player_mutation_level(MUT_SHOCK_RESISTANCE))
re++;
return (re);
}
bool player_control_teleport(bool calc_unid, bool temp, bool items)
{
return ((temp && you.duration[DUR_CONTROL_TELEPORT])
|| (items
&& player_equip(EQ_RINGS, RING_TELEPORT_CONTROL, calc_unid))
|| player_mutation_level(MUT_TELEPORT_CONTROL));
}
int player_res_torment(bool, bool temp)
{
return (player_mutation_level(MUT_TORMENT_RESISTANCE)
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_LICH
|| you.species == SP_VAMPIRE && you.hunger_state == HS_STARVING
|| temp && (20 * player_mutation_level(MUT_STOCHASTIC_TORMENT_RESISTANCE) >= random2(100)));
}
// Funny that no races are susceptible to poisons. {dlb}
// If temp is set to false, temporary sources or resistance won't be counted.
int player_res_poison(bool calc_unid, bool temp, bool items)
{
int rp = 0;
if (items)
{
// rings of poison resistance
rp += player_equip( EQ_RINGS, RING_POISON_RESISTANCE, calc_unid );
// Staves
rp += player_equip( EQ_STAFF, STAFF_POISON, calc_unid );
// ego armour:
rp += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_POISON_RESISTANCE );
// body armour:
rp += player_equip( EQ_BODY_ARMOUR, ARM_GOLD_DRAGON_ARMOUR );
rp += player_equip( EQ_BODY_ARMOUR, ARM_SWAMP_DRAGON_ARMOUR );
// randart weapons:
rp += scan_artefacts(ARTP_POISON, calc_unid);
}
// mutations:
rp += player_mutation_level(MUT_POISON_RESISTANCE);
// Only thirsty vampires are naturally poison resistant.
if (you.species == SP_VAMPIRE && you.hunger_state < HS_SATIATED)
rp++;
if (temp)
{
// spells:
if (you.duration[DUR_RESIST_POISON] > 0)
rp++;
// transformations:
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_LICH:
case TRAN_ICE_BEAST:
case TRAN_STATUE:
case TRAN_DRAGON:
rp++;
break;
}
}
if (rp > 1)
rp = 1;
return (rp);
}
int player_res_sticky_flame(bool calc_unid, bool temp, bool items)
{
int rsf = 0;
if (you.species == SP_MOTTLED_DRACONIAN && you.experience_level > 5)
rsf++;
if (items && player_equip(EQ_BODY_ARMOUR, ARM_MOTTLED_DRAGON_ARMOUR))
rsf++;
if (rsf > 1)
rsf = 1;
return (rsf);
}
int player_spec_death()
{
int sd = 0;
// Staves
sd += player_equip( EQ_STAFF, STAFF_DEATH );
// body armour:
if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_ARCHMAGI ))
sd++;
// species:
if (you.species == SP_MUMMY)
{
if (you.experience_level >= 13)
sd++;
if (you.experience_level >= 26)
sd++;
}
// transformations:
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_LICH)
sd++;
return sd;
}
int player_spec_holy()
{
//if ( you.char_class == JOB_PRIEST || you.char_class == JOB_PALADIN )
// return 1;
return 0;
}
int player_spec_fire()
{
int sf = 0;
// staves:
sf += player_equip( EQ_STAFF, STAFF_FIRE );
// rings of fire:
sf += player_equip( EQ_RINGS, RING_FIRE );
if (you.duration[DUR_FIRE_SHIELD])
sf++;
// if it's raining... {due}
if (in_what_cloud(CLOUD_RAIN))
sf--;
return sf;
}
int player_spec_cold()
{
int sc = 0;
// staves:
sc += player_equip( EQ_STAFF, STAFF_COLD );
// rings of ice:
sc += player_equip( EQ_RINGS, RING_ICE );
return sc;
}
int player_spec_earth()
{
int se = 0;
// Staves
se += player_equip( EQ_STAFF, STAFF_EARTH );
return se;
}
int player_spec_air()
{
int sa = 0;
// Staves
sa += player_equip( EQ_STAFF, STAFF_AIR );
return sa;
}
int player_spec_conj()
{
int sc = 0;
// Staves
sc += player_equip( EQ_STAFF, STAFF_CONJURATION );
// armour of the Archmagi
if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_ARCHMAGI ))
sc++;
return sc;
}
int player_spec_ench()
{
int se = 0;
// Staves
se += player_equip( EQ_STAFF, STAFF_ENCHANTMENT );
// armour of the Archmagi
if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_ARCHMAGI ))
se++;
return se;
}
int player_spec_summ()
{
int ss = 0;
// Staves
ss += player_equip( EQ_STAFF, STAFF_SUMMONING );
// armour of the Archmagi
if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_ARCHMAGI ))
ss++;
return ss;
}
int player_spec_poison()
{
int sp = 0;
// Staves
sp += player_equip( EQ_STAFF, STAFF_POISON );
if (player_equip_unrand(UNRAND_OLGREB))
sp++;
return sp;
}
int player_energy()
{
int pe = 0;
// Staves
pe += player_equip( EQ_STAFF, STAFF_ENERGY );
return pe;
}
// If temp is set to false, temporary sources of resistance won't be
// counted.
int player_prot_life(bool calc_unid, bool temp, bool items)
{
int pl = 0;
// Hunger is temporary, true, but that's something you can control,
// especially as life protection only increases the hungrier you
// get.
if (you.species == SP_VAMPIRE)
{
switch (you.hunger_state)
{
case HS_STARVING:
case HS_NEAR_STARVING:
pl = 3;
break;
case HS_VERY_HUNGRY:
case HS_HUNGRY:
pl = 2;
break;
case HS_SATIATED:
pl = 1;
break;
default:
break;
}
}
// Same here. Your piety status, and, hence, TSO's protection, is
// something you can more or less control.
if (you.religion == GOD_SHINING_ONE && you.piety > pl * 50)
pl = you.piety / 50;
if (temp)
{
// Now, transformations could stop at any time.
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_STATUE:
pl++;
break;
case TRAN_LICH:
pl += 3;
break;
default:
break;
}
}
if (items)
{
if (wearing_amulet(AMU_WARDING, calc_unid))
pl++;
// rings
pl += player_equip(EQ_RINGS, RING_LIFE_PROTECTION, calc_unid);
// armour (checks body armour only)
pl += player_equip_ego_type(EQ_ALL_ARMOUR, SPARM_POSITIVE_ENERGY);
// randart wpns
pl += scan_artefacts(ARTP_NEGATIVE_ENERGY, calc_unid);
}
// undead/demonic power
pl += player_mutation_level(MUT_NEGATIVE_ENERGY_RESISTANCE);
pl = std::min(3, pl);
return (pl);
}
// New player movement speed system... allows for a bit more than
// "player runs fast" and "player walks slow" in that the speed is
// actually calculated (allowing for centaurs to get a bonus from
// swiftness and other such things). Levels of the mutation now
// also have meaning (before they all just meant fast). Most of
// this isn't as fast as it used to be (6 for having anything), but
// even a slight speed advantage is very good... and we certainly don't
// want to go past 6 (see below). -- bwr
int player_movement_speed(void)
{
int mv = 10;
if (you.swimming())
{
// This is swimming... so it doesn't make sense to really
// apply the other things (the mutation is "cover ground",
// swiftness is an air spell, can't wear boots, can't be
// transformed).
mv = 6;
}
else
{
// transformations
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER)
mv = 8;
else if (player_in_bat_form())
mv = 5; // but allowed minimum is six
else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_PIG)
mv = 7;
// armour
if (player_equip_ego_type( EQ_BOOTS, SPARM_RUNNING ))
mv -= 2;
// ponderous brand
mv += 2 * player_equip_ego_type(EQ_ALL_ARMOUR, SPARM_PONDEROUSNESS);
// In the air, can fly fast (should be lightly burdened).
if (you.light_flight())
mv--;
// Swiftness is an Air spell, it doesn't work in water, but
// flying players will move faster.
if (you.duration[DUR_SWIFTNESS] > 0 && !you.in_water())
mv -= (you.flight_mode() == FL_FLY ? 4 : 2);
// Mutations: -2, -3, -4, unless innate and shapechanged.
if (player_mutation_level(MUT_FAST) > 0
&& (!you.demon_pow[MUT_FAST] || !player_is_shapechanged()) )
{
mv -= (player_mutation_level(MUT_FAST) + 1);
}
// Burden
if (you.burden_state == BS_ENCUMBERED)
mv += 1;
else if (you.burden_state == BS_OVERLOADED)
mv += 3;
}
// We'll use the old value of six as a minimum, with haste this could
// end up as a speed of three, which is about as fast as we want
// the player to be able to go (since 3 is 3.33x as fast and 2 is 5x,
// which is a bit of a jump, and a bit too fast) -- bwr
if (mv < 6)
mv = 6;
// Nagas move slowly:
if (you.species == SP_NAGA && !player_is_shapechanged())
{
mv *= 14;
mv /= 10;
}
return (mv);
}
// This function differs from the above in that it's used to set the
// initial time_taken value for the turn. Everything else (movement,
// spellcasting, combat) applies a ratio to this value.
int player_speed(void)
{
int ps = 10;
if (you.duration[DUR_SLOW])
ps *= 2;
if (you.duration[DUR_HASTE])
ps /= 2;
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_STATUE:
ps *= 15;
ps /= 10;
break;
default:
break;
}
return ps;
}
static int _player_armour_racial_bonus(const item_def& item)
{
if (item.base_type != OBJ_ARMOUR)
return 0;
int racial_bonus = 0;
const unsigned long armour_race = get_equip_race(item);
// get the armour race value that corresponds to the character's race:
const unsigned long racial_type
= ((player_genus(GENPC_DWARVEN)) ? ISFLAG_DWARVEN :
(player_genus(GENPC_ELVEN)) ? ISFLAG_ELVEN :
(you.species == SP_HILL_ORC) ? ISFLAG_ORCISH
: 0);
// Dwarven armour is universally good -- bwr
if (armour_race == ISFLAG_DWARVEN)
racial_bonus += 4;
if (racial_type && armour_race == racial_type)
{
// Elven armour is light, but still gives one level to elves.
// Orcish and Dwarven armour are worth +2 to the correct
// species, plus the plus that anyone gets with dwarven armour.
// -- bwr
if (racial_type == ISFLAG_ELVEN)
racial_bonus += 2;
else
racial_bonus += 4;
// an additional bonus for Beogh worshippers
if (you.religion == GOD_BEOGH && !player_under_penance())
{
if (you.piety >= 185)
racial_bonus += racial_bonus * 9 / 4;
else if (you.piety >= 160)
racial_bonus += racial_bonus * 2;
else if (you.piety >= 120)
racial_bonus += racial_bonus * 7 / 4;
else if (you.piety >= 80)
racial_bonus += racial_bonus * 5 / 4;
else if (you.piety >= 40)
racial_bonus += racial_bonus * 3 / 4;
else
racial_bonus += racial_bonus / 4;
}
}
return racial_bonus;
}
bool is_light_armour( const item_def &item )
{
if (get_equip_race(item) == ISFLAG_ELVEN)
return (true);
switch (item.sub_type)
{
case ARM_ROBE:
case ARM_ANIMAL_SKIN:
case ARM_LEATHER_ARMOUR:
case ARM_TROLL_HIDE:
case ARM_TROLL_LEATHER_ARMOUR:
case ARM_STEAM_DRAGON_HIDE:
case ARM_STEAM_DRAGON_ARMOUR:
case ARM_MOTTLED_DRAGON_HIDE:
case ARM_MOTTLED_DRAGON_ARMOUR:
return (true);
default:
return (false);
}
}
bool player_light_armour(bool with_skill)
{
if (!player_wearing_slot(EQ_BODY_ARMOUR))
return (true);
// We're wearing some kind of body armour and it's not melded.
const int arm = you.equip[EQ_BODY_ARMOUR];
if (with_skill
&& property(you.inv[arm], PARM_EVASION) + you.skills[SK_ARMOUR]/3 >= 0)
{
return (true);
}
return (is_light_armour(you.inv[arm]));
}
//
// This function returns true if the player has a radically different
// shape... minor changes like blade hands don't count, also note
// that lich transformation doesn't change the character's shape
// (so we end up with Naga-lichs, Spiggan-lichs, Minotaur-lichs)
// it just makes the character undead (with the benefits that implies). --bwr
//
bool player_is_shapechanged(void)
{
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_NONE
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_LICH)
{
return (false);
}
return (true);
}
// New and improved 4.1 evasion model, courtesy Brent Ross.
int player_evasion(ev_ignore_type evit)
{
// XXX: you.body_size() implementations are incomplete, fix.
const size_type size = you.body_size(PSIZE_BODY);
const size_type torso = you.body_size(PSIZE_TORSO);
const int size_factor = SIZE_MEDIUM - size;
int ev = 10 + 2 * size_factor;
// Repulsion fields and size are all that matters when paralysed.
if (you.cannot_move() && !(evit & EV_IGNORE_HELPLESS))
{
ev = 2 + size_factor;
if (player_mutation_level(MUT_REPULSION_FIELD) > 0)
ev += (player_mutation_level(MUT_REPULSION_FIELD) * 2) - 1;
return ((ev < 1) ? 1 : ev);
}
// Calculate the base bonus here, but it may be reduced by heavy
// armour below.
int dodge_bonus = (you.skills[SK_DODGING] * you.dex + 7)
/ (20 - size_factor);
// Limit on bonus from dodging:
const int max_bonus = (you.skills[SK_DODGING] * (7 + size_factor)) / 9;
if (dodge_bonus > max_bonus)
dodge_bonus = max_bonus;
// Some lesser armours have small penalties now (shields, barding).
for (int i = EQ_CLOAK; i < EQ_BODY_ARMOUR; i++)
{
if (!player_wearing_slot(i))
continue;
int pen = property( you.inv[ you.equip[i] ], PARM_EVASION );
// Reducing penalty of larger shields for larger characters.
if (i == EQ_SHIELD && torso > SIZE_MEDIUM)
pen += (torso - SIZE_MEDIUM);
if (pen < 0)
ev += pen;
}
// Handle main body armour penalty.
if (you.equip[EQ_BODY_ARMOUR] != -1)
{
// XXX: magnify arm_penalty for weak characters?
// Remember: arm_penalty and ev_change are negative.
int arm_penalty = property( you.inv[ you.equip[EQ_BODY_ARMOUR] ],
PARM_EVASION );
// The very large races take a penalty to AC for not being able
// to fully cover, and in compensation we give back some freedom
// of movement here. Likewise, the very small are extra encumbered
// by armour (which partially counteracts their size bonus above).
if (size < SIZE_SMALL || size > SIZE_LARGE)
{
arm_penalty -= ((size - SIZE_MEDIUM) * arm_penalty) / 4;
if (arm_penalty > 0)
arm_penalty = 0;
}
int ev_change = arm_penalty;
ev_change += (you.skills[SK_ARMOUR] * you.strength) / 60;
if (ev_change > arm_penalty / 2)
ev_change = arm_penalty / 2;
ev += ev_change;
// This reduces dodging ability in heavy armour.
if (!player_light_armour())
dodge_bonus += (arm_penalty * 30 + 15) / you.strength;
}
if (dodge_bonus > 0) // always a bonus
ev += dodge_bonus;
if (you.duration[DUR_AGILITY])
ev += 5;
if (you.duration[DUR_PHASE_SHIFT] && !(evit & EV_IGNORE_PHASESHIFT))
ev += 8;
if (you.duration[DUR_STONEMAIL])
ev -= 2;
ev += player_equip( EQ_RINGS_PLUS, RING_EVASION );
if (player_equip_ego_type( EQ_WEAPON, SPWPN_EVASION ))
ev += 5;
ev += scan_artefacts( ARTP_EVASION );
// ponderous ev mod
ev -= 2 * player_equip_ego_type(EQ_ALL_ARMOUR, SPARM_PONDEROUSNESS);
if (player_mutation_level(MUT_REPULSION_FIELD) > 0)
ev += (player_mutation_level(MUT_REPULSION_FIELD) * 2) - 1;
// transformation penalties/bonuses not covered by size alone:
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_STATUE:
ev -= 5; // stiff
break;
default:
break;
}
switch (you.species)
{
case SP_MERFOLK:
// Merfolk get an evasion bonus in water.
if (you.swimming())
{
// ... though a bit less so if swimming in heavy armour.
int factor = 4;
int min_bonus = 2;
if (grd(you.pos()) == DNGN_DEEP_WATER && !player_light_armour())
{
factor = 6;
min_bonus = 1;
}
const int ev_bonus = std::min(9, std::max(min_bonus, ev / factor));
ev += ev_bonus;
}
break;
case SP_KENKU:
// Flying Kenku get an evasion bonus.
if (you.flight_mode() == FL_FLY)
{
const int ev_bonus = std::min(9, std::max(1, ev / 5));
ev += ev_bonus;
}
break;
default:
break;
}
return (ev);
}
// Obsolete evasion calc.
#if 0
int old_player_evasion(void)
{
int ev = 10;
int armour_ev_penalty;
if (you.equip[EQ_BODY_ARMOUR] == -1)
armour_ev_penalty = 0;
else
{
armour_ev_penalty = property( you.inv[you.equip[EQ_BODY_ARMOUR]],
PARM_EVASION );
}
// We return 2 here to give the player some chance of not being hit,
// repulsion fields still work while paralysed
if (you.duration[DUR_PARALYSIS])
return (2 + player_mutation_level(MUT_REPULSION_FIELD) * 2);
if (you.species == SP_CENTAUR)
ev -= 3;
if (you.equip[EQ_BODY_ARMOUR] != -1)
{
int ev_change = 0;
ev_change = armour_ev_penalty;
ev_change += you.skills[SK_ARMOUR] / 3;
if (ev_change > armour_ev_penalty / 3)
ev_change = armour_ev_penalty / 3;
ev += ev_change; // remember that it's negative
}
ev += player_equip( EQ_RINGS_PLUS, RING_EVASION );
if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_PONDEROUSNESS ))
ev -= 2;
if (you.duration[DUR_STONEMAIL])
ev -= 2;
if (you.duration[DUR_PHASE_SHIFT])
ev += 8; //jmf: is this a reasonable value?
int emod = 0;
if (!player_light_armour())
{
// meaning that the armour evasion modifier is often effectively
// applied twice, but not if you're wearing elven armour
emod += (armour_ev_penalty * 14) / 10;
}
emod += you.skills[SK_DODGING] / 2;
if (emod > 0)
ev += emod;
if (player_mutation_level(MUT_REPULSION_FIELD) > 0)
ev += (player_mutation_level(MUT_REPULSION_FIELD) * 2) - 1;
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_DRAGON:
ev -= 3;
break;
case TRAN_STATUE:
ev -= 5;
break;
case TRAN_SPIDER:
ev += 3;
break;
case TRAN_BAT:
ev += 20 + (you.experience_level - 10);
break;
default:
break;
}
ev += scan_artefacts(ARTP_EVASION);
return (ev);
}
#endif
int player_magical_power(void)
{
int ret = 0;
ret += 13 * player_equip(EQ_STAFF, STAFF_POWER);
ret += 9 * player_equip(EQ_RINGS, RING_MAGICAL_POWER);
ret += scan_artefacts(ARTP_MAGICAL_POWER);
return (ret);
}
int player_mag_abil(bool is_weighted)
{
int ma = 0;
// Brilliance Potion
ma += 6 * (you.duration[DUR_BRILLIANCE] ? 1 : 0);
ma += 3 * player_equip(EQ_RINGS, RING_WIZARDRY);
// Staves
ma += 4 * player_equip(EQ_STAFF, STAFF_WIZARDRY);
// armour of the Archmagi (checks body armour only)
ma += 2 * player_equip_ego_type(EQ_BODY_ARMOUR, SPARM_ARCHMAGI);
return ((is_weighted) ? ((ma * you.intel) / 10) : ma);
}
int player_shield_class(void) //jmf: changes for new spell
{
int shield = 0;
int stat = 0;
if (you.incapacitated())
return (0);
if (player_wearing_slot(EQ_SHIELD))
{
const item_def& item = you.inv[you.equip[EQ_SHIELD]];
int base_shield = property(item, PARM_AC);
int racial_bonus = _player_armour_racial_bonus(item);
// bonus applied only to base, see above for effect:
shield += base_shield * 100;
shield += base_shield * 5 * you.skills[SK_SHIELDS];
shield += base_shield * racial_bonus * 10 / 3;
shield += item.plus * 100;
if (item.sub_type == ARM_BUCKLER)
stat = you.dex * 38;
else if (item.sub_type == ARM_LARGE_SHIELD)
stat = you.dex * 12 + you.strength * 26;
else
stat = you.dex * 19 + you.strength * 19;
}
else
{
if (you.duration[DUR_MAGIC_SHIELD])
{
stat = 600 + you.skills[SK_EVOCATIONS] * 50;
shield += 300 + you.skills[SK_EVOCATIONS] * 25;
}
if (!you.duration[DUR_FIRE_SHIELD]
&& you.duration[DUR_CONDENSATION_SHIELD])
{
shield += 300 + (you.skills[SK_ICE_MAGIC] * 25);
stat = std::max(stat, you.intel * 38);
}
}
if (you.duration[DUR_DIVINE_SHIELD])
{
shield += you.attribute[ATTR_DIVINE_SHIELD] * 150;
stat = std::max(stat, int(you.attribute[ATTR_DIVINE_SHIELD] * 300));
}
if (shield + stat > 0)
shield += skill_bump(SK_SHIELDS) * 38;
return (shield + stat + 50) / 100;
}
int player_sust_abil(bool calc_unid)
{
int sa = 0;
sa += player_equip(EQ_RINGS, RING_SUSTAIN_ABILITIES, calc_unid);
return (sa);
}
int carrying_capacity(burden_state_type bs)
{
// Yuck. We need this for gameplay - it nerfs small forms too much
// otherwise - but there's no good way to rationalize here... --sorear
int used_weight = std::max(you.body_weight(), you.body_weight(true));
int cap = (2 * used_weight) + (you.strength * 300)
+ (you.airborne() ? 1000 : 0);
// We are nice to the lighter species in that strength adds absolutely
// instead of relatively to body weight. --dpeg
if (bs == BS_UNENCUMBERED)
return ((cap * 5) / 6);
else if (bs == BS_ENCUMBERED)
return ((cap * 11) / 12);
else
return (cap);
}
int burden_change(void)
{
const burden_state_type old_burdenstate = you.burden_state;
const bool was_flying_light = you.light_flight();
you.burden = 0;
if (you.duration[DUR_STONEMAIL])
you.burden += 800;
for (int bu = 0; bu < ENDOFPACK; bu++)
{
if (you.inv[bu].quantity < 1)
continue;
you.burden += item_mass( you.inv[bu] ) * you.inv[bu].quantity;
}
you.burden_state = BS_UNENCUMBERED;
set_redraw_status( REDRAW_BURDEN );
you.redraw_evasion = true;
// changed the burdened levels to match the change to max_carried
if (you.burden < carrying_capacity(BS_UNENCUMBERED))
{
you.burden_state = BS_UNENCUMBERED;
// this message may have to change, just testing {dlb}
if (old_burdenstate != you.burden_state)
mpr("Your possessions no longer seem quite so burdensome.");
}
else if (you.burden < carrying_capacity(BS_ENCUMBERED))
{
you.burden_state = BS_ENCUMBERED;
if (old_burdenstate != you.burden_state)
{
mpr("You are being weighed down by all of your possessions.");
learned_something_new(TUT_HEAVY_LOAD);
}
}
else
{
you.burden_state = BS_OVERLOADED;
if (old_burdenstate != you.burden_state)
{
mpr("You are being crushed by all of your possessions.");
learned_something_new(TUT_HEAVY_LOAD);
}
}
// Stop travel if we get burdened (as from potions of might/levitation
// wearing off).
if (you.burden_state > old_burdenstate)
interrupt_activity( AI_BURDEN_CHANGE );
const bool is_flying_light = you.light_flight();
if (is_flying_light != was_flying_light)
{
mpr(is_flying_light ? "You feel quicker in the air."
: "You feel heavier in the air.");
}
return (you.burden);
}
// force is true for forget_map command on level map.
void forget_map(unsigned char chance_forgotten, bool force)
{
ASSERT(!crawl_state.arena);
if (force && !yesno("Really forget level map?", true, 'n'))
return;
// The radius is only used in labyrinths.
const bool use_lab_check = (!force && you.level_type == LEVEL_LABYRINTH
&& chance_forgotten < 100);
const int radius = (use_lab_check && you.species == SP_MINOTAUR) ? 40*40
: 25*25;
for (rectangle_iterator ri(0); ri; ++ri)
{
if (!you.see_cell(*ri)
&& (force || x_chance_in_y(chance_forgotten, 100)
|| use_lab_check && (you.pos()-*ri).abs() > radius))
{
env.map_knowledge(*ri).clear();
#ifdef USE_TILE
set_map_knowledge_obj(*ri, DNGN_UNSEEN);
tiles.update_minimap(ri->x, ri->y);
env.tile_bk_fg(*ri) = 0;
env.tile_bk_bg(*ri) = tileidx_feature(DNGN_UNSEEN, ri->x, ri->y);
#endif
}
}
#ifdef USE_TILE
tiles.update_minimap_bounds();
#endif
}
void gain_exp( unsigned int exp_gained, unsigned int* actual_gain,
unsigned int* actual_avail_gain)
{
if (crawl_state.arena)
return;
if (player_equip_ego_type( EQ_BODY_ARMOUR, SPARM_ARCHMAGI ))
exp_gained = div_rand_round( exp_gained, 4 );
const unsigned long old_exp = you.experience;
const int old_avail = you.exp_available;
dprf("gain_exp: %d", exp_gained );
if (you.experience + exp_gained > (unsigned int)MAX_EXP_TOTAL)
you.experience = MAX_EXP_TOTAL;
else
you.experience += exp_gained;
if (you.duration[DUR_SAGE])
{
// Bonus skill training from Sage.
you.exp_available =
(exp_gained * you.sage_bonus_degree) / 100 + exp_gained / 2;
exercise(you.sage_bonus_skill, 20);
you.exp_available = old_avail;
exp_gained /= 2;
}
if (you.exp_available + exp_gained > (unsigned int)MAX_EXP_POOL)
you.exp_available = MAX_EXP_POOL;
else
you.exp_available += exp_gained;
level_change();
if (actual_gain != NULL)
*actual_gain = you.experience - old_exp;
if (actual_avail_gain != NULL)
*actual_avail_gain = you.exp_available - old_avail;
}
void level_change(bool skip_attribute_increase)
{
const bool wiz_cmd = crawl_state.prev_cmd == CMD_WIZARD
|| crawl_state.repeat_cmd == CMD_WIZARD;
// necessary for the time being, as level_change() is called
// directly sometimes {dlb}
you.redraw_experience = true;
while (you.experience_level < 27
&& you.experience > exp_needed(you.experience_level + 2))
{
bool skip_more = false;
if (!skip_attribute_increase && !wiz_cmd)
{
crawl_state.cancel_cmd_all();
if (is_processing_macro())
flush_input_buffer(FLUSH_ABORT_MACRO);
}
else if (crawl_state.is_replaying_keys()
|| crawl_state.is_repeating_cmd() || is_processing_macro())
{
skip_more = true;
}
const int old_hp = you.hp;
const int old_maxhp = you.hp_max;
int hp_adjust = 0;
int mp_adjust = 0;
// [ds] Make sure we increment you.experience_level and apply
// any stat/hp increases only after we've cleared all prompts
// for this experience level. If we do part of the work before
// the prompt, and a player on telnet gets disconnected, the
// SIGHUP will save Crawl in the in-between state and rob the
// player of their level-up perks.
const int new_exp = you.experience_level + 1;
if (new_exp <= you.max_level)
{
mprf(MSGCH_INTRINSIC_GAIN,
"Welcome back to level %d!", new_exp);
if (!skip_more)
more();
// No more prompts for this XL past this point.
you.experience_level = new_exp;
// Gain back the hp and mp we lose in lose_level(). -- bwr
inc_hp(4, true);
inc_mp(1, true);
}
else // Character has gained a new level
{
if (new_exp == 27)
{
mprf(MSGCH_INTRINSIC_GAIN,
"You have reached level 27, the final one!");
}
else
{
mprf(MSGCH_INTRINSIC_GAIN, "You have reached level %d!",
new_exp);
}
if (!(new_exp % 3) && !skip_attribute_increase)
_attribute_increase();
else if (!skip_more)
more();
// No more prompts for this XL past this point.
int brek = 0;
if (new_exp > 21)
brek = 2 + new_exp % 2;
else if (new_exp > 12)
brek = 4; // up from 2 + rand(3) -- bwr
else
brek = 5 + new_exp % 2; // up from 3 + rand(4) -- bwr
you.experience_level = new_exp;
inc_hp(brek, true);
inc_mp(1, true);
switch (you.species)
{
case SP_HUMAN:
if (!(you.experience_level % 5))
modify_stat(STAT_RANDOM, 1, false, "level gain");
break;
case SP_HIGH_ELF:
if (you.experience_level % 3)
hp_adjust--;
if (!(you.experience_level % 2))
mp_adjust++;
if (!(you.experience_level % 3))
{
modify_stat((coinflip() ? STAT_INTELLIGENCE
: STAT_DEXTERITY), 1, false,
"level gain");
}
break;
case SP_DEEP_ELF:
if (you.experience_level < 17)
hp_adjust--;
if (!(you.experience_level % 3))
hp_adjust--;
mp_adjust++;
if (!(you.experience_level % 4))
modify_stat(STAT_INTELLIGENCE, 1, false, "level gain");
break;
case SP_SLUDGE_ELF:
if (you.experience_level % 3)
hp_adjust--;
else
mp_adjust++;
if (!(you.experience_level % 4))
{
modify_stat((coinflip() ? STAT_INTELLIGENCE
: STAT_DEXTERITY), 1, false,
"level gain");
}
break;
case SP_MOUNTAIN_DWARF:
if (!(you.experience_level % 2))
hp_adjust++;
if (!(you.experience_level % 3))
mp_adjust--;
if (!(you.experience_level % 4))
modify_stat(STAT_STRENGTH, 1, false, "level gain");
break;
case SP_DEEP_DWARF:
hp_adjust++;
if (you.experience_level == 14)
{
mpr("You feel somewhat more resistant.",
MSGCH_INTRINSIC_GAIN);
perma_mutate(MUT_NEGATIVE_ENERGY_RESISTANCE, 1);
}
if ((you.experience_level == 9)
|| (you.experience_level == 18))
{
perma_mutate(MUT_PASSIVE_MAPPING, 1);
}
if (!(you.experience_level % 4))
{
modify_stat(coinflip() ? STAT_STRENGTH
: STAT_INTELLIGENCE, 1, false,
"level gain");
}
break;
case SP_HALFLING:
if (!(you.experience_level % 5))
modify_stat(STAT_DEXTERITY, 1, false, "level gain");
if (you.experience_level % 3)
hp_adjust--;
break;
case SP_KOBOLD:
if (!(you.experience_level % 5))
{
modify_stat((coinflip() ? STAT_STRENGTH
: STAT_DEXTERITY), 1, false,
"level gain");
}
if (you.experience_level < 17)
hp_adjust--;
if (!(you.experience_level % 2))
hp_adjust--;
break;
case SP_HILL_ORC:
// lower because of HD raise -- bwr
// if (you.experience_level < 17)
// hp_adjust++;
if (!(you.experience_level % 2))
hp_adjust++;
if (!(you.experience_level % 3))
mp_adjust--;
if (!(you.experience_level % 5))
modify_stat(STAT_STRENGTH, 1, false, "level gain");
break;
case SP_MUMMY:
if (you.experience_level == 13 || you.experience_level == 26)
{
mpr("You feel more in touch with the powers of death.",
MSGCH_INTRINSIC_GAIN);
}
if (you.experience_level == 13) // level 13 for now -- bwr
{
mpr("You can now infuse your body with magic to restore "
"decomposition.", MSGCH_INTRINSIC_GAIN);
}
break;
case SP_VAMPIRE:
if (you.experience_level == 3)
{
if (you.hunger_state > HS_SATIATED)
{
mpr("If you weren't so full you could now transform "
"into a vampire bat.", MSGCH_INTRINSIC_GAIN);
}
else
{
mpr("You can now transform into a vampire bat.",
MSGCH_INTRINSIC_GAIN);
}
}
else if (you.experience_level == 6)
{
mpr("You can now bottle potions of blood from chopped up "
"corpses.", MSGCH_INTRINSIC_GAIN);
}
else if (you.experience_level == 10)
{
mpr("Cursed equipment will now meld into your body when "
"transforming into a vampire bat.",
MSGCH_INTRINSIC_GAIN);
}
break;
case SP_NAGA:
hp_adjust++;
if (!(you.experience_level % 4))
modify_stat(STAT_RANDOM, 1, false, "level gain");
if (!(you.experience_level % 3))
{
mpr("Your skin feels tougher.", MSGCH_INTRINSIC_GAIN);
you.redraw_armour_class = true;
}
break;
case SP_TROLL:
hp_adjust++;
if (!(you.experience_level % 2))
hp_adjust++;
if (you.experience_level % 3)
mp_adjust--;
if (!(you.experience_level % 3))
modify_stat(STAT_STRENGTH, 1, false, "level gain");
break;
case SP_OGRE:
hp_adjust++;
if (!(you.experience_level % 3))
modify_stat(STAT_STRENGTH, 1, false, "level gain");
break;
case SP_RED_DRACONIAN:
case SP_WHITE_DRACONIAN:
case SP_GREEN_DRACONIAN:
case SP_YELLOW_DRACONIAN:
// Grey is handled later.
case SP_BLACK_DRACONIAN:
case SP_PURPLE_DRACONIAN:
case SP_MOTTLED_DRACONIAN:
case SP_PALE_DRACONIAN:
case SP_BASE_DRACONIAN:
if (you.experience_level == 7)
{
#ifdef USE_TILE
init_player_doll();
#endif
switch (you.species)
{
case SP_RED_DRACONIAN:
mpr("Your scales start taking on a fiery red colour.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_WHITE_DRACONIAN:
mpr("Your scales start taking on an icy white colour.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_GREEN_DRACONIAN:
mpr("Your scales start taking on a green colour.",
MSGCH_INTRINSIC_GAIN);
// Green draconians get this at level 7.
perma_mutate(MUT_POISON_RESISTANCE, 1);
break;
case SP_YELLOW_DRACONIAN:
mpr("Your scales start taking on a golden yellow colour.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_BLACK_DRACONIAN:
mpr("Your scales start turning black.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_PURPLE_DRACONIAN:
mpr("Your scales start taking on a rich purple colour.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_MOTTLED_DRACONIAN:
mpr("Your scales start taking on a weird mottled pattern.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_PALE_DRACONIAN:
mpr("Your scales start fading to a pale grey colour.",
MSGCH_INTRINSIC_GAIN);
break;
case SP_BASE_DRACONIAN:
mpr("");
break;
default:
break;
}
more();
redraw_screen();
}
if (you.experience_level == 14)
{
switch (you.species)
{
case SP_RED_DRACONIAN:
perma_mutate(MUT_HEAT_RESISTANCE, 1);
break;
case SP_WHITE_DRACONIAN:
perma_mutate(MUT_COLD_RESISTANCE, 1);
break;
default:
break;
}
}
else if (you.species == SP_BLACK_DRACONIAN
&& you.experience_level == 18)
{
perma_mutate(MUT_SHOCK_RESISTANCE, 1);
}
if (!(you.experience_level % 3))
hp_adjust++;
if (you.experience_level > 7 && !(you.experience_level % 4))
{
mpr("Your scales feel tougher.", MSGCH_INTRINSIC_GAIN);
you.redraw_armour_class = true;
modify_stat(STAT_RANDOM, 1, false, "level gain");
}
break;
case SP_GREY_DRACONIAN:
if (you.experience_level == 7)
{
#ifdef USE_TILE
init_player_doll();
#endif
mpr("Your scales start turning grey.",
MSGCH_INTRINSIC_GAIN);
more();
redraw_screen();
}
if (!(you.experience_level % 3))
{
hp_adjust++;
if (you.experience_level > 7)
hp_adjust++;
}
if (you.experience_level > 7 && !(you.experience_level % 2))
{
mpr("Your scales feel tougher.", MSGCH_INTRINSIC_GAIN);
you.redraw_armour_class = true;
}
if ((you.experience_level > 7 && !(you.experience_level % 3))
|| you.experience_level == 4 || you.experience_level == 7)
{
modify_stat(STAT_RANDOM, 1, false, "level gain");
}
break;
case SP_CENTAUR:
if (!(you.experience_level % 4))
{
modify_stat((coinflip() ? STAT_STRENGTH
: STAT_DEXTERITY), 1, false,
"level gain");
}
// lowered because of HD raise -- bwr
// if (you.experience_level < 17)
// hp_adjust++;
if (!(you.experience_level % 2))
hp_adjust++;
if (!(you.experience_level % 3))
mp_adjust--;
break;
case SP_DEMIGOD:
if (!(you.experience_level % 2))
modify_stat(STAT_RANDOM, 1, false, "level gain");
// lowered because of HD raise -- bwr
// if (you.experience_level < 17)
// hp_adjust++;
if (!(you.experience_level % 2))
hp_adjust++;
if (you.experience_level % 3)
mp_adjust++;
break;
case SP_SPRIGGAN:
if (you.experience_level < 17)
hp_adjust--;
if (you.experience_level % 3)
hp_adjust--;
mp_adjust++;
if (!(you.experience_level % 5))
{
modify_stat((coinflip() ? STAT_INTELLIGENCE
: STAT_DEXTERITY), 1, false,
"level gain");
}
break;
case SP_MINOTAUR:
// lowered because of HD raise -- bwr
// if (you.experience_level < 17)
// hp_adjust++;
if (!(you.experience_level % 2))
hp_adjust++;
if (!(you.experience_level % 2))
mp_adjust--;
if (!(you.experience_level % 4))
{
modify_stat((coinflip() ? STAT_STRENGTH
: STAT_DEXTERITY), 1, false,
"level gain");
}
break;
case SP_DEMONSPAWN:
{
bool gave_message = false;
for (unsigned i = 0; i < you.demonic_traits.size(); ++i)
{
if (you.demonic_traits[i].level_gained
== you.experience_level)
{
if (!gave_message)
{
mpr("Your demonic ancestry asserts itself...",
MSGCH_INTRINSIC_GAIN);
gave_message = true;
}
perma_mutate(you.demonic_traits[i].mutation, 1);
}
}
}
if (!(you.experience_level % 4))
modify_stat(STAT_RANDOM, 1, false, "level gain");
break;
case SP_GHOUL:
// lowered because of HD raise -- bwr
// if (you.experience_level < 17)
// hp_adjust++;
if (!(you.experience_level % 2))
hp_adjust++;
if (!(you.experience_level % 3))
mp_adjust--;
if (!(you.experience_level % 5))
modify_stat(STAT_STRENGTH, 1, false, "level gain");
break;
case SP_KENKU:
if (you.experience_level < 17)
hp_adjust--;
if (!(you.experience_level % 3))
hp_adjust--;
if (!(you.experience_level % 4))
modify_stat(STAT_RANDOM, 1, false, "level gain");
if (you.experience_level == 5)
{
mpr("You have gained the ability to fly.",
MSGCH_INTRINSIC_GAIN);
}
else if (you.experience_level == 15)
mpr("You can now fly continuously.", MSGCH_INTRINSIC_GAIN);
break;
case SP_MERFOLK:
if (you.experience_level % 3)
hp_adjust++;
if (!(you.experience_level % 5))
modify_stat(STAT_RANDOM, 1, false, "level gain");
break;
default:
break;
}
}
// add hp and mp adjustments - GDL
inc_max_hp(hp_adjust);
inc_max_mp(mp_adjust);
deflate_hp(you.hp_max, false);
you.hp = old_hp * you.hp_max / old_maxhp;
you.magic_points = std::max(0, you.magic_points);
// Get "real" values for note-taking, i.e. ignore Berserk,
// transformations or equipped items.
const int note_maxhp = get_real_hp(false, false);
const int note_maxmp = get_real_mp(false);
char buf[200];
sprintf(buf, "HP: %d/%d MP: %d/%d",
std::min(you.hp, note_maxhp), note_maxhp,
std::min(you.magic_points, note_maxmp), note_maxmp);
take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0, buf));
// recalculate for game
calc_hp();
calc_mp();
if (you.experience_level > you.max_level)
you.max_level = you.experience_level;
xom_is_stimulated(16);
learned_something_new(TUT_NEW_LEVEL);
}
redraw_skill(you.your_name, player_title());
// Increase tutorial time-out now that it's actually become useful
// for a longer time.
if (Tutorial.tutorial_left && you.experience_level >= 7)
tutorial_finished();
}
// Here's a question for you: does the ordering of mods make a difference?
// (yes) -- are these things in the right order of application to stealth?
// - 12mar2000 {dlb}
int check_stealth(void)
{
ASSERT(!crawl_state.arena);
#ifdef WIZARD
// Extreme stealthiness can be enforced by wizmode stealth setting.
if (you.skills[SK_STEALTH] > 27)
return (1000);
#endif
if (you.attribute[ATTR_SHADOWS] || you.berserk())
return (0);
int stealth = you.dex * 3;
if (you.skills[SK_STEALTH])
{
if (player_genus(GENPC_DRACONIAN))
stealth += (you.skills[SK_STEALTH] * 12);
else
{
switch (you.species) // why not use body_size here?
{
case SP_TROLL:
case SP_OGRE:
case SP_CENTAUR:
stealth += (you.skills[SK_STEALTH] * 9);
break;
case SP_MINOTAUR:
stealth += (you.skills[SK_STEALTH] * 12);
break;
case SP_VAMPIRE:
// Thirsty/bat-form vampires are (much) more stealthy
if (you.hunger_state == HS_STARVING)
stealth += (you.skills[SK_STEALTH] * 21);
else if (player_in_bat_form()
|| you.hunger_state <= HS_NEAR_STARVING)
{
stealth += (you.skills[SK_STEALTH] * 20);
}
else if (you.hunger_state < HS_SATIATED)
stealth += (you.skills[SK_STEALTH] * 19);
else
stealth += (you.skills[SK_STEALTH] * 18);
break;
case SP_HALFLING:
case SP_KOBOLD:
case SP_SPRIGGAN:
case SP_NAGA: // not small but very good at stealth
stealth += (you.skills[SK_STEALTH] * 18);
break;
default:
stealth += (you.skills[SK_STEALTH] * 15);
break;
}
}
}
if (you.burden_state > BS_UNENCUMBERED)
stealth /= you.burden_state;
if (you.confused())
stealth /= 3;
const int arm = you.equip[EQ_BODY_ARMOUR];
const int cloak = you.equip[EQ_CLOAK];
const int boots = you.equip[EQ_BOOTS];
if (arm != -1 && !player_light_armour())
stealth -= (item_mass( you.inv[arm] ) / 10);
if (cloak != -1 && get_equip_race(you.inv[cloak]) == ISFLAG_ELVEN)
stealth += 20;
if (boots != -1)
{
if (get_armour_ego_type( you.inv[boots] ) == SPARM_STEALTH)
stealth += 50;
if (get_equip_race(you.inv[boots]) == ISFLAG_ELVEN)
stealth += 20;
}
if ( you.duration[DUR_STEALTH] )
stealth += 80;
if (you.duration[DUR_AGILITY])
stealth += 50;
stealth += scan_artefacts( ARTP_STEALTH );
if (you.airborne())
stealth += 10;
else if (you.in_water())
{
// Merfolk can sneak up on monsters underwater -- bwr
if (you.species == SP_MERFOLK)
stealth += 50;
else if ( !you.can_swim() && !you.extra_balanced() )
stealth /= 2; // splashy-splashy
}
else if (player_mutation_level(MUT_HOOVES))
stealth -= 10; // clippety-clop
// Radiating silence is the negative complement of shouting all the
// time... a sudden change from background noise to no noise is going
// to clue anything in to the fact that something is very wrong...
// a personal silence spell would naturally be different, but this
// silence radiates for a distance and prevents monster spellcasting,
// which pretty much gives away the stealth game.
if (you.duration[DUR_SILENCE])
stealth -= 50;
stealth = std::max(0, stealth);
return (stealth);
}
static void _attribute_increase()
{
mpr("Your experience leads to an increase in your attributes!",
MSGCH_INTRINSIC_GAIN);
learned_something_new(TUT_CHOOSE_STAT);
more();
mesclr();
mpr("Increase (S)trength, (I)ntelligence, or (D)exterity? ", MSGCH_PROMPT);
while (true)
{
const int keyin = getch();
switch (keyin)
{
#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
case ESCAPE:
case -1:
// [ds] It's safe to save the game here; when the player
// reloads, the game will re-prompt for their level-up
// stat gain.
if (crawl_state.seen_hups)
sighup_save_and_exit();
break;
#endif
case 's':
case 'S':
modify_stat(STAT_STRENGTH, 1, false, "level gain");
you.last_chosen = STAT_STRENGTH;
return;
case 'i':
case 'I':
modify_stat(STAT_INTELLIGENCE, 1, false, "level gain");
you.last_chosen = STAT_INTELLIGENCE;
return;
case 'd':
case 'D':
modify_stat(STAT_DEXTERITY, 1, false, "level gain");
you.last_chosen = STAT_DEXTERITY;
return;
}
}
}
// Rearrange stats, biased towards the stat chosen last at level up.
void jiyva_stat_action()
{
char* max_statp[] = { &you.max_strength, &you.max_intel, &you.max_dex };
char* base_statp[] = { &you.strength, &you.intel, &you.dex };
int incremented_weight[] = {1, 1, 1};
int decremented_weight[3];
int stat_up_choice;
int stat_down_choice;
incremented_weight[you.last_chosen] = 2;
for (int x = 0; x < 3; ++x)
decremented_weight[x] = std::min(10, std::max(0, *max_statp[x] - 7));
stat_up_choice = choose_random_weighted(incremented_weight,
incremented_weight + 3);
stat_down_choice = choose_random_weighted(decremented_weight,
decremented_weight + 3);
if (stat_up_choice != stat_down_choice)
{
// We have a stat change noticeable to the player at this point.
// This could be lethal if the player currently has 1 in a stat
// but has a max stat of something higher -- perhaps we should
// check for that?
(*max_statp[stat_up_choice])++;
(*max_statp[stat_down_choice])--;
(*base_statp[stat_up_choice])++;
(*base_statp[stat_down_choice])--;
simple_god_message("'s power touches on your attributes.");
you.redraw_strength = true;
you.redraw_intelligence = true;
you.redraw_dexterity = true;
burden_change();
}
}
static const char * _get_rotting_how()
{
ASSERT(you.rotting > 0 || you.species == SP_GHOUL);
if (you.rotting > 15)
return (" before your eyes");
if (you.rotting > 8)
return (" away quickly");
if (you.rotting > 4)
return (" badly");
if (you.species == SP_GHOUL)
return (" faster than usual");
return ("");
}
// Returns the medium duration value which is usually announced by a special
// message ("XY is about to time out") or a change of colour in the
// status display.
// Note that these values cannot be relied on when playing since there are
// random decrements precisely to avoid this.
int get_expiration_threshold(duration_type dur)
{
switch (dur)
{
case DUR_FIRE_SHIELD:
case DUR_SILENCE: // no message
return (5 * BASELINE_DELAY);
case DUR_DEFLECT_MISSILES:
case DUR_REPEL_MISSILES:
case DUR_REGENERATION:
case DUR_INSULATION:
case DUR_STONEMAIL:
case DUR_SWIFTNESS:
case DUR_INVIS:
case DUR_HASTE:
// The following are not shown in the status lines.
case DUR_ICY_ARMOUR:
case DUR_PHASE_SHIFT:
case DUR_CONTROL_TELEPORT:
case DUR_DEATH_CHANNEL:
return (6 * BASELINE_DELAY);
case DUR_LEVITATION:
case DUR_TRANSFORMATION: // not on status
case DUR_DEATHS_DOOR: // not on status
case DUR_SLIMIFY:
return (10 * BASELINE_DELAY);
// These get no messages when they "flicker".
case DUR_SAGE:
case DUR_BARGAIN:
return (15 * BASELINE_DELAY);
case DUR_CONFUSING_TOUCH:
return (20 * BASELINE_DELAY);
default:
return (0);
}
}
// Is a given duration about to expire?
bool dur_expiring(duration_type dur)
{
const int value = you.duration[dur];
if (value <= 0)
return (false);
return (value <= get_expiration_threshold(dur));
}
static void _output_expiring_message(duration_type dur, const char* msg)
{
if (you.duration[dur])
{
const bool expires = dur_expiring(dur);
mprf("%s%s", expires ? "Expiring: " : "", msg);
}
}
void display_char_status()
{
if (you.is_undead == US_SEMI_UNDEAD && you.hunger_state == HS_ENGORGED)
mpr("You feel almost alive.");
else if (you.is_undead)
mpr("You are undead.");
else if (you.duration[DUR_DEATHS_DOOR])
{
_output_expiring_message(DUR_DEATHS_DOOR,
"You are standing in death's doorway.");
}
else
mpr("You are alive.");
const int halo_size = you.halo_radius();
if (halo_size > 0)
{
if (halo_size > 6)
mpr("You are illuminated by a large divine halo.");
else if (halo_size > 3)
mpr("You are illuminated by a divine halo.");
else
mpr("You are illuminated by a small divine halo.");
}
if (you.species == SP_VAMPIRE)
{
std::string msg = "At your current hunger state you ";
std::vector<std::string> attrib;
switch (you.hunger_state)
{
case HS_STARVING:
attrib.push_back("resist poison");
attrib.push_back("significantly resist cold");
attrib.push_back("strongly resist negative energy");
attrib.push_back("resist torment");
attrib.push_back("do not heal.");
break;
case HS_NEAR_STARVING:
attrib.push_back("resist poison");
attrib.push_back("significantly resist cold");
attrib.push_back("strongly resist negative energy");
attrib.push_back("have an extremely slow metabolism");
attrib.push_back("heal slowly.");
break;
case HS_HUNGRY:
case HS_VERY_HUNGRY:
attrib.push_back("resist poison");
attrib.push_back("resist cold");
attrib.push_back("significantly resist negative energy");
if (you.hunger_state == HS_HUNGRY)
attrib.push_back("have a slow metabolism");
else
attrib.push_back("have a very slow metabolism");
attrib.push_back("heal slowly.");
break;
case HS_SATIATED:
attrib.push_back("resist negative energy.");
break;
case HS_FULL:
attrib.push_back("have a fast metabolism");
attrib.push_back("heal quickly.");
break;
case HS_VERY_FULL:
attrib.push_back("have a very fast metabolism");
attrib.push_back("heal quickly.");
break;
case HS_ENGORGED:
attrib.push_back("have an extremely fast metabolism");
attrib.push_back("heal extremely quickly.");
break;
}
if (!attrib.empty())
{
msg += comma_separated_line(attrib.begin(), attrib.end());
mpr(msg.c_str());
}
}
if (you.duration[DUR_TRANSFORMATION] > 0)
{
std::string text;
if ((you.species != SP_VAMPIRE || !player_in_bat_form())
&& dur_expiring(DUR_TRANSFORMATION))
{
text = "Expiring: ";
}
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
text += "You are in spider-form.";
break;
case TRAN_BAT:
text += "You are in ";
if (you.species == SP_VAMPIRE)
text += "vampire ";
text += "bat-form.";
break;
case TRAN_BLADE_HANDS:
text += "You have blades for hands.";
break;
case TRAN_STATUE:
text += "You are a statue.";
break;
case TRAN_ICE_BEAST:
text += "You are an ice creature.";
break;
case TRAN_DRAGON:
text += "You are in dragon-form.";
break;
case TRAN_LICH:
text += "You are in lich-form.";
break;
case TRAN_PIG:
text += "You are a filthy swine.";
break;
}
mpr(text.c_str());
}
if (you.burden_state == BS_ENCUMBERED)
mpr("You are burdened.");
else if (you.burden_state == BS_OVERLOADED)
mpr("You are overloaded with stuff.");
else if (you.species == SP_KENKU && you.flight_mode() == FL_FLY)
{
if (you.travelling_light())
mpr("Your small burden allows quick flight.");
else
mpr("Your heavy burden is slowing your flight.");
}
if (you.duration[DUR_SAGE])
{
std::string msg = "You are studying ";
msg += skill_name(you.sage_bonus_skill);
msg += ".";
_output_expiring_message(DUR_SAGE, msg.c_str());
}
_output_expiring_message(DUR_BARGAIN, "You get a bargain in shops.");
if (you.duration[DUR_BREATH_WEAPON])
mpr("You are short of breath.");
if (you.duration[DUR_LIQUID_FLAMES])
mpr("You are covered in liquid flames.");
if (you.duration[DUR_FIRE_SHIELD])
{
_output_expiring_message(DUR_FIRE_SHIELD,
"You are surrounded by a ring of flames.");
_output_expiring_message(DUR_FIRE_SHIELD,
"You are immune to clouds of flame.");
}
_output_expiring_message(DUR_ICY_ARMOUR,
"You are protected by an icy armour.");
_output_expiring_message(DUR_REPEL_MISSILES,
"You are protected from missiles.");
_output_expiring_message(DUR_DEFLECT_MISSILES, "You deflect missiles.");
if (you.duration[DUR_PRAYER])
mpr("You are praying.");
// Disease and regen influence each other.
if (you.disease)
{
if (!you.duration[DUR_REGENERATION])
mpr("You are sick.");
else
{
_output_expiring_message(DUR_REGENERATION,
"recuperating from your illness");
}
}
else
{
bool no_heal =
(you.species == SP_VAMPIRE && you.hunger_state == HS_STARVING)
|| (player_mutation_level(MUT_SLOW_HEALING) == 3);
if (!no_heal)
_output_expiring_message(DUR_REGENERATION, "regenerating");
}
_output_expiring_message(DUR_SWIFTNESS, "You can move swiftly.");
_output_expiring_message(DUR_INSULATION, "You are insulated.");
_output_expiring_message(DUR_STONEMAIL,
"You are covered in scales of stone.");
if (you.duration[DUR_CONTROLLED_FLIGHT])
mpr("You can control your flight.");
if (you.duration[DUR_TELEPORT])
mpr("You are about to teleport.");
_output_expiring_message(DUR_CONTROL_TELEPORT,
"You can control teleportation.");
_output_expiring_message(DUR_DEATH_CHANNEL, "You are channeling the dead.");
_output_expiring_message(DUR_PHASE_SHIFT,
"You are out of phase with the material plane.");
_output_expiring_message(DUR_SILENCE, "You radiate silence.");
if (you.duration[DUR_STONESKIN])
mpr("Your skin is tough as stone.");
if (you.duration[DUR_SEE_INVISIBLE])
mpr("You can see invisible.");
_output_expiring_message(DUR_INVIS, "You are invisible.");
if (you.confused())
mpr("You are confused.");
// TODO: Distinguish between mermaids and sirens!
if (you.beheld())
mpr("You are mesmerised.");
// How exactly did you get to show the status?
if (you.paralysed())
mpr("You are paralysed.");
if (you.petrified())
mpr("You are petrified.");
if (you.asleep())
mpr("You are asleep.");
if (you.duration[DUR_EXHAUSTED])
mpr("You are exhausted.");
if (you.duration[DUR_SLOW] && you.duration[DUR_HASTE])
mpr("You are under both slowing and hasting effects.");
else if (you.duration[DUR_SLOW])
mpr("Your actions are slowed.");
else if (you.duration[DUR_HASTE])
_output_expiring_message(DUR_HASTE, "Your actions are hasted.");
if (you.duration[DUR_MIGHT])
mpr("You are mighty.");
if (you.duration[DUR_BRILLIANCE])
mpr("You are brilliant.");
if (you.duration[DUR_AGILITY])
mpr("You are agile.");
if (you.duration[DUR_DIVINE_VIGOUR])
mpr("You are divinely vigorous.");
if (you.duration[DUR_DIVINE_STAMINA])
mpr("You are divinely fortified.");
if (you.berserk())
mpr("You are possessed by a berserker rage.");
if (you.airborne())
{
const bool expires = dur_expiring(DUR_LEVITATION)
&& !you.permanent_flight();
mprf(expires ? MSGCH_WARN : MSGCH_PLAIN,
"%sYou are hovering above the floor.",
expires ? "Expiring: " : "");
}
if (you.attribute[ATTR_HELD])
mpr("You are held in a net.");
if (you.duration[DUR_POISONING])
{
mprf("You are %s poisoned.",
(you.duration[DUR_POISONING] > 10) ? "extremely" :
(you.duration[DUR_POISONING] > 5) ? "very" :
(you.duration[DUR_POISONING] > 3) ? "quite"
: "mildly" );
}
if (you.disease)
{
int high = 120 * BASELINE_DELAY;
int low = 40 * BASELINE_DELAY;
mprf("You are %sdiseased.",
(you.disease > high) ? "badly " :
(you.disease > low) ? ""
: "mildly ");
}
if (you.rotting || you.species == SP_GHOUL)
mprf("Your flesh is rotting%s.", _get_rotting_how());
// Prints a contamination message.
contaminate_player(0, false, true);
if (you.duration[DUR_CONFUSING_TOUCH])
{
int hi = 40 * BASELINE_DELAY;
int low = 20 * BASELINE_DELAY;
mprf("Your hands are glowing %s red.",
(you.duration[DUR_CONFUSING_TOUCH] > hi) ? "an extremely bright" :
(you.duration[DUR_CONFUSING_TOUCH] > low) ? "bright"
: "a soft");
}
if (you.duration[DUR_SURE_BLADE])
{
mprf("You have a %sbond with your blade.",
(you.duration[DUR_SURE_BLADE] > 15 * BASELINE_DELAY) ? "strong " :
(you.duration[DUR_SURE_BLADE] > 5 * BASELINE_DELAY) ? ""
: "weak ");
}
const int move_cost = (player_speed() * player_movement_speed()) / 10;
const bool water = you.in_water();
const bool swim = you.swimming();
const bool lev = you.airborne();
const bool fly = (you.flight_mode() == FL_FLY);
const bool swift = (you.duration[DUR_SWIFTNESS] > 0);
mprf( "Your %s speed is %s%s%s.",
// order is important for these:
(swim) ? "swimming" :
(water) ? "wading" :
(fly) ? "flying" :
(lev) ? "levitating"
: "movement",
(water && !swim) ? "uncertain and " :
(!water && swift) ? "aided by the wind" : "",
(!water && swift) ? ((move_cost >= 10) ? ", but still "
: " and ")
: "",
(move_cost < 8) ? "very quick" :
(move_cost < 10) ? "quick" :
(move_cost == 10) ? "average" :
(move_cost < 13) ? "slow"
: "very slow" );
#if DEBUG_DIAGNOSTICS
const int to_hit = calc_your_to_hit( false ) * 2;
mprf("To-hit: %d", to_hit);
#endif
/*
// Messages based largely on percentage chance of missing the
// average EV 10 humanoid, and very agile EV 30 (pretty much
// max EV for monsters currently).
//
// "awkward" - need lucky hit (less than EV)
// "difficult" - worse than 2 in 3
// "hard" - worse than fair chance
mprf("%s given your current equipment.",
(to_hit < 1) ? "You are completely incapable of fighting" :
(to_hit < 5) ? "Hitting even clumsy monsters is extremely awkward" :
(to_hit < 10) ? "Hitting average monsters is awkward" :
(to_hit < 15) ? "Hitting average monsters is difficult" :
(to_hit < 20) ? "Hitting average monsters is hard" :
(to_hit < 30) ? "Very agile monsters are a bit awkward to hit" :
(to_hit < 45) ? "Very agile monsters are a bit difficult to hit" :
(to_hit < 60) ? "Very agile monsters are a bit hard to hit" :
(to_hit < 100) ? "You feel comfortable with your ability to fight"
: "You feel confident with your ability to fight" );
*/
// magic resistance
const int mr = you.res_magic();
mprf("You are %s resistant to magic.", magic_res_adjective(mr).c_str());
// character evaluates their ability to sneak around:
mprf("You feel %s.", stealth_desc(check_stealth()).c_str());
dprf("stealth: %d", check_stealth());
}
bool player_item_conserve(bool calc_unid)
{
return (player_equip(EQ_AMULET, AMU_CONSERVATION, calc_unid)
|| player_equip_ego_type(EQ_CLOAK, SPARM_PRESERVATION));
}
int player_mental_clarity(bool calc_unid, bool items)
{
const int ret = (3 * player_equip(EQ_AMULET, AMU_CLARITY, calc_unid)
* items)
+ player_mutation_level(MUT_CLARITY);
return ((ret > 3) ? 3 : ret);
}
int player_spirit_shield(bool calc_unid)
{
return player_equip(EQ_AMULET, AMU_GUARDIAN_SPIRIT, calc_unid)
+ player_equip_ego_type(EQ_HELMET, SPARM_SPIRIT_SHIELD);
}
// Returns whether the player has the effect of the amulet from a
// non-amulet source.
bool extrinsic_amulet_effect(jewellery_type amulet)
{
switch (amulet)
{
case AMU_CONTROLLED_FLIGHT:
return (you.duration[DUR_CONTROLLED_FLIGHT]
|| player_genus(GENPC_DRACONIAN)
|| (you.species == SP_KENKU && you.experience_level >= 5)
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_BAT);
case AMU_CLARITY:
return (player_mutation_level(MUT_CLARITY) > 0);
case AMU_RESIST_CORROSION:
if (you.religion == GOD_JIYVA && you.piety > piety_breakpoint(2))
return (true);
// else fall-through
case AMU_CONSERVATION:
return (player_equip_ego_type(EQ_CLOAK, SPARM_PRESERVATION) > 0);
case AMU_THE_GOURMAND:
return (player_mutation_level(MUT_GOURMAND) > 0);
default:
return (false);
}
}
bool wearing_amulet(jewellery_type amulet, bool calc_unid)
{
if (extrinsic_amulet_effect(amulet))
return (true);
if (!player_wearing_slot(EQ_AMULET))
return (false);
const item_def& amu(you.inv[you.equip[EQ_AMULET]]);
return (amu.sub_type == amulet && (calc_unid || item_type_known(amu)));
}
static int _species_exp_mod(species_type species)
{
if (player_genus(GENPC_DRACONIAN, species))
return 14;
else if (player_genus(GENPC_DWARVEN, species))
return 13;
switch (species)
{
case SP_HUMAN:
case SP_HALFLING:
case SP_HILL_ORC:
case SP_KOBOLD:
return 10;
case SP_OGRE:
return 11;
case SP_SLUDGE_ELF:
case SP_NAGA:
case SP_GHOUL:
case SP_MERFOLK:
return 12;
case SP_SPRIGGAN:
case SP_KENKU:
return 13;
case SP_DEEP_ELF:
case SP_CENTAUR:
case SP_MINOTAUR:
case SP_DEMONSPAWN:
return 14;
case SP_HIGH_ELF:
case SP_MUMMY:
case SP_VAMPIRE:
case SP_TROLL:
return 15;
case SP_DEMIGOD:
return 16;
default:
return 0;
}
}
unsigned long exp_needed(int lev)
{
lev--;
unsigned long level = 0;
// Basic plan:
// Section 1: levels 1- 5, second derivative goes 10-10-20-30.
// Section 2: levels 6-13, second derivative is exponential/doubling.
// Section 3: levels 14-27, second derivative is constant at 6000.
//
// Section three is constant so we end up with high levels at about
// their old values (level 27 at 850k), without delta2 ever decreasing.
// The values that are considerably different (ie level 13 is now 29000,
// down from 41040 are because the second derivative goes from 9040 to
// 1430 at that point in the original, and then slowly builds back
// up again). This function smoothes out the old level 10-15 area
// considerably.
// Here's a table:
//
// level xp delta delta2
// ===== ======= ===== ======
// 1 0 0 0
// 2 10 10 10
// 3 30 20 10
// 4 70 40 20
// 5 140 70 30
// 6 270 130 60
// 7 520 250 120
// 8 1010 490 240
// 9 1980 970 480
// 10 3910 1930 960
// 11 7760 3850 1920
// 12 15450 7690 3840
// 13 29000 13550 5860
// 14 48500 19500 5950
// 15 74000 25500 6000
// 16 105500 31500 6000
// 17 143000 37500 6000
// 18 186500 43500 6000
// 19 236000 49500 6000
// 20 291500 55500 6000
// 21 353000 61500 6000
// 22 420500 67500 6000
// 23 494000 73500 6000
// 24 573500 79500 6000
// 25 659000 85500 6000
// 26 750500 91500 6000
// 27 848000 97500 6000
switch (lev)
{
case 1:
level = 1;
break;
case 2:
level = 10;
break;
case 3:
level = 30;
break;
case 4:
level = 70;
break;
default:
if (lev < 13)
{
lev -= 4;
level = 10 + 10 * lev + (60 << lev);
}
else
{
lev -= 12;
level = 15500 + 10500 * lev + 3000 * lev * lev;
}
break;
}
return ((level - 1) * _species_exp_mod(you.species) / 10);
}
// returns bonuses from rings of slaying, etc.
int slaying_bonus(char which_affected, bool ranged)
{
int ret = 0;
if (which_affected == PWPN_HIT)
{
ret += player_equip( EQ_RINGS_PLUS, RING_SLAYING );
ret += scan_artefacts(ARTP_ACCURACY);
if (player_equip_ego_type(EQ_GLOVES, SPARM_ARCHERY))
ret += ranged ? 5 : -1;
}
else if (which_affected == PWPN_DAMAGE)
{
ret += player_equip( EQ_RINGS_PLUS2, RING_SLAYING );
ret += scan_artefacts(ARTP_DAMAGE);
if (player_equip_ego_type(EQ_GLOVES, SPARM_ARCHERY))
ret += ranged ? 3 : -1;
}
ret += std::min(you.duration[DUR_SLAYING] / (13 * BASELINE_DELAY), 6);
return (ret);
}
// Checks each equip slot for an evokable item (jewellery or randart).
// Returns true if any of these has the same ability as the one handed in.
bool items_give_ability(const int slot, artefact_prop_type abil)
{
for (int i = EQ_WEAPON; i < NUM_EQUIP; i++)
{
if (!player_wearing_slot(i))
continue;
const int eq = you.equip[i];
// skip item to compare with
if (eq == slot)
continue;
// only weapons give their effects when in our hands
if (i == EQ_WEAPON && you.inv[ eq ].base_type != OBJ_WEAPONS)
continue;
if (eq == EQ_LEFT_RING || eq == EQ_RIGHT_RING)
{
if (abil == ARTP_LEVITATE && you.inv[eq].sub_type == RING_LEVITATION)
return (true);
if (abil == ARTP_INVISIBLE && you.inv[eq].sub_type == RING_INVISIBILITY)
return (true);
}
else if (eq == EQ_AMULET)
{
if (abil == ARTP_BERSERK && you.inv[eq].sub_type == AMU_RAGE)
return (true);
}
// other items are not evokable
if (!is_artefact( you.inv[ eq ] ))
continue;
if (artefact_wpn_property(you.inv[ eq ], abil))
return (true);
}
// none of the equipped items possesses this ability
return (false);
}
// Checks each equip slot for a randart, and adds up all of those with
// a given property. Slow if any randarts are worn, so avoid where
// possible.
int scan_artefacts(artefact_prop_type which_property, bool calc_unid)
{
int retval = 0;
for (int i = EQ_WEAPON; i < NUM_EQUIP; ++i)
{
if (!player_wearing_slot(i))
continue;
const int eq = you.equip[i];
// Only weapons give their effects when in our hands.
if (i == EQ_WEAPON && you.inv[ eq ].base_type != OBJ_WEAPONS)
continue;
if (!is_artefact( you.inv[ eq ] ))
continue;
// Ignore unidentified items [TileCrawl dump enhancements].
if (!item_ident(you.inv[ eq ], ISFLAG_KNOW_PROPERTIES)
&& !calc_unid)
{
continue;
}
retval += artefact_wpn_property( you.inv[ eq ], which_property );
}
return (retval);
}
void modify_stat(stat_type which_stat, char amount, bool suppress_msg,
const char *cause, bool see_source)
{
ASSERT(!crawl_state.arena);
char *ptr_stat = NULL;
char *ptr_stat_max = NULL;
bool *ptr_redraw = NULL;
kill_method_type kill_type = NUM_KILLBY;
// sanity - is non-zero amount?
if (amount == 0)
return;
// Stop delays if a stat drops.
if (amount < 0)
interrupt_activity( AI_STAT_CHANGE );
std::string msg = "You feel ";
if (which_stat == STAT_RANDOM)
which_stat = static_cast<stat_type>(random2(NUM_STATS));
switch (which_stat)
{
case STAT_STRENGTH:
ptr_stat = &you.strength;
ptr_stat_max = &you.max_strength;
ptr_redraw = &you.redraw_strength;
kill_type = KILLED_BY_WEAKNESS;
msg += ((amount > 0) ? "stronger." : "weaker.");
break;
case STAT_INTELLIGENCE:
ptr_stat = &you.intel;
ptr_stat_max = &you.max_intel;
ptr_redraw = &you.redraw_intelligence;
kill_type = KILLED_BY_STUPIDITY;
msg += ((amount > 0) ? "clever." : "stupid.");
break;
case STAT_DEXTERITY:
ptr_stat = &you.dex;
ptr_stat_max = &you.max_dex;
ptr_redraw = &you.redraw_dexterity;
kill_type = KILLED_BY_CLUMSINESS;
msg += ((amount > 0) ? "agile." : "clumsy.");
break;
default:
break;
}
if (!suppress_msg && amount != 0)
mpr( msg.c_str(), (amount > 0) ? MSGCH_INTRINSIC_GAIN : MSGCH_WARN );
*ptr_stat += amount;
*ptr_stat_max += amount;
*ptr_redraw = 1;
if (amount < 0 && *ptr_stat < 1)
{
if (cause == NULL)
ouch(INSTANT_DEATH, NON_MONSTER, kill_type);
else
ouch(INSTANT_DEATH, NON_MONSTER, kill_type, cause, see_source);
}
if (ptr_stat == &you.strength)
burden_change();
if (ptr_stat == &you.dex)
you.redraw_evasion = true;
}
void modify_stat(stat_type which_stat, char amount, bool suppress_msg,
const std::string& cause, bool see_source)
{
modify_stat(which_stat, amount, suppress_msg, cause.c_str(), see_source);
}
void modify_stat(stat_type which_stat, char amount, bool suppress_msg,
const monsters* cause)
{
if (cause == NULL || invalid_monster(cause))
{
modify_stat(which_stat, amount, suppress_msg, NULL, true);
return;
}
bool vis = you.can_see(cause);
std::string name = cause->name(DESC_NOCAP_A, true);
if (cause->has_ench(ENCH_SHAPESHIFTER))
name += " (shapeshifter)";
else if (cause->has_ench(ENCH_GLOWING_SHAPESHIFTER))
name += " (glowing shapeshifter)";
modify_stat(which_stat, amount, suppress_msg, name, vis);
}
void modify_stat(stat_type which_stat, char amount, bool suppress_msg,
const item_def &cause, bool removed)
{
std::string name = cause.name(DESC_NOCAP_THE, false, true, false, false,
ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES);
std::string verb;
switch (cause.base_type)
{
case OBJ_ARMOUR:
case OBJ_JEWELLERY:
if (removed)
verb = "removing";
else
verb = "wearing";
break;
case OBJ_WEAPONS:
case OBJ_STAVES:
if (removed)
verb = "unwielding";
else
verb = "wielding";
break;
case OBJ_WANDS: verb = "zapping"; break;
case OBJ_FOOD: verb = "eating"; break;
case OBJ_SCROLLS: verb = "reading"; break;
case OBJ_POTIONS: verb = "drinking"; break;
default: verb = "using";
}
modify_stat(which_stat, amount, suppress_msg,
verb + " " + name, true);
}
void dec_hp(int hp_loss, bool fatal, const char *aux)
{
ASSERT(!crawl_state.arena);
if (!fatal && you.hp < 1)
you.hp = 1;
if (!fatal && hp_loss >= you.hp)
hp_loss = you.hp - 1;
if (hp_loss < 1)
return;
// If it's not fatal, use ouch() so that notes can be taken. If it IS
// fatal, somebody else is doing the bookkeeping, and we don't want to mess
// with that.
if (!fatal && aux)
ouch(hp_loss, NON_MONSTER, KILLED_BY_SOMETHING, aux);
else
you.hp -= hp_loss;
you.redraw_hit_points = true;
}
void dec_mp(int mp_loss)
{
ASSERT(!crawl_state.arena);
if (mp_loss < 1)
return;
you.magic_points -= mp_loss;
you.magic_points = std::max(0, you.magic_points);
if (Options.magic_point_warning
&& you.magic_points < (you.max_magic_points
* Options.magic_point_warning) / 100)
{
mpr( "* * * LOW MAGIC WARNING * * *", MSGCH_DANGER );
}
take_note(Note(NOTE_MP_CHANGE, you.magic_points, you.max_magic_points));
you.redraw_magic_points = true;
}
bool enough_hp(int minimum, bool suppress_msg)
{
ASSERT(!crawl_state.arena);
// We want to at least keep 1 HP. -- bwr
if (you.hp < minimum + 1)
{
if (!suppress_msg)
mpr("You haven't enough vitality at the moment.");
crawl_state.cancel_cmd_again();
crawl_state.cancel_cmd_repeat();
return (false);
}
return (true);
}
bool enough_mp(int minimum, bool suppress_msg, bool include_items)
{
ASSERT(!crawl_state.arena);
bool rc = false;
if (get_real_mp(include_items) < minimum)
{
if (!suppress_msg)
mpr("You haven't enough magic capacity.");
}
else if (you.magic_points < minimum)
{
if (!suppress_msg)
mpr("You haven't enough magic at the moment.");
}
else
rc = true;
if (!rc)
{
crawl_state.cancel_cmd_again();
crawl_state.cancel_cmd_repeat();
}
return (rc);
}
// Note that "max_too" refers to the base potential, the actual
// resulting max value is subject to penalties, bonuses, and scalings.
void inc_mp(int mp_gain, bool max_too)
{
ASSERT(!crawl_state.arena);
if (mp_gain < 1)
return;
bool wasnt_max = (you.magic_points < you.max_magic_points);
you.magic_points += mp_gain;
if (max_too)
inc_max_mp(mp_gain);
if (you.magic_points > you.max_magic_points)
you.magic_points = you.max_magic_points;
if (wasnt_max && you.magic_points == you.max_magic_points)
interrupt_activity(AI_FULL_MP);
take_note(Note(NOTE_MP_CHANGE, you.magic_points, you.max_magic_points));
you.redraw_magic_points = true;
}
// Note that "max_too" refers to the base potential, the actual
// resulting max value is subject to penalties, bonuses, and scalings.
// To avoid message spam, don't take notes when HP increases.
void inc_hp(int hp_gain, bool max_too)
{
ASSERT(!crawl_state.arena);
if (hp_gain < 1)
return;
bool wasnt_max = (you.hp < you.hp_max);
you.hp += hp_gain;
if (max_too)
inc_max_hp( hp_gain );
if (you.hp > you.hp_max)
you.hp = you.hp_max;
if (wasnt_max && you.hp == you.hp_max)
interrupt_activity(AI_FULL_HP);
you.redraw_hit_points = true;
}
void rot_hp(int hp_loss)
{
you.base_hp -= hp_loss;
calc_hp();
if (you.species != SP_GHOUL)
xom_is_stimulated(hp_loss * 32);
you.redraw_hit_points = true;
}
void unrot_hp(int hp_recovered)
{
if (hp_recovered >= 5000 - you.base_hp)
you.base_hp = 5000;
else
you.base_hp += hp_recovered;
calc_hp();
you.redraw_hit_points = true;
}
int player_rotted()
{
return (5000 - you.base_hp);
}
void rot_mp(int mp_loss)
{
you.base_magic_points -= mp_loss;
calc_mp();
you.redraw_magic_points = true;
}
void inc_max_hp( int hp_gain )
{
you.base_hp2 += hp_gain;
calc_hp();
take_note(Note(NOTE_MAXHP_CHANGE, you.hp_max));
you.redraw_hit_points = true;
}
void dec_max_hp( int hp_loss )
{
you.base_hp2 -= hp_loss;
calc_hp();
take_note(Note(NOTE_MAXHP_CHANGE, you.hp_max));
you.redraw_hit_points = true;
}
void inc_max_mp( int mp_gain )
{
you.base_magic_points2 += mp_gain;
calc_mp();
take_note(Note(NOTE_MAXMP_CHANGE, you.max_magic_points));
you.redraw_magic_points = true;
}
void dec_max_mp( int mp_loss )
{
you.base_magic_points2 -= mp_loss;
calc_mp();
take_note(Note(NOTE_MAXMP_CHANGE, you.max_magic_points));
you.redraw_magic_points = true;
}
// Use of floor: false = hp max, true = hp min. {dlb}
void deflate_hp(int new_level, bool floor)
{
ASSERT(!crawl_state.arena);
if (floor && you.hp < new_level)
you.hp = new_level;
else if (!floor && you.hp > new_level)
you.hp = new_level;
// Must remain outside conditional, given code usage. {dlb}
you.redraw_hit_points = true;
}
// Note that "max_too" refers to the base potential, the actual
// resulting max value is subject to penalties, bonuses, and scalings.
void set_hp(int new_amount, bool max_too)
{
ASSERT(!crawl_state.arena);
if (you.hp != new_amount)
you.hp = new_amount;
if (max_too && you.hp_max != new_amount)
{
you.base_hp2 = 5000 + new_amount;
calc_hp();
}
if (you.hp > you.hp_max)
you.hp = you.hp_max;
if (max_too)
take_note(Note(NOTE_MAXHP_CHANGE, you.hp_max));
// Must remain outside conditional, given code usage. {dlb}
you.redraw_hit_points = true;
}
// Note that "max_too" refers to the base potential, the actual
// resulting max value is subject to penalties, bonuses, and scalings.
void set_mp(int new_amount, bool max_too)
{
ASSERT(!crawl_state.arena);
if (you.magic_points != new_amount)
you.magic_points = new_amount;
if (max_too && you.max_magic_points != new_amount)
{
// Note that this gets scaled down for values > 18.
you.base_magic_points2 = 5000 + new_amount;
calc_mp();
}
if (you.magic_points > you.max_magic_points)
you.magic_points = you.max_magic_points;
take_note(Note(NOTE_MP_CHANGE, you.magic_points, you.max_magic_points));
if (max_too)
take_note(Note(NOTE_MAXMP_CHANGE, you.max_magic_points));
// Must remain outside conditional, given code usage. {dlb}
you.redraw_magic_points = true;
}
// If trans is true, being berserk and/or transformed is taken into account
// here. Else, the base hp is calculated. If rotted is true, calculate the
// real max hp you'd have if the rotting was cured.
int get_real_hp(bool trans, bool rotted)
{
int hitp;
hitp = (you.base_hp - 5000) + (you.base_hp2 - 5000);
hitp += (you.experience_level * you.skills[SK_FIGHTING]) / 5;
// Being berserk makes you resistant to damage. I don't know why.
if (trans && you.berserk())
hitp *= 2;
if (trans)
{
// Some transformations give you extra hp.
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_STATUE:
hitp *= 15;
hitp /= 10;
break;
case TRAN_ICE_BEAST:
hitp *= 12;
hitp /= 10;
break;
case TRAN_DRAGON:
hitp *= 16;
hitp /= 10;
break;
}
}
if (rotted)
hitp += player_rotted();
// Frail and robust mutations, and divine vigour.
hitp *= 10 + player_mutation_level(MUT_ROBUST)
+ you.attribute[ATTR_DIVINE_VIGOUR]
- player_mutation_level(MUT_FRAIL);
hitp /= 10;
return (hitp);
}
int get_real_mp(bool include_items)
{
// base_magic_points2 accounts for species
int enp = (you.base_magic_points2 - 5000);
int spell_extra = (you.experience_level * you.skills[SK_SPELLCASTING]) / 4;
int invoc_extra = (you.experience_level * you.skills[SK_INVOCATIONS]) / 6;
enp += std::max(spell_extra, invoc_extra);
enp = stepdown_value(enp, 9, 18, 45, 100);
// This is our "rotted" base (applied after scaling):
enp += (you.base_magic_points - 5000);
// Yes, we really do want this duplication... this is so the stepdown
// doesn't truncate before we apply the rotted base. We're doing this
// the nice way. -- bwr
enp = std::min(enp, 50);
// Now applied after scaling so that power items are more useful -- bwr
if (include_items)
enp += player_magical_power();
// Analogous to ROBUST/FRAIL
enp *= 10 + player_mutation_level(MUT_HIGH_MAGIC)
+ you.attribute[ATTR_DIVINE_VIGOUR]
- player_mutation_level(MUT_LOW_MAGIC);
enp /= 10;
if (enp > 50)
enp = 50 + ((enp - 50) / 2);
enp = std::max(enp, 0);
return enp;
}
int get_contamination_level()
{
const int glow = you.magic_contamination;
if (glow > 60)
return (glow / 20 + 2);
if (glow > 40)
return (4);
if (glow > 25)
return (3);
if (glow > 15)
return (2);
if (glow > 5)
return (1);
return (0);
}
// controlled is true if the player actively did something to cause
// contamination (such as drink a known potion of resistance),
// status_only is true only for the status output
void contaminate_player(int change, bool controlled, bool status_only)
{
ASSERT(!crawl_state.arena);
if (status_only && !you.magic_contamination)
return;
// get current contamination level
int old_amount = you.magic_contamination;
int old_level = get_contamination_level();
int new_level = 0;
#if DEBUG_DIAGNOSTICS
if (change > 0 || (change < 0 && you.magic_contamination))
{
mprf(MSGCH_DIAGNOSTICS, "change: %d radiation: %d",
change, change + you.magic_contamination );
}
#endif
// make the change
if (change + you.magic_contamination < 0)
you.magic_contamination = 0;
else
{
if (change + you.magic_contamination > 250)
you.magic_contamination = 250;
else
you.magic_contamination += change;
}
// figure out new level
new_level = get_contamination_level();
if (status_only || (new_level >= 1 && old_level == 0)
|| (old_amount == 0 && you.magic_contamination > 0))
{
if (new_level > 3)
{
mpr( (new_level == 4) ?
"Your entire body has taken on an eerie glow!" :
"You are engulfed in a nimbus of crackling magics!");
}
else if (new_level == 0 && old_amount == 0
&& you.magic_contamination > 0)
{
mpr("You are very lightly contaminated with residual magic.");
}
else
{
mprf("You are %s with residual magics%s",
(new_level == 3) ? "practically glowing" :
(new_level == 2) ? "heavily infused" :
(new_level == 1) ? "contaminated"
: "lightly contaminated",
(new_level == 3) ? "!" : ".");
}
}
else if (new_level != old_level)
{
mprf((change > 0) ? MSGCH_WARN : MSGCH_RECOVERY,
"You feel %s contaminated with magical energies.",
(change > 0) ? "more" : "less" );
if (change > 0)
xom_is_stimulated(new_level * 32);
if (new_level == 0 && you.duration[DUR_INVIS] && !you.backlit())
{
mpr("You fade completely from view now that you are no longer "
"glowing from magical contamination.");
}
}
else if (old_level == 0 && old_amount > 0 && you.magic_contamination == 0)
mpr("Your magical contamination has completely faded away.");
if (status_only)
return;
if (you.magic_contamination > 0)
learned_something_new(TUT_GLOWING);
// Zin doesn't like mutations or mutagenic radiation.
if (you.religion == GOD_ZIN)
{
// Whenever the glow status is first reached, give a warning message.
if (old_level < 1 && new_level >= 1)
did_god_conduct(DID_CAUSE_GLOWING, 0, false);
// If the player actively did something to increase glowing,
// Zin is displeased.
else if (controlled && change > 0 && old_level > 0)
did_god_conduct(DID_CAUSE_GLOWING, 1 + new_level, true);
}
}
bool confuse_player(int amount, bool resistable)
{
ASSERT(!crawl_state.arena);
if (amount <= 0)
return (false);
if (resistable && wearing_amulet(AMU_CLARITY))
{
mpr("You feel momentarily confused.");
// Identify the amulet if necessary.
if (!extrinsic_amulet_effect(AMU_CLARITY))
{
// Since it's not extrinsic, it must be from the amulet.
ASSERT(player_wearing_slot(EQ_AMULET));
item_def* const amu = you.slot_item(EQ_AMULET);
if (!item_ident(*amu, ISFLAG_KNOW_TYPE))
{
set_ident_flags(*amu, ISFLAG_KNOW_TYPE);
mprf("You are wearing: %s",
amu->name(DESC_INVENTORY_EQUIP).c_str());
}
}
return (false);
}
const int old_value = you.duration[DUR_CONF];
you.increase_duration(DUR_CONF, amount, 40);
if (you.duration[DUR_CONF] > old_value)
{
you.check_awaken(500);
mprf(MSGCH_WARN, "You are %sconfused.",
old_value > 0 ? "more " : "");
learned_something_new(TUT_YOU_ENCHANTED);
xom_is_stimulated((you.duration[DUR_CONF] - old_value)
/ BASELINE_DELAY);
}
return (true);
}
bool curare_hits_player(int death_source, int amount)
{
ASSERT(!crawl_state.arena);
poison_player(amount);
const bool res_poison = player_res_poison() > 0;
int hurted = 0;
if (you.res_asphyx() <= 0)
{
hurted = roll_dice(2, 6);
// Note that the hurtage is halved by poison resistance.
if (res_poison)
hurted /= 2;
if (hurted)
{
mpr("You have difficulty breathing.");
ouch(hurted, death_source, KILLED_BY_CURARE,
"curare-induced apnoea");
}
potion_effect(POT_SLOWING, 2 + random2(4 + amount));
}
return (hurted > 0);
}
bool poison_player(int amount, bool force)
{
ASSERT(!crawl_state.arena);
if (!force && player_res_poison() > 0 || amount <= 0)
return (false);
const int old_value = you.duration[DUR_POISONING];
you.duration[DUR_POISONING] += amount;
if (you.duration[DUR_POISONING] > 40)
you.duration[DUR_POISONING] = 40;
if (you.duration[DUR_POISONING] > old_value)
{
mprf(MSGCH_WARN, "You are %spoisoned.",
old_value > 0 ? "more " : "");
learned_something_new(TUT_YOU_POISON);
}
return (true);
}
void dec_poison_player()
{
// If Cheibriados has slowed your life processes, there's a
// chance that your poison level is simply unaffected and
// you aren't hurt by poison.
if (GOD_CHEIBRIADOS == you.religion
&& you.piety >= piety_breakpoint(0)
&& coinflip())
return;
if (you.duration[DUR_POISONING] > 0)
{
if (x_chance_in_y(you.duration[DUR_POISONING], 5))
{
int hurted = 1;
msg_channel_type channel = MSGCH_PLAIN;
const char *adj = "";
if (you.duration[DUR_POISONING] > 10
&& random2(you.duration[DUR_POISONING]) >= 8)
{
hurted = random2(10) + 5;
channel = MSGCH_DANGER;
adj = "extremely ";
}
else if (you.duration[DUR_POISONING] > 5 && coinflip())
{
hurted = coinflip() ? 3 : 2;
channel = MSGCH_WARN;
adj = "very ";
}
int oldhp = you.hp;
ouch(hurted, NON_MONSTER, KILLED_BY_POISON);
if (you.hp < oldhp)
mprf(channel, "You feel %ssick.", adj);
if ((you.hp == 1 && one_chance_in(3)) || one_chance_in(8))
reduce_poison_player(1);
}
}
}
void reduce_poison_player(int amount)
{
if (amount <= 0)
return;
const int old_value = you.duration[DUR_POISONING];
you.duration[DUR_POISONING] -= amount;
if (you.duration[DUR_POISONING] < 0)
you.duration[DUR_POISONING] = 0;
if (you.duration[DUR_POISONING] < old_value)
{
mprf(MSGCH_RECOVERY, "You feel %sbetter.",
you.duration[DUR_POISONING] > 0 ? "a little " : "");
}
}
bool miasma_player()
{
ASSERT(!crawl_state.arena);
if (you.res_rotting())
return (false);
// Zin's protection.
if (you.religion == GOD_ZIN && x_chance_in_y(you.piety, MAX_PIETY))
{
simple_god_message(" protects your body from miasma!");
return (false);
}
bool success = poison_player(1);
if (you.hp_max > 4 && coinflip())
{
rot_hp(1);
success = true;
}
if (one_chance_in(3))
{
potion_effect(POT_SLOWING, 5);
success = true;
}
return (success);
}
bool napalm_player(int amount)
{
ASSERT(!crawl_state.arena);
if (player_res_sticky_flame() || amount <= 0)
return (false);
const int old_value = you.duration[DUR_LIQUID_FLAMES];
you.increase_duration(DUR_LIQUID_FLAMES, amount, 100);
if (you.duration[DUR_LIQUID_FLAMES] > old_value)
mpr("You are covered in liquid flames!", MSGCH_WARN);
return (true);
}
void dec_napalm_player(int delay)
{
if (you.duration[DUR_LIQUID_FLAMES] > BASELINE_DELAY)
{
if (feat_is_watery(grd(you.pos())))
{
mpr("The flames go out!", MSGCH_WARN);
you.duration[DUR_LIQUID_FLAMES] = 0;
return;
}
mpr("You are covered in liquid flames!", MSGCH_WARN);
expose_player_to_element(BEAM_NAPALM, 12);
const int res_fire = player_res_fire();
if (res_fire > 0)
{
ouch((((random2avg(9, 2) + 1) * delay) /
(1 + (res_fire * res_fire))) / BASELINE_DELAY, NON_MONSTER,
KILLED_BY_BURNING);
}
if (res_fire <= 0)
{
ouch(((random2avg(9, 2) + 1) * delay) / BASELINE_DELAY,
NON_MONSTER, KILLED_BY_BURNING);
if (res_fire < 0)
{
ouch(((random2avg(9, 2) + 1) * delay)
/ BASELINE_DELAY, NON_MONSTER, KILLED_BY_BURNING);
}
}
if (you.duration[DUR_CONDENSATION_SHIELD] > 0)
remove_condensation_shield();
}
you.duration[DUR_LIQUID_FLAMES] -= delay;
if (you.duration[DUR_LIQUID_FLAMES] < 0)
you.duration[DUR_LIQUID_FLAMES] = 0;
}
bool slow_player(int turns)
{
ASSERT(!crawl_state.arena);
if (turns <= 0)
return (false);
if (stasis_blocks_effect(true, "%s rumbles.", 20, "%s rumbles."))
return (false);
// Doubling these values because moving while slowed takes twice the
// usual delay.
turns *= 2;
int threshold = 100 * 2;
if (you.duration[DUR_SLOW] >= threshold * BASELINE_DELAY)
mpr("You already are as slow as you could be.");
else
{
if (you.duration[DUR_SLOW] == 0)
mpr("You feel yourself slow down.");
else
mpr("You feel as though you will be slow longer.");
you.increase_duration(DUR_SLOW, turns, threshold);
learned_something_new(TUT_YOU_ENCHANTED);
}
return (true);
}
void dec_slow_player(int delay)
{
if (!you.duration[DUR_SLOW])
return;
if (you.duration[DUR_SLOW] > BASELINE_DELAY)
{
// BCR - Amulet of resist slow affects slow counter.
you.duration[DUR_SLOW] -= delay;
}
if (you.duration[DUR_SLOW] <= BASELINE_DELAY)
{
mpr("You feel yourself speed up.", MSGCH_DURATION);
you.duration[DUR_SLOW] = 0;
}
}
bool haste_player(int turns)
{
ASSERT(!crawl_state.arena);
if (turns <= 0)
return (false);
if (stasis_blocks_effect(true, "%s emits a piercing whistle.", 20,
"%s makes your neck tingle."))
{
return (false);
}
// Cutting the nominal turns in half since hasted actions take half the
// usual delay.
turns /= 2;
const int threshold = 40;
if (you.duration[DUR_HASTE] == 0)
mpr("You feel yourself speed up.");
else if (you.duration[DUR_HASTE] > threshold * BASELINE_DELAY)
mpr("You already have as much speed as you can handle.");
else
{
mpr("You feel as though your hastened speed will last longer.");
contaminate_player(1, true); // always deliberate
}
you.increase_duration(DUR_HASTE, turns, threshold);
did_god_conduct(DID_STIMULANTS, 4 + random2(4));
return (true);
}
void dec_haste_player(int delay)
{
if (!you.duration[DUR_HASTE])
return;
if (you.duration[DUR_HASTE] > BASELINE_DELAY)
{
int old_dur = you.duration[DUR_HASTE];
you.duration[DUR_HASTE] -= delay;
int threshold = 6 * BASELINE_DELAY;
// message if we cross the threshold
if (old_dur > threshold && you.duration[DUR_HASTE] <= threshold)
{
mpr("Your extra speed is starting to run out.", MSGCH_DURATION);
if (coinflip())
you.duration[DUR_HASTE] -= BASELINE_DELAY;
}
}
else if (you.duration[DUR_HASTE] <= BASELINE_DELAY)
{
mpr("You feel yourself slow down.", MSGCH_DURATION);
you.duration[DUR_HASTE] = 0;
}
}
void dec_disease_player(int delay)
{
if (you.disease > 0)
{
// If Cheibriados has slowed your life processes, there's a
// chance that your disease level is unaffected.
if (GOD_CHEIBRIADOS == you.religion
&& you.piety >= piety_breakpoint(0)
&& coinflip())
{
return;
}
you.disease -= delay;
if(you.disease < 0)
you.disease = 0;
// kobolds and regenerators recuperate quickly
if (you.disease > 5 * BASELINE_DELAY
&& (you.species == SP_KOBOLD
|| you.duration[DUR_REGENERATION]
|| player_mutation_level(MUT_REGENERATION) == 3))
{
you.disease -= 2 * BASELINE_DELAY;
}
if (you.disease == 0)
mpr("You feel your health improve.", MSGCH_RECOVERY);
}
}
int count_worn_ego(int which_ego)
{
int result = 0;
for (int slot = EQ_CLOAK; slot <= EQ_BODY_ARMOUR; ++slot)
{
if (you.equip[slot] != -1
&& get_armour_ego_type(you.inv[you.equip[slot]]) == which_ego)
{
result++;
}
}
return (result);
}
static int _strength_modifier()
{
int result = 0;
if (you.duration[DUR_MIGHT])
result += 5;
if (you.duration[DUR_DIVINE_STAMINA])
result += you.attribute[ATTR_DIVINE_STAMINA];
// ego items of strength
result += 3 * count_worn_ego(SPARM_STRENGTH);
// rings of strength
result += player_equip(EQ_RINGS_PLUS, RING_STRENGTH);
// randarts of strength
result += scan_artefacts(ARTP_STRENGTH);
// mutations
result += player_mutation_level(MUT_STRONG)
- player_mutation_level(MUT_WEAK);
result += player_mutation_level(MUT_STRONG_STIFF)
- player_mutation_level(MUT_FLEXIBLE_WEAK);
// transformations
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_STATUE: result += 2; break;
case TRAN_DRAGON: result += 10; break;
case TRAN_LICH: result += 3; break;
case TRAN_BAT: result -= 5; break;
default: break;
}
return (result);
}
static int _int_modifier()
{
int result = 0;
if (you.duration[DUR_BRILLIANCE])
result += 5;
if (you.duration[DUR_DIVINE_STAMINA])
result += you.attribute[ATTR_DIVINE_STAMINA];
// ego items of intelligence
result += 3 * count_worn_ego(SPARM_INTELLIGENCE);
// rings of intelligence
result += player_equip(EQ_RINGS_PLUS, RING_INTELLIGENCE);
// randarts of intelligence
result += scan_artefacts(ARTP_INTELLIGENCE);
// mutations
result += player_mutation_level(MUT_CLEVER)
- player_mutation_level(MUT_DOPEY);
return (result);
}
static int _dex_modifier()
{
int result = 0;
if (you.duration[DUR_AGILITY])
result += 5;
if (you.duration[DUR_DIVINE_STAMINA])
result += you.attribute[ATTR_DIVINE_STAMINA];
// ego items of dexterity
result += 3 * count_worn_ego(SPARM_DEXTERITY);
// rings of dexterity
result += player_equip(EQ_RINGS_PLUS, RING_DEXTERITY);
// randarts of dexterity
result += scan_artefacts(ARTP_DEXTERITY);
// mutations
result += player_mutation_level(MUT_AGILE)
- player_mutation_level(MUT_CLUMSY);
result += player_mutation_level(MUT_FLEXIBLE_WEAK)
- player_mutation_level(MUT_STRONG_STIFF);
result -= player_mutation_level(MUT_BLACK_SCALES);
result -= player_mutation_level(MUT_BONEY_PLATES);
const int grey2_modifier[] = { 0, -1, -1, -2 };
const int metallic_modifier[] = { 0, -2, -3, -4 };
const int yellow_modifier[] = { 0, 0, -1, -2 };
const int red2_modifier[] = { 0, 0, -1, -2 };
result += grey2_modifier[player_mutation_level(MUT_GREY2_SCALES)];
result += metallic_modifier[player_mutation_level(MUT_METALLIC_SCALES)];
result += yellow_modifier[player_mutation_level(MUT_YELLOW_SCALES)];
result += red2_modifier[player_mutation_level(MUT_RED2_SCALES)];
// transformations
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER: result += 5; break;
case TRAN_STATUE: result -= 2; break;
case TRAN_BAT: result += 5; break;
default: break;
}
return (result);
}
int stat_modifier(stat_type stat)
{
switch (stat)
{
case STAT_STRENGTH: return _strength_modifier();
case STAT_INTELLIGENCE: return _int_modifier();
case STAT_DEXTERITY: return _dex_modifier();
default:
mprf(MSGCH_ERROR, "Bad stat: %d", stat);
return 0;
}
}
bool player::is_habitable_feat(dungeon_feature_type actual_grid) const
{
if (!can_pass_through_feat(actual_grid))
return (false);
if (airborne())
return (true);
if (actual_grid == DNGN_LAVA
|| actual_grid == DNGN_DEEP_WATER && !can_swim())
{
return (false);
}
return (true);
}
player::player()
: m_quiver(0)
{
init();
kills = new KillMaster(); // why isn't this done in init()?
}
player::player(const player &other)
: m_quiver(0)
{
init();
// why doesn't this do a copy_from?
player_quiver* saved_quiver = m_quiver;
*this = other;
m_quiver = saved_quiver;
kills = new KillMaster(*(other.kills));
*m_quiver = *(other.m_quiver);
}
// why is this not called "operator="?
void player::copy_from(const player &other)
{
if (this == &other)
return;
KillMaster *saved_kills = kills;
player_quiver* saved_quiver = m_quiver;
*this = other;
kills = saved_kills;
*kills = *(other.kills);
m_quiver = saved_quiver;
*m_quiver = *(other.m_quiver);
}
// player struct initialization
void player::init()
{
birth_time = time( NULL );
real_time = 0;
num_turns = 0L;
last_view_update = 0L;
#ifdef WIZARD
wizard = (Options.wiz_mode == WIZ_YES) ? true : false;
#else
wizard = false;
#endif
your_name = "";
banished = false;
banished_by.clear();
entering_level = false;
lava_in_sight = -1;
water_in_sight = -1;
transit_stair = DNGN_UNSEEN;
berserk_penalty = 0;
disease = 0;
elapsed_time = 0;
rotting = 0;
unrand_reacts = 0;
magic_contamination = 0;
base_hp = 5000;
base_hp2 = 5000;
hp_max = 0;
base_magic_points = 5000;
base_magic_points2 = 5000;
max_magic_points = 0;
magic_points_regeneration = 0;
strength = 0;
max_strength = 0;
intel = 0;
max_intel = 0;
dex = 0;
max_dex = 0;
experience = 0;
experience_level = 1;
max_level = 1;
char_class = JOB_UNKNOWN;
species = SP_UNKNOWN;
hunger = 6000;
hunger_state = HS_SATIATED;
wield_change = false;
redraw_quiver = false;
received_weapon_warning = false;
gold = 0;
// speed = 10; // 0.75; // unused
burden = 0;
burden_state = BS_UNENCUMBERED;
spell_no = 0;
your_level = 0;
level_type = LEVEL_DUNGEON;
entry_cause = EC_SELF_EXPLICIT;
entry_cause_god = GOD_NO_GOD;
where_are_you = BRANCH_MAIN_DUNGEON;
char_direction = GDT_DESCENDING;
opened_zot = false;
royal_jelly_dead = false;
prev_targ = MHITNOT;
pet_target = MHITNOT;
prev_grd_targ.reset();
position.reset();
prev_move.reset();
running.clear();
travel_x = 0;
travel_y = 0;
travel_z = level_id();
religion = GOD_NO_GOD;
piety = 0;
piety_hysteresis = 0;
gift_timeout = 0;
penance.init(0);
worshipped.init(0);
num_gifts.init(0);
equip.init(-1);
spells.init(SPELL_NO_SPELL);
spell_letter_table.init(-1);
ability_letter_table.init(ABIL_NON_ABILITY);
mutation.init(0);
demon_pow.init(0);
demonic_traits.clear();
had_book.init(false);
unique_items.init(UNIQ_NOT_EXISTS);
skills.init(0);
skill_points.init(0);
skill_order.init(MAX_SKILL_ORDER);
practise_skill.init(true);
skill_cost_level = 1;
total_skill_points = 0;
attribute.init(0);
quiver.init(ENDOFPACK);
sacrifice_value.init(0);
for (int i = 0; i < ENDOFPACK; i++)
inv[i].clear();
duration.init(0);
exp_available = 25;
global_info.make_global();
global_info.assert_validity();
for (int i = 0; i < NUM_BRANCHES; i++)
{
branch_info[i].level_type = LEVEL_DUNGEON;
branch_info[i].branch = i;
branch_info[i].assert_validity();
}
for (int i = 0; i < (NUM_LEVEL_AREA_TYPES - 1); i++)
{
non_branch_info[i].level_type = i + 1;
non_branch_info[i].branch = -1;
non_branch_info[i].assert_validity();
}
#ifdef USE_TILE
last_clicked_grid = coord_def();
last_clicked_item = -1;
#endif
if (m_quiver)
delete m_quiver;
m_quiver = new player_quiver;
// Currently only set if Xom accidentally kills the player.
reset_escaped_death();
on_current_level = true;
#if WIZARD || DEBUG
you.never_die = false;
#endif
}
player_save_info player_save_info::operator=(const player& rhs)
{
name = rhs.your_name;
experience = rhs.experience;
experience_level = rhs.experience_level;
wizard = rhs.wizard;
species = rhs.species;
class_name = rhs.class_name;
religion = rhs.religion;
second_god_name = rhs.second_god_name;
#ifdef USE_TILE
held_in_net = false;
#endif
return (*this);
}
bool player_save_info::operator<(const player_save_info& rhs) const
{
return experience < rhs.experience
|| (experience == rhs.experience && name < rhs.name);
}
std::string player_save_info::short_desc() const
{
std::ostringstream desc;
desc << name << ", a level " << experience_level << ' '
<< species_name(species, experience_level) << ' '
<< class_name;
if (religion == GOD_JIYVA)
desc << " of " << god_name_jiyva(true);
else if (religion != GOD_NO_GOD)
desc << " of " << god_name(religion);
#ifdef WIZARD
if (wizard)
desc << " (WIZ)";
#endif
return desc.str();
}
player::~player()
{
delete kills;
delete m_quiver;
}
bool player::is_levitating() const
{
return (duration[DUR_LEVITATION]);
}
bool player::in_water() const
{
return (!airborne() && !beogh_water_walk()
&& feat_is_water(grd(this->pos())));
}
bool player::can_swim() const
{
// Transforming could be fatal if it would cause unequipment of
// stat-boosting boots or heavy armour.
return (species == SP_MERFOLK && merfolk_change_is_safe(true));
}
int player::visible_igrd(const coord_def &where) const
{
if (grd(where) == DNGN_LAVA
|| (grd(where) == DNGN_DEEP_WATER && species != SP_MERFOLK))
{
return (NON_ITEM);
}
return igrd(where);
}
bool player::swimming() const
{
return in_water() && can_swim();
}
bool player::submerged() const
{
return (false);
}
bool player::has_spell(spell_type spell) const
{
for (int i = 0; i < 25; i++)
{
if (spells[i] == spell)
return (true);
}
return (false);
}
bool player::extra_balanced() const
{
return (species == SP_NAGA && !transform_changed_physiology());
}
bool player::floundering() const
{
return in_water() && !can_swim() && !extra_balanced();
}
bool player::can_pass_through_feat(dungeon_feature_type grid) const
{
return !feat_is_solid(grid);
}
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(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 (attribute[ATTR_TRANSFORMATION])
{
case TRAN_STATUE:
weight *= 2;
break;
case TRAN_LICH:
weight /= 2;
break;
default:
break;
}
return (weight);
}
int player::total_weight() const
{
return (body_weight() + burden);
}
bool player::cannot_speak() const
{
if (silenced(this->pos()))
return (true);
if (this->cannot_move()) // we allow talking during sleep ;)
return (true);
// No transform that prevents the player from speaking yet.
return (false);
}
std::string player::shout_verb() const
{
const int transform = attribute[ATTR_TRANSFORMATION];
switch (transform)
{
case TRAN_DRAGON:
return "roar";
case TRAN_SPIDER:
return "hiss";
case TRAN_BAT:
return "squeak";
case TRAN_PIG:
return "squeal";
default: // depends on SCREAM mutation
int level = player_mutation_level(MUT_SCREAM);
if (level <= 1)
return "shout";
else if (level == 2)
return "yell";
else // level == 3
return "scream";
}
}
int player::damage_type(int)
{
const int wpn = equip[EQ_WEAPON];
if (wpn != -1)
return (get_vorpal_type(inv[wpn]));
else if (attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
return (DVORP_SLICING);
else if (has_usable_claws())
return (DVORP_CLAWING);
return (DVORP_CRUSHING);
}
int player::damage_brand(int)
{
int ret = SPWPN_NORMAL;
const int wpn = equip[EQ_WEAPON];
if (wpn != -1)
{
if (!is_range_weapon(inv[wpn]))
ret = get_weapon_brand(inv[wpn]);
}
else if (duration[DUR_CONFUSING_TOUCH])
ret = SPWPN_CONFUSE;
else if (mutation[MUT_DRAIN_LIFE])
ret = SPWPN_DRAINING;
else
{
switch (attribute[ATTR_TRANSFORMATION])
{
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 (this->species == SP_VAMPIRE && one_chance_in(8))
ret = SPWPN_VAMPIRICISM;
break;
default:
break;
}
}
return (ret);
}
// Returns the item in the given equipment slot, NULL if the slot is empty.
// eq must be in [EQ_WEAPON, EQ_AMULET], or bad things will happen.
item_def *player::slot_item(equipment_type eq)
{
ASSERT(eq >= EQ_WEAPON && eq <= EQ_AMULET);
const int item = equip[eq];
return (item == -1 ? NULL : &inv[item]);
}
// Returns the item in the player's weapon slot.
item_def *player::weapon(int /* which_attack */)
{
return (slot_item(EQ_WEAPON));
}
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, body_size()) == HANDS_TWO;
if (two_handed && !ignore_shield && player_wearing_slot(EQ_SHIELD))
return (false);
return could_wield(item, ignore_brand, ignore_transform);
}
bool player::could_wield(const item_def &item, bool ignore_brand,
bool /* ignore_transform */) const
{
if (body_size(PSIZE_TORSO) < SIZE_LARGE && item_mass(item) >= 300)
return (false);
// Small species wielding large weapons...
if (body_size(PSIZE_BODY) < SIZE_MEDIUM
&& !check_weapon_wieldable_size(item, body_size(PSIZE_BODY)))
{
return (false);
}
if (!ignore_brand)
{
if (undead_or_demonic() && is_holy_item(item))
return (false);
}
return (true);
}
// Returns the shield the player is wearing, or NULL if none.
item_def *player::shield()
{
if (!you_tran_can_wear(EQ_SHIELD))
return (NULL);
return (slot_item(EQ_SHIELD));
}
std::string player::name(description_level_type type, bool) const
{
return (pronoun_you(type));
}
std::string player::pronoun(pronoun_type pro, bool) const
{
switch (pro)
{
default:
case PRONOUN_CAP: return "You";
case PRONOUN_NOCAP: return "you";
case PRONOUN_CAP_POSSESSIVE: return "Your";
case PRONOUN_NOCAP_POSSESSIVE: return "your";
case PRONOUN_REFLEXIVE: return "yourself";
case PRONOUN_OBJECTIVE: return "you";
}
}
std::string player::conj_verb(const std::string &verb) const
{
return (verb);
}
std::string player::hand_name(bool plural, bool *can_plural) const
{
if (can_plural != NULL)
*can_plural = true;
return your_hand(plural);
}
std::string player::foot_name(bool plural, bool *can_plural) const
{
bool _can_plural;
if (can_plural == NULL)
can_plural = &_can_plural;
*can_plural = true;
std::string str;
if (this->attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER)
str = "hind leg";
else if (!transform_changed_physiology())
{
if (player_mutation_level(MUT_HOOVES))
str = "hoof";
else if (player_mutation_level(MUT_TALONS))
str = "talon";
else if (this->species == SP_NAGA)
{
str = "underbelly";
*can_plural = false;
}
else if (this->species == SP_MERFOLK && this->swimming())
{
str = "tail";
*can_plural = false;
}
}
if (str.empty())
return (plural ? "feet" : "foot");
if (plural && *can_plural)
str = pluralise(str);
return str;
}
std::string player::arm_name(bool plural, bool *can_plural) const
{
if (transform_changed_physiology())
return hand_name(plural, can_plural);
if (can_plural != NULL)
*can_plural = true;
std::string str = "arm";
if (player_genus(GENPC_DRACONIAN) || this->species == SP_NAGA)
str = "scaled arm";
else if (this->species == SP_KENKU)
str = "feathered arm";
else if (this->species == SP_MUMMY)
str = "bandage wrapped arm";
if (plural)
str = pluralise(str);
return (str);
}
monster_type player::id() const
{
return (MONS_PLAYER);
}
int player::mindex() const
{
return (MHITYOU);
}
int player::get_experience_level() const
{
return (experience_level);
}
bool player::alive() const
{
// Simplistic, but if the player dies the game is over anyway, so
// nobody can ask further questions.
return (true);
}
bool player::is_summoned(int* _duration, int* summon_type) const
{
if (_duration != NULL)
*_duration = -1;
if (summon_type != NULL)
*summon_type = 0;
return (false);
}
bool player::fumbles_attack(bool verbose)
{
// Fumbling in shallow water.
if (floundering())
{
if (x_chance_in_y(4, dex) || one_chance_in(5))
{
if (verbose)
mpr("Unstable footing causes you to fumble your attack.");
return (true);
}
}
return (false);
}
bool player::cannot_fight() const
{
return (false);
}
// If you have a randart equipped that has the ARTP_ANGRY property,
// there's a 1/20 chance of it becoming activated whenever you
// attack a monster. (Same as the berserk mutation at level 1.)
// The probabilities for actually going berserk are cumulative!
static bool _equipment_make_berserk()
{
for (int eq = EQ_WEAPON; eq < NUM_EQUIP; eq++)
{
const item_def *item = you.slot_item((equipment_type) eq);
if (!item)
continue;
if (!is_artefact(*item))
continue;
if (artefact_wpn_property(*item, ARTP_ANGRY) && one_chance_in(20))
return (true);
}
// nothing found
return (false);
}
void player::attacking(actor *other)
{
ASSERT(!crawl_state.arena);
if (other && other->atype() == ACT_MONSTER)
{
const monsters *mon = dynamic_cast<monsters*>(other);
if (!mon->friendly() && !mon->neutral())
pet_target = mon->mindex();
}
if (player_mutation_level(MUT_BERSERK)
&& x_chance_in_y(player_mutation_level(MUT_BERSERK) * 10 - 5, 100)
|| _equipment_make_berserk())
{
go_berserk(false);
}
}
void player::go_berserk(bool intentional, bool potion)
{
::go_berserk(intentional, potion);
}
bool player::can_go_berserk() const
{
return (can_go_berserk(false));
}
bool player::can_go_berserk(bool intentional, bool potion) const
{
const bool verbose = intentional || potion;
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 (beheld())
{
if (verbose)
mpr("You are too mesmerised to rage.");
// or else they won't notice -- no message here
return (false);
}
if (is_undead
&& (is_undead != US_SEMI_UNDEAD || hunger_state <= HS_SATIATED))
{
if (verbose)
mpr("You cannot raise a blood rage in your lifeless body.");
// or else you won't notice -- no message here
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 (wearing_amulet(AMU_STASIS, false))
{
if (verbose)
{
const item_def *amulet = you.slot_item(EQ_AMULET);
mprf("You cannot go berserk with %s on.",
amulet? amulet->name(DESC_NOCAP_YOUR).c_str() : "your amulet");
}
return (false);
}
if (!intentional && !potion && player_mental_clarity(true))
{
if (verbose)
{
mpr("You're too calm and focused to rage.");
item_def *amu;
if (!player_mental_clarity(false) && wearing_amulet(AMU_CLARITY)
&& (amu = &you.inv[you.equip[EQ_AMULET]]) && !item_type_known(*amu))
{
set_ident_type(amu->base_type, amu->sub_type, ID_KNOWN_TYPE);
set_ident_flags(*amu, ISFLAG_KNOW_PROPERTIES);
mprf("You are wearing: %s",
amu->name(DESC_INVENTORY_EQUIP).c_str());
}
}
return (false);
}
return (true);
}
bool player::berserk() const
{
return (duration[DUR_BERSERKER]);
}
void player::god_conduct(conduct_type thing_done, int level)
{
::did_god_conduct(thing_done, level);
}
void player::banish(const std::string &who)
{
ASSERT(!crawl_state.arena);
banished = true;
banished_by = who;
}
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);
}
// For semi-undead species (Vampire!) reduce food cost for spells and abilities
// to 50% (hungry, very hungry) or zero (near starving, starving).
int calc_hunger(int food_cost)
{
if (you.is_undead == US_SEMI_UNDEAD && you.hunger_state < HS_SATIATED)
{
if (you.hunger_state <= HS_NEAR_STARVING)
return 0;
return (food_cost/2);
}
return (food_cost);
}
int player::warding() const
{
if (wearing_amulet(AMU_WARDING))
return (30);
return (0);
}
bool player::paralysed() const
{
return (duration[DUR_PARALYSIS]);
}
bool player::cannot_move() const
{
return (paralysed() || petrified());
}
bool player::confused() const
{
return (duration[DUR_CONF]);
}
bool player::caught() const
{
return (attribute[ATTR_HELD]);
}
bool player::petrified() const
{
return (duration[DUR_PETRIFIED]);
}
int player::shield_block_penalty() const
{
return (5 * shield_blocks * shield_blocks);
}
int player::shield_bonus() const
{
const int shield_class = player_shield_class();
if (shield_class <= 0)
return (-100);
return random2avg(shield_class * 2, 2) / 3 - 1;
}
int player::shield_bypass_ability(int tohit) const
{
return (15 + tohit / 2);
}
void player::shield_block_succeeded(actor *foe)
{
actor::shield_block_succeeded(foe);
shield_blocks++;
if (coinflip())
exercise(SK_SHIELDS, 1);
}
bool player::wearing_light_armour(bool with_skill) const
{
return (player_light_armour(with_skill));
}
void player::exercise(skill_type sk, int qty)
{
::exercise(sk, qty);
}
int player::skill(skill_type sk, bool bump) const
{
return (bump? skill_bump(sk) : skills[sk]);
}
int player::armour_class() const
{
int AC = 0;
for (int eq = EQ_CLOAK; eq <= EQ_BODY_ARMOUR; ++eq)
{
if (eq == EQ_SHIELD)
continue;
if (!player_wearing_slot(eq))
continue;
const item_def& item = inv[equip[eq]];
const int ac_value = property(item, PARM_AC ) * 100;
int racial_bonus = _player_armour_racial_bonus(item);
AC += ac_value * (30 + 2 * skills[SK_ARMOUR] + racial_bonus) / 30;
AC += item.plus * 100;
// The deformed don't fit into body armour very well.
// (This includes nagas and centaurs.)
if (eq == EQ_BODY_ARMOUR && player_mutation_level(MUT_DEFORMED))
AC -= ac_value / 2;
}
AC += player_equip( EQ_RINGS_PLUS, RING_PROTECTION ) * 100;
if (player_equip_ego_type( EQ_WEAPON, SPWPN_PROTECTION ))
AC += 500;
if (player_equip_ego_type( EQ_SHIELD, SPARM_PROTECTION ))
AC += 300;
AC += scan_artefacts(ARTP_AC) * 100;
if (duration[DUR_ICY_ARMOUR])
AC += 400 + 100 * skills[SK_ICE_MAGIC] / 3; // max 13
if (duration[DUR_STONEMAIL])
AC += 500 + 100 * skills[SK_EARTH_MAGIC] / 2; // max 18
if (duration[DUR_STONESKIN])
AC += 200 + 100 * skills[SK_EARTH_MAGIC] / 5; // max 7
if (mutation[MUT_ICEMAIL])
AC += (100 * ICEMAIL_MAX)
- (duration[DUR_ICEMAIL_DEPLETED] * 100
* ICEMAIL_MAX / ICEMAIL_TIME);
if (attribute[ATTR_TRANSFORMATION] == TRAN_NONE
|| attribute[ATTR_TRANSFORMATION] == TRAN_LICH
|| attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
{
// Being a lich doesn't preclude the benefits of hide/scales -- bwr
//
// Note: Even though necromutation is a high level spell, it does
// allow the character full armour (so the bonus is low). -- bwr
if (attribute[ATTR_TRANSFORMATION] == TRAN_LICH)
AC += (300 + 100 * skills[SK_NECROMANCY] / 6); // max 7
//jmf: only give:
if (player_genus(GENPC_DRACONIAN))
{
if (experience_level < 8)
AC += 200;
else if (species == SP_GREY_DRACONIAN)
AC += 100 + 100 * (experience_level - 4) / 2; // max 12
else
AC += 100 + (100 * experience_level / 4); // max 7
}
else
{
switch (species)
{
case SP_NAGA:
AC += 100 * experience_level / 3; // max 9
break;
default:
break;
}
}
// Scales -- some evil uses of the fact that boolean "true" == 1...
// I'll spell things out with some comments -- bwr
// mutations:
// these give: +1, +2, +3
AC += 100 * player_mutation_level(MUT_TOUGH_SKIN);
AC += 100 * player_mutation_level(MUT_GREY_SCALES);
AC += 100 * player_mutation_level(MUT_SHAGGY_FUR);
AC += 100 * player_mutation_level(MUT_BLUE_SCALES);
AC += 100 * player_mutation_level(MUT_SPECKLED_SCALES);
AC += 100 * player_mutation_level(MUT_IRIDESCENT_SCALES);
AC += 100 * player_mutation_level(MUT_PATTERNED_SCALES);
// these give: +1, +3, +5
if (player_mutation_level(MUT_GREEN_SCALES) > 0)
AC += (player_mutation_level(MUT_GREEN_SCALES) * 200) - 100;
if (player_mutation_level(MUT_NACREOUS_SCALES) > 0)
AC += (player_mutation_level(MUT_NACREOUS_SCALES) * 200) - 100;
if (player_mutation_level(MUT_BLACK2_SCALES) > 0)
AC += (player_mutation_level(MUT_BLACK2_SCALES) * 200) - 100;
if (player_mutation_level(MUT_WHITE_SCALES) > 0)
AC += (player_mutation_level(MUT_WHITE_SCALES) * 200) - 100;
// these give: +2, +4, +6
AC += player_mutation_level(MUT_GREY2_SCALES) * 200;
AC += player_mutation_level(MUT_YELLOW_SCALES) * 200;
AC += player_mutation_level(MUT_PURPLE_SCALES) * 200;
// black gives: +3, +6, +9
AC += player_mutation_level(MUT_BLACK_SCALES) * 300;
// boney plates give: +2, +3, +4
if (player_mutation_level(MUT_BONEY_PLATES) > 0)
AC += 100 * (player_mutation_level(MUT_BONEY_PLATES) + 1);
// red gives +1, +2, +4
AC += 100 * (player_mutation_level(MUT_RED_SCALES)
+ (player_mutation_level(MUT_RED_SCALES) == 3));
// indigo gives: +2, +3, +5
if (player_mutation_level(MUT_INDIGO_SCALES) > 0)
{
AC += 100 * (1 + player_mutation_level(MUT_INDIGO_SCALES)
+ (player_mutation_level(MUT_INDIGO_SCALES) == 3));
}
// brown gives: +2, +4, +5
AC += 100 * ((player_mutation_level(MUT_BROWN_SCALES) * 2)
- (player_mutation_level(MUT_BROWN_SCALES) == 3));
// orange gives: +1, +3, +4
AC += 100 * (player_mutation_level(MUT_ORANGE_SCALES)
+ (player_mutation_level(MUT_ORANGE_SCALES) > 1));
// knobbly red gives: +2, +5, +7
AC += 100 * ((player_mutation_level(MUT_RED2_SCALES) * 2)
+ (player_mutation_level(MUT_RED2_SCALES) > 1));
// metallic gives +3, +7, +10
AC += 100 * (player_mutation_level(MUT_METALLIC_SCALES) * 3
+ (player_mutation_level(MUT_METALLIC_SCALES) > 1));
}
else
{
// transformations:
switch (attribute[ATTR_TRANSFORMATION])
{
case TRAN_NONE:
case TRAN_BLADE_HANDS:
case TRAN_LICH: // can wear normal body armour (small bonus)
break;
case TRAN_SPIDER: // low level (small bonus), also gets EV
AC += (200 + 100 * skills[SK_POISON_MAGIC] / 6); // max 6
break;
case TRAN_ICE_BEAST:
AC += (500 + 100 * (skills[SK_ICE_MAGIC] + 1) / 4); // max 12
if (duration[DUR_ICY_ARMOUR])
AC += (100 + 100 * skills[SK_ICE_MAGIC] / 4); // max +7
break;
case TRAN_DRAGON:
AC += (700 + 100 * skills[SK_FIRE_MAGIC] / 3); // max 16
break;
case TRAN_STATUE: // main ability is armour (high bonus)
AC += (1700 + 100 * skills[SK_EARTH_MAGIC] / 2); // max 30
if (duration[DUR_STONESKIN] || duration[DUR_STONEMAIL])
AC += (100 + 100 * skills[SK_EARTH_MAGIC] / 4); // max +7
break;
default:
break;
}
}
return (AC / 100);
}
int player::melee_evasion(const actor *act, ev_ignore_type evit) const
{
return (player_evasion(evit)
- ((!act || act->visible_to(this)
|| (evit & EV_IGNORE_HELPLESS)) ? 0 : 10)
- (you_are_delayed()
&& !(evit & EV_IGNORE_HELPLESS)
&& !is_run_delay(current_delay_action())? 5 : 0));
}
bool player::heal(int amount, bool max_too)
{
::inc_hp(amount, max_too);
return true; /* TODO Check whether the player was healed. */
}
mon_holy_type player::holiness() const
{
if (is_undead)
return (MH_UNDEAD);
if (species == SP_DEMONSPAWN)
return (MH_DEMONIC);
return (MH_NATURAL);
}
bool player::undead_or_demonic() const
{
const mon_holy_type holi = holiness();
return (holi == MH_UNDEAD || holi == MH_DEMONIC);
}
bool player::is_holy() const
{
if (is_good_god(religion))
return (true);
return (false);
}
bool player::is_unholy() const
{
return (holiness() == MH_DEMONIC);
}
bool player::is_evil() const
{
if (holiness() == MH_UNDEAD)
return (true);
if (is_evil_god(religion))
return (true);
return (false);
}
bool player::is_chaotic() const
{
if (is_chaotic_god(religion))
return (true);
return (false);
}
// Output active level of player mutation.
// Might be lower than real mutation for non-"Alive" Vampires.
int player_mutation_level(mutation_type mut)
{
const int mlevel = you.mutation[mut];
if (mutation_is_fully_active(mut))
return (mlevel);
// For now, dynamic mutations only apply to semi-undead.
ASSERT(you.is_undead == US_SEMI_UNDEAD);
// Assumption: stat mutations are physical, and thus always fully active.
switch (you.hunger_state)
{
case HS_ENGORGED:
return (mlevel);
case HS_VERY_FULL:
case HS_FULL:
return (std::min(mlevel, 2));
case HS_SATIATED:
return (std::min(mlevel, 1));
}
return (0);
}
int player::res_fire() const
{
return (player_res_fire());
}
int player::res_steam() const
{
return (player_res_steam());
}
int player::res_cold() const
{
return (player_res_cold());
}
int player::res_elec() const
{
return (player_res_electricity() * 2);
}
int player::res_water_drowning() const
{
return (res_asphyx() ||
(you.species == SP_MERFOLK && !transform_changed_physiology()));
}
int player::res_asphyx() const
{
// The undead are immune to asphyxiation, or so we'll assume.
if (this->is_undead)
return 1;
switch (this->attribute[ATTR_TRANSFORMATION])
{
case TRAN_LICH:
case TRAN_STATUE:
return 1;
break;
}
return 0;
}
int player::res_poison() const
{
return (player_res_poison());
}
int player::res_rotting() const
{
if (is_undead
&& (is_undead != US_SEMI_UNDEAD || hunger_state < HS_SATIATED))
{
return (1);
}
return (0);
}
int player::res_sticky_flame() const
{
return (player_res_sticky_flame());
}
int player::res_holy_energy(const actor *attacker) const
{
if (undead_or_demonic())
return (-2);
if (is_evil())
return (-1);
if (is_holy())
return (1);
return (0);
}
int player::res_negative_energy() const
{
return (player_prot_life());
}
int player::res_torment() const
{
return (player_res_torment());
}
int player::res_magic() const
{
int rm = 0;
switch (this->species)
{
case SP_MOUNTAIN_DWARF:
case SP_HILL_ORC:
rm = this->experience_level * 2;
break;
default:
rm = this->experience_level * 3;
break;
case SP_HIGH_ELF:
case SP_SLUDGE_ELF:
case SP_DEEP_ELF:
case SP_VAMPIRE:
case SP_DEMIGOD:
case SP_OGRE:
rm = this->experience_level * 4;
break;
case SP_NAGA:
rm = this->experience_level * 5;
break;
case SP_PURPLE_DRACONIAN:
case SP_DEEP_DWARF:
rm = this->experience_level * 6;
break;
case SP_SPRIGGAN:
rm = this->experience_level * 7;
break;
}
// randarts
rm += scan_artefacts(ARTP_MAGIC);
// armour
rm += 30 * player_equip_ego_type(EQ_ALL_ARMOUR, SPARM_MAGIC_RESISTANCE);
// rings of magic resistance
rm += 40 * player_equip(EQ_RINGS, RING_PROTECTION_FROM_MAGIC);
// Enchantment skill
rm += 2 * this->skills[SK_ENCHANTMENTS];
// Mutations
rm += 30 * player_mutation_level(MUT_MAGIC_RESISTANCE);
// transformations
if (this->attribute[ATTR_TRANSFORMATION] == TRAN_LICH)
rm += 50;
// Trog's Hand
if (this->attribute[ATTR_DIVINE_REGENERATION])
rm += 70;
// Enchantment effect
if (this->duration[DUR_LOWERED_MR])
rm /= 2;
return (rm);
}
bool player::confusable() const
{
return (player_mental_clarity() == 0);
}
bool player::slowable() const
{
return true;
}
flight_type player::flight_mode() const
{
if (attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| attribute[ATTR_TRANSFORMATION] == TRAN_BAT)
{
return (FL_FLY);
}
else if (is_levitating())
{
return (this->duration[DUR_CONTROLLED_FLIGHT]
|| wearing_amulet(AMU_CONTROLLED_FLIGHT) ? FL_FLY
: FL_LEVITATE);
}
else
return (FL_NONE);
}
bool player::permanent_levitation() const
{
// Boots of levitation keep you with DUR_LEVITATION >= 2 at
// all times. This is so that you can evoke stop-levitation
// in order to actually cancel levitation (by setting
// DUR_LEVITATION to 1.) Note that antimagic() won't do this.
return (airborne() && player_equip_ego_type(EQ_BOOTS, SPARM_LEVITATION)
&& this->duration[DUR_LEVITATION] > 1);
}
bool player::permanent_flight() const
{
return (airborne() && wearing_amulet(AMU_CONTROLLED_FLIGHT)
&& this->species == SP_KENKU && this->experience_level >= 15);
}
bool player::light_flight() const
{
// Only Kenku get perks for flying light.
return (species == SP_KENKU
&& flight_mode() == FL_FLY && travelling_light());
}
bool player::travelling_light() const
{
return (this->burden < carrying_capacity(BS_UNENCUMBERED) * 70 / 100);
}
int player::mons_species() const
{
if (player_genus(GENPC_DRACONIAN))
return (MONS_DRACONIAN);
switch (species)
{
case SP_HILL_ORC:
return (MONS_ORC);
case SP_HIGH_ELF: case SP_DEEP_ELF: case SP_SLUDGE_ELF:
return (MONS_ELF);
default:
return (MONS_HUMAN);
}
}
void player::poison(actor *agent, int amount)
{
::poison_player(amount);
}
void player::expose_to_element(beam_type element, int st)
{
::expose_player_to_element(element, st);
}
void player::blink(bool allow_partial_control)
{
random_blink(allow_partial_control);
}
void player::teleport(bool now, bool abyss_shift, bool wizard_tele)
{
ASSERT(!crawl_state.arena);
if (now)
you_teleport_now(true, abyss_shift, wizard_tele);
else
you_teleport();
}
int player::hurt(const actor *agent, int amount, beam_type flavour,
bool cleanup_dead)
{
// We ignore cleanup_dead here.
if (agent->atype() == ACT_MONSTER)
{
const monsters *mon = dynamic_cast<const monsters*>(agent);
ouch(amount, mon->mindex(),
KILLED_BY_MONSTER, "", mon->visible_to(&you));
}
else
{
// Should never happen!
ASSERT(false);
ouch(amount, NON_MONSTER, KILLED_BY_SOMETHING);
}
if ((flavour == BEAM_NUKE || flavour == BEAM_DISINTEGRATION) && can_bleed())
{
blood_spray(pos(), id(), amount / 5);
}
return (amount);
}
void player::drain_stat(int stat, int amount, actor *attacker)
{
if (attacker == NULL)
lose_stat(stat, amount, false, "");
else if (attacker->atype() == ACT_MONSTER)
lose_stat(stat, amount, dynamic_cast<monsters*>(attacker), false);
else if (attacker->atype() == ACT_PLAYER)
lose_stat(stat, amount, false, "suicide");
else
lose_stat(stat, amount, false, "");
}
bool player::rot(actor *who, int amount, int immediate, bool quiet)
{
ASSERT(!crawl_state.arena);
if (amount <= 0 && immediate <= 0)
return (false);
if (res_rotting())
{
mpr("You feel terrible.");
return (false);
}
// Zin's protection.
if (religion == GOD_ZIN && x_chance_in_y(piety, MAX_PIETY))
{
simple_god_message(" protects your body from decay!");
return (false);
}
if (immediate > 0)
rot_hp(immediate);
if (this->rotting < 40)
{
// Either this, or the actual rotting message should probably
// be changed so that they're easier to tell apart. -- bwr
mprf(MSGCH_WARN, "You feel your flesh %s away!",
this->rotting > 0 ? "rotting" : "start to rot");
this->rotting += amount;
learned_something_new(TUT_YOU_ROTTING);
}
if (one_chance_in(4))
sicken(50 + random2(100));
return (true);
}
bool player::drain_exp(actor *who, bool quiet, int pow)
{
return (::drain_exp());
}
void player::confuse(actor *who, int str)
{
confuse_player(str);
}
void player::paralyse(actor *who, int str)
{
ASSERT(!crawl_state.arena);
// The shock is too mild to do damage.
if (stasis_blocks_effect(true, "%s gives you a mild electric shock."))
return;
int ¶lysis(duration[DUR_PARALYSIS]);
mprf("You %s the ability to move!",
paralysis ? "still haven't" : "suddenly lose");
str *= BASELINE_DELAY;
if (str > paralysis && (paralysis < 3 || one_chance_in(paralysis)))
paralysis = str;
if (paralysis > 13 * BASELINE_DELAY)
paralysis = 13 * BASELINE_DELAY;
}
void player::petrify(actor *who, int str)
{
ASSERT(!crawl_state.arena);
if (stasis_blocks_effect(true, "%s gives you a mild electric shock."))
return;
str *= BASELINE_DELAY;
int &petrif(duration[DUR_PETRIFIED]);
mprf("You %s the ability to move!",
petrif ? "still haven't" : "suddenly lose");
if (str > petrif && (petrif < 3 || one_chance_in(petrif)))
petrif = str;
petrif = std::min(13 * BASELINE_DELAY, petrif);
}
void player::slow_down(actor *foe, int str)
{
::slow_player(str);
}
int player::has_claws(bool allow_tran) const
{
if (allow_tran)
{
// these transformations bring claws with them
if (attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON)
return (3);
// transformations other than these will override claws
if (attribute[ATTR_TRANSFORMATION] != TRAN_NONE
&& attribute[ATTR_TRANSFORMATION] != TRAN_STATUE
&& attribute[ATTR_TRANSFORMATION] != TRAN_LICH)
{
return (0);
}
}
// XXX: Some assumptions about mutations and species in here.
int sp_claws = species_has_claws(species);
if (sp_claws > 0)
return (sp_claws);
return (player_mutation_level(MUT_CLAWS));
}
bool player::has_usable_claws(bool allow_tran) const
{
return (equip[EQ_GLOVES] == -1 && has_claws(allow_tran));
}
god_type player::deity() const
{
return (religion);
}
kill_category player::kill_alignment() const
{
return (KC_YOU);
}
bool player::sicken(int amount)
{
ASSERT(!crawl_state.arena);
if (res_rotting() || amount <= 0)
return (false);
// Zin's protection.
if (religion == GOD_ZIN && x_chance_in_y(piety, MAX_PIETY))
{
simple_god_message(" protects your body from disease!");
return (false);
}
mpr("You feel ill.");
disease += amount * BASELINE_DELAY;
if (disease > 210 * BASELINE_DELAY)
disease = 210 * BASELINE_DELAY;
learned_something_new(TUT_YOU_SICK);
return (true);
}
bool player::can_see_invisible(bool calc_unid) const
{
int si = 0;
si += player_equip( EQ_RINGS, RING_SEE_INVISIBLE, calc_unid );
// armour: (checks head armour only)
si += player_equip_ego_type( EQ_HELMET, SPARM_SEE_INVISIBLE );
if (player_mutation_level(MUT_ACUTE_VISION) > 0)
si += player_mutation_level(MUT_ACUTE_VISION);
//jmf: added see_invisible spell
if (this->duration[DUR_SEE_INVISIBLE] > 0)
si++;
// randart wpns
int artefacts = scan_artefacts(ARTP_EYESIGHT, calc_unid);
if (artefacts > 0)
si += artefacts;
if (si > 1)
si = 1;
return (si);
}
bool player::can_see_invisible() const
{
return (can_see_invisible(true));
}
bool player::invisible() const
{
return (duration[DUR_INVIS] && !backlit());
}
bool player::misled() const
{
return (duration[DUR_MISLED]);
}
bool player::visible_to(const actor *looker) const
{
if (crawl_state.arena)
return (false);
if (this == looker)
return (can_see_invisible() || !invisible());
const monsters* mon = dynamic_cast<const monsters*>(looker);
return (!invisible()
|| in_water()
|| mon->can_see_invisible()
|| mons_sense_invis(mon));
}
bool player::backlit(bool check_haloed) const
{
return (get_contamination_level() > 0 || duration[DUR_CORONA]
|| (check_haloed ? haloed() : false)
|| duration[DUR_LIQUID_FLAMES]);
}
// This is the imperative version.
void player::backlight()
{
if (!this->duration[DUR_INVIS])
{
if (this->duration[DUR_CORONA])
mpr("You glow brighter.");
else
mpr("You are outlined in light.");
you.increase_duration(DUR_CORONA, random_range(15, 35), 250);
}
else
{
mpr("You feel strangely conspicuous.");
you.increase_duration(DUR_CORONA, random_range(3, 5), 250);
}
}
bool player::can_mutate() const
{
return (true);
}
bool player::can_safely_mutate() const
{
if (!can_mutate())
return (false);
return (!this->is_undead
|| this->is_undead == US_SEMI_UNDEAD
&& this->hunger_state == HS_ENGORGED);
}
bool player::can_bleed() const
{
if (this->is_undead && (this->species != SP_VAMPIRE
|| this->hunger_state <= HS_SATIATED))
{
return (false);
}
const int tran = this->attribute[ATTR_TRANSFORMATION];
// The corresponding monsters don't bleed either.
if (tran == TRAN_STATUE || tran == TRAN_ICE_BEAST
|| tran == TRAN_LICH || tran == TRAN_SPIDER)
{
return (false);
}
return (true);
}
bool player::mutate()
{
ASSERT(!crawl_state.arena);
if (!can_mutate())
return (false);
if (one_chance_in(5))
{
if (::mutate(RANDOM_MUTATION))
{
learned_something_new(TUT_YOU_MUTATED);
return (true);
}
}
return (give_bad_mutation());
}
bool player::is_icy() const
{
return (attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST);
}
bool player::is_fiery() const
{
return (false);
}
void player::set_position(const coord_def &c)
{
ASSERT(!crawl_state.arena);
const bool real_move = (c != pos());
actor::set_position(c);
if (real_move)
{
this->reset_prev_move();
dungeon_events.fire_position_event(DET_PLAYER_MOVED, c);
// Reset lava/water nearness check to unknown, so it'll be
// recalculated for the next monster that tries to reach us.
this->lava_in_sight = this->water_in_sight = -1;
}
}
void player::moveto(const coord_def &c)
{
if (c != this->pos())
clear_trapping_net();
crawl_view.set_player_at(c);
set_position(c);
}
bool player::move_to_pos(const coord_def &c)
{
actor *target = actor_at(c);
if (!target || target->submerged())
{
moveto(c);
return true;
}
return false;
}
void player::apply_location_effects(const coord_def &oldpos,
killer_type killer,
int killernum)
{
move_player_to_grid(pos(), false, true, true, false);
}
void player::shiftto(const coord_def &c)
{
crawl_view.shift_player_to(c);
set_position(c);
}
void player::reset_prev_move()
{
prev_move.reset();
}
bool player::asleep() const
{
return (duration[DUR_SLEEP]);
}
bool player::cannot_act() const
{
return (asleep() || cannot_move());
}
bool player::can_throw_large_rocks() const
{
return (player_genus(GENPC_OGRE) || species == SP_TROLL);
}
bool player::can_smell() const
{
return (species != SP_MUMMY);
}
void player::hibernate(int)
{
ASSERT(!crawl_state.arena);
if (!can_hibernate())
{
mpr("You feel weary for a moment.");
return;
}
mpr("You fall asleep.");
stop_delay();
flash_view(DARKGREY);
// Do this *after* redrawing the view, or viewwindow() will no-op.
set_duration(DUR_SLEEP, 3 + random2avg(5, 2));
}
void player::put_to_sleep(actor*, int power)
{
ASSERT(!crawl_state.arena);
if (duration[DUR_SLEEP])
return;
mpr("You fall asleep!");
stop_delay();
flash_view(DARKGREY);
// As above, do this after redraw.
set_duration(DUR_SLEEP, 5 + random2avg(power/10, 5));
}
void player::awake()
{
ASSERT(!crawl_state.arena);
duration[DUR_SLEEP] = 0;
mpr("You wake up.");
flash_view(BLACK);
}
void player::check_awaken(int disturbance)
{
if (asleep() && x_chance_in_y(disturbance + 1, 50))
awake();
}
////////////////////////////////////////////////////////////////////////////
PlaceInfo::PlaceInfo()
: level_type(-2), branch(-2), num_visits(0),
levels_seen(0), mon_kill_exp(0), mon_kill_exp_avail(0),
turns_total(0), turns_explore(0), turns_travel(0), turns_interlevel(0),
turns_resting(0), turns_other(0), elapsed_total(0.0),
elapsed_explore(0.0), elapsed_travel(0.0), elapsed_interlevel(0.0),
elapsed_resting(0.0), elapsed_other(0.0)
{
for (int i = 0; i < KC_NCATEGORIES; i++)
mon_kill_num[i] = 0;
}
bool PlaceInfo::is_global() const
{
return (level_type == -1 && branch == -1);
}
void PlaceInfo::make_global()
{
level_type = -1;
branch = -1;
}
void PlaceInfo::assert_validity() const
{
// Check that level_type and branch match up.
ASSERT(is_global()
|| level_type == LEVEL_DUNGEON && branch >= BRANCH_MAIN_DUNGEON
&& branch < NUM_BRANCHES
|| level_type > LEVEL_DUNGEON && level_type < NUM_LEVEL_AREA_TYPES
&& branch == -1);
// Can't have visited a place without seeing any of its levels, and
// vice versa.
ASSERT(num_visits == 0 && levels_seen == 0
|| num_visits > 0 && levels_seen > 0);
if (level_type == LEVEL_LABYRINTH || level_type == LEVEL_ABYSS)
ASSERT(num_visits == levels_seen);
else if (level_type == LEVEL_PANDEMONIUM)
// Ziggurats can allow a player to return to the same
// Pandemonium level.
// ASSERT(num_visits <= levels_seen);
;
else if (level_type == LEVEL_DUNGEON && branches[branch].depth > 0)
ASSERT(levels_seen <= (unsigned long) branches[branch].depth);
ASSERT(turns_total == (turns_explore + turns_travel + turns_interlevel
+ turns_resting + turns_other));
ASSERT(elapsed_total == (elapsed_explore + elapsed_travel
+ elapsed_interlevel + elapsed_resting
+ elapsed_other));
}
const std::string PlaceInfo::short_name() const
{
if (level_type == LEVEL_DUNGEON)
return branches[branch].shortname;
else
{
switch (level_type)
{
case LEVEL_ABYSS:
return "Abyss";
case LEVEL_PANDEMONIUM:
return "Pandemonium";
case LEVEL_LABYRINTH:
return "Labyrinth";
case LEVEL_PORTAL_VAULT:
return "Portal Chamber";
default:
return "Bug";
}
}
}
const PlaceInfo &PlaceInfo::operator += (const PlaceInfo &other)
{
num_visits += other.num_visits;
levels_seen += other.levels_seen;
mon_kill_exp += other.mon_kill_exp;
mon_kill_exp_avail += other.mon_kill_exp_avail;
for (int i = 0; i < KC_NCATEGORIES; i++)
mon_kill_num[i] += other.mon_kill_num[i];
turns_total += other.turns_total;
turns_explore += other.turns_explore;
turns_travel += other.turns_travel;
turns_interlevel += other.turns_interlevel;
turns_resting += other.turns_resting;
turns_other += other.turns_other;
elapsed_total += other.elapsed_total;
elapsed_explore += other.elapsed_explore;
elapsed_travel += other.elapsed_travel;
elapsed_interlevel += other.elapsed_interlevel;
elapsed_resting += other.elapsed_resting;
elapsed_other += other.elapsed_other;
return (*this);
}
const PlaceInfo &PlaceInfo::operator -= (const PlaceInfo &other)
{
num_visits -= other.num_visits;
levels_seen -= other.levels_seen;
mon_kill_exp -= other.mon_kill_exp;
mon_kill_exp_avail -= other.mon_kill_exp_avail;
for (int i = 0; i < KC_NCATEGORIES; i++)
mon_kill_num[i] -= other.mon_kill_num[i];
turns_total -= other.turns_total;
turns_explore -= other.turns_explore;
turns_travel -= other.turns_travel;
turns_interlevel -= other.turns_interlevel;
turns_resting -= other.turns_resting;
turns_other -= other.turns_other;
elapsed_total -= other.elapsed_total;
elapsed_explore -= other.elapsed_explore;
elapsed_travel -= other.elapsed_travel;
elapsed_interlevel -= other.elapsed_interlevel;
elapsed_resting -= other.elapsed_resting;
elapsed_other -= other.elapsed_other;
return (*this);
}
PlaceInfo PlaceInfo::operator + (const PlaceInfo &other) const
{
PlaceInfo copy = *this;
copy += other;
return copy;
}
PlaceInfo PlaceInfo::operator - (const PlaceInfo &other) const
{
PlaceInfo copy = *this;
copy -= other;
return copy;
}
PlaceInfo& player::get_place_info() const
{
return get_place_info(where_are_you, level_type);
}
PlaceInfo& player::get_place_info(branch_type branch) const
{
return get_place_info(branch, LEVEL_DUNGEON);
}
PlaceInfo& player::get_place_info(level_area_type level_type2) const
{
return get_place_info(NUM_BRANCHES, level_type2);
}
PlaceInfo& player::get_place_info(branch_type branch,
level_area_type level_type2) const
{
ASSERT(level_type2 == LEVEL_DUNGEON && branch >= BRANCH_MAIN_DUNGEON
&& branch < NUM_BRANCHES
|| level_type2 > LEVEL_DUNGEON && level_type < NUM_LEVEL_AREA_TYPES);
if (level_type2 == LEVEL_DUNGEON)
return (PlaceInfo&) branch_info[branch];
else
return (PlaceInfo&) non_branch_info[level_type2 - 1];
}
void player::set_place_info(PlaceInfo place_info)
{
place_info.assert_validity();
if (place_info.is_global())
global_info = place_info;
else if (place_info.level_type == LEVEL_DUNGEON)
branch_info[place_info.branch] = place_info;
else
non_branch_info[place_info.level_type - 1] = place_info;
}
std::vector<PlaceInfo> player::get_all_place_info(bool visited_only,
bool dungeon_only) const
{
std::vector<PlaceInfo> list;
for (int i = 0; i < NUM_BRANCHES; i++)
{
if (visited_only && branch_info[i].num_visits == 0
|| dungeon_only && branch_info[i].level_type != LEVEL_DUNGEON)
{
continue;
}
list.push_back(branch_info[i]);
}
for (int i = 0; i < (NUM_LEVEL_AREA_TYPES - 1); i++)
{
if (visited_only && non_branch_info[i].num_visits == 0
|| dungeon_only && non_branch_info[i].level_type != LEVEL_DUNGEON)
{
continue;
}
list.push_back(non_branch_info[i]);
}
return list;
}
bool player::do_shaft()
{
dungeon_feature_type force_stair = DNGN_UNSEEN;
if (!is_valid_shaft_level())
return (false);
// Handle instances of do_shaft() being invoked magically when
// the player isn't standing over a shaft.
if (get_trap_type(this->pos()) != TRAP_SHAFT)
{
switch (grd(this->pos()))
{
case DNGN_FLOOR:
case DNGN_OPEN_DOOR:
case DNGN_TRAP_MECHANICAL:
case DNGN_TRAP_MAGICAL:
case DNGN_TRAP_NATURAL:
case DNGN_UNDISCOVERED_TRAP:
case DNGN_ENTER_SHOP:
break;
default:
return (false);
}
handle_items_on_shaft(this->pos(), false);
if (airborne() || total_weight() == 0)
return (true);
force_stair = DNGN_TRAP_NATURAL;
}
down_stairs(your_level, force_stair);
return (true);
}
bool player::did_escape_death() const
{
return (escaped_death_cause != NUM_KILLBY);
}
void player::reset_escaped_death()
{
escaped_death_cause = NUM_KILLBY;
escaped_death_aux = "";
}
void player::add_gold(int delta)
{
set_gold(gold + delta);
}
void player::del_gold(int delta)
{
set_gold(gold - delta);
}
void player::set_gold(int amount)
{
ASSERT(amount >= 0);
if (amount != gold)
{
const int old_gold = gold;
gold = amount;
shopping_list.gold_changed(old_gold, gold);
}
}
void player::increase_duration(duration_type dur, int turns, int cap,
const char* msg)
{
if (msg)
mpr(msg);
cap *= BASELINE_DELAY;
you.duration[dur] += turns * BASELINE_DELAY;
if (cap && you.duration[dur] > cap)
you.duration[dur] = cap;
}
void player::set_duration(duration_type dur, int turns,
int cap, const char * msg)
{
you.duration[dur] = 0;
increase_duration(dur, turns, cap, msg);
}