#include "npc.h"

#include "ammo.h"
#include "auto_pickup.h"
#include "coordinate_conversions.h"
#include "game.h"
#include "item_group.h"
#include "itype.h"
#include "iuse_actor.h"
#include "json.h"
#include "map.h"
#include "mapdata.h"
#include "messages.h"
#include "mission.h"
#include "monfaction.h"
#include "morale_types.h"
#include "mtype.h"
#include "mutation.h"
#include "npc_class.h"
#include "output.h"
#include "overmap.h"
#include "overmapbuffer.h"
#include "skill.h"
#include "sounds.h"
#include "string_formatter.h"
#include "trait_group.h"
#include "veh_type.h"
#include "vehicle.h"
#include "vpart_position.h"
#include "vpart_reference.h"

const skill_id skill_mechanics( "mechanics" );
const skill_id skill_electronics( "electronics" );
const skill_id skill_speech( "speech" );
const skill_id skill_barter( "barter" );
const skill_id skill_gun( "gun" );
const skill_id skill_pistol( "pistol" );
const skill_id skill_throw( "throw" );
const skill_id skill_rifle( "rifle" );
const skill_id skill_dodge( "dodge" );
const skill_id skill_melee( "melee" );
const skill_id skill_unarmed( "unarmed" );
const skill_id skill_computer( "computer" );
const skill_id skill_firstaid( "firstaid" );
const skill_id skill_bashing( "bashing" );
const skill_id skill_stabbing( "stabbing" );
const skill_id skill_archery( "archery" );
const skill_id skill_cooking( "cooking" );
const skill_id skill_tailor( "tailor" );
const skill_id skill_shotgun( "shotgun" );
const skill_id skill_smg( "smg" );
const skill_id skill_launcher( "launcher" );
const skill_id skill_cutting( "cutting" );

const efftype_id effect_drunk( "drunk" );
const efftype_id effect_high( "high" );
const efftype_id effect_pkill1( "pkill1" );
const efftype_id effect_pkill2( "pkill2" );
const efftype_id effect_pkill3( "pkill3" );
const efftype_id effect_pkill_l( "pkill_l" );
const efftype_id effect_infection( "infection" );
const efftype_id effect_bouldering( "bouldering" );

static const trait_id trait_BEAUTIFUL2( "BEAUTIFUL2" );
static const trait_id trait_BEAUTIFUL3( "BEAUTIFUL3" );
static const trait_id trait_BEAUTIFUL( "BEAUTIFUL" );
static const trait_id trait_CANNIBAL( "CANNIBAL" );
static const trait_id trait_DEFORMED2( "DEFORMED2" );
static const trait_id trait_DEFORMED3( "DEFORMED3" );
static const trait_id trait_DEFORMED( "DEFORMED" );
static const trait_id trait_PRETTY( "PRETTY" );
static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" );
static const trait_id trait_SAPIOVORE( "SAPIOVORE" );
static const trait_id trait_TERRIFYING( "TERRIFYING" );
static const trait_id trait_UGLY( "UGLY" );

void starting_clothes( npc &who, const npc_class_id &type, bool male );
void starting_inv( npc &who, const npc_class_id &type );

npc::npc()
    : player()
    , restock( calendar::before_time_starts )
    , companion_mission_time( calendar::before_time_starts )
    , companion_mission_time_ret( calendar::before_time_starts )
    , last_updated( calendar::turn )
{
    submap_coords = point( 0, 0 );
    position.x = -1;
    position.y = -1;
    position.z = 500;
    last_player_seen_pos = cata::nullopt;
    last_seen_player_turn = 999;
    wanted_item_pos = no_goal_point;
    guard_pos = no_goal_point;
    goal = no_goal_point;
    fetching_item = false;
    has_new_items = true;
    worst_item_value = 0;
    str_max = 0;
    dex_max = 0;
    int_max = 0;
    per_max = 0;
    my_fac = nullptr;
    miss_id = mission_type_id::NULL_ID();
    marked_for_death = false;
    death_drops = true;
    dead = false;
    hit_by_player = false;
    moves = 100;
    mission = NPC_MISSION_NULL;
    myclass = npc_class_id::NULL_ID();
    patience = 0;
    attitude = NPCATT_NULL;

    *path_settings = pathfinding_settings( 0, 1000, 1000, 10, true, true, true, false );
}

standard_npc::standard_npc( const std::string &name, const std::vector<itype_id> &clothing,
                            int sk_lvl, int s_str, int s_dex, int s_int, int s_per )
{
    this->name = name;
    position = { 0, 0, 0 };

    str_cur = std::max( s_str, 0 );
    str_max = std::max( s_str, 0 );
    dex_cur = std::max( s_dex, 0 );
    dex_max = std::max( s_dex, 0 );
    per_cur = std::max( s_per, 0 );
    per_max = std::max( s_per, 0 );
    int_cur = std::max( s_int, 0 );
    int_max = std::max( s_int, 0 );

    recalc_hp();
    for( int i = 0; i < num_hp_parts; i++ ) {
        hp_cur[i] = hp_max[i];
    }
    for( auto &e : Skill::skills ) {
        set_skill_level( e.ident(), std::max( sk_lvl, 0 ) );
    }

    for( const auto &e : clothing ) {
        wear_item( item( e ) );
    }

    for( item &e : worn ) {
        if( e.has_flag( "VARSIZE" ) ) {
            e.item_tags.insert( "FIT" );
        }
    }
}

npc::npc( npc && ) = default;
npc &npc::operator=( npc && ) = default;

static std::map<string_id<npc_template>, npc_template> npc_templates;

void npc_template::load( JsonObject &jsobj )
{
    npc guy;
    guy.idz = jsobj.get_string( "id" );
    guy.name.clear();
    if( jsobj.has_string( "name_unique" ) ) {
        guy.name = static_cast<std::string>( _( jsobj.get_string( "name_unique" ).c_str() ) );
    }
    if( jsobj.has_string( "name_suffix" ) ) {
        guy.name += ", " + static_cast<std::string>( _( jsobj.get_string( "name_suffix" ).c_str() ) );
    }
    if( jsobj.has_string( "gender" ) ) {
        if( jsobj.get_string( "gender" ) == "male" ) {
            guy.male = true;
        } else {
            guy.male = false;
        }
    }
    if( jsobj.has_string( "faction" ) ) {
        guy.fac_id = faction_id( jsobj.get_string( "faction" ) );
    }

    if( jsobj.has_int( "class" ) ) {
        guy.myclass = npc_class::from_legacy_int( jsobj.get_int( "class" ) );
    } else if( jsobj.has_string( "class" ) ) {
        guy.myclass = npc_class_id( jsobj.get_string( "class" ) );
    }

    guy.set_attitude( npc_attitude( jsobj.get_int( "attitude" ) ) );
    guy.mission = npc_mission( jsobj.get_int( "mission" ) );
    guy.chatbin.first_topic = jsobj.get_string( "chat" );
    if( jsobj.has_string( "mission_offered" ) ) {
        guy.miss_id = mission_type_id( jsobj.get_string( "mission_offered" ) );
    } else {
        guy.miss_id = mission_type_id::NULL_ID();
    }
    npc_templates[string_id<npc_template>( guy.idz )].guy = std::move( guy );
}

void npc_template::reset()
{
    npc_templates.clear();
}

void npc_template::check_consistency()
{
    for( const auto &e : npc_templates ) {
        const auto &guy = e.second.guy;
        if( !guy.myclass.is_valid() ) {
            debugmsg( "Invalid NPC class %s", guy.myclass.c_str() );
        }
    }
}

template<>
bool string_id<npc_template>::is_valid() const
{
    return npc_templates.count( *this ) > 0;
}

template<>
const npc_template &string_id<npc_template>::obj() const
{
    const auto found = npc_templates.find( *this );
    if( found == npc_templates.end() ) {
        debugmsg( "Tried to get invalid npc: %s", c_str() );
        static const npc_template dummy;
        return dummy;
    }
    return found->second;
}

void npc::load_npc_template( const string_id<npc_template> &ident )
{
    auto found = npc_templates.find( ident );
    if( found == npc_templates.end() ) {
        debugmsg( "Tried to get invalid npc: %s", ident.c_str() );
        return;
    }
    const npc &tguy = found->second.guy;

    idz = tguy.idz;
    myclass = npc_class_id( tguy.myclass );
    randomize( myclass );
    std::string tmpname = tguy.name;
    if( tmpname[0] == ',' ) {
        name = name + tguy.name;
    } else {
        name = tguy.name;
        //Assume if the name is unique, the gender might also be.
        male = tguy.male;
    }
    fac_id = tguy.fac_id;
    set_fac( fac_id );
    attitude = tguy.attitude;
    mission = tguy.mission;
    chatbin.first_topic = tguy.chatbin.first_topic;
    if( !tguy.miss_id.is_null() ) {
        add_new_mission( mission::reserve_new( tguy.miss_id, getID() ) );
    }
}

npc::~npc() = default;

std::string npc::save_info() const
{
    return ::serialize( *this );
}

void npc::load_info( std::string data )
{
    std::stringstream dump;
    dump << data;

    JsonIn jsin( dump );
    try {
        deserialize( jsin );
    } catch( const JsonError &jsonerr ) {
        debugmsg( "Bad npc json\n%s", jsonerr.c_str() );
    }
    if( !fac_id.str().empty() ) {
        set_fac( fac_id );
    }
}

