#include "player.h"

#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <map>
#include <string>
#include <sstream>
#include <limits>

#include "action.h"
#include "activity_handlers.h"
#include "addiction.h"
#include "ammo.h"
#include "bionics.h"
#include "cata_utility.h"
#include "catacharset.h"
#include "catalua.h"
#include "coordinate_conversions.h"
#include "craft_command.h"
#include "cursesdef.h"
#include "debug.h"
#include "effect.h"
#include "fault.h"
#include "filesystem.h"
#include "fungal_effects.h"
#include "game.h"
#include "get_version.h"
#include "help.h" // get_hint
#include "input.h"
#include "inventory.h"
#include "item.h"
#include "item_location.h"
#include "itype.h"
#include "iuse_actor.h"
#include "map.h"
#include "map_iterator.h"
#include "mapdata.h"
#include "martialarts.h"
#include "material.h"
#include "messages.h"
#include "mission.h"
#include "monstergenerator.h"
#include "morale.h"
#include "morale_types.h"
#include "mtype.h"
#include "mutation.h"
#include "name.h"
#include "npc.h"
#include "options.h"
#include "output.h"
#include "overlay_ordering.h"
#include "overmap.h"
#include "overmapbuffer.h"
#include "pickup.h"
#include "profession.h"
#include "ranged.h"
#include "recipe_dictionary.h"
#include "requirements.h"
#include "skill.h"
#include "sounds.h"
#include "string_formatter.h"
#include "submap.h"
#include "text_snippets.h"
#include "translations.h"
#include "trap.h"
#include "ui.h"
#include "uistate.h"
#include "veh_type.h"
#include "vehicle.h"
#include "vitamin.h"
#include "vpart_position.h"
#include "vpart_range.h"
#include "vpart_reference.h"
#include "weather.h"
#include "weather_gen.h"

constexpr double SQRT_2 = 1.41421356237309504880;

const double MAX_RECOIL = 3000;

const mtype_id mon_player_blob( "mon_player_blob" );
const mtype_id mon_shadow_snake( "mon_shadow_snake" );

const skill_id skill_dodge( "dodge" );
const skill_id skill_gun( "gun" );
const skill_id skill_mechanics( "mechanics" );
const skill_id skill_swimming( "swimming" );
const skill_id skill_throw( "throw" );
const skill_id skill_unarmed( "unarmed" );

const efftype_id effect_adrenaline( "adrenaline" );
const efftype_id effect_alarm_clock( "alarm_clock" );
const efftype_id effect_asthma( "asthma" );
const efftype_id effect_attention( "attention" );
const efftype_id effect_bandaged( "bandaged" );
const efftype_id effect_bite( "bite" );
const efftype_id effect_blind( "blind" );
const efftype_id effect_blisters( "blisters" );
const efftype_id effect_bloodworms( "bloodworms" );
const efftype_id effect_boomered( "boomered" );
const efftype_id effect_brainworms( "brainworms" );
const efftype_id effect_cig( "cig" );
const efftype_id effect_cold( "cold" );
const efftype_id effect_common_cold( "common_cold" );
const efftype_id effect_contacts( "contacts" );
const efftype_id effect_corroding( "corroding" );
const efftype_id effect_cough_suppress( "cough_suppress" );
const efftype_id effect_darkness( "darkness" );
const efftype_id effect_datura( "datura" );
const efftype_id effect_deaf( "deaf" );
const efftype_id effect_depressants( "depressants" );
const efftype_id effect_dermatik( "dermatik" );
const efftype_id effect_disabled( "disabled" );
const efftype_id effect_disinfected( "disinfected" );
const efftype_id effect_downed( "downed" );
const efftype_id effect_drunk( "drunk" );
const efftype_id effect_earphones( "earphones" );
const efftype_id effect_evil( "evil" );
const efftype_id effect_flu( "flu" );
const efftype_id effect_foodpoison( "foodpoison" );
const efftype_id effect_formication( "formication" );
const efftype_id effect_frostbite( "frostbite" );
const efftype_id effect_frostbite_recovery( "frostbite_recovery" );
const efftype_id effect_fungus( "fungus" );
const efftype_id effect_glowing( "glowing" );
const efftype_id effect_grabbed( "grabbed" );
const efftype_id effect_hallu( "hallu" );
const efftype_id effect_happy( "happy" );
const efftype_id effect_hot( "hot" );
const efftype_id effect_infected( "infected" );
const efftype_id effect_iodine( "iodine" );
const efftype_id effect_irradiated( "irradiated" );
const efftype_id effect_jetinjector( "jetinjector" );
const efftype_id effect_lack_sleep( "lack_sleep" );
const efftype_id effect_sleep_deprived( "sleep_deprived" );
const efftype_id effect_lying_down( "lying_down" );
const efftype_id effect_mending( "mending" );
const efftype_id effect_meth( "meth" );
const efftype_id effect_narcosis( "narcosis" );
const efftype_id effect_nausea( "nausea" );
const efftype_id effect_onfire( "onfire" );
const efftype_id effect_paincysts( "paincysts" );
const efftype_id effect_pkill( "pkill" );
const efftype_id effect_pkill1( "pkill1" );
const efftype_id effect_pkill2( "pkill2" );
const efftype_id effect_pkill3( "pkill3" );
const efftype_id effect_recover( "recover" );
const efftype_id effect_sad( "sad" );
const efftype_id effect_shakes( "shakes" );
const efftype_id effect_sleep( "sleep" );
const efftype_id effect_slept_through_alarm( "slept_through_alarm" );
const efftype_id effect_spores( "spores" );
const efftype_id effect_stim( "stim" );
const efftype_id effect_stim_overdose( "stim_overdose" );
const efftype_id effect_stunned( "stunned" );
const efftype_id effect_tapeworm( "tapeworm" );
const efftype_id effect_took_prozac( "took_prozac" );
const efftype_id effect_took_xanax( "took_xanax" );
const efftype_id effect_visuals( "visuals" );
const efftype_id effect_weed_high( "weed_high" );
const efftype_id effect_winded( "winded" );
const efftype_id effect_bleed( "bleed" );
const efftype_id effect_magnesium_supplements( "magnesium" );

const matype_id style_none( "style_none" );
const matype_id style_kicks( "style_kicks" );

const species_id ROBOT( "ROBOT" );

static const bionic_id bio_ads( "bio_ads" );
static const bionic_id bio_advreactor( "bio_advreactor" );
static const bionic_id bio_armor_arms( "bio_armor_arms" );
static const bionic_id bio_armor_eyes( "bio_armor_eyes" );
static const bionic_id bio_armor_head( "bio_armor_head" );
static const bionic_id bio_armor_legs( "bio_armor_legs" );
static const bionic_id bio_armor_torso( "bio_armor_torso" );
static const bionic_id bio_blaster( "bio_blaster" );
static const bionic_id bio_carbon( "bio_carbon" );
static const bionic_id bio_climate( "bio_climate" );
static const bionic_id bio_cloak( "bio_cloak" );
static const bionic_id bio_cqb( "bio_cqb" );
static const bionic_id bio_dis_acid( "bio_dis_acid" );
static const bionic_id bio_dis_shock( "bio_dis_shock" );
static const bionic_id bio_drain( "bio_drain" );
static const bionic_id bio_earplugs( "bio_earplugs" );
static const bionic_id bio_ears( "bio_ears" );
static const bionic_id bio_eye_optic( "bio_eye_optic" );
static const bionic_id bio_faraday( "bio_faraday" );
static const bionic_id bio_flashlight( "bio_flashlight" );
static const bionic_id bio_geiger( "bio_geiger" );
static const bionic_id bio_gills( "bio_gills" );
static const bionic_id bio_ground_sonar( "bio_ground_sonar" );
static const bionic_id bio_heatsink( "bio_heatsink" );
static const bionic_id bio_itchy( "bio_itchy" );
static const bionic_id bio_laser( "bio_laser" );
static const bionic_id bio_leaky( "bio_leaky" );
static const bionic_id bio_lighter( "bio_lighter" );
static const bionic_id bio_membrane( "bio_membrane" );
static const bionic_id bio_memory( "bio_memory" );
static const bionic_id bio_metabolics( "bio_metabolics" );
static const bionic_id bio_noise( "bio_noise" );
static const bionic_id bio_plut_filter( "bio_plut_filter" );
static const bionic_id bio_power_weakness( "bio_power_weakness" );
static const bionic_id bio_purifier( "bio_purifier" );
static const bionic_id bio_reactor( "bio_reactor" );
static const bionic_id bio_recycler( "bio_recycler" );
static const bionic_id bio_shakes( "bio_shakes" );
static const bionic_id bio_sleepy( "bio_sleepy" );
static const bionic_id bn_bio_solar( "bn_bio_solar" );
static const bionic_id bio_spasm( "bio_spasm" );
static const bionic_id bio_speed( "bio_speed" );
static const bionic_id bio_syringe( "bio_syringe" );
static const bionic_id bio_tools( "bio_tools" );
static const bionic_id bio_trip( "bio_trip" );
static const bionic_id bio_uncanny_dodge( "bio_uncanny_dodge" );
static const bionic_id bio_ups( "bio_ups" );
static const bionic_id bio_watch( "bio_watch" );
static const bionic_id bio_synaptic_regen( "bio_synaptic_regen" );

static const trait_id trait_ACIDBLOOD( "ACIDBLOOD" );
static const trait_id trait_ACIDPROOF( "ACIDPROOF" );
static const trait_id trait_ADDICTIVE( "ADDICTIVE" );
static const trait_id trait_ADRENALINE( "ADRENALINE" );
static const trait_id trait_ALBINO( "ALBINO" );
static const trait_id trait_AMORPHOUS( "AMORPHOUS" );
static const trait_id trait_AMPHIBIAN( "AMPHIBIAN" );
static const trait_id trait_ANTENNAE( "ANTENNAE" );
static const trait_id trait_ANTLERS( "ANTLERS" );
static const trait_id trait_ARACHNID_ARMS( "ARACHNID_ARMS" );
static const trait_id trait_ARACHNID_ARMS_OK( "ARACHNID_ARMS_OK" );
static const trait_id trait_ASTHMA( "ASTHMA" );
static const trait_id trait_BADBACK( "BADBACK" );
static const trait_id trait_BADCARDIO( "BADCARDIO" );
static const trait_id trait_BADHEARING( "BADHEARING" );
static const trait_id trait_BADKNEES( "BADKNEES" );
static const trait_id trait_BARK( "BARK" );
static const trait_id trait_BIRD_EYE( "BIRD_EYE" );
static const trait_id trait_CANINE_EARS( "CANINE_EARS" );
static const trait_id trait_CANNIBAL( "CANNIBAL" );
static const trait_id trait_CENOBITE( "CENOBITE" );
static const trait_id trait_CEPH_EYES( "CEPH_EYES" );
static const trait_id trait_CF_HAIR( "CF_HAIR" );
static const trait_id trait_CHAOTIC( "CHAOTIC" );
static const trait_id trait_CHAOTIC_BAD( "CHAOTIC_BAD" );
static const trait_id trait_CHEMIMBALANCE( "CHEMIMBALANCE" );
static const trait_id trait_CHITIN2( "CHITIN2" );
static const trait_id trait_CHITIN3( "CHITIN3" );
static const trait_id trait_CHITIN_FUR( "CHITIN_FUR" );
static const trait_id trait_CHITIN_FUR2( "CHITIN_FUR2" );
static const trait_id trait_CHITIN_FUR3( "CHITIN_FUR3" );
static const trait_id trait_CHLOROMORPH( "CHLOROMORPH" );
static const trait_id trait_CLUMSY( "CLUMSY" );
static const trait_id trait_COLDBLOOD( "COLDBLOOD" );
static const trait_id trait_COLDBLOOD2( "COLDBLOOD2" );
static const trait_id trait_COLDBLOOD3( "COLDBLOOD3" );
static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" );
static const trait_id trait_COMPOUND_EYES( "COMPOUND_EYES" );
static const trait_id trait_CRAFTY( "CRAFTY" );
static const trait_id trait_DEBUG_BIONIC_POWER( "DEBUG_BIONIC_POWER" );
static const trait_id trait_DEBUG_CLOAK( "DEBUG_CLOAK" );
static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
static const trait_id trait_DEBUG_LS( "DEBUG_LS" );
static const trait_id trait_DEBUG_NODMG( "DEBUG_NODMG" );
static const trait_id trait_DEBUG_NOTEMP( "DEBUG_NOTEMP" );
static const trait_id trait_DISIMMUNE( "DISIMMUNE" );
static const trait_id trait_DISRESISTANT( "DISRESISTANT" );
static const trait_id trait_DOWN( "DOWN" );
static const trait_id trait_EAGLEEYED( "EAGLEEYED" );
static const trait_id trait_EASYSLEEPER( "EASYSLEEPER" );
static const trait_id trait_EASYSLEEPER2( "EASYSLEEPER2" );
static const trait_id trait_ELECTRORECEPTORS( "ELECTRORECEPTORS" );
static const trait_id trait_EATHEALTH( "EATHEALTH" );
static const trait_id trait_FASTHEALER( "FASTHEALER" );
static const trait_id trait_FASTHEALER2( "FASTHEALER2" );
static const trait_id trait_FASTLEARNER( "FASTLEARNER" );
static const trait_id trait_FASTREADER( "FASTREADER" );
static const trait_id trait_FAT( "FAT" );
static const trait_id trait_FELINE_EARS( "FELINE_EARS" );
static const trait_id trait_FELINE_FUR( "FELINE_FUR" );
static const trait_id trait_FLEET( "FLEET" );
static const trait_id trait_FLEET2( "FLEET2" );
static const trait_id trait_FLOWERS( "FLOWERS" );
static const trait_id trait_FORGETFUL( "FORGETFUL" );
static const trait_id trait_FRESHWATEROSMOSIS( "FRESHWATEROSMOSIS" );
static const trait_id trait_FUR( "FUR" );
static const trait_id trait_GILLS( "GILLS" );
static const trait_id trait_GILLS_CEPH( "GILLS_CEPH" );
static const trait_id trait_GOODCARDIO( "GOODCARDIO" );
static const trait_id trait_GOODCARDIO2( "GOODCARDIO2" );
static const trait_id trait_GOODHEARING( "GOODHEARING" );
static const trait_id trait_GOODMEMORY( "GOODMEMORY" );
static const trait_id trait_HATES_BOOKS( "HATES_BOOKS" );
static const trait_id trait_HEAVYSLEEPER( "HEAVYSLEEPER" );
static const trait_id trait_HEAVYSLEEPER2( "HEAVYSLEEPER2" );
static const trait_id trait_HOARDER( "HOARDER" );
static const trait_id trait_HOLLOW_BONES( "HOLLOW_BONES" );
static const trait_id trait_HOOVES( "HOOVES" );
static const trait_id trait_HORNS_POINTED( "HORNS_POINTED" );
static const trait_id trait_HUGE( "HUGE" );
static const trait_id trait_HUGE_OK( "HUGE_OK" );
static const trait_id trait_HYPEROPIC( "HYPEROPIC" );
static const trait_id trait_ILLITERATE( "ILLITERATE" );
static const trait_id trait_INFIMMUNE( "INFIMMUNE" );
static const trait_id trait_INSECT_ARMS( "INSECT_ARMS" );
static const trait_id trait_INSECT_ARMS_OK( "INSECT_ARMS_OK" );
static const trait_id trait_INSOMNIA( "INSOMNIA" );
static const trait_id trait_INT_SLIME( "INT_SLIME" );
static const trait_id trait_JITTERY( "JITTERY" );
static const trait_id trait_LARGE( "LARGE" );
static const trait_id trait_LARGE_OK( "LARGE_OK" );
static const trait_id trait_LEAVES( "LEAVES" );
static const trait_id trait_LEG_TENTACLES( "LEG_TENTACLES" );
static const trait_id trait_LEG_TENT_BRACE( "LEG_TENT_BRACE" );
static const trait_id trait_LIGHTFUR( "LIGHTFUR" );
static const trait_id trait_LIGHTSTEP( "LIGHTSTEP" );
static const trait_id trait_LIGHT_BONES( "LIGHT_BONES" );
static const trait_id trait_LOVES_BOOKS( "LOVES_BOOKS" );
static const trait_id trait_LUPINE_EARS( "LUPINE_EARS" );
static const trait_id trait_LUPINE_FUR( "LUPINE_FUR" );
static const trait_id trait_MEMBRANE( "MEMBRANE" );
static const trait_id trait_MOODSWINGS( "MOODSWINGS" );
static const trait_id trait_MOREPAIN( "MORE_PAIN" );
static const trait_id trait_MOREPAIN2( "MORE_PAIN2" );
static const trait_id trait_MOREPAIN3( "MORE_PAIN3" );
static const trait_id trait_MOUSE_EARS( "MOUSE_EARS" );
static const trait_id trait_MYOPIC( "MYOPIC" );
static const trait_id trait_M_BLOSSOMS( "M_BLOSSOMS" );
static const trait_id trait_M_DEPENDENT( "M_DEPENDENT" );
static const trait_id trait_M_IMMUNE( "M_IMMUNE" );
static const trait_id trait_M_SKIN2( "M_SKIN2" );
static const trait_id trait_M_SKIN3( "M_SKIN3" );
static const trait_id trait_M_SPORES( "M_SPORES" );
static const trait_id trait_NARCOLEPTIC( "NARCOLEPTIC" );
static const trait_id trait_NAUSEA( "NAUSEA" );
static const trait_id trait_NONADDICTIVE( "NONADDICTIVE" );
static const trait_id trait_NOPAIN( "NOPAIN" );
static const trait_id trait_NO_THIRST( "NO_THIRST" );
static const trait_id trait_PACIFIST( "PACIFIST" );
static const trait_id trait_PADDED_FEET( "PADDED_FEET" );
static const trait_id trait_PAINREC1( "PAINREC1" );
static const trait_id trait_PAINREC2( "PAINREC2" );
static const trait_id trait_PAINREC3( "PAINREC3" );
static const trait_id trait_PAINRESIST( "PAINRESIST" );
static const trait_id trait_PAINRESIST_TROGLO( "PAINRESIST_TROGLO" );
static const trait_id trait_PARAIMMUNE( "PARAIMMUNE" );
static const trait_id trait_PARKOUR( "PARKOUR" );
static const trait_id trait_PAWS( "PAWS" );
static const trait_id trait_PAWS_LARGE( "PAWS_LARGE" );
static const trait_id trait_PER_SLIME( "PER_SLIME" );
static const trait_id trait_PER_SLIME_OK( "PER_SLIME_OK" );
static const trait_id trait_PONDEROUS1( "PONDEROUS1" );
static const trait_id trait_PONDEROUS2( "PONDEROUS2" );
static const trait_id trait_PONDEROUS3( "PONDEROUS3" );
static const trait_id trait_PRED2( "PRED2" );
static const trait_id trait_PRED3( "PRED3" );
static const trait_id trait_PRED4( "PRED4" );
static const trait_id trait_PROF_DICEMASTER( "PROF_DICEMASTER" );
static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" );
static const trait_id trait_PYROMANIA( "PYROMANIA" );
static const trait_id trait_QUICK( "QUICK" );
static const trait_id trait_QUILLS( "QUILLS" );
static const trait_id trait_RADIOACTIVE1( "RADIOACTIVE1" );
static const trait_id trait_RADIOACTIVE2( "RADIOACTIVE2" );
static const trait_id trait_RADIOACTIVE3( "RADIOACTIVE3" );
static const trait_id trait_RADIOGENIC( "RADIOGENIC" );
static const trait_id trait_REGEN( "REGEN" );
static const trait_id trait_REGEN_LIZ( "REGEN_LIZ" );
static const trait_id trait_ROOTS2( "ROOTS2" );
static const trait_id trait_ROOTS3( "ROOTS3" );
static const trait_id trait_SAPIOVORE( "SAPIOVORE" );
static const trait_id trait_SAVANT( "SAVANT" );
static const trait_id trait_SCHIZOPHRENIC( "SCHIZOPHRENIC" );
static const trait_id trait_SEESLEEP( "SEESLEEP" );
static const trait_id trait_SELFAWARE( "SELFAWARE" );
static const trait_id trait_SHARKTEETH( "SHARKTEETH" );
static const trait_id trait_SHELL2( "SHELL2" );
static const trait_id trait_SHOUT1( "SHOUT1" );
static const trait_id trait_SHOUT2( "SHOUT2" );
static const trait_id trait_SHOUT3( "SHOUT3" );
static const trait_id trait_SLEEK_SCALES( "SLEEK_SCALES" );
static const trait_id trait_SLIMESPAWNER( "SLIMESPAWNER" );
static const trait_id trait_SLIMY( "SLIMY" );
static const trait_id trait_SLOWHEALER( "SLOWHEALER" );
static const trait_id trait_SLOWLEARNER( "SLOWLEARNER" );
static const trait_id trait_SLOWREADER( "SLOWREADER" );
static const trait_id trait_SLOWRUNNER( "SLOWRUNNER" );
static const trait_id trait_SMELLY( "SMELLY" );
static const trait_id trait_SMELLY2( "SMELLY2" );
static const trait_id trait_SORES( "SORES" );
static const trait_id trait_SPINES( "SPINES" );
static const trait_id trait_SPIRITUAL( "SPIRITUAL" );
static const trait_id trait_SQUEAMISH( "SQUEAMISH" );
static const trait_id trait_STIMBOOST( "STIMBOOST" );
static const trait_id trait_STRONGSTOMACH( "STRONGSTOMACH" );
static const trait_id trait_SUNBURN( "SUNBURN" );
static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" );
static const trait_id trait_TAIL_FIN( "TAIL_FIN" );
static const trait_id trait_THICK_SCALES( "THICK_SCALES" );
static const trait_id trait_THORNS( "THORNS" );
static const trait_id trait_THRESH_SPIDER( "THRESH_SPIDER" );
static const trait_id trait_TOUGH_FEET( "TOUGH_FEET" );
static const trait_id trait_TROGLO( "TROGLO" );
static const trait_id trait_TROGLO2( "TROGLO2" );
static const trait_id trait_TROGLO3( "TROGLO3" );
static const trait_id trait_UNOBSERVANT( "UNOBSERVANT" );
static const trait_id trait_UNSTABLE( "UNSTABLE" );
static const trait_id trait_URSINE_EARS( "URSINE_EARS" );
static const trait_id trait_URSINE_EYE( "URSINE_EYE" );
static const trait_id trait_URSINE_FUR( "URSINE_FUR" );
static const trait_id trait_VISCOUS( "VISCOUS" );
static const trait_id trait_VOMITOUS( "VOMITOUS" );
static const trait_id trait_WATERSLEEP( "WATERSLEEP" );
static const trait_id trait_WEAKSCENT( "WEAKSCENT" );
static const trait_id trait_WEAKSTOMACH( "WEAKSTOMACH" );
static const trait_id trait_WEBBED( "WEBBED" );
static const trait_id trait_WEB_SPINNER( "WEB_SPINNER" );
static const trait_id trait_WEB_WALKER( "WEB_WALKER" );
static const trait_id trait_WEB_WEAVER( "WEB_WEAVER" );
static const trait_id trait_WHISKERS( "WHISKERS" );
static const trait_id trait_WHISKERS_RAT( "WHISKERS_RAT" );
static const trait_id trait_WINGS_BUTTERFLY( "WINGS_BUTTERFLY" );
static const trait_id trait_WOOLALLERGY( "WOOLALLERGY" );

static const itype_id OPTICAL_CLOAK_ITEM_ID( "optical_cloak" );

stat_mod player::get_pain_penalty() const
{
    stat_mod ret;
    int pain = get_perceived_pain();
    if( pain <= 0 ) {
        return ret;
    }

    int stat_penalty = std::floor( std::pow( pain, 0.8f ) / 10.0f );

    bool ceno = has_trait( trait_CENOBITE );
    if( !ceno ) {
        ret.strength = stat_penalty;
        ret.dexterity = stat_penalty;
    }

    if( !has_trait( trait_INT_SLIME ) ) {
        ret.intelligence = 1 + stat_penalty;
    } else {
        ret.intelligence = 1 + pain / 5;
    }

    ret.perception = stat_penalty * 2 / 3;

    ret.speed = std::pow( pain, 0.7f );
    if( ceno ) {
        ret.speed /= 2;
    }

    ret.speed = std::min( ret.speed, 50 );
    return ret;
}

player::player() : Character()
, next_climate_control_check( calendar::before_time_starts )
, cached_time( calendar::before_time_starts )
{
    id = -1; // -1 is invalid
    str_cur = 8;
    str_max = 8;
    dex_cur = 8;
    dex_max = 8;
    int_cur = 8;
    int_max = 8;
    per_cur = 8;
    per_max = 8;
    dodges_left = 1;
    blocks_left = 1;
    power_level = 0;
    max_power_level = 0;
    stamina = 1000; //Temporary value for stamina. It will be reset later from external json option.
    stim = 0;
    pkill = 0;
    radiation = 0;
    tank_plut = 0;
    reactor_plut = 0;
    slow_rad = 0;
    cash = 0;
    scent = 500;
    male = true;
    prof = profession::has_initialized() ? profession::generic() :
           nullptr; //workaround for a potential structural limitation, see player::create

    start_location = start_location_id( "shelter" );
    moves = 100;
    movecounter = 0;
    oxygen = 0;
    last_climate_control_ret = false;
    active_mission = nullptr;
    in_vehicle = false;
    controlling_vehicle = false;
    grab_point = tripoint_zero;
    grab_type = OBJECT_NONE;
    hauling = false;
    move_mode = "walk";
    style_selected = style_none;
    keep_hands_free = false;
    focus_pool = 100;
    last_item = itype_id( "null" );
    sight_max = 9999;
    last_batch = 0;
    lastconsumed = itype_id( "null" );
    next_expected_position = cata::nullopt;
    death_drops = true;
    show_map_memory = true;

    empty_traits();

    temp_cur.fill( BODYTEMP_NORM );
    frostbite_timer.fill( 0 );
    temp_conv.fill( BODYTEMP_NORM );
    body_wetness.fill( 0 );
    nv_cached = false;
    volume = 0;

    for( const auto &v : vitamin::all() ) {
        vitamin_levels[ v.first ] = 0;
    }

    memorial_log.clear();

    drench_capacity[bp_eyes] = 1;
    drench_capacity[bp_mouth] = 1;
    drench_capacity[bp_head] = 7;
    drench_capacity[bp_leg_l] = 11;
    drench_capacity[bp_leg_r] = 11;
    drench_capacity[bp_foot_l] = 3;
    drench_capacity[bp_foot_r] = 3;
    drench_capacity[bp_arm_l] = 10;
    drench_capacity[bp_arm_r] = 10;
    drench_capacity[bp_hand_l] = 3;
    drench_capacity[bp_hand_r] = 3;
    drench_capacity[bp_torso] = 40;

    recalc_sight_limits();
    reset_encumbrance();

    ma_styles = {{
        style_none, style_kicks
    }};
}

player::~player() = default;
player::player(player &&) = default;
player &player::operator=(player &&) = default;

void player::normalize()
{
    Character::normalize();

    style_selected = style_none;

    recalc_hp();

    temp_conv.fill( BODYTEMP_NORM );
    stamina = get_stamina_max();
}

std::string player::disp_name( bool possessive ) const
{
    if( !possessive ) {
        if( is_player() ) {
            return pgettext( "not possessive", "you" );
        }
        return name;
    } else {
        if( is_player() ) {
            return _( "your" );
        }
        return string_format( _( "%s's" ), name.c_str() );
    }
}

std::string player::skin_name() const
{
    //TODO: Return actual deflecting layer name
    return _( "armor" );
}

void player::reset_stats()
{
    // Trait / mutation buffs
    if( has_trait( trait_THICK_SCALES ) ) {
        add_miss_reason( _( "Your thick scales get in the way." ), 2 );
    }
    if( has_trait( trait_CHITIN2 ) || has_trait( trait_CHITIN3 ) || has_trait( trait_CHITIN_FUR3 ) ) {
        add_miss_reason( _( "Your chitin gets in the way." ), 1 );
    }
    if( has_trait( trait_COMPOUND_EYES ) && !wearing_something_on( bp_eyes ) ) {
        mod_per_bonus( 1 );
    }
    if( has_trait( trait_INSECT_ARMS ) ) {
        add_miss_reason( _( "Your insect limbs get in the way." ), 2 );
    }
    if( has_trait( trait_INSECT_ARMS_OK ) ) {
        if( !wearing_something_on( bp_torso ) ) {
            mod_dex_bonus( 1 );
        } else {
            mod_dex_bonus( -1 );
            add_miss_reason( _( "Your clothing restricts your insect arms." ), 1 );
        }
    }
    if( has_trait( trait_WEBBED ) ) {
        add_miss_reason( _( "Your webbed hands get in the way." ), 1 );
    }
    if( has_trait( trait_ARACHNID_ARMS ) ) {
        add_miss_reason( _( "Your arachnid limbs get in the way." ), 4 );
    }
    if( has_trait( trait_ARACHNID_ARMS_OK ) ) {
        if( !wearing_something_on( bp_torso ) ) {
            mod_dex_bonus( 2 );
        } else if( !exclusive_flag_coverage( "OVERSIZE" ).test( bp_torso ) ) {
            mod_dex_bonus( -2 );
            add_miss_reason( _( "Your clothing constricts your arachnid limbs." ), 2 );
        }
    }
    const auto set_fake_effect_dur = [this]( const efftype_id &type, const time_duration &dur ) {
        effect &eff = get_effect( type );
        if( eff.get_duration() == dur ) {
            return;
        }

        if( eff.is_null() && dur > 0_turns ) {
            add_effect( type, dur, num_bp, true );
        } else if( dur > 0_turns ) {
            eff.set_duration( dur );
        } else {
            remove_effect( type, num_bp );
        }
    };
    // Painkiller
    set_fake_effect_dur( effect_pkill, 1_turns * pkill );

    // Pain
    if( get_perceived_pain() > 0 ) {
        const auto ppen = get_pain_penalty();
        mod_str_bonus( -ppen.strength );
        mod_dex_bonus( -ppen.dexterity );
        mod_int_bonus( -ppen.intelligence );
        mod_per_bonus( -ppen.perception );
        if( ppen.dexterity > 0 ) {
            add_miss_reason( _( "Your pain distracts you!" ), unsigned( ppen.dexterity ) );
        }
    }

    // Radiation
    set_fake_effect_dur( effect_irradiated, 1_turns * radiation );
    // Morale
    const int morale = get_morale_level();
    set_fake_effect_dur( effect_happy, 1_turns * morale );
    set_fake_effect_dur( effect_sad, 1_turns * -morale );

    // Stimulants
    set_fake_effect_dur( effect_stim, 1_turns * stim );
    set_fake_effect_dur( effect_depressants, 1_turns * -stim );
    if( has_trait ( trait_STIMBOOST ) ) {
        set_fake_effect_dur ( effect_stim_overdose, 1_turns * (stim - 60) );
    } else {
        set_fake_effect_dur ( effect_stim_overdose, 1_turns * (stim - 30) );
    }
    // Starvation
    if( get_starvation() >= 200 ) {
        // We die at 6000
        const int dex_mod = -( get_starvation() + 300 ) / 1000;
        add_miss_reason( _( "You're weak from hunger." ), unsigned( -dex_mod ) );
        mod_str_bonus( -( get_starvation() + 300 ) / 500 );
        mod_dex_bonus( dex_mod );
        mod_int_bonus( -( get_starvation() + 300 ) / 1000 );
    }
    // Thirst
    if( get_thirst() >= 200 ) {
        // We die at 1200
        const int dex_mod = -get_thirst() / 200;
        add_miss_reason( _( "You're weak from thirst." ), unsigned( -dex_mod ) );
        mod_str_bonus( -get_thirst() / 200 );
        mod_dex_bonus( dex_mod );
        mod_int_bonus( -get_thirst() / 200 );
        mod_per_bonus( -get_thirst() / 200 );
    }
    if( get_sleep_deprivation() >= SLEEP_DEPRIVATION_HARMLESS ) {
        set_fake_effect_dur( effect_sleep_deprived, 1_turns * get_sleep_deprivation() );
    }
    else if( has_effect( effect_sleep_deprived ) ) {
        remove_effect( effect_sleep_deprived );
    }

    // Dodge-related effects
    mod_dodge_bonus( mabuff_dodge_bonus() -
                     ( encumb( bp_leg_l ) + encumb( bp_leg_r ) ) / 20.0f -
                     ( encumb( bp_torso ) / 10.0f ) );
    // Whiskers don't work so well if they're covered
    if( has_trait( trait_WHISKERS ) && !wearing_something_on( bp_mouth ) ) {
        mod_dodge_bonus( 1 );
    }
    if( has_trait( trait_WHISKERS_RAT ) && !wearing_something_on( bp_mouth ) ) {
        mod_dodge_bonus( 2 );
    }
    // Spider hair is basically a full-body set of whiskers, once you get the brain for it
    if( has_trait( trait_CHITIN_FUR3 ) ) {
        static const std::array<body_part, 5> parts {{bp_head, bp_arm_r, bp_arm_l, bp_leg_r, bp_leg_l}};
        for( auto bp : parts ) {
            if( !wearing_something_on( bp ) ) {
                mod_dodge_bonus( +1 );
            }
        }
        // Torso handled separately, bigger bonus
        if( !wearing_something_on( bp_torso ) ) {
            mod_dodge_bonus( 4 );
        }
    }

    // Hit-related effects
    mod_hit_bonus( mabuff_tohit_bonus() + weapon.type->m_to_hit );

    // Apply static martial arts buffs
    ma_static_effects();

    if( calendar::once_every( 1_minutes ) ) {
        update_mental_focus();
    }

    // Effects
    for( auto maps : *effects ) {
        for( auto i : maps.second ) {
            const auto &it = i.second;
            bool reduced = resists_effect( it );
            mod_str_bonus( it.get_mod( "STR", reduced ) );
            mod_dex_bonus( it.get_mod( "DEX", reduced ) );
            mod_per_bonus( it.get_mod( "PER", reduced ) );
            mod_int_bonus( it.get_mod( "INT", reduced ) );
        }
    }

    Character::reset_stats();

    recalc_sight_limits();
    recalc_speed_bonus();
}

void player::process_turn()
{
    // Has to happen before reset_stats
    clear_miss_reasons();

    Character::process_turn();

    // If we're actively handling something we can't just drop it on the ground
    // in the middle of handling it
    if ( activity.targets.empty() ) {
        drop_invalid_inventory();
    }

    // Didn't just pick something up
    last_item = itype_id( "null" );

    if( has_active_bionic( bio_metabolics ) && power_level + 25 <= max_power_level &&
        get_hunger() < 100 && calendar::once_every( 5_turns ) ) {
        mod_hunger( 2 );
        charge_power( 25 );
    }
    if( has_trait( trait_DEBUG_BIONIC_POWER ) ) {
        charge_power( max_power_level );
    }

    visit_items( [this]( item * e ) {
        e->process_artifact( this, pos() );
        return VisitResponse::NEXT;
    } );

    suffer();

    // Set our scent towards the norm
    int norm_scent = 500;
    if( has_trait( trait_WEAKSCENT ) ) {
        norm_scent = 300;
    }
    if( has_trait( trait_SMELLY ) ) {
        norm_scent = 800;
    }
    if( has_trait( trait_SMELLY2 ) ) {
        norm_scent = 1200;
    }
    // Not so much that you don't have a scent
    // but that you smell like a plant, rather than
    // a human. When was the last time you saw a critter
    // attack a bluebell or an apple tree?
    if( ( has_trait( trait_FLOWERS ) ) && ( !( has_trait( trait_CHLOROMORPH ) ) ) ) {
        norm_scent -= 200;
    }
    // You *are* a plant.  Unless someone hunts triffids by scent,
    // you don't smell like prey.
    if( has_trait( trait_CHLOROMORPH ) ) {
        norm_scent = 0;
    }

    // Scent increases fast at first, and slows down as it approaches normal levels.
    // Estimate it will take about norm_scent * 2 turns to go from 0 - norm_scent / 2
    // Without smelly trait this is about 1.5 hrs. Slows down significantly after that.
    if( scent < rng( 0, norm_scent ) ) {
        scent++;
    }

    // Unusually high scent decreases steadily until it reaches normal levels.
    if( scent > norm_scent ) {
        scent--;
    }

    // We can dodge again! Assuming we can actually move...
    if( !in_sleep_state() ) {
        blocks_left = get_num_blocks();
        dodges_left = get_num_dodges();
    } else {
        blocks_left = 0;
        dodges_left = 0;
    }

    // auto-learning. This is here because skill-increases happens all over the place:
    // SkillLevel::readBook (has no connection to the skill or the player),
    // player::read, player::practice, ...
    /** @EFFECT_UNARMED >1 allows spontaneous discovery of brawling martial art style */
    if( get_skill_level( skill_unarmed ) >= 2 ) {
        const matype_id brawling( "style_brawling" );
        if( !has_martialart( brawling ) ) {
            add_martialart( brawling );
            add_msg_if_player( m_info, _( "You learned a new style." ) );
        }
    }
    if( inp_mngr.get_previously_pressed_key() == KEY_LEFT ) {
        facing = FD_LEFT;
    }
    else if( inp_mngr.get_previously_pressed_key() == KEY_RIGHT ) {
        facing = FD_RIGHT;
    }
}

void player::action_taken()
{
    nv_cached = false;
}

void player::update_morale()
{
    morale->decay( 10_turns );
    apply_persistent_morale();
}

void player::apply_persistent_morale()
{
    // Hoarders get a morale penalty if they're not carrying a full inventory.
    if( has_trait( trait_HOARDER ) ) {
        int pen = ( volume_capacity() - volume_carried() ) / 125_ml;
        if( pen > 70 ) {
            pen = 70;
        }
        if( pen <= 0 ) {
            pen = 0;
        }
        if( has_effect( effect_took_xanax ) ) {
            pen = pen / 7;
        } else if( has_effect( effect_took_prozac ) ) {
            pen = pen / 2;
        }
        if( pen > 0 ) {
            add_morale( MORALE_PERM_HOARDER, -pen, -pen, 1_minutes, 1_minutes, true );
        }
    }
}

void player::update_mental_focus()
{
    int focus_gain_rate = calc_focus_equilibrium() - focus_pool;

    // handle negative gain rates in a symmetric manner
    int base_change = 1;
    if( focus_gain_rate < 0 ) {
        base_change = -1;
        focus_gain_rate = -focus_gain_rate;
    }

    // for every 100 points, we have a flat gain of 1 focus.
    // for every n points left over, we have an n% chance of 1 focus
    int gain = focus_gain_rate / 100;
    if( rng( 1, 100 ) <= ( focus_gain_rate % 100 ) ) {
        gain++;
    }

    focus_pool += ( gain * base_change );

    // Fatigue should at least prevent high focus
    // This caps focus gain at 60(arbitrary value) if you're Dead Tired
    if( get_fatigue() >= DEAD_TIRED && focus_pool > 60 ) {
        focus_pool = 60;
    }

    // Moved from calc_focus_equilibrium, because it is now const
    if( activity.id() == activity_id( "ACT_READ" ) ) {
        const item *book = activity.targets[0].get_item();
        if( get_item_position( book ) == INT_MIN || !book->is_book() ) {
            add_msg_if_player( m_bad, _( "You lost your book! You stop reading." ) );
            activity.set_to_null();
        }
    }
}

// written mostly by FunnyMan3595 in Github issue #613 (DarklingWolf's repo),
// with some small edits/corrections by Soron
int player::calc_focus_equilibrium() const
{
    int focus_gain_rate = 100;

    if( activity.id() == activity_id( "ACT_READ" ) ) {
        const item &book = *activity.targets[0].get_item();
        if( book.is_book() && get_item_position( &book ) != INT_MIN ) {
            auto &bt = *book.type->book;
            // apply a penalty when we're actually learning something
            const SkillLevel &skill_level = get_skill_level_object( bt.skill );
            if( skill_level.can_train() && skill_level < bt.level ) {
                focus_gain_rate -= 50;
            }
        }
    }

    int eff_morale = get_morale_level();
    // Factor in perceived pain, since it's harder to rest your mind while your body hurts.
    // Cenobites don't mind, though
    if( !has_trait( trait_CENOBITE ) ) {
        eff_morale = eff_morale - get_perceived_pain();
    }

    if( eff_morale < -99 ) {
        // At very low morale, focus goes up at 1% of the normal rate.
        focus_gain_rate = 1;
    } else if( eff_morale <= 50 ) {
        // At -99 to +50 morale, each point of morale gives 1% of the normal rate.
        focus_gain_rate += eff_morale;
    } else {
        /* Above 50 morale, we apply strong diminishing returns.
         * Each block of 50% takes twice as many morale points as the previous one:
         * 150% focus gain at 50 morale (as before)
         * 200% focus gain at 150 morale (100 more morale)
         * 250% focus gain at 350 morale (200 more morale)
         * ...
         * Cap out at 400% focus gain with 3,150+ morale, mostly as a sanity check.
         */

        int block_multiplier = 1;
        int morale_left = eff_morale;
        while( focus_gain_rate < 400 ) {
            if( morale_left > 50 * block_multiplier ) {
                // We can afford the entire block.  Get it and continue.
                morale_left -= 50 * block_multiplier;
                focus_gain_rate += 50;
                block_multiplier *= 2;
            } else {
                // We can't afford the entire block.  Each block_multiplier morale
                // points give 1% focus gain, and then we're done.
                focus_gain_rate += morale_left / block_multiplier;
                break;
            }
        }
    }

    // This should be redundant, but just in case...
    if( focus_gain_rate < 1 ) {
        focus_gain_rate = 1;
    } else if( focus_gain_rate > 400 ) {
        focus_gain_rate = 400;
    }

    return focus_gain_rate;
}

/* Here lies the intended effects of body temperature

Assumption 1 : a naked person is comfortable at 19C/66.2F (31C/87.8F at rest).
Assumption 2 : a "lightly clothed" person is comfortable at 13C/55.4F (25C/77F at rest).
Assumption 3 : the player is always running, thus generating more heat.
Assumption 4 : frostbite cannot happen above 0C temperature.*
* In the current model, a naked person can get frostbite at 1C. This isn't true, but it's a compromise with using nice whole numbers.

Here is a list of warmth values and the corresponding temperatures in which the player is comfortable, and in which the player is very cold.

Warmth  Temperature (Comfortable)    Temperature (Very cold)    Notes
  0       19C /  66.2F               -11C /  12.2F               * Naked
 10       13C /  55.4F               -17C /   1.4F               * Lightly clothed
 20        7C /  44.6F               -23C /  -9.4F
 30        1C /  33.8F               -29C / -20.2F
 40       -5C /  23.0F               -35C / -31.0F
 50      -11C /  12.2F               -41C / -41.8F
 60      -17C /   1.4F               -47C / -52.6F
 70      -23C /  -9.4F               -53C / -63.4F
 80      -29C / -20.2F               -59C / -74.2F
 90      -35C / -31.0F               -65C / -85.0F
100      -41C / -41.8F               -71C / -95.8F

WIND POWER
Except for the last entry, pressures are sort of made up...

Breeze : 5mph (1015 hPa)
Strong Breeze : 20 mph (1000 hPa)
Moderate Gale : 30 mph (990 hPa)
Storm : 50 mph (970 hPa)
Hurricane : 100 mph (920 hPa)
HURRICANE : 185 mph (880 hPa) [Ref: Hurricane Wilma]
*/

void player::update_bodytemp()
{
    if( has_trait( trait_DEBUG_NOTEMP ) ) {
        temp_cur.fill( BODYTEMP_NORM );
        temp_conv.fill( BODYTEMP_NORM );
        return;
    }
    /* Cache calls to g->get_temperature( player position ), used in several places in function */
    const auto player_local_temp = g->get_temperature( pos() );
    // NOTE : visit weather.h for some details on the numbers used
    // Converts temperature to Celsius/10
    int Ctemperature = int( 100 * temp_to_celsius( player_local_temp ) );
    const w_point weather = *g->weather_precise;
    int vehwindspeed = 0;
    if( const optional_vpart_position vp = g->m.veh_at( pos() ) ) {
        vehwindspeed = abs( vp->vehicle().velocity / 100 ); // vehicle velocity in mph
    }
    const oter_id &cur_om_ter = overmap_buffer.ter( global_omt_location() );
    bool sheltered = g->is_sheltered( pos() );
    double total_windpower = get_local_windpower( weather.windpower + vehwindspeed, cur_om_ter, pos(), weather.winddirection, sheltered );

    // Let's cache this not to check it num_bp times
    const bool has_bark = has_trait( trait_BARK );
    const bool has_sleep = has_effect( effect_sleep );
    const bool has_sleep_state = has_sleep || in_sleep_state();
    const bool has_heatsink = has_bionic( bio_heatsink ) || is_wearing( "rm13_armor_on" ) ||
                              has_trait( trait_M_SKIN2 ) || has_trait( trait_M_SKIN3 );
    const bool has_common_cold = has_effect( effect_common_cold );
    const bool has_climate_control = in_climate_control();
    const bool use_floor_warmth = can_use_floor_warmth();
    const furn_id furn_at_pos = g->m.furn( pos() );
    // Temperature norms
    // Ambient normal temperature is lower while asleep
    const int ambient_norm = has_sleep ? 3100 : 1900;

    /**
     * Calculations that affect all body parts equally go here, not in the loop
     */
    // Hunger / Starvation
    // -1000 when about to starve to death
    // -1333 when starving with light eater
    // -2000 if you managed to get 0 metabolism rate somehow
    const float met_rate = metabolic_rate();
    const int hunger_warmth = int( 2000 * std::min( met_rate, 1.0f ) - 2000 );
    // Give SOME bonus to those living furnaces with extreme metabolism
    const int metabolism_warmth = int( std::max( 0.0f, met_rate - 1.0f ) * 1000 );
    // Fatigue
    // ~-900 when exhausted
    const int fatigue_warmth = has_sleep ? 0 : int( std::min( 0.0f, -1.5f * get_fatigue() ) );

    // Sunlight
    const int sunlight_warmth = g->is_in_sunlight( pos() ) ? 0 :
                                ( g->weather == WEATHER_SUNNY ? 1000 : 500 );
    const int best_fire = get_heat_radiation( pos(), true );

    const int lying_warmth = use_floor_warmth ? floor_warmth( pos() ) : 0;
    const int water_temperature =
        100 * temp_to_celsius( g->get_cur_weather_gen().get_water_temperature() );

    // Correction of body temperature due to traits and mutations
    // Lower heat is applied always
    const int mutation_heat_low = bodytemp_modifier_traits( false );
    const int mutation_heat_high = bodytemp_modifier_traits( true );
    // Difference between high and low is the "safe" heat - one we only apply if it's beneficial
    const int mutation_heat_bonus = mutation_heat_high - mutation_heat_low;

    // Current temperature and converging temperature calculations
    for( const body_part bp : all_body_parts ) {
        // Skip eyes
        if( bp == bp_eyes ) {
            continue;
        }

        // This adjusts the temperature scale to match the bodytemp scale,
        // it needs to be reset every iteration
        int adjusted_temp = ( Ctemperature - ambient_norm );
        int bp_windpower = total_windpower;
        // Represents the fact that the body generates heat when it is cold.
        // TODO : should this increase hunger?
        double scaled_temperature = logarithmic_range( BODYTEMP_VERY_COLD, BODYTEMP_VERY_HOT,
                                    temp_cur[bp] );
        // Produces a smooth curve between 30.0 and 60.0.
        double homeostasis_adjustement = 30.0 * ( 1.0 + scaled_temperature );
        int clothing_warmth_adjustement = int( homeostasis_adjustement * warmth( bp ) );
        int clothing_warmth_adjusted_bonus = int( homeostasis_adjustement * bonus_item_warmth( bp ) );
        // WINDCHILL

        bp_windpower = static_cast<int>( static_cast<float>( bp_windpower ) * ( 1 - get_wind_resistance( bp ) / 100.0 ) );
        // Calculate windchill
        int windchill = get_local_windchill( player_local_temp,
                                             get_local_humidity( weather.humidity, g->weather,
                                                     sheltered ),
                                             bp_windpower );
        // If you're standing in water, air temperature is replaced by water temperature. No wind.
        const ter_id ter_at_pos = g->m.ter( pos() );
        // Convert to 0.01C
        if( ( ter_at_pos == t_water_dp || ter_at_pos == t_water_pool || ter_at_pos == t_swater_dp ) ||
            ( ( ter_at_pos == t_water_sh || ter_at_pos == t_swater_sh || ter_at_pos == t_sewage ) &&
              ( bp == bp_foot_l || bp == bp_foot_r || bp == bp_leg_l || bp == bp_leg_r ) ) ) {
            adjusted_temp += water_temperature - Ctemperature; // Swap out air temp for water temp.
            windchill = 0;
        }

        // Convergent temperature is affected by ambient temperature,
        // clothing warmth, and body wetness.
        temp_conv[bp] = BODYTEMP_NORM + adjusted_temp + windchill * 100 + clothing_warmth_adjustement;
        // HUNGER / STARVATION
        temp_conv[bp] += hunger_warmth;
        // FATIGUE
        temp_conv[bp] += fatigue_warmth;
        // Mutations
        temp_conv[bp] += mutation_heat_low;
        // DIRECT HEAT SOURCES (generates body heat, helps fight frostbite)
        // Bark : lowers blister count to -5; harder to get blisters
        int blister_count = ( has_bark ? -5 : 0 ); // If the counter is high, your skin starts to burn

        const int h_radiation = get_heat_radiation( pos(), false );
        if( frostbite_timer[bp] > 0 ) {
            frostbite_timer[bp] -= std::max( 5, h_radiation );
        }
        // 111F (44C) is a temperature in which proteins break down: https://en.wikipedia.org/wiki/Burn
        blister_count += h_radiation - 111 > 0 ? std::max( static_cast<int>( sqrt( h_radiation - 111 ) ), 0 ) : 0;

        const bool pyromania = has_trait( trait_PYROMANIA );
        // BLISTERS : Skin gets blisters from intense heat exposure.
        // Fire protection protects from blisters.
        // Heatsinks give near-immunity.
        if( blister_count - get_armor_fire( bp ) - ( has_heatsink ? 20 : 0 ) > 0 ) {
            add_effect( effect_blisters, 1_turns, bp );
            if( pyromania ) {
                add_morale( MORALE_PYROMANIA_NEARFIRE, 10, 10, 1_hours, 30_minutes ); // Proximity that's close enough to harm us gives us a bit of a thrill
                rem_morale( MORALE_PYROMANIA_NOFIRE );
            }
        } else if( pyromania && best_fire >= 1 ) { // Only give us fire bonus if there's actually fire
            add_morale( MORALE_PYROMANIA_NEARFIRE, 5, 5, 30_minutes, 15_minutes ); // Gain a much smaller mood boost even if it doesn't hurt us
            rem_morale( MORALE_PYROMANIA_NOFIRE );
        }

        temp_conv[bp] += sunlight_warmth;
        // DISEASES
        if( bp == bp_head && has_effect( effect_flu ) ) {
            temp_conv[bp] += 1500;
        }
        if( has_common_cold ) {
            temp_conv[bp] -= 750;
        }
        // Loss of blood results in loss of body heat, 1% bodyheat lost per 2% hp lost
        temp_conv[bp] -= blood_loss( bp ) * temp_conv[bp] / 200;

        // EQUALIZATION
        switch( bp ) {
            case bp_torso:
                temp_equalizer( bp_torso, bp_arm_l );
                temp_equalizer( bp_torso, bp_arm_r );
                temp_equalizer( bp_torso, bp_leg_l );
                temp_equalizer( bp_torso, bp_leg_r );
                temp_equalizer( bp_torso, bp_head );
                break;
            case bp_head:
                temp_equalizer( bp_head, bp_torso );
                temp_equalizer( bp_head, bp_mouth );
                break;
            case bp_arm_l:
                temp_equalizer( bp_arm_l, bp_torso );
                temp_equalizer( bp_arm_l, bp_hand_l );
                break;
            case bp_arm_r:
                temp_equalizer( bp_arm_r, bp_torso );
                temp_equalizer( bp_arm_r, bp_hand_r );
                break;
            case bp_leg_l:
                temp_equalizer( bp_leg_l, bp_torso );
                temp_equalizer( bp_leg_l, bp_foot_l );
                break;
            case bp_leg_r:
                temp_equalizer( bp_leg_r, bp_torso );
                temp_equalizer( bp_leg_r, bp_foot_r );
                break;
            case bp_mouth:
                temp_equalizer( bp_mouth, bp_head );
                break;
            case bp_hand_l:
                temp_equalizer( bp_hand_l, bp_arm_l );
                break;
            case bp_hand_r:
                temp_equalizer( bp_hand_r, bp_arm_r );
                break;
            case bp_foot_l:
                temp_equalizer( bp_foot_l, bp_leg_l );
                break;
            case bp_foot_r:
                temp_equalizer( bp_foot_r, bp_leg_r );
                break;
            default:
                debugmsg( "Wacky body part temperature equalization!" );
                break;
        }

        // Climate Control eases the effects of high and low ambient temps
        if( has_climate_control ) {
            temp_conv[bp] = temp_corrected_by_climate_control( temp_conv[bp] );
        }

        // FINAL CALCULATION : Increments current body temperature towards convergent.
        int bonus_fire_warmth = 0;
        if( !has_sleep_state && best_fire > 0 ) {
            // Warming up over a fire
            // Extremities are easier to extend over a fire
            switch( bp ) {
                case bp_head:
                case bp_torso:
                case bp_mouth:
                case bp_leg_l:
                case bp_leg_r:
                    bonus_fire_warmth = best_fire * best_fire * 150; // Not much
                    break;
                case bp_arm_l:
                case bp_arm_r:
                    bonus_fire_warmth = best_fire * 600; // A fair bit
                    break;
                case bp_foot_l:
                case bp_foot_r:
                    if( furn_at_pos != f_null ) {
                        // Can sit on something to lift feet up to the fire
                        bonus_fire_warmth = best_fire * furn_at_pos.obj().bonus_fire_warmth_feet;
                    } else {
                        // Has to stand
                        bonus_fire_warmth = best_fire * 300;
                    }
                    break;
                case bp_hand_l:
                case bp_hand_r:
                    bonus_fire_warmth = best_fire * 1500; // A lot
                default:
                    break;
            }
        }

        const int comfortable_warmth = bonus_fire_warmth + lying_warmth;
        const int bonus_warmth = comfortable_warmth + metabolism_warmth + mutation_heat_bonus;
        if( bonus_warmth > 0 ) {
            // Approximate temp_conv needed to reach comfortable temperature in this very turn
            // Basically inverted formula for temp_cur below
            int desired = 501 * BODYTEMP_NORM - 499 * temp_cur[bp];
            if( std::abs( BODYTEMP_NORM - desired ) < 1000 ) {
                desired = BODYTEMP_NORM; // Ensure that it converges
            } else if( desired > BODYTEMP_HOT ) {
                desired = BODYTEMP_HOT; // Cap excess at sane temperature
            }

            if( desired < temp_conv[bp] ) {
                // Too hot, can't help here
            } else if( desired < temp_conv[bp] + bonus_warmth ) {
                // Use some heat, but not all of it
                temp_conv[bp] = desired;
            } else {
                // Use all the heat
                temp_conv[bp] += bonus_warmth;
            }

            // Morale bonus for comfiness - only if actually comfy (not too warm/cold)
            // Spread the morale bonus in time.
            if( comfortable_warmth > 0 &&
                calendar::turn % MINUTES( 1 ) == ( MINUTES( bp ) / MINUTES( num_bp ) ) &&
                get_effect_int( effect_cold, num_bp ) == 0 &&
                get_effect_int( effect_hot, num_bp ) == 0 &&
                temp_cur[bp] > BODYTEMP_COLD && temp_cur[bp] <= BODYTEMP_NORM ) {
                add_morale( MORALE_COMFY, 1, 10, 2_minutes, 1_minutes, true );
            }
        }

        int temp_before = temp_cur[bp];
        int temp_difference = temp_before - temp_conv[bp]; // Negative if the player is warming up.
        // exp(-0.001) : half life of 60 minutes, exp(-0.002) : half life of 30 minutes,
        // exp(-0.003) : half life of 20 minutes, exp(-0.004) : half life of 15 minutes
        int rounding_error = 0;
        // If temp_diff is small, the player cannot warm up due to rounding errors. This fixes that.
        if( temp_difference < 0 && temp_difference > -600 ) {
            rounding_error = 1;
        }
        if( temp_cur[bp] != temp_conv[bp] ) {
            temp_cur[bp] = int( temp_difference * exp( -0.002 ) + temp_conv[bp] + rounding_error );
        }
        // This statement checks if we should be wearing our bonus warmth.
        // If, after all the warmth calculations, we should be, then we have to recalculate the temperature.
        if( clothing_warmth_adjusted_bonus != 0 &&
            ( ( temp_conv[bp] + clothing_warmth_adjusted_bonus ) < BODYTEMP_HOT ||
              temp_cur[bp] < BODYTEMP_COLD ) ) {
            temp_conv[bp] += clothing_warmth_adjusted_bonus;
            rounding_error = 0;
            if( temp_difference < 0 && temp_difference > -600 ) {
                rounding_error = 1;
            }
            if( temp_before != temp_conv[bp] ) {
                temp_difference = temp_before - temp_conv[bp];
                temp_cur[bp] = int( temp_difference * exp( -0.002 ) + temp_conv[bp] + rounding_error );
            }
        }
        int temp_after = temp_cur[bp];
        // PENALTIES
        if( temp_cur[bp] < BODYTEMP_FREEZING ) {
            add_effect( effect_cold, 1_turns, bp, true, 3 );
        } else if( temp_cur[bp] < BODYTEMP_VERY_COLD ) {
            add_effect( effect_cold, 1_turns, bp, true, 2 );
        } else if( temp_cur[bp] < BODYTEMP_COLD ) {
            add_effect( effect_cold, 1_turns, bp, true, 1 );
        } else if( temp_cur[bp] > BODYTEMP_SCORCHING ) {
            add_effect( effect_hot, 1_turns, bp, true, 3 );
        } else if( temp_cur[bp] > BODYTEMP_VERY_HOT ) {
            add_effect( effect_hot, 1_turns, bp, true, 2 );
        } else if( temp_cur[bp] > BODYTEMP_HOT ) {
            add_effect( effect_hot, 1_turns, bp, true, 1 );
        } else {
            if( temp_cur[bp] >= BODYTEMP_COLD ) {
                remove_effect( effect_cold, bp );
            }
            if( temp_cur[bp] <= BODYTEMP_HOT ) {
                remove_effect( effect_hot, bp );
            }
        }
        // FROSTBITE - only occurs to hands, feet, face
        /**

        Source : http://www.atc.army.mil/weather/windchill.pdf

        Temperature and wind chill are main factors, mitigated by clothing warmth. Each 10 warmth protects against 2C of cold.

        1200 turns in low risk, + 3 tics
        450 turns in moderate risk, + 8 tics
        50 turns in high risk, +72 tics

        Let's say frostnip @ 1800 tics, frostbite @ 3600 tics

        >> Chunked into 8 parts (http://imgur.com/xlTPmJF)
        -- 2 hour risk --
        Between 30F and 10F
        Between 10F and -5F, less than 20mph, -4x + 3y - 20 > 0, x : F, y : mph
        -- 45 minute risk --
        Between 10F and -5F, less than 20mph, -4x + 3y - 20 < 0, x : F, y : mph
        Between 10F and -5F, greater than 20mph
        Less than -5F, less than 10 mph
        Less than -5F, more than 10 mph, -4x + 3y - 170 > 0, x : F, y : mph
        -- 5 minute risk --
        Less than -5F, more than 10 mph, -4x + 3y - 170 < 0, x : F, y : mph
        Less than -35F, more than 10 mp
        **/

        if( bp == bp_mouth || bp == bp_foot_r || bp == bp_foot_l || bp == bp_hand_r || bp == bp_hand_l ) {
            // Handle the frostbite timer
            // Need temps in F, windPower already in mph
            int wetness_percentage = 100 * body_wetness[bp] / drench_capacity[bp]; // 0 - 100
            // Warmth gives a slight buff to temperature resistance
            // Wetness gives a heavy nerf to temperature resistance
            int Ftemperature = int( player_local_temp +
                                    warmth( bp ) * 0.2 - 20 * wetness_percentage / 100 );
            // Windchill reduced by your armor
            int FBwindPower = int( total_windpower * ( 1 - get_wind_resistance( bp ) / 100.0 ) );

            int intense = get_effect_int( effect_frostbite, bp );

            // This has been broken down into 8 zones
            // Low risk zones (stops at frostnip)
            if( temp_cur[bp] < BODYTEMP_COLD &&
                ( ( Ftemperature < 30 && Ftemperature >= 10 ) ||
                  ( Ftemperature < 10 && Ftemperature >= -5 &&
                    FBwindPower < 20 && -4 * Ftemperature + 3 * FBwindPower - 20 >= 0 ) ) ) {
                if( frostbite_timer[bp] < 2000 ) {
                    frostbite_timer[bp] += 3;
                }
                if( one_in( 100 ) && !has_effect( effect_frostbite, bp ) ) {
                    add_msg( m_warning, _( "Your %s will be frostnipped in the next few hours." ),
                             body_part_name( bp ).c_str() );
                }
                // Medium risk zones
            } else if( temp_cur[bp] < BODYTEMP_COLD &&
                       ( ( Ftemperature < 10 && Ftemperature >= -5 && FBwindPower < 20 &&
                           -4 * Ftemperature + 3 * FBwindPower - 20 < 0 ) ||
                         ( Ftemperature < 10 && Ftemperature >= -5 && FBwindPower >= 20 ) ||
                         ( Ftemperature < -5 && FBwindPower < 10 ) ||
                         ( Ftemperature < -5 && FBwindPower >= 10 &&
                           -4 * Ftemperature + 3 * FBwindPower - 170 >= 0 ) ) ) {
                frostbite_timer[bp] += 8;
                if( one_in( 100 ) && intense < 2 ) {
                    add_msg( m_warning, _( "Your %s will be frostbitten within the hour!" ),
                             body_part_name( bp ).c_str() );
                }
                // High risk zones
            } else if( temp_cur[bp] < BODYTEMP_COLD &&
                       ( ( Ftemperature < -5 && FBwindPower >= 10 &&
                           -4 * Ftemperature + 3 * FBwindPower - 170 < 0 ) ||
                         ( Ftemperature < -35 && FBwindPower >= 10 ) ) ) {
                frostbite_timer[bp] += 72;
                if( one_in( 100 ) && intense < 2 ) {
                    add_msg( m_warning, _( "Your %s will be frostbitten any minute now!" ),
                             body_part_name( bp ).c_str() );
                }
                // Risk free, so reduce frostbite timer
            } else {
                frostbite_timer[bp] -= 3;
            }

            // Handle the bestowing of frostbite
            if( frostbite_timer[bp] < 0 ) {
                frostbite_timer[bp] = 0;
            } else if( frostbite_timer[bp] > 4200 ) {
                // This ensures that the player will recover in at most 3 hours.
                frostbite_timer[bp] = 4200;
            }
            // Frostbite, no recovery possible
            if( frostbite_timer[bp] >= 3600 ) {
                add_effect( effect_frostbite, 1_turns, bp, true, 2 );
                remove_effect( effect_frostbite_recovery, bp );
                // Else frostnip, add recovery if we were frostbitten
            } else if( frostbite_timer[bp] >= 1800 ) {
                if( intense == 2 ) {
                    add_effect( effect_frostbite_recovery, 1_turns, bp, true );
                }
                add_effect( effect_frostbite, 1_turns, bp, true, 1 );
                // Else fully recovered
            } else if( frostbite_timer[bp] == 0 ) {
                remove_effect( effect_frostbite, bp );
                remove_effect( effect_frostbite_recovery, bp );
            }
        }
        // Warn the player if condition worsens
        if( temp_before > BODYTEMP_FREEZING && temp_after < BODYTEMP_FREEZING ) {
            //~ %s is bodypart
            add_msg( m_warning, _( "You feel your %s beginning to go numb from the cold!" ),
                     body_part_name( bp ).c_str() );
        } else if( temp_before > BODYTEMP_VERY_COLD && temp_after < BODYTEMP_VERY_COLD ) {
            //~ %s is bodypart
            add_msg( m_warning, _( "You feel your %s getting very cold." ),
                     body_part_name( bp ).c_str() );
        } else if( temp_before > BODYTEMP_COLD && temp_after < BODYTEMP_COLD ) {
            //~ %s is bodypart
            add_msg( m_warning, _( "You feel your %s getting chilly." ),
                     body_part_name( bp ).c_str() );
        } else if( temp_before < BODYTEMP_SCORCHING && temp_after > BODYTEMP_SCORCHING ) {
            //~ %s is bodypart
            add_msg( m_bad, _( "You feel your %s getting red hot from the heat!" ),
                     body_part_name( bp ).c_str() );
        } else if( temp_before < BODYTEMP_VERY_HOT && temp_after > BODYTEMP_VERY_HOT ) {
            //~ %s is bodypart
            add_msg( m_warning, _( "You feel your %s getting very hot." ),
                     body_part_name( bp ).c_str() );
        } else if( temp_before < BODYTEMP_HOT && temp_after > BODYTEMP_HOT ) {
            //~ %s is bodypart
            add_msg( m_warning, _( "You feel your %s getting warm." ),
                     body_part_name( bp ).c_str() );
        }

        // Warn the player that wind is going to be a problem.
        // But only if it can be a problem, no need to spam player with "wind chills your scorching body"
        if( temp_conv[bp] <= BODYTEMP_COLD && windchill < -10 && one_in( 200 ) ) {
            add_msg( m_bad, _( "The wind is making your %s feel quite cold." ),
                     body_part_name( bp ).c_str() );
        } else if( temp_conv[bp] <= BODYTEMP_COLD && windchill < -20 && one_in( 100 ) ) {
            add_msg( m_bad,
                     _( "The wind is very strong, you should find some more wind-resistant clothing for your %s." ),
                     body_part_name( bp ).c_str() );
        } else if( temp_conv[bp] <= BODYTEMP_COLD && windchill < -30 && one_in( 50 ) ) {
            add_msg( m_bad, _( "Your clothing is not providing enough protection from the wind for your %s!" ),
                     body_part_name( bp ).c_str() );
        }
    }
}

bool player::can_use_floor_warmth() const
{
    // TODO: Reading? Waiting?
    return in_sleep_state();
}

int player::floor_bedding_warmth( const tripoint &pos )
{
    const trap &trap_at_pos = g->m.tr_at( pos );
    const ter_id ter_at_pos = g->m.ter( pos );
    const furn_id furn_at_pos = g->m.furn( pos );
    int floor_bedding_warmth = 0;

    const optional_vpart_position vp = g->m.veh_at( pos );
    const bool veh_bed = static_cast<bool>( vp.part_with_feature( "BED", true ) );
    const bool veh_seat = static_cast<bool>( vp.part_with_feature( "SEAT", true ) );

    // Search the floor for bedding
    if( furn_at_pos != f_null ) {
        floor_bedding_warmth += furn_at_pos.obj().floor_bedding_warmth;
    } else if( veh_bed && veh_seat ) {
        // BED+SEAT is intentionally worse than just BED
        floor_bedding_warmth += 250;
    } else if( veh_bed ) {
        floor_bedding_warmth += 300;
    } else if( veh_seat ) {
        floor_bedding_warmth += 200;
    } else if( trap_at_pos.loadid == tr_fur_rollmat ) {
        floor_bedding_warmth += 0;
    } else if( trap_at_pos.loadid == tr_cot || ter_at_pos == t_improvised_shelter ) {
        floor_bedding_warmth -= 500;
    } else if( trap_at_pos.loadid == tr_rollmat ) {
        floor_bedding_warmth -= 1000;
    } else {
        floor_bedding_warmth -= 2000;
    }

    return floor_bedding_warmth;
}

int player::floor_item_warmth( const tripoint &pos )
{
    if( !g->m.has_items( pos ) ) {
        return 0;
    }

    int item_warmth = 0;
    // Search the floor for items
    const auto floor_item = g->m.i_at( pos );
    for( const auto &elem : floor_item ) {
        if( !elem.is_armor() ) {
            continue;
        }
        // Items that are big enough and covers the torso are used to keep warm.
        // Smaller items don't do as good a job
        if( elem.volume() > 250_ml &&
            ( elem.covers( bp_torso ) || elem.covers( bp_leg_l ) ||
              elem.covers( bp_leg_r ) ) ) {
            item_warmth += 60 * elem.get_warmth() * elem.volume() / 2500_ml;
        }
    }

    return item_warmth;
}

int player::floor_warmth( const tripoint &pos ) const
{
    const int item_warmth = floor_item_warmth( pos );
    int bedding_warmth = floor_bedding_warmth( pos );

    // If the PC has fur, etc, that will apply too
    int floor_mut_warmth = bodytemp_modifier_traits_floor();
    // DOWN does not provide floor insulation, though.
    // Better-than-light fur or being in one's shell does.
    if( ( !( has_trait( trait_DOWN ) ) ) && ( floor_mut_warmth >= 200 ) ) {
        bedding_warmth = std::max( 0, bedding_warmth );
    }
    return ( item_warmth + bedding_warmth + floor_mut_warmth );
}

int player::bodytemp_modifier_traits( bool overheated ) const
{
    int mod = 0;
    for( auto &iter : my_mutations ) {
        mod += overheated ? iter.first->bodytemp_min :
               iter.first->bodytemp_max;
    }
    return mod;
}

int player::bodytemp_modifier_traits_floor() const
{
    int mod = 0;
    for( auto &iter : my_mutations ) {
        mod += iter.first->bodytemp_sleep;
    }
    return mod;
}

int player::temp_corrected_by_climate_control( int temperature ) const
{
    const int variation = int( BODYTEMP_NORM * 0.5 );
    if( temperature < BODYTEMP_SCORCHING + variation &&
        temperature > BODYTEMP_FREEZING - variation ) {
        if( temperature > BODYTEMP_SCORCHING ) {
            temperature = BODYTEMP_VERY_HOT;
        } else if( temperature > BODYTEMP_VERY_HOT ) {
            temperature = BODYTEMP_HOT;
        } else if( temperature > BODYTEMP_HOT ) {
            temperature = BODYTEMP_NORM;
        } else if( temperature < BODYTEMP_FREEZING ) {
            temperature = BODYTEMP_VERY_COLD;
        } else if( temperature < BODYTEMP_VERY_COLD ) {
            temperature = BODYTEMP_COLD;
        } else if( temperature < BODYTEMP_COLD ) {
            temperature = BODYTEMP_NORM;
        }
    }
    return temperature;
}

int player::blood_loss( body_part bp ) const
{
    int hp_cur_sum = 1;
    int hp_max_sum = 1;

    if( bp == bp_leg_l || bp == bp_leg_r ) {
        hp_cur_sum = hp_cur[hp_leg_l] + hp_cur[hp_leg_r];
        hp_max_sum = hp_max[hp_leg_l] + hp_max[hp_leg_r];
    } else if( bp == bp_arm_l || bp == bp_arm_r ) {
        hp_cur_sum = hp_cur[hp_arm_l] + hp_cur[hp_arm_r];
        hp_max_sum = hp_max[hp_arm_l] + hp_max[hp_arm_r];
    } else if( bp == bp_torso ) {
        hp_cur_sum = hp_cur[hp_torso];
        hp_max_sum = hp_max[hp_torso];
    } else if( bp == bp_head ) {
        hp_cur_sum = hp_cur[hp_head];
        hp_max_sum = hp_max[hp_head];
    }

    hp_cur_sum = std::min( hp_max_sum, std::max( 0, hp_cur_sum ) );
    return 100 - ( 100 * hp_cur_sum ) / hp_max_sum;
}

void player::temp_equalizer( body_part bp1, body_part bp2 )
{
    // Body heat is moved around.
    // Shift in one direction only, will be shifted in the other direction separately.
    int diff = int( ( temp_cur[bp2] - temp_cur[bp1] ) * 0.0001 ); // If bp1 is warmer, it will lose heat
    temp_cur[bp1] += diff;
}

int player::hunger_speed_penalty( int hunger )
{
    // We die at 6000 hunger
    // Hunger hits speed less hard than thirst does
    static const std::vector<std::pair<float, float>> hunger_thresholds = {{
            std::make_pair( 100.0f, 0.0f ),
            std::make_pair( 300.0f, -15.0f ),
            std::make_pair( 1000.0f, -40.0f ),
            std::make_pair( 6000.0f, -75.0f )
        }
    };
    return static_cast<int>( multi_lerp( hunger_thresholds, hunger ) );
}

int player::thirst_speed_penalty( int thirst )
{
    // We die at 1200 thirst
    // Start by dropping speed really fast, but then level it off a bit
    static const std::vector<std::pair<float, float>> thirst_thresholds = {{
            std::make_pair( 40.0f, 0.0f ),
            std::make_pair( 300.0f, -25.0f ),
            std::make_pair( 600.0f, -50.0f ),
            std::make_pair( 1200.0f, -75.0f )
        }
    };
    return static_cast<int>( multi_lerp( thirst_thresholds, thirst ) );
}

void player::recalc_speed_bonus()
{
    // Minus some for weight...
    int carry_penalty = 0;
    if( weight_carried() > weight_capacity() && !has_trait( trait_id( "DEBUG_STORAGE" ) ) ) {
        carry_penalty = 25 * ( weight_carried() - weight_capacity() ) / ( weight_capacity() );
    }
    mod_speed_bonus( -carry_penalty );

    mod_speed_bonus( -get_pain_penalty().speed );

    if( get_thirst() > 40 ) {
        mod_speed_bonus( thirst_speed_penalty( get_thirst() ) );
    }
    if( ( get_hunger() + get_starvation() ) > 100 ) {
        mod_speed_bonus( hunger_speed_penalty( get_hunger() + get_starvation() ) );
    }

    for( auto maps : *effects ) {
        for( auto i : maps.second ) {
            bool reduced = resists_effect( i.second );
            mod_speed_bonus( i.second.get_mod( "SPEED", reduced ) );
        }
    }

    // add martial arts speed bonus
    mod_speed_bonus( mabuff_speed_bonus() );

    // Not sure why Sunlight Dependent is here, but OK
    // Ectothermic/COLDBLOOD4 is intended to buff folks in the Summer
    // Threshold-crossing has its charms ;-)
    if( g != nullptr ) {
        if( has_trait( trait_SUNLIGHT_DEPENDENT ) && !g->is_in_sunlight( pos() ) ) {
            mod_speed_bonus( -( g->light_level( posz() ) >= 12 ? 5 : 10 ) );
        }
        /* Cache call to game::get_temperature( player position ) since it can be used several times here */
        const auto player_local_temp = g->get_temperature( pos() );
        if( has_trait( trait_COLDBLOOD4 ) || ( has_trait( trait_COLDBLOOD3 ) && player_local_temp < 65 ) ) {
            mod_speed_bonus( ( player_local_temp - 65 ) / 2 );
        } else if( has_trait( trait_COLDBLOOD2 ) && player_local_temp < 65 ) {
            mod_speed_bonus( ( player_local_temp - 65 ) / 3 );
        } else if( has_trait( trait_COLDBLOOD ) && player_local_temp < 65 ) {
            mod_speed_bonus( ( player_local_temp - 65 ) / 5 );
        }
    }

    if( has_trait( trait_M_SKIN2 ) || has_trait( trait_M_SKIN3 ) ) {
        mod_speed_bonus( -20 ); // Could be worse--you've got the armor from a (sessile!) Spire
    }

    if( has_artifact_with( AEP_SPEED_UP ) ) {
        mod_speed_bonus( 20 );
    }
    if( has_artifact_with( AEP_SPEED_DOWN ) ) {
        mod_speed_bonus( -20 );
    }

    if( has_trait( trait_QUICK ) ) { // multiply by 1.1
        set_speed_bonus( int( get_speed() * 1.1 ) - get_speed_base() );
    }
    if( has_bionic( bio_speed ) ) { // multiply by 1.1
        set_speed_bonus( int( get_speed() * 1.1 ) - get_speed_base() );
    }
    if( has_trait( trait_CRAFTY ) ) { // multiply by 1.15
        set_speed_bonus( int( get_speed() * 1.15 ) - get_speed_base() );
    }

    // Speed cannot be less than 25% of base speed, so minimal speed bonus is -75% base speed.
    const int min_speed_bonus = int( -0.75 * get_speed_base() );
    if( get_speed_bonus() < min_speed_bonus ) {
        set_speed_bonus( min_speed_bonus );
    }
}

int player::run_cost( int base_cost, bool diag ) const
{
    float movecost = float( base_cost );
    if( diag ) {
        movecost *= 0.7071f; // because everything here assumes 100 is base
    }

    const bool flatground = movecost < 105;
    // The "FLAT" tag includes soft surfaces, so not a good fit.
    const bool on_road = flatground && g->m.has_flag( "ROAD", pos() );
    const bool on_fungus = g->m.has_flag_ter_or_furn( "FUNGUS" , pos() );

    if( has_trait( trait_PARKOUR ) && movecost > 100 ) {
        movecost *= .5f;
        if( movecost < 100 ) {
            movecost = 100;
        }
    }
    if( has_trait( trait_BADKNEES ) && movecost > 100 ) {
        movecost *= 1.25f;
        if( movecost < 100 ) {
            movecost = 100;
        }
    }
    if( has_trait( trait_M_IMMUNE ) && on_fungus ) {
        if( movecost > 75 ) {
            movecost = 75; // Mycal characters are faster on their home territory, even through things like shrubs
        }
    }

    if( hp_cur[hp_leg_l] == 0 ) {
        movecost += 50;
    } else if( hp_cur[hp_leg_l] < hp_max[hp_leg_l] * .40 ) {
        movecost += 25;
    }

    if( hp_cur[hp_leg_r] == 0 ) {
        movecost += 50;
    } else if( hp_cur[hp_leg_r] < hp_max[hp_leg_r] * .40 ) {
        movecost += 25;
    }

    if( has_trait( trait_FLEET ) && flatground ) {
        movecost *= .85f;
    }
    if( has_trait( trait_FLEET2 ) && flatground ) {
        movecost *= .7f;
    }
    if( has_trait( trait_SLOWRUNNER ) && flatground ) {
        movecost *= 1.15f;
    }
    if( has_trait( trait_PADDED_FEET ) && !footwear_factor() ) {
        movecost *= .9f;
    }
    if( has_trait( trait_LIGHT_BONES ) ) {
        movecost *= .9f;
    }
    if( has_trait( trait_HOLLOW_BONES ) ) {
        movecost *= .8f;
    }
    if( has_active_mutation( trait_id( "WINGS_INSECT" ) ) ) {
        movecost *= .75f;
    }
    if( has_trait( trait_WINGS_BUTTERFLY ) ) {
        movecost -= 10; // You can't fly, but you can make life easier on your legs
    }
    if( has_trait( trait_LEG_TENTACLES ) ) {
        movecost += 20;
    }
    if( has_trait( trait_FAT ) ) {
        movecost *= 1.05f;
    }
    if( has_trait( trait_PONDEROUS1 ) ) {
        movecost *= 1.1f;
    }
    if( has_trait( trait_PONDEROUS2 ) ) {
        movecost *= 1.2f;
    }
    if( has_trait( trait_AMORPHOUS ) ) {
        movecost *= 1.25f;
    }
    if( has_trait( trait_PONDEROUS3 ) ) {
        movecost *= 1.3f;
    }
    if( has_trait( trait_CRAFTY ) ) {
        movecost *= 0.75f;
    }
    if( worn_with_flag( "SLOWS_MOVEMENT" ) ) {
        movecost *= 1.1f;
    }
    if( worn_with_flag( "FIN" ) ) {
        movecost *= 1.5f;
    }
    if( worn_with_flag( "ROLLER_INLINE" ) ) {
        if( on_road ) {
            movecost *= 0.5f;
        } else {
            movecost *= 1.5f;
        }
    }
    // Quad skates might be more stable than inlines,
    // but that also translates into a slower speed when on good surfaces.
    if( worn_with_flag( "ROLLER_QUAD" ) ) {
        if( on_road ) {
            movecost *= 0.7f;
        } else {
            movecost *= 1.3f;
        }
    }

    movecost +=
        ( ( encumb( bp_foot_l ) + encumb( bp_foot_r ) ) * 2.5 +
          ( encumb( bp_leg_l ) + encumb( bp_leg_r ) ) * 1.5 ) / 10;

    // ROOTS3 does slow you down as your roots are probing around for nutrients,
    // whether you want them to or not.  ROOTS1 is just too squiggly without shoes
    // to give you some stability.  Plants are a bit of a slow-mover.  Deal.
    const bool mutfeet = has_trait( trait_LEG_TENTACLES ) || has_trait( trait_PADDED_FEET ) ||
                         has_trait( trait_HOOVES ) || has_trait( trait_TOUGH_FEET ) || has_trait( trait_ROOTS2 );
    if( !is_wearing_shoes( side::LEFT ) && !mutfeet ) {
        movecost += 8;
    }
    if( !is_wearing_shoes( side::RIGHT ) && !mutfeet ) {
        movecost += 8;
    }

    if( has_trait( trait_ROOTS3 ) && g->m.has_flag( "DIGGABLE", pos() ) ) {
        movecost += 10 * footwear_factor();
    }

    // Both walk and run speed drop to half their maximums as stamina approaches 0.
    // Convert stamina to a float first to allow for decimal place carrying
    float stamina_modifier = ( float( stamina ) / get_stamina_max() + 1 ) / 2;
    if( move_mode == "run" && stamina > 0 ) {
        // Rationale: Average running speed is 2x walking speed. (NOT sprinting)
        stamina_modifier *= 2.0;
    }
    movecost /= stamina_modifier;

    if( diag ) {
        movecost *= SQRT_2;
    }

    return int( movecost );
}

int player::swim_speed() const
{
    int ret;
    const auto usable = exclusive_flag_coverage( "ALLOWS_NATURAL_ATTACKS" );
    float hand_bonus_mult = ( usable.test( bp_hand_l ) ? 0.5f : 0.0f ) +
                            ( usable.test( bp_hand_r ) ? 0.5f : 0.0f );

    if( !has_trait( trait_AMPHIBIAN ) ) {
        ret = 440 + weight_carried() / 60_gram - 50 * get_skill_level( skill_swimming );
     /** AMPHIBIAN increases base swim speed */
    } else {
        ret = 200 + weight_carried() / 120_gram - 50 * get_skill_level( skill_swimming );
    }
    /** @EFFECT_STR increases swim speed bonus from PAWS */
    if( has_trait( trait_PAWS ) ) {
        ret -= hand_bonus_mult * ( 20 + str_cur * 3 );
    }
    /** @EFFECT_STR increases swim speed bonus from PAWS_LARGE */
    if( has_trait( trait_PAWS_LARGE ) ) {
        ret -= hand_bonus_mult * ( 20 + str_cur * 4 );
    }
    /** @EFFECT_STR increases swim speed bonus from swim_fins */
    if( worn_with_flag( "FIN", bp_foot_l ) || worn_with_flag( "FIN", bp_foot_r) ) {
        if ( worn_with_flag ( "FIN", bp_foot_l) && worn_with_flag( "FIN", bp_foot_r) ){
            ret -= ( 15 * str_cur );
        } else {
            ret -= ( 15 * str_cur ) / 2;
        }
    }
    /** @EFFECT_STR increases swim speed bonus from WEBBED */
    if( has_trait( trait_WEBBED ) ) {
        ret -= hand_bonus_mult * ( 60 + str_cur * 5 );
    }
    /** @EFFECT_STR increases swim speed bonus from TAIL_FIN */
    if( has_trait( trait_TAIL_FIN ) ) {
        ret -= 100 + str_cur * 10;
    }
    if( has_trait( trait_SLEEK_SCALES ) ) {
        ret -= 100;
    }
    if( has_trait( trait_LEG_TENTACLES ) ) {
        ret -= 60;
    }
    if( has_trait( trait_FAT ) ) {
        ret -= 30;
    }
    /** @EFFECT_SWIMMING increases swim speed */
    ret += ( 50 - get_skill_level( skill_swimming ) * 2 ) * ( ( encumb( bp_leg_l ) + encumb(
                bp_leg_r ) ) / 10 );
    ret += ( 80 - get_skill_level( skill_swimming ) * 3 ) * ( encumb( bp_torso ) / 10 );
    if( get_skill_level( skill_swimming ) < 10 ) {
        for( auto &i : worn ) {
            ret += i.volume() / 125_ml * ( 10 - get_skill_level( skill_swimming ) );
        }
    }
    /** @EFFECT_STR increases swim speed */

    /** @EFFECT_DEX increases swim speed */
    ret -= str_cur * 6 + dex_cur * 4;
    if( worn_with_flag( "FLOTATION" ) ) {
        ret = std::min( ret, 400 );
        ret = std::max( ret, 200 );
    }
    // If (ret > 500), we can not swim; so do not apply the underwater bonus.
    if( underwater && ret < 500 ) {
        ret -= 50;
    }
    if( ret < 30 ) {
        ret = 30;
    }
    return ret;
}

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

bool player::is_on_ground() const
{
    return hp_cur[hp_leg_l] == 0 || hp_cur[hp_leg_r] == 0 || has_effect( effect_downed );
}

bool player::is_elec_immune() const
{
    return is_immune_damage( DT_ELECTRIC );
}

bool player::is_immune_effect( const efftype_id &eff ) const
{
    if( eff == effect_downed ) {
        return is_throw_immune() || ( has_trait( trait_LEG_TENT_BRACE ) && footwear_factor() == 0 );
    } else if( eff == effect_onfire ) {
        return is_immune_damage( DT_HEAT );
    } else if( eff == effect_deaf ) {
        return worn_with_flag( "DEAF" ) || worn_with_flag( "PARTIAL_DEAF" ) || has_bionic( bio_ears ) || is_wearing( "rm13_armor_on" );
    } else if( eff == effect_corroding ) {
        return is_immune_damage( DT_ACID ) || has_trait( trait_SLIMY ) || has_trait( trait_VISCOUS );
    } else if( eff == effect_nausea ) {
        return has_trait( trait_STRONGSTOMACH );
    }

    return false;
}

float player::stability_roll() const
{
    /** @EFFECT_STR improves player stability roll */

    /** @EFFECT_PER slightly improves player stability roll */

    /** @EFFECT_DEX slightly improves player stability roll */

    /** @EFFECT_MELEE improves player stability roll */
    return get_melee() + get_str() + ( get_per() / 3.0f ) + ( get_dex() / 4.0f );
}

bool player::is_immune_damage( const damage_type dt ) const
{
    switch( dt ) {
        case DT_NULL:
            return true;
        case DT_TRUE:
            return false;
        case DT_BIOLOGICAL:
            return false;
        case DT_BASH:
            return false;
        case DT_CUT:
            return false;
        case DT_ACID:
            return has_trait( trait_ACIDPROOF );
        case DT_STAB:
            return false;
        case DT_HEAT:
            return has_trait( trait_M_SKIN2 ) || has_trait( trait_M_SKIN3 );
        case DT_COLD:
            return false;
        case DT_ELECTRIC:
            return has_active_bionic( bio_faraday ) ||
                   worn_with_flag( "ELECTRIC_IMMUNE" ) ||
                   has_artifact_with( AEP_RESIST_ELECTRICITY );
        default:
            return true;
    }
}

double player::recoil_vehicle() const
{
    // @todo: vary penalty dependent upon vehicle part on which player is boarded

    if( in_vehicle ) {
        if( const optional_vpart_position vp = g->m.veh_at( pos() ) ) {
            return double( abs( vp->vehicle().velocity ) ) * 3 / 100;
        }
    }
    return 0;
}

double player::recoil_total() const
{
    return recoil + recoil_vehicle();
}

bool player::is_underwater() const
{
    return underwater;
}

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

void player::set_underwater( bool u )
{
    if( underwater != u ) {
        underwater = u;
        recalc_sight_limits();
    }
}

nc_color player::basic_symbol_color() const
{
    if( has_effect( effect_onfire ) ) {
        return c_red;
    }
    if( has_effect( effect_stunned ) ) {
        return c_light_blue;
    }
    if( has_effect( effect_boomered ) ) {
        return c_pink;
    }
    if( has_active_mutation( trait_id( "SHELL2" ) ) ) {
        return c_magenta;
    }
    if( underwater ) {
        return c_blue;
    }
    if( has_active_bionic( bio_cloak ) || has_artifact_with( AEP_INVISIBLE ) ||
        has_active_optcloak() || has_trait( trait_DEBUG_CLOAK ) ) {
        return c_dark_gray;
    }
    return c_white;
}

void player::load_info( std::string data )
{
    try {
        ::deserialize( *this, data );
    } catch( const std::exception &jsonerr ) {
        debugmsg( "Bad player json\n%s", jsonerr.what() );
    }
}

std::string player::save_info() const
{
    return ::serialize( *this ) + "\n" + dump_memorial();
}

void player::memorial( std::ostream &memorial_file, const std::string &epitaph )
{
    static const char *eol = cata_files::eol();

    //Size of indents in the memorial file
    const std::string indent = "  ";

    const std::string pronoun = male ? _( "He" ) : _( "She" );

    //Avoid saying "a male unemployed" or similar
    std::string profession_name;
    if( prof == prof->generic() ) {
        if( male ) {
            profession_name = _( "an unemployed male" );
        } else {
            profession_name = _( "an unemployed female" );
        }
    } else {
        profession_name = string_format( _( "a %s" ), prof->gender_appropriate_name( male ).c_str() );
    }

    const std::string locdesc = overmap_buffer.get_description_at( global_sm_location() );
    //~ First parameter is a pronoun ("He"/"She"), second parameter is a description
    // that designates the location relative to its surroundings.
    const std::string kill_place = string_format( _( "%1$s was killed in a %2$s." ),
                                    pronoun.c_str(), locdesc.c_str() );

    //Header
    memorial_file << string_format( _( "Cataclysm - Dark Days Ahead version %s memorial file" ),
                                    getVersionString() ) << eol;
    memorial_file << eol;
    memorial_file << string_format( _( "In memory of: %s" ), name.c_str() ) << eol;
    if( epitaph.length() > 0 ) { //Don't record empty epitaphs
        //~ The "%s" will be replaced by an epitaph as displayed in the memorial files. Replace the quotation marks as appropriate for your language.
        memorial_file << string_format( pgettext( "epitaph", "\"%s\"" ), epitaph.c_str() ) << eol << eol;
    }
    //~ First parameter: Pronoun, second parameter: a profession name (with article)
    memorial_file << string_format( _( "%1$s was %2$s when the apocalypse began." ),
                                    pronoun.c_str(), profession_name.c_str() ) << eol;
    memorial_file << string_format( _( "%1$s died on %2$s." ), pronoun, to_string( time_point( calendar::turn ) ) ) << eol;
    memorial_file << kill_place << eol;
    memorial_file << eol;

    //Misc
    memorial_file << string_format( _( "Cash on hand: %s" ), format_money( cash ) ) << eol;
    memorial_file << eol;

    //HP

    const auto limb_hp =
    [this, &memorial_file, &indent]( const std::string & desc, const hp_part bp ) {
        memorial_file << indent << string_format( desc, get_hp( bp ), get_hp_max( bp ) ) << eol;
    };

    memorial_file << _( "Final HP:" ) << eol;
    limb_hp( _( " Head: %d/%d" ), hp_head );
    limb_hp( _( "Torso: %d/%d" ), hp_torso );
    limb_hp( _( "L Arm: %d/%d" ), hp_arm_l );
    limb_hp( _( "R Arm: %d/%d" ), hp_arm_r );
    limb_hp( _( "L Leg: %d/%d" ), hp_leg_l );
    limb_hp( _( "R Leg: %d/%d" ), hp_leg_r );
    memorial_file << eol;

    //Stats
    memorial_file << _( "Final Stats:" ) << eol;
    memorial_file << indent << string_format( _( "Str %d" ), str_cur )
                  << indent << string_format( _( "Dex %d" ), dex_cur )
                  << indent << string_format( _( "Int %d" ), int_cur )
                  << indent << string_format( _( "Per %d" ), per_cur ) << eol;
    memorial_file << _( "Base Stats:" ) << eol;
    memorial_file << indent << string_format( _( "Str %d" ), str_max )
                  << indent << string_format( _( "Dex %d" ), dex_max )
                  << indent << string_format( _( "Int %d" ), int_max )
                  << indent << string_format( _( "Per %d" ), per_max ) << eol;
    memorial_file << eol;

    //Last 20 messages
    memorial_file << _( "Final Messages:" ) << eol;
    std::vector<std::pair<std::string, std::string> > recent_messages = Messages::recent_messages( 20 );
    for( auto &recent_message : recent_messages ) {
        memorial_file << indent << recent_message.first << " " << recent_message.second;
        memorial_file << eol;
    }
    memorial_file << eol;

    //Kill list
    memorial_file << _( "Kills:" ) << eol;

    int total_kills = 0;

    std::map<std::tuple<std::string, std::string>, int> kill_counts;

    // map <name, sym> to kill count
    for( const auto &type : MonsterGenerator::generator().get_all_mtypes() ) {
        if( g->kill_count( type.id ) > 0 ) {
            kill_counts[std::tuple<std::string, std::string>(
                            type.nname(),
                            type.sym
                        )] += g->kill_count( type.id );
            total_kills += g->kill_count( type.id );
        }
    }

    for( const auto& entry : kill_counts ) {
        memorial_file << "  " << std::get<1>( entry.first ) << " - "
                      << string_format( "%4d", entry.second ) << " "
                      << std::get<0>( entry.first ) << eol;
    }

    if( total_kills == 0 ) {
        memorial_file << indent << _( "No monsters were killed." ) << eol;
    } else {
        memorial_file << string_format( _( "Total kills: %d" ), total_kills ) << eol;
    }
    memorial_file << eol;

    //Skills
    memorial_file << _( "Skills:" ) << eol;
    for( auto &pair : *_skills ) {
        const SkillLevel &lobj = pair.second;
        //~ 1. skill name, 2. skill level, 3. exercise percentage to next level
        memorial_file << indent << string_format( _( "%s: %d (%d %%)" ), pair.first->name(), lobj.level(),
                      lobj.exercise() ) << eol;
    }
    memorial_file << eol;

    //Traits
    memorial_file << _( "Traits:" ) << eol;
    for( auto &iter : my_mutations ) {
        memorial_file << indent << mutation_branch::get_name( iter.first ) << eol;
    }
    if( !my_mutations.empty() ) {
        memorial_file << indent << _( "(None)" ) << eol;
    }
    memorial_file << eol;

    //Effects (illnesses)
    memorial_file << _( "Ongoing Effects:" ) << eol;
    bool had_effect = false;
    if( get_perceived_pain() > 0 ) {
        had_effect = true;
        memorial_file << indent << _( "Pain" ) << " (" << get_perceived_pain() << ")";
    }
    if( !had_effect ) {
        memorial_file << indent << _( "(None)" ) << eol;
    }
    memorial_file << eol;

    //Bionics
    memorial_file << _( "Bionics:" ) << eol;
    int total_bionics = 0;
    for( size_t i = 0; i < my_bionics->size(); ++i ) {
        memorial_file << indent << ( i + 1 ) << ": " << (*my_bionics)[i].id->name << eol;
        total_bionics++;
    }
    if( total_bionics == 0 ) {
        memorial_file << indent << _( "No bionics were installed." ) << eol;
    } else {
        memorial_file << string_format( _( "Total bionics: %d" ), total_bionics ) << eol;
    }
    memorial_file << string_format( _( "Bionic Power: <color_light_blue>%d</color>/<color_light_blue>%d</color>" ), power_level,  max_power_level ) << eol;
    memorial_file << eol;

    //Equipment
    memorial_file << _( "Weapon:" ) << eol;
    memorial_file << indent << weapon.invlet << " - " << weapon.tname( 1, false ) << eol;
    memorial_file << eol;

    memorial_file << _( "Equipment:" ) << eol;
    for( auto &elem : worn ) {
        item next_item = elem;
        memorial_file << indent << next_item.invlet << " - " << next_item.tname( 1, false );
        if( next_item.charges > 0 ) {
            memorial_file << " (" << next_item.charges << ")";
        } else if( next_item.contents.size() == 1 && next_item.contents.front().charges > 0 ) {
            memorial_file << " (" << next_item.contents.front().charges << ")";
        }
        memorial_file << eol;
    }
    memorial_file << eol;

    //Inventory
    memorial_file << _( "Inventory:" ) << eol;
    inv.restack( *this );
    invslice slice = inv.slice();
    for( auto &elem : slice ) {
        item &next_item = elem->front();
        memorial_file << indent << next_item.invlet << " - " <<
                      next_item.tname( unsigned( elem->size() ), false );
        if( elem->size() > 1 ) {
            memorial_file << " [" << elem->size() << "]";
        }
        if( next_item.charges > 0 ) {
            memorial_file << " (" << next_item.charges << ")";
        } else if( next_item.contents.size() == 1 && next_item.contents.front().charges > 0 ) {
            memorial_file << " (" << next_item.contents.front().charges << ")";
        }
        memorial_file << eol;
    }
    memorial_file << eol;

    //Lifetime stats
    memorial_file << _( "Lifetime Stats" ) << eol;
    memorial_file << indent << string_format( _( "Distance walked: %d squares" ),
                  lifetime_stats.squares_walked ) << eol;
    memorial_file << indent << string_format( _( "Damage taken: %d damage" ),
                  lifetime_stats.damage_taken ) << eol;
    memorial_file << indent << string_format( _( "Damage healed: %d damage" ),
                  lifetime_stats.damage_healed ) << eol;
    memorial_file << indent << string_format( _( "Headshots: %d" ),
                  lifetime_stats.headshots ) << eol;
    memorial_file << eol;

    //History
    memorial_file << _( "Game History" ) << eol;
    memorial_file << dump_memorial();

}

/**
 * Adds an event to the memorial log, to be written to the memorial file when
 * the character dies. The message should contain only the informational string,
 * as the timestamp and location will be automatically prepended.
 */
void player::add_memorial_log( const std::string &male_msg, const std::string &female_msg )
{
    const std::string &msg = male ? male_msg : female_msg;

    if( msg.empty() ) {
        return;
    }

    const oter_id &cur_ter = overmap_buffer.ter( global_omt_location() );
    const std::string &location = cur_ter->get_name();

    std::stringstream log_message;
    log_message << "| " << to_string( time_point( calendar::turn ) ) << " | " << location << " | " << msg;

    memorial_log.push_back( log_message.str() );

}

/**
 * Loads the data in a memorial file from the given ifstream. All the memorial
 * entry lines begin with a pipe (|).
 * @param fin The ifstream to read the memorial entries from.
 */
void player::load_memorial_file( std::istream &fin )
{
    std::string entry;
    memorial_log.clear();
    while( fin.peek() == '|' ) {
        getline( fin, entry );
        memorial_log.push_back( entry );
    }
}

/**
 * Concatenates all of the memorial log entries, delimiting them with newlines,
 * and returns the resulting string. Used for saving and for writing out to the
 * memorial file.
 */
std::string player::dump_memorial() const
{
    static const char *eol = cata_files::eol();
    std::stringstream output;

    for( auto &elem : memorial_log ) {
        output << elem << eol;
    }

    return output.str();
}

void player::mod_stat( const std::string &stat, float modifier )
{
    if( stat == "thirst" ) {
        mod_thirst( modifier );
    } else if( stat == "fatigue" ) {
        mod_fatigue( modifier );
    } else if( stat == "oxygen" ) {
        oxygen += modifier;
    } else if( stat == "stamina" ) {
        stamina += modifier;
        stamina = std::min( stamina, get_stamina_max() );
        stamina = std::max( 0, stamina );
    } else {
        // Fall through to the creature method.
        Character::mod_stat( stat, modifier );
    }
}

void player::disp_morale()
{
    morale->display( ( calc_focus_equilibrium() - focus_pool ) / 100.0 );
}

bool player::has_conflicting_trait( const trait_id &flag ) const
{
    return ( has_opposite_trait( flag ) || has_lower_trait( flag ) || has_higher_trait( flag ) || has_same_type_trait ( flag ) );
}

bool player::has_opposite_trait( const trait_id &flag ) const
{
    for( auto &i : flag->cancels ) {
        if( has_trait( i ) ) {
            return true;
        }
    }
    return false;
}

bool player::has_lower_trait( const trait_id &flag ) const
{
    for( auto &i : flag->prereqs ) {
        if( has_trait( i ) || has_lower_trait( i ) ) {
            return true;
        }
    }
    return false;
}

bool player::has_higher_trait( const trait_id &flag ) const
{
    for( auto &i : flag->replacements ) {
        if( has_trait( i ) || has_higher_trait( i ) ) {
            return true;
        }
    }
    return false;
}

bool player::has_same_type_trait( const trait_id &flag ) const
{
    for( auto &i : get_mutations_in_types( flag->types ) ) {
        if( has_trait( i ) && flag != i ) {
            return true;
        }
    }
    return false;
}

bool player::crossed_threshold() const
{
    for( auto &mut : my_mutations ) {
        if( mut.first->threshold ) {
            return true;
        }
    }
    return false;
}

bool player::purifiable( const trait_id &flag ) const
{
    return flag->purifiable;
}

void player::build_mut_dependency_map( const trait_id &mut, std::unordered_map<trait_id, int> &dependency_map, int distance )
{
    // Skip base traits and traits we've seen with a lower distance
    const auto lowest_distance = dependency_map.find( mut );
    if( !has_base_trait( mut ) && (lowest_distance == dependency_map.end() || distance < lowest_distance->second) ) {
        dependency_map[mut] = distance;
        // Recurse over all prerequisite and replacement mutations
        const auto &mdata = mut.obj();
        for( auto &i : mdata.prereqs ) {
            build_mut_dependency_map( i, dependency_map, distance + 1 );
        }
        for( auto &i : mdata.prereqs2 ) {
            build_mut_dependency_map( i, dependency_map, distance + 1 );
        }
        for( auto &i : mdata.replacements ) {
            build_mut_dependency_map( i, dependency_map, distance + 1 );
        }
    }
}

void player::set_highest_cat_level()
{
    mutation_category_level.clear();

    // For each of our mutations...
    for( auto &mut : my_mutations ) {
        // ...build up a map of all prerequisite/replacement mutations along the tree, along with their distance from the current mutation
        std::unordered_map<trait_id, int> dependency_map;
        build_mut_dependency_map( mut.first, dependency_map, 0);

        // Then use the map to set the category levels
        for ( auto &i : dependency_map ) {
            const auto &mdata = i.first.obj();
            for( auto &cat : mdata.category ) {
                // Decay category strength based on how far it is from the current mutation
                mutation_category_level[cat] += 8 / static_cast<int>( std::pow( 2, i.second ) );
            }
        }
    }
}

/// Returns the mutation category with the highest strength
std::string player::get_highest_category() const
{
    int iLevel = 0;
    std::string sMaxCat;

    for( const auto &elem : mutation_category_level ) {
        if( elem.second > iLevel ) {
            sMaxCat = elem.first;
            iLevel = elem.second;
        } else if( elem.second == iLevel ) {
            sMaxCat.clear();  // no category on ties
        }
    }
    return sMaxCat;
}

/// Returns a randomly selected dream
std::string player::get_category_dream( const std::string &cat,
                                        int strength ) const
{
    std::vector<dream> valid_dreams;
    //Pull the list of dreams
    for( auto &i : dreams ) {
        //Pick only the ones matching our desired category and strength
        if( ( i.category == cat ) && ( i.strength == strength ) ) {
            // Put the valid ones into our list
            valid_dreams.push_back( i );
        }
    }
    if( valid_dreams.empty() ) {
        return "";
    }
    const dream &selected_dream = random_entry( valid_dreams );
    return random_entry( selected_dream.messages() );
}

bool player::in_climate_control()
{
    bool regulated_area = false;
    // Check
    if( has_active_bionic( bio_climate ) ) {
        return true;
    }
    if( has_trait( trait_M_SKIN3 ) && g->m.has_flag_ter_or_furn( "FUNGUS" , pos() ) && in_sleep_state() ) {
        return true;
    }
    for( auto &w : worn ) {
        if( w.active && w.is_power_armor() ) {
            return true;
        }
        if( worn_with_flag( "CLIMATE_CONTROL" ) ) {
            return true;
        }
    }
    if( calendar::turn >= next_climate_control_check ) {
        // save CPU and simulate acclimation.
        next_climate_control_check = calendar::turn + 20_turns;
        if( const optional_vpart_position vp = g->m.veh_at( pos() ) ) {
            regulated_area = (
                                 vp->is_inside() &&  // Already checks for opened doors
                                 vp->vehicle().total_power_w( true ) > 0 // Out of gas? No AC for you!
                             );  // TODO: (?) Force player to scrounge together an AC unit
        }
        // TODO: AC check for when building power is implemented
        last_climate_control_ret = regulated_area;
        if( !regulated_area ) {
            // Takes longer to cool down / warm up with AC, than it does to step outside and feel cruddy.
            next_climate_control_check += 40_turns;
        }
    } else {
        return last_climate_control_ret;
    }
    return regulated_area;
}

std::list<item *> player::get_radio_items()
{
    std::list<item *> rc_items;
    const invslice &stacks = inv.slice();
    for( auto &stack : stacks ) {
        item &stack_iter = stack->front();
        if( stack_iter.has_flag( "RADIO_ACTIVATION" ) ) {
            rc_items.push_back( &stack_iter );
        }
    }

    for( auto &elem : worn ) {
        if( elem.has_flag( "RADIO_ACTIVATION" ) ) {
            rc_items.push_back( &elem );
        }
    }

    if( is_armed() ) {
        if( weapon.has_flag( "RADIO_ACTIVATION" ) ) {
            rc_items.push_back( &weapon );
        }
    }
    return rc_items;
}

std::list<item *> player::get_artifact_items()
{
    std::list<item *> art_items;
    const invslice &stacks = inv.slice();
    for( auto &stack : stacks ) {
        item &stack_iter = stack->front();
        if( stack_iter.is_artifact() ) {
            art_items.push_back( &stack_iter );
        }
    }
    for( auto &elem : worn ) {
        if( elem.is_artifact() ) {
            art_items.push_back( &elem );
        }
    }
    if( is_armed() ) {
        if( weapon.is_artifact() ) {
            art_items.push_back( &weapon );
        }
    }
    return art_items;
}

bool player::has_active_optcloak() const
{
    for( auto &w : worn ) {
        if( w.active && w.typeId() == OPTICAL_CLOAK_ITEM_ID ) {
            return true;
        }
    }
    return false;
}

void player::charge_power( int amount )
{
    power_level = clamp( power_level + amount, 0, max_power_level );
}

/*
 * Calculate player brightness based on the brightest active item, as
 * per itype tag LIGHT_* and optional CHARGEDIM ( fade starting at 20% charge )
 * item.light.* is -unimplemented- for the moment, as it is a custom override for
 * applying light sources/arcs with specific angle and direction.
 */
float player::active_light() const
{
    float lumination = 0;

    int maxlum = 0;
    has_item_with( [&maxlum]( const item & it ) {
        const int lumit = it.getlight_emit();
        if( maxlum < lumit ) {
            maxlum = lumit;
        }
        return false; // continue search, otherwise has_item_with would cancel the search
    } );

    lumination = static_cast<float>( maxlum );

    if( lumination < 60 && has_active_bionic( bio_flashlight ) ) {
        lumination = 60;
    } else if( lumination < 25 && has_artifact_with( AEP_GLOW ) ) {
        lumination = 25;
    } else if( lumination < 5 && has_effect( effect_glowing ) ) {
        lumination = 5;
    }
    return lumination;
}

tripoint player::global_square_location() const
{
    return g->m.getabs( position );
}

tripoint player::global_sm_location() const
{
    return ms_to_sm_copy( global_square_location() );
}

tripoint player::global_omt_location() const
{
    return ms_to_omt_copy( global_square_location() );
}

const tripoint &player::pos() const
{
    return position;
}

int player::sight_range( int light_level ) const
{
    /* Via Beer-Lambert we have:
     * light_level * (1 / exp( LIGHT_TRANSPARENCY_OPEN_AIR * distance) ) <= LIGHT_AMBIENT_LOW
     * Solving for distance:
     * 1 / exp( LIGHT_TRANSPARENCY_OPEN_AIR * distance ) <= LIGHT_AMBIENT_LOW / light_level
     * 1 <= exp( LIGHT_TRANSPARENCY_OPEN_AIR * distance ) * LIGHT_AMBIENT_LOW / light_level
     * light_level <= exp( LIGHT_TRANSPARENCY_OPEN_AIR * distance ) * LIGHT_AMBIENT_LOW
     * log(light_level) <= LIGHT_TRANSPARENCY_OPEN_AIR * distance + log(LIGHT_AMBIENT_LOW)
     * log(light_level) - log(LIGHT_AMBIENT_LOW) <= LIGHT_TRANSPARENCY_OPEN_AIR * distance
     * log(LIGHT_AMBIENT_LOW / light_level) <= LIGHT_TRANSPARENCY_OPEN_AIR * distance
     * log(LIGHT_AMBIENT_LOW / light_level) * (1 / LIGHT_TRANSPARENCY_OPEN_AIR) <= distance
     */
    int range = int( -log( get_vision_threshold( int( g->m.ambient_light_at( pos() ) ) ) /
                           static_cast<float>( light_level ) ) *
                     ( 1.0 / LIGHT_TRANSPARENCY_OPEN_AIR ) );
    // int range = log(light_level * LIGHT_AMBIENT_LOW) / LIGHT_TRANSPARENCY_OPEN_AIR;

    // Clamp to [1, sight_max].
    return std::max( 1, std::min( range, sight_max ) );
}

int player::unimpaired_range() const
{
    return std::min( sight_max, 60 );
}

bool player::overmap_los( const tripoint &omt, int sight_points )
{
    const tripoint ompos = global_omt_location();
    if( omt.x < ompos.x - sight_points || omt.x > ompos.x + sight_points ||
        omt.y < ompos.y - sight_points || omt.y > ompos.y + sight_points ) {
        // Outside maximum sight range
        return false;
    }

    const std::vector<tripoint> line = line_to( ompos, omt, 0, 0 );
    for( size_t i = 0; i < line.size() && sight_points >= 0; i++ ) {
        const tripoint &pt = line[i];
        const oter_id &ter = overmap_buffer.ter( pt );
        sight_points -= int( ter->get_see_cost() );
        if( sight_points < 0 ) {
            return false;
        }
    }
    return true;
}

int player::overmap_sight_range( int light_level ) const
{
    int sight = sight_range( light_level );
    if( sight < SEEX ) {
        return 0;
    }
    if( sight <= SEEX * 4 ) {
        return ( sight / ( SEEX / 2 ) );
    }
    sight = has_trait( trait_BIRD_EYE ) ? 15 : 10;

    /** @EFFECT_PER determines overmap sight range */
    sight += ( -4 + static_cast<int>( get_per() / 2 ) );
    bool has_optic = ( has_item_with_flag( "ZOOM" ) || has_bionic( bio_eye_optic ) );

    if( has_trait( trait_EAGLEEYED ) && has_optic ) { //optic AND scout = +15
        sight += 15;
    } else if( has_trait( trait_EAGLEEYED ) != has_optic ) { //optic OR scout = +10
        sight += 10;
    }

    if( has_trait( trait_UNOBSERVANT ) && sight > 3 ) {
        sight = 3; //surprise! you can't see!
    }

    return sight;
}

#define MAX_CLAIRVOYANCE 40
int player::clairvoyance() const
{
    if( vision_mode_cache[VISION_CLAIRVOYANCE_SUPER] ) {
        return MAX_CLAIRVOYANCE;
    }

    if( vision_mode_cache[VISION_CLAIRVOYANCE_PLUS] ) {
        return 8;
    }

    if( vision_mode_cache[VISION_CLAIRVOYANCE] ) {
        return 3;
    }

    return 0;
}

bool player::sight_impaired() const
{
    return ( ( ( has_effect( effect_boomered ) || has_effect( effect_darkness ) ) &&
               ( !( has_trait( trait_PER_SLIME_OK ) ) ) ) ||
             ( underwater && !has_bionic( bio_membrane ) && !has_trait( trait_MEMBRANE ) &&
               !worn_with_flag( "SWIM_GOGGLES" ) && !has_trait( trait_PER_SLIME_OK ) &&
               !has_trait( trait_CEPH_EYES ) && !has_trait( trait_SEESLEEP ) ) ||
             ( ( has_trait( trait_MYOPIC ) || has_trait( trait_URSINE_EYE ) ) &&
               !worn_with_flag( "FIX_NEARSIGHT" ) &&
               !has_effect( effect_contacts ) &&
               !has_bionic( bio_eye_optic ) ) ||
                has_trait( trait_PER_SLIME ) );
}

bool player::has_two_arms() const
{
    // If you've got a blaster arm, low hp arm, or you're inside a shell then you don't have two
    // arms to use.
    return !( ( has_bionic( bio_blaster ) || hp_cur[hp_arm_l] < 10 || hp_cur[hp_arm_r] < 10 ) ||
              has_active_mutation( trait_id( "SHELL2" ) ) );
}

bool player::avoid_trap( const tripoint &pos, const trap &tr ) const
{
    /** @EFFECT_DEX increases chance to avoid traps */

    /** @EFFECT_DODGE increases chance to avoid traps */
    int myroll = dice( 3, dex_cur + get_skill_level( skill_dodge ) * 1.5 );
    int traproll;
    if( tr.can_see( pos, *this ) ) {
        traproll = dice( 3, tr.get_avoidance() );
    } else {
        traproll = dice( 6, tr.get_avoidance() );
    }

    if( has_trait( trait_LIGHTSTEP ) ) {
        myroll += dice( 2, 6 );
    }

    if( has_trait( trait_CLUMSY ) ) {
        myroll -= dice( 2, 6 );
    }

    return myroll >= traproll;
}

bool player::has_alarm_clock() const
{
    return ( has_item_with_flag( "ALARMCLOCK" ) ||
             ( g->m.veh_at( pos() ) &&
               !empty( g->m.veh_at( pos() )->vehicle().get_avail_parts( "ALARMCLOCK" ) ) ) ||
             has_bionic( bio_watch ) );
}

bool player::has_watch() const
{
    return ( has_item_with_flag( "WATCH" ) ||
             ( g->m.veh_at( pos() ) &&
               !empty( g->m.veh_at( pos() )->vehicle().get_avail_parts( "WATCH" ) ) ) ||
             has_bionic( bio_watch ) );
}

void player::pause()
{
    moves = 0;
    recoil = MAX_RECOIL;

    // Train swimming if underwater
    if( !in_vehicle ) {
        if( underwater ) {
            practice( skill_swimming, 1 );
            drench( 100, { { bp_leg_l, bp_leg_r, bp_torso, bp_arm_l,
                         bp_arm_r, bp_head, bp_eyes, bp_mouth,
                         bp_foot_l, bp_foot_r, bp_hand_l, bp_hand_r } }, true );
        } else if( g->m.has_flag( TFLAG_DEEP_WATER, pos() ) ) {
            practice( skill_swimming, 1 );
            // Same as above, except no head/eyes/mouth
            drench( 100, { { bp_leg_l, bp_leg_r, bp_torso, bp_arm_l,
                         bp_arm_r, bp_foot_l, bp_foot_r, bp_hand_l,
                         bp_hand_r } }, true );
        } else if( g->m.has_flag( "SWIMMABLE", pos() ) ) {
            drench( 40, { { bp_foot_l, bp_foot_r, bp_leg_l, bp_leg_r } }, false );
        }
    }

    // Try to put out clothing/hair fire
    if( has_effect( effect_onfire ) ) {
        time_duration total_removed = 0_turns;
        time_duration total_left = 0_turns;
        bool on_ground = has_effect( effect_downed );
        for( const body_part bp : all_body_parts ) {
            effect &eff = get_effect( effect_onfire, bp );
            if( eff.is_null() ) {
                continue;
            }

            // @todo: Tools and skills
            total_left += eff.get_duration();
            // Being on the ground will smother the fire much faster because you can roll
            const time_duration dur_removed = on_ground ? eff.get_duration() / 2 + 2_turns : 1_turns;
            eff.mod_duration( -dur_removed );
            total_removed += dur_removed;
        }

        // Don't drop on the ground when the ground is on fire
        if( total_left > 1_minutes && !is_dangerous_fields( g->m.field_at( pos() ) ) ) {
            add_effect( effect_downed, 2_turns, num_bp, false, 0, true );
            add_msg_player_or_npc( m_warning,
                                   _( "You roll on the ground, trying to smother the fire!" ),
                                   _( "<npcname> rolls on the ground!" ) );
        } else if( total_removed > 0_turns ) {
            add_msg_player_or_npc( m_warning,
                                   _( "You attempt to put out the fire on you!" ),
                                   _( "<npcname> attempts to put out the fire on them!" ) );
        }
    }

    if( is_npc() ) {
        // The stuff below doesn't apply to NPCs
        // search_surroundings should eventually do, though
        return;
    }

    VehicleList vehs = g->m.get_vehicles();
    vehicle *veh = nullptr;
    for( auto &v : vehs ) {
        veh = v.v;
        if( veh && veh->is_moving() && veh->player_in_control( *this ) ) {
            if( one_in( 8 ) ) {
                double exp_temp = 1 + veh->total_mass() / 400.0_kilogram + std::abs( veh->velocity / 3200.0 );
                int experience = int( exp_temp );
                if( exp_temp - experience > 0 && x_in_y( exp_temp - experience, 1.0 ) ) {
                    experience++;
                }
                practice( skill_id( "driving" ), experience );
            }
            break;
        }
    }

    search_surroundings();
}

void player::shout( std::string msg )
{
    int base = 10;
    int shout_multiplier = 2;

    // Mutations make shouting louder, they also define the default message
    if ( has_trait( trait_SHOUT2 ) ) {
        base = 15;
        shout_multiplier = 3;
        if ( msg.empty() ) {
            msg = is_player() ? _( "yourself scream loudly!" ) : _( "a loud scream!" );
        }
    }

    if ( has_trait( trait_SHOUT3 ) ) {
        shout_multiplier = 4;
        base = 20;
        if ( msg.empty() ) {
            msg = is_player() ? _( "yourself let out a piercing howl!" ) : _( "a piercing howl!" );
        }
    }

    if ( msg.empty() ) {
        msg = is_player() ? _( "yourself shout loudly!" ) : _( "a loud shout!" );
    }
    // Masks and such dampen the sound
    // Balanced around  whisper for wearing bondage mask
    // and noise ~= 10(door smashing) for wearing dust mask for character with strength = 8
    /** @EFFECT_STR increases shouting volume */
    const int penalty = encumb( bp_mouth ) * 3 / 2;
    int noise = base + str_cur * shout_multiplier - penalty;

    // Minimum noise volume possible after all reductions.
    // Volume 1 can't be heard even by player
    constexpr int minimum_noise = 2;

    if ( noise <= base ) {
        std::string dampened_shout;
        std::transform( msg.begin(), msg.end(), std::back_inserter( dampened_shout ), tolower );
        noise = std::max( minimum_noise, noise );
        msg = std::move( dampened_shout );
    }

    // Screaming underwater is not good for oxygen and harder to do overall
    if ( underwater ) {
        if ( !has_trait( trait_GILLS ) && !has_trait( trait_GILLS_CEPH ) ) {
            mod_stat( "oxygen", -noise );
        }

        noise = std::max( minimum_noise, noise / 2 );
    }

    // TODO: indistinct noise descriptions should be handled in the sounds code
    if( noise <= minimum_noise ) {
        add_msg_if_player( m_warning,
                           _( "The sound of your voice is almost completely muffled!" ) );
        msg = is_player() ? _( "your muffled shout" ) : _( "an indistinct voice" );
    } else if( noise * 2 <= noise + penalty ) {
        // The shout's volume is 1/2 or lower of what it would be without the penalty
        add_msg_if_player( m_warning, _( "The sound of your voice is significantly muffled!" ) );
    }

    sounds::sound( pos(), noise, sounds::sound_t::speech, msg );
}

void player::toggle_move_mode()
{
    if( move_mode == "walk" ) {
        if( stamina > 0 && !has_effect( effect_winded ) ) {
            move_mode = "run";
            if( is_hauling() ) {
                stop_hauling();
            }
            add_msg(_("You start running."));
        } else {
            add_msg(m_bad, _("You're too tired to run."));
        }
    } else if( move_mode == "run" ) {
        move_mode = "walk";
        add_msg(_("You slow to a walk."));
    }
}

void player::search_surroundings()
{
    if (controlling_vehicle) {
        return;
    }
    // Search for traps in a larger area than before because this is the only
    // way we can "find" traps that aren't marked as visible.
    // Detection formula takes care of likelihood of seeing within this range.
    for( const tripoint &tp : g->m.points_in_radius( pos(), 5 ) ) {
        const trap &tr = g->m.tr_at( tp );
        if( tr.is_null() || tp == pos() ) {
            continue;
        }
        if( !sees( tp ) ) {
            continue;
        }
        if( tr.name().empty() || tr.can_see( tp, *this ) ) {
            // Already seen, or has no name -> can never be seen
            continue;
        }
        // Chance to detect traps we haven't yet seen.
        if (tr.detect_trap( tp, *this )) {
            if( tr.get_visibility() > 0 ) {
                // Only bug player about traps that aren't trivial to spot.
                const std::string direction = direction_name(
                    direction_from( pos(), tp ) );
                add_msg_if_player(_("You've spotted a %1$s to the %2$s!"),
                                  tr.name().c_str(), direction.c_str());
            }
            add_known_trap( tp, tr);
        }
    }
}

int player::read_speed(bool return_stat_effect) const
{
    // Stat window shows stat effects on based on current stat
    const int intel = get_int();
    /** @EFFECT_INT increases reading speed */
    int ret = 1000 - 50 * (intel - 8);
    if( has_trait( trait_FASTREADER ) ) {
        ret *= .8;
    }
    if( has_trait( trait_PROF_DICEMASTER ) ) {
        ret *= .9;
    }

    if( has_trait( trait_SLOWREADER ) ) {
        ret *= 1.3;
    }

    if( ret < 100 ) {
        ret = 100;
    }
    // return_stat_effect actually matters here
    return (return_stat_effect ? ret : ret / 10);
}

int player::rust_rate(bool return_stat_effect) const
{
    if (get_option<std::string>( "SKILL_RUST" ) == "off") {
        return 0;
    }

    // Stat window shows stat effects on based on current stat
    int intel = get_int();
    /** @EFFECT_INT reduces skill rust */
    int ret = ((get_option<std::string>( "SKILL_RUST" ) == "vanilla" || get_option<std::string>( "SKILL_RUST" ) == "capped") ? 500 : 500 - 35 * (intel - 8));

    if (has_trait( trait_FORGETFUL )) {
        ret *= 1.33;
    }

    if (has_trait( trait_GOODMEMORY )) {
        ret *= .66;
    }

    if (ret < 0) {
        ret = 0;
    }

    // return_stat_effect actually matters here
    return (return_stat_effect ? ret : ret / 10);
}

int player::talk_skill() const
{
    /** @EFFECT_INT slightly increases talking skill */

    /** @EFFECT_PER slightly increases talking skill */

    /** @EFFECT_SPEECH increases talking skill */
    int ret = get_int() + get_per() + get_skill_level( skill_id( "speech" ) ) * 3;
    return ret;
}

int player::intimidation() const
{
    /** @EFFECT_STR increases intimidation factor */
    int ret = get_str() * 2;
    if (weapon.is_gun()) {
        ret += 10;
    }
    if( weapon.damage_melee( DT_BASH ) >= 12 ||
        weapon.damage_melee( DT_CUT  ) >= 12 ||
        weapon.damage_melee( DT_STAB ) >= 12 ) {
        ret += 5;
    }

    if (stim > 20) {
        ret += 2;
    }
    if( has_effect( effect_drunk ) ) {
        ret -= 4;
    }

    return ret;
}

bool player::is_dead_state() const
{
    return hp_cur[hp_head] <= 0 || hp_cur[hp_torso] <= 0;
}

void player::on_dodge( Creature *source, float difficulty )
{
    static const matec_id tec_none( "tec_none" );

    // Each avoided hit consumes an available dodge
    // When no more available we are likely to fail player::dodge_roll
    dodges_left--;

    // dodging throws of our aim unless we are either skilled at dodging or using a small weapon
    if( is_armed() && weapon.is_gun() ) {
        recoil += std::max( weapon.volume() / 250_ml - get_skill_level( skill_dodge ), 0 ) * rng( 0, 100 );
        recoil = std::min( MAX_RECOIL, recoil );
    }

    // Even if we are not to train still call practice to prevent skill rust
    difficulty = std::max( difficulty, 0.0f );
    practice( skill_dodge, difficulty * 2, difficulty );

    ma_ondodge_effects();

    // For adjacent attackers check for techniques usable upon successful dodge
    if( source && square_dist( pos(), source->pos() ) == 1 ) {
        matec_id tec = pick_technique( *source, used_weapon(), false, true, false );
        if( tec != tec_none ) {
            melee_attack( *source, false, tec );
        }
    }
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( source );
    lua_callback_args_info.emplace_back( difficulty );
    lua_callback( "on_player_dodge", lua_callback_args_info );
}

void player::on_hit( Creature *source, body_part bp_hit,
                     float /*difficulty*/ , dealt_projectile_attack const* const proj ) {
    check_dead_state();
    if( source == nullptr || proj != nullptr ) {
        return;
    }

    bool u_see = g->u.sees( *this );
    if( has_active_bionic( bionic_id( "bio_ods" ) ) && power_level > 5 ) {
        if( is_player() ) {
            add_msg( m_good, _( "Your offensive defense system shocks %s in mid-attack!" ),
                             source->disp_name().c_str());
        } else if( u_see ) {
            add_msg( _( "%1$s's offensive defense system shocks %2$s in mid-attack!" ),
                        disp_name().c_str(),
                        source->disp_name().c_str() );
        }
        int shock = rng( 1, 4 );
        charge_power( -shock );
        damage_instance ods_shock_damage;
        ods_shock_damage.add_damage( DT_ELECTRIC, shock * 5 );
        // Should hit body part used for attack
        source->deal_damage( this, bp_torso, ods_shock_damage );
    }
    if( !wearing_something_on( bp_hit ) &&
        ( has_trait( trait_SPINES ) || has_trait( trait_QUILLS ) ) ) {
        int spine = rng( 1, has_trait( trait_QUILLS ) ? 20 : 8 );
        if( !is_player() ) {
            if( u_see ) {
                add_msg(_("%1$s's %2$s puncture %3$s in mid-attack!"), name.c_str(),
                            (has_trait( trait_QUILLS ) ? _("quills") : _("spines")),
                            source->disp_name().c_str());
            }
        } else {
            add_msg(m_good, _("Your %1$s puncture %2$s in mid-attack!"),
                            (has_trait( trait_QUILLS ) ? _("quills") : _("spines")),
                            source->disp_name().c_str());
        }
        damage_instance spine_damage;
        spine_damage.add_damage(DT_STAB, spine);
        source->deal_damage(this, bp_torso, spine_damage);
    }
    if ((!(wearing_something_on(bp_hit))) && (has_trait( trait_THORNS )) && (!(source->has_weapon()))) {
        if (!is_player()) {
            if( u_see ) {
                add_msg(_("%1$s's %2$s scrape %3$s in mid-attack!"), name.c_str(),
                            _("thorns"), source->disp_name().c_str());
            }
        } else {
            add_msg(m_good, _("Your thorns scrape %s in mid-attack!"), source->disp_name().c_str());
        }
        int thorn = rng(1, 4);
        damage_instance thorn_damage;
        thorn_damage.add_damage(DT_CUT, thorn);
        // In general, critters don't have separate limbs
        // so safer to target the torso
        source->deal_damage(this, bp_torso, thorn_damage);
    }
    if ((!(wearing_something_on(bp_hit))) && (has_trait( trait_CF_HAIR ))) {
        if (!is_player()) {
            if( u_see ) {
                add_msg(_("%1$s gets a load of %2$s's %3$s stuck in!"), source->disp_name().c_str(),
                  name.c_str(), (_("hair")));
            }
        } else {
            add_msg(m_good, _("Your hairs detach into %s!"), source->disp_name().c_str());
        }
        source->add_effect( effect_stunned, 2_turns );
        if (one_in(3)) { // In the eyes!
            source->add_effect( effect_blind, 2_turns );
        }
    }
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( source );
    lua_callback_args_info.emplace_back( bp_hit );
    //lua_callback_args_info.emplace_back( proj );
    lua_callback( "on_player_hit", lua_callback_args_info );
}

void player::on_hurt( Creature *source, bool disturb /*= true*/ )
{
    if( has_trait( trait_ADRENALINE ) && !has_effect( effect_adrenaline ) &&
        ( hp_cur[hp_head] < 25 || hp_cur[hp_torso] < 15 ) ) {
        add_effect( effect_adrenaline, 20_minutes );
    }

    if( disturb ) {
        if( has_effect( effect_sleep ) && !has_effect( effect_narcosis ) ) {
            wake_up();
        }
        if( !is_npc() ) {
            if( source != nullptr ) {
                g->cancel_activity_or_ignore_query( distraction_type::attacked, string_format( _( "You were attacked by %s!" ),
                                                    source->disp_name().c_str() ) );
            } else {
                g->cancel_activity_or_ignore_query( distraction_type::attacked, _( "You were hurt!" ) );
            }
        }
    }

    if( is_dead_state() ) {
        set_killer( source );
    }
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( source );
    lua_callback_args_info.emplace_back( disturb );
    lua_callback( "on_player_hurt", lua_callback_args_info );
}

bool player::immune_to( body_part bp, damage_unit dam ) const
{
    if( has_trait( trait_DEBUG_NODMG ) || is_immune_damage( dam.type ) ) {
        return true;
    }

    passive_absorb_hit( bp, dam );

    for( const item &cloth : worn ) {
        if( cloth.get_coverage() == 100 && cloth.covers( bp ) ) {
            cloth.mitigate_damage( dam );
        }
    }

    return dam.amount <= 0;
}

dealt_damage_instance player::deal_damage( Creature* source, body_part bp,
                                           const damage_instance& d )
{
    if( has_trait( trait_DEBUG_NODMG ) ) {
        return dealt_damage_instance();
    }

    //damage applied here
    dealt_damage_instance dealt_dams = Creature::deal_damage( source, bp, d );
    //block reduction should be by applied this point
    int dam = dealt_dams.total_damage();

    // TODO: Pre or post blit hit tile onto "this"'s location here
    if( dam > 0 && g->u.sees( pos() ) ) {
        g->draw_hit_player( *this, dam );

        if( is_player() && source ) {
            //monster hits player melee
            SCT.add( posx(), posy(),
                     direction_from( 0, 0, posx() - source->posx(), posy() - source->posy() ),
                     get_hp_bar( dam, get_hp_max( player::bp_to_hp( bp ) ) ).first, m_bad,
                     body_part_name( bp ), m_neutral );
        }
    }

    // handle snake artifacts
    if( has_artifact_with( AEP_SNAKES ) && dam >= 6 ) {
        int snakes = dam / 6;
        std::vector<tripoint> valid;
        for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) {
            if( g->is_empty( dest ) ) {
                valid.push_back( dest );
            }
        }
        if( snakes > int( valid.size() ) ) {
            snakes = int( valid.size() );
        }
        if( snakes == 1 ) {
            add_msg( m_warning, _( "A snake sprouts from your body!" ) );
        } else if( snakes >= 2 ) {
            add_msg( m_warning, _( "Some snakes sprout from your body!" ) );
        }
        for( int i = 0; i < snakes && !valid.empty(); i++ ) {
            const tripoint target = random_entry_removed( valid );
            if( monster * const snake = g->summon_mon( mon_shadow_snake, target ) ) {
                snake->friendly = -1;
            }
        }
    }

    // And slimespawners too
    if( ( has_trait( trait_SLIMESPAWNER ) ) && ( dam >= 10 ) && one_in( 20 - dam ) ) {
        std::vector<tripoint> valid;
        for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) {
            if( g->is_empty( dest ) ) {
                valid.push_back( dest );
            }
        }
        add_msg( m_warning, _( "Slime is torn from you, and moves on its own!" ) );
        int numslime = 1;
        for( int i = 0; i < numslime && !valid.empty(); i++ ) {
            const tripoint target = random_entry_removed( valid );
            if( monster * const slime = g->summon_mon( mon_player_blob, target ) ) {
                slime->friendly = -1;
            }
        }
    }

    //Acid blood effects.
    bool u_see = g->u.sees( *this );
    int cut_dam = dealt_dams.type_damage( DT_CUT );
    if( source && has_trait( trait_ACIDBLOOD ) && !one_in( 3 ) &&
        ( dam >= 4 || cut_dam > 0 ) && ( rl_dist( g->u.pos(), source->pos() ) <= 1) ) {
        if( is_player() ) {
            add_msg( m_good, _( "Your acidic blood splashes %s in mid-attack!" ),
                     source->disp_name().c_str() );
        } else if( u_see ) {
            add_msg( _( "%1$s's acidic blood splashes on %2$s in mid-attack!" ),
                     disp_name().c_str(), source->disp_name().c_str() );
        }
        damage_instance acidblood_damage;
        acidblood_damage.add_damage( DT_ACID, rng( 4, 16 ) );
        if( !one_in( 4 ) ) {
            source->deal_damage( this, bp_arm_l, acidblood_damage );
            source->deal_damage( this, bp_arm_r, acidblood_damage );
        } else {
            source->deal_damage( this, bp_torso, acidblood_damage );
            source->deal_damage( this, bp_head, acidblood_damage );
        }
    }

    int recoil_mul = 100;
    switch( bp ) {
        case bp_eyes:
            if( dam > 5 || cut_dam > 0 ) {
                const time_duration minblind = std::max( 1_turns, 1_turns * ( dam + cut_dam ) / 10 );
                const time_duration maxblind = std::min( 5_turns, 1_turns * ( dam + cut_dam ) / 4 );
                add_effect( effect_blind, rng( minblind, maxblind ) );
            }
            break;
        case bp_torso:
            break;
        case bp_hand_l: // Fall through to arms
        case bp_arm_l:
            // Hit to arms/hands are really bad to our aim
            recoil_mul = 200;
            break;
        case bp_hand_r: // Fall through to arms
        case bp_arm_r:
            recoil_mul = 200;
            break;
        case bp_foot_l: // Fall through to legs
        case bp_leg_l:
            break;
        case bp_foot_r: // Fall through to legs
        case bp_leg_r:
            break;
        case bp_mouth: // Fall through to head damage
        case bp_head:
            // @todo: Some daze maybe? Move drain?
            break;
        default:
            debugmsg( "Wacky body part hit!" );
    }

    // @todo: Scale with damage in a way that makes sense for power armors, plate armor and naked skin.
    recoil += recoil_mul * weapon.volume() / 250_ml;
    recoil = std::min( MAX_RECOIL, recoil );
    //looks like this should be based off of dealt damages, not d as d has no damage reduction applied.
    // Skip all this if the damage isn't from a creature. e.g. an explosion.
    if( source != nullptr ) {
        if ( source->has_flag( MF_GRABS ) && !source->is_hallucination() ) {
            /** @EFFECT_DEX increases chance to avoid being grabbed, if DEX>STR */

            /** @EFFECT_STR increases chance to avoid being grabbed, if STR>DEX */
            if( has_grab_break_tec() && get_grab_resist() > 0 &&
                ( get_dex() > get_str() ? rng( 0, get_dex() ) : rng( 0, get_str() ) ) >
                    rng( 0, 10 ) ) {
                if( has_effect( effect_grabbed ) ) {
                    add_msg_if_player( m_warning ,_("You are being grabbed by %s, but you bat it away!"),
                                       source->disp_name().c_str() );
                } else {
                    add_msg_player_or_npc( m_info, _("You are being grabbed by %s, but you break its grab!"),
                                           _("<npcname> are being grabbed by %s, but they break its grab!"),
                                           source->disp_name().c_str() );
                }
            } else {
                int prev_effect = get_effect_int( effect_grabbed );
                add_effect( effect_grabbed, 2_turns, bp_torso, false, prev_effect + 2 );
                add_msg_player_or_npc(m_bad, _("You are grabbed by %s!"), _("<npcname> is grabbed by %s!"),
                                source->disp_name().c_str());
            }
        }
    }

    if( get_option<bool>( "FILTHY_WOUNDS" ) ) {
        int sum_cover = 0;
        for( const item &i : worn ) {
            if( i.covers( bp ) && i.is_filthy() ) {
                sum_cover += i.get_coverage();
            }
        }

        // Chance of infection is damage (with cut and stab x4) * sum of coverage on affected body part, in percent.
        // i.e. if the body part has a sum of 100 coverage from filthy clothing,
        // each point of damage has a 1% change of causing infection.
        if( sum_cover > 0 ) {
            const int cut_type_dam = dealt_dams.type_damage( DT_CUT ) + dealt_dams.type_damage( DT_STAB );
            const int combined_dam = dealt_dams.type_damage( DT_BASH ) + ( cut_type_dam * 4 );
            const int infection_chance = ( combined_dam * sum_cover ) / 100;
            if( x_in_y( infection_chance, 100 ) ) {
                if( has_effect( effect_bite, bp ) ) {
                    add_effect( effect_bite, 40_minutes, bp, true );
                } else if( has_effect( effect_infected, bp ) ) {
                    add_effect( effect_infected, 25_minutes, bp, true );
                } else {
                    add_effect( effect_bite, 1_turns, bp, true );
                }
                add_msg_if_player( _( "Filth from your clothing has implanted deep in the wound." ) );
            }
        }
    }

    on_hurt( source );
    return dealt_dams;
}

void player::mod_pain( int npain) {
    if( npain > 0 ) {
        if( has_trait( trait_NOPAIN ) ) {
            return;
        }
        // always increase pain gained by one from these bad mutations
        if( has_trait( trait_MOREPAIN ) ) {
            npain += std::max( 1, roll_remainder( npain * 0.25 ));
        } else if( has_trait( trait_MOREPAIN2 ) ) {
            npain += std::max( 1, roll_remainder( npain * 0.5 ));
        } else if( has_trait( trait_MOREPAIN3 ) ) {
            npain += std::max( 1, roll_remainder( npain * 1.0 ));
        }

        if( npain > 1 ) {
            // if it's 1 it'll just become 0, which is bad
            if( has_trait( trait_PAINRESIST_TROGLO ) ) {
                npain = roll_remainder( npain * 0.5 );
            } else if( has_trait( trait_PAINRESIST ) ) {
                npain = roll_remainder( npain * 0.67 );
            }
        }
    }
    Creature::mod_pain( npain );
}

void player::set_pain(int npain)
{
    const int prev_pain = get_perceived_pain();
    Creature::set_pain( npain );
    const int cur_pain = get_perceived_pain();

    if( cur_pain != prev_pain ) {
        react_to_felt_pain( cur_pain - prev_pain );
        on_stat_change( "perceived_pain", cur_pain );
    }
}

int player::get_perceived_pain() const
{
    if( get_effect_int( effect_adrenaline ) > 1 ) {
        return 0;
    }

    return std::max( get_pain() - get_painkiller(), 0 );
}

void player::mod_painkiller(int npkill)
{
    set_painkiller( pkill + npkill );
}

void player::set_painkiller(int npkill)
{
    npkill = std::max( npkill, 0 );
    if( pkill != npkill ) {
        const int prev_pain = get_perceived_pain();
        pkill = npkill;
        on_stat_change( "pkill", pkill );
        const int cur_pain = get_perceived_pain();

        if( cur_pain != prev_pain ) {
            react_to_felt_pain( cur_pain - prev_pain );
            on_stat_change( "perceived_pain", cur_pain );
        }
    }
}

int player::get_painkiller() const
{
    return pkill;
}

void player::react_to_felt_pain( int intensity )
{
    if( intensity <= 0 ) {
        return;
    }
    if( is_player() && intensity >= 2 ) {
        g->cancel_activity_or_ignore_query( distraction_type::pain,  _( "Ouch, something hurts!" ) );
    }
    // Only a large pain burst will actually wake people while sleeping.
    if( has_effect( effect_sleep ) && !has_effect( effect_narcosis ) ) {
        int pain_thresh = rng( 3, 5 );

        if( has_trait( trait_HEAVYSLEEPER ) ) {
            pain_thresh += 2;
        } else if ( has_trait( trait_HEAVYSLEEPER2 ) ) {
            pain_thresh += 5;
        }

        if( intensity >= pain_thresh ) {
            wake_up();
        }
    }
}

int player::reduce_healing_effect( const efftype_id &eff_id, int remove_med, body_part hurt ){
    effect &e = get_effect( eff_id, hurt );
    int intensity = e.get_intensity();
    if( remove_med < intensity ) {
        if( eff_id == effect_bandaged ){
            add_msg_if_player( m_bad, _( "Bandages on your %s were damaged!" ), body_part_name( hurt ) );
        } else  if( eff_id == effect_disinfected ){
            add_msg_if_player( m_bad, _( "You got some filth on your disinfected %s!" ), body_part_name( hurt ) );
        }
    } else {
        if( eff_id == effect_bandaged ){
            add_msg_if_player( m_bad, _( "Bandages on your %s were destroyed!" ), body_part_name( hurt ) );
        } else  if( eff_id == effect_disinfected ){
            add_msg_if_player( m_bad, _( "Your %s is no longer disinfected!" ), body_part_name( hurt ) );
        }
    }
    e.mod_duration( -6_hours * remove_med );
    return intensity;
}

/*
    Where damage to player is actually applied to hit body parts
    Might be where to put bleed stuff rather than in player::deal_damage()
 */
void player::apply_damage(Creature *source, body_part hurt, int dam, const bool bypass_med )
{
    if( is_dead_state() || has_trait( trait_DEBUG_NODMG ) ) {
        // don't do any more damage if we're already dead
        // Or if we're debugging and don't want to die
        return;
    }

    hp_part hurtpart = bp_to_hp( hurt );
    if( hurtpart == num_hp_parts ) {
        debugmsg("Wacky body part hurt!");
        hurtpart = hp_torso;
    }

    mod_pain( dam / 2 );

    hp_cur[hurtpart] -= dam;
    if (hp_cur[hurtpart] < 0) {
        lifetime_stats.damage_taken += hp_cur[hurtpart];
        hp_cur[hurtpart] = 0;
    }

    if( hp_cur[hurtpart] <= 0 && ( source == nullptr || !source->is_hallucination() ) ) {
        if( has_effect( effect_mending, hurt ) ) {
            effect &e = get_effect( effect_mending, hurt );
            float remove_mend = dam / 20.0f;
            e.mod_duration( -e.get_max_duration() * remove_mend );
        }
    }

    lifetime_stats.damage_taken += dam;
    if( dam > get_painkiller() ) {
        on_hurt( source );
    }

    if( !bypass_med ) {
        // remove healing effects if damaged
        int remove_med = roll_remainder( dam / 5.0f );
        if( remove_med > 0 && has_effect( effect_bandaged, hurt ) ) {
            remove_med -= reduce_healing_effect( effect_bandaged , remove_med, hurt );
        }
        if( remove_med > 0 && has_effect( effect_disinfected, hurt ) ) {
            remove_med -= reduce_healing_effect( effect_disinfected , remove_med, hurt );
        }
    }
}

void player::heal(body_part healed, int dam)
{
    hp_part healpart;
    switch (healed) {
        case bp_eyes: // Fall through to head damage
        case bp_mouth: // Fall through to head damage
        case bp_head:
            healpart = hp_head;
            break;
        case bp_torso:
            healpart = hp_torso;
            break;
        case bp_hand_l:
            // Shouldn't happen, but fall through to arms
            debugmsg("Heal against hands!");
            /* fallthrough */
        case bp_arm_l:
            healpart = hp_arm_l;
            break;
        case bp_hand_r:
            // Shouldn't happen, but fall through to arms
            debugmsg("Heal against hands!");
            /* fallthrough */
        case bp_arm_r:
            healpart = hp_arm_r;
            break;
        case bp_foot_l:
            // Shouldn't happen, but fall through to legs
            debugmsg("Heal against feet!");
            /* fallthrough */
        case bp_leg_l:
            healpart = hp_leg_l;
            break;
        case bp_foot_r:
            // Shouldn't happen, but fall through to legs
            debugmsg("Heal against feet!");
            /* fallthrough */
        case bp_leg_r:
            healpart = hp_leg_r;
            break;
        default:
            debugmsg("Wacky body part healed!");
            healpart = hp_torso;
    }
    heal( healpart, dam );
}

void player::heal(hp_part healed, int dam)
{
    if (hp_cur[healed] > 0) {
        hp_cur[healed] += dam;
        if (hp_cur[healed] > hp_max[healed]) {
            lifetime_stats.damage_healed -= hp_cur[healed] - hp_max[healed];
            hp_cur[healed] = hp_max[healed];
        }
        lifetime_stats.damage_healed += dam;
    }
}

void player::healall(int dam)
{
    for( int healed_part = 0; healed_part < num_hp_parts; healed_part++) {
        heal( static_cast<hp_part>( healed_part ), dam );
    }
}

void player::hurtall(int dam, Creature *source, bool disturb /*= true*/)
{
    if( is_dead_state() || has_trait( trait_DEBUG_NODMG ) || dam <= 0 ) {
        return;
    }

    for( int i = 0; i < num_hp_parts; i++ ) {
        const hp_part bp = static_cast<hp_part>( i );
        // Don't use apply_damage here or it will annoy the player with 6 queries
        hp_cur[bp] -= dam;
        lifetime_stats.damage_taken += dam;
        if( hp_cur[bp] < 0 ) {
            lifetime_stats.damage_taken += hp_cur[bp];
            hp_cur[bp] = 0;
        }
    }

    // Low pain: damage is spread all over the body, so not as painful as 6 hits in one part
    mod_pain( dam );
    on_hurt( source, disturb );
}

int player::hitall(int dam, int vary, Creature *source)
{
    int damage_taken = 0;
    for (int i = 0; i < num_hp_parts; i++) {
        const body_part bp = hp_to_bp( static_cast<hp_part>( i ) );
        int ddam = vary ? dam * rng (100 - vary, 100) / 100 : dam;
        int cut = 0;
        auto damage = damage_instance::physical(ddam, cut, 0);
        damage_taken += deal_damage( source, bp, damage ).total_damage();
    }
    return damage_taken;
}

float player::fall_damage_mod() const
{
    float ret = 1.0f;

    // Ability to land properly is 2x as important as dexterity itself
    /** @EFFECT_DEX decreases damage from falling */

    /** @EFFECT_DODGE decreases damage from falling */
    float dex_dodge = dex_cur / 2 + get_skill_level( skill_dodge );
    // Penalize for wearing heavy stuff
    dex_dodge -= ( ( ( encumb(bp_leg_l) + encumb(bp_leg_r) ) / 2 ) + ( encumb(bp_torso) / 1 ) ) / 10;
    // But prevent it from increasing damage
    dex_dodge = std::max( 0.0f, dex_dodge );
    // 100% damage at 0, 75% at 10, 50% at 20 and so on
    ret *= (100.0f - (dex_dodge * 4.0f)) / 100.0f;

    if( has_trait( trait_PARKOUR ) ) {
        ret *= 2.0f / 3.0f;
    }

    // TODO: Bonus for Judo, mutations. Penalty for heavy weight (including mutations)
    return std::max( 0.0f, ret );
}

// force is maximum damage to hp before scaling
int player::impact( const int force, const tripoint &p )
{
    // Falls over ~30m are fatal more often than not
    // But that would be quite a lot considering 21 z-levels in game
    // so let's assume 1 z-level is comparable to 30 force

    if( force <= 0 ) {
        return force;
    }

    // Damage modifier (post armor)
    float mod = 1.0f;
    int effective_force = force;
    int cut = 0;
    // Percentage armor penetration - armor won't help much here
    // TODO: Make cushioned items like bike helmets help more
    float armor_eff = 1.0f;
    // Shock Absorber CBM heavily reduces damage
    const bool shock_absorbers = has_active_bionic( bionic_id( "bio_shock_absorber" ) );

    // Being slammed against things rather than landing means we can't
    // control the impact as well
    const bool slam = p != pos();
    std::string target_name = "a swarm of bugs";
    Creature *critter = g->critter_at( p );
    if( critter != this && critter != nullptr ) {
        target_name = critter->disp_name();
        // Slamming into creatures and NPCs
        // TODO: Handle spikes/horns and hard materials
        armor_eff = 0.5f; // 2x as much as with the ground
        // TODO: Modify based on something?
        mod = 1.0f;
        effective_force = force;
    } else if( const optional_vpart_position vp = g->m.veh_at( p ) ) {
        // Slamming into vehicles
        // TODO: Integrate it with vehicle collision function somehow
        target_name = vp->vehicle().disp_name();
        if( vp.part_with_feature( "SHARP", true ) ) {
            // Now we're actually getting impaled
            cut = force; // Lots of fun
        }

        mod = slam ? 1.0f : fall_damage_mod();
        armor_eff = 0.25f; // Not much
        if( !slam && vp->part_with_feature( "ROOF", true ) ) {
            // Roof offers better landing than frame or pavement
            effective_force /= 2; // TODO: Make this not happen with heavy duty/plated roof
        }
    } else {
        // Slamming into terrain/furniture
        target_name = g->m.disp_name( p );
        int hard_ground = g->m.has_flag( TFLAG_DIGGABLE, p ) ? 0 : 3;
        armor_eff = 0.25f; // Not much
        // Get cut by stuff
        // This isn't impalement on metal wreckage, more like flying through a closed window
        cut = g->m.has_flag( TFLAG_SHARP, p ) ? 5 : 0;
        effective_force = force + hard_ground;
        mod = slam ? 1.0f : fall_damage_mod();
        if( g->m.has_furn( p ) ) {
            // TODO: Make furniture matter
        } else if( g->m.has_flag( TFLAG_SWIMMABLE, p ) ) {
            // TODO: Some formula of swimming
            effective_force /= 4;
        }
    }

    // Rescale for huge force
    // At >30 force, proper landing is impossible and armor helps way less
    if( effective_force > 30 ) {
        // Armor simply helps way less
        armor_eff *= 30.0f / effective_force;
        if( mod < 1.0f ) {
            // Everything past 30 damage gets a worse modifier
            const float scaled_mod = std::pow( mod, 30.0f / effective_force );
            const float scaled_damage = ( 30.0f * mod ) + scaled_mod * ( effective_force - 30.0f );
            mod = scaled_damage / effective_force;
        }
    }

    if( !slam && mod < 1.0f && mod * force < 5 ) {
        // Perfect landing, no damage (regardless of armor)
        add_msg_if_player( m_warning, _("You land on %s."), target_name.c_str() );
        return 0;
    }

    // Shock absorbers kick in only when they need to, so if our other protections fail, fall back on them
    if( shock_absorbers ) {
        effective_force -= 15; // Provide a flat reduction to force
        if( mod > 0.25f ) {
            mod = 0.25f; // And provide a 75% reduction against that force if we don't have it already
        }
        if( effective_force < 0 ) {
            effective_force = 0;
        }
    }

    int total_dealt = 0;
    if( mod * effective_force >= 5 ) {
        for( int i = 0; i < num_hp_parts; i++ ) {
            const body_part bp = hp_to_bp( static_cast<hp_part>( i ) );
            const int bash = effective_force * rng( 60, 100 ) / 100;
            damage_instance di;
            di.add_damage( DT_BASH, bash, 0, armor_eff, mod );
            // No good way to land on sharp stuff, so here modifier == 1.0f
            di.add_damage( DT_CUT, cut, 0, armor_eff, 1.0f );
            total_dealt += deal_damage( nullptr, bp, di ).total_damage();
        }
    }

    if( total_dealt > 0 && is_player() ) {
        // "You slam against the dirt" is fine
        add_msg( m_bad, _( "You are slammed against %s for %d damage." ),
                 target_name.c_str(), total_dealt );
    } else if( is_player() && shock_absorbers ) {
        add_msg( m_bad, _( "You are slammed against %s!" ),
                 target_name.c_str(), total_dealt );
        add_msg( m_good, _( "...but your shock absorbers negate the damage!" ) );
    } else if( slam ) {
        // Only print this line if it is a slam and not a landing
        // Non-players should only get this one: player doesn't know how much damage was dealt
        // and landing messages for each slammed creature would be too much
        add_msg_player_or_npc( m_bad,
                               _( "You are slammed against %s." ),
                               _( "<npcname> is slammed against %s." ),
                               target_name.c_str() );
    } else {
        // No landing message for NPCs
        add_msg_if_player( m_warning, _( "You land on %s." ), target_name.c_str() );
    }

    if( x_in_y( mod, 1.0f ) ) {
        add_effect( effect_downed, rng( 1_turns, 1_turns + mod * 3_turns ) );
    }

    return total_dealt;
}

void player::knock_back_from( const tripoint &p )
{
    if( p == pos() ) {
        return;
    }

    tripoint to = pos();
    const tripoint dp = pos() - p;
    to.x += sgn( dp.x );
    to.y += sgn( dp.y );

    // First, see if we hit a monster
    if( monster * const critter = g->critter_at<monster>( to ) ) {
        deal_damage( critter, bp_torso, damage_instance( DT_BASH, critter->type->size ) );
        add_effect( effect_stunned, 1_turns );
        /** @EFFECT_STR_MAX allows knocked back player to knock back, damage, stun some monsters */
        if ((str_max - 6) / 4 > critter->type->size) {
            critter->knock_back_from(pos()); // Chain reaction!
            critter->apply_damage( this, bp_torso, (str_max - 6) / 4);
            critter->add_effect( effect_stunned, 1_turns );
        } else if ((str_max - 6) / 4 == critter->type->size) {
            critter->apply_damage( this, bp_torso, (str_max - 6) / 4);
            critter->add_effect( effect_stunned, 1_turns );
        }
        critter->check_dead_state();

        add_msg_player_or_npc(_("You bounce off a %s!"), _("<npcname> bounces off a %s!"),
                              critter->name().c_str() );
        return;
    }

    if( npc * const np = g->critter_at<npc>( to ) ) {
        deal_damage( np, bp_torso, damage_instance( DT_BASH, np->get_size() ) );
        add_effect( effect_stunned, 1_turns );
        np->deal_damage( this, bp_torso, damage_instance( DT_BASH, 3 ) );
        add_msg_player_or_npc( _("You bounce off %s!"), _("<npcname> bounces off %s!"), np->name.c_str() );
        np->check_dead_state();
        return;
    }

    // If we're still in the function at this point, we're actually moving a tile!
    if (g->m.has_flag( "LIQUID", to ) && g->m.has_flag( TFLAG_DEEP_WATER, to )) {
        if (!is_npc()) {
            g->plswim( to );
        }
        // TODO: NPCs can't swim!
    } else if (g->m.impassable( to )) { // Wait, it's a wall

        // It's some kind of wall.
        apply_damage( nullptr, bp_torso, 3 ); // TODO: who knocked us back? Maybe that creature should be the source of the damage?
        add_effect( effect_stunned, 2_turns );
        add_msg_player_or_npc( _("You bounce off a %s!"), _("<npcname> bounces off a %s!"),
                               g->m.obstacle_name( to ).c_str() );

    } else { // It's no wall
        setpos( to );
    }
}

int player::hp_percentage() const
{
    int total_cur = 0;
    int total_max = 0;
    // Head and torso HP are weighted 3x and 2x, respectively
    total_cur = hp_cur[hp_head] * 3 + hp_cur[hp_torso] * 2;
    total_max = hp_max[hp_head] * 3 + hp_max[hp_torso] * 2;
    for (int i = hp_arm_l; i < num_hp_parts; i++) {
        total_cur += hp_cur[i];
        total_max += hp_max[i];
    }

    return (100 * total_cur) / total_max;
}

// Returns the number of multiples of tick_length we would "pass" on our way `from` to `to`
// For example, if `tick_length` is 1 hour, then going from 0:59 to 1:01 should return 1
inline int ticks_between( const time_point &from, const time_point &to,
                          const time_duration &tick_length )
{
    return ( to_turn<int>( to ) / to_turns<int>( tick_length ) ) - ( to_turn<int>
            ( from ) / to_turns<int>( tick_length ) );
}

void player::update_body()
{
    update_body( calendar::turn - 1_turns, calendar::turn );
}

void player::update_body( const time_point &from, const time_point &to )
{
    update_stamina( to_turns<int>( to - from ) );
    const int five_mins = ticks_between( from, to, 5_minutes );
    if( five_mins > 0 ) {
        check_needs_extremes();
        update_needs( five_mins );
        regen( five_mins );
        // Note: mend ticks once per 5 minutes, but wants rate in TURNS, not 5 minute intervals
        mend( five_mins * MINUTES( 5 ) );
    }

    const int thirty_mins = ticks_between( from, to, 30_minutes );
    if( thirty_mins > 0 ) {
        // Radiation kills health even at low doses
        update_health( has_trait( trait_RADIOGENIC ) ? 0 : -radiation );
        get_sick();
    }

    for( const auto& v : vitamin::all() ) {
        const time_duration rate = vitamin_rate( v.first );
        if( rate > 0_turns ) {
            int qty = ticks_between( from, to, rate );
            if( qty > 0 ) {
                vitamin_mod( v.first, 0 - qty );
            }

        } else if ( rate < 0_turns ) {
            // mutations can result in vitamins being generated (but never accumulated)
            int qty = ticks_between( from, to, -rate );
            if( qty > 0 ) {
                vitamin_mod( v.first, qty );
            }
        }
    }
}

void player::update_vitamins( const vitamin_id& vit )
{
    if( is_npc() ) {
        return; // NPCs cannot develop vitamin diseases
    }

    efftype_id def = vit.obj().deficiency();
    efftype_id exc = vit.obj().excess();

    int lvl = vit.obj().severity( vitamin_get( vit ) );
    if( lvl <= 0 ) {
        remove_effect( def );
    }
    if( lvl >= 0 ) {
        remove_effect( exc );
    }
    if( lvl > 0 ) {
        if( has_effect( def, num_bp ) ) {
            get_effect( def, num_bp ).set_intensity( lvl, true );
        } else {
            add_effect( def, 1_turns, num_bp, true, lvl );
        }
    }
    if( lvl < 0 ) {
        if( has_effect( exc, num_bp ) ) {
            get_effect( exc, num_bp ).set_intensity( lvl, true );
        } else {
            add_effect( exc, 1_turns, num_bp, true, lvl );
        }
    }
}

void player::get_sick()
{
    // NPCs are too dumb to handle infections now
    if( is_npc() || has_trait( trait_DISIMMUNE ) ) {
        // In a shocking twist, disease immunity prevents diseases.
        return;
    }

    if( has_effect( effect_flu ) || has_effect( effect_common_cold ) ) {
        // While it's certainly possible to get sick when you already are,
        // it wouldn't be very fun.
        return;
    }

    // Normal people get sick about 2-4 times/year.
    int base_diseases_per_year = 3;
    if (has_trait( trait_DISRESISTANT )) {
        // Disease resistant people only get sick once a year.
        base_diseases_per_year = 1;
    }

    // This check runs once every 30 minutes, so double to get hours, *24 to get days.
    const int checks_per_year = 2 * 24 * 365;

    // Health is in the range [-200,200].
    // Diseases are half as common for every 50 health you gain.
    float health_factor = std::pow(2.0f, get_healthy() / 50.0f);

    int disease_rarity = static_cast<int>( checks_per_year * health_factor / base_diseases_per_year );
    add_msg( m_debug, "disease_rarity = %d", disease_rarity);
    if (one_in(disease_rarity)) {
        if (one_in(6)) {
            // The flu typically lasts 3-10 days.
            add_env_effect( effect_flu, bp_mouth, 3, rng( 3_days, 10_days ) );
        } else {
            // A cold typically lasts 1-14 days.
            add_env_effect( effect_common_cold, bp_mouth, 3, rng( 1_days, 14_days ) );
        }
    }
}

void player::check_needs_extremes()
{
    // Check if we've overdosed... in any deadly way.
    if (stim > 250) {
        add_msg_if_player(m_bad, _("You have a sudden heart attack!"));
        add_memorial_log(pgettext("memorial_male", "Died of a drug overdose."),
                           pgettext("memorial_female", "Died of a drug overdose."));
        hp_cur[hp_torso] = 0;
    } else if (stim < -200 || get_painkiller() > 240) {
        add_msg_if_player(m_bad, _("Your breathing stops completely."));
        add_memorial_log(pgettext("memorial_male", "Died of a drug overdose."),
                           pgettext("memorial_female", "Died of a drug overdose."));
        hp_cur[hp_torso] = 0;
    } else if( has_effect( effect_jetinjector ) && get_effect_dur( effect_jetinjector ) > 40_minutes ) {
        if (!(has_trait( trait_NOPAIN ))) {
            add_msg_if_player(m_bad, _("Your heart spasms painfully and stops."));
        } else {
            add_msg_if_player(_("Your heart spasms and stops."));
        }
        add_memorial_log(pgettext("memorial_male", "Died of a healing stimulant overdose."),
                           pgettext("memorial_female", "Died of a healing stimulant overdose."));
        hp_cur[hp_torso] = 0;
    } else if( get_effect_dur( effect_adrenaline ) > 50_minutes ) {
        add_msg_if_player( m_bad, _("Your heart spasms and stops.") );
        add_memorial_log( pgettext("memorial_male", "Died of adrenaline overdose."),
                          pgettext("memorial_female", "Died of adrenaline overdose.") );
        hp_cur[hp_torso] = 0;
    }

    // Check if we're starving or have starved
    if( is_player() && get_hunger() >= 300 && get_starvation() >= 2700 ) {
        if( get_starvation() >= 5700 ) {
            add_msg_if_player(m_bad, _("You have starved to death."));
            add_memorial_log(pgettext("memorial_male", "Died of starvation."),
                               pgettext("memorial_female", "Died of starvation."));
            hp_cur[hp_torso] = 0;
        } else if( get_starvation() >= 4700 && calendar::once_every( 1_hours ) ) {
            add_msg_if_player(m_warning, _("Food..."));
        } else if( get_starvation() >= 3700 && calendar::once_every( 1_hours ) ) {
            add_msg_if_player(m_warning, _("You are STARVING!"));
        } else if( calendar::once_every( 1_hours ) ) {
            add_msg_if_player(m_warning, _("Your stomach feels so empty..."));
        }
    }

    // Check if we're dying of thirst
    if( is_player() && get_thirst() >= 600 ) {
        if( get_thirst() >= 1200 ) {
            add_msg_if_player(m_bad, _("You have died of dehydration."));
            add_memorial_log(pgettext("memorial_male", "Died of thirst."),
                               pgettext("memorial_female", "Died of thirst."));
            hp_cur[hp_torso] = 0;
        } else if( get_thirst() >= 1000 && calendar::once_every( 30_minutes ) ) {
            add_msg_if_player(m_warning, _("Even your eyes feel dry..."));
        } else if( get_thirst() >= 800 && calendar::once_every( 30_minutes ) ) {
            add_msg_if_player(m_warning, _("You are THIRSTY!"));
        } else if( calendar::once_every( 30_minutes ) ) {
            add_msg_if_player(m_warning, _("Your mouth feels so dry..."));
        }
    }

    // Check if we're falling asleep, unless we're sleeping
    if( get_fatigue() >= EXHAUSTED + 25 && !in_sleep_state() ) {
        if( get_fatigue() >= MASSIVE_FATIGUE ) {
            add_msg_if_player(m_bad, _("Survivor sleep now."));
            add_memorial_log(pgettext("memorial_male", "Succumbed to lack of sleep."),
                               pgettext("memorial_female", "Succumbed to lack of sleep."));
            mod_fatigue(-10);
            fall_asleep();
        } else if( get_fatigue() >= 800 && calendar::once_every( 30_minutes ) ) {
            add_msg_if_player(m_warning, _("Anywhere would be a good place to sleep..."));
        } else if( calendar::once_every( 30_minutes ) ) {
            add_msg_if_player(m_warning, _("You feel like you haven't slept in days."));
        }
    }

    // Even if we're not Exhausted, we really should be feeling lack/sleep earlier
    // Penalties start at Dead Tired and go from there
    if( get_fatigue() >= DEAD_TIRED && !in_sleep_state() ) {
        if( get_fatigue() >= 700 ) {
            if( calendar::once_every( 30_minutes ) ) {
                add_msg_if_player( m_warning, _("You're too physically tired to stop yawning.") );
                add_effect( effect_lack_sleep, 30_minutes + 1_turns );
            }
            /** @EFFECT_INT slightly decreases occurrence of short naps when dead tired */
            if( one_in(50 + int_cur) ) {
                // Rivet's idea: look out for microsleeps!
                fall_asleep( 5_turns );
            }
        } else if( get_fatigue() >= EXHAUSTED ) {
            if( calendar::once_every( 30_minutes ) ) {
                add_msg_if_player( m_warning, _("How much longer until bedtime?") );
                add_effect( effect_lack_sleep, 30_minutes + 1_turns );
            }
            /** @EFFECT_INT slightly decreases occurrence of short naps when exhausted */
            if (one_in(100 + int_cur)) {
                fall_asleep( 5_turns );
            }
        } else if( get_fatigue() >= DEAD_TIRED && calendar::once_every( 30_minutes ) ) {
            add_msg_if_player( m_warning, _("*yawn* You should really get some sleep.") );
            add_effect( effect_lack_sleep, 30_minutes + 1_turns );
        }
    }

    // Sleep deprivation kicks in if lack of sleep is avoided with stimulants or otherwise for long periods of time
    int sleep_deprivation = get_sleep_deprivation();
    float sleep_deprivation_pct = sleep_deprivation / static_cast<float>( SLEEP_DEPRIVATION_MASSIVE );

    if( sleep_deprivation >= SLEEP_DEPRIVATION_HARMLESS && !in_sleep_state() ) {
        if( calendar::once_every( 60_minutes ) ) {
            if( sleep_deprivation < SLEEP_DEPRIVATION_MINOR ) {
                add_msg_if_player( m_warning, _( "Your mind feels tired. It's been a while since you've slept well." ) );
                mod_fatigue( 1 );
            }
            else if( sleep_deprivation < SLEEP_DEPRIVATION_SERIOUS ) {
                add_msg_if_player( m_bad, _( "Your mind feels foggy from lack of good sleep, and your eyes keep trying to close against your will." ) );
                mod_fatigue( 5 );

                if( one_in( 10 ) ) {
                    mod_healthy_mod( -1, 0 );
                }
            }
            else if( sleep_deprivation < SLEEP_DEPRIVATION_MAJOR ) {
                add_msg_if_player( m_bad, _( "Your mind feels weary, and you dread every wakeful minute that passes. You crave sleep, and feel like you're about to collapse." ) );
                mod_fatigue( 10 );

                if( one_in( 5 ) ) {
                    mod_healthy_mod( -2, 0 );
                }
            }
            else if( sleep_deprivation < SLEEP_DEPRIVATION_MASSIVE ) {
                add_msg_if_player( m_bad, _( "You haven't slept decently for so long that your whole body is screaming for mercy. It's a miracle that you're still awake, but it just feels like a curse now." ) );
                mod_fatigue( 40 );

                mod_healthy_mod( -5, 0 );
            }
            // else you pass out for 20 hours, guaranteed

            // Microsleeps are slightly worse if you're sleep deprived, but not by much. (chance: 1 in (75 + int_cur) at lethal sleep deprivation)
            // Note: these can coexist with fatigue-related microsleeps
            /** @EFFECT_INT slightly decreases occurrence of short naps when sleep deprived */
            if( one_in( static_cast<int>( sleep_deprivation_pct * 75 ) + int_cur ) ) {
                fall_asleep( 5_turns );
            }

            // Stimulants can be used to stay awake a while longer, but after a while you'll just collapse.
            bool can_pass_out = ( stim < 30 && sleep_deprivation >= SLEEP_DEPRIVATION_MINOR ) || sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR;

            if( can_pass_out && calendar::once_every( 10_minutes ) ) {
                /** @EFFECT_PER slightly increases resilience against passing out from sleep deprivation */
                if( one_in( static_cast<int>( ( 1 - sleep_deprivation_pct ) * 100 ) + per_cur ) || sleep_deprivation >= SLEEP_DEPRIVATION_MASSIVE ) {
                    add_msg_if_player( m_bad, _( "Your body collapses to sleep deprivation, your neglected fatigue rushing back all at once, and you pass out on the spot." ) );
                    if( get_fatigue() < EXHAUSTED ) {
                        set_fatigue( EXHAUSTED );
                    }

                    if( sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR ) {
                        fall_asleep( 20_hours );
                    }
                    else if( sleep_deprivation >= SLEEP_DEPRIVATION_SERIOUS ) {
                        fall_asleep( 16_hours );
                    }
                    else {
                        fall_asleep( 12_hours );
                    }
                }
            }

        }
    }

}

needs_rates player::calc_needs_rates()
{
    effect &sleep = get_effect( effect_sleep );
    const bool has_recycler = has_bionic( bio_recycler );
    const bool asleep = !sleep.is_null();

    needs_rates rates;
    rates.hunger = metabolic_rate();
    add_msg_if_player( m_debug, "Metabolic rate: %.2f", rates.hunger );

    rates.thirst = get_option< float >( "PLAYER_THIRST_RATE" );
    rates.thirst *= 1.0f +  mutation_value( "thirst_modifier" );
    if( worn_with_flag( "SLOWS_THIRST" ) ) {
        rates.thirst *= 0.7f;
    }

    // Note: intentionally not in metabolic rate
    if( has_recycler ) {
        // Recycler won't help much with mutant metabolism - it is intended for human one
        rates.hunger = std::min( rates.hunger, std::max( 0.5f, rates.hunger - 0.5f ) );
        rates.thirst = std::min( rates.thirst, std::max( 0.5f, rates.thirst - 0.5f ) );
    }

    if( asleep ) {
        rates.recovery = 1.0f + mutation_value( "fatigue_regen_modifier" );
        if( !is_hibernating() ) {
            // Hunger and thirst advance more slowly while we sleep. This is the standard rate.
            rates.hunger *= 0.5f;
            rates.thirst *= 0.5f;
            const int intense = sleep.is_null() ? 0 : sleep.get_intensity();
            // Accelerated recovery capped to 2x over 2 hours
            // After 16 hours of activity, equal to 7.25 hours of rest
            const int accelerated_recovery_chance = 24 - intense + 1;
            const float accelerated_recovery_rate = 1.0f / accelerated_recovery_chance;
            rates.recovery += accelerated_recovery_rate;

        } else {
            // Hunger and thirst advance *much* more slowly whilst we hibernate.
            rates.hunger *= ( 2.0f / 7.0f );
            rates.thirst *= ( 2.0f / 7.0f );
        }
        rates.recovery -= float( get_perceived_pain() ) / 60;

    } else {
        rates.recovery = 0;
    }

    if( is_npc() ) {
        rates.hunger *= 0.25f;
        rates.thirst *= 0.25f;
    }
    rates.fatigue = get_option< float >( "PLAYER_FATIGUE_RATE" );
    rates.fatigue *= 1.0f + mutation_value( "fatigue_modifier" );
    return rates;
}

void player::update_needs( int rate_multiplier )
{
    // Hunger, thirst, & fatigue up every 5 minutes
    effect &sleep = get_effect( effect_sleep );
    // No food/thirst/fatigue clock at all
    const bool debug_ls = has_trait( trait_DEBUG_LS );
    // No food/thirst, capped fatigue clock (only up to tired)
    const bool npc_no_food = is_npc() && get_option<bool>( "NO_NPC_FOOD" );
    const bool foodless = debug_ls || npc_no_food;
    const bool asleep = !sleep.is_null();
    const bool lying = asleep || has_effect( effect_lying_down ) ||
                       activity.id() == "ACT_TRY_SLEEP";
    const bool mouse = has_trait( trait_NO_THIRST );
    const bool mycus = has_trait( trait_M_DEPENDENT );

    needs_rates rates = calc_needs_rates();

    if( !foodless && rates.hunger > 0.0f ) {
        const int rolled_hunger = divide_roll_remainder( rates.hunger * rate_multiplier, 1.0 );
        mod_hunger( rolled_hunger );

        // if the playing is famished, starvation increases
        if( get_hunger() >= 300 ) {
            mod_starvation( rolled_hunger );
        } else {
            mod_starvation( -rolled_hunger );
        }
    }

    if( !foodless && rates.thirst > 0.0f ) {
        mod_thirst( divide_roll_remainder( rates.thirst * rate_multiplier, 1.0 ) );
    }
    if( mycus ) {
        // Mycus feeders synchronize hunger and thirst, since their only source of both is the mycus fruit.
        if( get_hunger() > get_thirst() ) {
            set_thirst( get_hunger() );
        } else if( get_thirst() > get_hunger() ) {
            set_hunger( get_thirst() );
        }
    } else if( mouse ) {
        // Metabolic Rehydration makes PT mice gain all their hydration from food.
        set_thirst( get_hunger() );
    }

    const bool wasnt_fatigued = get_fatigue() <= DEAD_TIRED;
    // Don't increase fatigue if sleeping or trying to sleep or if we're at the cap.
    if( get_fatigue() < 1050 && !asleep && !debug_ls ) {
        if( rates.fatigue > 0.0f ) {
            int fatigue_roll = divide_roll_remainder( rates.fatigue * rate_multiplier, 1.0 );
            mod_fatigue( fatigue_roll );

            if( get_option< bool >( "SLEEP_DEPRIVATION" ) ) {
                // Synaptic regen bionic stops SD while awake and boosts it while sleeping
                if( !has_active_bionic( bio_synaptic_regen ) ) {
                    // fatigue_roll should be around 1 - so the counter increases by 1 every minute on average,
                    // but characters who need less sleep will also get less sleep deprived, and vice-versa.

                    // Note: Since needs are updated in 5-minute increments, we have to multiply the roll again by
                    // 5. If rate_multiplier is > 1, fatigue_roll will be higher and this will work out.
                    mod_sleep_deprivation( fatigue_roll * 5 );
                }
            }

            if( npc_no_food && get_fatigue() > TIRED ) {
                set_fatigue( TIRED );
                set_sleep_deprivation( 0 );
            }
        }
    } else if( asleep ) {
        if( rates.recovery > 0.0f ) {
            int recovered = divide_roll_remainder( rates.recovery * rate_multiplier, 1.0 );
            if( get_fatigue() - recovered < -20 ) {
                // Should be wake up, but that could prevent some retroactive regeneration
                sleep.set_duration( 1_turns );
                mod_fatigue( -25 );
            } else {
                mod_fatigue( -recovered );
                if( get_option< bool >( "SLEEP_DEPRIVATION" ) ) {
                    // Sleeping on the ground, no bionic = 1x rest_modifier
                    // Sleeping on a bed, no bionic      = 2x rest_modifier
                    // Sleeping on a comfy bed, no bionic= 3x rest_modifier

                    // Sleeping on the ground, bionic    = 3x rest_modifier
                    // Sleeping on a bed, bionic         = 6x rest_modifier
                    // Sleeping on a comfy bed, bionic   = 9x rest_modifier
                    float rest_modifier = ( has_active_bionic( bio_synaptic_regen ) ? 3 : 1 );
                    // Magnesium supplements also add a flat bonus to recovery speed
                    if( has_effect( effect_magnesium_supplements ) ) {
                        rest_modifier += 1;
                    }

                    comfort_level comfort = base_comfort_value( pos() );

                    if( comfort >= comfort_level::very_comfortable ) {
                        rest_modifier *= 3;
                    }
                    else  if( comfort >= comfort_level::comfortable ) {
                        rest_modifier *= 2.5;
                    }
                    else if( comfort >= comfort_level::slightly_comfortable ) {
                        rest_modifier *= 2;
                    }

                    // If we're just tired, we'll get a decent boost to our sleep quality.
                    // The opposite is true for very tired characters.
                    if( get_fatigue() < DEAD_TIRED ) {
                        rest_modifier += 2;
                    }
                    else if( get_fatigue() >= EXHAUSTED ) {
                        rest_modifier = ( rest_modifier > 2 ) ? rest_modifier - 2 : 1;
                    }

                    // Recovered is multiplied by 2 as well, since we spend 1/3 of the day sleeping
                    mod_sleep_deprivation( -rest_modifier * ( recovered * 2 ) );
                }
            }
        }
    }
    if( is_player() && wasnt_fatigued && get_fatigue() > DEAD_TIRED && !lying ) {
        if( !activity ) {
            add_msg_if_player( m_warning, _( "You're feeling tired.  %s to lie down for sleep." ),
                               press_x( ACTION_SLEEP ) );
        } else {
            g->cancel_activity_query( _( "You're feeling tired." ) );
        }
    }

    if( stim < 0 ) {
        stim = std::min( stim + rate_multiplier, 0 );
    } else if( stim > 0 ) {
        stim = std::max( stim - rate_multiplier, 0 );
    }

    if( get_painkiller() > 0 ) {
        mod_painkiller( -std::min( get_painkiller(), rate_multiplier ) );
    }

    if( g->is_in_sunlight( pos() ) ) {
        if( has_bionic( bn_bio_solar ) ) {
            charge_power( rate_multiplier * 25 );
        }
        if( has_active_bionic( bionic_id( "bio_cable" ) ) ) {
            if( is_wearing( "solarpack_on" ) ) {
                charge_power( rate_multiplier * 25 );
            }
            if( is_wearing( "q_solarpack_on" ) ) {
                charge_power( rate_multiplier * 50 );
            }
        }
    }

    // Huge folks take penalties for cramming themselves in vehicles
    if( in_vehicle && ( has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK ) ) ) {
        add_msg_if_player( m_bad,
                           _( "You're cramping up from stuffing yourself in this vehicle." ) );
        if( is_npc() ) {
             npc &as_npc = dynamic_cast<npc &>( *this );
             as_npc.complain_about( "cramped_vehicle", 1_hours, "<cramped_vehicle>", false );
        }
        mod_pain_noresist( 2 * rng( 2, 3 ) );
        focus_pool -= 1;
    }

    int dec_stom_food = static_cast<int>( get_stomach_food() * 0.2 );
    int dec_stom_water = static_cast<int>( get_stomach_water() * 0.2 );
    dec_stom_food = dec_stom_food < 10 ? 10 : dec_stom_food;
    dec_stom_water = dec_stom_water < 10 ? 10 : dec_stom_water;
    mod_stomach_food( -dec_stom_food );
    mod_stomach_water( -dec_stom_water );
}

void player::regen( int rate_multiplier )
{
    int pain_ticks = rate_multiplier;
    while( get_pain() > 0 && pain_ticks-- > 0 ) {
        mod_pain( -roll_remainder( 0.2f + get_pain() / 50.0f ) );
    }

    float rest = rest_quality();
    float heal_rate = healing_rate( rest ) * MINUTES(5);
    if( heal_rate > 0.0f ) {
        healall( roll_remainder( rate_multiplier * heal_rate ) );
    } else if( heal_rate < 0.0f ) {
        int rot_rate = roll_remainder( rate_multiplier * -heal_rate );
        // Has to be in loop because some effects depend on rounding
        while( rot_rate-- > 0 ) {
            hurtall( 1, nullptr, false );
        }
    }

    // include healing effects
    for( int i = 0; i < num_hp_parts; i++ ) {
        body_part bp = hp_to_bp( static_cast<hp_part>( i ) );
        float healing = healing_rate_medicine( rest, bp ) * MINUTES( 5 ) ;

        int healing_apply = roll_remainder( healing );
        heal( bp, healing_apply );
        if( damage_bandaged[i] > 0 ) {
            damage_bandaged[i] -= healing_apply;
            if( damage_bandaged[i] <= 0 ) {
                damage_bandaged[i] = 0;
                remove_effect( effect_bandaged, bp );
                add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) );
            }
        }
        if( damage_disinfected[i] > 0 ) {
            damage_disinfected[i] -= healing_apply;
            if( damage_disinfected[i] <= 0 ) {
                damage_disinfected[i] = 0;
                remove_effect( effect_disinfected, bp );
                add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) );
            }
        }

        // remove effects if the limb was healed by other way
        if( has_effect( effect_bandaged, bp ) && ( hp_cur[i] == hp_max[i] ) ) {
            damage_bandaged[i] = 0;
            remove_effect( effect_bandaged, bp );
            add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) );
        }
        if( has_effect( effect_disinfected, bp ) && ( hp_cur[i] == hp_max[i] ) ) {
            damage_disinfected[i] = 0;
            remove_effect( effect_disinfected, bp );
            add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) );
        }
    }

    if( radiation > 0 ) {
        radiation = std::max( 0, radiation - roll_remainder( rate_multiplier / 50.0f ) );
    }
}

void player::update_stamina( int turns )
{
    float stamina_recovery = 0.0f;
    // Recover some stamina every turn.
    // Mutated stamina works even when winded
    float stamina_multiplier = ( !has_effect( effect_winded ) ? 1.0f : 0.0f ) +
                               mutation_value( "stamina_regen_modifier" );
    if( stamina_multiplier > 0.0f ) {
        // But mouth encumbrance interferes, even with mutated stamina.
        stamina_recovery += stamina_multiplier * std::max( 1.0f, 10.0f - ( encumb( bp_mouth ) / 10.0f ) );
        // TODO: recovering stamina causes hunger/thirst/fatigue.
        // TODO: Tiredness slowing recovery
    }

    // stim recovers stamina (or impairs recovery)
    if( stim > 0 ) {
        // TODO: Make stamina recovery with stims cost health
        stamina_recovery += std::min( 5.0f, stim / 20.0f );
    } else if( stim < 0 ) {
        // Affect it less near 0 and more near full
        // Stamina maxes out around 1000, stims kill at -200
        // At -100 stim fully counters regular regeneration at 500 stamina,
        // halves at 250 stamina, cuts by 25% at 125 stamina
        // At -50 stim fully counters at 1000, halves at 500
        stamina_recovery += stim * stamina / 1000.0f / 5.0f;
    }

    const int max_stam = get_stamina_max();
    if( power_level >= 3 && has_active_bionic( bio_gills ) ) {
        int bonus = std::min<int>( power_level / 3, max_stam - stamina - stamina_recovery * turns );
        bonus = std::min( bonus, 3 );
        if( bonus > 0 ) {
            charge_power( -3 * bonus );
            stamina_recovery += bonus;
        }
    }

    stamina = roll_remainder( stamina + stamina_recovery * turns );

    // Cap at max
    stamina = std::min( std::max( stamina, 0 ), max_stam );
}

bool player::is_hibernating() const
{
    // Hibernating only kicks in whilst Engorged; separate tracking for hunger/thirst here
    // as a safety catch.  One test subject managed to get two Colds during hibernation;
    // since those add fatigue and dry out the character, the subject went for the full 10 days plus
    // a little, and came out of it well into Parched.  Hibernating shouldn't endanger your
    // life like that--but since there's much less fluid reserve than food reserve,
    // simply using the same numbers won't work.
    return has_effect( effect_sleep ) && get_hunger() <= -60 && get_thirst() <= 80 && has_active_mutation( trait_id( "HIBERNATE" ) );
}

void player::add_addiction(add_type type, int strength)
{
    if( type == ADD_NULL ) {
        return;
    }
    time_duration timer = 2_hours;
    if( has_trait( trait_ADDICTIVE ) ) {
        strength *= 2;
        timer = 1_hours;
    } else if( has_trait( trait_NONADDICTIVE ) ) {
        strength /= 2;
        timer = 6_hours;
    }
    //Update existing addiction
    for( auto &i : addictions ) {
        if( i.type != type ) {
            continue;
        }

        if( i.sated < 0_turns ) {
            i.sated = timer;
        } else if( i.sated < 10_minutes ) {
            i.sated += timer; // TODO: Make this variable?
        } else {
            i.sated += timer / 2;
        }
        if( i.intensity < MAX_ADDICTION_LEVEL && strength > i.intensity * rng( 2, 5 ) ) {
            i.intensity++;
        }

        add_msg( m_debug, "Updating addiction: %d intensity, %d sated",
                 i.intensity, to_turns<int>( i.sated ) );

        return;
    }

    // Add a new addiction
    const int roll = rng( 0, 100 );
    add_msg( m_debug, "Addiction: roll %d vs strength %d", roll, strength );
    if( roll < strength ) {
        //~ %s is addiction name
        const std::string &type_name = addiction_type_name( type );
        add_memorial_log( pgettext("memorial_male", "Became addicted to %s."),
                          pgettext("memorial_female", "Became addicted to %s."),
                          type_name.c_str() );
        add_msg( m_debug, "%s got addicted to %s", disp_name().c_str(), type_name.c_str() );
        addictions.emplace_back( type, 1 );
    }
}

bool player::has_addiction(add_type type) const
{
    return std::any_of( addictions.begin(), addictions.end(),
        [type]( const addiction &ad ) {
        return ad.type == type && ad.intensity >= MIN_ADDICTION_LEVEL;
    } );
}

void player::rem_addiction( add_type type )
{
    auto iter = std::find_if( addictions.begin(), addictions.end(),
        [type]( const addiction &ad ) {
        return ad.type == type;
    } );

    if( iter != addictions.end() ) {
        //~ %s is addiction name
        add_memorial_log(pgettext("memorial_male", "Overcame addiction to %s."),
                    pgettext("memorial_female", "Overcame addiction to %s."),
                    addiction_type_name(type).c_str());
        addictions.erase( iter );
    }
}

int player::addiction_level( add_type type ) const
{
    auto iter = std::find_if( addictions.begin(), addictions.end(),
        [type]( const addiction &ad ) {
        return ad.type == type;
    } );
    return iter != addictions.end() ? iter->intensity : 0;
}

void player::siphon( vehicle &veh, const itype_id &type )
{
    auto qty = veh.fuel_left( type );
    if( qty <= 0 ) {
        add_msg( m_bad, _( "There is not enough %s left to siphon it." ), item::nname( type ).c_str() );
        return;
    }

    item liquid( type, calendar::turn, qty );
    if( g->handle_liquid( liquid, nullptr, 1, nullptr, &veh ) ) {
        veh.drain( type, qty - liquid.charges );
    }
}

void player::cough(bool harmful, int loudness)
{
    if( harmful ) {
        const int stam = stamina;
        mod_stat( "stamina", -100 );
        if( stam < 100 && x_in_y( 100 - stam, 100 ) ) {
            apply_damage( nullptr, bp_torso, 1 );
        }
    }

    if( has_effect( effect_cough_suppress ) ) {
        return;
    }

    if( !is_npc() ) {
        add_msg(m_bad, _("You cough heavily."));
    }
    sounds::sound( pos(), loudness, sounds::sound_t::speech, _( "a hacking cough." ) );

    moves -= 80;

    if( has_effect( effect_sleep ) && !has_effect( effect_narcosis ) &&
        ( ( harmful && one_in( 3 ) ) || one_in( 10 ) ) ) {
        wake_up();
    }
}

void player::add_pain_msg(int val, body_part bp) const
{
    if (has_trait( trait_NOPAIN )) {
        return;
    }
    if (bp == num_bp) {
        if (val > 20) {
            add_msg_if_player(_("Your body is wracked with excruciating pain!"));
        } else if (val > 10) {
            add_msg_if_player(_("Your body is wracked with terrible pain!"));
        } else if (val > 5) {
            add_msg_if_player(_("Your body is wracked with pain!"));
        } else if (val > 1) {
            add_msg_if_player(_("Your body pains you!"));
        } else {
            add_msg_if_player(_("Your body aches."));
        }
    } else {
        if (val > 20) {
            add_msg_if_player(_("Your %s is wracked with excruciating pain!"),
                                body_part_name_accusative(bp).c_str());
        } else if (val > 10) {
            add_msg_if_player(_("Your %s is wracked with terrible pain!"),
                                body_part_name_accusative(bp).c_str());
        } else if (val > 5) {
            add_msg_if_player(_("Your %s is wracked with pain!"),
                                body_part_name_accusative(bp).c_str());
        } else if (val > 1) {
            add_msg_if_player(_("Your %s pains you!"),
                                body_part_name_accusative(bp).c_str());
        } else {
            add_msg_if_player(_("Your %s aches."),
                                body_part_name_accusative(bp).c_str());
        }
    }
}

void player::print_health() const
{
    if( !is_player() ) {
        return;
    }
    int current_health = get_healthy();
    if( has_trait( trait_SELFAWARE ) ) {
        add_msg_if_player( _( "Your current health value is %d." ), current_health );
    }

    if( current_health > 0 &&
        ( has_effect( effect_common_cold ) || has_effect( effect_flu ) ) ) {
        return;
    }

    static const std::map<int, std::string> msg_categories = {
        { -100, "health_horrible" },
        { -50, "health_very_bad" },
        { -10, "health_bad" },
        { 10, "" },
        { 50, "health_good" },
        { 100, "health_very_good" },
        { INT_MAX, "health_great" }
    };

    auto iter = msg_categories.lower_bound( current_health );
    if( iter != msg_categories.end() && !iter->second.empty() ) {
        const std::string &msg = SNIPPET.random_from_category( iter->second );
        add_msg_if_player( current_health > 0 ? m_good : m_bad, msg.c_str() );
    }
}

void player::process_one_effect( effect &it, bool is_new )
{
    bool reduced = resists_effect(it);
    double mod = 1;
    body_part bp = it.get_bp();
    int val = 0;

    // Still hardcoded stuff, do this first since some modify their other traits
    hardcoded_effects(it);

    const auto get_effect = [&it, is_new]( const std::string &arg, bool reduced ) {
        if( is_new ) {
            return it.get_amount( arg, reduced );
        }
        return it.get_mod( arg, reduced );
    };

    // Handle miss messages
    auto msgs = it.get_miss_msgs();
    if (!msgs.empty()) {
        for (auto i : msgs) {
            add_miss_reason(_(i.first.c_str()), unsigned(i.second));
        }
    }

    // Handle health mod
    val = get_effect("H_MOD", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "H_MOD", val, reduced, mod ) ) {
            int bounded = bound_mod_to_vals(
                    get_healthy_mod(), val, it.get_max_val("H_MOD", reduced),
                    it.get_min_val("H_MOD", reduced));
            // This already applies bounds, so we pass them through.
            mod_healthy_mod(bounded, get_healthy_mod() + bounded);
        }
    }

    // Handle health
    val = get_effect("HEALTH", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "HEALTH", val, reduced, mod ) ) {
            mod_healthy(bound_mod_to_vals(get_healthy(), val,
                        it.get_max_val("HEALTH", reduced), it.get_min_val("HEALTH", reduced)));
        }
    }

    // Handle stim
    val = get_effect("STIM", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "STIM", val, reduced, mod ) ) {
            stim += bound_mod_to_vals(stim, val, it.get_max_val("STIM", reduced),
                                        it.get_min_val("STIM", reduced));
        }
    }

    // Handle hunger
    val = get_effect("HUNGER", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "HUNGER", val, reduced, mod ) ) {
            mod_hunger(bound_mod_to_vals(get_hunger(), val, it.get_max_val("HUNGER", reduced),
                                        it.get_min_val("HUNGER", reduced)));
        }
    }

    // Handle starvation
    val = get_effect("STARVATION", reduced);
    if(val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "STARVATION", val, reduced, mod ) ) {
            mod_starvation(bound_mod_to_vals(get_starvation(), val, it.get_max_val("STARVATION", reduced),
                                        it.get_min_val("STARVATION", reduced)));
        }
    }

    // Handle thirst
    val = get_effect("THIRST", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "THIRST", val, reduced, mod ) ) {
            mod_thirst(bound_mod_to_vals(get_thirst(), val, it.get_max_val("THIRST", reduced),
                                        it.get_min_val("THIRST", reduced)));
        }
    }

    // Handle fatigue
    val = get_effect("FATIGUE", reduced);
    // Prevent ongoing fatigue effects while asleep.
    // These are meant to change how fast you get tired, not how long you sleep.
    if (val != 0 && !in_sleep_state()) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "FATIGUE", val, reduced, mod ) ) {
            mod_fatigue(bound_mod_to_vals(get_fatigue(), val, it.get_max_val("FATIGUE", reduced),
                                        it.get_min_val("FATIGUE", reduced)));
        }
    }

    // Handle Radiation
    val = get_effect("RAD", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "RAD", val, reduced, mod ) ) {
            radiation += bound_mod_to_vals(radiation, val, it.get_max_val("RAD", reduced), 0);
            // Radiation can't go negative
            if (radiation < 0) {
                radiation = 0;
            }
        }
    }

    // Handle Pain
    val = get_effect("PAIN", reduced);
    if (val != 0) {
        mod = 1;
        if (it.get_sizing("PAIN")) {
            if (has_trait( trait_FAT )) {
                mod *= 1.5;
            }
            if (has_trait( trait_LARGE ) || has_trait( trait_LARGE_OK )) {
                mod *= 2;
            }
            if (has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK )) {
                mod *= 3;
            }
        }
        if( is_new || it.activated( calendar::turn, "PAIN", val, reduced, mod ) ) {
            int pain_inc = bound_mod_to_vals(get_pain(), val, it.get_max_val("PAIN", reduced), 0);
            mod_pain(pain_inc);
            if (pain_inc > 0) {
                add_pain_msg(val, bp);
            }
        }
    }

    // Handle Damage
    val = get_effect("HURT", reduced);
    if (val != 0) {
        mod = 1;
        if (it.get_sizing("HURT")) {
            if (has_trait( trait_FAT )) {
                mod *= 1.5;
            }
            if (has_trait( trait_LARGE ) || has_trait( trait_LARGE_OK )) {
                mod *= 2;
            }
            if (has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK )) {
                mod *= 3;
            }
        }
        if( is_new || it.activated( calendar::turn, "HURT", val, reduced, mod ) ) {
            if (bp == num_bp) {
                if (val > 5) {
                    add_msg_if_player(_("Your %s HURTS!"), body_part_name_accusative(bp_torso).c_str());
                } else {
                    add_msg_if_player(_("Your %s hurts!"), body_part_name_accusative(bp_torso).c_str());
                }
                apply_damage(nullptr, bp_torso, val);
            } else {
                if (val > 5) {
                    add_msg_if_player(_("Your %s HURTS!"), body_part_name_accusative(bp).c_str());
                } else {
                    add_msg_if_player(_("Your %s hurts!"), body_part_name_accusative(bp).c_str());
                }
                apply_damage(nullptr, bp, val);
            }
        }
    }

    // Handle Sleep
    val = get_effect("SLEEP", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "SLEEP", val, reduced, mod ) ) {
            add_msg_if_player(_("You pass out!"));
            fall_asleep( time_duration::from_turns( val ) );
        }
    }

    // Handle painkillers
    val = get_effect("PKILL", reduced);
    if (val != 0) {
        mod = it.get_addict_mod("PKILL", addiction_level(ADD_PKILLER));
        if( is_new || it.activated( calendar::turn, "PKILL", val, reduced, mod ) ) {
            mod_painkiller(bound_mod_to_vals(pkill, val, it.get_max_val("PKILL", reduced), 0));
        }
    }

    // Handle coughing
    mod = 1;
    val = 0;
    if( it.activated( calendar::turn, "COUGH", val, reduced, mod ) ) {
        cough(it.get_harmful_cough());
    }

    // Handle vomiting
    mod = vomit_mod();
    val = 0;
    if( it.activated( calendar::turn, "VOMIT", val, reduced, mod ) ) {
        vomit();
    }

    // Handle stamina
    val = get_effect("STAMINA", reduced);
    if (val != 0) {
        mod = 1;
        if( is_new || it.activated( calendar::turn, "STAMINA", val, reduced, mod ) ) {
            stamina += bound_mod_to_vals( stamina, val,
                                          it.get_max_val("STAMINA", reduced),
                                          it.get_min_val("STAMINA", reduced) );
            if( stamina < 0 ) {
                // TODO: Make it drain fatigue and/or oxygen?
                stamina = 0;
            } else if( stamina > get_stamina_max() ) {
                stamina = get_stamina_max();
            }
        }
    }

    // Speed and stats are handled in recalc_speed_bonus and reset_stats respectively
}

void player::process_effects() {
    //Special Removals
    if (has_effect( effect_darkness ) && g->is_in_sunlight(pos())) {
        remove_effect( effect_darkness );
    }
    if (has_trait( trait_M_IMMUNE ) && has_effect( effect_fungus )) {
        vomit();
        remove_effect( effect_fungus );
        add_msg_if_player(m_bad,  _("We have mistakenly colonized a local guide!  Purging now."));
    }
    if (has_trait( trait_PARAIMMUNE ) && (has_effect( effect_dermatik ) || has_effect( effect_tapeworm ) ||
          has_effect( effect_bloodworms ) || has_effect( effect_brainworms ) || has_effect( effect_paincysts )) ) {
        remove_effect( effect_dermatik );
        remove_effect( effect_tapeworm );
        remove_effect( effect_bloodworms );
        remove_effect( effect_brainworms );
        remove_effect( effect_paincysts );
        add_msg_if_player(m_good, _("Something writhes and inside of you as it dies."));
    }
    if (has_trait( trait_ACIDBLOOD ) && (has_effect( effect_dermatik ) || has_effect( effect_bloodworms ) ||
          has_effect( effect_brainworms ))) {
        remove_effect( effect_dermatik );
        remove_effect( effect_bloodworms );
        remove_effect( effect_brainworms );
    }
    if (has_trait( trait_EATHEALTH ) && has_effect( effect_tapeworm ) ) {
        remove_effect( effect_tapeworm );
        add_msg_if_player(m_good, _("Your bowels gurgle as something inside them dies."));
    }
    if (has_trait( trait_INFIMMUNE ) && (has_effect( effect_bite ) || has_effect( effect_infected ) ||
          has_effect( effect_recover ) ) ) {
        remove_effect( effect_bite );
        remove_effect( effect_infected );
        remove_effect( effect_recover );
    }
    if (!(in_sleep_state()) && has_effect( effect_alarm_clock ) ) {
        remove_effect( effect_alarm_clock );
    }

    //Human only effects
    for( auto &elem : *effects ) {
        for( auto &_effect_it : elem.second ) {
            process_one_effect( _effect_it.second, false );
        }
    }

    Creature::process_effects();
}

double player::vomit_mod()
{
    double mod = 1;
    if (has_effect( effect_weed_high )) {
        mod *= .1;
    }
    if (has_trait( trait_STRONGSTOMACH )) {
        mod *= .5;
    }
    if (has_trait( trait_WEAKSTOMACH )) {
        mod *= 2;
    }
    if (has_trait( trait_NAUSEA )) {
        mod *= 3;
    }
    if (has_trait( trait_VOMITOUS )) {
        mod *= 3;
    }
    // If you're already nauseous, any food in your stomach greatly
    // increases chance of vomiting. Liquids don't provoke vomiting, though.
    if( get_stomach_food() != 0 && has_effect( effect_nausea ) ) {
        mod *= 5 * get_effect_int( effect_nausea );
    }
    return mod;
}

void player::suffer()
{
    // @todo: Remove this section and encapsulate hp_cur
    for( int i = 0; i < num_hp_parts; i++ ) {
        body_part bp = hp_to_bp( static_cast<hp_part>( i ) );
        if( hp_cur[i] <= 0 ) {
            add_effect( effect_disabled, 1_turns, bp, true );
        }
    }

    for (size_t i = 0; i < my_bionics->size(); i++) {
        if ((*my_bionics)[i].powered) {
            process_bionic(i);
        }
    }

    for( auto &mut : my_mutations ) {
        auto &tdata = mut.second;
        if (!tdata.powered ) {
            continue;
        }
        const auto &mdata = mut.first.obj();
        if (tdata.powered && tdata.charge > 0) {
        // Already-on units just lose a bit of charge
        tdata.charge--;
        } else {
            // Not-on units, or those with zero charge, have to pay the power cost
            if (mdata.cooldown > 0) {
                tdata.powered = true;
                tdata.charge = mdata.cooldown - 1;
            }
            if (mdata.hunger){
                mod_hunger(mdata.cost);
                if (get_hunger() >= 700) { // Well into Famished
                    add_msg_if_player( m_warning, _( "You're too famished to keep your %s going." ), mdata.name() );
                    tdata.powered = false;
                }
            }
            if (mdata.thirst){
                mod_thirst(mdata.cost);
                if (get_thirst() >= 260) { // Well into Dehydrated
                    add_msg_if_player( m_warning, _( "You're too dehydrated to keep your %s going." ), mdata.name() );
                    tdata.powered = false;
                }
            }
            if (mdata.fatigue){
                mod_fatigue(mdata.cost);
                if (get_fatigue() >= EXHAUSTED) { // Exhausted
                    add_msg_if_player( m_warning, _( "You're too exhausted to keep your %s going." ), mdata.name() );
                    tdata.powered = false;
                }
            }

            if( !tdata.powered ) {
                apply_mods(mut.first, false);
            }
        }
    }

    if (underwater) {
        if (!has_trait( trait_GILLS ) && !has_trait( trait_GILLS_CEPH )) {
            oxygen--;
        }
        if (oxygen < 12 && worn_with_flag("REBREATHER")) {
                oxygen += 12;
            }
        if (oxygen <= 5) {
            if (has_bionic( bio_gills ) && power_level >= 25) {
                oxygen += 5;
                charge_power(-25);
            } else {
                add_msg_if_player(m_bad, _("You're drowning!"));
                apply_damage( nullptr, bp_torso, rng( 1, 4 ) );
            }
        }
        if( has_trait( trait_FRESHWATEROSMOSIS ) && !g->m.has_flag_ter( "SALT_WATER", pos() ) && get_thirst() > -60 ) {
            mod_thirst( -1 );
        }
    }

    if( has_trait( trait_SHARKTEETH ) && one_in( 14400 ) ) {
        add_msg_if_player( m_neutral, _( "You shed a tooth!" ) );
        g->m.spawn_item( pos(), "bone", 1 );
    }

    if( has_active_mutation( trait_id( "WINGS_INSECT" ) ) ) {
        //~Sound of buzzing Insect Wings
        sounds::sound( pos(), 10, sounds::sound_t::movement, _("BZZZZZ"));
    }

    bool wearing_shoes = is_wearing_shoes( side::LEFT ) || is_wearing_shoes( side::RIGHT );
    if( has_trait( trait_ROOTS3 ) && g->m.has_flag( "PLOWABLE", pos() ) && !wearing_shoes ) {
        if (one_in(100)) {
            add_msg_if_player(m_good, _("This soil is delicious!"));
            if (get_hunger() > -20) {
                mod_hunger(-2);
            }
            if (get_thirst() > -20) {
                mod_thirst(-2);
            }
            mod_healthy_mod(10, 50);
            // No losing oneself in the fertile embrace of rich
            // New England loam.  But it can be a near thing.
            /** @EFFECT_INT decreases chance of losing focus points while eating soil with ROOTS3 */
            if ( (one_in(int_cur)) && (focus_pool >= 25) ) {
                focus_pool--;
            }
        } else if (one_in(50)){
            if (get_hunger() > -20) {
                mod_hunger(-1);
            }
            if (get_thirst() > -20) {
                mod_thirst(-1);
            }
            mod_healthy_mod(5, 50);
        }
    }

    if( !in_sleep_state() ) {
        if ( !has_trait( trait_id( "DEBUG_STORAGE" ) ) && ( weight_carried() > 4 * weight_capacity() ) ) {
            if( has_effect( effect_downed ) ) {
                add_effect( effect_downed, 1_turns, num_bp, false, 0, true );
            } else {
                add_effect( effect_downed, 2_turns, num_bp, false, 0, true );
            }
        }
        time_duration timer = -6_hours;
        if( has_trait( trait_ADDICTIVE ) ) {
            timer = -10_hours;
        } else if( has_trait( trait_NONADDICTIVE ) ) {
            timer = -3_hours;
        }
        for( auto &cur_addiction : addictions ) {
            if( cur_addiction.sated <= 0_turns &&
                cur_addiction.intensity >= MIN_ADDICTION_LEVEL ) {
                addict_effect( *this, cur_addiction );
            }
            cur_addiction.sated -= 1_turns;
            // Higher intensity addictions heal faster
            if( cur_addiction.sated - 10_minutes * cur_addiction.intensity < timer ) {
                if( cur_addiction.intensity <= 2 ) {
                    rem_addiction( cur_addiction.type );
                    break;
                } else {
                    cur_addiction.intensity--;
                    cur_addiction.sated = 0_turns;
                }
            }
        }
        if( has_trait( trait_CHEMIMBALANCE ) ) {
            if( one_in( 3600 ) && !has_trait( trait_NOPAIN ) ) {
                add_msg_if_player( m_bad, _( "You suddenly feel sharp pain for no reason." ) );
                mod_pain( 3 * rng( 1, 3 ) );
            }
            if( one_in( 3600 ) ) {
                int pkilladd = 5 * rng( -1, 2 );
                if( pkilladd > 0 ) {
                    add_msg_if_player(m_bad, _("You suddenly feel numb."));
                } else if ((pkilladd < 0) && (!(has_trait( trait_NOPAIN )))) {
                    add_msg_if_player(m_bad, _("You suddenly ache."));
                }
                mod_painkiller(pkilladd);
            }
            if (one_in(3600)) {
                add_msg_if_player(m_bad, _("You feel dizzy for a moment."));
                moves -= rng(10, 30);
            }
            if (one_in(3600)) {
                int hungadd = 5 * rng(-1, 3);
                if (hungadd > 0) {
                    add_msg_if_player(m_bad, _("You suddenly feel hungry."));
                } else {
                    add_msg_if_player(m_good, _("You suddenly feel a little full."));
                }
                mod_hunger(hungadd);
            }
            if (one_in(3600)) {
                add_msg_if_player(m_bad, _("You suddenly feel thirsty."));
                mod_thirst(5 * rng(1, 3));
            }
            if (one_in(3600)) {
                add_msg_if_player(m_good, _("You feel fatigued all of a sudden."));
                mod_fatigue(10 * rng(2, 4));
            }
            if (one_in(4800)) {
                if (one_in(3)) {
                    add_morale(MORALE_FEELING_GOOD, 20, 100);
                } else {
                    add_morale(MORALE_FEELING_BAD, -20, -100);
                }
            }
            if (one_in(3600)) {
                if (one_in(3)) {
                    add_msg_if_player(m_bad, _("You suddenly feel very cold."));
                    temp_cur.fill( BODYTEMP_VERY_COLD );
                } else {
                    add_msg_if_player(m_bad, _("You suddenly feel cold."));
                    temp_cur.fill( BODYTEMP_COLD );
                }
            }
            if (one_in(3600)) {
                if (one_in(3)) {
                    add_msg_if_player(m_bad, _("You suddenly feel very hot."));
                    temp_cur.fill( BODYTEMP_VERY_HOT );
                } else {
                    add_msg_if_player(m_bad, _("You suddenly feel hot."));
                    temp_cur.fill( BODYTEMP_HOT );
                }
            }
        }
        if( ( has_trait( trait_SCHIZOPHRENIC ) || has_artifact_with( AEP_SCHIZO ) ) )
        {
            if( is_player() ) {
                bool done_effect = false;
                // Sound
                if( one_in( to_turns<int>( 4_hours ) ) ) {
                    sound_hallu();
                }

                // Follower turns hostile
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    std::vector<std::shared_ptr<npc>> followers = overmap_buffer.get_npcs_near_player( 12 );

                    std::string who_gets_angry = name;
                    if( !followers.empty() ) {
                        who_gets_angry = random_entry_ref( followers )->name;
                    }
                    add_msg( m_bad, _( "%1$s gets angry!" ), who_gets_angry );
                    done_effect = true;
                }

                // Monster dies
                if( !done_effect && one_in( to_turns<int>( 6_hours ) ) ) {

                    // TODO: move to monster group json
                    static const mtype_id mon_zombie( "mon_zombie" );
                    static const mtype_id mon_zombie_fat( "mon_zombie_fat" );
                    static const mtype_id mon_zombie_fireman( "mon_zombie_fireman" );
                    static const mtype_id mon_zombie_cop( "mon_zombie_cop" );
                    static const mtype_id mon_zombie_soldier( "mon_zombie_soldier" );
                    static const std::array<mtype_id, 5> monsters = { {
                            mon_zombie, mon_zombie_fat, mon_zombie_fireman, mon_zombie_cop, mon_zombie_soldier
                        }
                    };
                    add_msg( _( "%s dies!" ), random_entry_ref( monsters )->nname() );
                    done_effect = true;
                }

                // Limb Breaks
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    add_msg( m_bad, _( "Your limb breaks!" ) );
                    done_effect = true;
                }

                // NPC chat
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    std::string i_name = Name::generate( one_in( 2 ) );

                    std::string i_talk = SNIPPET.random_from_category( "<lets_talk>" );
                    parse_tags( i_talk, *this, *this );

                    add_msg( _( "%1$s says: \"%2$s\"" ), i_name, i_talk );
                    done_effect = true;
                }

                // Skill raise
                if( !done_effect && one_in( to_turns<int>( 12_hours ) ) ) {
                    skill_id raised_skill = Skill::random_skill();
                    add_msg( m_good, _( "You increase %1$s to level %2$d" ), raised_skill.c_str(), get_skill_level( raised_skill ) + 1);
                    done_effect = true;
                }

                // Talk to self
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    std::vector<std::string> talk_s{ _( "Hey, can you hear me?" ),
                                                     _( "Don't touch me." ),
                                                     _( "What's your name?" ),
                                                     _( "I thought you were my friend." ),
                                                     _( "How are you today?" ),
                                                     _( "Shut up! Don't lie to me." ),
                                                     _( "Why would you do that?" ),
                                                     _( "Please, don't go." ),
                                                     _( "Don't leave me alone!" ),
                                                     _( "Yeah, sure." ),
                                                     _( "No way, man." ),
                                                     _( "Do you really think so?" ),
                                                     _( "Is it really time for that?" ),
                                                     _( "Sorry, I can't hear you." ),
                                                     _( "You've told me already." ),
                                                     _( "I know!" ),
                                                     _( "Why are you following me?" ),
                                                     _( "This place is dangerous, you shouldn't be here." ),
                                                     _( "What are you doing out here?" ),
                                                     _( "That's not true, is it?" ),
                                                     _( "Are you hurt?" ) };

                    add_msg( _( "%1$s says: \"%2$s\"" ), name, random_entry_ref( talk_s ) );
                    done_effect = true;
                }

                // Talking weapon
                if( !done_effect && !weapon.is_null() ) {
                    // If player has a weapon, picks a message from said weapon
                    // Weapon tells player to kill a monster if any are nearby
                    // Weapon is concerned for player if bleeding
                    // Weapon is concerned for itself if damaged
                    // Otherwise random chit-chat

                    std::string i_name_w = weapon.has_var( "item_label" ) ?
                                           weapon.get_var( "item_label" ) :
                                           _( "Your " ) + weapon.type_name();

                    std::vector<std::weak_ptr<monster>> mons = g->all_monsters().items;

                    std::string i_talk_w;
                    bool does_talk = false;
                    if( !mons.empty() &&
                        one_in( to_turns<int>( 12_minutes ) ) ) {
                        std::vector<std::string> mon_near{ _( "Hey, let's go kill that %1$s!" ),
                                                           _( "Did you see that %1$s!?" ),
                                                           _( "I want to kill that %1$s!" ),
                                                           _( "Let me kill that %1$s!" ),
                                                           _( "Hey, I need to kill that %1$s!" ),
                                                           _( "I want to watch that %1$s bleed!" ),
                                                           _( "Wait, that %1$s needs to die!" ),
                                                           _( "Go kill that %1$s!" ),
                                                           _( "Look at that %1$s!" ),
                                                           _( "That %1$s doesn't deserve to live!" ) };
                        std::string talk_w = random_entry_ref( mon_near );
                        std::vector<std::string> seen_mons;
                        for( auto &n : mons ) {
                            if( sees( *n.lock() ) ) {
                                seen_mons.emplace_back( n.lock()->get_name() );
                            }
                        }
                        if( !seen_mons.empty() ) {
                            i_talk_w = string_format( talk_w, random_entry_ref( seen_mons ) );
                            does_talk = true;
                        }
                    } else if( has_effect( effect_bleed ) &&
                               one_in( to_turns<int>( 5_minutes ) ) ) {
                        std::vector<std::string> bleeding{ _( "Hey, you're bleeding." ),
                                                           _( "Your wound looks pretty bad." ),
                                                           _( "Shouldn't you put a bandage on that?" ),
                                                           _( "Please don't die! No one else lets me kill things!" ),
                                                           _( "You look hurt, did I do that?" ),
                                                           _( "Are you supposed to be bleeding?" ),
                                                           _( "You're not going to die, are you?" ),
                                                           _( "Kill a few more before you bleed out!" ) };
                        i_talk_w = random_entry_ref( bleeding );
                        does_talk = true;
                    } else if( weapon.damage() >= weapon.max_damage() / 3 &&
                               one_in( to_turns<int>( 1_hours ) ) ) {
                        std::vector<std::string> damaged{ _( "Hey fix me up." ),
                                                          _( "I need healing!" ),
                                                          _( "I hurt all over..." ),
                                                          _( "You can put me back together, right?" ),
                                                          _( "I... I can't move my legs!" ),
                                                          _( "Medic!" ),
                                                          _( "I can still fight, don't replace me!" ),
                                                          _( "They got me!" ),
                                                          _( "Go on without me..." ),
                                                          _( "Am I gonna die?" ) };
                        i_talk_w = random_entry_ref( damaged );
                        does_talk = true;
                    } else if( one_in( to_turns<int>( 4_hours ) ) ) {
                        std::vector<std::string> misc{ _( "Let me kill something already!" ),
                                                       _( "I'm your best friend, right?" ),
                                                       _( "I love you!" ),
                                                       _( "How are you today?" ),
                                                       _( "Do you think it will rain today?" ),
                                                       _( "Did you hear that?" ),
                                                       _( "Try not to drop me." ),
                                                       _( "How many do you think we've killed?" ),
                                                       _( "I'll keep you safe!" ) };
                        i_talk_w = random_entry_ref( misc );
                        does_talk = true;
                    }
                    if( does_talk ) {
                        add_msg( _( "%1$s says: \"%2$s\"" ), i_name_w, i_talk_w );
                    }
                    done_effect = true;
                }
                // Bad feeling
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    add_msg( m_warning, _( "You get a bad feeling." ) );
                    add_morale( MORALE_FEELING_BAD, -50, -150 );
                    done_effect = true;
                }
                // Formication
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    body_part bp = random_body_part( true );
                    add_effect( effect_formication, 1_hours, bp );
                    done_effect = true;
                }
                // Numbness
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    add_msg( m_bad, _( "You suddenly feel so numb..." ) );
                    mod_painkiller( 25 );
                    done_effect = true;
                }
                // Hallucination
                if( !done_effect && one_in( to_turns<int>( 6_hours ) ) ) {
                    add_effect( effect_hallu, 6_hours );
                    done_effect = true;
                }
                // Visuals
                if( !done_effect && one_in( to_turns<int>( 2_hours ) ) ) {
                    add_effect( effect_visuals, rng( 15_turns, 60_turns ) );
                    done_effect = true;
                }
                // Shaking
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    add_msg( m_bad, _( "You start to shake uncontrollably." ) );
                    add_effect( effect_shakes, rng( 2_minutes, 5_minutes ) );
                    done_effect = true;
                }
                // Shout
                if( !done_effect && one_in( to_turns<int>( 4_hours ) ) ) {
                    std::vector<std::string> shouts{ _( "\"Get away from there!\"" ),
                                                     _( "\"What do you think you're doing?\"" ),
                                                     _( "\"Stop laughing at me!\"" ),
                                                     _( "\"Don't point that thing at me!\"" ),
                                                     _( "\"Stay away from me!\"" ),
                                                     _( "\"No! Stop!\"" ),
                                                     _( "\"Get the fuck away from me!\"" ),
                                                     _( "\"That's not true!\"" ),
                                                     _( "\"What do you want from me?\"" ),
                                                     _( "\"I didn't mean to do it!\"" ),
                                                     _( "\"It wasn't my fault!\"" ),
                                                     _( "\"I had to do it!\"" ),
                                                     _( "\"They made me do it!\"" ),
                                                     _( "\"What are you!?\"" ),
                                                     _( "\"I should never have trusted you!\"" ) };

                    std::string i_shout = random_entry_ref( shouts );
                    shout( "yourself shout, " + i_shout );
                    done_effect = true;
                }
                // Drop weapon
                if( !done_effect && one_in( to_turns<int>( 2_days ) ) ) {
                    if( !weapon.is_null() ) {
                        std::string i_name_w = weapon.has_var( "item_label" ) ?
                                               weapon.get_var( "item_label" ) :
                                               "your " + weapon.type_name();

                        std::vector<std::string> drops{ "%1$s starts burning your hands!",
                                                        "%1$s feels freezing cold!",
                                                        "An electric shock shoots into your hand from %1$s!",
                                                        "%1$s lied to you.",
                                                        "%1$s said something stupid.",
                                                        "%1$s is running away!" };

                        std::string str = string_format( random_entry_ref( drops ), i_name_w );
                        str[0] = toupper( str[0] );

                        add_msg( m_bad, str.c_str() );
                        drop( get_item_position( &weapon ), pos() );
                    }
                    done_effect = true;
                }
            }
        }

        if( ( has_trait( trait_NARCOLEPTIC ) || has_artifact_with( AEP_SCHIZO ) ) ) {
            if( one_in( to_turns<int>( 8_hours ) ) ) {
                add_msg( m_bad, _( "You're suddenly overcome with the urge to sleep and you pass out." ) );
                add_effect( effect_lying_down, 20_minutes );
            }
        }

        if (has_trait( trait_JITTERY ) && !has_effect( effect_shakes )) {
            if (stim > 50 && one_in(300 - stim)) {
                add_effect( effect_shakes, 30_minutes + 1_turns * stim );
            } else if (get_hunger() > 80 && one_in(500 - get_hunger())) {
                add_effect( effect_shakes, 40_minutes );
            }
        }

        if (has_trait( trait_MOODSWINGS ) && one_in(3600)) {
            if (rng(1, 20) > 9) { // 55% chance
                add_morale(MORALE_MOODSWING, -100, -500);
            } else {  // 45% chance
                add_morale(MORALE_MOODSWING, 100, 500);
            }
        }

        if (has_trait( trait_VOMITOUS ) && one_in(4200)) {
            vomit();
        }

        if (has_trait( trait_SHOUT1 ) && one_in(3600)) {
            shout();
        }
        if (has_trait( trait_SHOUT2 ) && one_in(2400)) {
            shout();
        }
        if (has_trait( trait_SHOUT3 ) && one_in(1800)) {
            shout();
        }
        if (has_trait( trait_M_SPORES ) && one_in(2400)) {
            spores();
        }
        if (has_trait( trait_M_BLOSSOMS ) && one_in(1800)) {
            blossoms();
        }
    } // Done with while-awake-only effects

    if( has_trait( trait_ASTHMA ) && one_in( ( 3600 - stim * 50 ) * ( has_effect( effect_sleep ) ? 10 : 1 ) ) &&
        !has_effect( effect_adrenaline ) & !has_effect( effect_datura ) ) {
        bool auto_use = has_charges( "inhaler", 1 );
        bool oxygenator = has_bionic( bio_gills ) && power_level >= 3;
        if ( underwater ) {
            oxygen = oxygen / 2;
            auto_use = false;
        }

        if( in_sleep_state() && !has_effect( effect_narcosis ) ) {
            inventory map_inv;
            map_inv.form_from_map( g->u.pos(), 2 );
            // check if character has an oxygenator first
            if( oxygenator ) {
                add_msg_if_player( m_bad, _( "You have an asthma attack!" ) );
                charge_power( -3 );
                add_msg_if_player( m_info, _( "You use your Oxygenator to clear it up, then go back to sleep." ) );
            } else if ( auto_use ) {
                add_msg_if_player( m_bad, _( "You have an asthma attack!" ) );
                use_charges( "inhaler", 1 );
                add_msg_if_player( m_info, _( "You use your inhaler and go back to sleep." ) );
            // check if an inhaler is somewhere near
            } else if ( map_inv.has_charges( "inhaler", 1 ) ) {
                add_msg_if_player( m_bad, _( "You have an asthma attack!" ) );
                // create new variable to resolve a reference issue
                long amount = 1;
                g->m.use_charges( g->u.pos(), 2, "inhaler", amount );
                add_msg_if_player( m_info, _( "You use your inhaler and go back to sleep." ) );
            } else {
                add_effect( effect_asthma, rng( 5_minutes, 20_minutes ) );
                if( has_effect( effect_sleep ) ) {
                    wake_up();
                } else {
                    g->cancel_activity_or_ignore_query( distraction_type::asthma,  _( "You have an asthma attack!" ) );
                }
            }
        } else if ( auto_use ) {
            use_charges( "inhaler", 1 );
            moves -= 40;
            const auto charges = charges_of( "inhaler" );
            if( charges == 0 ) {
                add_msg_if_player( m_bad, _( "You use your last inhaler charge." ) );
            } else {
                add_msg_if_player( m_info, ngettext( "You use your inhaler, only %d charge left.",
                                                     "You use your inhaler, only %d charges left.", charges ),
                                   charges );
            }
        } else {
            add_effect( effect_asthma, rng( 5_minutes, 20_minutes ) );
            if ( !is_npc() ) {
                g->cancel_activity_or_ignore_query( distraction_type::asthma,  _( "You have an asthma attack!" ) );
            }
        }
    }

    if (has_trait( trait_LEAVES ) && g->is_in_sunlight(pos()) && one_in(600)) {
        mod_hunger(-1);
    }

    if (get_pain() > 0) {
        if (has_trait( trait_PAINREC1 ) && one_in(600)) {
            mod_pain( -1 );
        }
        if (has_trait( trait_PAINREC2 ) && one_in(300)) {
            mod_pain( -1 );
        }
        if (has_trait( trait_PAINREC3 ) && one_in(150)) {
            mod_pain( -1 );
        }
    }

    if( ( has_trait( trait_ALBINO ) || has_effect( effect_datura ) ) &&
        g->is_in_sunlight( pos() ) && one_in(10) ) {
        // Umbrellas can keep the sun off the skin and sunglasses - off the eyes.
        if( !weapon.has_flag( "RAIN_PROTECT" ) ) {
            add_msg_if_player( m_bad, _( "The sunlight is really irritating your skin." ) );
            if( has_effect( effect_sleep ) && !has_effect( effect_narcosis ) ) {
                wake_up();
            }
            if( one_in(10) ) {
                mod_pain(1);
            }
            else focus_pool --;
        }
        if( !( ( (worn_with_flag( "SUN_GLASSES" ) ) || worn_with_flag( "BLIND" ) ) && ( wearing_something_on( bp_eyes ) ) )
            && !has_bionic( bionic_id( "bio_sunglasses" ) ) ) {
            add_msg_if_player( m_bad, _( "The sunlight is really irritating your eyes." ) );
            if( one_in(10) ) {
                mod_pain(1);
            }
            else focus_pool --;
        }
    }

    if (has_trait( trait_SUNBURN ) && g->is_in_sunlight(pos()) && one_in(10)) {
        if( !( weapon.has_flag( "RAIN_PROTECT" ) ) ) {
            add_msg_if_player(m_bad, _("The sunlight burns your skin!"));
        if( has_effect( effect_sleep ) && !has_effect( effect_narcosis ) ) {
            wake_up();
        }
        mod_pain(1);
        hurtall(1, nullptr);
        }
    }

    if((has_trait( trait_TROGLO ) || has_trait( trait_TROGLO2 )) &&
        g->is_in_sunlight(pos()) && g->weather == WEATHER_SUNNY) {
        mod_str_bonus(-1);
        mod_dex_bonus(-1);
        add_miss_reason(_("The sunlight distracts you."), 1);
        mod_int_bonus(-1);
        mod_per_bonus(-1);
    }
    if (has_trait( trait_TROGLO2 ) && g->is_in_sunlight(pos())) {
        mod_str_bonus(-1);
        mod_dex_bonus(-1);
        add_miss_reason(_("The sunlight distracts you."), 1);
        mod_int_bonus(-1);
        mod_per_bonus(-1);
    }
    if (has_trait( trait_TROGLO3 ) && g->is_in_sunlight(pos())) {
        mod_str_bonus(-4);
        mod_dex_bonus(-4);
        add_miss_reason(_("You can't stand the sunlight!"), 4);
        mod_int_bonus(-4);
        mod_per_bonus(-4);
    }

    if (has_trait( trait_SORES )) {
        for( const body_part bp : all_body_parts ) {
            if( bp == bp_head ) {
                continue;
            }
            int sores_pain = 5 + 0.4 * abs( encumb( bp ) );
            if (get_pain() < sores_pain) {
                set_pain( sores_pain );
            }
        }
    }
        //Web Weavers...weave web
    if (has_active_mutation( trait_WEB_WEAVER ) && !in_vehicle) {
      g->m.add_field( pos(), fd_web, 1 ); //this adds density to if its not already there.

     }

    // Blind/Deaf for brief periods about once an hour,
    // and visuals about once every 30 min.
    if (has_trait( trait_PER_SLIME )) {
        if (one_in(600) && !has_effect( effect_deaf )) {
            add_msg_if_player(m_bad, _("Suddenly, you can't hear anything!"));
            add_effect( effect_deaf, rng( 20_minutes, 60_minutes ) );
        }
        if (one_in(600) && !(has_effect( effect_blind ))) {
            add_msg_if_player(m_bad, _("Suddenly, your eyes stop working!"));
            add_effect( effect_blind, rng( 2_minutes, 6_minutes ) );
        }
        // Yes, you can be blind and hallucinate at the same time.
        // Your post-human biology is truly remarkable.
        if (one_in(300) && !(has_effect( effect_visuals ))) {
            add_msg_if_player(m_bad, _("Your visual centers must be acting up..."));
            add_effect( effect_visuals, rng( 36_minutes, 72_minutes ) );
        }
    }

    if (has_trait( trait_WEB_SPINNER ) && !in_vehicle && one_in(3)) {
        g->m.add_field( pos(), fd_web, 1 ); //this adds density to if its not already there.
    }

    if (has_trait( trait_UNSTABLE ) && !has_trait( trait_CHAOTIC_BAD ) && one_in(28800)) { // Average once per 2 days
        mutate();
    }
    if (( has_trait( trait_CHAOTIC ) || has_trait ( trait_CHAOTIC_BAD )) && one_in(7200)) { // Should be once every 12 hours
        mutate();
    }
    if (has_artifact_with(AEP_MUTAGENIC) && one_in(28800)) {
        mutate();
    }
    if (has_artifact_with(AEP_FORCE_TELEPORT) && one_in(600)) {
        g->teleport(this);
    }
    const bool needs_fire = !has_morale( MORALE_PYROMANIA_NEARFIRE ) && !has_morale( MORALE_PYROMANIA_STARTFIRE );
    if( has_trait( trait_PYROMANIA ) && needs_fire && !in_sleep_state() && calendar::once_every( 2_hours ) ) {
        add_morale( MORALE_PYROMANIA_NOFIRE, -1, -30, 24_hours, 24_hours );
        if( calendar::once_every( 4_hours ) ) {
            std::string smokin_hot_fiyah = SNIPPET.random_from_category( "pyromania_withdrawal" );
            add_msg_if_player( m_bad, _( smokin_hot_fiyah.c_str() ) );
        }
    }

    // checking for radioactive items in inventory
    const int item_radiation = leak_level( "RADIOACTIVE" );

    const int map_radiation = g->m.get_radiation( pos() );

    float rads = map_radiation / 100.0f + item_radiation / 10.0f;

    int rad_mut = 0;
    if( has_trait( trait_RADIOACTIVE3 ) ) {
        rad_mut = 3;
    } else if( has_trait( trait_RADIOACTIVE2 ) ) {
        rad_mut = 2;
    } else if( has_trait( trait_RADIOACTIVE1 ) ) {
        rad_mut = 1;
    }

    // Spread less radiation when sleeping (slower metabolism etc.)
    // Otherwise it can quickly get to the point where you simply can't sleep at all
    const bool rad_mut_proc = rad_mut > 0 &&
                              x_in_y( rad_mut, in_sleep_state() ? HOURS( 3 ) : MINUTES( 30 ) );

    bool has_helmet = false;
    const bool power_armored = is_wearing_power_armor( &has_helmet );
    const bool rad_resist = power_armored || worn_with_flag( "RAD_RESIST" );

    if( rad_mut > 0 ) {
        const bool kept_in = is_rad_immune() || ( rad_resist && !one_in( 4 ) );
        if( kept_in ) {
            // As if standing on a map tile with radiation level equal to rad_mut
            rads += rad_mut / 100.0f;
        }

        if( rad_mut_proc && !kept_in ) {
            // Irradiate a random nearby point
            // If you can't, irradiate the player instead
            tripoint rad_point = pos() + point( rng( -3, 3 ), rng( -3, 3 ) );
            // TODO: Radioactive vehicles?
            if( g->m.get_radiation( rad_point ) < rad_mut ) {
                g->m.adjust_radiation( rad_point, 1 );
            } else {
                rads += rad_mut;
            }
        }
    }

    // Used to control vomiting from radiation to make it not-annoying
    bool radiation_increasing = irradiate( rads );

    if( radiation_increasing && calendar::once_every( 3_minutes ) && has_bionic( bio_geiger ) ) {
        add_msg_if_player( m_warning,
                           _( "You feel an anomalous sensation coming from your radiation sensors." ) );
    }

    if( calendar::once_every( 15_minutes ) ) {
        if (radiation < 0) {
            radiation = 0;
        } else if (radiation > 2000) {
            radiation = 2000;
        }
        if( get_option<bool>( "RAD_MUTATION" ) && rng(100, 10000) < radiation ) {
            mutate();
            radiation -= 50;
        } else if( radiation > 50 && rng(1, 3000) < radiation &&
                   ( get_stomach_food() > 0 || get_stomach_water() > 0 ||
                     radiation_increasing || !in_sleep_state() ) ) {
            vomit();
            radiation -= 1;
        }
    }

    const bool radiogenic = has_trait( trait_RADIOGENIC );
    if( radiogenic && calendar::once_every( 30_minutes ) && radiation > 0 ) {
        // At 200 irradiation, twice as fast as REGEN
        if( x_in_y( radiation, 200 ) ) {
            healall( 1 );
            if( rad_mut == 0 ) {
                // Don't heal radiation if we're generating it naturally
                // That would counter the main downside of radioactivity
                radiation -= 5;
            }
        }
    }

    if( !radiogenic && radiation > 0 ) {
        // Even if you heal the radiation itself, the damage is done.
        const int hmod = get_healthy_mod();
        if( hmod > 200 - radiation ) {
            set_healthy_mod( std::max( -200, 200 - radiation ) );
        }
    }

    if( radiation > 200 && calendar::once_every( 10_minutes ) && x_in_y( radiation, 1000 ) ) {
        hurtall( 1, nullptr );
        radiation -= 5;
    }

    if (reactor_plut || tank_plut || slow_rad) {
        // Microreactor CBM and supporting bionics
        if (has_bionic( bio_reactor ) || has_bionic( bio_advreactor ) ) {
            //first do the filtering of plutonium from storage to reactor
            if (tank_plut > 0) {
                int plut_trans;
                if (has_active_bionic( bio_plut_filter ) ) {
                    plut_trans = tank_plut * 0.025;
                } else {
                    plut_trans = tank_plut * 0.005;
                }
                if (plut_trans < 1) {
                    plut_trans = 1;
                }
                tank_plut -= plut_trans;
                reactor_plut += plut_trans;
            }
            //leaking radiation, reactor is unshielded, but still better than a simple tank
            slow_rad += ((tank_plut * 0.1) + (reactor_plut * 0.01));
            //begin power generation
            if (reactor_plut > 0) {
                int power_gen = 0;
                if (has_bionic( bio_advreactor ) ) {
                    if ((reactor_plut * 0.05) > 2000){
                        power_gen = 2000;
                    } else {
                        power_gen = reactor_plut * 0.05;
                        if (power_gen < 1) {
                            power_gen = 1;
                        }
                    }
                    slow_rad += (power_gen * 3);
                    while (slow_rad >= 50) {
                        if (power_gen >= 1) {
                            slow_rad -= 50;
                            power_gen -= 1;
                            reactor_plut -= 1;
                        } else {
                        break;
                        }
                    }
                } else if (has_bionic( bio_reactor ) ) {
                    if ((reactor_plut * 0.025) > 500){
                        power_gen = 500;
                    } else {
                        power_gen = reactor_plut * 0.025;
                        if (power_gen < 1) {
                            power_gen = 1;
                        }
                    }
                    slow_rad += (power_gen * 3);
                }
                reactor_plut -= power_gen;
                while (power_gen >= 250) {
                    apply_damage( nullptr, bp_torso, 1);
                    mod_pain(1);
                    add_msg_if_player(m_bad, _("Your chest burns as your power systems overload!"));
                    charge_power(50);
                    power_gen -= 60; // ten units of power lost due to short-circuiting into you
                }
                charge_power(power_gen);
            }
        } else {
            slow_rad += (((reactor_plut * 0.4) + (tank_plut * 0.4)) * 100);
            //plutonium in body without any kind of container.  Not good at all.
            reactor_plut *= 0.6;
            tank_plut *= 0.6;
        }
        while (slow_rad >= 1000) {
            radiation += 1;
            slow_rad -= 1000;
        }
    }

    // Negative bionics effects
    if (has_bionic( bio_dis_shock ) && one_in(1200)) {
        add_msg_if_player(m_bad, _("You suffer a painful electrical discharge!"));
        mod_pain(1);
        moves -= 150;

        if (weapon.typeId() == "e_handcuffs" && weapon.charges > 0) {
            weapon.charges -= rng(1, 3) * 50;
            if (weapon.charges < 1) {
                weapon.charges = 1;
            }

            add_msg_if_player(m_good, _("The %s seems to be affected by the discharge."), weapon.tname().c_str());
        }
        sfx::play_variant_sound( "bionics", "elec_discharge", 100 );
    }
    if (has_bionic( bio_dis_acid ) && one_in(1500)) {
        add_msg_if_player(m_bad, _("You suffer a burning acidic discharge!"));
        hurtall(1, nullptr);
        sfx::play_variant_sound( "bionics", "acid_discharge", 100 );
        sfx::do_player_death_hurt( g->u, false );
    }
    if (has_bionic( bio_drain ) && power_level > 24 && one_in(600)) {
        add_msg_if_player(m_bad, _("Your batteries discharge slightly."));
        charge_power(-25);
        sfx::play_variant_sound( "bionics", "elec_crackle_low", 100 );
    }
    if (has_bionic( bio_noise ) && one_in(500)) {
        // TODO: NPCs with said bionic
        if(!is_deaf()) {
            add_msg(m_bad, _("A bionic emits a crackle of noise!"));
            sfx::play_variant_sound( "bionics", "elec_blast", 100 );
        } else {
            add_msg(m_bad, _("You feel your faulty bionic shuddering."));
            sfx::play_variant_sound( "bionics", "elec_blast_muffled", 100 );
        }
        sounds::sound( pos(), 60, sounds::sound_t::movement, _( "Crackle!" ) );
    }
    if (has_bionic( bio_power_weakness ) && max_power_level > 0 &&
        power_level >= max_power_level * .75) {
        mod_str_bonus(-3);
    }
    if (has_bionic( bio_trip ) && one_in(500) && !has_effect( effect_visuals )) {
        add_msg_if_player(m_bad, _("Your vision pixelates!"));
        add_effect( effect_visuals, 10_minutes );
        sfx::play_variant_sound( "bionics", "pixelated", 100 );
    }
    if (has_bionic( bio_spasm ) && one_in(3000) && !has_effect( effect_downed )) {
        add_msg_if_player(m_bad, _("Your malfunctioning bionic causes you to spasm and fall to the floor!"));
        mod_pain(1);
        add_effect( effect_stunned, 1_turns );
        add_effect( effect_downed, 1_turns, num_bp, false, 0, true );
        sfx::play_variant_sound( "bionics", "elec_crackle_high", 100 );
    }
    if (has_bionic( bio_shakes ) && power_level > 24 && one_in(1200)) {
        add_msg_if_player(m_bad, _("Your bionics short-circuit, causing you to tremble and shiver."));
        charge_power(-25);
        add_effect( effect_shakes, 5_minutes );
        sfx::play_variant_sound( "bionics", "elec_crackle_med", 100 );
    }
    if (has_bionic( bio_leaky ) && one_in(500)) {
        mod_healthy_mod(-50, -200);
    }
    if (has_bionic( bio_sleepy ) && one_in(500) && !in_sleep_state()) {
        mod_fatigue(1);
    }
    if (has_bionic( bio_itchy ) && one_in(500) && !has_effect( effect_formication )) {
        add_msg_if_player(m_bad, _("Your malfunctioning bionic itches!"));
        body_part bp = random_body_part(true);
        add_effect( effect_formication, 10_minutes, bp );
    }

    // Artifact effects
    if (has_artifact_with(AEP_ATTENTION)) {
        add_effect( effect_attention, 3_turns );
    }

    // Stim +250 kills
    if ( stim > 210 ) {
        if ( one_in( 20 ) && !has_effect( effect_downed ) ) {
            add_msg_if_player(m_bad, _("Your muscles spasm!"));
            if( !has_effect( effect_downed ) ) {
                add_msg_if_player(m_bad, _("You fall to the ground!"));
                add_effect( effect_downed, rng( 6_turns, 20_turns ) );
            }
        }
    }
    if ( stim > 170 ) {
        if ( !has_effect( effect_winded ) && calendar::once_every( 10_minutes ) ) {
            add_msg(m_bad, _("You feel short of breath.") );
            add_effect( effect_winded, 10_minutes + 1_turns );
        }
    }
    if ( stim > 110 ) {
        if ( !has_effect( effect_shakes ) && calendar::once_every( 10_minutes ) ) {
            add_msg( _("You shake uncontrollably.") );
            add_effect( effect_shakes, 15_minutes + 1_turns );
        }
    }
    if ( stim > 75 ) {
        if ( !one_in( 20 ) && !has_effect( effect_nausea ) ) {
            add_msg( _("You feel nauseous...") );
            add_effect( effect_nausea, 5_minutes );
        }
    }

    // Stim -200 or painkillers 240 kills
    if ( stim < -160 || pkill > 200 ) {
        if ( one_in(30) && !in_sleep_state() ) {
            add_msg_if_player(m_bad, _("You black out!") );
            const time_duration dur = rng( 30_minutes, 60_minutes );
            add_effect( effect_downed, dur );
            add_effect( effect_blind, dur );
            fall_asleep( dur );
        }
    }
    if ( stim < -120 || pkill > 160 ) {
        if ( !has_effect( effect_winded ) && calendar::once_every( 10_minutes ) ) {
            add_msg(m_bad, _("Your breathing slows down.") );
            add_effect( effect_winded, 10_minutes + 1_turns );
        }
    }
    if ( stim < -85 || pkill > 145 ) {
        if ( one_in( 15 ) ) {
            add_msg_if_player(m_bad, _("You feel dizzy for a moment."));
            mod_moves( -rng(10, 30) );
            if ( one_in(3) && !has_effect( effect_downed ) ) {
                add_msg_if_player(m_bad, _("You stumble and fall over!"));
                add_effect( effect_downed, rng( 3_turns, 10_turns ) );
            }
        }
    }
    if( stim < -60 || pkill > 130 ) {
        if( calendar::once_every( 10_minutes ) ) {
            add_msg( m_warning, _( "You feel tired..." ) );
            mod_fatigue( rng( 1, 2 ) );
        }
    }

    int sleep_deprivation = !in_sleep_state() ? get_sleep_deprivation() : 0;
    // Stimulants can lessen the PERCEIVED effects of sleep deprivation, but
    // they do nothing to cure it. As such, abuse is even more dangerous now.
    if( stim > 0 ) {
        // 100% of blacking out = 20160sd ; Max. stim modifier = 12500sd @ 250stim
        // Note: Very high stim already has its own slew of bad effects,
        // so the "useful" part of this bonus is actually lower.
        sleep_deprivation -= stim * 50;
    }

    // Harmless warnings
    if( sleep_deprivation >= SLEEP_DEPRIVATION_HARMLESS ) {
        if( one_in( 500 ) ) {
            switch( dice(1, 4) ) {
                default:
                case 1:
                    add_msg_if_player( m_warning, _( "You tiredly rub your eyes." ) );
                    break;
                case 2:
                    add_msg_if_player( m_warning, _( "You let out a small yawn." ) );
                    break;
                case 3:
                    add_msg_if_player( m_warning, _( "You stretch your back." ) );
                    break;
                case 4:
                    add_msg_if_player( m_warning, _( "You feel mentally tired." ) );
                    break;
            }
        }
    }
    // Minor discomfort
    if( sleep_deprivation >= SLEEP_DEPRIVATION_MINOR ) {
        if( one_in( 750 ) ) {
            add_msg_if_player( m_warning, _( "You feel lightheaded for a moment." ) );
            moves -= 10;
        }
        if( one_in( 1000 ) ) {
            add_msg_if_player( m_warning, _( "Your muscles spasm uncomfortably." ) );
            mod_pain( 2 );
        }
        if( !has_effect( effect_visuals ) && one_in( 1500 ) ) {
            add_msg_if_player( m_warning, _( "Your vision blurs a little." ) );
            add_effect( effect_visuals, rng( 1_minutes, 5_minutes ) );
        }
    }
    // Slight disability
    if( sleep_deprivation >= SLEEP_DEPRIVATION_SERIOUS ) {
        if( one_in( 750 ) ) {
            add_msg_if_player( m_bad, _( "Your mind lapses into unawareness briefly." ) );
            moves -= rng( 20, 80 );
        }
        if( one_in( 1250 ) ) {
            add_msg_if_player( m_bad, _( "Your muscles ache in stressfully unpredictable ways." ) );
            mod_pain( rng( 2, 10 ) );
        }
        if( one_in( 3000 ) ) {
            add_msg_if_player( m_bad, _( "You have a distractingly painful headache." ) );
            mod_pain( rng( 10, 25 ) );
        }
    }
    // Major disability, high chance of passing out also relevant
    if( sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR ) {
        if( !has_effect( effect_nausea ) && one_in( 5000 ) ) {
            add_msg_if_player( m_bad, _( "You feel heartburn and an acid taste in your mouth." ) );
            mod_pain( 5 );
            add_effect( effect_nausea, rng( 5_minutes, 30_minutes ) );
        }
        if( one_in( 3000 ) ) {
            add_msg_if_player( m_bad, _( "Your mind is so tired that you feel you can't trust your eyes anymore." ) );
            add_effect( effect_hallu, rng( 5_minutes, 60_minutes ) );
        }
        if( !has_effect( effect_shakes ) && one_in( 4250 ) ) {
            add_msg_if_player( m_bad, _( "Your muscles spasm uncontrollably, and you have trouble keeping your balance." ) );
            add_effect( effect_shakes, 15_minutes );
        }
        else if( has_effect( effect_shakes ) && one_in( 75 ) ) {
            moves -= 10;
            add_msg_if_player( m_warning, _( "Your shaking legs make you stumble." ) );
            if( !has_effect( effect_downed ) && one_in( 10 ) ) {
                add_msg_if_player( m_bad, _( "You fall over!" ) );
                add_effect( effect_downed, rng( 3_turns, 10_turns ) );
            }
        }
    }
}

bool player::irradiate( float rads, bool bypass )
{
    int rad_mut = 0;
    if( has_trait( trait_RADIOACTIVE3 ) ) {
        rad_mut = 3;
    } else if( has_trait( trait_RADIOACTIVE2 ) ) {
        rad_mut = 2;
    } else if( has_trait( trait_RADIOACTIVE1 ) ) {
        rad_mut = 1;
    }

    if( rads > 0 ) {
        bool has_helmet = false;
        const bool power_armored = is_wearing_power_armor( &has_helmet );
        const bool rad_resist = power_armored || worn_with_flag( "RAD_RESIST" );

        if( is_rad_immune() && !bypass ) {
            // Power armor and some high-tech gear protects completely from radiation
            rads = 0.0f;
        } else if( rad_resist && !bypass ) {
            rads /= 4.0f;
        }

        if( has_effect( effect_iodine ) ) {
            // Radioactive mutation makes iodine less efficient (but more useful)
            rads *= 0.3f + 0.1f * rad_mut;
        }

        int rads_max = roll_remainder( rads );
        radiation += rng( 0, rads_max );

        // Apply rads to any radiation badges.
        for( item *const it : inv_dump() ) {
            if( it->typeId() != "rad_badge" ) {
                continue;
            }

            // Actual irradiation levels of badges and the player aren't precisely matched.
            // This is intentional.
            const int before = it->irridation;

            const int delta = rng( 0, rads_max );
            if( delta == 0 ) {
                continue;
            }

            it->irridation += delta;

            // If in inventory (not worn), don't print anything.
            if( inv.has_item( *it ) ) {
                continue;
            }

            // If the color hasn't changed, don't print anything.
            const std::string &col_before = rad_badge_color( before );
            const std::string &col_after = rad_badge_color( it->irridation );
            if( col_before == col_after ) {
                continue;
            }

            add_msg_if_player( m_warning, _( "Your radiation badge changes from %1$s to %2$s!" ),
                               col_before.c_str(), col_after.c_str() );
        }

        if( rads > 0.0f ) {
            return true;
        }
    }
    return false;
}

// At minimum level, return at_min, at maximum at_max
float addiction_scaling( float at_min, float at_max, float add_lvl )
{
    // Not addicted
    if( add_lvl < MIN_ADDICTION_LEVEL ) {
        return 1.0f;
    }

    return lerp( at_min, at_max, ( add_lvl - MIN_ADDICTION_LEVEL ) / MAX_ADDICTION_LEVEL );
}

void player::mend( int rate_multiplier )
{
    // Wearing splints can slowly mend a broken limb back to 1 hp.
    bool any_broken = false;
    for( int i = 0; i < num_hp_parts; i++ ) {
        if( hp_cur[i] <= 0 ) {
            any_broken = true;
            break;
        }
    }

    if( !any_broken ) {
        return;
    }

    double healing_factor = 1.0;
    // Studies have shown that alcohol and tobacco use delay fracture healing time
    // Being under effect is 50% slowdown
    // Being addicted but not under effect scales from 25% slowdown to 75% slowdown
    // The improvement from being intoxicated over withdrawal is intended
    if( has_effect( effect_cig ) ) {
        healing_factor *= 0.5;
    } else {
        healing_factor *= addiction_scaling( 0.25f, 0.75f, addiction_level( ADD_CIG ) );
    }

    if( has_effect( effect_drunk ) ) {
        healing_factor *= 0.5;
    } else {
        healing_factor *= addiction_scaling( 0.25f, 0.75f, addiction_level( ADD_ALCOHOL ) );
    }

    if( radiation > 0 && !has_trait( trait_RADIOGENIC ) ) {
        healing_factor *= clamp( ( 1000.0f - radiation ) / 1000.0f, 0.0f, 1.0f );
    }

    // Bed rest speeds up mending
    if( has_effect( effect_sleep ) ) {
        healing_factor *= 4.0;
    } else if( get_fatigue() > DEAD_TIRED ) {
        // but being dead tired does not...
        healing_factor *= 0.75;
    } else {
        // If not dead tired, resting without sleep also helps
        healing_factor *= 1.0f + rest_quality();
    }

    // Being healthy helps.
    healing_factor *= 1.0f + get_healthy() / 200.0f;

    // Very hungry starts lowering the chance
    // Healing stops completely halfway between near starving and starving
    healing_factor *= 1.0f - clamp( ( get_hunger() - 100.0f ) / 2000.0f, 0.0f, 1.0f );
    // Similar for thirst - starts at very thirsty, drops to 0 ~halfway between two last statuses
    healing_factor *= 1.0f - clamp( ( get_thirst() - 80.0f ) / 300.0f, 0.0f, 1.0f );

    // Mutagenic healing factor!
    bool needs_splint = true;
    if( has_trait( trait_REGEN_LIZ ) ) {
        healing_factor *= 20.0;
        needs_splint = false;
    } else if( has_trait( trait_REGEN ) ) {
        healing_factor *= 16.0;
    } else if( has_trait( trait_FASTHEALER2 ) ) {
        healing_factor *= 4.0;
    } else if( has_trait( trait_FASTHEALER ) ) {
        healing_factor *= 2.0;
    } else if( has_trait( trait_SLOWHEALER ) ) {
        healing_factor *= 0.5;
    }

    add_msg( m_debug, "Limb mend healing factor: %.2f", healing_factor );
    if( healing_factor <= 0.0f ) {
        // The section below assumes positive healing rate
        return;
    }

    for( int i = 0; i < num_hp_parts; i++ ) {
        const bool broken = (hp_cur[i] <= 0);
        if( !broken ) {
            continue;
        }

        body_part part = hp_to_bp( static_cast<hp_part>( i ) );
        if( needs_splint && !worn_with_flag( "SPLINT", part ) ) {
            continue;
        }

        const time_duration dur_inc = 1_turns * roll_remainder( rate_multiplier * healing_factor );
        auto &eff = get_effect( effect_mending, part );
        if( eff.is_null() ) {
            add_effect( effect_mending, dur_inc, part, true );
            continue;
        }

        eff.set_duration( eff.get_duration() + dur_inc );

        if( eff.get_duration() >= eff.get_max_duration() ) {
            hp_cur[i] = 1;
            remove_effect( effect_mending, part );
            //~ %s is bodypart
            add_memorial_log( pgettext("memorial_male", "Broken %s began to mend."),
                              pgettext("memorial_female", "Broken %s began to mend."),
                              body_part_name( part ).c_str() );
            //~ %s is bodypart
            add_msg_if_player( m_good, _("Your %s has started to mend!"),
                               body_part_name( part ).c_str() );
        }
    }
}

void player::vomit()
{
    add_memorial_log(pgettext("memorial_male", "Threw up."),
                     pgettext("memorial_female", "Threw up."));

    const int stomach_contents = get_stomach_food() + get_stomach_water();
    if( stomach_contents != 0 ) {
        mod_hunger(get_stomach_food());
        mod_thirst(get_stomach_water());

        set_stomach_food(0);
        set_stomach_water(0);
        // Remove all joy form previously eaten food and apply the penalty
        rem_morale( MORALE_FOOD_GOOD );
        rem_morale( MORALE_FOOD_HOT );
        rem_morale( MORALE_HONEY ); // bears must suffer too
        add_morale( MORALE_VOMITED, -2 * stomach_contents, -40, 90_minutes, 45_minutes, false ); // 1.5 times longer

        g->m.add_field( adjacent_tile(), fd_bile, 1 );

        add_msg_player_or_npc( m_bad, _("You throw up heavily!"), _("<npcname> throws up heavily!") );
    } else {
        add_msg_if_player( m_warning, _( "You retched, but your stomach is empty." ) );
    }

    if( !has_effect( effect_nausea ) ) { // Prevents never-ending nausea
        const effect dummy_nausea( &effect_nausea.obj(), 0_turns, num_bp, false, 1, calendar::turn );
        add_effect( effect_nausea, std::max( dummy_nausea.get_max_duration() * stomach_contents / 21,
                                             dummy_nausea.get_int_dur_factor() ) );
    }

    moves -= 100;
    for( auto &elem : *effects ) {
        for( auto &_effect_it : elem.second ) {
            auto &it = _effect_it.second;
            if( it.get_id() == effect_foodpoison ) {
                it.mod_duration( -30_minutes );
            } else if( it.get_id() == effect_drunk ) {
                it.mod_duration( rng( -10_minutes, -50_minutes ) );
            }
        }
    }
    remove_effect( effect_pkill1 );
    remove_effect( effect_pkill2 );
    remove_effect( effect_pkill3 );
    // Don't wake up when just retching
    if( stomach_contents != 0 ) {
        wake_up();
    }
}

void player::sound_hallu() {
    // Random 'dangerous' sound from a random direction
    // 1/5 chance to be a loud sound
    std::vector<std::string> dir{ "north",
                                  "northeast",
                                  "northwest",
                                  "south",
                                  "southeast",
                                  "southwest",
                                  "east",
                                  "west" };

    std::vector<std::string> dirz{ "and above you ", "and below you " };

    std::vector<std::tuple<std::string, std::string, std::string>> desc{
                std::make_tuple( "whump!", "smash_fail", "t_door_c" ),
                std::make_tuple( "crash!", "smash_success", "t_door_c" ),
                std::make_tuple( "glass breaking!", "smash_success", "t_window_domestic" ) };

    std::vector<std::tuple<std::string, std::string, std::string>> desc_big{
                std::make_tuple( "huge explosion!", "explosion", "default" ),
                std::make_tuple( "bang!", "fire_gun", "glock_19" ),
                std::make_tuple( "blam!", "fire_gun", "mossberg_500" ),
                std::make_tuple( "crash!", "smash_success", "t_wall" ),
                std::make_tuple( "SMASH!", "smash_success", "t_wall" ) };

    std::string i_dir = dir[rng( 0, dir.size() - 1 )];

    if( one_in( 10 ) ) {
        i_dir += " " + dirz[rng( 0, dirz.size() - 1 )];
    }

    std::string i_desc;
    std::pair<std::string, std::string> i_sound;
    if( one_in( 5 ) ) {
        int r_int = rng( 0, desc_big.size() - 1 );
        i_desc = std::get<0>( desc_big[r_int] );
        i_sound = std::make_pair( std::get<1>( desc_big[r_int] ), std::get<2>( desc_big[r_int] ) );
    } else {
        int r_int = rng( 0, desc.size() - 1 );
        i_desc = std::get<0>( desc[r_int] );
        i_sound = std::make_pair( std::get<1>( desc[r_int] ), std::get<2>( desc[r_int] ) );
    }

    add_msg( m_warning, _( "From the %1$s you hear %2$s" ), i_dir.c_str(), i_desc.c_str() );
    sfx::play_variant_sound( i_sound.first, i_sound.second, rng( 20, 80 ) );
}

void player::drench( int saturation, const body_part_set &flags, bool ignore_waterproof )
{
    if( saturation < 1 ) {
        return;
    }

    // OK, water gets in your AEP suit or whatever.  It wasn't built to keep you dry.
    if( has_trait( trait_DEBUG_NOTEMP ) || has_active_mutation( trait_id( "SHELL2" ) ) ||
        ( !ignore_waterproof && is_waterproof(flags) ) ) {
        return;
    }

    // Make the body wet
    for( const body_part bp : all_body_parts ) {
        // Different body parts have different size, they can only store so much water
        int bp_wetness_max = 0;
        if( flags.test( bp ) ) {
            bp_wetness_max = drench_capacity[bp];
        }

        if( bp_wetness_max == 0 ){
            continue;
        }
        // Different sources will only make the bodypart wet to a limit
        int source_wet_max = saturation * bp_wetness_max * 2 / 100;
        int wetness_increment = ignore_waterproof ? 100 : 2;
        // Respect maximums
        const int wetness_max = std::min( source_wet_max, bp_wetness_max );
        if( body_wetness[bp] < wetness_max ){
            body_wetness[bp] = std::min( wetness_max, body_wetness[bp] + wetness_increment );
        }
    }

    // Remove onfire effect
    if( saturation > 10 || x_in_y( saturation, 10 ) ) {
        remove_effect( effect_onfire );
    }
}

void player::drench_mut_calc()
{
    for( const body_part bp : all_body_parts ) {
        int ignored = 0;
        int neutral = 0;
        int good = 0;

        for( const auto &iter : my_mutations ) {
            const mutation_branch &mdata = iter.first.obj();
            const auto wp_iter = mdata.protection.find( bp );
            if( wp_iter != mdata.protection.end() ) {
                ignored += wp_iter->second.x;
                neutral += wp_iter->second.y;
                good += wp_iter->second.z;
            }
        }

        mut_drench[bp][WT_GOOD] = good;
        mut_drench[bp][WT_NEUTRAL] = neutral;
        mut_drench[bp][WT_IGNORED] = ignored;
    }
}

void player::apply_wetness_morale( int temperature )
{
    // First, a quick check if we have any wetness to calculate morale from
    // Faster than checking all worn items for friendliness
    if( !std::any_of( body_wetness.begin(), body_wetness.end(),
        []( const int w ) { return w != 0; } ) ) {
        return;
    }

    // Normalize temperature to [-1.0,1.0]
    temperature = std::max( 0, std::min( 100, temperature ) );
    const double global_temperature_mod = -1.0 + ( 2.0 * temperature / 100.0 );

    int total_morale = 0;
    const auto wet_friendliness = exclusive_flag_coverage( "WATER_FRIENDLY" );
    for( const body_part bp : all_body_parts ) {
        // Sum of body wetness can go up to 103
        const int part_drench = body_wetness[bp];
        if( part_drench == 0 ) {
            continue;
        }

        const auto &part_arr = mut_drench[bp];
        const int part_ignored = part_arr[WT_IGNORED];
        const int part_neutral = part_arr[WT_NEUTRAL];
        const int part_good    = part_arr[WT_GOOD];

        if( part_ignored >= part_drench ) {
            continue;
        }

        int bp_morale = 0;
        const bool is_friendly = wet_friendliness.test( bp );
        const int effective_drench = part_drench - part_ignored;
        if( is_friendly ) {
            // Using entire bonus from mutations and then some "human" bonus
            bp_morale = std::min( part_good, effective_drench ) + effective_drench / 2;
        } else if( effective_drench < part_good ) {
            // Positive or 0
            // Won't go higher than part_good / 2
            // Wet slime/scale doesn't feel as good when covered by wet rags/fur/kevlar
            bp_morale = std::min( effective_drench, part_good - effective_drench );
        } else if( effective_drench > part_good + part_neutral ) {
            // This one will be negative
            bp_morale = part_good + part_neutral - effective_drench;
        }

        // Clamp to [COLD,HOT] and cast to double
        const double part_temperature =
            std::min( BODYTEMP_HOT, std::max( BODYTEMP_COLD, temp_cur[bp] ) );
        // 0.0 at COLD, 1.0 at HOT
        const double part_mod = (part_temperature - BODYTEMP_COLD) /
                                (BODYTEMP_HOT - BODYTEMP_COLD);
        // Average of global and part temperature modifiers, each in range [-1.0, 1.0]
        double scaled_temperature = ( global_temperature_mod + part_mod ) / 2;

        if( bp_morale < 0 ) {
            // Damp, hot clothing on hot skin feels bad
            scaled_temperature = fabs( scaled_temperature );
        }

        // For an unmutated human swimming in deep water, this will add up to:
        // +51 when hot in 100% water friendly clothing
        // -103 when cold/hot in 100% unfriendly clothing
        total_morale += static_cast<int>( bp_morale * ( 1.0 + scaled_temperature ) / 2.0 );
    }

    if( total_morale == 0 ) {
        return;
    }

    int morale_effect = total_morale / 8;
    if( morale_effect == 0 ) {
        if( total_morale > 0 ) {
            morale_effect = 1;
        } else {
            morale_effect = -1;
        }
    }
    // 11_turns because decay is applied in 10_turn increments
    add_morale( MORALE_WET, morale_effect, total_morale, 11_turns, 11_turns, true );
}

void player::update_body_wetness( const w_point &weather )
{
    // Average number of turns to go from completely soaked to fully dry
    // assuming average temperature and humidity
    constexpr int average_drying = HOURS(2);

    // A modifier on drying time
    double delay = 1.0;
    // Weather slows down drying
    delay -= ( weather.temperature - 65 ) / 100.0;
    delay += ( weather.humidity - 66 ) / 100.0;
    delay = std::max( 0.1, delay );
    // Fur/slime retains moisture
    if( has_trait( trait_LIGHTFUR ) || has_trait( trait_FUR ) || has_trait( trait_FELINE_FUR ) ||
        has_trait( trait_LUPINE_FUR ) || has_trait( trait_CHITIN_FUR ) || has_trait( trait_CHITIN_FUR2 ) ||
        has_trait( trait_CHITIN_FUR3 )) {
        delay = delay * 6 / 5;
    }
    if( has_trait( trait_URSINE_FUR ) || has_trait( trait_SLIMY ) ) {
        delay = delay * 3 / 2;
    }

    if( !one_in_improved( average_drying * delay / 100.0 ) ) {
        // No drying this turn
        return;
    }

    // Now per-body-part stuff
    // To make drying uniform, make just one roll and reuse it
    const int drying_roll = rng( 1, 100 );
    if( drying_roll > 40 ) {
        // Wouldn't affect anything
        return;
    }

    for( const body_part bp : all_body_parts ) {
        if( body_wetness[bp] == 0 ) {
            continue;
        }
        // This is to normalize drying times
        int drying_chance = drench_capacity[bp];
        // Body temperature affects duration of wetness
        // Note: Using temp_conv rather than temp_cur, to better approximate environment
        if( temp_conv[bp] >= BODYTEMP_SCORCHING ) {
            drying_chance *= 2;
        } else if( temp_conv[bp] >=  BODYTEMP_VERY_HOT ) {
            drying_chance = drying_chance * 3 / 2;
        } else if( temp_conv[bp] >= BODYTEMP_HOT ) {
            drying_chance = drying_chance * 4 / 3;
        } else if( temp_conv[bp] > BODYTEMP_COLD ) {
            // Comfortable, doesn't need any changes
        } else {
            // Evaporation doesn't change that much at lower temp
            drying_chance = drying_chance * 3 / 4;
        }

        if( drying_chance < 1 ) {
            drying_chance = 1;
        }

        // @todo: Make evaporation reduce body heat
        if( drying_chance >= drying_roll ) {
            body_wetness[bp] -= 1;
            if( body_wetness[bp] < 0 ) {
                body_wetness[bp] = 0;
            }
        }
    }
    // TODO: Make clothing slow down drying
}

int player::get_morale_level() const
{
    return morale->get_level();
}

void player::add_morale(morale_type type, int bonus, int max_bonus,
                        const time_duration &duration, const time_duration &decay_start,
                        bool capped, const itype* item_type)
{
    morale->add( type, bonus, max_bonus, duration, decay_start, capped, item_type );
}

int player::has_morale( morale_type type ) const
{
    return morale->has( type );
}

void player::rem_morale(morale_type type, const itype* item_type)
{
    morale->remove( type, item_type );
}

void player::clear_morale()
{
    morale->clear();
}

bool player::has_morale_to_read() const
{
    return get_morale_level() >= -40;
}

void player::check_and_recover_morale()
{
    player_morale test_morale;

    for( const auto &wit : worn ) {
        test_morale.on_item_wear( wit );
    }

    for( const auto &mut : my_mutations ) {
        test_morale.on_mutation_gain( mut.first );
    }

    for( auto &elem : *effects ) {
        for( auto &_effect_it : elem.second ) {
            const effect &e = _effect_it.second;
            test_morale.on_effect_int_change( e.get_id(), e.get_intensity(), e.get_bp() );
        }
    }

    test_morale.on_stat_change( "hunger", get_hunger() + get_starvation() );
    test_morale.on_stat_change( "thirst", get_thirst() );
    test_morale.on_stat_change( "fatigue", get_fatigue() );
    test_morale.on_stat_change( "pain", get_pain() );
    test_morale.on_stat_change( "pkill", get_painkiller() );
    test_morale.on_stat_change( "perceived_pain", get_perceived_pain() );

    apply_persistent_morale();

    if( !morale->consistent_with( test_morale ) ) {
        *morale = player_morale( test_morale ); // Recover consistency
        add_msg( m_debug, "%s morale was recovered.", disp_name( true ).c_str() );
    }
}

void player::on_worn_item_transform( const item &it ) {
	morale->on_worn_item_transform( it );
}

void player::process_active_items()
{
    if( weapon.needs_processing() && weapon.process( this, pos(), false ) ) {
        weapon = item();
    }

    std::vector<item *> inv_active = inv.active_items();
    for( auto tmp_it : inv_active ) {

        if( tmp_it->process( this, pos(), false ) ) {
            inv.remove_item(tmp_it);
        }
    }

    // worn items
    remove_worn_items_with( [this]( item &itm ) {
        return itm.needs_processing() && itm.process( this, pos(), false );
    } );

    long ch_UPS = charges_of( "UPS" );
    item *cloak = nullptr;
    item *power_armor = nullptr;
    // Manual iteration because we only care about *worn* active items.
    for( auto &w : worn ) {
        if( !w.active ) {
            continue;
        }
        if( w.typeId() == OPTICAL_CLOAK_ITEM_ID ) {
            cloak = &w;
        }
        // Only the main power armor item can be active, the other ones (hauling frame, helmet) aren't.
        if( power_armor == nullptr && w.is_power_armor() ) {
            power_armor = &w;
        }
    }
    if( cloak != nullptr ) {
        if( ch_UPS >= 20 ) {
            use_charges( "UPS", 20 );
            if( ch_UPS < 200 && one_in( 3 ) ) {
                add_msg_if_player( m_warning, _( "Your optical cloak flickers for a moment!" ) );
            }
        } else if( ch_UPS > 0 ) {
            use_charges( "UPS", ch_UPS );
        } else {
            add_msg_if_player( m_warning, _( "Your optical cloak flickers for a moment as it becomes opaque." ) );
            // Bypass the "you deactivate the ..." message
            cloak->active = false;
        }
    }

    // For powered armor, an armor-powering bionic should always be preferred over UPS usage.
    if( power_armor != nullptr ) {
        const int power_cost = 4;
        bool bio_powered = can_interface_armor() && power_level > 0;
        // Bionic power costs are handled elsewhere.
        if( !bio_powered ) {
            if( ch_UPS >= power_cost ) {
                use_charges( "UPS", power_cost );
            } else {
                // Deactivate armor here, bypassing the usual deactivation message.
                add_msg_if_player( m_warning, _( "Your power armor disengages." ) );
                power_armor->active = false;
            }
        }
    }

    // Load all items that use the UPS to their minimal functional charge,
    // The tool is not really useful if its charges are below charges_to_use
    ch_UPS = charges_of( "UPS" ); // might have been changed by cloak
    long ch_UPS_used = 0;
    for( size_t i = 0; i < inv.size() && ch_UPS_used < ch_UPS; i++ ) {
        item &it = inv.find_item(i);
        if( !it.has_flag( "USE_UPS" ) ) {
            continue;
        }
        if( it.charges < it.type->maximum_charges() ) {
            ch_UPS_used++;
            it.charges++;
        }
    }
    if( weapon.has_flag( "USE_UPS" ) &&  ch_UPS_used < ch_UPS &&
        weapon.charges < weapon.type->maximum_charges() ) {
        ch_UPS_used++;
        weapon.charges++;
    }

    for( item& worn_item : worn ) {
        if( ch_UPS_used >= ch_UPS ) {
            break;
        }
        if( !worn_item.has_flag( "USE_UPS" ) ) {
            continue;
        }
        if( worn_item.charges < worn_item.type->maximum_charges() ) {
            ch_UPS_used++;
            worn_item.charges++;
        }
    }
    if( ch_UPS_used > 0 ) {
        use_charges( "UPS", ch_UPS_used );
    }
}

item player::reduce_charges( int position, long quantity )
{
    item &it = i_at( position );
    if( it.is_null() ) {
        debugmsg( "invalid item position %d for reduce_charges", position );
        return item();
    }
    if( it.charges <= quantity ) {
        return i_rem( position );
    }
    it.mod_charges( -quantity );
    item tmp( it );
    tmp.charges = quantity;
    return tmp;
}

item player::reduce_charges( item *it, long quantity )
{
    if( !has_item( *it ) ) {
        debugmsg( "invalid item (name %s) for reduce_charges", it->tname().c_str() );
        return item();
    }
    if( it->charges <= quantity ) {
        return i_rem( it );
    }
    it->mod_charges( -quantity );
    item result( *it );
    result.charges = quantity;
    return result;
}

int player::invlet_to_position( const long linvlet ) const
{
    // Invlets may come from curses, which may also return any kind of key codes, those being
    // of type long and they can become valid, but different characters when casted to char.
    // Example: KEY_NPAGE (returned when the player presses the page-down key) is 0x152,
    // casted to char would yield 0x52, which happens to be 'R', a valid invlet.
    if( linvlet > std::numeric_limits<char>::max() || linvlet < std::numeric_limits<char>::min() ) {
        return INT_MIN;
    }
    const char invlet = static_cast<char>( linvlet );
    if( is_npc() ) {
        DebugLog( D_WARNING,  D_GAME ) << "Why do you need to call player::invlet_to_position on npc " << name;
    }
    if( weapon.invlet == invlet ) {
        return -1;
    }
    auto iter = worn.begin();
    for( size_t i = 0; i < worn.size(); i++, iter++ ) {
        if( iter->invlet == invlet ) {
            return worn_position_to_index( i );
        }
    }
    return inv.invlet_to_position( invlet );
}

bool player::can_interface_armor() const {
    bool okay = std::any_of( my_bionics->begin(), my_bionics->end(),
        []( const bionic &b ) { return b.powered && b.info().armor_interface; } );
    return okay;
}

const martialart &player::get_combat_style() const
{
    return style_selected.obj();
}

std::vector<item *> player::inv_dump()
{
    std::vector<item *> ret;
    if( is_armed() && can_unwield( weapon ).success() ) {
        ret.push_back(&weapon);
    }
    for (auto &i : worn) {
        ret.push_back(&i);
    }
    inv.dump(ret);
    return ret;
}

std::list<item> player::use_amount( itype_id it, int _quantity, const std::function<bool( const item & )> &filter )
{
    std::list<item> ret;
    long quantity = _quantity; // Don't want to change the function signature right now
    if (weapon.use_amount(it, quantity, ret)) {
        remove_weapon();
    }
    for( auto a = worn.begin(); a != worn.end() && quantity > 0; ) {
        if( a->use_amount( it, quantity, ret, filter ) ) {
            a->on_takeoff( *this );
            a = worn.erase( a );
        } else {
            ++a;
        }
    }
    if (quantity <= 0) {
        return ret;
    }
    std::list<item> tmp = inv.use_amount( it, quantity, filter );
    ret.splice(ret.end(), tmp);
    return ret;
}

bool player::use_charges_if_avail(const itype_id &it, long quantity)
{
    if (has_charges(it, quantity))
    {
        use_charges(it, quantity);
        return true;
    }
    return false;
}

bool player::has_fire(const int quantity) const
{
// TODO: Replace this with a "tool produces fire" flag.

    if( g->m.has_nearby_fire( pos() ) ) {
        return true;
    } else if( has_item_with_flag( "FIRE" ) ) {
        return true;
    } else if( has_item_with_flag( "FIRESTARTER" ) ) {
        auto firestarters = all_items_with_flag( "FIRESTARTER" );
        for( auto &i : firestarters ) {
            if( has_charges( i->typeId(), quantity ) ) {
                return true;
            }
        }
    } else if (has_active_bionic( bio_tools ) && power_level > quantity * 5 ) {
        return true;
    } else if (has_bionic( bio_lighter ) && power_level > quantity * 5 ) {
        return true;
    } else if (has_bionic( bio_laser ) && power_level > quantity * 5 ) {
        return true;
    } else if( is_npc() ) {
        // A hack to make NPCs use their Molotovs
        return true;
    }
    return false;
}

void player::use_fire(const int quantity)
{
//Okay, so checks for nearby fires first,
//then held lit torch or candle, bionic tool/lighter/laser
//tries to use 1 charge of lighters, matches, flame throwers
//If there is enough power, will use power of one activation of the bio_lighter, bio_tools and bio_laser
// (home made, military), hotplate, welder in that order.
// bio_lighter, bio_laser, bio_tools, has_active_bionic("bio_tools"

    if( g->m.has_nearby_fire( pos() ) ) {
        return;
    } else if( has_item_with_flag( "FIRE" ) ) {
        return;
    } else if( has_item_with_flag( "FIRESTARTER" ) ) {
        auto firestarters = all_items_with_flag( "FIRESTARTER" );
        for( auto &i : firestarters ) {
            if( has_charges( i->typeId(), quantity ) ) {
                use_charges( i->typeId(), quantity );
                return;
            }
        }
    } else if (has_active_bionic( bio_tools ) && power_level > quantity * 5 ) {
        charge_power( -quantity * 5 );
        return;
    } else if (has_bionic( bio_lighter ) && power_level > quantity * 5 ) {
        charge_power( -quantity * 5 );
        return;
    } else if (has_bionic( bio_laser ) && power_level > quantity * 5 ) {
        charge_power( -quantity * 5 );
        return;
    }
}

std::list<item> player::use_charges( const itype_id& what, long qty )
{
    std::list<item> res;

    if( qty <= 0 ) {
        return res;

    } else if( what == "toolset" ) {
        charge_power( -qty );
        return res;

    } else if( what == "fire" ) {
        use_fire( qty );
        return res;

    } else if( what == "UPS" ) {
        if( power_level > 0 && has_active_bionic( bio_ups ) ) {
            auto bio = std::min( long( power_level ), qty );
            charge_power( -bio );
            qty -= std::min( qty, bio );
        }

        auto adv = charges_of( "adv_UPS_off", static_cast<long>( ceil( qty * 0.6 ) ) );
        if( adv > 0 ) {
            auto found = use_charges( "adv_UPS_off", adv );
            res.splice( res.end(), found );
            qty -= std::min( qty, long( adv / 0.6 ) );
        }

        auto ups = charges_of( "UPS_off", qty );
        if( ups > 0 ) {
            auto found = use_charges( "UPS_off", ups );
            res.splice( res.end(), found );
            qty -= std::min( qty, ups );
        }
    }

    std::vector<item *> del;

    bool has_tool_with_UPS = false;
    visit_items( [this, &what, &qty, &res, &del, &has_tool_with_UPS]( item *e ) {
        if( e->use_charges( what, qty, res, pos() ) ) {
            del.push_back( e );
        }
        if( e->typeId() == what && e->has_flag( "USE_UPS" ) ) {
            has_tool_with_UPS = true;
        }
        return qty > 0 ? VisitResponse::SKIP : VisitResponse::ABORT;
    } );

    for( auto e : del ) {
        remove_item( *e );
    }

    if( has_tool_with_UPS ) {
        use_charges( "UPS", qty );
    }

    return res;
}

bool player::covered_with_flag( const std::string &flag, const body_part_set &parts ) const
{
    if( parts.none() ) {
        return true;
    }

    body_part_set to_cover( parts );

    for( const auto &elem : worn ) {
        if( !elem.has_flag( flag ) ) {
            continue;
        }

        to_cover &= ~elem.get_covered_body_parts();

        if( to_cover.none() ) {
            return true;    // Allows early exit.
        }
    }

    return to_cover.none();
}

bool player::is_waterproof( const body_part_set &parts ) const
{
    return covered_with_flag("WATERPROOF", parts);
}

int player::amount_worn(const itype_id &id) const
{
    int amount = 0;
    for(auto &elem : worn) {
        if(elem.typeId() == id) {
            ++amount;
        }
    }
    return amount;
}

bool player::has_charges(const itype_id &it, long quantity, const std::function<bool( const item & )> &filter ) const
{
    if (it == "fire" || it == "apparatus") {
        return has_fire(quantity);
    }
    return charges_of( it, quantity, filter ) == quantity;
}

int  player::leak_level( const std::string &flag ) const
{
    int leak_level = 0;
    leak_level = inv.leak_level(flag);
    return leak_level;
}

bool player::has_mission_item(int mission_id) const
{
    return mission_id != -1 && has_item_with( has_mission_item_filter{ mission_id } );
}

//Returns the amount of charges that were consumed by the player
int player::drink_from_hands(item& water) {
    int charges_consumed = 0;
    if( query_yn( _( "Drink %s from your hands?" ),
                  colorize( water.type_name(), water.color_in_inventory() ) ) )
    {
        // Create a dose of water no greater than the amount of water remaining.
        item water_temp( water );
        // If player is slaked water might not get consumed.
        consume_item( water_temp );
        charges_consumed = water.charges - water_temp.charges;
        if( charges_consumed > 0 )
        {
            moves -= 350;
        }
    }

    return charges_consumed;
}

// @todo: Properly split medications and food instead of hacking around
bool player::consume_med( item &target )
{
    if( !target.is_medication() ) {
        return false;
    }

    const itype_id tool_type = target.type->comestible->tool;
    const auto req_tool = item::find_type( tool_type );
    bool tool_override = false;
    if( tool_type == "syringe" && has_bionic( bio_syringe ) ) {
        tool_override = true;
    }
    if( req_tool->tool ) {
        if( !( has_amount( tool_type, 1 ) && has_charges( tool_type, req_tool->tool->charges_per_use ) ) && !tool_override ) {
            add_msg_if_player( m_info, _( "You need a %s to consume that!" ), req_tool->nname( 1 ).c_str() );
            return false;
        }
        use_charges( tool_type, req_tool->tool->charges_per_use );
    }

    long amount_used = 1;
    if( target.type->has_use() ) {
        amount_used = target.type->invoke( *this, target, pos() );
        if( amount_used <= 0 ) {
            return false;
        }
    }

    // @todo: Get the target it was used on
    // Otherwise injecting someone will give us addictions etc.
    consume_effects( target );
    mod_moves( -250 );
    target.charges -= amount_used;
    return target.charges <= 0;
}

bool player::consume_item( item &target )
{
    if( target.is_null() ) {
        add_msg_if_player( m_info, _( "You do not have that item." ) );
        return false;
    }
    if( is_underwater() && !has_trait( trait_WATERSLEEP ) ) {
        add_msg_if_player( m_info, _( "You can't do that while underwater." ) );
        return false;
    }

    item &comest = get_comestible_from( target );

    if( comest.is_null() ) {
        add_msg_if_player( m_info, _( "You can't eat your %s." ), target.tname().c_str() );
        if(is_npc()) {
            debugmsg("%s tried to eat a %s", name.c_str(), target.tname().c_str());
        }
        return false;
    }

    if( consume_med( comest ) ||
        eat( comest ) ||
        feed_battery_with( comest ) ||
        feed_reactor_with( comest ) ||
        feed_furnace_with( comest ) ) {

        if( target.is_container() ) {
            target.on_contents_changed();
        }

        return comest.charges <= 0;
    }

    return false;
}

bool player::consume(int target_position)
{
    auto &target = i_at( target_position );

    if( consume_item( target ) ) {
        const bool was_in_container = !can_consume_as_is( target );

        if( was_in_container ) {
            i_rem( &target.contents.front() );
        } else {
            i_rem( &target );
        }

        //Restack and sort so that we don't lie about target's invlet
        if( target_position >= 0 ) {
            inv.restack( *this );
        }

        if( was_in_container && target_position == -1 ) {
            add_msg_if_player(_("You are now wielding an empty %s."), weapon.tname().c_str());
        } else if( was_in_container && target_position < -1 ) {
            add_msg_if_player(_("You are now wearing an empty %s."), target.tname().c_str());
        } else if( was_in_container && !is_npc() ) {
            bool drop_it = false;
            if (get_option<std::string>( "DROP_EMPTY" ) == "no") {
                drop_it = false;
            } else if (get_option<std::string>( "DROP_EMPTY" ) == "watertight") {
                drop_it = !target.is_watertight_container();
            } else if (get_option<std::string>( "DROP_EMPTY" ) == "all") {
                drop_it = true;
            }
            if (drop_it) {
                add_msg(_("You drop the empty %s."), target.tname().c_str());
                g->m.add_item_or_charges( pos(), inv.remove_item(&target) );
            } else {
                int quantity = inv.const_stack( inv.position_by_item( &target ) ).size();
                char letter = target.invlet ? target.invlet : ' ';
                add_msg( m_info, _( "%c - %d empty %s" ), letter, quantity, target.tname( quantity ).c_str() );
            }
        }
    } else if( target_position >= 0 ) {
        if( Pickup::handle_spillable_contents( *this, target, g->m ) ) {
            i_rem( &target );
        }
        inv.restack( *this );
        inv.unsort();
    }

    return true;
}

void player::rooted_message() const
{
    bool wearing_shoes = is_wearing_shoes( side::LEFT ) || is_wearing_shoes( side::RIGHT );
    if( (has_trait( trait_ROOTS2 ) || has_trait( trait_ROOTS3 ) ) &&
        g->m.has_flag("PLOWABLE", pos()) &&
        !wearing_shoes ) {
        add_msg(m_info, _("You sink your roots into the soil."));
    }
}

void player::rooted()
// Should average a point every two minutes or so; ground isn't uniformly fertile
// Overfilling triggered hibernation checks, so capping.
{
    double shoe_factor = footwear_factor();
    if( (has_trait( trait_ROOTS2 ) || has_trait( trait_ROOTS3 )) &&
        g->m.has_flag("PLOWABLE", pos()) && shoe_factor != 1.0 ) {
        if( one_in(20.0 / (1.0 - shoe_factor)) ) {
            if (get_hunger() > -20) {
                mod_hunger(-1);
            }
            if (get_thirst() > -20) {
                mod_thirst(-1);
            }
            mod_healthy_mod(5, 50);
        }
    }
}

item::reload_option player::select_ammo( const item &base, std::vector<item::reload_option> opts ) const
{
    using reload_option = item::reload_option;

    if( opts.empty() ) {
        add_msg_if_player( m_info, _( "Never mind." ) );
        return reload_option();
    }

    uilist menu;
    menu.text = string_format( base.is_watertight_container() ? _( "Refill %s" ) : base.has_flag( "RELOAD_AND_SHOOT" ) ? _( "Select ammo for %s" ) : _( "Reload %s" ),
            base.tname() );
    menu.w_width = -1;
    menu.w_height = -1;

    // Construct item names
    std::vector<std::string> names;
    std::transform( opts.begin(), opts.end(), std::back_inserter( names ), [&]( const reload_option& e ) {
        if( e.ammo->is_magazine() && e.ammo->ammo_data() ) {
            if( e.ammo->ammo_current() == "battery" ) {
                // This battery ammo is not a real object that can be recovered but pseudo-object that represents charge
                //~ magazine with ammo count
                return string_format( _( "%s (%d)" ), e.ammo->type_name().c_str(), e.ammo->ammo_remaining() );
            } else {
                //~ magazine with ammo (count)
                return string_format( _( "%s with %s (%d)" ), e.ammo->type_name().c_str(),
                                      e.ammo->ammo_data()->nname( e.ammo->ammo_remaining() ).c_str(), e.ammo->ammo_remaining() );
            }

        } else if( e.ammo->is_watertight_container() ||
                ( e.ammo->is_ammo_container() && g->u.is_worn( *e.ammo ) ) ) {
            // worn ammo containers should be named by their contents with their location also updated below
            return e.ammo->contents.front().display_name();

        } else {
            return ( ammo_location && ammo_location == e.ammo ? "* " : "" ) + e.ammo->display_name();
        }
    } );

    // Get location descriptions
    std::vector<std::string> where;
    std::transform( opts.begin(), opts.end(), std::back_inserter( where ), []( const reload_option& e ) {
        bool is_ammo_container = e.ammo->is_ammo_container();
        if( is_ammo_container || e.ammo->is_container() ) {
            if( is_ammo_container && g->u.is_worn( *e.ammo ) ) {
                return e.ammo->type_name();
            }
            return string_format( _("%s, %s"), e.ammo->type_name(), e.ammo.describe( &g->u ) );
        }
        return e.ammo.describe( &g->u );
    } );

    // Pads elements to match longest member and return length
    auto pad = []( std::vector<std::string>& vec, int n, int t ) -> int {
        for( const auto& e : vec ) {
            n = std::max( n, utf8_width( e, true ) + t );
        }
        for( auto& e : vec ) {
            e += std::string( n - utf8_width( e, true ), ' ' );
        }
        return n;
    };

    // Pad the first column including 4 trailing spaces
    int w = pad( names, utf8_width( menu.text, true ), 6 );
    menu.text.insert( 0, 2, ' ' ); // add space for UI hotkeys
    menu.text += std::string( w + 2 - utf8_width( menu.text, true ), ' ' );

    // Pad the location similarly (excludes leading "| " and trailing " ")
    w = pad( where, utf8_width( _( "| Location " ) ) - 3, 6 );
    menu.text += _("| Location " );
    menu.text += std::string( w + 3 - utf8_width( _( "| Location " ) ), ' ' );

    menu.text += _( "| Amount  " );
    menu.text += _( "| Moves   " );

    // We only show ammo statistics for guns and magazines
    if( base.is_gun() || base.is_magazine() ) {
        menu.text += _( "| Damage  | Pierce  " );
    }

    auto draw_row = [&]( int idx ) {
        const auto& sel = opts[ idx ];
        std::string row = string_format( "%s| %s |", names[ idx ].c_str(), where[ idx ].c_str() );
        row += string_format( ( sel.ammo->is_ammo() || sel.ammo->is_ammo_container() ) ? " %-7d |" : "         |", sel.qty() );
        row += string_format( " %-7d ", sel.moves() );

        if( base.is_gun() || base.is_magazine() ) {
            const itype *ammo = sel.ammo->is_ammo_container() ? sel.ammo->contents.front().ammo_data() : sel.ammo->ammo_data();
            if( ammo ) {
                if( ammo->ammo->prop_damage ) {
                    row += string_format("| *%-6d | %-7d", static_cast<int>( *ammo->ammo->prop_damage ),
                                         ammo->ammo->legacy_pierce );
                } else {
                    const damage_instance &dam = ammo->ammo->damage;
                    row += string_format("| %-7d | %-7d", static_cast<int>( dam.total_damage()),
                                         static_cast<int>( dam.empty() ? 0.0f : (*dam.begin()).res_pen ));
                }
            } else {
                row += "|         |         ";
            }
        }
        return row;
    };

    itype_id last = uistate.lastreload[ base.ammo_type() ];
    // We keep the last key so that pressing the key twice (for example, r-r for reload)
    // will always pick the first option on the list.
    int last_key = inp_mngr.get_previously_pressed_key();
    bool last_key_bound = false;
    // This is the entry that has out default
    int default_to = 0;

    // If last_key is RETURN, don't use that to override hotkey
    if ( last_key == '\n' ) {
        last_key_bound = true;
        default_to = -1;
    }

    for( auto i = 0; i < static_cast<int>( opts.size() ); ++i ) {
        const item& ammo = opts[ i ].ammo->is_ammo_container() ? opts[ i ].ammo->contents.front() : *opts[ i ].ammo;

        char hotkey = -1;
        if( g->u.has_item( ammo ) ) {
            // if ammo in player possession and either it or any container has a valid invlet use this
            if( ammo.invlet ) {
                hotkey = ammo.invlet;
            } else {
                for( const auto obj : g->u.parents( ammo ) ) {
                    if( obj->invlet ) {
                        hotkey = obj->invlet;
                        break;
                    }
                }
            }
        }
        if( last == ammo.typeId() ) {
            if( !last_key_bound && hotkey == -1 ) {
                // If this is the first occurrence of the most recently used type of ammo and the hotkey
                // was not already set above then set it to the keypress that opened this prompt
                hotkey = last_key;
                last_key_bound = true;
            }
            if( !last_key_bound ) {
                // Pressing the last key defaults to the first entry of compatible type
                default_to = i;
                last_key_bound = true;
            }
        }
        if( hotkey == last_key ) {
            last_key_bound = true;
            // Prevent the default from being used: key is bound to something already
            default_to = -1;
        }

        menu.addentry( i, true, hotkey, draw_row( i ) );
    }

    struct reload_callback : public uilist_callback {
        public:
            std::vector<item::reload_option> &opts;
            const std::function<std::string( int )> draw_row;
            int last_key;
            const int default_to;
            const bool can_partial_reload;

            reload_callback( std::vector<item::reload_option> &_opts,
                             std::function<std::string( int )> _draw_row,
                             int _last_key, int _default_to, bool _can_partial_reload ) :
                           opts( _opts ), draw_row( _draw_row ),
                           last_key( _last_key ), default_to( _default_to ),
                           can_partial_reload( _can_partial_reload )
            {}

            bool key( const input_context &, const input_event &event, int idx, uilist *menu ) override {
                auto cur_key = event.get_first_input();
                if( default_to != -1 && cur_key == last_key ) {
                    // Select the first entry on the list
                    menu->ret = default_to;
                    return true;
                }
                if( idx < 0 || idx >= static_cast<int>( opts.size() ) ) {
                    return false;
                }
                auto &sel = opts[ idx ];
                switch( cur_key ) {
                    case KEY_LEFT:
                        if ( can_partial_reload ) {
                            sel.qty( sel.qty() - 1 );
                            menu->entries[ idx ].txt = draw_row( idx );
                        }
                        return true;

                    case KEY_RIGHT:
                        if ( can_partial_reload ) {
                            sel.qty( sel.qty() + 1 );
                            menu->entries[ idx ].txt = draw_row( idx );
                        }
                        return true;
                }
                return false;
            }
    } cb( opts, draw_row, last_key, default_to, !base.has_flag( "RELOAD_ONE" ) );
    menu.callback = &cb;

    menu.query();
    if( menu.ret < 0 || static_cast<size_t>( menu.ret ) >= opts.size() ) {
        add_msg_if_player( m_info, _( "Never mind." ) );
        return reload_option();
    }

    const item_location& sel = opts[ menu.ret ].ammo;
    uistate.lastreload[ base.ammo_type() ] = sel->is_ammo_container() ? sel->contents.front().typeId() : sel->typeId();
    return std::move( opts[ menu.ret ] );
}

item::reload_option player::select_ammo( const item& base, bool prompt ) const
{
    using reload_option = item::reload_option;
    std::vector<reload_option> ammo_list;

    auto opts = base.gunmods();
    opts.push_back( &base );

    if( base.magazine_current() ) {
        opts.push_back( base.magazine_current() );
    }

    for( const auto mod : base.gunmods() ) {
        if( mod->magazine_current() ) {
            opts.push_back( mod->magazine_current() );
        }
    }

    bool ammo_match_found = false;
    for( const auto e : opts ) {
        for( item_location& ammo : find_ammo( *e ) ) {
            // don't try to unload frozen liquids
            if( ammo->is_watertight_container() && ammo->contents_made_of( SOLID ) ) {
                continue;
            }
            auto id = ( ammo->is_ammo_container() || ammo->is_container() )
                ? ammo->contents.front().typeId()
                : ammo->typeId();
            if( e->can_reload_with( id ) ) {
                // Speedloaders require an empty target.
                if( !ammo->has_flag( "SPEEDLOADER" ) || e->ammo_remaining() < 1 ) {
                    ammo_match_found = true;
                }
            }
            if( can_reload( *e, id ) || e->has_flag( "RELOAD_AND_SHOOT" ) ) {
                ammo_list.emplace_back( this, e, &base, std::move( ammo ) );
            }
        }
    }

    if( ammo_list.empty() ) {
        if( !base.is_magazine() && !base.magazine_integral() && !base.magazine_current() ) {
            add_msg_if_player( m_info, _( "You need a compatible magazine to reload the %s!" ), base.tname().c_str() );

        } else if( ammo_match_found ) {
            add_msg_if_player( m_info, _( "Nothing to reload!" ) );
        } else {
            std::string name;
            if ( base.ammo_data() ) {
                name = base.ammo_data()->nname( 1 );
            } else if ( base.is_watertight_container() ) {
                name = base.is_container_empty() ? "liquid" : base.contents.front().tname();
            } else {
                name = base.ammo_type()->name();
            }
            add_msg_if_player( m_info, _( "You don't have any %s to reload your %s!" ),
                               name.c_str(), base.tname() );
        }
        return reload_option();
    }

    // sort in order of move cost (ascending), then remaining ammo (descending) with empty magazines always last
    std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const reload_option& lhs, const reload_option& rhs ) {
        return lhs.ammo->ammo_remaining() > rhs.ammo->ammo_remaining();
    } );
    std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const reload_option& lhs, const reload_option& rhs ) {
        return lhs.moves() < rhs.moves();
    } );
    std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const reload_option& lhs, const reload_option& rhs ) {
        return ( lhs.ammo->ammo_remaining() != 0 ) > ( rhs.ammo->ammo_remaining() != 0 );
    } );

    if( is_npc() ) {
        return std::move( ammo_list[ 0 ] );
    }

    if( !prompt && ammo_list.size() == 1 ) {
        // unconditionally suppress the prompt if there's only one option
        return std::move( ammo_list[ 0 ] );
    }

    return select_ammo( base, std::move( ammo_list ) );
}

ret_val<bool> player::can_wear( const item& it  ) const
{
    if( !it.is_armor() ) {
        return ret_val<bool>::make_failure( _( "Putting on a %s would be tricky." ), it.tname().c_str() );
    }

    if( it.is_power_armor() ) {
        for( auto &elem : worn ) {
            if( ( elem.get_covered_body_parts() & it.get_covered_body_parts() ).any() ) {
                return ret_val<bool>::make_failure( _( "Can't wear power armor over other gear!" ) );
            }
        }
        if( !it.covers( bp_torso ) ) {
            bool power_armor = false;
            if( !worn.empty() ) {
                for( auto &elem : worn ) {
                    if( elem.is_power_armor() ) {
                        power_armor = true;
                        break;
                    }
                }
            }
            if( !power_armor ) {
                return ret_val<bool>::make_failure( _( "You can only wear power armor components with power armor!" ) );
            }
        }

        for( auto &i : worn ) {
            if( i.is_power_armor() && i.typeId() == it.typeId() ) {
                return ret_val<bool>::make_failure( _( "Can't wear more than one %s!" ), it.tname().c_str() );
            }
        }
    } else {
        // Only headgear can be worn with power armor, except other power armor components.
        // You can't wear headgear if power armor helmet is already sitting on your head.
        bool has_helmet = false;
        if( is_wearing_power_armor( &has_helmet ) &&
            ( has_helmet || !(it.covers( bp_head ) || it.covers( bp_mouth ) || it.covers( bp_eyes ) ) ) ) {
            return ret_val<bool>::make_failure( _( "Can't wear %s with power armor!" ), it.tname().c_str() );
        }
    }

    // Check if we don't have both hands available before wearing a briefcase, shield, etc. Also occurs if we're already wearing one.
    if( it.has_flag( "RESTRICT_HANDS" ) && ( !has_two_arms() || worn_with_flag( "RESTRICT_HANDS" ) || weapon.is_two_handed( *this ) ) ) {
        return ret_val<bool>::make_failure( ( is_player() ? _( "You don't have a hand free to wear that." )
                                              : string_format( _( "%s doesn't have a hand free to wear that." ), name.c_str() ) ) );
    }

    for( auto &i : worn ) {
        if( i.has_flag( "ONLY_ONE" ) && i.typeId() == it.typeId() ) {
            return ret_val<bool>::make_failure( _( "Can't wear more than one %s!" ), it.tname().c_str() );
        }
    }

    if( amount_worn( it.typeId() ) >= MAX_WORN_PER_TYPE ) {
        return ret_val<bool>::make_failure( _( "Can't wear %i or more %s at once." ),
                               MAX_WORN_PER_TYPE + 1, it.tname( MAX_WORN_PER_TYPE + 1 ).c_str() );
    }

    if( ( ( it.covers( bp_foot_l ) && is_wearing_shoes( side::LEFT ) ) ||
          ( it.covers( bp_foot_r ) && is_wearing_shoes( side::RIGHT ) ) ) &&
          ( !it.has_flag( "OVERSIZE" ) || !it.has_flag( "OUTER" ) ) &&
          !it.has_flag( "SKINTIGHT" ) && !it.has_flag( "BELTED" ) ) {
        // Checks to see if the player is wearing shoes
        return ret_val<bool>::make_failure( ( is_player() ? _( "You're already wearing footwear!" )
                                              : string_format( _( "%s is already wearing footwear!" ), name.c_str() ) ) );
    }

    if( it.covers( bp_head ) &&
        !it.has_flag( "HELMET_COMPAT" ) &&
        !it.has_flag( "SKINTIGHT" ) &&
        !it.has_flag( "OVERSIZE" ) &&
        is_wearing_helmet() ) {
        return ret_val<bool>::make_failure( wearing_something_on( bp_head ),
                                            ( is_player() ? _( "You can't wear that with other headgear!" )
                                             : string_format( _( "%s can't wear that with other headgear!" ), name.c_str() ) ) );
    }

    if( it.covers( bp_head ) &&
        ( it.has_flag( "SKINTIGHT" ) || it.has_flag( "HELMET_COMPAT" ) ) &&
        ( head_cloth_encumbrance() + it.get_encumber( *this ) > 20 ) ) {
        return ret_val<bool>::make_failure( ( is_player() ? _( "You can't wear that much on your head!" )
                                              : string_format( _( "%s can't wear that much on their head!" ), name.c_str() ) ) );
    }

    if( has_trait( trait_WOOLALLERGY ) && ( it.made_of( material_id( "wool" ) ) || it.item_tags.count( "wooled" ) ) ) {
        return ret_val<bool>::make_failure( _( "Can't wear that, it's made of wool!" ) );
    }

    if( it.is_filthy() && has_trait( trait_SQUEAMISH ) ) {
        return ret_val<bool>::make_failure( _( "Can't wear that, it's filthy!" ) );
    }

    if( !it.has_flag( "OVERSIZE" ) ) {
        for( const trait_id &mut : get_mutations() ) {
            const auto &branch = mut.obj();
            if( branch.conflicts_with_item( it ) ) {
                return ret_val<bool>::make_failure( _( "Your %s mutation prevents you from wearing your %s." ),
                             branch.name(), it.type_name().c_str() );
            }
        }
        if( it.covers(bp_head) &&
            !it.made_of( material_id( "wool" ) ) && !it.made_of( material_id( "cotton" ) ) &&
            !it.made_of( material_id( "nomex" ) ) && !it.made_of( material_id( "leather" ) ) &&
            ( has_trait( trait_HORNS_POINTED ) || has_trait( trait_ANTENNAE ) || has_trait( trait_ANTLERS ) ) ) {
            return ret_val<bool>::make_failure( _( "Cannot wear a helmet over %s." ),
                            ( has_trait( trait_HORNS_POINTED ) ? _( "horns" ) :
                            ( has_trait( trait_ANTENNAE ) ? _( "antennae" ) : _( "antlers" ) ) ) );
        }
    }

    return ret_val<bool>::make_success();
}

ret_val<bool> player::can_wield( const item &it ) const
{
    if( it.made_of_from_type( LIQUID ) ) {
        return ret_val<bool>::make_failure( _( "Can't wield spilt liquids." ) );
    }

    if( it.is_two_handed( *this ) && ( !has_two_arms() || worn_with_flag( "RESTRICT_HANDS" ) ) ) {
        if( worn_with_flag( "RESTRICT_HANDS" ) ) {
            return ret_val<bool>::make_failure( _( "Something you are wearing hinders the use of both hands." ) );
        } else if( it.has_flag( "ALWAYS_TWOHAND" ) ) {
            return ret_val<bool>::make_failure( _( "The %s can't be wielded with only one arm." ), it.tname().c_str() );
        } else {
            return ret_val<bool>::make_failure( _( "You are too weak to wield %s with only one arm." ), it.tname().c_str() );
        }
    }

    return ret_val<bool>::make_success();
}

ret_val<bool> player::can_unwield( const item& it ) const
{
    if( it.has_flag( "NO_UNWIELD" ) ) {
        return ret_val<bool>::make_failure( _( "You cannot unwield your %s." ), it.tname().c_str() );
    }

    return ret_val<bool>::make_success();
}

bool player::is_wielding( const item& target ) const
{
    return &weapon == &target;
}

bool player::wield( item& target )
{
    if( is_wielding( target ) ) {
        return true;
    }

    if( !can_wield( target ).success() ) {
        return false;
    }

    if( !unwield() ) {
        return false;
    }

    if( target.is_null() ) {
        return true;
    }

    // Query whether to draw an item from a holster when attempting to wield the holster
    if( target.get_use( "holster" ) && !target.contents.empty() ) {
        if( query_yn( _( "Draw %s from %s?" ), target.get_contained().tname(), target.tname() ) ) {
            invoke_item( &target );
            return false;
        }
    }

    // Wielding from inventory is relatively slow and does not improve with increasing weapon skill.
    // Worn items (including guns with shoulder straps) are faster but still slower
    // than a skilled player with a holster.
    // There is an additional penalty when wielding items from the inventory whilst currently grabbed.

    bool worn = is_worn( target );
    int mv = item_handling_cost( target, true, worn ? INVENTORY_HANDLING_PENALTY / 2 : INVENTORY_HANDLING_PENALTY );

    if( worn ) {
        target.on_takeoff( *this );
    }

    add_msg( m_debug, "wielding took %d moves", mv );
    moves -= mv;

    if( has_item( target ) ) {
        weapon = i_rem( &target );
    } else {
        weapon = target;
    }

    last_item = weapon.typeId();
    recoil = MAX_RECOIL;

    weapon.on_wield( *this, mv );

    inv.update_invlet( weapon );
    inv.update_cache_with_item( weapon );

    return true;
}

bool player::unwield()
{
    if( weapon.is_null() ) {
        return true;
    }

    if( !can_unwield( weapon ).success() ) {
        return false;
    }

    const std::string query = string_format( _( "Stop wielding %s?" ), weapon.tname().c_str() );

    if ( !dispose_item( item_location( *this, &weapon ), query ) ) {
        return false;
    }

    inv.unsort();

    return true;
}

// ids of martial art styles that are available with the bio_cqb bionic.
static const std::vector<matype_id> bio_cqb_styles {{
    matype_id{ "style_karate" }, matype_id{ "style_judo" }, matype_id{ "style_muay_thai" }, matype_id{ "style_biojutsu" }
}};

bool player::pick_style() // Style selection menu
{
    enum style_selection {
        KEEP_HANDS_FREE = 0,
        STYLE_OFFSET
    };

    // If there are style already, cursor starts there
    // if no selected styles, cursor starts from no-style

    // Any other keys quit the menu
    const std::vector<matype_id> &selectable_styles = has_active_bionic( bio_cqb ) ? bio_cqb_styles : ma_styles;

    input_context ctxt( "MELEE_STYLE_PICKER" );
    ctxt.register_action( "SHOW_DESCRIPTION" );

    uilist kmenu;
    kmenu.text = string_format( _( "Select a style. (press %s for more info)" ), ctxt.get_desc( "SHOW_DESCRIPTION" ).c_str() );
    ma_style_callback callback( static_cast<size_t>( STYLE_OFFSET ), selectable_styles );
    kmenu.callback = &callback;
    kmenu.input_category = "MELEE_STYLE_PICKER";
    kmenu.additional_actions.emplace_back( "SHOW_DESCRIPTION", "" );
    kmenu.desc_enabled = true;
    kmenu.addentry_desc( KEEP_HANDS_FREE, true, 'h', keep_hands_free ? _( "Keep hands free (on)" ) : _( "Keep hands free (off)" ),
                         _( "When this is enabled, player won't wield things unless explicitly told to." ) );

    kmenu.selected = STYLE_OFFSET;

    for( size_t i = 0; i < selectable_styles.size(); i++ ) {
        auto &style = selectable_styles[i].obj();
        //Check if this style is currently selected
        const bool selected = selectable_styles[i] == style_selected;
        std::string entry_text = _( style.name.c_str() );
        if( selected ) {
            kmenu.selected = i + STYLE_OFFSET;
            entry_text = colorize( entry_text, c_pink );
        }
        kmenu.addentry_desc( i + STYLE_OFFSET, true, -1, entry_text, _( style.description.c_str() ) );
    }

    kmenu.query();
    int selection = kmenu.ret;

    if( selection >= STYLE_OFFSET ) {
        style_selected = selectable_styles[selection - STYLE_OFFSET];
    } else if( selection == KEEP_HANDS_FREE ) {
        keep_hands_free = !keep_hands_free;
    } else {
        return false;
    }

    return true;
}

hint_rating player::rate_action_wear( const item &it ) const
{
    //TODO flag already-worn items as HINT_IFFY

    if (!it.is_armor()) {
        return HINT_CANT;
    }

    return can_wear( it ).success() ? HINT_GOOD : HINT_IFFY;
}

hint_rating player::rate_action_change_side( const item &it ) const {
   if (!is_worn(it)) {
      return HINT_IFFY;
   }

    if (!it.is_sided()) {
        return HINT_CANT;
    }

    return HINT_GOOD;
}

bool player::can_reload( const item& it, const itype_id& ammo ) const {
    if( !it.is_reloadable_with( ammo ) ) {
        return false;
    }

    if( it.is_ammo_belt() ) {
        const auto &linkage = it.type->magazine->linkage;
        if( linkage && !has_charges( *linkage, 1 ) ) {
            return false;
        }
    }

    return true;
}

bool player::dispose_item( item_location &&obj, const std::string& prompt )
{
    uilist menu;
    menu.text = prompt.empty() ? string_format( _( "Dispose of %s" ), obj->tname().c_str() ) : prompt;

    using dispose_option = struct {
        std::string prompt;
        bool enabled;
        char invlet;
        int moves;
        std::function<bool()> action;
    };

    std::vector<dispose_option> opts;

    const bool bucket = obj->is_bucket_nonempty();

    opts.emplace_back( dispose_option {
        bucket ? _( "Spill contents and store in inventory" ) : _( "Store in inventory" ),
        volume_carried() + obj->volume() <= volume_capacity(), '1',
        item_handling_cost( *obj ),
        [this, bucket, &obj] {
            if( bucket && !obj->spill_contents( *this ) ) {
                return false;
            }

            moves -= item_handling_cost( *obj );
            inv.add_item_keep_invlet( *obj );
            inv.unsort();
            obj.remove_item();
            return true;
        }
    } );

    opts.emplace_back( dispose_option {
        _( "Drop item" ), true, '2', 0, [this, &obj] {
            g->m.add_item_or_charges( pos(), *obj );
            obj.remove_item();
            return true;
        }
    } );

    opts.emplace_back( dispose_option {
        bucket ? _( "Spill contents and wear item" ) : _( "Wear item" ),
        can_wear( *obj ).success(), '3', item_wear_cost( *obj ),
        [this, bucket, &obj] {
            if( bucket && !obj->spill_contents( *this ) ) {
                return false;
            }

            item it = *obj;
            obj.remove_item();
            return !!wear_item( it );
        }
    } );

    for( auto& e : worn ) {
        if( e.can_holster( *obj ) ) {
            auto ptr = dynamic_cast<const holster_actor *>( e.type->get_use( "holster" )->get_actor_ptr() );
            opts.emplace_back( dispose_option {
                string_format( _( "Store in %s" ), e.tname().c_str() ), true, e.invlet,
                item_store_cost( *obj, e, false, ptr->draw_cost ),
                [this, ptr, &e, &obj]{
                    return ptr->store( *this, e, *obj );
                }
            } );
        }
    }

    int w = utf8_width( menu.text, true ) + 4;
    for( const auto& e : opts ) {
        w = std::max( w, utf8_width( e.prompt, true ) + 4 );
    }
    for( auto& e : opts ) {
        e.prompt += std::string( w - utf8_width( e.prompt, true ), ' ' );
    }

    menu.text.insert( 0, 2, ' ' ); // add space for UI hotkeys
    menu.text += std::string( w + 2 - utf8_width( menu.text, true ), ' ' );
    menu.text += _( " | Moves  " );

    for( const auto& e : opts ) {
        menu.addentry( -1, e.enabled, e.invlet, string_format( e.enabled ? "%s | %-7d" : "%s |", e.prompt.c_str(), e.moves ) );
    }

    menu.query();
    if( menu.ret >= 0 ) {
        return opts[ menu.ret ].action();
    }
    return false;
}

void player::mend_item( item_location&& obj, bool interactive )
{
    if( g->u.has_trait( trait_DEBUG_HS ) ) {
        uilist menu;
        menu.text = _( "Toggle which fault?" );
        std::vector<std::pair<fault_id, bool>> opts;
        for( const auto& f : obj->faults_potential() ) {
            opts.emplace_back( f, !!obj->faults.count( f ) );
            menu.addentry( -1, true, -1, string_format( "%s %s", opts.back().second ? _( "Mend" ) : _( "Break" ),
                                                        f.obj().name().c_str() ) );
        }
        if( opts.empty() ) {
            add_msg( m_info, _( "The %s doesn't have any faults to toggle." ), obj->tname() );
            return;
        }
        menu.query();
        if( menu.ret >= 0 ) {
            if( opts[ menu.ret ].second ) {
                obj->faults.erase( opts[ menu.ret ].first );
            } else {
                obj->faults.insert( opts[ menu.ret ].first );
            }
        }
        return;
    }

    std::vector<std::pair<const fault *, bool>> faults;
    std::transform( obj->faults.begin(), obj->faults.end(), std::back_inserter( faults ), []( const fault_id& e ) {
        return std::make_pair<const fault *, bool>( &e.obj(), false );
    } );

    if( faults.empty() ) {
        if( interactive ) {
            add_msg( m_info, _( "The %s doesn't have any faults to mend." ), obj->tname().c_str() );
        }
        return;
    }

    auto inv = crafting_inventory();

    for( auto& f : faults ) {
        f.second = f.first->requirements().can_make_with_inventory( inv );
    }

    int sel = 0;
    if( interactive ) {
        uilist menu;
        menu.text = _( "Mend which fault?" );
        menu.desc_enabled = true;
        menu.desc_lines = 0; // Let uilist handle description height

        int w = 80;

        for( auto& f : faults ) {
            auto reqs = f.first->requirements();
            auto tools = reqs.get_folded_tools_list( w, c_white, inv );
            auto comps = reqs.get_folded_components_list( w, c_white, inv );

            std::ostringstream descr;
            descr << _( "<color_white>Time required:</color>\n" );
            //@todo: better have a from_moves function
            descr << "> " << to_string_approx( time_duration::from_turns( f.first->time() / 100 ) ) << "\n";
            descr << _( "<color_white>Skills:</color>\n" );
            for( const auto& e : f.first->skills() ) {
                bool hasSkill = get_skill_level( e.first ) >= e.second;
                if ( !hasSkill && f.second ) {
                    f.second = false;
                }
                //~ %1$s represents the internal color name which shouldn't be translated, %2$s is skill name, and %3$i is skill level
                descr << string_format( _( "> <color_%1$s>%2$s %3$i</color>\n" ), hasSkill ? "c_green" : "c_red",
                                        e.first.obj().name().c_str(), e.second );
            }

            std::copy( tools.begin(), tools.end(), std::ostream_iterator<std::string>( descr, "\n" ) );
            std::copy( comps.begin(), comps.end(), std::ostream_iterator<std::string>( descr, "\n" ) );

            menu.addentry_desc( -1, true, -1, f.first->name(), descr.str() );
        }
        menu.query();
        if( menu.ret < 0 ) {
            add_msg( _( "Never mind." ) );
            return;
        }
        sel = menu.ret;
    }

    if( sel >= 0 ) {
        if( !faults[ sel ].second ) {
            if( interactive ) {
                add_msg( m_info, _( "You are currently unable to mend the %s." ), obj->tname().c_str() );
            }
            return;
        }

        assign_activity( activity_id( "ACT_MEND_ITEM" ), faults[ sel ].first->time() );
        activity.name = faults[ sel ].first->id().str();
        activity.targets.push_back( std::move( obj ) );
    }
}

int player::item_handling_cost( const item& it, bool penalties, int base_cost ) const
{
    int mv = base_cost;
    if( penalties ) {
        // 40 moves per liter, up to 200 at 5 liters
        mv += std::min( 200, it.volume() / 20_ml );
    }

    if( weapon.typeId() == "e_handcuffs" ) {
        mv *= 4;
    } else if( penalties && has_effect( effect_grabbed ) ) {
        mv *= 2;
    }

    // For single handed items use the least encumbered hand
    if( it.is_two_handed( *this ) ) {
        mv += encumb( bp_hand_l ) + encumb( bp_hand_r );
    } else {
        mv += std::min( encumb( bp_hand_l ), encumb( bp_hand_r ) );
    }

    return std::min( std::max( mv, 0 ), MAX_HANDLING_COST );
}

int player::item_store_cost( const item& it, const item& /* container */, bool penalties, int base_cost ) const
{
    /** @EFFECT_PISTOL decreases time taken to store a pistol */
    /** @EFFECT_SMG decreases time taken to store an SMG */
    /** @EFFECT_RIFLE decreases time taken to store a rifle */
    /** @EFFECT_SHOTGUN decreases time taken to store a shotgun */
    /** @EFFECT_LAUNCHER decreases time taken to store a launcher */
    /** @EFFECT_STABBING decreases time taken to store a stabbing weapon */
    /** @EFFECT_CUTTING decreases time taken to store a cutting weapon */
    /** @EFFECT_BASHING decreases time taken to store a bashing weapon */
    int lvl = get_skill_level( it.is_gun() ? it.gun_skill() : it.melee_skill() );
    return item_handling_cost( it, penalties, base_cost ) / ( ( lvl + 10.0f ) / 10.0f );
}

int player::item_reload_cost( const item& it, const item& ammo, long qty ) const
{
    if( ammo.is_ammo() ) {
        qty = std::max( std::min( ammo.charges, qty ), 1L );
    } else if( ammo.is_ammo_container() || ammo.is_container() ) {
        qty = std::max( std::min( ammo.contents.front().charges, qty ), 1L );
    } else if( ammo.is_magazine() ) {
        qty = 1;
    } else {
        debugmsg( "cannot determine reload cost as %s is neither ammo or magazine", ammo.tname().c_str() );
        return 0;
    }

    // If necessary create duplicate with appropriate number of charges
    item obj = ammo;
    obj = obj.split( qty );
    if( obj.is_null() ) {
        obj = ammo;
    }
    // No base cost for handling ammo - that's already included in obtain cost
    // We have the ammo in our hands right now
    int mv = item_handling_cost( obj, true, 0 );

    if( ammo.has_flag( "MAG_BULKY" ) ) {
        mv *= 1.5; // bulky magazines take longer to insert
    }

    if( !it.is_gun() && !it.is_magazine() ) {
        return mv + 100; // reload a tool or sealable container
    }

    /** @EFFECT_GUN decreases the time taken to reload a magazine */
    /** @EFFECT_PISTOL decreases time taken to reload a pistol */
    /** @EFFECT_SMG decreases time taken to reload an SMG */
    /** @EFFECT_RIFLE decreases time taken to reload a rifle */
    /** @EFFECT_SHOTGUN decreases time taken to reload a shotgun */
    /** @EFFECT_LAUNCHER decreases time taken to reload a launcher */

    int cost = ( it.is_gun() ? it.get_reload_time() : it.type->magazine->reload_time ) * qty;

    skill_id sk = it.is_gun() ? it.type->gun->skill_used : skill_gun;
    mv += cost / ( 1.0f + std::min( get_skill_level( sk ) * 0.1f, 1.0f ) );

    if( it.has_flag( "STR_RELOAD" ) ) {
        /** @EFFECT_STR reduces reload time of some weapons */
        mv -= get_str() * 20;
    }

    return std::max( mv, 25 );
}

int player::item_wear_cost( const item& it ) const
{
    double mv = item_handling_cost( it );

    switch( it.get_layer() ) {
        case UNDERWEAR:
            mv *= 1.5;
            break;

        case REGULAR_LAYER:
            break;

        case WAIST_LAYER:
        case OUTER_LAYER:
            mv /= 1.5;
            break;

        case BELTED_LAYER:
            mv /= 2.0;
            break;
        default:
            break;
    }

    mv *= std::max( it.get_encumber( *this ) / 10.0, 1.0 );

    return mv;
}

cata::optional<std::list<item>::iterator>
player::wear( int pos, bool interactive )
{
    return wear( i_at( pos ), interactive );
}

cata::optional<std::list<item>::iterator>
player::wear( item& to_wear, bool interactive )
{
    if( is_worn( to_wear ) ) {
        if( interactive ) {
            add_msg_player_or_npc( m_info,
                                   _( "You are already wearing that." ),
                                   _( "<npcname> is already wearing that.")
                                   );
        }
        return cata::nullopt;
    }
    if( to_wear.is_null() ) {
        if( interactive ) {
            add_msg_player_or_npc( m_info,
                                   _( "You don't have that item."),
                                   _( "<npcname> doesn't have that item."));
        }
        return cata::nullopt;
    }

    bool was_weapon;
    item to_wear_copy( to_wear );
    if( &to_wear == &weapon ) {
        weapon = item();
        was_weapon = true;
    } else {
        inv.remove_item( &to_wear );
        inv.restack( *this );
        was_weapon = false;
    }

    auto result = wear_item( to_wear_copy, interactive );
    if( !result ) {
        if( was_weapon ) {
            weapon = to_wear_copy;
        } else {
            inv.add_item( to_wear_copy, true );
        }
        return cata::nullopt;
    }

    return result;
}

cata::optional<std::list<item>::iterator>
player::wear_item( const item &to_wear, bool interactive )
{
    const auto ret = can_wear( to_wear );
    if( !ret.success() ) {
        if( interactive ) {
            add_msg_if_player( m_info, "%s", ret.c_str() );
        }
        return cata::nullopt;
    }

    const bool was_deaf = is_deaf();
    const bool supertinymouse = g->u.has_trait( trait_id( "SMALL2" ) ) || g->u.has_trait( trait_id( "SMALL_OK" ) );
    last_item = to_wear.typeId();

    std::list<item>::iterator position = position_to_wear_new_item( to_wear );
    std::list<item>::iterator new_item_it = worn.insert( position, to_wear );

    if( interactive ) {
        add_msg_player_or_npc(
                              _("You put on your %s."),
                              _("<npcname> puts on their %s."),
                              to_wear.tname().c_str() );
        moves -= item_wear_cost( to_wear );

        for( const body_part bp : all_body_parts ) {
            if (to_wear.covers(bp) && encumb(bp) >= 40)
            {
                add_msg_if_player(m_warning,
                    bp == bp_eyes ?
                    _("Your %s are very encumbered! %s"):_("Your %s is very encumbered! %s"),
                    body_part_name( bp ), encumb_text( bp ) );
            }
        }
        if( !was_deaf && is_deaf() ) {
            add_msg_if_player( m_info, _( "You're deafened!" ) );
        }
        if( supertinymouse && !to_wear.has_flag( "UNDERSIZE" ) ) {
            add_msg_if_player( m_warning, _( "This %s is too big to wear comfortably! Maybe it could be refitted..."), to_wear.tname().c_str() );
        } else if( to_wear.has_flag( "UNDERSIZE" ) ) {
            add_msg_if_player( m_warning, _( "This %s is too small to wear comfortably! Maybe it could be refitted..."), to_wear.tname().c_str() );
        }
    } else {
        add_msg_if_npc( _("<npcname> puts on their %s."), to_wear.tname().c_str() );
    }

    new_item_it->on_wear( *this );

    inv.update_invlet( *new_item_it );
    inv.update_cache_with_item( *new_item_it );

    recalc_sight_limits();
    reset_encumbrance();

    return new_item_it;
}

bool player::change_side( item &it, bool interactive )
{
    if( !it.swap_side() ) {
        if( interactive ) {
            add_msg_player_or_npc( m_info,
                                   _( "You cannot swap the side on which your %s is worn." ),
                                   _( "<npcname> cannot swap the side on which their %s is worn." ),
                                   it.tname().c_str() );
        }
        return false;
    }

    if( interactive ) {
        add_msg_player_or_npc( m_info, _( "You swap the side on which your %s is worn." ),
                                       _( "<npcname> swaps the side on which their %s is worn." ),
                                       it.tname().c_str() );
    }

    mod_moves( -250 );
    reset_encumbrance();

    return true;
}

bool player::change_side (int pos, bool interactive) {
    item &it( i_at( pos ) );

    if( !is_worn( it ) ) {
        if( interactive ) {
            add_msg_player_or_npc( m_info,
                                   _( "You are not wearing that item." ),
                                   _( "<npcname> isn't wearing that item." ) );
        }
        return false;
    }

    return change_side( it, interactive );
}

hint_rating player::rate_action_takeoff( const item &it ) const
{
    if (!it.is_armor()) {
        return HINT_CANT;
    }

    if (is_worn(it)) {
      return HINT_GOOD;
    }

    return HINT_IFFY;
}

std::list<const item *> player::get_dependent_worn_items( const item &it ) const
{
    std::list<const item *> dependent;
    // Adds dependent worn items recursively
    const std::function<void( const item &it )> add_dependent = [ & ]( const item &it ) {
        for( const auto &wit : worn ) {
            if( &wit == &it || !wit.is_worn_only_with( it ) ) {
                continue;
            }
            const auto iter = std::find_if( dependent.begin(), dependent.end(),
                [ &wit ]( const item *dit ) {
                    return &wit == dit;
                } );
            if( iter == dependent.end() ) { // Not in the list yet
                add_dependent( wit );
                dependent.push_back( &wit );
            }
        }
    };

    if( is_worn( it ) ) {
        add_dependent( it );
    }

    return dependent;
}

ret_val<bool> player::can_takeoff( const item& it, const std::list<item> *res ) const
{
    auto iter = std::find_if( worn.begin(), worn.end(), [ &it ]( const item &wit ) {
        return &it == &wit;
    } );

    if( iter == worn.end() ) {
        return ret_val<bool>::make_failure( !is_npc() ? _( "You are not wearing that item." ) : _( "<npcname> is not wearing that item." ) );
    }

    if( res == nullptr && !get_dependent_worn_items( it ).empty() ) {
        return ret_val<bool>::make_failure( !is_npc() ? _( "You can't take off power armor while wearing other power armor components." ) : _( "<npcname> can't take off power armor while wearing other power armor components." ) );
    }

    return ret_val<bool>::make_success();
}

bool player::takeoff( const item &it, std::list<item> *res )
{
    const auto ret = can_takeoff( it, res );
    if ( !ret.success() ) {
        add_msg( m_info, "%s", ret.c_str() );
        return false;
    }

    auto iter = std::find_if( worn.begin(), worn.end(), [ &it ]( const item &wit ) {
        return &it == &wit;
    } );

    if( res == nullptr ) {
        if( volume_carried() + it.volume() > volume_capacity_reduced_by( it.get_storage() ) ) {
            if( is_npc() || query_yn( _( "No room in inventory for your %s.  Drop it?" ),
                                      colorize( it.tname(), it.color_in_inventory() ) ) ) {
                drop( get_item_position( &it ), pos() );
                return true; // the drop activity ends up taking off the item anyway so shouldn't try to do it again here
            } else {
                return false;
            }
        }
        iter->on_takeoff( *this );
        inv.add_item_keep_invlet( it );
    } else {
        iter->on_takeoff( *this );
        res->push_back( it );
    }

    add_msg_player_or_npc( _( "You take off your %s." ),
                           _( "<npcname> takes off their %s." ),
                           it.tname().c_str() );

    mod_moves( -250 );    // TODO: Make this variable
    worn.erase( iter );

    recalc_sight_limits();
    reset_encumbrance();

    return true;
}

bool player::takeoff( int pos )
{
    return takeoff( i_at( pos ) );
}

void player::drop( int pos, const tripoint &where )
{
    const item &it = i_at( pos );
    const int count = it.count();

    drop( { std::make_pair( pos, count ) }, where );
}

void player::drop( const std::list<std::pair<int, int>> &what, const tripoint &target, bool stash )
{
    const activity_id type( stash ? "ACT_STASH" : "ACT_DROP" );

    if( what.empty() ) {
        return;
    }

    if( rl_dist( pos(), target ) > 1 || !( stash || g->m.can_put_items( target ) ) ) {
        add_msg_player_or_npc( m_info, _( "You can't place items here!" ),
                                       _( "<npcname> can't place items here!" ) );
        return;
    }

    assign_activity( type );
    activity.placement = target - pos();

    for( auto item_pair : what ) {
        if( can_unwield( i_at( item_pair.first ) ).success() ) {
            activity.values.push_back( item_pair.first );
            activity.values.push_back( item_pair.second );
        }
    }
    // @todo: Remove the hack. Its here because npcs don't process activities
    if( is_npc() ) {
        activity.do_turn( *this );
    }
}

bool player::add_or_drop_with_msg( item &it, const bool unloading )
{
    if( it.made_of( LIQUID ) ) {
        g->consume_liquid( it, 1 );
        return it.charges <= 0;
    }
    it.charges = this->i_add_to_container( it, unloading );
    if( it.is_ammo() && it.charges == 0 ) {
        return true;
    } else if( !this->can_pickVolume( it ) ) {
        put_into_vehicle_or_drop( *this, item_drop_reason::too_large, { it } );
    } else if( !this->can_pickWeight( it, !get_option<bool>( "DANGEROUS_PICKUPS" ) ) ) {
        put_into_vehicle_or_drop( *this, item_drop_reason::too_heavy, { it } );
    } else {
        auto &ni = this->i_add( it );
        add_msg( _( "You put the %s in your inventory." ), ni.tname().c_str() );
        add_msg( m_info, "%c - %s", ni.invlet == 0 ? ' ' : ni.invlet, ni.tname().c_str() );
    }
    return true;
}

bool player::unload(item &it)
{
    // Unload a container consuming moves per item successfully removed
    if( it.is_container() || it.is_bandolier() ) {
        if( it.contents.empty() ) {
            add_msg( m_info, _( "The %s is already empty!" ), it.tname().c_str() );
            return false;
        }
        if( !it.can_unload_liquid() ) {
            add_msg( m_info, _( "The liquid can't be unloaded in its current state!" ) );
            return false;
        }

        bool changed = false;
        it.contents.erase( std::remove_if( it.contents.begin(), it.contents.end(), [this,
        &changed]( item & e ) {
            long old_charges = e.charges;
            const bool consumed = this->add_or_drop_with_msg( e, true );
            changed = changed || consumed || e.charges != old_charges;
            if( consumed ) {
                this->mod_moves( -this->item_handling_cost( e ) );
            }
            return consumed;
        } ), it.contents.end() );
        if( changed ) {
            it.on_contents_changed();
        }
        return true;
    }

    // If item can be unloaded more than once (currently only guns) prompt user to choose
    std::vector<std::string> msgs( 1, it.tname() );
    std::vector<item *> opts( 1, &it );

    for( auto e : it.gunmods() ) {
        if( e->is_gun() && !e->has_flag( "NO_UNLOAD" ) &&
            ( e->magazine_current() || e->ammo_remaining() > 0 || e->casings_count() > 0 ) ) {
            msgs.emplace_back( e->tname() );
            opts.emplace_back( e );
        }
    }

    item *target = nullptr;
    if( opts.size() > 1 ) {
        const int ret = uilist( _( "Unload what?" ), msgs );
        if( ret >= 0 ) {
            target = opts[ret];
        }
    } else {
        target = &it;
    }

    if( target == nullptr ) {
        return false;
    }

    // Next check for any reasons why the item cannot be unloaded
    if( !target->ammo_type() || target->ammo_capacity() <= 0 ) {
        add_msg( m_info, _( "You can't unload a %s!" ), target->tname().c_str() );
        return false;
    }

    if( target->has_flag( "NO_UNLOAD" ) ) {
        if( target->has_flag( "RECHARGE" ) || target->has_flag( "USE_UPS" ) ) {
            add_msg( m_info, _( "You can't unload a rechargeable %s!" ), target->tname().c_str() );
        } else {
            add_msg( m_info, _( "You can't unload a %s!" ), target->tname().c_str() );
        }
        return false;
    }

    if( !target->magazine_current() && target->ammo_remaining() <= 0 && target->casings_count() <= 0 ) {
        if( target->is_tool() ) {
            add_msg( m_info, _( "Your %s isn't charged." ), target->tname().c_str() );
        } else {
            add_msg( m_info, _( "Your %s isn't loaded." ), target->tname().c_str() );
        }
        return false;
    }

    target->casings_handle( [&]( item & e ) {
        return this->i_add_or_drop( e );
    } );

    if( target->is_magazine() ) {
        player_activity unload_mag_act( activity_id( "ACT_UNLOAD_MAG" ) );
        g->u.assign_activity( unload_mag_act );
        g->u.activity.targets.emplace_back( item_location(*this, target) );

        // Calculate the time to remove the contained ammo (consuming half as much time as required to load the magazine)
        int mv = 0;
        for( auto &content : target->contents ) {
            mv += this->item_reload_cost(it, content, content.charges) / 2;
        }
        g->u.activity.moves_left += mv;

        // I think this means if unload is not done on ammo-belt, it takes as long as it takes to reload a mag.
        if( !it.is_ammo_belt() ) {
            g->u.activity.moves_left += mv;
        }
        g->u.activity.auto_resume = true;

        return true;

    } else if( target->magazine_current() ) {
        if( !this->add_or_drop_with_msg( *target->magazine_current(), true ) ) {
            return false;
        }
        // Eject magazine consuming half as much time as required to insert it
        this->moves -= this->item_reload_cost( *target, *target->magazine_current(), -1 ) / 2;

        target->contents.erase( std::remove_if( target->contents.begin(),
        target->contents.end(), [&target]( const item & e ) {
            return target->magazine_current() == &e;
        } ) );

    } else if( target->ammo_remaining() ) {
        long qty = target->ammo_remaining();

        if( target->ammo_type() == ammotype( "plutonium" ) ) {
            qty = target->ammo_remaining() / PLUTONIUM_CHARGES;
            if( qty > 0 ) {
                add_msg( _( "You recover %i unused plutonium." ), qty );
            } else {
                add_msg( m_info, _( "You can't remove partially depleted plutonium!" ) );
                return false;
            }
        }

        // Construct a new ammo item and try to drop it
        item ammo( target->ammo_current(), calendar::turn, qty );

        if( ammo.made_of_from_type( LIQUID ) ) {
            if( !this->add_or_drop_with_msg( ammo ) ) {
                qty -= ammo.charges; // only handled part (or none) of the liquid
            }
            if( qty <= 0 ) {
                return false; // no liquid was moved
            }

        } else if( !this->add_or_drop_with_msg( ammo, qty > 1 ) ) {
            return false;
        }

        // If successful remove appropriate qty of ammo consuming half as much time as required to load it
        this->moves -= this->item_reload_cost( *target, ammo, qty ) / 2;

        if( target->ammo_type() == ammotype( "plutonium" ) ) {
            qty *= PLUTONIUM_CHARGES;
        }

        target->ammo_set( target->ammo_current(), target->ammo_remaining() - qty );
    }

    // Turn off any active tools
    if( target->is_tool() && target->active && target->ammo_remaining() == 0 ) {
        target->type->invoke( *this, *target, this->pos() );
    }

    add_msg( _( "You unload your %s." ), target->tname().c_str() );
    return true;
}

void player::use_wielded() {
    use(-1);
}

hint_rating player::rate_action_reload( const item &it ) const
{
    hint_rating res = HINT_CANT;

    // Guns may contain additional reloadable mods so check these first
    for( const auto mod : it.gunmods() ) {
        switch( rate_action_reload( *mod ) ) {
            case HINT_GOOD:
                return HINT_GOOD;

            case HINT_CANT:
                continue;

            case HINT_IFFY:
                res = HINT_IFFY;
        }
    }

    if( !it.is_reloadable() ) {
        return res;
    }

    return can_reload( it ) ? HINT_GOOD : HINT_IFFY;
}

hint_rating player::rate_action_unload( const item &it ) const
{
    if( ( it.is_container() || it.is_bandolier() ) && !it.contents.empty() &&
        it.can_unload_liquid() ) {
        return HINT_GOOD;
    }

    if( it.has_flag("NO_UNLOAD") ) {
        return HINT_CANT;
    }

    if( it.magazine_current() ) {
        return HINT_GOOD;
    }

    for( auto e : it.gunmods() ) {
        if( e->is_gun() && !e->has_flag( "NO_UNLOAD" ) &&
            ( e->magazine_current() || e->ammo_remaining() > 0 || e->casings_count() > 0 ) ) {
            return HINT_GOOD;
        }
    }

    if( it.ammo_type().is_null() ) {
        return HINT_CANT;
    }

    if( it.ammo_remaining() > 0 || it.casings_count() > 0 ) {
        return HINT_GOOD;
    }

    if ( it.ammo_capacity() > 0 ) {
        return HINT_IFFY;
    }

    return HINT_CANT;
}

hint_rating player::rate_action_mend( const item &it ) const
{
    // @todo: check also if item damage could be repaired via a tool
    if( !it.faults.empty() ) {
        return HINT_GOOD;
    }
    return it.faults_potential().empty() ? HINT_CANT : HINT_IFFY;
}

hint_rating player::rate_action_disassemble( const item &it )
{
    if( can_disassemble( it, crafting_inventory() ).success() ) {
        return HINT_GOOD; // possible

    } else if( recipe_dictionary::get_uncraft( it.typeId() ) ) {
        return HINT_IFFY; // potentially possible but we currently lack requirements

    } else {
        return HINT_CANT; // never possible
    }
}

hint_rating player::rate_action_use( const item &it ) const
{
    if( it.is_tool() ) {
        return it.ammo_sufficient() ? HINT_GOOD : HINT_IFFY;

    } else if (it.is_gunmod()) {
        /** @EFFECT_GUN >0 allows rating estimates for gun modifications */
        if (get_skill_level( skill_gun ) == 0) {
            return HINT_IFFY;
        } else {
            return HINT_GOOD;
        }
    } else if( it.is_food() || it.is_medication() || it.is_book() || it.is_armor() ) {
        return HINT_IFFY; //the rating is subjective, could be argued as HINT_CANT or HINT_GOOD as well
    } else if( it.type->has_use() ) {
        return HINT_GOOD;
    } else if( !it.is_container_empty() ) {
        return rate_action_use( it.get_contained() );
    }

    return HINT_CANT;
}

bool player::has_enough_charges( const item &it, bool show_msg ) const
{
    if( !it.is_tool() || !it.ammo_required() ) {
        return true;
    }
    if( it.has_flag( "USE_UPS" ) ) {
        if( has_charges( "UPS", it.ammo_required() ) || it.ammo_sufficient() ) {
            return true;
        }
        if( show_msg ) {
            add_msg_if_player( m_info,
                    ngettext( "Your %s needs %d charge from some UPS.",
                              "Your %s needs %d charges from some UPS.",
                              it.ammo_required() ),
                    it.tname().c_str(), it.ammo_required() );
        }
        return false;
    } else if( !it.ammo_sufficient() ) {
        if( show_msg ) {
            add_msg_if_player( m_info,
                    ngettext( "Your %s has %d charge but needs %d.",
                              "Your %s has %d charges but needs %d.",
                              it.ammo_remaining() ),
                    it.tname().c_str(), it.ammo_remaining(), it.ammo_required() );
        }
        return false;
    }
    return true;
}

bool player::consume_charges( item& used, long qty )
{
    if( qty < 0 ) {
        debugmsg( "Tried to consume negative charges" );
        return false;
    }

    if( qty == 0 ) {
        return false;
    }

    if( !used.is_tool() && !used.is_food() && !used.is_medication() ) {
        debugmsg( "Tried to consume charges for non-tool, non-food, non-med item" );
        return false;
    }

    // Consume comestibles destroying them if no charges remain
    if( used.is_food() || used.is_medication() ) {
        used.charges -= qty;
        if( used.charges <= 0 ) {
            i_rem( &used );
            return true;
        }
        return false;
    }

    // Tools which don't require ammo are instead destroyed
    if( used.is_tool() && !used.ammo_required() ) {
        i_rem( &used );
        return true;
    }

    // USE_UPS never occurs on base items but is instead added by the UPS tool mod
    if( used.has_flag( "USE_UPS" ) ) {
        // With the new UPS system, we'll want to use any charges built up in the tool before pulling from the UPS
        // The usage of the item was already approved, so drain item if possible, otherwise use UPS
        if( used.charges >= qty ) {
            used.ammo_consume( qty, pos() );
        } else {
            use_charges( "UPS", qty );
        }
    } else {
        used.ammo_consume( std::min( qty, used.ammo_remaining() ), pos() );
    }
    return false;
}

void player::use( int inventory_position )
{
    item &used = i_at( inventory_position );
    auto loc = item_location( *this, &used );

    use( loc.clone() );
}

void player::use( item_location loc )
{
    item &used = *loc.get_item();
    int inventory_position = loc.where() == item_location::type::character ?
                             this->get_item_position( &used ) : INT_MIN;

    if( used.is_null() ) {
        add_msg( m_info, _( "You do not have that item." ) );
        return;
    }

    last_item = used.typeId();

    if( used.is_tool() ) {
        if( !used.type->has_use() ) {
            add_msg_if_player( _( "You can't do anything interesting with your %s." ), used.tname().c_str() );
            return;
        }
        invoke_item( &used, loc.position() );

    } else if( used.is_food() ||
               used.is_medication() ||
               used.get_contained().is_food() ||
               used.get_contained().is_medication() ) {
        consume( inventory_position );

    } else if( used.is_book() ) {
        read( inventory_position );

    } else if ( used.type->has_use() ) {
        invoke_item( &used, loc.position() );

    } else {
        add_msg( m_info, _( "You can't do anything interesting with your %s." ),
                 used.tname().c_str() );
    }
}

bool player::invoke_item( item* used )
{
    return invoke_item( used, pos() );
}

bool player::invoke_item( item* used, const tripoint &pt )
{
    const auto &use_methods = used->type->use_methods;

    if( use_methods.empty() ) {
        return false;
    } else if( use_methods.size() == 1 ) {
        return invoke_item( used, use_methods.begin()->first, pt );
    }

    uilist umenu;

    umenu.text = string_format( _( "What to do with your %s?" ), used->tname().c_str() );
    umenu.hilight_disabled = true;

    for( const auto &e : use_methods ) {
        const auto res = e.second.can_call( *this, *used, false, pt );
        umenu.addentry_desc( MENU_AUTOASSIGN, res.success(), MENU_AUTOASSIGN, e.second.get_name(), res.str() );
    }

    umenu.desc_enabled = std::any_of( umenu.entries.begin(), umenu.entries.end(), []( const uilist_entry &elem ) {
        return !elem.desc.empty();
    });

    umenu.query();

    int choice = umenu.ret;
    if( choice < 0 || choice >= static_cast<int>( use_methods.size() ) ) {
        return false;
    }

    const std::string &method = std::next( use_methods.begin(), choice )->first;

    return invoke_item( used, method, pt );
}

bool player::invoke_item( item* used, const std::string &method )
{
    return invoke_item( used, method, pos() );
}

bool player::invoke_item( item* used, const std::string &method, const tripoint &pt )
{
    if( !has_enough_charges( *used, true ) ) {
        return false;
    }

    item *actually_used = used->get_usable_item( method );
    if( actually_used == nullptr ) {
        debugmsg( "Tried to invoke a method %s on item %s, which doesn't have this method",
                  method.c_str(), used->tname().c_str() );
        return false;
    }

    long charges_used = actually_used->type->invoke( *this, *actually_used, pt, method );

    if( used->is_tool() || used->is_medication() || used->get_contained().is_medication() ) {
        return consume_charges( *actually_used, charges_used );
    } else if( used->is_bionic() && charges_used > 0 ) {
        i_rem( used );
        return true;
    }

    return false;
}

void player::reassign_item( item &it, long invlet )
{
    bool remove_old = true;
    if( invlet ) {
        item &prev = i_at( invlet_to_position( invlet ) );
        if( !prev.is_null() ) {
            remove_old = it.typeId() != prev.typeId();
            inv.reassign_item( prev, it.invlet, remove_old );
        }
    }

    if( !invlet || inv_chars.valid( invlet ) ) {
        const auto iter = inv.assigned_invlet.find( it.invlet );
        bool found = iter != inv.assigned_invlet.end();
        if( found ) {
            inv.assigned_invlet.erase( iter );
        }
        if( invlet && ( !found || it.invlet != invlet ) ) {
            inv.assigned_invlet[invlet] = it.typeId();
        }
        inv.reassign_item( it, invlet, remove_old );
    }
}

bool player::gunmod_remove( item &gun, item& mod )
{
    auto iter = std::find_if( gun.contents.begin(), gun.contents.end(), [&mod]( const item& e ) {
        return &mod == &e;
    } );
    if( iter == gun.contents.end() ) {
        debugmsg( "Cannot remove non-existent gunmod" );
        return false;
    }
    if( mod.ammo_remaining() && !g->unload( mod ) ) {
        return false;
    }

    gun.gun_set_mode( gun_mode_id( "DEFAULT" ) );
    moves -= mod.type->gunmod->install_time / 2;

    if( mod.typeId() == "brass_catcher" ) {
        gun.casings_handle( [&]( item &e ) {
            return i_add_or_drop( e );
        } );
    }

    const itype *modtype = mod.type;

    i_add_or_drop( mod );
    gun.contents.erase( iter );

    //If the removed gunmod added mod locations, check to see if any mods are in invalid locations
    if( !modtype->gunmod->add_mod.empty() ) {
        std::map<gunmod_location, int> mod_locations = gun.get_mod_locations();
        for( auto slot : mod_locations ) {
            int free_slots = gun.get_free_mod_locations( slot.first );

            for( auto the_mod : gun.gunmods() ) {
                if( the_mod->type->gunmod->location == slot.first && free_slots < 0 ) {
                    gunmod_remove( gun, *the_mod );
                    free_slots++;
                } else if( mod_locations.find( the_mod->type->gunmod->location ) ==
                           mod_locations.end() ) {
                    gunmod_remove( gun, *the_mod );
                }
            }
        }
    }

    //~ %1$s - gunmod, %2$s - gun.
    add_msg_if_player( _( "You remove your %1$s from your %2$s." ), modtype->nname( 1 ).c_str(), gun.tname().c_str() );

    return true;
}

std::pair<int, int> player::gunmod_installation_odds( const item& gun, const item& mod ) const
{
    // Mods with INSTALL_DIFFICULT have a chance to fail, potentially damaging the gun
    if( !mod.has_flag( "INSTALL_DIFFICULT" ) || has_trait( trait_DEBUG_HS ) ) {
        return std::make_pair( 100, 0 );
    }

    int roll = 100; // chance of success (%)
    int risk = 0;   // chance of failure (%)
    int chances = 1; // start with 1 in 6 (~17% chance)

    for( const auto &e : mod.type->min_skills ) {
        // gain an additional chance for every level above the minimum requirement
        skill_id sk = e.first == "weapon" ? gun.gun_skill() : skill_id( e.first );
        chances += std::max( get_skill_level( sk ) - e.second, 0 );
    }
    // cap success from skill alone to 1 in 5 (~83% chance)
    roll = std::min( double( chances ), 5.0 ) / 6.0 * 100;
    // focus is either a penalty or bonus of at most +/-10%
    roll += ( std::min( std::max( focus_pool, 140 ), 60 ) - 100 ) / 4;
    // dexterity and intelligence give +/-2% for each point above or below 12
    roll += ( get_dex() - 12 ) * 2;
    roll += ( get_int() - 12 ) * 2;
    // each level of damage to the base gun reduces success by 10%
    roll -= std::max( gun.damage_level( 4 ), 0 ) * 10;
    roll = std::min( std::max( roll, 0 ), 100 );

    // risk of causing damage on failure increases with less durable guns
    risk = ( 100 - roll ) * ( ( 10.0 - std::min( gun.type->gun->durability, 9 ) ) / 10.0 );

    return std::make_pair( roll, risk );
}

void player::gunmod_add( item &gun, item &mod )
{
    if( !gun.is_gunmod_compatible( mod ).success() ) {
        debugmsg( "Tried to add incompatible gunmod" );
        return;
    }

    if( !has_item( gun ) && !has_item( mod ) ) {
        debugmsg( "Tried gunmod installation but mod/gun not in player possession" );
        return;
    }

    // first check at least the minimum requirements are met
    if( !has_trait( trait_DEBUG_HS ) && !can_use( mod, gun ) ) {
        return;
    }

    // any (optional) tool charges that are used during installation
    auto odds = gunmod_installation_odds( gun, mod );
    int roll = odds.first;
    int risk = odds.second;

    std::string tool;
    int qty = 0;

    if( mod.is_irremovable() ) {
        if( !query_yn( _( "Permanently install your %1$s in your %2$s?" ),
                       colorize( mod.tname(), mod.color_in_inventory() ),
                       colorize( gun.tname(), gun.color_in_inventory() ) ) ) {
            add_msg_if_player( _( "Never mind." ) );
            return; // player canceled installation
        }
    }

    // if chance of success <100% prompt user to continue
    if( roll < 100 ) {
        uilist prompt;
        prompt.text = string_format( _( "Attach your %1$s to your %2$s?" ), mod.tname().c_str(),
                                     gun.tname().c_str() );

        std::vector<std::function<void()>> actions;

        prompt.addentry( -1, true, 'w',
                         string_format( _( "Try without tools (%i%%) risking damage (%i%%)" ), roll, risk ) );
        actions.emplace_back( [&] {} );

        prompt.addentry( -1, has_charges( "small_repairkit", 100 ), 'f',
                         string_format( _( "Use 100 charges of firearm repair kit (%i%%)" ), std::min( roll * 2, 100 ) ) );

        actions.emplace_back( [&] {
            tool = "small_repairkit";
            qty = 100;
            roll *= 2; // firearm repair kit improves success...
            risk /= 2; // ...and reduces the risk of damage upon failure
        } );

        prompt.addentry( -1, has_charges( "large_repairkit", 25 ), 'g',
                         string_format( _( "Use 25 charges of gunsmith repair kit (%i%%)" ), std::min( roll * 3, 100 ) ) );

        actions.emplace_back( [&] {
            tool = "large_repairkit";
            qty = 25;
            roll *= 3; // gunsmith repair kit improves success markedly...
            risk = 0;  // ...and entirely prevents damage upon failure
        } );

        prompt.query();
        if( prompt.ret < 0 ) {
            add_msg_if_player( _( "Never mind." ) );
            return; // player canceled installation
        }
        actions[ prompt.ret ]();
    }

    int turns = !has_trait( trait_DEBUG_HS ) ? mod.type->gunmod->install_time : 0;

    assign_activity( activity_id( "ACT_GUNMOD_ADD" ), turns, -1, get_item_position( &gun ), tool );
    activity.values.push_back( get_item_position( &mod ) );
    activity.values.push_back( roll ); // chance of success (%)
    activity.values.push_back( risk ); // chance of damage (%)
    activity.values.push_back( qty ); // tool charges
}

void player::toolmod_add( item_location tool, item_location mod )
{
    if( !tool && !mod ) {
        debugmsg( "Tried toolmod installation but mod/tool not available" );
        return;
    }
    // first check at least the minimum requirements are met
    if( !has_trait( trait_DEBUG_HS ) && !can_use( *mod, *tool ) ) {
        return;
    }

    if( !query_yn( _( "Permanently install your %1$s in your %2$s?" ),
                   colorize( mod->tname().c_str(), mod->color_in_inventory() ),
                   colorize( tool->tname().c_str(), tool->color_in_inventory() ) ) ) {
        add_msg_if_player( _( "Never mind." ) );
        return; // player canceled installation
    }

    assign_activity( activity_id( "ACT_TOOLMOD_ADD" ), 1, -1 );
    activity.targets.emplace_back( std::move( tool ) );
    activity.targets.emplace_back( std::move( mod ) );
}

hint_rating player::rate_action_read( const item &it ) const
{
    if( !it.is_book() ) {
        return HINT_CANT;
    }

    std::vector<std::string> dummy;
    return get_book_reader( it, dummy ) == nullptr ? HINT_IFFY : HINT_GOOD;
}

const player *player::get_book_reader( const item &book, std::vector<std::string> &reasons ) const
{
    const player *reader = nullptr;
    if( !book.is_book() ) {
        reasons.push_back( string_format( _( "Your %s is not good reading material." ),
                                          book.tname().c_str() ) );
        return nullptr;
    }

    // Check for conditions that immediately disqualify the player from reading:
    const optional_vpart_position vp = g->m.veh_at( pos() );
    if( vp && vp->vehicle().player_in_control( *this ) ) {
        reasons.emplace_back( _( "It's a bad idea to read while driving!" ) );
        return nullptr;
    }
    const auto &type = book.type->book;
    if( !fun_to_read( book ) && !has_morale_to_read() && has_identified( book.typeId() ) ) {
        // Low morale still permits skimming
        reasons.emplace_back( _( "What's the point of studying?  (Your morale is too low!)" ) );
        return nullptr;
    }
    const skill_id &skill = type->skill;
    const int skill_level = get_skill_level( skill );
    if( skill && skill_level < type->req && has_identified( book.typeId() ) ) {
        reasons.push_back( string_format( _( "You need %s %d to understand the jargon!" ),
                                          skill.obj().name().c_str(), type->req ) );
        return nullptr;
    }

    // Check for conditions that disqualify us only if no NPCs can read to us
    if( type->intel > 0 && has_trait( trait_ILLITERATE ) ) {
        reasons.emplace_back( _( "You're illiterate!" ) );
    } else if( has_trait( trait_HYPEROPIC ) && !worn_with_flag( "FIX_FARSIGHT" ) &&
               !has_effect( effect_contacts ) && !has_bionic( bio_eye_optic ) ) {
        reasons.emplace_back( _( "Your eyes won't focus without reading glasses." ) );
    } else if( fine_detail_vision_mod() > 4 ) {
        // Too dark to read only applies if the player can read to himself
        reasons.emplace_back( _( "It's too dark to read!" ) );
        return nullptr;
    } else {
        return this;
    }

    //Check for NPCs to read for you, negates Illiterate and Far Sighted
    //The fastest-reading NPC is chosen
    if( is_deaf() ) {
        reasons.emplace_back( _( "Maybe someone could read that to you, but you're deaf!" ) );
        return nullptr;
    }

    int time_taken = INT_MAX;
    auto candidates = get_crafting_helpers();

    for( const npc *elem : candidates ) {
        // Check for disqualifying factors:
        if( type->intel > 0 && elem->has_trait( trait_ILLITERATE ) ) {
            reasons.push_back( string_format( _( "%s is illiterate!" ),
                                              elem->disp_name().c_str() ) );
        } else if( skill && elem->get_skill_level( skill ) < type->req &&
                   has_identified( book.typeId() ) ) {
            reasons.push_back( string_format( _( "%s needs %s %d to understand the jargon!" ),
                                              elem->disp_name().c_str(), skill.obj().name().c_str(), type->req ) );
        } else if( elem->has_trait( trait_HYPEROPIC ) && !elem->worn_with_flag( "FIX_FARSIGHT" ) &&
                   !elem->has_effect( effect_contacts ) ) {
            reasons.push_back( string_format( _( "%s needs reading glasses!" ),
                                              elem->disp_name().c_str() ) );
        } else if( std::min( fine_detail_vision_mod(), elem->fine_detail_vision_mod() ) > 4 ) {
            reasons.push_back( string_format(
                                   _( "It's too dark for %s to read!" ),
                                   elem->disp_name().c_str() ) );
        } else if( !elem->sees( *this ) ) {
            reasons.push_back( string_format( _( "%s could read that to you, but they can't see you." ),
                                              elem->disp_name().c_str() ) );
        } else if( !elem->fun_to_read( book ) && !elem->has_morale_to_read() &&
                   has_identified( book.typeId() ) ) {
            // Low morale still permits skimming
            reasons.push_back( string_format( _( "%s morale is too low!" ), elem->disp_name( true ).c_str() ) );
        } else {
            int proj_time = time_to_read( book, *elem );
            if( proj_time < time_taken ) {
                reader = elem;
                time_taken = proj_time;
            }
        }
    } //end for all candidates
    return reader;
}

int player::time_to_read( const item &book, const player &reader, const player *learner ) const
{
    const auto &type = book.type->book;
    const skill_id &skill = type->skill;
    // The reader's reading speed has an effect only if they're trying to understand the book as they read it
    // Reading speed is assumed to be how well you learn from books (as opposed to hands-on experience)
    const bool try_understand = reader.fun_to_read( book ) ||
                                reader.get_skill_level( skill ) < type->level;
    int reading_speed = try_understand ? std::max( reader.read_speed(), read_speed() ) : read_speed();
    if( learner ) {
        reading_speed = std::max( reading_speed, learner->read_speed() );
    }

    int retval = type->time * reading_speed;
    retval *= std::min( fine_detail_vision_mod(), reader.fine_detail_vision_mod() );

    const int effective_int = std::min( {int_cur, reader.get_int(), learner ? learner->get_int() : INT_MAX } );
    if( type->intel > effective_int && !reader.has_trait( trait_PROF_DICEMASTER ) ) {
        retval += type->time * ( type->intel - effective_int ) * 100;
    }
    if( !has_identified( book.typeId() ) ) {
        retval /= 10; //skimming
    }
    return retval;
}

bool player::fun_to_read( const item &book ) const
{
    // If you don't have a problem with eating humans, To Serve Man becomes rewarding
    if( ( has_trait( trait_CANNIBAL ) || has_trait( trait_PSYCHOPATH ) || has_trait( trait_SAPIOVORE ) ) &&
        book.typeId() == "cookbook_human" ) {
        return true;
    } else if( has_trait( trait_SPIRITUAL ) && book.has_flag( "INSPIRATIONAL" ) ) {
        return true;
    } else {
        return book_fun_for( book, *this ) > 0;
    }
}

int player::book_fun_for( const item &book, const player &p ) const
{
    int fun_bonus = book.type->book->fun;
    if( !book.is_book() ) {
        debugmsg( "called player::book_fun_for with non-book" );
        return 0;
    }

    if( has_trait( trait_LOVES_BOOKS ) ) {
        fun_bonus++;
    } else if( has_trait( trait_HATES_BOOKS ) ) {
        if( book.type->book->fun > 0 ) {
            fun_bonus = 0;
        } else {
            fun_bonus--;
        }
    }
    // If you don't have a problem with eating humans, To Serve Man becomes rewarding
    if( ( p.has_trait( trait_CANNIBAL ) || p.has_trait( trait_PSYCHOPATH ) ||
          p.has_trait( trait_SAPIOVORE ) ) &&
        book.typeId() == "cookbook_human" ) {
        fun_bonus = abs( fun_bonus );
    } else if( p.has_trait( trait_SPIRITUAL ) && book.has_flag( "INSPIRATIONAL" ) ) {
        fun_bonus = abs( fun_bonus * 3 );
    }
    return fun_bonus;
}

/**
 * Explanation of ACT_READ activity values:
 *
 * position: ID of the reader
 * targets: 1-element vector with the item_location (always in inventory/wielded) of the book being read
 * index: We are studying until the player with this ID gains a level; 0 indicates reading once
 * values: IDs of the NPCs who will learn something
 * str_values: Parallel to values, these contain the learning penalties (as doubles in string form) as follows:
 *             Experience gained = Experience normally gained * penalty
 */

bool player::read( int inventory_position, const bool continuous )
{
    item &it = i_at( inventory_position );
    if( it.is_null() ) {
        add_msg( m_info, _( "Never mind." ) );
        return false;
    }
    std::vector<std::string> fail_messages;
    const player *reader = get_book_reader( it, fail_messages );
    if( reader == nullptr ) {
        // We can't read, and neither can our followers
        for( const std::string &reason : fail_messages ) {
            add_msg( m_bad, reason );
        }
        return false;
    }
    const int time_taken = time_to_read( it, *reader );

    add_msg( m_debug, "player::read: time_taken = %d", time_taken );
    player_activity act( activity_id( "ACT_READ" ), time_taken, continuous ? activity.index : 0, reader->getID() );
    act.targets.emplace_back( item_location( *this, &it ) );

    // If the player hasn't read this book before, skim it to get an idea of what's in it.
    if( !has_identified( it.typeId() ) ) {
        if( reader != this ) {
            add_msg( m_info, fail_messages[0] );
            add_msg( m_info, _( "%s reads aloud..." ), reader->disp_name().c_str() );
        }
        assign_activity( act );
        return true;
    }

    if( it.typeId() == "guidebook" ) {
        // special guidebook effect: print a misc. hint when read
        if( reader != this ) {
            add_msg( m_info, fail_messages[0] );
            dynamic_cast<const npc *>( reader )->say( get_hint() );
        } else {
            add_msg( m_info, get_hint().c_str() );
        }
        mod_moves( -100 );
        return false;
    }

    const auto &type = it.type->book;
    const skill_id &skill = type->skill;
    const std::string skill_name = skill ? skill.obj().name() : "";

    // Find NPCs to join the study session:
    std::map<npc *, std::string> learners;
    std::map<npc *, std::string> fun_learners; //reading only for fun
    std::map<npc *, std::string> nonlearners;
    auto candidates = get_crafting_helpers();
    for( npc *elem : candidates ) {
        const int lvl = elem->get_skill_level( skill );
        const bool skill_req = ( elem->fun_to_read( it ) && ( !skill || lvl >= type->req ) ) ||
                               ( skill && lvl < type->level && lvl >= type->req );
        const bool morale_req = elem->fun_to_read( it ) || elem->has_morale_to_read();

        if( !skill_req && elem != reader ) {
            if( skill && lvl < type->req ) {
                nonlearners.insert( { elem, string_format( _( " (needs %d %s)" ), type->req, skill_name.c_str() ) } );
            } else if( skill ) {
                nonlearners.insert( { elem, string_format( _( " (already has %d %s)" ), type->level, skill_name.c_str() ) } );
            } else {
                nonlearners.insert( { elem, _( " (uninterested)" ) } );
            }
        } else if( elem->is_deaf() && reader != elem ) {
            nonlearners.insert( { elem, _( " (deaf)" ) } );
        } else if( !morale_req ) {
            nonlearners.insert( { elem, _( " (too sad)" ) } );
        } else if( skill && lvl < type->level ) {
            const double penalty = static_cast<double>( time_taken ) / time_to_read( it, *reader, elem );
            learners.insert( {elem, elem == reader ? _( " (reading aloud to you)" ) : ""} );
            act.values.push_back( elem->getID() );
            act.str_values.push_back( to_string( penalty ) );
        } else {
            fun_learners.insert( {elem, elem == reader ? _( " (reading aloud to you)" ) : "" } );
            act.values.push_back( elem->getID() );
            act.str_values.emplace_back( "1" );
        }
    }

    if( !continuous ) {
        //only show the menu if there's useful information or multiple options
        if( skill || !nonlearners.empty() || !fun_learners.empty() ) {
            uilist menu;

            // Some helpers to reduce repetition:
            auto length = []( const std::pair<npc *, std::string> &elem ) {
                return elem.first->disp_name().size() + elem.second.size();
            };

            auto max_length = [&length]( const std::map<npc *, std::string> &m ) {
                auto max_ele = std::max_element( m.begin(), m.end(), [&length]( std::pair<npc *, std::string> left,
                std::pair<npc *, std::string> right ) {
                    return length( left ) < length( right );
                } );
                return max_ele == m.end() ? 0 : length( *max_ele );
            };

            auto get_text =
                [&]( const std::map<npc *, std::string> &m, const std::pair<npc *, std::string> &elem )
            {
                const int lvl = elem.first->get_skill_level( skill );
                const std::string lvl_text = skill ? string_format( _( " | current level: %d" ), lvl ) : "";
                const std::string name_text = elem.first->disp_name() + elem.second;
                return string_format( ( "%-*s%s" ), static_cast<int>( max_length( m ) ),
                                      name_text.c_str(), lvl_text.c_str() );
            };

            auto add_header = [&menu]( const std::string & str ) {
                menu.addentry( -1, false, -1, "" );
                uilist_entry header( -1, false, -1, str , c_yellow, c_yellow );
                header.force_color = true;
                menu.entries.push_back( header );
            };

            menu.title = !skill ? string_format( _( "Reading %s" ), it.type_name().c_str() ) :
                         string_format( _( "Reading %s (can train %s from %d to %d)" ), it.type_name().c_str(),
                                        skill_name.c_str(), type->req, type->level );

            if( skill ) {
                const int lvl = get_skill_level( skill );
                menu.addentry( getID(), lvl < type->level, '0',
                               string_format( _( "Read until you gain a level | current level: %d" ), lvl ) );
            } else {
                menu.addentry( -1, false, '0', _( "Read until you gain a level" ) );
            }
            menu.addentry( 0, true, '1', _( "Read once" ) );

            if( skill && !learners.empty() ) {
                add_header( _( "Read until this NPC gains a level:" ) );
                for( const auto &elem : learners ) {
                    menu.addentry( elem.first->getID(), true, -1, get_text( learners, elem ) );
                }
            }
            if( !fun_learners.empty() ) {
                add_header( _( "Reading for fun:" ) );
                for( const auto &elem : fun_learners ) {
                    menu.addentry( -1, false, -1, get_text( fun_learners, elem ) );
                }
            }
            if( !nonlearners.empty() ) {
                add_header( _( "Not participating:" ) );
                for( const auto &elem : nonlearners ) {
                    menu.addentry( -1, false, -1, get_text( nonlearners, elem ) );
                }
            }

            menu.query( true );
            if( menu.ret == UILIST_CANCEL ) {
                add_msg( m_info, _( "Never mind." ) );
                return false;
            }
            act.index = menu.ret;
        }
        add_msg( m_info, _( "Now reading %s, %s to stop early." ),
                 it.type_name().c_str(), press_x( ACTION_PAUSE ).c_str() );
    }

    // Print some informational messages, but only the first time or if the information changes

    if( !continuous || activity.position != act.position ) {
        if( reader != this ) {
            add_msg( m_info, fail_messages[0] );
            add_msg( m_info, _( "%s reads aloud..." ), reader->disp_name().c_str() );
        } else if( !learners.empty() || !fun_learners.empty() ) {
            add_msg( m_info, _( "You read aloud..." ) );
        }
    }

    if( !continuous ||
    !std::all_of( learners.begin(), learners.end(), [&]( std::pair<npc *, std::string> elem ) {
    return std::count( activity.values.begin(), activity.values.end(), elem.first->getID() ) != 0;
    } ) ||
    !std::all_of( activity.values.begin(), activity.values.end(), [&]( int elem ) {
        return learners.find( g->find_npc( elem ) ) != learners.end();
    } ) ) {

        if( learners.size() == 1 ) {
            add_msg( m_info, _( "%s studies with you." ), learners.begin()->first->disp_name().c_str() );
        } else if( !learners.empty() ) {
            const std::string them = enumerate_as_string( learners.begin(),
            learners.end(), [&]( std::pair<npc *, std::string> elem ) {
                return elem.first->disp_name();
            } );
            add_msg( m_info, _( "%s study with you." ), them.c_str() );
        }

        // Don't include the reader as it would be too redundant.
        std::set<std::string> readers;
        for( const auto &elem : fun_learners ) {
            if( elem.first != reader ) {
                readers.insert( elem.first->disp_name() );
            }
        }
        if( readers.size() == 1 ) {
            add_msg( m_info, _( "%s reads with you for fun." ), readers.begin()->c_str() );
        } else if( !readers.empty() ) {
            const std::string them = enumerate_as_string( readers );
            add_msg( m_info, _( "%s read with you for fun." ), them.c_str() );
        }
    }

    if( std::min( fine_detail_vision_mod(), reader->fine_detail_vision_mod() ) > 1.0 ) {
        add_msg( m_warning,
                 _( "It's difficult for %s to see fine details right now. Reading will take longer than usual." ),
                 reader->disp_name().c_str() );
    }

    const bool complex_penalty = type->intel > std::min( int_cur, reader->get_int() ) && !reader->has_trait( trait_PROF_DICEMASTER );
    const player *complex_player = reader->get_int() < int_cur ? reader : this;
    if( complex_penalty && !continuous ) {
        add_msg( m_warning,
                 _( "This book is too complex for %s to easily understand. It will take longer to read." ),
                 complex_player->disp_name().c_str() );
    }

    assign_activity( act );

    // Reinforce any existing morale bonus/penalty, so it doesn't decay
    // away while you read more.
    const time_duration decay_start = 1_turns * time_taken / 1000;
    std::set<player *> apply_morale = { this };
    for( const auto &elem : learners ) {
        apply_morale.insert( elem.first );
    }
    for( const auto &elem : fun_learners ) {
        apply_morale.insert( elem.first );
    }
    for( player *elem : apply_morale ) {
        //Fun bonuses for spritual and To Serve Man are no longer calculated here.
        elem->add_morale( MORALE_BOOK, 0, book_fun_for( it, *elem ) * 15, decay_start + 30_minutes,
                          decay_start, false, it.type );
    }

    return true;
}

void player::do_read( item &book )
{
    const auto &reading = book.type->book;
    if( !reading ) {
        activity.set_to_null();
        return;
    }
    const skill_id &skill = reading->skill;

    if( !has_identified( book.typeId() ) ) {
        // Note that we've read the book.
        items_identified.insert( book.typeId() );

        add_msg( _( "You skim %s to find out what's in it." ), book.type_name().c_str() );
        if( skill && get_skill_level_object( skill ).can_train() ) {
            add_msg(m_info, _("Can bring your %s skill to %d."),
                    skill.obj().name().c_str(), reading->level);
            if( reading->req != 0 ){
                add_msg(m_info, _("Requires %s level %d to understand."),
                        skill.obj().name().c_str(), reading->req);
            }
        }

        if (reading->intel != 0) {
            add_msg(m_info, _("Requires intelligence of %d to easily read."), reading->intel);
        }
        //It feels wrong to use a pointer to *this, but I can't find any other player pointers in this method.
        if( book_fun_for( book, *this ) != 0 ) {
            add_msg( m_info, _( "Reading this book affects your morale by %d" ), book_fun_for( book, *this ) );
        }
        add_msg(m_info, ngettext("A chapter of this book takes %d minute to read.",
                         "A chapter of this book takes %d minutes to read.", reading->time),
                reading->time );

        std::vector<std::string> recipe_list;
        for( const auto & elem : reading->recipes ) {
            // If the player knows it, they recognize it even if it's not clearly stated.
            if( elem.is_hidden() && !knows_recipe( elem.recipe ) ) {
                continue;
            }
            recipe_list.push_back( elem.name );
        }
        if( !recipe_list.empty() ) {
            std::string recipe_line = string_format(
                ngettext( "This book contains %1$u crafting recipe: %2$s",
                          "This book contains %1$u crafting recipes: %2$s",
                          static_cast<unsigned long>( recipe_list.size() ) ),
                static_cast<unsigned long>( recipe_list.size() ),
                enumerate_as_string( recipe_list ).c_str() );
            add_msg( m_info, recipe_line );
        }
        if( recipe_list.size() != reading->recipes.size() ) {
            add_msg( m_info, _( "It might help you figuring out some more recipes." ) );
        }
        activity.set_to_null();
        return;
    }

    std::vector<std::pair<player *, double>> learners; //learners and their penalties
    for( size_t i = 0; i < activity.values.size(); i++ ) {
        player *n = g->find_npc( activity.values[i] );
        if( n != nullptr ) {
            const std::string &s = activity.get_str_value( i, "1" );
            learners.push_back( { n, strtod( s.c_str(), nullptr ) } );
        }
        // Otherwise they must have died/teleported or something
    }
    learners.push_back( { this, 1.0 } );
    bool continuous = false; //whether to continue reading or not
    std::set<std::string> little_learned; // NPCs who learned a little about the skill
    std::set<std::string> cant_learn;
    std::list<std::string> out_of_chapters;

    for( auto &elem : learners ) {
        player *learner = elem.first;

        if( book_fun_for( book, *learner ) != 0 ) {
            //Fun bonus is no longer calculated here.
            learner->add_morale( MORALE_BOOK, book_fun_for( book, *learner ) * 5, book_fun_for( book, *learner ) * 15, 1_hours, 30_minutes, true,
                book.type );
        }

        book.mark_chapter_as_read( *learner );

        if( skill && learner->get_skill_level( skill ) < reading->level &&
            learner->get_skill_level_object( skill ).can_train() ) {
            SkillLevel &skill_level = learner->get_skill_level_object( skill );
            const int originalSkillLevel = skill_level.level();

            // Calculate experience gained
            /** @EFFECT_INT increases reading comprehension */
            // Enhanced Memory Banks modestly boosts experience
            int min_ex = std::max( 1, reading->time / 10 + learner->get_int() / 4 );
            int max_ex = reading->time /  5 + learner->get_int() / 2 - originalSkillLevel;
            if( has_active_bionic( bio_memory ) ) {
                min_ex += 2;
            }
            if( max_ex < 2 ) {
                max_ex = 2;
            }
            if( max_ex > 10 ) {
                max_ex = 10;
            }
            if( max_ex < min_ex ) {
                max_ex = min_ex;
            }

            min_ex *= ( originalSkillLevel + 1 ) * elem.second;
            min_ex = std::max( min_ex, 1 );
            max_ex *= ( originalSkillLevel + 1 ) * elem.second;
            max_ex = std::max( min_ex, max_ex );

            skill_level.readBook( min_ex, max_ex, reading->level );

            std::string skill_name = skill.obj().name();

            if( skill_level != originalSkillLevel ) {
                if( learner->is_player() ) {
                    add_msg( m_good, _( "You increase %s to level %d." ), skill.obj().name().c_str(),
                             originalSkillLevel + 1 );
                    if( skill_level.level() % 4 == 0 ) {
                        //~ %s is skill name. %d is skill level
                        add_memorial_log( pgettext( "memorial_male", "Reached skill level %1$d in %2$s." ),
                                          pgettext( "memorial_female", "Reached skill level %1$d in %2$s." ),
                                          skill_level.level(), skill_name );
                    }
                    const std::string skill_increase_source = "book";
                    CallbackArgumentContainer lua_callback_args_info;
                    lua_callback_args_info.emplace_back( getID() );
                    lua_callback_args_info.emplace_back( skill_increase_source );
                    lua_callback_args_info.emplace_back( skill.str() );
                    lua_callback_args_info.emplace_back( originalSkillLevel + 1 );
                    lua_callback( "on_player_skill_increased", lua_callback_args_info );
                    lua_callback( "on_skill_increased" ); // Legacy callback
                } else {
                    add_msg( m_good, _( "%s increases their %s level." ), learner->disp_name().c_str(), skill_name );
                }
            } else {
                //skill_level == originalSkillLevel
                if( activity.index == learner->getID() ) {
                    continuous = true;
                }
                if( learner->is_player() ) {
                    add_msg( m_info, _( "You learn a little about %s! (%d%%)" ), skill_name, skill_level.exercise() );
                } else {
                    little_learned.insert( learner->disp_name() );
                }
            }

            if( ( skill_level == reading->level || !skill_level.can_train() ) ||
                ( ( learner->has_trait( trait_id( "SCHIZOPHRENIC" ) ) ||
                    learner->has_artifact_with( AEP_SCHIZO ) ) && one_in( 25 ) ) )
            {
                if( learner->is_player() ) {
                    add_msg( m_info, _( "You can no longer learn from %s." ), book.type_name().c_str() );
                } else {
                    cant_learn.insert( learner->disp_name() );
                }
            }
        } else if( skill ) {
            if( learner->is_player() ) {
                add_msg( m_info, _( "You can no longer learn from %s." ), book.type_name().c_str() );
            } else {
                cant_learn.insert( learner->disp_name() );
            }
        }
    } //end for all learners

    if( little_learned.size() == 1 ) {
        add_msg( m_info, _( "%s learns a little about %s!" ), little_learned.begin()->c_str(),
                 skill.obj().name().c_str() );
    } else if( !little_learned.empty() ) {
        const std::string little_learned_msg = enumerate_as_string( little_learned );
        add_msg( m_info, _( "%s learn a little about %s!" ), little_learned_msg.c_str(),
                 skill.obj().name().c_str() );
    }

    if( !cant_learn.empty() ) {
        const std::string names = enumerate_as_string( cant_learn );
        add_msg( m_info, _( "%s can no longer learn from %s." ), names.c_str(), book.type_name().c_str() );
    }
    if( !out_of_chapters.empty() ) {
        const std::string names = enumerate_as_string( out_of_chapters );
        add_msg( m_info, _( "Rereading the %s isn't as much fun for %s." ),
                 book.type_name().c_str(), names.c_str() );
        if( out_of_chapters.front() == disp_name() && one_in( 6 ) ) {
            add_msg( m_info, _( "Maybe you should find something new to read..." ) );
        }
    }

    if( continuous ) {
        activity.set_to_null();
        read( get_item_position( &book ), true );
        if( activity ) {
            return;
        }
    }

    // NPCs can't learn martial arts from manuals (yet).
    auto m = book.type->use_methods.find( "MA_MANUAL" );
    if( m != book.type->use_methods.end() ) {
        m->second.call( *this, book, false, pos() );
    }

    activity.set_to_null();
}

bool player::has_identified( const std::string &item_id ) const
{
    return items_identified.count( item_id ) > 0;
}

bool player::studied_all_recipes(const itype &book) const
{
    if( !book.book ) {
        return true;
    }
    for( auto &elem : book.book->recipes ) {
        if( !knows_recipe( elem.recipe ) ) {
            return false;
        }
    }
    return true;
}

const recipe_subset &player::get_learned_recipes() const
{
    // Cache validity check
    if( *_skills != *valid_autolearn_skills ) {
        for( const auto &r : recipe_dict.all_autolearn() ) {
            if( meets_skill_requirements( r->autolearn_requirements ) ) {
                learned_recipes->include( r );
            }
        }
        *valid_autolearn_skills = *_skills; // Reassign the validity stamp
    }

    return *learned_recipes;
}

const recipe_subset player::get_recipes_from_books( const inventory &crafting_inv ) const
{
    recipe_subset res;

    for( const auto &stack : crafting_inv.const_slice() ) {
        const item &candidate = stack->front();

        for( std::pair<const recipe *, int> recipe_entry :
               candidate.get_available_recipes( *this ) ) {
            res.include( recipe_entry.first, recipe_entry.second );
        }
    }

    return res;
}

const std::set<itype_id> player::get_books_for_recipe( const inventory &crafting_inv, const recipe *r ) const
{
    std::set<itype_id> book_ids;
    const int skill_level = get_skill_level( r->skill_used );
    for( auto &book_lvl : r->booksets ) {
        itype_id book_id = book_lvl.first;
        int required_skill_level = book_lvl.second;
        // NPCs don't need to identify books
        if( is_player() && !items_identified.count( book_id ) ) {
            continue;
        }

        if( skill_level >= required_skill_level && crafting_inv.amount_of( book_id ) > 0 ) {
            book_ids.insert( book_id );
        }
    }
    return book_ids;
}

const recipe_subset player::get_available_recipes( const inventory &crafting_inv, const std::vector<npc *> *helpers ) const
{
    recipe_subset res( get_learned_recipes() );

    res.include( get_recipes_from_books( crafting_inv ) );

    if( helpers != nullptr ) {
        for( npc *np : *helpers ) {
            // Directly form the helper's inventory
            res.include( get_recipes_from_books( np->inv ) );
            // Being told what to do
            res.include_if( np->get_learned_recipes(), [ this ]( const recipe &r ) {
                return get_skill_level( r.skill_used ) >= int( r.difficulty * 0.8f ); // Skilled enough to understand
            } );
        }
    }

    return res;
}

void player::try_to_sleep( const time_duration &dur )
{
    const optional_vpart_position vp = g->m.veh_at( pos() );
    const trap &trap_at_pos = g->m.tr_at(pos());
    const ter_id ter_at_pos = g->m.ter(pos());
    const furn_id furn_at_pos = g->m.furn(pos());
    bool plantsleep = false;
    bool fungaloid_cosplay = false;
    bool websleep = false;
    bool webforce = false;
    bool websleeping = false;
    bool in_shell = false;
    bool watersleep = false;
    if (has_trait( trait_CHLOROMORPH )) {
        plantsleep = true;
        if( (ter_at_pos == t_dirt || ter_at_pos == t_pit ||
              ter_at_pos == t_dirtmound || ter_at_pos == t_pit_shallow ||
              ter_at_pos == t_grass) && !vp &&
              furn_at_pos == f_null ) {
            add_msg_if_player(m_good, _("You relax as your roots embrace the soil."));
        } else if (vp) {
            add_msg_if_player(m_bad, _("It's impossible to sleep in this wheeled pot!"));
        } else if (furn_at_pos != f_null) {
            add_msg_if_player(m_bad, _("The humans' furniture blocks your roots. You can't get comfortable."));
        } else { // Floor problems
            add_msg_if_player(m_bad, _("Your roots scrabble ineffectively at the unyielding surface."));
        }
    }
    else if( has_trait( trait_M_SKIN3 ) ) {
        fungaloid_cosplay = true;
        if( g->m.has_flag_ter_or_furn( "FUNGUS" , pos() ) ) {
            add_msg_if_player( m_good, _( "Our fibers meld with the ground beneath us.  The gills on our neck begin to seed the air with spores as our awareness fades." ) );
        }
    }
    if (has_trait( trait_WEB_WALKER )) {
        websleep = true;
    }
    // Not sure how one would get Arachnid w/o web-making, but Just In Case
    if (has_trait( trait_THRESH_SPIDER ) && (has_trait( trait_WEB_SPINNER ) || (has_trait( trait_WEB_WEAVER ))) ) {
        webforce = true;
    }
    if (websleep || webforce) {
        int web = g->m.get_field_strength( pos(), fd_web );
            if (!webforce) {
            // At this point, it's kinda weird, but surprisingly comfy...
            if (web >= 3) {
                add_msg_if_player(m_good, _("These thick webs support your weight, and are strangely comfortable..."));
                websleeping = true;
            } else if( web > 0 ) {
                add_msg_if_player(m_info, _("You try to sleep, but the webs get in the way.  You brush them aside."));
                g->m.remove_field( pos(), fd_web );
            }
        } else {
            // Here, you're just not comfortable outside a nice thick web.
            if (web >= 3) {
                add_msg_if_player(m_good, _("You relax into your web."));
                websleeping = true;
            } else {
                add_msg_if_player(m_bad, _("You try to sleep, but you feel exposed and your spinnerets keep twitching."));
                add_msg_if_player(m_info, _("Maybe a nice thick web would help you sleep."));
            }
        }
    }
    if (has_active_mutation( trait_SHELL2 )) {
        // Your shell's interior is a comfortable place to sleep.
        in_shell = true;
    }
    if( has_trait( trait_WATERSLEEP ) ) {
        if( underwater ) {
            add_msg_if_player( m_good, _( "You lay beneath the waves' embrace, gazing up through the water's surface..." ) );
            watersleep = true;
        } else if( g->m.has_flag_ter( "SWIMMABLE", pos() ) ) {
            add_msg_if_player( m_good, _( "You settle into the water and begin to drowse..." ) );
            watersleep = true;
        }
    }
    if( !plantsleep && ( furn_at_pos.obj().comfort > static_cast<int>( comfort_level::neutral ) ||
                         ter_at_pos == t_improvised_shelter ||
                         trap_at_pos.loadid == tr_cot || trap_at_pos.loadid == tr_rollmat ||
                         trap_at_pos.loadid == tr_fur_rollmat ||
                         in_shell || websleeping || watersleep ||
                         vp.part_with_feature( "SEAT", true ) ||
                         vp.part_with_feature( "BED", true ) ) ) {
        add_msg_if_player(m_good, _("This is a comfortable place to sleep."));
    } else if (ter_at_pos != t_floor && !plantsleep && !fungaloid_cosplay && !watersleep) {
        add_msg_if_player( ter_at_pos.obj().movecost <= 2 ?
                 _("It's a little hard to get to sleep on this %s.") :
                 _("It's hard to get to sleep on this %s."),
                 ter_at_pos.obj().name().c_str() );
    }
    add_msg_if_player( _( "You start trying to fall asleep." ) );
    assign_activity( activity_id( "ACT_TRY_SLEEP" ), to_moves<int>( dur ) );
}

comfort_level player::base_comfort_value( const tripoint &p ) const
{
    // Comfort of sleeping spots is "objective", while sleep_spot( p ) is "subjective"
    // As in the latter also checks for fatigue and other variables while this function
    // only looks at the base comfyness of something. It's still subjective, in a sense,
    // as arachnids who sleep in webs will find most places comfortable for instance.
    int comfort = 0;

    bool plantsleep = has_trait( trait_CHLOROMORPH );
    bool fungaloid_cosplay = has_trait( trait_M_SKIN3 );
    bool websleep = has_trait( trait_WEB_WALKER );
    bool webforce = has_trait( trait_THRESH_SPIDER ) && ( has_trait( trait_WEB_SPINNER ) || ( has_trait( trait_WEB_WEAVER ) ) );
    bool in_shell = has_active_mutation( trait_SHELL2 );
    bool watersleep = has_trait( trait_WATERSLEEP );

    const optional_vpart_position vp = g->m.veh_at( p );
    const maptile tile = g->m.maptile_at( p );
    const trap &trap_at_pos = tile.get_trap_t();
    const ter_id ter_at_pos = tile.get_ter();
    const furn_id furn_at_pos = tile.get_furn();

    int web = g->m.get_field_strength( p, fd_web );

    // Some mutants have different comfort needs
    if( !plantsleep && !webforce ) {
        if( in_shell ) {
            comfort += 1 + static_cast<int>( comfort_level::slightly_comfortable );
            // Note: shelled individuals can still use sleeping aids!
        }
        else if( vp ) {
            if( vp.part_with_feature( "BED", true ) ) {
                comfort += 1 + static_cast<int>( comfort_level::slightly_comfortable );
            }
            else if( vp.part_with_feature( "SEAT", true ) ) {
                comfort += 0 + static_cast<int>( comfort_level::slightly_comfortable );
            }
            else {
                // Sleeping elsewhere is uncomfortable
                comfort -= g->m.move_cost( p );
            }
        }
        // Not in a vehicle, start checking furniture/terrain/traps at this point in decreasing order
        else if( furn_at_pos != f_null ) {
            comfort += 0 + furn_at_pos.obj().comfort;
        }
        else if( trap_at_pos.loadid == tr_cot ) {
            comfort += 1 + static_cast<int>( comfort_level::slightly_comfortable );
        }
        // Web sleepers can use their webs if better furniture isn't available
        else if( websleep && web >= 3 ) {
            comfort += 1 + static_cast<int>( comfort_level::slightly_comfortable );
        }
        else if( trap_at_pos.loadid == tr_rollmat || trap_at_pos.loadid == tr_fur_rollmat ||
                 ter_at_pos == t_improvised_shelter ) {
            comfort += 0 + static_cast<int>( comfort_level::slightly_comfortable );
        }
        else if( ter_at_pos == t_floor || ter_at_pos == t_floor_waxed ||
                 ter_at_pos == t_carpet_red || ter_at_pos == t_carpet_yellow ||
                 ter_at_pos == t_carpet_green || ter_at_pos == t_carpet_purple ) {
            comfort += 1 + static_cast<int>( comfort_level::neutral );
        }
        else {
         // Not a comfortable sleeping spot
            comfort -= g->m.move_cost( p );
        }

        auto items = g->m.i_at( p );
        for( auto &items_it : items ) {
            if( items_it.has_flag( "SLEEP_AID" ) ) {
                // Note: BED + SLEEP_AID = 9 pts, or 1 pt below very_comfortable
                comfort += 1 + static_cast<int>( comfort_level::slightly_comfortable );
                break; // prevents using more than 1 sleep aid
            }
        }

        if( fungaloid_cosplay && g->m.has_flag_ter_or_furn( "FUNGUS", pos() ) ) {
            comfort += static_cast<int>( comfort_level::very_comfortable );
        }
        else if( watersleep && g->m.has_flag_ter( "SWIMMABLE", pos() ) ) {
            comfort += static_cast<int>( comfort_level::very_comfortable );
        }
    }
    else if( plantsleep ) {
        if( vp || furn_at_pos != f_null ) {
            // Sleep ain't happening in a vehicle or on furniture
            comfort = static_cast<int>( comfort_level::uncomfortable );
        }
        else {
            // It's very easy for Chloromorphs to get to sleep on soil!
            if( ter_at_pos == t_dirt || ter_at_pos == t_pit || ter_at_pos == t_dirtmound ||
                ter_at_pos == t_pit_shallow ) {
                comfort += static_cast<int>( comfort_level::very_comfortable );
            }
            // Not as much if you have to dig through stuff first
            else if( ter_at_pos == t_grass ) {
                comfort += static_cast<int>( comfort_level::comfortable );
            }
            // Sleep ain't happening
            else {
                comfort = static_cast<int>( comfort_level::uncomfortable );
            }
        }
    // Has webforce
    } else {
        if( web >= 3 ) {
            // Thick Web and you're good to go
            comfort += static_cast<int>( comfort_level::very_comfortable );
        }
        else {
            comfort = static_cast<int>( comfort_level::uncomfortable );
        }
    }

    if( comfort >= static_cast<int>( comfort_level::very_comfortable ) ) {
        return comfort_level::very_comfortable;
    }
    else if( comfort >= static_cast<int>( comfort_level::comfortable ) ) {
        return comfort_level::comfortable;
    }
    else if( comfort >= static_cast<int>( comfort_level::slightly_comfortable ) ) {
        return comfort_level::slightly_comfortable;
    }
    else if( comfort >= static_cast<int>( comfort_level::neutral ) ) {
        return comfort_level::neutral;
    }
    else return comfort_level::uncomfortable;
}

int player::sleep_spot( const tripoint &p ) const
{
    comfort_level base_level = base_comfort_value( p );
    int sleepy = static_cast<int>( base_level );
    bool watersleep = has_trait( trait_WATERSLEEP );

    if (has_addiction(ADD_SLEEP)) {
        sleepy -= 4;
    }
    if (has_trait( trait_INSOMNIA )) {
        // 12.5 points is the difference between "tired" and "dead tired"
        sleepy -= 12;
    }
    if (has_trait( trait_EASYSLEEPER )) {
        // Low fatigue (being rested) has a much stronger effect than high fatigue
        // so it's OK for the value to be that much higher
        sleepy += 24;
    }
    if (has_trait( trait_EASYSLEEPER2 )) {
        // Mousefolk can sleep just about anywhere.
        sleepy += 40;
    }
    if( watersleep && g->m.has_flag_ter( "SWIMMABLE", pos() ) ) {
        sleepy += 10; //comfy water!
    }

    if( get_fatigue() < TIRED + 1 ) {
        sleepy -= int( ( TIRED + 1 - get_fatigue() ) / 4 );
    } else {
        sleepy += int( ( get_fatigue() - TIRED + 1 ) / 16 );
    }

    if( stim > 0 || !has_trait( trait_INSOMNIA ) ) {
        sleepy -= 2 * stim;
    } else {
        // Make it harder for insomniac to get around the trait
        sleepy -= stim;
    }

    return sleepy;
}

bool player::can_sleep()
{
    if( has_effect( effect_meth ) ) {
        // Sleep ain't happening until that meth wears off completely.
        return false;
    }

    // Since there's a bit of randomness to falling asleep, we want to
    // prevent exploiting this if can_sleep() gets called over and over.
    // Only actually check if we can fall asleep no more frequently than
    // every 30 minutes.  We're assuming that if we return true, we'll
    // immediately be falling asleep after that.
    //
    // Also if player debug menu'd time backwards this breaks, just do the
    // check anyway, this will reset the timer if 'dur' is negative.
    const time_point now = calendar::turn;
    const time_duration dur = now - last_sleep_check;
    if( dur >= 30_minutes || dur < 0_turns ) {
        last_sleep_check = now;
        int sleepy = sleep_spot( pos() );
        sleepy += rng( -8, 8 );
        if( sleepy > 0 ) {
            return true;
        }
    }
    return false;
}

void player::fall_asleep()
{
    // Communicate to the player that he is using items on the floor
    std::string item_name = is_snuggling();
    if( item_name == "many" ) {
        if( one_in( 15 ) ) {
            add_msg_if_player( _( "You nestle your pile of clothes for warmth." ) );
        } else {
            add_msg_if_player( _( "You use your pile of clothes for warmth." ) );
        }
    } else if( item_name != "nothing" ) {
        if( one_in( 15 ) ) {
            add_msg_if_player( _( "You snuggle your %s to keep warm." ), item_name.c_str() );
        } else {
            add_msg_if_player( _( "You use your %s to keep warm." ), item_name.c_str() );
        }
    }
    if( has_active_mutation( trait_id( "HIBERNATE" ) ) && get_hunger() < -60 ) {
        add_memorial_log( pgettext( "memorial_male", "Entered hibernation." ),
                          pgettext( "memorial_female", "Entered hibernation." ) );
        // some days worth of round-the-clock Snooze.  Cata seasons default to 91 days.
        fall_asleep( 10_days );
        // If you're not fatigued enough for 10 days, you won't sleep the whole thing.
        // In practice, the fatigue from filling the tank from (no msg) to Time For Bed
        // will last about 8 days.
    }

    fall_asleep( 10_hours ); // default max sleep time.
}

void player::fall_asleep( const time_duration &duration )
{
    if( activity ) {
        if( activity.id() == "ACT_TRY_SLEEP" ) {
            activity.set_to_null();
        } else {
            cancel_activity();
        }
    }
    add_effect( effect_sleep, duration );
}

void player::wake_up()
{
    if( has_effect( effect_sleep ) ) {
        if( calendar::turn - get_effect( effect_sleep ).get_start_time() > 2_hours ) {
            print_health();
        }
        if( has_effect( effect_slept_through_alarm ) ) {
            if( has_bionic( bio_watch ) ) {
                add_msg_if_player( m_warning, _( "It looks like you've slept through your internal alarm..." ) );
            } else {
                add_msg_if_player( m_warning, _( "It looks like you've slept through the alarm..." ) );
            }
        }
    }

    remove_effect( effect_sleep );
    remove_effect( effect_slept_through_alarm );
    remove_effect( effect_lying_down );
    recalc_sight_limits();
}

std::string player::is_snuggling() const
{
    auto begin = g->m.i_at( pos() ).begin();
    auto end = g->m.i_at( pos() ).end();

    if( in_vehicle ) {
        if( const cata::optional<vpart_reference> vp = g->m.veh_at( pos() ).part_with_feature( VPFLAG_CARGO, false ) ) {
            vehicle *const veh = &vp->vehicle();
            const int cargo = vp->part_index();
            if( !veh->get_items(cargo).empty() ) {
                begin = veh->get_items(cargo).begin();
                end = veh->get_items(cargo).end();
            }
        }
    }
    const item* floor_armor = nullptr;
    int ticker = 0;

    // If there are no items on the floor, return nothing
    if( begin == end ) {
        return "nothing";
    }

    for( auto candidate = begin; candidate != end; ++candidate ) {
        if( !candidate->is_armor() ) {
            continue;
        } else if( candidate->volume() > 250_ml && candidate->get_warmth() > 0 &&
                   ( candidate->covers( bp_torso ) || candidate->covers( bp_leg_l ) ||
                     candidate->covers( bp_leg_r ) ) ) {
            floor_armor = &*candidate;
            ticker++;
        }
    }

    if ( ticker == 0 ) {
        return "nothing";
    }
    else if ( ticker == 1 ) {
        return floor_armor->type_name();
    }
    else if ( ticker > 1 ) {
        return "many";
    }

    return "nothing";
}

// Returned values range from 1.0 (unimpeded vision) to 11.0 (totally blind).
//  1.0 is LIGHT_AMBIENT_LIT or brighter
//  4.0 is a dark clear night, barely bright enough for reading and crafting
//  6.0 is LIGHT_AMBIENT_DIM
//  7.3 is LIGHT_AMBIENT_MINIMAL, a dark cloudy night, unlit indoors
// 11.0 is zero light or blindness
float player::fine_detail_vision_mod() const
{
    // PER_SLIME_OK implies you can get enough eyes around the bile
    // that you can generally see.  There still will be the haze, but
    // it's annoying rather than limiting.
    if( is_blind() ||
         ( ( has_effect( effect_boomered ) || has_effect( effect_darkness ) ) && !has_trait( trait_PER_SLIME_OK ) ) ) {
        return 11.0;
    }
    // Scale linearly as light level approaches LIGHT_AMBIENT_LIT.
    // If we're actually a source of light, assume we can direct it where we need it.
    // Therefore give a hefty bonus relative to ambient light.
    float own_light = std::max( 1.0, LIGHT_AMBIENT_LIT - active_light() - 2 );

    // Same calculation as above, but with a result 3 lower.
    float ambient_light = std::max( 1.0, LIGHT_AMBIENT_LIT - g->m.ambient_light_at( pos() ) + 1.0 );

    return std::min( own_light, ambient_light );
}

int player::get_wind_resistance(body_part bp) const
{
    int coverage = 0;
    float totalExposed = 1.0;
    int totalCoverage = 0;
    int penalty = 100;

    for( auto &i : worn ) {
        if( i.covers(bp) ) {
            if( i.made_of( material_id( "leather" ) ) || i.made_of( material_id( "plastic" ) ) || i.made_of( material_id( "bone" ) ) ||
                i.made_of( material_id( "chitin" ) ) || i.made_of( material_id( "nomex" ) ) ) {
                penalty = 10; // 90% effective
            } else if( i.made_of( material_id( "cotton" ) ) ) {
                penalty = 30;
            } else if( i.made_of( material_id( "wool" ) ) ) {
                penalty = 40;
            } else {
                penalty = 1; // 99% effective
            }

            coverage = std::max(0, i.get_coverage() - penalty);
            totalExposed *= (1.0 - coverage/100.0); // Coverage is between 0 and 1?
        }
    }

    // Your shell provides complete wind protection if you're inside it
    if (has_active_mutation( trait_SHELL2 )) {
        totalCoverage = 100;
        return totalCoverage;
    }

    totalCoverage = 100 - totalExposed*100;

    return totalCoverage;
}

int player::warmth(body_part bp) const
{
    int ret = 0;
    int warmth = 0;

    for (auto &i : worn) {
        if( i.covers( bp ) ) {
            warmth = i.get_warmth();
            // Wool items do not lose their warmth due to being wet.
            // Warmth is reduced by 0 - 66% based on wetness.
            if (!i.made_of( material_id( "wool" ) ))
            {
                warmth *= 1.0 - 0.66 * body_wetness[bp] / drench_capacity[bp];
            }
            ret += warmth;
        }
    }
    return ret;
}

int bestwarmth( const std::list< item > &its, const std::string &flag )
{
    int best = 0;
    for( auto &w : its ) {
        if( w.has_flag( flag ) && w.get_warmth() > best ) {
            best = w.get_warmth();
        }
    }
    return best;
}

int player::bonus_item_warmth(body_part bp) const
{
    int ret = 0;

    // If the player is not wielding anything big, check if hands can be put in pockets
    if( ( bp == bp_hand_l || bp == bp_hand_r ) && weapon.volume() < 500_ml ) {
        ret += bestwarmth( worn, "POCKETS" );
    }

    // If the player's head is not encumbered, check if hood can be put up
    if( bp == bp_head && encumb( bp_head ) < 10 ) {
        ret += bestwarmth( worn, "HOOD" );
    }

    // If the player's mouth is not encumbered, check if collar can be put up
    if( bp == bp_mouth && encumb( bp_mouth ) < 10 ) {
        ret += bestwarmth( worn, "COLLAR" );
    }

    return ret;
}

int player::get_armor_bash(body_part bp) const
{
    return get_armor_bash_base(bp) + armor_bash_bonus;
}

int player::get_armor_cut(body_part bp) const
{
    return get_armor_cut_base(bp) + armor_cut_bonus;
}

int player::get_armor_type( damage_type dt, body_part bp ) const
{
    switch( dt ) {
        case DT_TRUE:
            return 0;
        case DT_BIOLOGICAL:
            return 0;
        case DT_BASH:
            return get_armor_bash( bp );
        case DT_CUT:
            return get_armor_cut( bp );
        case DT_STAB:
            return get_armor_cut( bp ) * 0.8f;
        case DT_ACID:
        case DT_HEAT:
        case DT_COLD:
        case DT_ELECTRIC:
        {
            int ret = 0;
            for( auto &i : worn ) {
                if( i.covers( bp ) ) {
                    ret += i.damage_resist( dt );
                }
            }

            ret += mutation_armor( bp, dt );
            return ret;
        }
        case DT_NULL:
        case NUM_DT:
            // Let it error below
            break;
    }

    debugmsg( "Invalid damage type: %d", dt );
    return 0;
}

int player::get_armor_bash_base(body_part bp) const
{
    int ret = 0;
    for (auto &i : worn) {
        if (i.covers(bp)) {
            ret += i.bash_resist();
        }
    }
    if (has_bionic( bio_carbon ) ) {
        ret += 2;
    }
    if (bp == bp_head && has_bionic( bio_armor_head ) ) {
        ret += 3;
    }
    if ((bp == bp_arm_l || bp == bp_arm_r) && has_bionic( bio_armor_arms ) ) {
        ret += 3;
    }
    if (bp == bp_torso && has_bionic( bio_armor_torso ) ) {
        ret += 3;
    }
    if ((bp == bp_leg_l || bp == bp_leg_r) && has_bionic( bio_armor_legs ) ) {
        ret += 3;
    }
    if (bp == bp_eyes && has_bionic( bio_armor_eyes ) ) {
        ret += 3;
    }

    ret += mutation_armor( bp, DT_BASH );
    return ret;
}

int player::get_armor_cut_base(body_part bp) const
{
    int ret = 0;
    for (auto &i : worn) {
        if (i.covers(bp)) {
            ret += i.cut_resist();
        }
    }
    if (has_bionic( bio_carbon ) ) {
        ret += 4;
    }
    if (bp == bp_head && has_bionic( bio_armor_head ) ) {
        ret += 3;
    } else if ((bp == bp_arm_l || bp == bp_arm_r) && has_bionic( bio_armor_arms ) ) {
        ret += 3;
    } else if (bp == bp_torso && has_bionic( bio_armor_torso ) ) {
        ret += 3;
    } else if ((bp == bp_leg_l || bp == bp_leg_r) && has_bionic( bio_armor_legs ) ) {
        ret += 3;
    } else if (bp == bp_eyes && has_bionic( bio_armor_eyes ) ) {
        ret += 3;
    }

    ret += mutation_armor( bp, DT_CUT );
    return ret;
}

int player::get_armor_acid(body_part bp) const
{
    return get_armor_type( DT_ACID, bp );
}

int player::get_armor_fire(body_part bp) const
{
    return get_armor_type( DT_HEAT, bp );
}

void destroyed_armor_msg( Character &who, const std::string &pre_damage_name )
{
    //~ %s is armor name
    who.add_memorial_log( pgettext("memorial_male", "Worn %s was completely destroyed."),
                          pgettext("memorial_female", "Worn %s was completely destroyed."),
                          pre_damage_name.c_str() );
    who.add_msg_player_or_npc( m_bad, _("Your %s is completely destroyed!"),
                               _("<npcname>'s %s is completely destroyed!"),
                               pre_damage_name.c_str() );
}

bool player::armor_absorb( damage_unit& du, item& armor )
{
    if( rng( 1, 100 ) > armor.get_coverage() ) {
        return false;
    }

    // TODO: add some check for power armor
    armor.mitigate_damage( du );

    // We want armor's own resistance to this type, not the resistance it grants
    const int armors_own_resist = armor.damage_resist( du.type, true );
    if( armors_own_resist > 1000 ) {
        // This is some weird type that doesn't damage armors
        return false;
    }

    // Scale chance of article taking damage based on the number of parts it covers.
    // This represents large articles being able to take more punishment
    // before becoming ineffective or being destroyed.
    const int num_parts_covered = armor.get_covered_body_parts().count();
    if( !one_in( num_parts_covered ) ) {
        return false;
    }

    // Don't damage armor as much when bypassed by armor piercing
    // Most armor piercing damage comes from bypassing armor, not forcing through
    const int raw_dmg = du.amount;
    if( raw_dmg > armors_own_resist ) {
        // If damage is above armor value, the chance to avoid armor damage is
        // 50% + 50% * 1/dmg
        if( one_in( raw_dmg ) || one_in( 2 ) ) {
            return false;
        }
    } else {
        // Sturdy items and power armors never take chip damage.
        // Other armors have 0.5% of getting damaged from hits below their armor value.
        if( armor.has_flag("STURDY") || armor.is_power_armor() || !one_in( 200 ) ) {
            return false;
        }
    }

    auto &material = armor.get_random_material();
    std::string damage_verb = ( du.type == DT_BASH ) ?
        material.bash_dmg_verb() : material.cut_dmg_verb();

    const std::string pre_damage_name = armor.tname();
    const std::string pre_damage_adj = armor.get_base_material().dmg_adj( armor.damage_level( 4 ) );

    // add "further" if the damage adjective and verb are the same
    std::string format_string = ( pre_damage_adj == damage_verb ) ?
        _("Your %1$s is %2$s further!") : _("Your %1$s is %2$s!");
    add_msg_if_player( m_bad, format_string.c_str(), pre_damage_name.c_str(),
                       damage_verb.c_str());
    //item is damaged
    if( is_player() ) {
        SCT.add(posx(), posy(), NORTH, remove_color_tags( pre_damage_name ),
                m_neutral, damage_verb, m_info);
    }

    return armor.mod_damage( armor.has_flag( "FRAGILE" ) ?
        rng( 2 * itype::damage_scale, 3 * itype::damage_scale ) : itype::damage_scale, du.type );
}

float player::bionic_armor_bonus( body_part bp, damage_type dt ) const
{
    float result = 0.0f;
    // We only check the passive bionics
    if( has_bionic( bio_carbon ) ) {
        if( dt == DT_BASH ) {
            result += 2;
        } else if( dt == DT_CUT || dt == DT_STAB ) {
            result += 4;
        }
    }
    // All the other bionic armors reduce bash/cut/stab by 3
    // Map body parts to a set of bionics that protect it
    // @todo: JSONize passive bionic armor instead of hardcoding it
    static const std::map< body_part, bionic_id > armor_bionics = {
        { bp_head, { bio_armor_head } },
        { bp_arm_l, { bio_armor_arms } },
        { bp_arm_r, { bio_armor_arms } },
        { bp_torso, { bio_armor_torso } },
        { bp_leg_l, { bio_armor_legs } },
        { bp_leg_r, { bio_armor_legs } },
        { bp_eyes, { bio_armor_eyes } }
    };
    auto iter = armor_bionics.find( bp );
    if( iter != armor_bionics.end() && has_bionic( iter->second ) &&
        ( dt == DT_BASH || dt == DT_CUT || dt == DT_STAB ) ) {
        result += 3;
    }
    return result;
}

void player::passive_absorb_hit( body_part bp, damage_unit &du ) const
{
    // >0 check because some mutations provide negative armor
    // Thin skin check goes before subdermal armor plates because SUBdermal
    if( du.amount > 0.0f ) {
        // Horrible hack warning!
        // Get rid of this as soon as CUT and STAB are split
        if( du.type == DT_STAB ) {
            damage_unit du_copy = du;
            du_copy.type = DT_CUT;
            du.amount -= mutation_armor( bp, du_copy );
        } else {
            du.amount -= mutation_armor( bp, du );
        }
    }
    du.amount -= bionic_armor_bonus( bp, du.type ); //Check for passive armor bionics
    du.amount -= mabuff_armor_bonus( du.type );
    du.amount = std::max( 0.0f, du.amount );
}

void player::absorb_hit(body_part bp, damage_instance &dam) {
    std::list<item> worn_remains;
    bool armor_destroyed = false;

    for( auto &elem : dam.damage_units ) {
        if( elem.amount < 0 ) {
            // Prevents 0 damage hits (like from hallucinations) from ripping armor
            elem.amount = 0;
            continue;
        }

        // The bio_ads CBM absorbs damage before hitting armor
        if( has_active_bionic( bio_ads ) ) {
            if( elem.amount > 0 && power_level > 24 ) {
                if( elem.type == DT_BASH ) {
                    elem.amount -= rng( 1, 8 );
                } else if( elem.type == DT_CUT ) {
                    elem.amount -= rng( 1, 4 );
                } else if( elem.type == DT_STAB ) {
                    elem.amount -= rng( 1, 2 );
                }
                charge_power(-25);
            }
            if( elem.amount < 0 ) {
                elem.amount = 0;
            }
        }

        // Only the outermost armor can be set on fire
        bool outermost = true;
        // The worn vector has the innermost item first, so
        // iterate reverse to damage the outermost (last in worn vector) first.
        for( auto iter = worn.rbegin(); iter != worn.rend(); ) {
            item& armor = *iter;

            if( !armor.covers( bp ) ) {
                ++iter;
                continue;
            }

            const std::string pre_damage_name = armor.tname();
            bool destroy = false;

            // Heat damage can set armor on fire
            // Even though it doesn't cause direct physical damage to it
            if( outermost && elem.type == DT_HEAT && elem.amount >= 1.0f ) {
                // @todo: Different fire intensity values based on damage
                fire_data frd{ 2 };
                destroy = armor.burn( frd );
                int fuel = roll_remainder( frd.fuel_produced );
                if( fuel > 0 ) {
                    add_effect( effect_onfire, time_duration::from_turns( fuel + 1 ), bp, false, 0, false, true );
                }
            }

            if( !destroy ) {
                destroy = armor_absorb( elem, armor );
            }

            if( destroy ) {
                if( g->u.sees( *this ) ) {
                    SCT.add( posx(), posy(), NORTH, remove_color_tags( pre_damage_name ),
                             m_neutral, _( "destroyed" ), m_info);
                }
                destroyed_armor_msg( *this, pre_damage_name );
                armor_destroyed = true;
                armor.on_takeoff( *this );
                worn_remains.insert( worn_remains.end(), armor.contents.begin(), armor.contents.end() );
                // decltype is the type name of the iterator, note that reverse_iterator::base returns the
                // iterator to the next element, not the one the revers_iterator points to.
                // http://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator
                iter = decltype(iter)( worn.erase( --( iter.base() ) ) );
            } else {
                ++iter;
                outermost = false;
            }
        }

        passive_absorb_hit( bp, elem );

        if( elem.type == DT_BASH ) {
            if( has_trait( trait_LIGHT_BONES ) ) {
                elem.amount *= 1.4;
            }
            if( has_trait( trait_HOLLOW_BONES ) ) {
                elem.amount *= 1.8;
            }
        }

        elem.amount = std::max( elem.amount, 0.0f );
    }
    for( item& remain : worn_remains ) {
        g->m.add_item_or_charges( pos(), remain );
    }
    if( armor_destroyed ) {
        drop_invalid_inventory();
    }
}

int player::get_env_resist(body_part bp) const
{
    int ret = 0;
    for (auto &i : worn) {
        // Head protection works on eyes too (e.g. baseball cap)
        if (i.covers(bp) || (bp == bp_eyes && i.covers(bp_head))) {
            ret += i.get_env_resist();
        }
    }

    if (bp == bp_mouth && has_bionic( bio_purifier ) && ret < 5) {
        ret += 2;
        if (ret > 5) {
            ret = 5;
        }
    }

    if (bp == bp_eyes && has_bionic( bio_armor_eyes ) && ret < 5) {
        ret += 2;
        if (ret > 5) {
            ret = 5;
        }
    }
    if( bp == bp_eyes && has_trait( trait_SEESLEEP ) ) {
        ret += 8;
    }
    return ret;
}

bool player::wearing_something_on(body_part bp) const
{
    for (auto &i : worn) {
        if (i.covers(bp))
            return true;
    }
    return false;
}

bool player::natural_attack_restricted_on( body_part bp ) const
{
    for( auto &i : worn ) {
        if( i.covers( bp ) && !i.has_flag( "ALLOWS_NATURAL_ATTACKS" ) ) {
            return true;
        }
    }
    return false;
}

bool player::is_wearing_shoes( const side &which_side ) const
{
    bool left = true;
    bool right = true;
    if( which_side == side::LEFT || which_side == side::BOTH ) {
        left = false;
        for( const item &worn_item : worn ) {
            if (worn_item.covers(bp_foot_l) &&
                !worn_item.has_flag("BELTED") &&
                !worn_item.has_flag("SKINTIGHT")) {
                left = true;
                break;
            }
        }
    }
    if( which_side == side::RIGHT || which_side == side::BOTH ) {
        right = false;
        for( const item &worn_item : worn ) {
            if (worn_item.covers(bp_foot_r) &&
                !worn_item.has_flag("BELTED") &&
                !worn_item.has_flag("SKINTIGHT")) {
                right = true;
                break;
            }
        }
    }
    return (left && right);
}

bool player::is_wearing_helmet() const
{
    for( const item &i : worn ) {
        if( i.covers( bp_head ) &&
            !i.has_flag( "HELMET_COMPAT" ) &&
            !i.has_flag( "SKINTIGHT" ) &&
            !i.has_flag( "OVERSIZE" ) ) {
            return true;
        }
    }
    return false;
}

int player::head_cloth_encumbrance() const
{
    int ret = 0;
    for( auto &i : worn ) {
        const item *worn_item = &i;
        if( i.covers( bp_head ) && ( worn_item->has_flag( "HELMET_COMPAT" ) ||
                                     worn_item->has_flag( "SKINTIGHT" ) ) ) {
            ret += worn_item->get_encumber( *this );
        }
    }
    return ret;
}

double player::footwear_factor() const
{
    double ret = 0;
    if (wearing_something_on(bp_foot_l)) {
        ret += .5;
    }
    if (wearing_something_on(bp_foot_r)) {
        ret += .5;
    }
    return ret;
}

int player::shoe_type_count(const itype_id & it) const
{
    int ret = 0;
    if (is_wearing_on_bp(it, bp_foot_l)) {
        ret++;
    }
    if (is_wearing_on_bp(it, bp_foot_r)) {
        ret++;
    }
    return ret;
}

bool player::is_wearing_power_armor(bool *hasHelmet) const {
    bool result = false;
    for( auto &elem : worn ) {
        if( !elem.is_power_armor() ) {
            continue;
        }
        if (hasHelmet == nullptr) {
            // found power armor, helmet not requested, cancel loop
            return true;
        }
        // found power armor, continue search for helmet
        result = true;
        if( elem.covers( bp_head ) ) {
            *hasHelmet = true;
            return true;
        }
    }
    return result;
}

int player::adjust_for_focus(int amount) const
{
    int effective_focus = focus_pool;
    if (has_trait( trait_FASTLEARNER ))
    {
        effective_focus += 15;
    }
    if( has_active_bionic( bio_memory ) ) {
        effective_focus += 10;
    }
    if (has_trait( trait_SLOWLEARNER ))
    {
        effective_focus -= 15;
    }
    double tmp = amount * (effective_focus / 100.0);
    return roll_remainder(tmp);
}

void player::practice( const skill_id &id, int amount, int cap )
{
    SkillLevel &level = get_skill_level_object( id );
    const Skill &skill = id.obj();
    std::string skill_name = skill.name();

    if( !level.can_train() ) {
        // If leveling is disabled, don't train, don't drain focus, don't print anything
        // Leaving as a skill method rather than global for possible future skill cap setting
        return;
    }

    const auto highest_skill = [&]() {
        std::pair<skill_id, int> result( skill_id::NULL_ID(), -1 );
        for( const auto &pair : *_skills ) {
            const SkillLevel &lobj = pair.second;
            if( lobj.level() > result.second ) {
                result = std::make_pair( pair.first, lobj.level() );
            }
        }
        return result.first;
    };

    const bool isSavant = has_trait( trait_SAVANT );
    const skill_id savantSkill = isSavant ? highest_skill() : skill_id::NULL_ID();

    amount = adjust_for_focus(amount);

    if (has_trait( trait_PACIFIST ) && skill.is_combat_skill()) {
        if(!one_in(3)) {
          amount = 0;
        }
    }
    if (has_trait( trait_PRED2 ) && skill.is_combat_skill()) {
        if(one_in(3)) {
          amount *= 2;
        }
    }
    if (has_trait( trait_PRED3 ) && skill.is_combat_skill()) {
        amount *= 2;
    }

    if (has_trait( trait_PRED4 ) && skill.is_combat_skill()) {
        amount *= 3;
    }

    if (isSavant && id != savantSkill ) {
        amount /= 2;
    }

    if (amount > 0 && get_skill_level( id ) > cap) { //blunt grinding cap implementation for crafting
        amount = 0;
        if(is_player() && one_in(5)) {//remind the player intermittently that no skill gain takes place
            int curLevel = get_skill_level( id );
            add_msg(m_info, _("This task is too simple to train your %s beyond %d."),
                    skill_name, curLevel);
        }
    }

    if (amount > 0 && level.isTraining()) {
        int oldLevel = get_skill_level( id );
        get_skill_level_object( id ).train( amount );
        int newLevel = get_skill_level( id );
        if (is_player() && newLevel > oldLevel) {
            add_msg( m_good, _( "Your skill in %s has increased to %d!" ), skill_name, newLevel );
            const std::string skill_increase_source = "training";
            CallbackArgumentContainer lua_callback_args_info;
            lua_callback_args_info.emplace_back( getID() );
            lua_callback_args_info.emplace_back( skill_increase_source );
            lua_callback_args_info.emplace_back( id.str() );
            lua_callback_args_info.emplace_back( newLevel );
            lua_callback( "on_player_skill_increased", lua_callback_args_info );
            lua_callback( "on_skill_increased" ); //Legacy callback
        }
        if(is_player() && newLevel > cap) {
            //inform player immediately that the current recipe can't be used to train further
            add_msg( m_info, _( "You feel that %s tasks of this level are becoming trivial." ),
                     skill_name );
        }

        int chance_to_drop = focus_pool;
        focus_pool -= chance_to_drop / 100;
        // Apex Predators don't think about much other than killing.
        // They don't lose Focus when practicing combat skills.
        if ((rng(1, 100) <= (chance_to_drop % 100)) && (!(has_trait( trait_PRED4 ) &&
                                                          skill.is_combat_skill()))) {
            focus_pool--;
        }
    }

    get_skill_level_object( id ).practice();
}

int player::exceeds_recipe_requirements( const recipe &rec ) const
{
    return get_all_skills().exceeds_recipe_requirements( rec );
}

bool player::has_recipe_requirements( const recipe &rec ) const
{
    return get_all_skills().has_recipe_requirements( rec );
}

bool player::can_decomp_learn( const recipe &rec ) const
{
    return !rec.learn_by_disassembly.empty() &&
           meets_skill_requirements( rec.learn_by_disassembly );
}

bool player::knows_recipe(const recipe *rec) const
{
    return get_learned_recipes().contains( rec );
}

int player::has_recipe( const recipe *r, const inventory &crafting_inv,
                        const std::vector<npc *> &helpers ) const
{
    if( !r->skill_used ) {
        return 0;
    }

    if( knows_recipe( r ) ) {
        return r->difficulty;
    }

    const auto available = get_available_recipes( crafting_inv, &helpers );
    return available.contains( r ) ? available.get_custom_difficulty( r ) : -1;
}

void player::learn_recipe( const recipe * const rec )
{
    if( rec->never_learn ){
        return;
    }
    learned_recipes->include( rec );
}

void player::assign_activity( const activity_id &type, int moves, int index, int pos, const std::string &name )
{
    assign_activity( player_activity( type, moves, index, pos, name ) );
}

void player::assign_activity( const player_activity &act, bool allow_resume )
{
    if( allow_resume && !backlog.empty() && backlog.front().can_resume_with( act, *this ) ) {
        add_msg_if_player( _("You resume your task.") );
        activity = backlog.front();
        backlog.pop_front();
        activity.resume_with( act );
    } else {
        if( activity ) {
            backlog.push_front( activity );
        }

        activity = act;
    }

    if( activity.rooted() ) {
        rooted_message();
    }
}

bool player::has_activity(const activity_id &type) const
{
    return activity.id() == type;
}

void player::cancel_activity()
{
    if( has_activity( activity_id( "ACT_MOVE_ITEMS" ) ) && is_hauling() ) {
        stop_hauling();
    }
    // Clear any backlog items that aren't auto-resume.
    for( auto backlog_item = backlog.begin(); backlog_item != backlog.end(); ) {
        if( backlog_item->auto_resume ) {
            backlog_item++;
        } else {
            backlog_item = backlog.erase( backlog_item );
        }
    }
    if( activity && activity.is_suspendable() ) {
        backlog.push_front( activity );
    }
    activity = player_activity();
}

void player::resume_backlog_activity()
{
    if( !backlog.empty() && backlog.front().auto_resume ) {
        activity = backlog.front();
        backlog.pop_front();
    }
}

bool player::has_gun_for_ammo( const ammotype &at ) const
{
    return has_item_with( [at]( const item & it ) {
        // item::ammo_type considers the active gunmod.
        return it.is_gun() && it.ammo_type() == at;
    } );
}

bool player::has_magazine_for_ammo( const ammotype &at ) const
{
    return has_item_with( [&at]( const item & it ) {
        return !it.has_flag( "NO_RELOAD" ) &&
               ( ( it.is_magazine() && it.ammo_type() == at ) ||
                 ( it.is_gun() && it.magazine_integral() && it.ammo_type() == at ) ||
                 ( it.is_gun() && it.magazine_current() != nullptr &&
                   it.magazine_current()->ammo_type() == at ) );
    } );
}

// mytest return weapon name to display in sidebar
std::string player::weapname() const
{
    if( weapon.is_gun() ) {
        std::string str;
        str = weapon.type_name();

        // Is either the base item or at least one auxiliary gunmod loaded (includes empty magazines)
        bool base = weapon.ammo_capacity() > 0 && !weapon.has_flag( "RELOAD_AND_SHOOT" );

        const auto mods = weapon.gunmods();
        bool aux = std::any_of( mods.begin(), mods.end(), [&]( const item *e ) {
            return e->is_gun() && e->ammo_capacity() > 0 && !e->has_flag( "RELOAD_AND_SHOOT" );
        } );

        if( base || aux ) {
            str += " (";
            if( base ) {
                str += std::to_string( weapon.ammo_remaining() );
                if( weapon.magazine_integral() ) {
                    str += "/" + std::to_string( weapon.ammo_capacity() );
                }
            } else {
                str += "---";
            }
            str += ")";

            for( auto e : mods ) {
                if( e->is_gun() && e->ammo_capacity() > 0 && !e->has_flag( "RELOAD_AND_SHOOT" ) ) {
                    str += " (" + std::to_string( e->ammo_remaining() );
                    if( e->magazine_integral() ) {
                        str += "/" + std::to_string( e->ammo_capacity() );
                    }
                    str += ")";
                }
            }
        }
        return _( "Weapon:" ) + std::string( " " ) + str;

    } else if( weapon.is_container() && weapon.contents.size() == 1 ) {
        return string_format( "Weapon  : %s (%d)", weapon.tname().c_str(),
                              weapon.contents.front().charges );

    } else if( !is_armed() ) {
        return _( "Weapon  : fists" );

    } else {
      return _( "Weapon:" ) + std::string( " " ) + weapon.tname();
    }
}

bool player::wield_contents( item &container, int pos, bool penalties, int base_cost )
{
    // if index not specified and container has multiple items then ask the player to choose one
    if( pos < 0 ) {
        std::vector<std::string> opts;
        std::transform( container.contents.begin(), container.contents.end(),
                        std::back_inserter( opts ), []( const item& elem ) {
                            return elem.display_name();
                        } );
        if( opts.size() > 1 ) {
            pos = uilist( _( "Wield what?" ), opts );
            if( pos < 0 ) {
                return false;
            }
        } else {
            pos = 0;
        }
    }

    if( pos >= static_cast<int>(container.contents.size() ) ) {
        debugmsg( "Tried to wield non-existent item from container (player::wield_contents)" );
        return false;
    }

    auto target = std::next( container.contents.begin(), pos );
    const auto ret = can_wield( *target );
    if( !ret.success() ) {
        add_msg_if_player( m_info, "%s", ret.c_str() );
        return false;
    }

    int mv = 0;

    if( is_armed() ) {
        if( !unwield() ) {
            return false;
        }
        inv.unsort();
    }

    weapon = std::move( *target );
    container.contents.erase( target );
    container.on_contents_changed();

    inv.update_invlet( weapon );
    inv.update_cache_with_item( weapon );
    last_item = weapon.typeId();

    /**
     * @EFFECT_PISTOL decreases time taken to draw pistols from holsters
     * @EFFECT_SMG decreases time taken to draw smgs from holsters
     * @EFFECT_RIFLE decreases time taken to draw rifles from holsters
     * @EFFECT_SHOTGUN decreases time taken to draw shotguns from holsters
     * @EFFECT_LAUNCHER decreases time taken to draw launchers from holsters
     * @EFFECT_STABBING decreases time taken to draw stabbing weapons from sheathes
     * @EFFECT_CUTTING decreases time taken to draw cutting weapons from scabbards
     * @EFFECT_BASHING decreases time taken to draw bashing weapons from holsters
     */
    int lvl = get_skill_level( weapon.is_gun() ? weapon.gun_skill() : weapon.melee_skill() );
    mv += item_handling_cost( weapon, penalties, base_cost ) / ( ( lvl + 10.0f ) / 10.0f );

    moves -= mv;

    weapon.on_wield( *this, mv );

    return true;
}

void player::store( item& container, item& put, bool penalties, int base_cost )
{
    moves -= item_store_cost( put, container, penalties, base_cost );
    container.put_in( i_rem( &put ) );
}

nc_color encumb_color(int level)
{
 if (level < 0)
  return c_green;
 if (level < 10)
  return c_light_gray;
 if (level < 40)
  return c_yellow;
 if (level < 70)
  return c_light_red;
 return c_red;
}

float player::get_melee() const
{
    return get_skill_level( skill_id( "melee" ) );
}

void player::setID (int i)
{
    if( id >= 0 ) {
        debugmsg( "tried to set id of a npc/player, but has already a id: %d", id );
    } else if( i < -1 ) {
        debugmsg( "tried to set invalid id of a npc/player: %d", i );
    } else {
        id = i;
    }
}

int player::getID () const
{
    return this->id;
}

bool player::uncanny_dodge()
{
    bool is_u = this == &g->u;
    bool seen = g->u.sees( *this );
    if( this->power_level < 74 || !this->has_active_bionic( bio_uncanny_dodge ) ) { return false; }
    tripoint adjacent = adjacent_tile();
    charge_power(-75);
    if( adjacent.x != posx() || adjacent.y != posy()) {
        position.x = adjacent.x;
        position.y = adjacent.y;
        if( is_u ) {
            add_msg( _("Time seems to slow down and you instinctively dodge!") );
        } else if( seen ) {
            add_msg( _("%s dodges... so fast!"), this->disp_name().c_str() );

        }
        return true;
    }
    if( is_u ) {
        add_msg( _("You try to dodge but there's no room!") );
    } else if( seen ) {
        add_msg( _("%s tries to dodge but there's no room!"), this->disp_name().c_str() );
    }
    return false;
}

// adjacent_tile() returns a safe, unoccupied adjacent tile. If there are no such tiles, returns player position instead.
tripoint player::adjacent_tile() const
{
    std::vector<tripoint> ret;
    int dangerous_fields = 0;
    for( const tripoint &p : g->m.points_in_radius( pos(), 1 ) ) {
        if( p == pos() ) {
            // Don't consider player position
            continue;
        }
        const trap &curtrap = g->m.tr_at( p );
        if( g->critter_at( p ) == nullptr && g->m.passable( p ) &&
            (curtrap.is_null() || curtrap.is_benign()) ) {
            // Only consider tile if unoccupied, passable and has no traps
            dangerous_fields = 0;
            auto &tmpfld = g->m.field_at( p );
            for( auto &fld : tmpfld ) {
                const field_entry &cur = fld.second;
                if( cur.is_dangerous() ) {
                    dangerous_fields++;
                }
            }

            if( dangerous_fields == 0 ) {
                ret.push_back( p );
            }
        }
    }

    return random_entry( ret, pos() ); // player position if no valid adjacent tiles
}

int player::climbing_cost( const tripoint &from, const tripoint &to ) const
{
    if( !g->m.valid_move( from, to, false, true ) ) {
        return 0;
    }

    const int diff = g->m.climb_difficulty( from );

    if( diff > 5 ) {
        return 0;
    }

    return 50 + diff * 100;
    // TODO: All sorts of mutations, equipment weight etc.
}

void player::environmental_revert_effect()
{
    addictions.clear();
    morale->clear();

    for (int part = 0; part < num_hp_parts; part++) {
        hp_cur[part] = hp_max[part];
    }
    set_hunger(0);
    set_thirst(0);
    set_fatigue(0);
    set_healthy(0);
    set_healthy_mod(0);
    stim = 0;
    set_pain(0);
    set_painkiller(0);
    radiation = 0;

    recalc_sight_limits();
    reset_encumbrance();
}

bool player::is_invisible() const
{
    static const bionic_id str_bio_cloak("bio_cloak"); // This function used in monster::plan_moves
    static const bionic_id str_bio_night("bio_night");
    return (
        has_active_bionic(str_bio_cloak) ||
        has_active_bionic(str_bio_night) ||
        has_active_optcloak() ||
        has_trait( trait_DEBUG_CLOAK ) ||
        has_artifact_with(AEP_INVISIBLE)
    );
}

int player::visibility( bool, int ) const
{
    // 0-100 %
    if ( is_invisible() ) {
        return 0;
    }
    // @todo:
    // if ( dark_clothing() && light check ...
    int stealth_modifier = std::floor( mutation_value( "stealth_modifier" ) );
    return clamp( 100 - stealth_modifier, 40, 160 );
}

void player::set_destination(const std::vector<tripoint> &route, const player_activity &destination_activity)
{
    auto_move_route = route;
    this->destination_activity = destination_activity;
    destination_point.emplace( g->m.getabs( route.back() ) );
}

void player::clear_destination()
{
    auto_move_route.clear();
    destination_activity = player_activity();
    destination_point = cata::nullopt;
    next_expected_position = cata::nullopt;
}

bool player::has_destination() const
{
    return !auto_move_route.empty();
}

bool player::has_destination_activity() const
{
    return !destination_activity.is_null() && destination_point && position == g->m.getlocal( *destination_point );
}

void player::start_destination_activity()
{
    if( !has_destination_activity() ) {
        debugmsg( "Tried to start invalid destination activity" );
        return;
    }

    assign_activity( destination_activity );
    clear_destination();
}

std::vector<tripoint> &player::get_auto_move_route()
{
    return auto_move_route;
}

action_id player::get_next_auto_move_direction()
{
    if (!has_destination()) {
        return ACTION_NULL;
    }

    if( next_expected_position ) {
        if( pos() != *next_expected_position ) {
            // We're off course, possibly stumbling or stuck, cancel auto move
            return ACTION_NULL;
        }
    }

    next_expected_position.emplace( auto_move_route.front() );
    auto_move_route.erase(auto_move_route.begin());

    tripoint dp = *next_expected_position - pos();

    // Make sure the direction is just one step and that
    // all diagonal moves have 0 z component
    if( abs( dp.x ) > 1 || abs( dp.y ) > 1 || abs( dp.z ) > 1 ||
        ( abs( dp.z ) != 0 && ( abs( dp.x ) != 0 || abs( dp.y ) != 0 ) ) ) {
        // Should never happen, but check just in case
        return ACTION_NULL;
    }
    return get_movement_direction_from_delta( dp.x, dp.y, dp.z );
}

bool player::defer_move( const tripoint &next ) {
    // next must be adjacent to current pos
    if( square_dist( next, pos() ) != 1 ) {
        return false;
    }
    // next must be adjacent to subsequent move in any preexisting automove route
    if( has_destination() && square_dist( auto_move_route.front(), next ) != 1 ) {
        return false;
    }
    auto_move_route.insert( auto_move_route.begin(), next );
    next_expected_position = pos();
    return true;
}

void player::shift_destination(int shiftx, int shifty)
{
    if( next_expected_position ) {
        next_expected_position->x += shiftx;
        next_expected_position->y += shifty;
    }

    for( auto &elem : auto_move_route ) {
        elem.x += shiftx;
        elem.y += shifty;
    }
}

void player::grab( object_type grab_type, const tripoint &grab_point )
{
    this->grab_type = grab_type;
    this->grab_point = grab_point;

    path_settings->avoid_rough_terrain = grab_type != OBJECT_NONE;
}

object_type player::get_grab_type() const
{
    return grab_type;
}

void player::start_hauling()
{
    add_msg( _( "You start hauling items along the ground." ) );
    if( is_armed() ) {
        add_msg( m_warning, _( "Your hands are not free, which makes hauling slower." ) );
    }
    hauling = true;
}

void player::stop_hauling()
{
    add_msg( _( "You stop hauling items." ) );
    hauling = false;
    if( has_activity( activity_id( "ACT_MOVE_ITEMS" ) ) ) {
        cancel_activity();
    }
}

bool player::is_hauling() const
{
    return hauling;
}

bool player::has_weapon() const
{
    return !unarmed_attack();
}

m_size player::get_size() const
{
    return MS_MEDIUM;
}

int player::get_hp() const
{
    return get_hp( num_hp_parts );
}

int player::get_hp( hp_part bp ) const
{
    if( bp < num_hp_parts ) {
        return hp_cur[bp];
    }
    int hp_total = 0;
    for( int i = 0; i < num_hp_parts; ++i ) {
        hp_total += hp_cur[i];
    }
    return hp_total;
}

int player::get_hp_max() const
{
    return get_hp_max( num_hp_parts );
}

int player::get_hp_max( hp_part bp ) const
{
    if( bp < num_hp_parts ) {
        return hp_max[bp];
    }
    int hp_total = 0;
    for( int i = 0; i < num_hp_parts; ++i ) {
        hp_total += hp_max[i];
    }
    return hp_total;
}

int player::get_stamina_max() const
{
    int maxStamina = get_option< int >( "PLAYER_MAX_STAMINA" );
    if( has_trait( trait_BADCARDIO ) ) {
        return maxStamina * 0.75;
    }
    if( has_trait( trait_GOODCARDIO ) ) {
        return maxStamina * 1.25;
    }
    if( has_trait( trait_GOODCARDIO2 ) ) {
        return maxStamina * 1.4;
    }
    return maxStamina;
}

void player::burn_move_stamina( int moves )
{
    int overburden_percentage = 0;
    units::mass current_weight = weight_carried();
    units::mass max_weight = weight_capacity();
    if (current_weight > max_weight) {
        overburden_percentage = (current_weight - max_weight) * 100 / max_weight;
    }
    // Regain 10 stamina / turn
    // 7/turn walking
    // 20/turn running
    int burn_ratio = 7;
    if( g->u.has_active_bionic( bionic_id( "bio_torsionratchet" ) ) ) {
        burn_ratio = burn_ratio * 2 - 3;
    }
    burn_ratio += overburden_percentage;
    if( move_mode == "run" ) {
        burn_ratio = burn_ratio * 3 - 1;
    }
    mod_stat( "stamina", -((moves * burn_ratio) / 100) );
    // Chance to suffer pain if overburden and stamina runs out or has trait BADBACK
    // Starts at 1 in 25, goes down by 5 for every 50% more carried
    if ((current_weight > max_weight) && (has_trait( trait_BADBACK ) || stamina == 0) && one_in(35 - 5 * current_weight / (max_weight / 2))) {
        add_msg_if_player(m_bad, _("Your body strains under the weight!"));
        // 1 more pain for every 800 grams more (5 per extra STR needed)
        if ( ( ( current_weight - max_weight ) / 800_gram > get_pain() && get_pain() < 100 ) ) {
            mod_pain(1);
        }
    }
}

Creature::Attitude player::attitude_to( const Creature &other ) const
{
    const auto m = dynamic_cast<const monster *>( &other );
    if( m != nullptr ) {
        if( m->friendly != 0 ) {
            return A_FRIENDLY;
        }
        switch( m->attitude( const_cast<player *>( this ) ) ) {
                // player probably does not want to harm them, but doesn't care much at all.
            case MATT_FOLLOW:
            case MATT_FPASSIVE:
            case MATT_IGNORE:
            case MATT_FLEE:
                return A_NEUTRAL;
                // player does not want to harm those.
            case MATT_FRIEND:
            case MATT_ZLAVE: // Don't want to harm your zlave!
                return A_FRIENDLY;
            case MATT_ATTACK:
                return A_HOSTILE;
            case MATT_NULL:
            case NUM_MONSTER_ATTITUDES:
                break;
        }

        return A_NEUTRAL;
    }

    const auto p = dynamic_cast<const npc *>( &other );
    if( p != nullptr ) {
        if( p->is_enemy() ) {
            return A_HOSTILE;
        } else if( p->is_friend() ) {
            return A_FRIENDLY;
        } else {
            return A_NEUTRAL;
        }
    } else if( &other == this ) {
        return A_FRIENDLY;
    }

    return A_NEUTRAL;
}

void player::toggle_map_memory()
{
    show_map_memory = !show_map_memory;
}

bool player::should_show_map_memory()
{
    return show_map_memory;
}

void player::serialize_map_memory( JsonOut &jsout ) const
{
    player_map_memory.store( jsout );
}

void player::deserialize_map_memory( JsonIn &jsin )
{
    player_map_memory.load( jsin );
}

memorized_terrain_tile player::get_memorized_tile( const tripoint &pos ) const
{
    return player_map_memory.get_tile( pos );
}

void player::memorize_tile( const tripoint &pos, const std::string &ter, const int subtile,
                            const int rotation )
{
    player_map_memory.memorize_tile( max_memorized_tiles(), pos, ter, subtile, rotation );
}

void player::memorize_symbol( const tripoint &pos, const long symbol )
{
    player_map_memory.memorize_symbol( max_memorized_tiles(), pos, symbol );
}

long player::get_memorized_symbol( const tripoint &p ) const
{
    return player_map_memory.get_symbol( p );
}

size_t player::max_memorized_tiles() const
{
    // Only check traits once a turn since this is called a huge number of times.
    if( current_map_memory_turn != calendar::turn ) {
        current_map_memory_turn = calendar::turn;
        if( has_active_bionic( bio_memory ) ) {
            current_map_memory_capacity = SEEX * SEEY * 20000; // 5000 overmap tiles
        } else if( has_trait( trait_FORGETFUL ) ) {
            current_map_memory_capacity = SEEX * SEEY * 200; // 50 overmap tiles
        } else if( has_trait( trait_GOODMEMORY ) ) {
            current_map_memory_capacity = SEEX * SEEY * 800; // 200 overmap tiles
        } else {
            current_map_memory_capacity = SEEX * SEEY * 400; // 100 overmap tiles
        }
    }
    return current_map_memory_capacity;
}

void player::clear_memorized_tile( const tripoint &pos )
{
    player_map_memory.clear_memorized_tile( pos );
}

bool player::sees( const tripoint &t, bool ) const
{
    static const bionic_id str_bio_night("bio_night");
    const int wanted_range = rl_dist( pos(), t );
    bool can_see = is_player() ? g->m.pl_sees( t, wanted_range ) :
        Creature::sees( t );
    // Clairvoyance is now pretty cheap, so we can check it early
    if( wanted_range < MAX_CLAIRVOYANCE && wanted_range < clairvoyance() ) {
        return true;
    }
    // Only check if we need to override if we already came to the opposite conclusion.
    if( can_see && wanted_range < 15 && wanted_range > sight_range(1) &&
        has_active_bionic(str_bio_night) ) {
        can_see = false;
    }
    if( can_see && wanted_range > unimpaired_range() ) {
        can_see = false;
    }

    return can_see;
}

bool player::sees( const Creature &critter ) const
{
    // This handles only the player/npc specific stuff (monsters don't have traits or bionics).
    const int dist = rl_dist( pos(), critter.pos() );
    if (dist <= 3 && has_trait( trait_ANTENNAE )) {
        return true;
    }
    if( critter.digging() && has_active_bionic( bio_ground_sonar ) ) {
        // Bypass the check below, the bionic sonar also bypasses the sees(point) check because
        // walls don't block sonar which is transmitted in the ground, not the air.
        // TODO: this might need checks whether the player is in the air, or otherwise not connected
        // to the ground. It also might need a range check.
        return true;
    }
    return Creature::sees( critter );
}

nc_color player::bodytemp_color(int bp) const
{
  nc_color color =  c_light_gray; // default
    if (bp == bp_eyes) {
        color = c_light_gray;    // Eyes don't count towards warmth
    } else if (temp_conv[bp] >  BODYTEMP_SCORCHING) {
        color = c_red;
    } else if (temp_conv[bp] >  BODYTEMP_VERY_HOT) {
        color = c_light_red;
    } else if (temp_conv[bp] >  BODYTEMP_HOT) {
        color = c_yellow;
    } else if (temp_conv[bp] >  BODYTEMP_COLD) {
        color = c_green;
    } else if (temp_conv[bp] >  BODYTEMP_VERY_COLD) {
        color = c_light_blue;
    } else if (temp_conv[bp] >  BODYTEMP_FREEZING) {
        color = c_cyan;
    } else if (temp_conv[bp] <= BODYTEMP_FREEZING) {
        color = c_blue;
    }
    return color;
}

//message related stuff
void player::add_msg_if_player( const std::string &msg ) const
{
    Messages::add_msg( msg );
}

void player::add_msg_player_or_npc( const std::string &player_msg, const std::string &/*npc_msg*/ ) const
{
    Messages::add_msg( player_msg );
}

void player::add_msg_if_player( const game_message_type type, const std::string &msg ) const
{
    Messages::add_msg( type, msg );
}

void player::add_msg_player_or_npc( const game_message_type type, const std::string &player_msg, const std::string &/*npc_msg*/ ) const
{
    Messages::add_msg( type, player_msg );
}

void player::add_msg_player_or_say( const std::string &player_msg, const std::string &/*npc_speech*/ ) const
{
    Messages::add_msg( player_msg );
}

void player::add_msg_player_or_say( const game_message_type type, const std::string &player_msg, const std::string &/*npc_speech*/ ) const
{
    Messages::add_msg( type, player_msg );
}

bool player::knows_trap( const tripoint &pos ) const
{
    const tripoint p = g->m.getabs( pos );
    return known_traps.count( p ) > 0;
}

void player::add_known_trap( const tripoint &pos, const trap &t)
{
    const tripoint p = g->m.getabs( pos );
    if( t.is_null() ) {
        known_traps.erase( p );
    } else {
        // TODO: known_traps should map to a trap_str_id
        known_traps[p] = t.id.str();
    }
}

bool player::is_deaf() const
{
    return get_effect_int( effect_deaf ) > 2 || worn_with_flag("DEAF") ||
           (has_active_bionic( bio_earplugs ) && !has_active_bionic( bio_ears ) ) ||
           ( has_trait( trait_M_SKIN3 ) && g->m.has_flag_ter_or_furn( "FUNGUS" , pos() ) && in_sleep_state() );
}

bool player::can_hear( const tripoint &source, const int volume ) const
{
    if( is_deaf() ) {
        return false;
    }

    // source is in-ear and at our square, we can hear it
    if ( source == pos() && volume == 0 ) {
        return true;
    }

    // TODO: sound attenuation due to weather

    const int dist = rl_dist( source, pos() );
    const float volume_multiplier = hearing_ability();
    return volume * volume_multiplier >= dist;
}

float player::hearing_ability() const
{
    float volume_multiplier = 1.0;

    // Mutation/Bionic volume modifiers
    if( has_active_bionic( bio_ears ) && !has_active_bionic( bio_earplugs ) ) {
        volume_multiplier *= 3.5;
    }
    if( has_trait( trait_PER_SLIME ) ) {
        // Random hearing :-/
        // (when it's working at all, see player.cpp)
        // changed from 0.5 to fix Mac compiling error
        volume_multiplier *= (rng(1, 2));
    }
    if( has_trait( trait_BADHEARING ) ) {
        volume_multiplier *= .5;
    }
    if( has_trait( trait_GOODHEARING ) ) {
        volume_multiplier *= 1.25;
    }
    if( has_trait( trait_CANINE_EARS ) ) {
        volume_multiplier *= 1.5;
    }
    if( has_trait( trait_URSINE_EARS ) || has_trait( trait_FELINE_EARS ) ) {
        volume_multiplier *= 1.25;
    }
    if( has_trait( trait_LUPINE_EARS ) ) {
        volume_multiplier *= 1.75;
    }
    if( has_trait( trait_MOUSE_EARS ) ) {
        volume_multiplier *= 2;
    }

    if( has_effect( effect_deaf ) ) {
        // Scale linearly up to 30 minutes
        volume_multiplier *= ( 30_minutes - get_effect_dur( effect_deaf ) ) / 30_minutes;
    }

    if( has_effect( effect_earphones ) ) {
        volume_multiplier *= .25;
    }

    return volume_multiplier;
}

std::string player::visible_mutations( const int visibility_cap ) const
{
    const std::string trait_str = enumerate_as_string( my_mutations.begin(), my_mutations.end(),
    [visibility_cap ]( const std::pair<trait_id, trait_data> &pr ) -> std::string {
        const auto &mut_branch = pr.first.obj();
        // Finally some use for visibility trait of mutations
        if( mut_branch.visibility > 0 && mut_branch.visibility >= visibility_cap )
        {
            return string_format( "<color_%s>%s</color>", string_from_color( mut_branch.get_display_color() ),
                                  mut_branch.name() );
        }

        return std::string();
    } );
    return trait_str;
}

std::string player::short_description() const
{
    std::stringstream ret;

    if( is_armed() ) {
        ret << _( "Wielding: " ) << weapon.tname() << ";   ";
    }
    const std::string worn_str = enumerate_as_string( worn.begin(), worn.end(),
    []( const item & it ) {
        return it.tname();
    } );
    if( !worn_str.empty() ) {
        ret << _( "Wearing: " ) << worn_str << ";";
    }
    const int visibility_cap = 0; // no cap
    const auto trait_str = visible_mutations( visibility_cap );
    if( !trait_str.empty() ) {
        ret << _( "   Traits: " ) << trait_str << ";";
    }
    return ret.str();
}

int player::print_info( const catacurses::window &w, int vStart, int, int column ) const
{
    mvwprintw( w, vStart++, column, _( "You (%s)" ), name.c_str() );
    return vStart;
}

bool player::is_visible_in_range( const Creature &critter, const int range ) const
{
    return sees( critter ) && rl_dist( pos(), critter.pos() ) <= range;
}

std::vector<Creature *> player::get_visible_creatures( const int range ) const
{
    return g->get_creatures_if( [this, range]( const Creature &critter ) -> bool {
        return this != &critter && pos() != critter.pos() && // @todo: get rid of fake npcs (pos() check)
          rl_dist( pos(), critter.pos() ) <= range && sees( critter );
    } );
}

std::vector<Creature *> player::get_targetable_creatures( const int range ) const
{
    return g->get_creatures_if( [this, range]( const Creature &critter ) -> bool {
        return this != &critter && pos() != critter.pos() && // @todo: get rid of fake npcs (pos() check)
          rl_dist( pos(), critter.pos() ) <= range &&
          ( sees( critter ) || sees_with_infrared( critter ) );
    } );
}

std::vector<Creature *> player::get_hostile_creatures( int range ) const
{
    return g->get_creatures_if( [this, range] ( const Creature &critter ) -> bool {
        float dist_to_creature;
        // Fixes circular distance range for ranged attacks
        if( !trigdist ) {
            dist_to_creature = rl_dist( pos(), critter.pos() );
        } else {
            dist_to_creature = round( trig_dist( pos(), critter.pos() ) );
        }
        return this != &critter && pos() != critter.pos() && // @todo: get rid of fake npcs (pos() check)
            dist_to_creature <= range && critter.attitude_to( *this ) == A_HOSTILE
            && sees( critter );
    } );
}

void player::place_corpse()
{
    //If the character/NPC is on a distant mission, don't drop their their gear when they die since they still have a local pos
    if( !death_drops ){
        return;
    }
    std::vector<item *> tmp = inv_dump();
    item body = item::make_corpse( mtype_id::NULL_ID(), calendar::turn, name );
    for( auto itm : tmp ) {
        g->m.add_item_or_charges( pos(), *itm );
    }
    for( auto & bio : *my_bionics ) {
        if( item::type_is_defined( bio.id.str() ) ) {
            body.put_in( item( bio.id.str(), calendar::turn ) );
        }
    }

    // Restore amount of installed pseudo-modules of Power Storage Units
    std::pair<int, int> storage_modules = amount_of_storage_bionics();
    for (int i = 0; i < storage_modules.first; ++i) {
        body.emplace_back( "bio_power_storage" );
    }
    for (int i = 0; i < storage_modules.second; ++i) {
        body.emplace_back( "bio_power_storage_mkII" );
    }
    g->m.add_item_or_charges( pos(), body );
}

void player::place_corpse( const tripoint &om_target )
{
    tinymap bay;
    bay.load( om_target.x * 2, om_target.y * 2, om_target.z, false );
    int finX = rng( 1, SEEX * 2 - 2 );
    int finY = rng( 1, SEEX * 2 - 2 );
    if( bay.furn( finX, finY) != furn_str_id( "f_null" ) ){
        for (int x = 0; x < SEEX * 2 - 1; x++){
            for (int y = 0; y < SEEY * 2 - 1; y++){
                if ( bay.furn(x,y) == furn_str_id( "f_null" ) ){
                    finX = x;
                    finY = y;
                }
            }
        }
    }

    std::vector<item *> tmp = inv_dump();
    item body = item::make_corpse( mtype_id::NULL_ID(), calendar::turn, name );
    for( auto itm : tmp ) {
        bay.add_item_or_charges( finX, finY, *itm );
    }
    for( auto & bio : *my_bionics ) {
        if( item::type_is_defined( bio.id.str() ) ) {
            body.put_in( item( bio.id.str(), calendar::turn ) );
        }
    }

    // Restore amount of installed pseudo-modules of Power Storage Units
    std::pair<int, int> storage_modules = amount_of_storage_bionics();
    for (int i = 0; i < storage_modules.first; ++i) {
        body.emplace_back( "bio_power_storage" );
    }
    for (int i = 0; i < storage_modules.second; ++i) {
        body.emplace_back( "bio_power_storage_mkII" );
    }
    bay.add_item_or_charges( finX, finY, body );
}

bool player::sees_with_infrared( const Creature &critter ) const
{
    const monster *m = dynamic_cast< const monster * >( &critter );
    // electroreceptors grants vision of robots and electric monsters through walls
    if( m != nullptr && has_trait( trait_ELECTRORECEPTORS ) && ( m->type->in_species( ROBOT ) || critter.has_flag( MF_ELECTRIC ) ) ) {
        return true;
    }

    if( !vision_mode_cache[IR_VISION] || !critter.is_warm() ) {
        return false;
    }

    if( is_player() || critter.is_player() ) {
        // Players should not use map::sees
        // Likewise, players should not be "looked at" with map::sees, not to break symmetry
        return g->m.pl_line_of_sight( critter.pos(), sight_range( DAYLIGHT_LEVEL ) );
    }

    return g->m.sees( pos(), critter.pos(), sight_range( DAYLIGHT_LEVEL ) );
}

std::vector<std::string> player::get_overlay_ids() const
{
    std::vector<std::string> rval;
    std::multimap<int, std::string> mutation_sorting;
    int order;
    std::string overlay_id;

    // first get effects
    for( const auto &eff_pr : *effects ) {
        rval.push_back( "effect_" + eff_pr.first.str() );
    }

    // then get mutations
    for( const auto &mut : my_mutations ) {
        overlay_id = ( mut.second.powered ? "active_" : "" ) + mut.first.str();
        order = get_overlay_order_of_mutation( overlay_id );
        mutation_sorting.insert( std::pair<int, std::string>( order, overlay_id ) );
    }

    // then get bionics
    for( const bionic &bio : *my_bionics ) {
        overlay_id = ( bio.powered ? "active_" : "" ) + bio.id.str();
        order = get_overlay_order_of_mutation( overlay_id );
        mutation_sorting.insert( std::pair<int, std::string>( order, overlay_id ) );
    }

    for( auto &mutorder : mutation_sorting ) {
        rval.push_back( "mutation_" + mutorder.second );
    }

    // next clothing
    // TODO: worry about correct order of clothing overlays
    for( const item &worn_item : worn ) {
        rval.push_back( "worn_" + worn_item.typeId() );
    }

    // last weapon
    // TODO: might there be clothing that covers the weapon?
    if( is_armed() ) {
        rval.push_back( "wielded_" + weapon.typeId() );
    }
    return rval;
}

void player::spores()
{
    fungal_effects fe( *g, g->m );
    //~spore-release sound
    sounds::sound( pos(), 10, sounds::sound_t::combat, _("Pouf!"));
    for( const tripoint &sporep : g->m.points_in_radius( pos(), 1 ) ) {
        if (sporep == pos()) {
            continue;
        }
        fe.fungalize( sporep, this, 0.25 );
    }
}

void player::blossoms()
{
    // Player blossoms are shorter-ranged, but you can fire much more frequently if you like.
    sounds::sound( pos(), 10, sounds::sound_t::combat, _("Pouf!"));
    for( const tripoint &tmp : g->m.points_in_radius( pos(), 2 ) ) {
        g->m.add_field( tmp, fd_fungal_haze, rng( 1, 2 ) );
    }
}

float player::power_rating() const
{
    int dmg = std::max( { weapon.damage_melee( DT_BASH ),
                          weapon.damage_melee( DT_CUT ),
                          weapon.damage_melee( DT_STAB ) } );

    int ret = 2;
    // Small guns can be easily hidden from view
    if( weapon.volume() <= 250_ml ) {
        ret = 2;
    } else if( weapon.is_gun() ) {
        ret = 4;
    } else if( dmg > 12 ) {
        ret = 3; // Melee weapon or weapon-y tool
    }
    if( has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK ) ) {
        ret += 1;
    }
    if( is_wearing_power_armor( nullptr ) ) {
        ret = 5; // No mercy!
    }
    return ret;
}

float player::speed_rating() const
{
    float ret = get_speed() / 100.0f;
    ret *= 100.0f / run_cost( 100, false );
    // Adjustment for player being able to run, but not doing so at the moment
    if( move_mode != "run" ) {
        ret *= 1.0f + (static_cast<float>( stamina ) / static_cast<float>( get_stamina_max() ));
    }
    return ret;
}

std::vector<const item *> player::all_items_with_flag( const std::string &flag ) const
{
    return items_with( [&flag]( const item & it ) {
        return it.has_flag( flag );
    } );
}

bool player::has_item_with_flag( const std::string &flag ) const
{
    return has_item_with( [&flag]( const item & it ) {
        return it.has_flag( flag );
    } );
}

void player::on_mutation_gain( const trait_id &mid )
{
    morale->on_mutation_gain( mid );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( mid.str() );
    lua_callback( "on_player_mutation_gain", lua_callback_args_info );
}

void player::on_mutation_loss( const trait_id &mid )
{
    morale->on_mutation_loss( mid );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( mid.str() );
    lua_callback( "on_player_mutation_loss", lua_callback_args_info );
}

void player::on_stat_change( const std::string &stat, int value )
{
    morale->on_stat_change( stat, value );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( stat );
    lua_callback_args_info.emplace_back( value );
    lua_callback( "on_player_stat_change", lua_callback_args_info );
}

void player::on_item_wear( const item &it )
{
    morale->on_item_wear( it );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( it );
    lua_callback( "on_player_item_wear", lua_callback_args_info );
}

void player::on_item_takeoff( const item &it )
{
    morale->on_item_takeoff( it );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( it );
    lua_callback( "on_player_item_takeoff", lua_callback_args_info );
}

void player::on_worn_item_washed( const item &it )
{
    if( is_worn( it ) ) {
        morale->on_worn_item_washed( it );
    }
}

void player::on_effect_int_change( const efftype_id &eid, int intensity, body_part bp )
{
    // Adrenaline can reduce perceived pain (or increase it when you enter comedown).
    // See @ref get_perceived_pain()
    if( eid == effect_adrenaline ) {
        // Note that calling this does no harm if it wasn't changed.
        on_stat_change( "perceived_pain", get_perceived_pain() );
    }

    morale->on_effect_int_change( eid, intensity, bp );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( eid.str() );
    lua_callback_args_info.emplace_back( intensity );
    lua_callback_args_info.emplace_back( bp );
    lua_callback( "on_player_effect_int_change", lua_callback_args_info );
}

void player::on_mission_assignment( mission &new_mission )
{
    active_missions.push_back( &new_mission );
    set_active_mission( new_mission );
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( new_mission.get_id() );
    lua_callback( "on_player_mission_assignment", lua_callback_args_info );
}

void player::on_mission_finished( mission &cur_mission )
{
    if( cur_mission.has_failed() ) {
        failed_missions.push_back( &cur_mission );
        add_msg_if_player( m_bad, _( "Mission \"%s\" is failed." ), cur_mission.name().c_str() );
    } else {
        completed_missions.push_back( &cur_mission );
        add_msg_if_player( m_good, _( "Mission \"%s\" is successfully completed." ), cur_mission.name().c_str() );
    }
    const auto iter = std::find( active_missions.begin(), active_missions.end(), &cur_mission );
    if( iter == active_missions.end() ) {
        debugmsg( "completed mission %d was not in the active_missions list", cur_mission.get_id() );
    } else {
        active_missions.erase( iter );
    }
    if( &cur_mission == active_mission ) {
        if( active_missions.empty() ) {
            active_mission = nullptr;
        } else {
            active_mission = active_missions.front();
        }
    }
    CallbackArgumentContainer lua_callback_args_info;
    lua_callback_args_info.emplace_back( getID() );
    lua_callback_args_info.emplace_back( cur_mission.get_id() );
    lua_callback( "on_player_mission_finished", lua_callback_args_info );
}

const targeting_data &player::get_targeting_data() {
    if( tdata.get() == nullptr ) {
        debugmsg( "Tried to get targeting data before setting it" );
        tdata.reset( new targeting_data() );
        tdata->relevant = nullptr;
        g->u.cancel_activity();
    }

    return *tdata;
}

void player::set_targeting_data( const targeting_data &td ) {
    tdata.reset( new targeting_data( td ) );
}

void player::set_active_mission( mission &cur_mission )
{
    const auto iter = std::find( active_missions.begin(), active_missions.end(), &cur_mission );
    if( iter == active_missions.end() ) {
        debugmsg( "new active mission %d is not in the active_missions list", cur_mission.get_id() );
    } else {
        active_mission = &cur_mission;
    }
}

mission *player::get_active_mission() const
{
    return active_mission;
}

tripoint player::get_active_mission_target() const
{
    if( active_mission == nullptr ) {
        return overmap::invalid_tripoint;
    }
    return active_mission->get_target();
}

std::vector<mission*> player::get_active_missions() const
{
    return active_missions;
}

std::vector<mission*> player::get_completed_missions() const
{
    return completed_missions;
}

std::vector<mission*> player::get_failed_missions() const
{
    return failed_missions;
}

bool player::query_yn( const std::string &mes ) const
{
    return ::query_yn( mes );
}

const pathfinding_settings &player::get_pathfinding_settings() const
{
    return *path_settings;
}

std::set<tripoint> player::get_path_avoid() const
{
    std::set<tripoint> ret;
    for( npc &guy : g->all_npcs() ) {
        if( sees( guy ) ) {
            ret.insert( guy.pos() );
        }
    }

    // @todo: Add known traps in a way that doesn't destroy performance

    return ret;
}

bool player::is_rad_immune() const
{
    bool has_helmet = false;
    return ( is_wearing_power_armor( &has_helmet ) && has_helmet ) || worn_with_flag( "RAD_PROOF" );
}

void player::do_skill_rust()
{
    const int rate = rust_rate();
    if( rate <= 0 ) {
        return;
    }
    for( auto &pair : *_skills ) {
        if( rate <= rng( 0, 1000 ) ) {
            continue;
        }

        const Skill &aSkill = *pair.first;
        SkillLevel &skill_level_obj = pair.second;

        if( aSkill.is_combat_skill() &&
            ( ( has_trait( trait_PRED2 ) && one_in( 4 ) ) ||
              ( has_trait( trait_PRED3 ) && one_in( 2 ) ) ||
              ( has_trait( trait_PRED4 ) && x_in_y( 2, 3 ) ) ) ) {
            // Their brain is optimized to remember this
            if( one_in( 15600 ) ) {
                // They've already passed the roll to avoid rust at
                // this point, but print a message about it now and
                // then.
                //
                // 13 combat skills, 600 turns/hr, 7800 tests/hr.
                // This means PRED2/PRED3/PRED4 think of hunting on
                // average every 8/4/3 hours, enough for immersion
                // without becoming an annoyance.
                //
                add_msg_if_player( _( "Your heart races as you recall your most recent hunt." ) );
                stim++;
            }
            continue;
        }

        const bool charged_bio_mem = power_level > 25 && has_active_bionic( bio_memory );
        const int oldSkillLevel = skill_level_obj.level();
        if( skill_level_obj.rust( charged_bio_mem ) ) {
            add_msg_if_player( m_warning, _( "Your knowledge of %s begins to fade, but your memory banks retain it!" ), aSkill.name() );
            charge_power( -25 );
        }
        const int newSkill = skill_level_obj.level();
        if( newSkill < oldSkillLevel ) {
            add_msg_if_player( m_bad, _( "Your skill in %s has reduced to %d!" ), aSkill.name(), newSkill );
        }
    }
}