void npc::randomize( const npc_class_id &type )
{
    if( getID() <= 0 ) {
        setID( g->assign_npc_id() );
    }

    weapon   = item( "null", 0 );
    inv.clear();
    personality.aggression = rng( -10, 10 );
    personality.bravery =    rng( -3, 10 );
    personality.collector =  rng( -1, 10 );
    personality.altruism =   rng( -10, 10 );
    moves = 100;
    mission = NPC_MISSION_NULL;
    male = one_in( 2 );
    pick_name();

    if( !type.is_valid() ) {
        debugmsg( "Invalid NPC class %s", type.c_str() );
        myclass = npc_class_id::NULL_ID();
    } else if( type.is_null() && !one_in( 5 ) ) {
        myclass = npc_class::random_common();
    } else {
        myclass = type;
    }

    const auto &the_class = myclass.obj();
    str_max = the_class.roll_strength();
    dex_max = the_class.roll_dexterity();
    int_max = the_class.roll_intelligence();
    per_max = the_class.roll_perception();

    if( myclass->get_shopkeeper_items() != "EMPTY_GROUP" ) {
        restock = calendar::turn + 3_days;
        cash += 100000;
    }

    for( auto &skill : Skill::skills ) {
        int level = myclass->roll_skill( skill.ident() );

        set_skill_level( skill.ident(), level );
    }

    if( type.is_null() ) { // Untyped; no particular specialization
    } else if( type == NC_EVAC_SHOPKEEP ) {
        personality.collector += rng( 1, 5 );

    } else if( type == NC_BARTENDER ) {
        personality.collector += rng( 1, 5 );

    } else if( type == NC_JUNK_SHOPKEEP ) {
        personality.collector += rng( 1, 5 );

    } else if( type == NC_ARSONIST ) {
        personality.aggression += rng( 0, 1 );
        personality.collector += rng( 0, 2 );

    } else if( type == NC_SOLDIER ) {
        personality.aggression += rng( 1, 3 );
        personality.bravery += rng( 0, 5 );

    } else if( type == NC_HACKER ) {
        personality.bravery -= rng( 1, 3 );
        personality.aggression -= rng( 0, 2 );

    } else if( type == NC_DOCTOR ) {
        personality.aggression -= rng( 0, 4 );
        cash += 10000 * rng( 0, 3 ) * rng( 0, 3 );

    } else if( type == NC_TRADER ) {
        personality.collector += rng( 1, 5 );
        cash += 25000 * rng( 1, 10 );

    } else if( type == NC_NINJA ) {
        personality.bravery += rng( 0, 3 );
        personality.collector -= rng( 1, 6 );
        // TODO: give ninja his styles back

    } else if( type == NC_COWBOY ) {
        personality.aggression += rng( 0, 2 );
        personality.bravery += rng( 1, 5 );

    } else if( type == NC_SCIENTIST ) {
        personality.aggression -= rng( 1, 5 );
        personality.bravery -= rng( 2, 8 );
        personality.collector += rng( 0, 2 );

    } else if( type == NC_BOUNTY_HUNTER ) {
        personality.aggression += rng( 1, 6 );
        personality.bravery += rng( 0, 5 );

    } else if( type == NC_THUG ) {
        personality.aggression += rng( 1, 6 );
        personality.bravery += rng( 0, 5 );

    } else if( type == NC_SCAVENGER ) {
        personality.aggression += rng( 1, 3 );
        personality.bravery += rng( 1, 4 );

    }
    //A universal barter boost to keep NPCs competitive with players
    //The int boost from trade wasn't active... now that it is, most
    //players will vastly outclass npcs in trade without a little help.
    mod_skill_level( skill_barter, rng( 2, 4 ) );

    recalc_hp();
    for( int i = 0; i < num_hp_parts; i++ ) {
        hp_cur[i] = hp_max[i];
    }

    starting_weapon( type );
    starting_clothes( *this, type, male );
    starting_inv( *this, type );
    has_new_items = true;

    my_mutations.clear();
    my_traits.clear();

    // Add fixed traits
    for( const auto &tid : trait_group::traits_from( myclass->traits ) ) {
        set_mutation( tid );
    }

    // Run mutation rounds
    for( const auto &mr : type->mutation_rounds ) {
        int rounds = mr.second.roll();
        for( int i = 0; i < rounds; ++i ) {
            mutate_category( mr.first );
        }
    }
}

void npc::randomize_from_faction( faction *fac )
{
    // Personality = aggression, bravery, altruism, collector
    my_fac = fac;
    fac_id = fac->id;
    randomize( npc_class_id::NULL_ID() );

    switch( fac->goal ) {
        case FACGOAL_DOMINANCE:
            personality.aggression += rng( 0, 3 );
            personality.bravery += rng( 1, 4 );
            personality.altruism -= rng( 0, 2 );
            break;
        case FACGOAL_CLEANSE:
            personality.aggression -= rng( 0, 3 );
            personality.bravery += rng( 2, 4 );
            personality.altruism += rng( 0, 3 );
            personality.collector -= rng( 0, 2 );
            break;
        case FACGOAL_SHADOW:
            personality.bravery += rng( 4, 7 );
            personality.collector -= rng( 0, 3 );
            int_max += rng( 0, 2 );
            per_max += rng( 0, 2 );
            break;
        case FACGOAL_APOCALYPSE:
            personality.aggression += rng( 2, 5 );
            personality.bravery += rng( 4, 8 );
            personality.altruism -= rng( 1, 4 );
            personality.collector -= rng( 2, 5 );
            break;
        case FACGOAL_ANARCHY:
            personality.aggression += rng( 3, 6 );
            personality.bravery += rng( 0, 4 );
            personality.altruism -= rng( 3, 8 );
            personality.collector -= rng( 3, 6 );
            int_max -= rng( 1, 3 );
            per_max -= rng( 0, 2 );
            str_max += rng( 0, 3 );
            break;
        case FACGOAL_KNOWLEDGE:
            if( one_in( 2 ) ) {
                randomize( NC_SCIENTIST );
            }
            personality.aggression -= rng( 2, 5 );
            personality.bravery -= rng( 1, 4 );
            personality.collector += rng( 2, 4 );
            int_max += rng( 2, 5 );
            str_max -= rng( 1, 4 );
            per_max -= rng( 0, 2 );
            dex_max -= rng( 0, 2 );
            break;
        case FACGOAL_NATURE:
            personality.aggression -= rng( 0, 3 );
            personality.collector -= rng( 1, 4 );
            break;
        case FACGOAL_CIVILIZATION:
            personality.aggression -= rng( 2, 4 );
            personality.altruism += rng( 1, 5 );
            personality.collector += rng( 1, 5 );
            break;
        default:
            //Suppress warnings
            break;
    }
    // Jobs
    if( fac->has_job( FACJOB_EXTORTION ) ) {
        personality.aggression += rng( 0, 3 );
        personality.bravery -= rng( 0, 2 );
        personality.altruism -= rng( 2, 6 );
    }
    if( fac->has_job( FACJOB_INFORMATION ) ) {
        int_max += rng( 0, 4 );
        per_max += rng( 0, 4 );
        personality.aggression -= rng( 0, 4 );
        personality.collector += rng( 1, 3 );
    }
    if( fac->has_job( FACJOB_TRADE ) || fac->has_job( FACJOB_CARAVANS ) ) {
        if( !one_in( 3 ) ) {
            randomize( NC_TRADER );
        }
        personality.aggression -= rng( 1, 5 );
        personality.collector += rng( 1, 4 );
        personality.altruism -= rng( 0, 3 );
    }
    if( fac->has_job( FACJOB_SCAVENGE ) ) {
        personality.collector += rng( 4, 8 );
    }
    if( fac->has_job( FACJOB_MERCENARIES ) ) {
        if( !one_in( 3 ) ) {
            switch( rng( 1, 3 ) ) {
                case 1:
                    randomize( NC_NINJA );
                    break;
                case 2:
                    randomize( NC_COWBOY );
                    break;
                case 3:
                    randomize( NC_BOUNTY_HUNTER );
                    break;
                default:
                    randomize( NC_COWBOY );
                    break;
            }
        }
        personality.aggression += rng( 0, 2 );
        personality.bravery += rng( 2, 4 );
        personality.altruism -= rng( 2, 4 );
        str_max += rng( 0, 2 );
        per_max += rng( 0, 2 );
        dex_max += rng( 0, 2 );
    }
    if( fac->has_job( FACJOB_ASSASSINS ) ) {
        personality.bravery -= rng( 0, 2 );
        personality.altruism -= rng( 1, 3 );
        per_max += rng( 1, 3 );
        dex_max += rng( 0, 2 );
    }
    if( fac->has_job( FACJOB_RAIDERS ) ) {
        if( one_in( 3 ) ) {
            randomize( NC_COWBOY );
        }
        personality.aggression += rng( 3, 5 );
        personality.bravery += rng( 0, 2 );
        personality.altruism -= rng( 3, 6 );
        str_max += rng( 0, 3 );
        int_max -= rng( 0, 2 );
    }
    if( fac->has_job( FACJOB_THIEVES ) ) {
        if( one_in( 3 ) ) {
            randomize( NC_NINJA );
        }
        personality.aggression -= rng( 2, 5 );
        personality.bravery -= rng( 1, 3 );
        personality.altruism -= rng( 1, 4 );
        str_max -= rng( 0, 2 );
        per_max += rng( 1, 4 );
        dex_max += rng( 1, 3 );
    }
    if( fac->has_job( FACJOB_DOCTORS ) ) {
        if( !one_in( 4 ) ) {
            randomize( NC_DOCTOR );
        }
        personality.aggression -= rng( 3, 6 );
        personality.bravery += rng( 0, 4 );
        personality.altruism += rng( 0, 4 );
        int_max += rng( 2, 4 );
        per_max += rng( 0, 2 );
        mod_skill_level( skill_firstaid, int( rng( 1, 5 ) ) );
    }
    if( fac->has_job( FACJOB_FARMERS ) ) {
        personality.aggression -= rng( 2, 4 );
        personality.altruism += rng( 0, 3 );
        str_max += rng( 1, 3 );
    }
    if( fac->has_job( FACJOB_DRUGS ) ) {
        personality.aggression -= rng( 0, 2 );
        personality.bravery -= rng( 0, 3 );
        personality.altruism -= rng( 1, 4 );
    }
    if( fac->has_job( FACJOB_MANUFACTURE ) ) {
        personality.aggression -= rng( 0, 2 );
        personality.bravery -= rng( 0, 2 );
        switch( rng( 1, 4 ) ) {
            case 1:
                mod_skill_level( skill_mechanics,   dice( 2, 4 ) );
                break;
            case 2:
                mod_skill_level( skill_electronics, dice( 2, 4 ) );
                break;
            case 3:
                mod_skill_level( skill_cooking,     dice( 2, 4 ) );
                break;
            case 4:
                mod_skill_level( skill_tailor,      dice( 2, 4 ) );
                break;
            default:
                mod_skill_level( skill_cooking,     dice( 2, 4 ) );
                break;
        }
    }

    if( fac->has_value( FACVAL_CHARITABLE ) ) {
        personality.aggression -= rng( 2, 5 );
        personality.bravery += rng( 0, 4 );
        personality.altruism += rng( 2, 5 );
    }
    if( fac->has_value( FACVAL_LONERS ) ) {
        personality.aggression -= rng( 1, 3 );
        personality.altruism -= rng( 1, 4 );
    }
    if( fac->has_value( FACVAL_EXPLORATION ) ) {
        per_max += rng( 0, 4 );
        personality.aggression -= rng( 0, 2 );
    }
    if( fac->has_value( FACVAL_ARTIFACTS ) ) {
        personality.collector += rng( 2, 5 );
        personality.altruism -= rng( 0, 2 );
    }
    if( fac->has_value( FACVAL_BIONICS ) ) {
        str_max += rng( 0, 2 );
        dex_max += rng( 0, 2 );
        per_max += rng( 0, 2 );
        int_max += rng( 0, 4 );
        if( one_in( 3 ) ) {
            mod_skill_level( skill_mechanics, dice( 2, 3 ) );
            mod_skill_level( skill_electronics, dice( 2, 3 ) );
            mod_skill_level( skill_firstaid, dice( 2, 3 ) );
        }
    }
    if( fac->has_value( FACVAL_BOOKS ) ) {
        str_max -= rng( 0, 2 );
        per_max -= rng( 0, 3 );
        int_max += rng( 0, 4 );
        personality.aggression -= rng( 1, 4 );
        personality.bravery -= rng( 0, 3 );
        personality.collector += rng( 0, 3 );
    }
    if( fac->has_value( FACVAL_TRAINING ) ) {
        str_max += rng( 0, 3 );
        dex_max += rng( 0, 3 );
        per_max += rng( 0, 2 );
        int_max += rng( 0, 2 );
        for( auto const &skill : Skill::skills ) {
            if( one_in( 3 ) ) {
                mod_skill_level( skill.ident(), rng( 2, 4 ) );
            }
        }
    }
    if( fac->has_value( FACVAL_ROBOTS ) ) {
        int_max += rng( 0, 3 );
        personality.aggression -= rng( 0, 3 );
        personality.collector += rng( 0, 3 );
    }
    if( fac->has_value( FACVAL_TREACHERY ) ) {
        personality.aggression += rng( 0, 3 );
        personality.altruism -= rng( 2, 5 );
    }
    if( fac->has_value( FACVAL_STRAIGHTEDGE ) ) {
        personality.collector -= rng( 0, 2 );
        str_max += rng( 0, 1 );
        per_max += rng( 0, 2 );
        int_max += rng( 0, 3 );
    }
    if( fac->has_value( FACVAL_LAWFUL ) ) {
        personality.aggression -= rng( 3, 7 );
        personality.altruism += rng( 1, 5 );
        int_max += rng( 0, 2 );
    }
    if( fac->has_value( FACVAL_CRUELTY ) ) {
        personality.aggression += rng( 3, 6 );
        personality.bravery -= rng( 1, 4 );
        personality.altruism -= rng( 2, 5 );
    }
}

void npc::set_fac( const string_id<faction> &id )
{
    my_fac = g->faction_manager_ptr->get( id );
    fac_id = my_fac->id;
}

// item id from group "<class-name>_<what>" or from fallback group
// may still be a null item!
item random_item_from( const npc_class_id &type, const std::string &what,
                       const std::string &fallback )
{
    auto result = item_group::item_from( type.str() + "_" + what );
    if( result.is_null() ) {
        result = item_group::item_from( fallback );
    }
    return result;
}

// item id from "<class-name>_<what>" or from "npc_<what>"
item random_item_from( const npc_class_id &type, const std::string &what )
{
    return random_item_from( type, what, "npc_" + what );
}

// item id from "<class-name>_<what>_<gender>" or from "npc_<what>_<gender>"
item get_clothing_item( const npc_class_id &type, const std::string &what, bool male )
{
    if( male ) {
        return random_item_from( type, what + "_male", "npc_" + what + "_male" );
    } else {
        return random_item_from( type, what + "_female", "npc_" + what + "_female" );
    }
}

void starting_clothes( npc &who, const npc_class_id &type, bool male )
{
    std::vector<item> ret;

    if( item_group::group_is_defined( type->worn_override ) ) {
        ret = item_group::items_from( type->worn_override );
    } else {
        ret.push_back( get_clothing_item( type, "pants", male ) );
        ret.push_back( get_clothing_item( type, "shirt", male ) );
        ret.push_back( random_item_from( type, "gloves" ) );
        ret.push_back( random_item_from( type, "coat" ) );
        ret.push_back( random_item_from( type, "shoes" ) );
        ret.push_back( random_item_from( type, "masks" ) );
        // Why is the alternative group not named "npc_glasses" but "npc_eyes"?
        ret.push_back( random_item_from( type, "glasses", "npc_eyes" ) );
        ret.push_back( random_item_from( type, "hat" ) );
        ret.push_back( random_item_from( type, "extra" ) );
    }

    who.worn.clear();
    for( item &it : ret ) {
        if( it.has_flag( "VARSIZE" ) ) {
            it.item_tags.insert( "FIT" );
        }

        if( who.can_wear( it ).success() ) {
            who.worn.push_back( it );
        }
    }
}

void starting_inv( npc &me, const npc_class_id &type )
{
    std::list<item> res;
    me.inv.clear();
    if( item_group::group_is_defined( type->carry_override ) ) {
        me.inv += item_group::items_from( type->carry_override );
        return;
    }

    res.emplace_back( "lighter" );
    // If wielding a gun, get some additional ammo for it
    if( me.weapon.is_gun() ) {
        item ammo( me.weapon.ammo_type()->default_ammotype() );
        ammo = ammo.in_its_container();
        if( ammo.made_of( LIQUID ) ) {
            item container( "bottle_plastic" );
            container.put_in( ammo );
            ammo = container;
        }

        // @todo: Move to npc_class
        // NC_COWBOY and NC_BOUNTY_HUNTER get 5-15 whilst all others get 3-6
        long qty = 1 + ( type == NC_COWBOY ||
                         type == NC_BOUNTY_HUNTER );
        qty = rng( qty, qty * 2 );

        while( qty-- != 0 && me.can_pickVolume( ammo ) ) {
            // @todo: give NPC a default magazine instead
            res.push_back( ammo );
        }
    }

    if( type == NC_ARSONIST ) {
        res.emplace_back( "molotov" );
    }

    long qty = ( type == NC_EVAC_SHOPKEEP ||
                 type == NC_TRADER ) ? 5 : 2;
    qty = rng( qty, qty * 3 );

    while( qty-- != 0 ) {
        item tmp = random_item_from( type, "misc" ).in_its_container();
        if( !tmp.is_null() ) {
            if( !one_in( 3 ) && tmp.has_flag( "VARSIZE" ) ) {
                tmp.item_tags.insert( "FIT" );
            }
            if( me.can_pickVolume( tmp ) ) {
                res.push_back( tmp );
            }
        }
    }

    res.erase( std::remove_if( res.begin(), res.end(), [&]( const item & e ) {
        return e.has_flag( "TRADER_AVOID" );
    } ), res.end() );

    me.inv += res;
}

void npc::spawn_at_sm( int x, int y, int z )
{
    spawn_at_precise( point( x, y ), tripoint( rng( 0, SEEX - 1 ), rng( 0, SEEY - 1 ), z ) );
}

void npc::spawn_at_precise( const point &submap_offset, const tripoint &square )
{
    submap_coords = submap_offset;
    submap_coords.x += square.x / SEEX;
    submap_coords.y += square.y / SEEY;
    position.x = square.x % SEEX;
    position.y = square.y % SEEY;
    position.z = square.z;
}

tripoint npc::global_square_location() const
{
    return tripoint( submap_coords.x * SEEX + posx() % SEEX, submap_coords.y * SEEY + posy() % SEEY,
                     position.z );
}

void npc::place_on_map()
{
    // The global absolute position (in map squares) of the npc is *always*
    // "submap_coords.x * SEEX + posx() % SEEX" (analog for y).
    // The main map assumes that pos is in its own (local to the main map)
    // coordinate system. We have to change pos to match that assumption
    const int dmx = submap_coords.x - g->get_levx();
    const int dmy = submap_coords.y - g->get_levy();
    const int offset_x = position.x % SEEX;
    const int offset_y = position.y % SEEY;
    // value of "submap_coords.x * SEEX + posx()" is unchanged
    setpos( tripoint( offset_x + dmx * SEEX, offset_y + dmy * SEEY, posz() ) );

    if( g->is_empty( pos() ) ) {
        return;
    }

    for( const tripoint &p : closest_tripoints_first( SEEX + 1, pos() ) ) {
        if( g->is_empty( p ) ) {
            setpos( p );
            return;
        }
    }

    debugmsg( "Failed to place NPC in a valid location near (%d,%d,%d)", posx(), posy(), posz() );
}

skill_id npc::best_skill() const
{
    int highest_level = std::numeric_limits<int>::min();
    skill_id highest_skill( skill_id::NULL_ID() );

    for( auto const &p : *_skills ) {
        if( p.first.obj().is_combat_skill() ) {
            int const level = p.second.level();
            if( level > highest_level ) {
                highest_level = level;
                highest_skill = p.first;
            }
        }
    }

    return highest_skill;
}

void npc::starting_weapon( const npc_class_id &type )
{
    if( item_group::group_is_defined( type->weapon_override ) ) {
        weapon = item_group::item_from( type->weapon_override );
        return;
    }

    const skill_id best = best_skill();

    // if NPC has no suitable skills default to stabbing weapon
    if( !best || best == skill_stabbing ) {
        weapon = random_item_from( type, "stabbing", "survivor_stabbing" );
    } else if( best == skill_bashing ) {
        weapon = random_item_from( type, "bashing", "survivor_bashing" );
    } else if( best == skill_cutting ) {
        weapon = random_item_from( type, "cutting", "survivor_cutting" );
    } else if( best == skill_throw ) {
        weapon = random_item_from( type, "throw" );
    } else if( best == skill_archery ) {
        weapon = random_item_from( type, "archery" );
    } else if( best == skill_pistol ) {
        weapon = random_item_from( type, "pistol", "guns_pistol_common" );
    } else if( best == skill_shotgun ) {
        weapon = random_item_from( type, "shotgun", "guns_shotgun_common" );
    } else if( best == skill_smg ) {
        weapon = random_item_from( type, "smg", "guns_smg_common" );
    } else if( best == skill_rifle ) {
        weapon = random_item_from( type, "rifle", "guns_rifle_common" );
    }

    if( weapon.is_gun() ) {
        weapon.ammo_set( weapon.type->gun->ammo->default_ammotype() );
    }
}

bool npc::wear_if_wanted( const item &it )
{
    // Note: this function isn't good enough to use with NPC AI alone
    // Restrict it to player's orders for now
    if( !it.is_armor() ) {
        return false;
    }

    // TODO: Make it depend on stuff
    static const std::array<int, num_bp> max_encumb = {{
            30, // bp_torso - Higher if ranged?
            100, // bp_head
            30, // bp_eyes - Lower if using ranged?
            30, // bp_mouth
            30, // bp_arm_l
            30, // bp_arm_r
            30, // bp_hand_l - Lower if throwing?
            30, // bp_hand_r
            // Must be enough to allow hazmat, turnout etc.
            30, // bp_leg_l - Higher if ranged?
            30, // bp_leg_r
            // Doesn't hurt much
            50, // bp_foot_l
            50, // bp_foot_r
        }
    };

    // Splints ignore limits, but only when being equipped on a broken part
    // TODO: Drop splints when healed
    bool splint = it.has_flag( "SPLINT" );
    if( splint ) {
        splint = false;
        for( int i = 0; i < num_hp_parts; i++ ) {
            hp_part hpp = hp_part( i );
            body_part bp = player::hp_to_bp( hpp );
            if( hp_cur[i] <= 0 && it.covers( bp ) ) {
                splint = true;
                break;
            }
        }
    }

    if( splint ) {
        return !!wear_item( it, false );
    }

    const int it_encumber = it.get_encumber();
    while( !worn.empty() ) {
        auto size_before = worn.size();
        bool encumb_ok = true;
        const auto new_enc = get_encumbrance( it );
        // Strip until we can put the new item on
        // This is one of the reasons this command is not used by the AI
        for( const body_part bp : all_body_parts ) {
            if( !it.covers( bp ) ) {
                continue;
            }

            if( it_encumber > max_encumb[bp] ) {
                // Not an NPC-friendly item
                return false;
            }

            if( new_enc[bp].encumbrance > max_encumb[bp] ) {
                encumb_ok = false;
                break;
            }
        }

        if( encumb_ok && can_wear( it ).success() ) {
            // @todo: Hazmat/power armor makes this not work due to 1 boots/headgear limit
            return !!wear_item( it, false );
        }
        // Otherwise, maybe we should take off one or more items and replace them
        bool took_off = false;
        for( const body_part bp : all_body_parts ) {
            if( !it.covers( bp ) ) {
                continue;
            }
            // Find an item that covers the same body part as the new item
            auto iter = std::find_if( worn.begin(), worn.end(), [bp]( const item & armor ) {
                return armor.covers( bp );
            } );
            if( iter != worn.end() ) {
                took_off = takeoff( *iter );
                break;
            }
        }

        if( !took_off || worn.size() >= size_before ) {
            // Shouldn't happen, but does
            return false;
        }
    }

    return worn.empty() && wear_item( it, false );
}

bool npc::wield( item &it )
{
    if( is_armed() ) {
        // If weapon has a shoulder strap, try to wear it.
        if( wear_item( weapon, false ) ) {
            // Wearing the item was successful, remove weapon and post message.
            add_msg_if_npc( m_info, _( "<npcname> wears the %s." ), weapon.tname().c_str() );
            remove_weapon();
            moves -= 15;
        } else { // Weapon cannot be worn or wearing was not successful. Store it in inventory if possible, otherwise drop it.
            if( volume_carried() + weapon.volume() <= volume_capacity() ) {
                add_msg_if_npc( m_info, _( "<npcname> puts away the %s." ), weapon.tname().c_str() );
                i_add( remove_weapon() );
                moves -= 15;
            } else { // No room for weapon, so we drop it
                add_msg_if_npc( m_info, _( "<npcname> drops the %s." ), weapon.tname().c_str() );
                g->m.add_item_or_charges( pos(), remove_weapon() );
            }
        }
    }

    if( it.is_null() ) {
        weapon = item();
        return true;
    }

    moves -= 15;
    if( has_item( it ) ) {
        weapon = remove_item( it );
    } else {
        weapon = it;
    }

    add_msg_if_npc( m_info, _( "<npcname> wields a %s." ),  weapon.tname().c_str() );
    return true;
}

void npc::form_opinion( const player &u )
{
    // FEAR
    if( u.weapon.is_gun() ) {
        // @todo: Make bows not guns
        if( weapon.is_gun() ) {
            op_of_u.fear += 2;
        } else {
            op_of_u.fear += 6;
        }
    } else if( u.weapon_value( u.weapon ) > 20 ) {
        op_of_u.fear += 2;
    } else if( !u.is_armed() ) {
        // Unarmed, but actually unarmed ("unarmed weapons" are not unarmed)
        op_of_u.fear -= 3;
    }

    ///\EFFECT_STR increases NPC fear of the player
    if( u.str_max >= 16 ) {
        op_of_u.fear += 2;
    } else if( u.str_max >= 12 ) {
        op_of_u.fear += 1;
    } else if( u.str_max <= 3 ) {
        op_of_u.fear -= 3;
    } else if( u.str_max <= 5 ) {
        op_of_u.fear -= 1;
    }

    for( int i = 0; i < num_hp_parts; i++ ) {
        if( u.hp_cur[i] <= u.hp_max[i] / 2 ) {
            op_of_u.fear--;
        }
        if( hp_cur[i] <= hp_max[i] / 2 ) {
            op_of_u.fear++;
        }
    }

    if( u.has_trait( trait_SAPIOVORE ) ) {
        op_of_u.fear += 10; // Sapiovores = Scary
    }

    if( u.has_trait( trait_PRETTY ) ) {
        op_of_u.fear += 1;
    } else if( u.has_trait( trait_BEAUTIFUL ) ) {
        op_of_u.fear += 2;
    } else if( u.has_trait( trait_BEAUTIFUL2 ) ) {
        op_of_u.fear += 3;
    } else if( u.has_trait( trait_BEAUTIFUL3 ) ) {
        op_of_u.fear += 4;
    } else if( u.has_trait( trait_UGLY ) ) {
        op_of_u.fear -= 1;
    } else if( u.has_trait( trait_DEFORMED ) ) {
        op_of_u.fear += 3;
    } else if( u.has_trait( trait_DEFORMED2 ) ) {
        op_of_u.fear += 6;
    } else if( u.has_trait( trait_DEFORMED3 ) ) {
        op_of_u.fear += 9;
    }

    if( u.has_trait( trait_TERRIFYING ) ) {
        op_of_u.fear += 6;
    }

    if( u.stim > 20 ) {
        op_of_u.fear++;
    }

    if( u.has_effect( effect_drunk ) ) {
        op_of_u.fear -= 2;
    }

    // TRUST
    if( op_of_u.fear > 0 ) {
        op_of_u.trust -= 3;
    } else {
        op_of_u.trust += 1;
    }

    if( u.weapon.is_gun() ) {
        op_of_u.trust -= 2;
    } else if( !u.is_armed() ) {
        op_of_u.trust += 2;
    }

    // @todo: More effects
    if( u.has_effect( effect_high ) ) {
        op_of_u.trust -= 1;
    }
    if( u.has_effect( effect_drunk ) ) {
        op_of_u.trust -= 2;
    }
    if( u.stim > 20 || u.stim < -20 ) {
        op_of_u.trust -= 1;
    }
    if( u.get_painkiller() > 30 ) {
        op_of_u.trust -= 1;
    }

    if( u.has_trait( trait_PRETTY ) ) {
        op_of_u.trust += 1;
    } else if( u.has_trait( trait_BEAUTIFUL ) ) {
        op_of_u.trust += 3;
    } else if( u.has_trait( trait_BEAUTIFUL2 ) ) {
        op_of_u.trust += 5;
    } else if( u.has_trait( trait_BEAUTIFUL3 ) ) {
        op_of_u.trust += 7;
    } else if( u.has_trait( trait_UGLY ) ) {
        op_of_u.trust -= 1;
    } else if( u.has_trait( trait_DEFORMED ) ) {
        op_of_u.trust -= 3;
    } else if( u.has_trait( trait_DEFORMED2 ) ) {
        op_of_u.trust -= 6;
    } else if( u.has_trait( trait_DEFORMED3 ) ) {
        op_of_u.trust -= 9;
    }

    if( op_of_u.trust > 0 ) {
        // Trust is worth a lot right now
        op_of_u.trust /= 2;
    }

    // VALUE
    op_of_u.value = 0;
    for( int i = 0; i < num_hp_parts; i++ ) {
        if( hp_cur[i] < hp_max[i] * 0.8f ) {
            op_of_u.value++;
        }
    }
    decide_needs();
    for( auto &i : needs ) {
        if( i == need_food || i == need_drink ) {
            op_of_u.value += 2;
        }
    }

    if( op_of_u.fear < personality.bravery + 10 &&
        op_of_u.fear - personality.aggression > -10 && op_of_u.trust > -8 ) {
        set_attitude( NPCATT_TALK );
    } else if( op_of_u.fear - 2 * personality.aggression - personality.bravery < -30 ) {
        set_attitude( NPCATT_KILL );
    } else if( my_fac != nullptr && my_fac->likes_u < -10 ) {
        set_attitude( NPCATT_KILL );
    } else {
        set_attitude( NPCATT_FLEE );
    }

    add_msg( m_debug, "%s formed an opinion of u: %s",
             name.c_str(), npc_attitude_name( attitude ).c_str() );
}

float npc::vehicle_danger( int radius ) const
{
    const tripoint from( posx() - radius, posy() - radius, posz() );
    const tripoint to( posx() + radius, posy() + radius, posz() );
    VehicleList vehicles = g->m.get_vehicles( from, to );

    int danger = 0;

    // TODO: check for most dangerous vehicle?
    for( unsigned int i = 0; i < vehicles.size(); ++i ) {
        const wrapped_vehicle &wrapped_veh = vehicles[i];
        if( wrapped_veh.v->velocity > 0 ) {
            float facing = wrapped_veh.v->face.dir();

            int ax = wrapped_veh.v->global_pos3().x;
            int ay = wrapped_veh.v->global_pos3().y;
            int bx = int( ax + cos( facing * M_PI / 180.0 ) * radius );
            int by = int( ay + sin( facing * M_PI / 180.0 ) * radius );

            // fake size
            /* This will almost certainly give the wrong size/location on customized
             * vehicles. This should just count frames instead. Or actually find the
             * size. */
            vehicle_part last_part = wrapped_veh.v->parts.back();
            int size = std::max( last_part.mount.x, last_part.mount.y );

            double normal = sqrt( static_cast<float>( ( bx - ax ) * ( bx - ax ) + ( by - ay ) * ( by - ay ) ) );
            int closest = int( abs( ( posx() - ax ) * ( by - ay ) - ( posy() - ay ) * ( bx - ax ) ) / normal );

            if( size > closest ) {
                danger = i;
            }
        }
    }
    return danger;
}

bool npc::turned_hostile() const
{
    return ( op_of_u.anger >= hostile_anger_level() );
}

int npc::hostile_anger_level() const
{
    return ( 20 + op_of_u.fear - personality.aggression );
}

void npc::make_angry()
{
    if( is_enemy() ) {
        return; // We're already angry!
    }

    // Make associated faction, if any, angry at the player too.
    if( my_fac != nullptr ) {
        my_fac->likes_u = std::max( -50, my_fac->likes_u - 50 );
        my_fac->respects_u = std::max( -50, my_fac->respects_u - 50 );
    }
    if( op_of_u.fear > 10 + personality.aggression + personality.bravery ) {
        set_attitude( NPCATT_FLEE ); // We don't want to take u on!
    } else {
        set_attitude( NPCATT_KILL ); // Yeah, we think we could take you!
    }
}

void npc::on_attacked( const Creature &attacker )
{
    if( attacker.is_player() && !is_enemy() ) {
        make_angry();
        hit_by_player = true;
    }
}

int npc::assigned_missions_value()
{
    int ret = 0;
    for( auto &m : chatbin.missions_assigned ) {
        ret += m->get_value();
    }
    return ret;
}

std::vector<skill_id> npc::skills_offered_to( const player &p ) const
{
    std::vector<skill_id> ret;
    for( const auto &pair : *_skills ) {
        const skill_id &id = pair.first;
        if( p.get_skill_level( id ) < pair.second.level() ) {
            ret.push_back( id );
        }
    }
    return ret;
}

std::vector<matype_id> npc::styles_offered_to( const player &p ) const
{
    std::vector<matype_id> ret;
    for( auto &i : ma_styles ) {
        if( !p.has_martialart( i ) ) {
            ret.push_back( i );
        }
    }
    return ret;
}

bool npc::fac_has_value( faction_value value ) const
{
    if( my_fac == nullptr ) {
        return false;
    }

    return my_fac->has_value( value );
}

bool npc::fac_has_job( faction_job job ) const
{
    if( my_fac == nullptr ) {
        return false;
    }

    return my_fac->has_job( job );
}

void npc::decide_needs()
{
    double needrank[num_needs];
    for( auto &elem : needrank ) {
        elem = 20;
    }
    if( weapon.is_gun() ) {
        needrank[need_ammo] = 5 * get_ammo( weapon.type->gun->ammo ).size();
    }

    needrank[need_weapon] = weapon_value( weapon );
    needrank[need_food] = 15 - get_hunger();
    needrank[need_drink] = 15 - get_thirst();
    invslice slice = inv.slice();
    for( auto &i : slice ) {
        item inventory_item = i->front();
        if( inventory_item.is_food( ) ) {
            needrank[ need_food ] += nutrition_for( inventory_item ) / 4;
            needrank[ need_drink ] += inventory_item.type->comestible->quench / 4;
        } else if( inventory_item.is_food_container() ) {
            needrank[ need_food ] += nutrition_for( inventory_item.contents.front() ) / 4;
            needrank[ need_drink ] += inventory_item.contents.front().type->comestible->quench / 4;
        }
    }
    needs.clear();
    size_t j;
    bool serious = false;
    for( int i = 1; i < num_needs; i++ ) {
        if( needrank[i] < 10 ) {
            serious = true;
        }
    }
    if( !serious ) {
        needs.push_back( need_none );
        needrank[0] = 10;
    }
    for( int i = 1; i < num_needs; i++ ) {
        if( needrank[i] < 20 ) {
            for( j = 0; j < needs.size(); j++ ) {
                if( needrank[i] < needrank[needs[j]] ) {
                    needs.insert( needs.begin() + j, npc_need( i ) );
                    j = needs.size() + 1;
                }
            }
            if( j == needs.size() ) {
                needs.push_back( npc_need( i ) );
            }
        }
    }
}

void npc::say( const std::string &line ) const
{
    std::string formatted_line = line;
    parse_tags( formatted_line, g->u, *this );
    if( has_trait( trait_id( "MUTE" ) ) ) {
        return;
    }

    const bool sees = g->u.sees( *this );
    const bool deaf = g->u.is_deaf();
    if( sees && !deaf ) {
        add_msg( _( "%1$s says: \"%2$s\"" ), name.c_str(), formatted_line.c_str() );
        sounds::sound( pos(), 16, "" );
    } else if( !sees ) {
        std::string sound = string_format( _( "%1$s saying \"%2$s\"" ), name.c_str(),
                                           formatted_line.c_str() );
        sounds::sound( pos(), 16, sound );
    } else {
        add_msg( m_warning, _( "%1$s says something but you can't hear it!" ), name.c_str() );
        sounds::sound( pos(), 16, "" );
    }
}

bool npc::wants_to_sell( const item &it ) const
{
    const int market_price = it.price( true );
    return wants_to_sell( it, value( it, market_price ), market_price );
}

bool npc::wants_to_sell( const item &it, int at_price, int market_price ) const
{
    ( void )it;

    if( mission == NPC_MISSION_SHOPKEEP ) {
        return true;
    }

    if( is_friend() ) {
        return true;
    }

    // TODO: Base on inventory
    return at_price - market_price <= 50;
}

bool npc::wants_to_buy( const item &it ) const
{
    const int market_price = it.price( true );
    return wants_to_buy( it, value( it, market_price ), market_price );
}

bool npc::wants_to_buy( const item &it, int at_price, int market_price ) const
{
    ( void )market_price;
    ( void )it;

    if( is_friend() ) {
        return true;
    }

    // TODO: Base on inventory
    return at_price >= 80;
}

void npc::shop_restock()
{
    restock = calendar::turn + 3_days;
    if( is_friend() ) {
        return;
    }

    const Group_tag &from = myclass->get_shopkeeper_items();
    if( from == "EMPTY_GROUP" ) {
        return;
    }

    units::volume total_space = volume_capacity();
    std::list<item> ret;

    while( total_space > 0 && !one_in( 50 ) ) {
        item tmpit = item_group::item_from( from, 0 );
        if( !tmpit.is_null() && total_space >= tmpit.volume() ) {
            ret.push_back( tmpit );
            total_space -= tmpit.volume();
        }
    }

    has_new_items = true;
    inv.clear();
    inv.push_back( ret );
}

int npc::minimum_item_value() const
{
    // TODO: Base on inventory
    int ret = 20;
    ret -= personality.collector;
    return ret;
}

void npc::update_worst_item_value()
{
    worst_item_value = 99999;
    // TODO: Cache this
    int inv_val = inv.worst_item_value( this );
    if( inv_val < worst_item_value ) {
        worst_item_value = inv_val;
    }
}

int npc::value( const item &it ) const
{
    int market_price = it.price( true );
    return value( it, market_price );
}

int npc::value( const item &it, int market_price ) const
{
    if( it.is_dangerous() || ( it.has_flag( "BOMB" ) && it.active ) || it.made_of( LIQUID ) ) {
        // NPCs won't be interested in buying active explosives or spilled liquids
        return -1000;
    }

    int ret = 0;
    // TODO: Cache own weapon value (it can be a bit expensive to compute 50 times/turn)
    double weapon_val = weapon_value( it ) - weapon_value( weapon );
    if( weapon_val > 0 ) {
        ret += weapon_val;
    }

    if( it.is_food() ) {
        int comestval = 0;
        if( nutrition_for( it ) > 0 || it.type->comestible->quench > 0 ) {
            comestval++;
        }
        if( get_hunger() > 40 ) {
            comestval += ( nutrition_for( it ) + get_hunger() - 40 ) / 6;
        }
        if( get_thirst() > 40 ) {
            comestval += ( it.type->comestible->quench + get_thirst() - 40 ) / 4;
        }
        if( comestval > 0 && will_eat( it ).success() ) {
            ret += comestval;
        }
    }

    if( it.is_ammo() ) {
        if( weapon.is_gun() && it.type->ammo->type.count( weapon.ammo_type() ) ) {
            ret += 14; // @todo: magazines - don't count ammo as usable if the weapon isn't.
        }

        if( std::any_of( it.type->ammo->type.begin(), it.type->ammo->type.end(),
        [&]( const ammotype & e ) {
        return has_gun_for_ammo( e );
        } ) ) {
            ret += 14; // @todo: consider making this cumulative (once was)
        }
    }

    if( it.is_book() ) {
        auto &book = *it.type->book;
        ret += book.fun;
        if( book.skill && get_skill_level( book.skill ) < book.level &&
            get_skill_level( book.skill ) >= book.req ) {
            ret += book.level * 3;
        }
    }

    // TODO: Sometimes we want more than one tool?  Also we don't want EVERY tool.
    if( it.is_tool() && !has_amount( it.typeId(), 1 ) ) {
        ret += 8;
    }

    // TODO: Artifact hunting from relevant factions
    // ALSO TODO: Bionics hunting from relevant factions
    if( fac_has_job( FACJOB_DRUGS ) && it.is_food() && it.type->comestible->addict >= 5 ) {
        ret += 10;
    }

    if( fac_has_job( FACJOB_DOCTORS ) && it.is_food() && it.type->comestible->comesttype == "MED" ) {
        ret += 10;
    }

    if( fac_has_value( FACVAL_BOOKS ) && it.is_book() ) {
        ret += 14;
    }

    if( fac_has_job( FACJOB_SCAVENGE ) ) {
        // Computed last for _reasons_.
        ret += 6;
        ret *= 1.3;
    }

    // Practical item value is more important than price
    ret *= 50;
    ret += market_price;
    return ret;
}

bool npc::has_healing_item( bool bleed, bool bite, bool infect )
{
    return !get_healing_item( bleed, bite, infect, true ).is_null();
}

item &npc::get_healing_item( bool bleed, bool bite, bool infect, bool first_best )
{
    item *best = &null_item_reference();
    visit_items( [&best, bleed, bite, infect, first_best]( item * node ) {
        const auto use = node->type->get_use( "heal" );
        if( use == nullptr ) {
            return VisitResponse::NEXT;
        }

        auto &actor = dynamic_cast<const heal_actor &>( *( use->get_actor_ptr() ) );
        if( ( !bleed || actor.bleed > 0 ) ||
            ( !bite || actor.bite > 0 ) ||
            ( !infect || actor.infect > 0 ) ) {
            best = node;
            if( first_best ) {
                return VisitResponse::ABORT;
            }
        }

        return VisitResponse::NEXT;
    } );

    return *best;
}

bool npc::has_painkiller()
{
    return inv.has_enough_painkiller( get_pain() );
}

bool npc::took_painkiller() const
{
    return ( has_effect( effect_pkill1 ) || has_effect( effect_pkill2 ) ||
             has_effect( effect_pkill3 ) || has_effect( effect_pkill_l ) );
}

bool npc::is_friend() const
{
    return attitude == NPCATT_FOLLOW || attitude == NPCATT_LEAD;
}

bool npc::is_minion() const
{
    return is_friend() && op_of_u.trust >= 5;
}

bool npc::guaranteed_hostile() const
{
    return is_enemy() || ( my_fac != nullptr && my_fac->likes_u < -10 );
}

bool npc::is_following() const
{
    switch( attitude ) {
        case NPCATT_FOLLOW:
        case NPCATT_WAIT:
            return true;
        default:
            return false;
    }
}

bool npc::is_leader() const
{
    return ( attitude == NPCATT_LEAD );
}

bool npc::is_enemy() const
{
    return attitude == NPCATT_KILL || attitude == NPCATT_FLEE;
}

bool npc::is_guarding() const
{
    return mission == NPC_MISSION_SHELTER || mission == NPC_MISSION_BASE ||
           mission == NPC_MISSION_SHOPKEEP || mission == NPC_MISSION_GUARD ||
           mission == NPC_MISSION_GUARD_ALLY ||
           has_effect( effect_infection );
}

Creature::Attitude npc::attitude_to( const Creature &other ) const
{
    if( is_friend() ) {
        // Friendly NPCs share player's alliances
        return g->u.attitude_to( other );
    }

    if( other.is_npc() ) {
        // Hostile NPCs are also hostile towards player's allied
        if( is_enemy() && other.attitude_to( g->u ) == A_FRIENDLY ) {
            return A_HOSTILE;
        }

        return A_NEUTRAL;
    } else if( other.is_player() ) {
        // For now, make it symmetric.
        return other.attitude_to( *this );
    }

    // @todo: Get rid of the ugly cast without duplicating checks
    const monster &m = dynamic_cast<const monster &>( other );
    switch( m.attitude( this ) ) {
        case MATT_FOLLOW:
        case MATT_FPASSIVE:
        case MATT_IGNORE:
        case MATT_FLEE:
            return A_NEUTRAL;
        case MATT_FRIEND:
        case MATT_ZLAVE:
            return A_FRIENDLY;
        case MATT_ATTACK:
            return A_HOSTILE;
        case MATT_NULL:
        case NUM_MONSTER_ATTITUDES:
            break;
    }

    return A_NEUTRAL;
}

int npc::smash_ability() const
{
    if( !is_following() || rules.allow_bash ) {
        ///\EFFECT_STR_NPC increases smash ability
        return str_cur + weapon.damage_melee( DT_BASH );
    }

    // Not allowed to bash
    return 0;
}

float npc::danger_assessment()
{
    return ai_cache.danger_assessment;
}

float npc::average_damage_dealt()
{
    return float( melee_value( weapon ) );
}

bool npc::bravery_check( int diff )
{
    return ( dice( 10 + personality.bravery, 6 ) >= dice( diff, 4 ) );
}

bool npc::emergency() const
{
    return emergency( ai_cache.danger_assessment );
}

bool npc::emergency( float danger ) const
{
    return ( danger > ( personality.bravery * 3 * hp_percentage() ) / 100 );
}

//Check if this npc is currently in the list of active npcs.
//Active npcs are the npcs near the player that are actively simulated.
bool npc::is_active() const
{
    return g->critter_at<npc>( pos() ) == this;
}

int npc::follow_distance() const
{
    // If the player is standing on stairs, follow closely
    // This makes the stair hack less painful to use
    if( is_friend() &&
        ( g->m.has_flag( TFLAG_GOES_DOWN, g->u.pos() ) ||
          g->m.has_flag( TFLAG_GOES_UP, g->u.pos() ) ) ) {
        return 1;
    }
    // @todo: Allow player to set that
    return 4;
}

nc_color npc::basic_symbol_color() const
{
    if( attitude == NPCATT_KILL ) {
        return c_red;
    } else if( attitude == NPCATT_FLEE ) {
        return c_red;
    } else if( is_friend() ) {
        return c_green;
    } else if( is_following() ) {
        return c_light_green;
    } else if( guaranteed_hostile() ) {
        return c_red;
    }
    return c_pink;
}

int npc::print_info( const catacurses::window &w, int line, int vLines, int column ) const
{
    const int last_line = line + vLines;
    const unsigned int iWidth = getmaxx( w ) - 2;
    // First line of w is the border; the next 4 are terrain info, and after that
    // is a blank line. w is 13 characters tall, and we can't use the last one
    // because it's a border as well; so we have lines 6 through 11.
    // w is also 48 characters wide - 2 characters for border = 46 characters for us
    mvwprintz( w, line++, column, c_white, _( "NPC: %s" ), name.c_str() );
    if( is_armed() ) {
        trim_and_print( w, line++, column, iWidth, c_red, _( "Wielding a %s" ), weapon.tname().c_str() );
    }

    const auto enumerate_print = [ w, last_line, column, iWidth, &line ]( std::string & str_in,
    nc_color color ) {
        // @todo: Replace with 'fold_and_print()'. Extend it with a 'height' argument to prevent leaking.
        size_t split;
        do {
            split = ( str_in.length() <= iWidth ) ? std::string::npos : str_in.find_last_of( ' ',
                    long( iWidth ) );
            if( split == std::string::npos ) {
                mvwprintz( w, line, column, color, str_in.c_str() );
            } else {
                mvwprintz( w, line, column, color, str_in.substr( 0, split ).c_str() );
            }
            str_in = str_in.substr( split + 1 );
            line++;
        } while( split != std::string::npos && line <= last_line );
    };

    const std::string worn_str = enumerate_as_string( worn.begin(), worn.end(), []( const item & it ) {
        return it.tname();
    } );
    if( !worn_str.empty() ) {
        std::string wearing = _( "Wearing: " ) + remove_color_tags( worn_str );
        enumerate_print( wearing, c_blue );
    }

    const int visibility_cap = g->u.get_per() - rl_dist( g->u.pos(), pos() );
    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
        // @todo: Balance this formula
        if( mut_branch.visibility > 0 && mut_branch.visibility >= visibility_cap )
        {
            return mut_branch.name();
        }

        return std::string();
    } );
    if( !trait_str.empty() ) {
        std::string mutations = _( "Traits: " ) + remove_color_tags( trait_str );
        enumerate_print( mutations, c_green );
    }

    return line;
}

std::string npc::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 << ";";
    }
    return ret.str();
}

std::string npc::opinion_text() const
{
    std::stringstream ret;
    if( op_of_u.trust <= -10 ) {
        ret << _( "Completely untrusting" );
    } else if( op_of_u.trust <= -6 ) {
        ret << _( "Very untrusting" );
    } else if( op_of_u.trust <= -3 ) {
        ret << _( "Untrusting" );
    } else if( op_of_u.trust <= 2 ) {
        ret << _( "Uneasy" );
    } else if( op_of_u.trust <= 4 ) {
        ret << _( "Trusting" );
    } else if( op_of_u.trust < 10 ) {
        ret << _( "Very trusting" );
    } else {
        ret << _( "Completely trusting" );
    }

    ret << " (" << _( "Trust: " ) << op_of_u.trust << "); ";

    if( op_of_u.fear <= -10 ) {
        ret << _( "Thinks you're laughably harmless" );
    } else if( op_of_u.fear <= -6 ) {
        ret << _( "Thinks you're harmless" );
    } else if( op_of_u.fear <= -3 ) {
        ret << _( "Unafraid" );
    } else if( op_of_u.fear <= 2 ) {
        ret << _( "Wary" );
    } else if( op_of_u.fear <= 5 ) {
        ret << _( "Afraid" );
    } else if( op_of_u.fear < 10 ) {
        ret << _( "Very afraid" );
    } else {
        ret << _( "Terrified" );
    }

    ret << " (" << _( "Fear: " ) << op_of_u.fear << "); ";

    if( op_of_u.value <= -10 ) {
        ret << _( "Considers you a major liability" );
    } else if( op_of_u.value <= -6 ) {
        ret << _( "Considers you a burden" );
    } else if( op_of_u.value <= -3 ) {
        ret << _( "Considers you an annoyance" );
    } else if( op_of_u.value <= 2 ) {
        ret << _( "Doesn't care about you" );
    } else if( op_of_u.value <= 5 ) {
        ret << _( "Values your presence" );
    } else if( op_of_u.value < 10 ) {
        ret << _( "Treasures you" );
    } else {
        ret << _( "Best Friends Forever!" );
    }

    ret << " (" << _( "Value: " ) << op_of_u.value << "); ";

    if( op_of_u.anger <= -10 ) {
        ret << _( "You can do no wrong!" );
    } else if( op_of_u.anger <= -6 ) {
        ret << _( "You're good people" );
    } else if( op_of_u.anger <= -3 ) {
        ret << _( "Thinks well of you" );
    } else if( op_of_u.anger <= 2 ) {
        ret << _( "Ambivalent" );
    } else if( op_of_u.anger <= 5 ) {
        ret << _( "Pissed off" );
    } else if( op_of_u.anger < 10 ) {
        ret << _( "Angry" );
    } else {
        ret << _( "About to kill you" );
    }

    ret << " (" << _( "Anger: " ) << op_of_u.anger << ")";

    return ret.str();
}

void npc::setpos( const tripoint &pos )
{
    position = pos;
    const point pos_om_old = sm_to_om_copy( submap_coords );
    submap_coords.x = g->get_levx() + pos.x / SEEX;
    submap_coords.y = g->get_levy() + pos.y / SEEY;
    const point pos_om_new = sm_to_om_copy( submap_coords );
    if( !is_fake() && pos_om_old != pos_om_new ) {
        overmap &om_old = overmap_buffer.get( pos_om_old.x, pos_om_old.y );
        overmap &om_new = overmap_buffer.get( pos_om_new.x, pos_om_new.y );
        if( const auto ptr = om_old.erase_npc( getID() ) ) {
            om_new.insert_npc( ptr );
        } else {
            // Don't move the npc pointer around to avoid having two overmaps
            // with the same npc pointer
            debugmsg( "could not find npc %s on its old overmap", name.c_str() );
        }
    }
}

void maybe_shift( cata::optional<tripoint> &pos, int dx, int dy )
{
    if( pos ) {
        pos->x += dx;
        pos->y += dy;
    }
}

void maybe_shift( tripoint &pos, int dx, int dy )
{
    if( pos != tripoint_min ) {
        pos.x += dx;
        pos.y += dy;
    }
}

void npc::shift( int sx, int sy )
{
    const int shiftx = sx * SEEX;
    const int shifty = sy * SEEY;

    setpos( pos() - point( shiftx, shifty ) );

    maybe_shift( wanted_item_pos, -shiftx, -shifty );
    maybe_shift( last_player_seen_pos, -shiftx, -shifty );
    maybe_shift( pulp_location, -shiftx, -shifty );
    path.clear();
}

bool npc::is_dead() const
{
    return dead || is_dead_state();
}

void npc::die( Creature *nkiller )
{
    if( dead ) {
        // We are already dead, don't die again, note that npc::dead is
        // *only* set to true in this function!
        return;
    }
    // Need to unboard from vehicle before dying, otherwise
    // the vehicle code cannot find us
    if( in_vehicle ) {
        g->m.unboard_vehicle( pos() );
    }

    dead = true;
    Character::die( nkiller );

    if( g->u.sees( *this ) ) {
        add_msg( _( "%s dies!" ), name.c_str() );
    }

    if( killer == &g->u && ( !guaranteed_hostile() || hit_by_player ) ) {
        g->record_npc_kill( *this );
        bool cannibal = g->u.has_trait( trait_CANNIBAL );
        bool psycho = g->u.has_trait( trait_PSYCHOPATH );
        if( g->u.has_trait( trait_SAPIOVORE ) ) {
            g->u.add_memorial_log( pgettext( "memorial_male",
                                             "Caught and killed an ape.  Prey doesn't have a name." ),
                                   pgettext( "memorial_female", "Caught and killed an ape.  Prey doesn't have a name." ) );
        } else if( psycho && cannibal ) {
            g->u.add_memorial_log( pgettext( "memorial_male",
                                             "Killed a delicious-looking innocent, %s, in cold blood." ),
                                   pgettext( "memorial_female", "Killed a delicious-looking innocent, %s, in cold blood." ),
                                   name.c_str() );
        } else if( psycho ) {
            g->u.add_memorial_log( pgettext( "memorial_male",
                                             "Killed an innocent, %s, in cold blood. They were weak." ),
                                   pgettext( "memorial_female", "Killed an innocent, %s, in cold blood. They were weak." ),
                                   name.c_str() );
        } else if( cannibal ) {
            g->u.add_memorial_log( pgettext( "memorial_male", "Killed an innocent, %s." ),
                                   pgettext( "memorial_female", "Killed an innocent, %s." ),
                                   name.c_str() );
            g->u.add_morale( MORALE_KILLED_INNOCENT, -5, 0, 2_days, 3_hours );
        } else {
            g->u.add_memorial_log( pgettext( "memorial_male",
                                             "Killed an innocent person, %s, in cold blood and felt terrible afterwards." ),
                                   pgettext( "memorial_female",
                                             "Killed an innocent person, %s, in cold blood and felt terrible afterwards." ),
                                   name.c_str() );
            g->u.add_morale( MORALE_KILLED_INNOCENT, -100, 0, 2_days, 3_hours );
        }
    }

    place_corpse();
}

std::string npc_attitude_name( npc_attitude att )
{
    switch( att ) {
        case NPCATT_NULL:          // Don't care/ignoring player
            return _( "Ignoring" );
        case NPCATT_TALK:          // Move to and talk to player
            return _( "Wants to talk" );
        case NPCATT_FOLLOW:        // Follow the player
            return _( "Following" );
        case NPCATT_LEAD:          // Lead the player, wait for them if they're behind
            return _( "Leading" );
        case NPCATT_WAIT:          // Waiting for the player
            return _( "Waiting for you" );
        case NPCATT_MUG:           // Mug the player
            return _( "Mugging you" );
        case NPCATT_WAIT_FOR_LEAVE:// Attack the player if our patience runs out
            return _( "Waiting for you to leave" );
        case NPCATT_KILL:          // Kill the player
            return _( "Attacking to kill" );
        case NPCATT_FLEE:          // Get away from the player
            return _( "Fleeing" );
        case NPCATT_HEAL:          // Get to the player and heal them
            return _( "Healing you" );
        default:
            break;
    }

    debugmsg( "Invalid attitude: %d", att );
    return _( "Unknown attitude" );
}

void npc::setID( int i )
{
    this->player::setID( i );
}

//message related stuff

//message related stuff
void npc::add_msg_if_npc( const std::string &msg ) const
{
    add_msg( replace_with_npc_name( msg ) );
}

void npc::add_msg_player_or_npc( const std::string &/*player_msg*/,
                                 const std::string &npc_msg ) const
{
    if( g->u.sees( *this ) ) {
        add_msg( replace_with_npc_name( npc_msg ) );
    }
}

void npc::add_msg_if_npc( const game_message_type type, const std::string &msg ) const
{
    add_msg( type, replace_with_npc_name( msg ) );
}

void npc::add_msg_player_or_npc( const game_message_type type, const std::string &/*player_msg*/,
                                 const std::string &npc_msg ) const
{
    if( g->u.sees( *this ) ) {
        add_msg( type, replace_with_npc_name( npc_msg ) );
    }
}

void npc::add_msg_player_or_say( const std::string &/*player_msg*/,
                                 const std::string &npc_speech ) const
{
    say( npc_speech );
}

void npc::add_msg_player_or_say( const game_message_type /*type*/,
                                 const std::string &/*player_msg*/, const std::string &npc_speech ) const
{
    say( npc_speech );
}

void npc::add_new_mission( class mission *miss )
{
    chatbin.add_new_mission( miss );
}

void npc::on_unload()
{
    last_updated = calendar::turn;
}

void npc::on_load()
{
    // Cap at some reasonable number, say 2 days
    const time_duration dt = std::min( calendar::turn - last_updated, 2_days );
    // TODO: Sleeping, healing etc.
    last_updated = calendar::turn;
    time_point cur = calendar::turn - dt;
    add_msg( m_debug, "on_load() by %s, %d turns", name, to_turns<int>( dt ) );
    // First update with 30 minute granularity, then 5 minutes, then turns
    for( ; cur < calendar::turn - 30_minutes; cur += 30_minutes + 1_turns ) {
        update_body( cur, cur + 30_minutes );
    }
    for( ; cur < calendar::turn - 5_minutes; cur += 5_minutes + 1_turns ) {
        update_body( cur, cur + 5_minutes );
    }
    for( ; cur < calendar::turn; cur += 1_turns ) {
        update_body( cur, cur + 1_turns );
    }

    if( dt > 0 ) {
        // This ensures food is properly rotten at load
        // Otherwise NPCs try to eat rotten food and fail
        process_active_items();
    }

    // Not necessarily true, but it's not a bad idea to set this
    has_new_items = true;

    // for spawned npcs
    if( g->m.has_flag( "UNSTABLE", pos() ) ) {
        add_effect( effect_bouldering, 1_turns, num_bp, true );
    } else if( has_effect( effect_bouldering ) ) {
        remove_effect( effect_bouldering );
    }
    if( g->m.veh_at( pos() ).part_with_feature( VPFLAG_BOARDABLE, true ) && !in_vehicle ) {
        g->m.board_vehicle( pos(), this );
    }
}

void npc_chatbin::add_new_mission( mission *miss )
{
    if( miss == nullptr ) {
        return;
    }
    missions.push_back( miss );
}

epilogue::epilogue()
{
    id = "NONE";
    group = "NONE";
    text = "Error: file lost!";
}

epilogue_map epilogue::_all_epilogue;

void epilogue::load_epilogue( JsonObject &jsobj )
{
    epilogue base;
    base.id = jsobj.get_string( "id" );
    base.group = jsobj.get_string( "group" );
    base.text = jsobj.get_string( "text" );

    _all_epilogue[base.id] = base;
}

epilogue *epilogue::find_epilogue( const std::string &ident )
{
    epilogue_map::iterator found = _all_epilogue.find( ident );
    if( found != _all_epilogue.end() ) {
        return &( found->second );
    } else {
        debugmsg( "Tried to get invalid epilogue template: %s", ident.c_str() );
        static epilogue null_epilogue;
        return &null_epilogue;
    }
}

void epilogue::random_by_group( std::string group )
{
    std::vector<epilogue> v;
    for( auto epi : _all_epilogue ) {
        if( epi.second.group == group ) {
            v.push_back( epi.second );
        }
    }
    if( v.empty() ) {
        return;
    }
    epilogue epi = random_entry( v );
    id = epi.id;
    group = epi.group;
    text = epi.text;
}

constexpr tripoint npc::no_goal_point;

bool npc::query_yn( const std::string &/*msg*/ ) const
{
    // NPCs don't like queries - most of them are in the form of "Do you want to get hurt?".
    return false;
}

float npc::speed_rating() const
{
    float ret = get_speed() / 100.0f;
    ret *= 100.0f / run_cost( 100, false );

    return ret;
}

bool npc::dispose_item( item_location &&obj, const std::string & )
{
    using dispose_option = struct {
        int moves;
        std::function<void()> action;
    };

    std::vector<dispose_option> opts;

    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 {
                item_store_cost( *obj, e, false, ptr->draw_cost ),
                [this, ptr, &e, &obj]{ ptr->store( *this, e, *obj ); }
            } );
        }
    }

    if( volume_carried() + obj->volume() <= volume_capacity() ) {
        opts.emplace_back( dispose_option {
            item_handling_cost( *obj ),
            [this, &obj] {
                moves -= item_handling_cost( *obj );
                inv.add_item_keep_invlet( *obj );
                obj.remove_item();
                inv.unsort();
            }
        } );
    }

    if( opts.empty() ) {
        // Drop it
        g->m.add_item_or_charges( pos(), *obj );
        obj.remove_item();
        return true;
    }

    const auto mn = std::min_element( opts.begin(), opts.end(),
    []( const dispose_option & lop, const dispose_option & rop ) {
        return lop.moves < rop.moves;
    } );

    mn->action();
    return true;
}

void npc::process_turn()
{
    player::process_turn();

    if( is_following() && calendar::once_every( 1_hours ) &&
        get_hunger() < 200 && get_thirst() < 100 && op_of_u.trust < 5 ) {
        // Friends who are well fed will like you more
        // 24 checks per day, best case chance at trust 0 is 1 in 48 for +1 trust per 2 days
        float trust_chance = 5 - op_of_u.trust;
        // Penalize for bad impression
        // TODO: Penalize for traits and actions (especially murder, unless NPC is psycho)
        int op_penalty = std::max( 0, op_of_u.anger ) +
                         std::max( 0, -op_of_u.value ) +
                         std::max( 0, op_of_u.fear );
        // Being barely hungry and thirsty, not in pain and not wounded means good care
        int state_penalty = get_hunger() + get_thirst() + ( 100 - hp_percentage() ) + get_pain();
        if( x_in_y( trust_chance, 240 + 10 * op_penalty + state_penalty ) ) {
            op_of_u.trust++;
        }

        // TODO: Similar checks for fear and anger
    }

    last_updated = calendar::turn;
    // TODO: Add decreasing trust/value/etc. here when player doesn't provide food
    // TODO: Make NPCs leave the player if there's a path out of map and player is sleeping/unseen/etc.
}

std::array<std::pair<std::string, overmap_location_str_id>, npc_need::num_needs> npc::need_data = {
    {
        { "need_none", overmap_location_str_id( "source_of_anything" ) },
        { "need_ammo", overmap_location_str_id( "source_of_ammo" )},
        { "need_weapon", overmap_location_str_id( "source_of_weapon" )},
        { "need_gun", overmap_location_str_id( "source_of_gun" ) },
        { "need_food", overmap_location_str_id( "source_of_food" )},
        { "need_drink", overmap_location_str_id( "source_of_drink" ) }
    }
};

std::string npc::get_need_str_id( const npc_need &need )
{
    return need_data[static_cast<size_t>( need )].first;
}

overmap_location_str_id npc::get_location_for( const npc_need &need )
{
    return need_data[static_cast<size_t>( need )].second;
}

std::ostream &operator<< ( std::ostream &os, const npc_need &need )
{
    return os << npc::get_need_str_id( need );
}

bool npc::will_accept_from_player( const item &it ) const
{
    if( is_minion() || g->u.has_trait( trait_id( "DEBUG_MIND_CONTROL" ) ) ||
        it.has_flag( "NPC_SAFE" ) ) {
        return true;
    }

    if( !it.type->use_methods.empty() ) {
        return false;
    }

    if( const auto &comest = it.is_container() ? it.get_contained().type->comestible :
                             it.type->comestible ) {
        if( comest->fun < 0 || it.poison > 0 ) {
            return false;
        }
    }

    return true;
}

const pathfinding_settings &npc::get_pathfinding_settings() const
{
    return get_pathfinding_settings( false );
}

const pathfinding_settings &npc::get_pathfinding_settings( bool no_bashing ) const
{
    path_settings->bash_strength = no_bashing ? 0 : smash_ability();
    // @todo: Extract climb skill
    const int climb = std::min( 20, get_dex() );
    if( climb > 1 ) {
        // Success is !one_in(dex), so 0%, 50%, 66%, 75%...
        // Penalty for failure chance is 1/success = 1/(1-failure) = 1/(1-(1/dex)) = dex/(dex-1)
        path_settings->climb_cost = ( 10 - climb / 5 ) * climb / ( climb - 1 );
    } else {
        // Climbing at this dexterity will always fail
        path_settings->climb_cost = 0;
    }

    return *path_settings;
}

std::set<tripoint> npc::get_path_avoid() const
{
    std::set<tripoint> ret;
    for( Creature &critter : g->all_creatures() ) {
        // @todo: Cache this somewhere
        ret.insert( critter.pos() );
    }
    return ret;
}

mfaction_id npc::get_monster_faction() const
{
    // Those can't be static int_ids, because mods add factions
    static const string_id<monfaction> human_fac( "human" );
    static const string_id<monfaction> player_fac( "player" );
    static const string_id<monfaction> bee_fac( "bee" );

    if( is_friend() ) {
        return player_fac.id();
    }

    if( has_trait( trait_id( "BEE" ) ) ) {
        return bee_fac.id();
    }

    return human_fac.id();
}

std::string npc::extended_description() const
{
    std::ostringstream ss;
    // For some reason setting it using str or constructor doesn't work
    ss << Character::extended_description();

    ss << std::endl << "--" << std::endl;
    if( attitude == NPCATT_KILL ) {
        ss << _( "Is trying to kill you." );
    } else if( attitude == NPCATT_FLEE ) {
        ss << _( "Is trying to flee from you." );
    } else if( is_friend() ) {
        ss << _( "Is your friend." );
    } else if( is_following() ) {
        ss << _( "Is following you." );
    } else if( guaranteed_hostile() ) {
        ss << _( "Will try to kill you or flee from you if you reveal yourself." );
    } else {
        ss << _( "Is neutral." );
    }

    if( hit_by_player ) {
        ss << "--" << std::endl;
        ss << _( "Is still innocent and killing them will be considered murder." );
        // @todo: "But you don't care because you're an edgy psycho"
    }

    return replace_colors( ss.str() );
}

void npc::set_companion_mission( npc &p, const std::string &id )
{
    const point omt_pos = ms_to_omt_copy( g->m.getabs( p.posx(), p.posy() ) );
    comp_mission.position = tripoint( omt_pos.x, omt_pos.y, p.posz() );
    comp_mission.mission_id =  id;
    comp_mission.role_id = p.companion_mission_role_id;
}

void npc::reset_companion_mission()
{
    comp_mission.position = tripoint( -999, -999, -999 );
    comp_mission.mission_id.clear();
    comp_mission.role_id.clear();
}

bool npc::has_companion_mission() const
{
    return !comp_mission.mission_id.empty();
}

npc_companion_mission npc::get_companion_mission() const
{
    return comp_mission;
}

attitude_group get_attitude_group( npc_attitude att )
{
    switch( att ) {
        case NPCATT_MUG:
        case NPCATT_WAIT_FOR_LEAVE:
        case NPCATT_KILL:
            return attitude_group::hostile;
        case NPCATT_FLEE:
            return attitude_group::fearful;
        case NPCATT_FOLLOW:
        case NPCATT_LEAD:
            return attitude_group::friendly;
        default:
            break;
    }
    return attitude_group::neutral;
}

npc_attitude npc::get_attitude() const
{
    return attitude;
}

void npc::set_attitude( npc_attitude new_attitude )
{
    if( new_attitude == attitude ) {
        return;
    }
    add_msg( m_debug, "%s changes attitude from %s to %s",
             name.c_str(), npc_attitude_name( attitude ).c_str(), npc_attitude_name( new_attitude ).c_str() );
    attitude_group new_group = get_attitude_group( new_attitude );
    attitude_group old_group = get_attitude_group( attitude );
    if( new_group != old_group && !is_fake() ) {
        switch( new_group ) {
            case attitude_group::hostile:
                add_msg_if_npc( m_bad, _( "<npcname> gets angry!" ) );
                break;
            case attitude_group::fearful:
                add_msg_if_npc( m_warning, _( "<npcname> gets scared!" ) );
                break;
            default:
                if( old_group == attitude_group::hostile ) {
                    add_msg_if_npc( m_good, _( "<npcname> calms down." ) );
                } else if( old_group == attitude_group::fearful ) {
                    add_msg_if_npc( _( "<npcname> is no longer afraid." ) );
                }
                break;
        }
    }
    attitude = new_attitude;
}
