From cc23abc9be97a577d9d7a0147b39fbda628a5728 Mon Sep 17 00:00:00 2001 From: Olanti Date: Sat, 17 Dec 2022 00:49:49 +0300 Subject: [PATCH] Character code reorganization 27-nov-2022 (#2208) * Move is_hallucination(), underwater check * Move dodge_roll() * Move reach_attack() * Get rid of melee_attack() overload, use default arg * Fix melee_value() checking wrong item * Move weapon_value() family, encapsulate cached_info * Remove dead code * Get rid of get_artifact_items() * Move can_autolearn() * Move is_dead_state() * Move power_bionics(), get rid of bionic indexes * Move power_mutations() and related definitions * Move and rename calc_fatigue_cap() * Move needs_rates * Move sleep-related definitions, fix wrong pos usages * Move avoid_trap() * Move vomit_mod() * Move update_body_wetness() * Move omt_path member from bionics section * Move can_interface_armor() * Move has_mission_item() * Move basic_symbol_color() * Move pause() and search_surroundings() * Move talk_skill() and intimidation() * Move is_stealthy() * Move stability_roll() * Break up character_functions.cpp * Move and merge turn processing functions * Move and rename weapname() * Move pain-related functions * Fix clang build * Remove unused static const variables --- src/activity_handlers.cpp | 2 +- src/avatar.cpp | 12 +- src/avatar.h | 2 + src/avatar_action.cpp | 5 +- src/avatar_functions.cpp | 152 +++ src/avatar_functions.h | 17 + src/bionics.cpp | 77 +- src/bionics.h | 2 +- src/bionics_ui.cpp | 47 +- src/bionics_ui.h | 9 + src/character.cpp | 419 ++++---- src/character.h | 161 +-- src/character_effects.cpp | 271 +++++ src/character_effects.h | 18 + src/character_functions.cpp | 354 ++++++- src/character_functions.h | 42 +- src/character_turn.cpp | 1109 ++++++++++++++++++++ src/character_turn.h | 25 + src/condition.cpp | 2 +- src/creature.cpp | 5 + src/creature.h | 3 +- src/game.cpp | 13 +- src/handle_action.cpp | 19 +- src/item.cpp | 6 +- src/iuse_actor.cpp | 4 +- src/martialarts.cpp | 18 +- src/martialarts.h | 5 +- src/melee.cpp | 52 +- src/monmove.cpp | 4 +- src/monster.cpp | 6 +- src/monster.h | 1 - src/mutation.cpp | 16 +- src/mutation_ui.cpp | 121 ++- src/mutation_ui.h | 28 + src/newcharacter.cpp | 2 +- src/npc.cpp | 10 +- src/npc.h | 14 +- src/npcmove.cpp | 68 +- src/npctalk.cpp | 21 +- src/panels.cpp | 17 +- src/player.cpp | 1667 +----------------------------- src/player.h | 139 --- src/player_activity.cpp | 3 +- src/player_hardcoded_effects.cpp | 18 +- src/ranged.cpp | 16 +- src/savegame_json.cpp | 10 +- src/suffer.cpp | 16 +- tests/mondefense_test.cpp | 3 +- tests/player_helpers.cpp | 18 +- tests/player_helpers.h | 2 +- 50 files changed, 2589 insertions(+), 2462 deletions(-) create mode 100644 src/avatar_functions.cpp create mode 100644 src/avatar_functions.h create mode 100644 src/bionics_ui.h create mode 100644 src/character_effects.cpp create mode 100644 src/character_turn.cpp create mode 100644 src/character_turn.h create mode 100644 src/mutation_ui.h diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 0ffebed06a6b..28102ca7f0c1 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -3485,7 +3485,7 @@ void activity_handlers::socialize_finish( player_activity *act, player *p ) void activity_handlers::try_sleep_do_turn( player_activity *act, player *p ) { if( !p->has_effect( effect_sleep ) ) { - if( p->can_sleep() ) { + if( character_funcs::roll_can_sleep( *p ) ) { act->set_to_null(); p->fall_asleep(); p->remove_value( "sleep_query" ); diff --git a/src/avatar.cpp b/src/avatar.cpp index eee352fd9dc0..0bcf2ad5e9a3 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -18,6 +18,7 @@ #include "cata_utility.h" #include "catacharset.h" #include "character.h" +#include "character_effects.h" #include "character_id.h" #include "character_functions.h" #include "character_martial_arts.h" @@ -992,11 +993,16 @@ void avatar::vomit() Character::vomit(); } +bool avatar::is_hallucination() const +{ + return false; +} + void avatar::disp_morale() { - int equilibrium = calc_focus_equilibrium(); + int equilibrium = character_effects::calc_focus_equilibrium( *this ); - int fatigue_cap = calc_fatigue_cap( this->get_fatigue() ); + int fatigue_cap = character_effects::calc_morale_fatigue_cap( this->get_fatigue() ); int pain_penalty = has_trait( trait_CENOBITE ) ? 0 : get_perceived_pain(); @@ -1203,7 +1209,7 @@ bool avatar::wield( item &target ) if( !unwield() ) { return false; } - cached_info.erase( "weapon_value" ); + clear_npc_ai_info_cache( "weapon_value" ); if( target.is_null() ) { return true; } diff --git a/src/avatar.h b/src/avatar.h index b884d3c0dbc0..b3d4cd001450 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -173,6 +173,8 @@ class avatar : public player */ void steal( npc &target ); + bool is_hallucination() const override; + pimpl translocators; int get_str_base() const override; diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 177d0ffbb336..17f39c6ea550 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -18,6 +18,7 @@ #include "calendar.h" #include "character.h" #include "character_martial_arts.h" +#include "character_turn.h" #include "creature.h" #include "cursesdef.h" #include "debug.h" @@ -573,7 +574,7 @@ static float rate_critter( const Creature &c ) { const npc *np = dynamic_cast( &c ); if( np != nullptr ) { - return np->weapon_value( np->weapon ); + return npc_ai::weapon_value( *np, np->weapon ); } const monster *m = dynamic_cast( &c ); @@ -593,7 +594,7 @@ void avatar_action::autoattack( avatar &you, map &m ) if( critters.empty() ) { add_msg( m_info, _( "No hostile creature in reach. Waiting a turn." ) ); if( g->check_safe_mode_allowed() ) { - you.pause(); + character_funcs::do_pause( you ); } return; } diff --git a/src/avatar_functions.cpp b/src/avatar_functions.cpp new file mode 100644 index 000000000000..6318efc237ac --- /dev/null +++ b/src/avatar_functions.cpp @@ -0,0 +1,152 @@ +#include "avatar_functions.h" + +#include "avatar.h" +#include "character_functions.h" +#include "field_type.h" +#include "map.h" +#include "mapdata.h" +#include "trap.h" +#include "vpart_position.h" +#include "vehicle.h" +#include "veh_type.h" + +static const trait_id trait_CHLOROMORPH( "CHLOROMORPH" ); +static const trait_id trait_M_SKIN3( "M_SKIN3" ); +static const trait_id trait_SHELL2( "SHELL2" ); +static const trait_id trait_THRESH_SPIDER( "THRESH_SPIDER" ); +static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); +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 bionic_id bio_soporific( "bio_soporific" ); + +namespace avatar_funcs +{ + +void try_to_sleep( avatar &you, const time_duration &dur ) +{ + map &here = get_map(); + const optional_vpart_position vp = here.veh_at( you.pos() ); + const trap &trap_at_pos = here.tr_at( you.pos() ); + const ter_id ter_at_pos = here.ter( you.pos() ); + const furn_id furn_at_pos = here.furn( you.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( you.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 ) { + you.add_msg_if_player( m_good, _( "You relax as your roots embrace the soil." ) ); + } else if( vp ) { + you.add_msg_if_player( m_bad, _( "It's impossible to sleep in this wheeled pot!" ) ); + } else if( furn_at_pos != f_null ) { + you.add_msg_if_player( m_bad, + _( "The humans' furniture blocks your roots. You can't get comfortable." ) ); + } else { // Floor problems + you.add_msg_if_player( m_bad, _( "Your roots scrabble ineffectively at the unyielding surface." ) ); + } + } else if( you.has_trait( trait_M_SKIN3 ) ) { + fungaloid_cosplay = true; + if( here.has_flag_ter_or_furn( "FUNGUS", you.pos() ) ) { + you.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( you.has_trait( trait_WEB_WALKER ) ) { + websleep = true; + } + // Not sure how one would get Arachnid w/o web-making, but Just In Case + if( you.has_trait( trait_THRESH_SPIDER ) && ( you.has_trait( trait_WEB_SPINNER ) || + ( you.has_trait( trait_WEB_WEAVER ) ) ) ) { + webforce = true; + } + if( websleep || webforce ) { + int web = here.get_field_intensity( you.pos(), fd_web ); + if( !webforce ) { + // At this point, it's kinda weird, but surprisingly comfy... + if( web >= 3 ) { + you.add_msg_if_player( m_good, + _( "These thick webs support your weight, and are strangely comfortable�" ) ); + websleeping = true; + } else if( web > 0 ) { + you.add_msg_if_player( m_info, + _( "You try to sleep, but the webs get in the way. You brush them aside." ) ); + here.remove_field( you.pos(), fd_web ); + } + } else { + // Here, you're just not comfortable outside a nice thick web. + if( web >= 3 ) { + you.add_msg_if_player( m_good, _( "You relax into your web." ) ); + websleeping = true; + } else { + you.add_msg_if_player( m_bad, + _( "You try to sleep, but you feel exposed and your spinnerets keep twitching." ) ); + you.add_msg_if_player( m_info, _( "Maybe a nice thick web would help you sleep." ) ); + } + } + } + if( you.has_active_mutation( trait_SHELL2 ) ) { + // Your shell's interior is a comfortable place to sleep. + in_shell = true; + } + if( you.has_trait( trait_WATERSLEEP ) ) { + if( you.is_underwater() ) { + you.add_msg_if_player( m_good, + _( "You lay beneath the waves' embrace, gazing up through the water's surface�" ) ); + watersleep = true; + } else if( here.has_flag_ter( "SWIMMABLE", you.pos() ) ) { + you.add_msg_if_player( m_good, _( "You settle into the water and begin to drowse�" ) ); + watersleep = true; + } + } + constexpr int confort_level_neutral = static_cast( character_funcs::comfort_level::neutral ); + if( !plantsleep && ( furn_at_pos.obj().comfort > confort_level_neutral || + ter_at_pos == t_improvised_shelter || + trap_at_pos.comfort > confort_level_neutral || + in_shell || websleeping || watersleep || + vp.part_with_feature( "SEAT", true ) || + vp.part_with_feature( "BED", true ) ) ) { + you.add_msg_if_player( m_good, _( "This is a comfortable place to sleep." ) ); + } else if( !plantsleep && !fungaloid_cosplay && !watersleep ) { + if( !vp && ter_at_pos != t_floor ) { + you.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() ); + } else if( vp ) { + if( vp->part_with_feature( VPFLAG_AISLE, true ) ) { + you.add_msg_if_player( + //~ %1$s: vehicle name, %2$s: vehicle part name + _( "It's a little hard to get to sleep on this %2$s in %1$s." ), + vp->vehicle().disp_name(), + vp->part_with_feature( VPFLAG_AISLE, true )->part().name( false ) ); + } else { + you.add_msg_if_player( + //~ %1$s: vehicle name + _( "It's hard to get to sleep in %1$s." ), + vp->vehicle().disp_name() ); + } + } + } + you.add_msg_if_player( _( "You start trying to fall asleep." ) ); + if( you.has_active_bionic( bio_soporific ) ) { + you.bio_soporific_powered_at_last_sleep_check = you.has_power(); + if( you.bio_soporific_powered_at_last_sleep_check ) { + // The actual bonus is applied in sleep_spot( p ). + you.add_msg_if_player( m_good, _( "Your soporific inducer starts working its magic." ) ); + } else { + you.add_msg_if_player( m_bad, _( "Your soporific inducer doesn't have enough power to operate." ) ); + } + } + you.assign_activity( activity_id( "ACT_TRY_SLEEP" ), to_moves( dur ) ); +} + +} // namespace avatar_funcs diff --git a/src/avatar_functions.h b/src/avatar_functions.h new file mode 100644 index 000000000000..24d6304bf2c3 --- /dev/null +++ b/src/avatar_functions.h @@ -0,0 +1,17 @@ +#pragma once +#ifndef CATA_SRC_AVATAR_FUNCTIONS_H +#define CATA_SRC_AVATAR_FUNCTIONS_H + +#include "calendar.h" + +class avatar; + +namespace avatar_funcs +{ + +/** Handles sleep attempts by the player, starts ACT_TRY_SLEEP activity */ +void try_to_sleep( avatar &you, const time_duration &dur = 30_minutes ); + +} // namespace avatar_funcs + +#endif // CATA_SRC_AVATAR_FUNCTIONS_H diff --git a/src/bionics.cpp b/src/bionics.cpp index 3dcdc70e9e36..22afb0f13c98 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -501,7 +501,8 @@ void npc::check_or_use_weapon_cbm( const bionic_id &cbm_id ) } const int cbm_ammo = free_power / bio.info().power_activate; - if( weapon_value( weapon, ammo_count ) < weapon_value( cbm_weapon, cbm_ammo ) ) { + if( npc_ai::weapon_value( *this, weapon, ammo_count ) < + npc_ai::weapon_value( *this, cbm_weapon, cbm_ammo ) ) { real_weapon = weapon; weapon = cbm_weapon; cbm_weapon_index = index; @@ -527,9 +528,8 @@ void npc::check_or_use_weapon_cbm( const bionic_id &cbm_id ) // // Well, because like diseases, which are also in a Big Switch, bionics don't // share functions.... -bool Character::activate_bionic( int b, bool eff_only ) +bool Character::activate_bionic( bionic &bio, bool eff_only ) { - bionic &bio = ( *my_bionics )[b]; const bool mounted = is_mounted(); if( bio.incapacitated_time > 0_turns ) { add_msg_if_player( m_info, _( "Your %s is shorting out and can't be activated." ), @@ -552,7 +552,7 @@ bool Character::activate_bionic( int b, bool eff_only ) // HACK: burn_fuel() doesn't check for available fuel in remote source on start. // If CBM is successfully activated, the check will occur when it actually tries to draw power if( !bio.info().is_remote_fueled ) { - if( !burn_fuel( b, true ) ) { + if( !burn_fuel( bio, true ) ) { return false; } } @@ -1061,10 +1061,8 @@ bool Character::activate_bionic( int b, bool eff_only ) return true; } -bool Character::deactivate_bionic( int b, bool eff_only ) +bool Character::deactivate_bionic( bionic &bio, bool eff_only ) { - bionic &bio = ( *my_bionics )[b]; - if( bio.incapacitated_time > 0_turns ) { add_msg_if_player( m_info, _( "Your %s is shorting out and can't be deactivated." ), bio.info().name ); @@ -1151,9 +1149,8 @@ bool Character::deactivate_bionic( int b, bool eff_only ) return true; } -bool Character::burn_fuel( int b, bool start ) +bool Character::burn_fuel( bionic &bio, bool start ) { - bionic &bio = ( *my_bionics )[b]; if( ( bio.info().fuel_opts.empty() && !bio.info().is_remote_fueled ) || bio.is_this_fuel_powered( fuel_type_muscle ) ) { return true; @@ -1161,7 +1158,7 @@ bool Character::burn_fuel( int b, bool start ) const bool is_metabolism_powered = bio.is_this_fuel_powered( fuel_type_metabolism ); const bool is_cable_powered = bio.info().is_remote_fueled; std::vector fuel_available = get_fuel_available( bio.id ); - float effective_efficiency = get_effective_efficiency( b, bio.info().fuel_efficiency ); + float effective_efficiency = get_effective_efficiency( bio, bio.info().fuel_efficiency ); if( is_cable_powered ) { const itype_id remote_fuel = find_remote_fuel(); @@ -1178,7 +1175,7 @@ bool Character::burn_fuel( int b, bool start ) _( "'s %s runs out of fuel and turn off." ), bio.info().name ); bio.powered = false; - deactivate_bionic( b, true ); + deactivate_bionic( bio, true ); return false; } } @@ -1187,7 +1184,7 @@ bool Character::burn_fuel( int b, bool start ) add_msg_player_or_npc( m_bad, _( "Your %s does not have enough fuel to start." ), _( "'s %s does not have enough fuel to start." ), bio.info().name ); - deactivate_bionic( b ); + deactivate_bionic( bio ); return false; } // don't produce power on start to avoid instant recharge exploit by turning bionic ON/OFF @@ -1229,7 +1226,7 @@ bool Character::burn_fuel( int b, bool start ) } } bio.powered = false; - deactivate_bionic( b, true ); + deactivate_bionic( bio, true ); return false; } else { if( current_fuel_stock > 0 ) { @@ -1279,7 +1276,7 @@ bool Character::burn_fuel( int b, bool start ) mod_power_level( units::from_kilojoule( fuel_energy ) * effective_efficiency ); } - heat_emission( b, fuel_energy ); + heat_emission( bio, fuel_energy ); if( bio.info().power_gen_emission ) { here.emit_field( pos(), bio.info().power_gen_emission ); } @@ -1299,7 +1296,7 @@ bool Character::burn_fuel( int b, bool start ) } bio.powered = false; - deactivate_bionic( b, true ); + deactivate_bionic( bio, true ); return false; } } @@ -1308,15 +1305,14 @@ bool Character::burn_fuel( int b, bool start ) return true; } -void Character::passive_power_gen( int b ) +void Character::passive_power_gen( bionic &bio ) { - const bionic &bio = ( *my_bionics )[b]; const float passive_fuel_efficiency = bio.info().passive_fuel_efficiency; if( bio.info().fuel_opts.empty() || bio.is_this_fuel_powered( fuel_type_muscle ) || passive_fuel_efficiency == 0.0 ) { return; } - const float effective_passive_efficiency = get_effective_efficiency( b, passive_fuel_efficiency ); + const float effective_passive_efficiency = get_effective_efficiency( bio, passive_fuel_efficiency ); const std::vector &fuel_available = get_fuel_available( bio.id ); map &here = get_map(); @@ -1346,7 +1342,7 @@ void Character::passive_power_gen( int b ) mod_power_level( units::from_kilojoule( fuel_energy ) * effective_passive_efficiency ); } - heat_emission( b, fuel_energy ); + heat_emission( bio, fuel_energy ); if( bio.info().power_gen_emission ) { here.emit_field( pos(), bio.info().power_gen_emission ); } @@ -1450,9 +1446,8 @@ void Character::reset_remote_fuel() remove_value( "rem_battery" ); } -void Character::heat_emission( int b, int fuel_energy ) +void Character::heat_emission( bionic &bio, int fuel_energy ) { - const bionic &bio = ( *my_bionics )[b]; if( !bio.info().exothermic_power_gen ) { return; } @@ -1471,9 +1466,8 @@ void Character::heat_emission( int b, int fuel_energy ) } } -float Character::get_effective_efficiency( int b, float fuel_efficiency ) +float Character::get_effective_efficiency( bionic &bio, float fuel_efficiency ) { - const bionic &bio = ( *my_bionics )[b]; const cata::optional &coverage_penalty = bio.info().coverage_power_gen_penalty; float effective_efficiency = fuel_efficiency; if( coverage_penalty ) { @@ -1523,9 +1517,8 @@ static bool attempt_recharge( Character &p, bionic &bio, units::energy &amount, return recharged; } -void Character::process_bionic( int b ) +void Character::process_bionic( bionic &bio ) { - bionic &bio = ( *my_bionics )[b]; if( ( !bio.id->fuel_opts.empty() || bio.id->is_remote_fueled ) && bio.is_auto_start_on() ) { const float start_threshold = bio.get_auto_start_thresh(); std::vector fuel_available = get_fuel_available( bio.id ); @@ -1542,7 +1535,7 @@ void Character::process_bionic( int b ) } } if( !fuel_available.empty() && get_power_level() <= start_threshold * get_max_power_level() ) { - g->u.activate_bionic( b ); + g->u.activate_bionic( bio ); } else if( get_power_level() <= start_threshold * get_max_power_level() && calendar::once_every( 1_hours ) ) { add_msg_player_or_npc( m_bad, _( "Your %s does not have enough fuel to use Auto Start." ), @@ -1553,7 +1546,7 @@ void Character::process_bionic( int b ) // Only powered bionics should be processed if( !bio.powered ) { - passive_power_gen( b ); + passive_power_gen( bio ); return; } @@ -1567,7 +1560,7 @@ void Character::process_bionic( int b ) if( bio.info().charge_time > 0 ) { if( bio.info().has_flag( STATIC( flag_str_id( "BIONIC_POWER_SOURCE" ) ) ) ) { // Convert fuel to bionic power - burn_fuel( b ); + burn_fuel( bio ); // Reset timer bio.charge_timer = bio.info().charge_time; } else { @@ -1579,7 +1572,7 @@ void Character::process_bionic( int b ) bio.powered = false; add_msg_if_player( m_neutral, _( "Your %s powers down." ), bio.info().name ); // This purposely bypasses the deactivation cost - deactivate_bionic( b, true ); + deactivate_bionic( bio, true ); return; } if( cost > 0_J ) { @@ -1608,12 +1601,12 @@ void Character::process_bionic( int b ) if( get_power_level() < bio.info().power_over_time ) { bio.powered = false; add_msg_if_player( m_warning, _( "Your %s shut down due to lack of power." ), bio.info().name ); - deactivate_bionic( b ); + deactivate_bionic( bio ); return; } else if( get_stored_kcal() < 0.85f * max_stored_kcal() ) { bio.powered = false; add_msg_if_player( m_warning, _( "Your %s shut down to conserve calories." ), bio.info().name ); - deactivate_bionic( b ); + deactivate_bionic( bio ); return; } if( calendar::once_every( 15_turns ) ) { @@ -1685,7 +1678,7 @@ void Character::process_bionic( int b ) add_msg_if_player( m_bad, _( "There is not enough humidity in the air for your %s to function." ), bio.info().name ); - deactivate_bionic( b ); + deactivate_bionic( bio ); } else if( water_available == 1 ) { add_msg_if_player( m_mixed, _( "Your %s issues a low humidity warning. Efficiency is reduced." ), @@ -1699,7 +1692,7 @@ void Character::process_bionic( int b ) add_msg_if_player( m_good, _( "You are properly hydrated. Your %s chirps happily." ), bio.info().name ); - deactivate_bionic( b ); + deactivate_bionic( bio ); } } else if( bio.id == bio_ads ) { if( bio.charge_timer < 2 ) { @@ -2012,10 +2005,9 @@ bool Character::uninstall_bionic( const bionic_id &b_id, player &installer, bool int chance_of_success = bionic_manip_cos( adjusted_skill, difficulty + 2 ); // Surgery is imminent, retract claws or blade if active - for( size_t i = 0; i < installer.my_bionics->size(); i++ ) { - const bionic &bio = ( *installer.my_bionics )[ i ]; + for( bionic &bio : *installer.my_bionics ) { if( bio.powered && bio.info().has_flag( flag_BIONIC_WEAPON ) ) { - installer.deactivate_bionic( i ); + installer.deactivate_bionic( bio ); } } @@ -2536,9 +2528,9 @@ void Character::add_bionic( const bionic_id &b ) return; } - my_bionics->push_back( bionic( b, get_free_invlet( *this->as_player() ) ) ); + my_bionics->push_back( bionic( b, get_free_invlet( *my_bionics ) ) ); if( b == bio_tools || b == bio_ears ) { - activate_bionic( my_bionics->size() - 1 ); + activate_bionic( my_bionics->back() ); } for( const bionic_id &inc_bid : b->included_bionics ) { @@ -2611,9 +2603,9 @@ void Character::remove_bionic( const bionic_id &b ) } } -int Character::num_bionics() const +bool Character::has_bionics() const { - return my_bionics->size(); + return !my_bionics->empty() || has_max_power(); } std::pair Character::amount_of_storage_bionics() const @@ -2649,11 +2641,6 @@ std::pair Character::amount_of_storage_bionics() const return results; } -bionic &Character::bionic_at_index( int i ) -{ - return ( *my_bionics )[i]; -} - void Character::clear_bionics() { my_bionics->clear(); diff --git a/src/bionics.h b/src/bionics.h index 6541fdd51c23..598297f258ca 100644 --- a/src/bionics.h +++ b/src/bionics.h @@ -201,7 +201,7 @@ class bionic_collection : public std::vector /**List of bodyparts occupied by a bionic*/ std::vector get_occupied_bodyparts( const bionic_id &bid ); -char get_free_invlet( player &p ); +char get_free_invlet( bionic_collection &bionics ); std::string list_occupied_bps( const bionic_id &bio_id, const std::string &intro, bool each_bp_on_new_line = true ); diff --git a/src/bionics_ui.cpp b/src/bionics_ui.cpp index c36d7a8e41de..e557554f0471 100644 --- a/src/bionics_ui.cpp +++ b/src/bionics_ui.cpp @@ -1,4 +1,4 @@ -#include "player.h" // IWYU pragma: associated +#include "bionics_ui.h" #include //std::min #include @@ -8,6 +8,7 @@ #include "bionics.h" #include "catacharset.h" #include "cata_utility.h" +#include "character.h" #include "enum_conversions.h" #include "flat_set.h" #include "game.h" @@ -158,14 +159,14 @@ std::string enum_to_string( bionic_ui_sort_mode mode ) } } // namespace io -bionic *player::bionic_by_invlet( const int ch ) +static bionic *bionic_by_invlet( bionic_collection &bionics, const int ch ) { // space is a special case for unassigned if( ch == ' ' ) { return nullptr; } - for( auto &elem : *my_bionics ) { + for( auto &elem : bionics ) { if( elem.invlet == ch ) { return &elem; } @@ -173,17 +174,17 @@ bionic *player::bionic_by_invlet( const int ch ) return nullptr; } -char get_free_invlet( player &p ) +char get_free_invlet( bionic_collection &bionics ) { for( auto &inv_char : bionic_chars ) { - if( p.bionic_by_invlet( inv_char ) == nullptr ) { + if( bionic_by_invlet( bionics, inv_char ) == nullptr ) { return inv_char; } } return ' '; } -static void draw_bionics_titlebar( const catacurses::window &window, player *p, +static void draw_bionics_titlebar( const catacurses::window &window, Character *p, bionic_menu_mode mode ) { input_context ctxt( "BIONICS" ); @@ -525,10 +526,11 @@ static nc_color get_bionic_text_color( const bionic &bio, const bool isHighlight return type; } -void player::power_bionics() +void show_bionics_ui( Character &who ) { - sorted_bionics passive = filtered_bionics( *my_bionics, TAB_PASSIVE ); - sorted_bionics active = filtered_bionics( *my_bionics, TAB_ACTIVE ); + bionic_collection &bionics = *who.my_bionics; + sorted_bionics passive = filtered_bionics( bionics, TAB_PASSIVE ); + sorted_bionics active = filtered_bionics( bionics, TAB_ACTIVE ); bionic *bio_last = nullptr; bionic_tab_mode tab_mode = TAB_ACTIVE; @@ -564,7 +566,7 @@ void player::power_bionics() HEIGHT = std::min( TERMY, std::max( FULL_SCREEN_HEIGHT, TITLE_HEIGHT + TITLE_TAB_HEIGHT + - static_cast( my_bionics->size() ) + 2 ) ); + static_cast( bionics.size() ) + 2 ) ); WIDTH = FULL_SCREEN_WIDTH + ( TERMX - FULL_SCREEN_WIDTH ) / 2; const point START( ( TERMX - WIDTH ) / 2, ( TERMY - HEIGHT ) / 2 ); //wBio is the entire bionic window @@ -634,11 +636,11 @@ void player::power_bionics() int max_width = 0; std::vector bps; std::map bp_to_pos; - for( const bodypart_id &bp : get_all_body_parts() ) { - const int total = get_total_bionics_slots( bp ); + for( const bodypart_id &bp : who.get_all_body_parts() ) { + const int total = who.get_total_bionics_slots( bp ); const std::string s = string_format( "%s: %d/%d", body_part_name_as_heading( bp->token, 1 ), - total - get_free_bionics_slots( bp ), total ); + total - who.get_free_bionics_slots( bp ), total ); bps.push_back( s ); bp_to_pos.emplace( bp.id(), bps.size() - 1 ); max_width = std::max( max_width, utf8_width( s ) ); @@ -694,7 +696,7 @@ void player::power_bionics() wnoutrefresh( wBio ); draw_bionics_tabs( w_tabs, active.size(), passive.size(), tab_mode ); - draw_bionics_titlebar( w_title, this, menu_mode ); + draw_bionics_titlebar( w_title, &who, menu_mode ); if( menu_mode == EXAMINING && !current_bionic_list->empty() ) { draw_description( w_description, *( *current_bionic_list )[cursor] ); } @@ -756,7 +758,7 @@ void player::power_bionics() auto &bio_list = tab_mode == TAB_ACTIVE ? active : passive; tmp = bio_list[cursor]; } else { - tmp = bionic_by_invlet( ch ); + tmp = bionic_by_invlet( bionics, ch ); } if( tmp == nullptr ) { @@ -777,7 +779,7 @@ void player::power_bionics() bionic_chars.get_allowed_chars() ); continue; } - bionic *otmp = bionic_by_invlet( newch ); + bionic *otmp = bionic_by_invlet( bionics, newch ); if( otmp != nullptr ) { std::swap( tmp->invlet, otmp->invlet ); } else { @@ -830,14 +832,14 @@ void player::power_bionics() } else if( action == "SORT" ) { uistate.bionic_sort_mode = pick_sort_mode(); // FIXME: is there a better way to resort? - active = filtered_bionics( *my_bionics, TAB_ACTIVE ); - passive = filtered_bionics( *my_bionics, TAB_PASSIVE ); + active = filtered_bionics( bionics, TAB_ACTIVE ); + passive = filtered_bionics( bionics, TAB_PASSIVE ); } else if( action == "CONFIRM" || action == "ANY_INPUT" ) { auto &bio_list = tab_mode == TAB_ACTIVE ? active : passive; if( action == "CONFIRM" && !current_bionic_list->empty() ) { tmp = bio_list[cursor]; } else { - tmp = bionic_by_invlet( ch ); + tmp = bionic_by_invlet( bionics, ch ); if( tmp && tmp != bio_last ) { // new bionic selected, update cursor and scroll position int temp_cursor = 0; @@ -868,13 +870,12 @@ void player::power_bionics() const bionic_data &bio_data = bio_id.obj(); if( menu_mode == ACTIVATING ) { if( bio_data.activated ) { - int b = tmp - &( *my_bionics )[0]; hide = true; ui.mark_resize(); if( tmp->powered ) { - deactivate_bionic( b ); + who.deactivate_bionic( *tmp ); } else { - activate_bionic( b ); + who.activate_bionic( *tmp ); // Clear the menu if we are firing a bionic gun if( tmp->info().has_flag( STATIC( flag_str_id( "BIONIC_GUN" ) ) ) || tmp->ammo_count > 0 ) { break; @@ -883,7 +884,7 @@ void player::power_bionics() hide = false; ui.mark_resize(); g->invalidate_main_ui_adaptor(); - if( moves < 0 ) { + if( who.moves < 0 ) { return; } continue; diff --git a/src/bionics_ui.h b/src/bionics_ui.h new file mode 100644 index 000000000000..6539302047fb --- /dev/null +++ b/src/bionics_ui.h @@ -0,0 +1,9 @@ +#pragma once +#ifndef CATA_SRC_BIONICS_UI_H +#define CATA_SRC_BIONICS_UI_H + +class Character; + +void show_bionics_ui( Character &who ); + +#endif // CATA_SRC_BIONICS_UI_H diff --git a/src/character.cpp b/src/character.cpp index dc35c954d7c6..8aadd44ebe79 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -20,6 +20,7 @@ #include "bionics.h" #include "cata_utility.h" #include "catacharset.h" +#include "character_functions.h" #include "character_martial_arts.h" #include "character_stat.h" #include "clzones.h" @@ -171,6 +172,7 @@ static const efftype_id effect_riding( "riding" ); static const efftype_id effect_saddled( "monster_saddled" ); static const efftype_id effect_sleep( "sleep" ); static const efftype_id effect_slept_through_alarm( "slept_through_alarm" ); +static const efftype_id effect_stunned( "stunned" ); static const efftype_id effect_tied( "tied" ); static const efftype_id effect_took_prozac( "took_prozac" ); static const efftype_id effect_took_xanax( "took_xanax" ); @@ -219,6 +221,7 @@ static const trait_id trait_WOOLALLERGY( "WOOLALLERGY" ); static const bionic_id bio_ads( "bio_ads" ); static const bionic_id bio_blindfold( "bio_blindfold" ); static const bionic_id bio_climate( "bio_climate" ); +static const bionic_id bio_cloak( "bio_cloak" ); static const bionic_id bio_earplugs( "bio_earplugs" ); static const bionic_id bio_ears( "bio_ears" ); static const bionic_id bio_faraday( "bio_faraday" ); @@ -226,7 +229,6 @@ static const bionic_id bio_flashlight( "bio_flashlight" ); 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_hydraulics( "bio_hydraulics" ); static const bionic_id bio_infrared( "bio_infrared" ); static const bionic_id bio_jointservo( "bio_jointservo" ); static const bionic_id bio_laser( "bio_laser" ); @@ -250,7 +252,6 @@ static const bionic_id afs_bio_linguistic_coprocessor( "afs_bio_linguistic_copro static const trait_id trait_BARK( "BARK" ); static const trait_id trait_BIRD_EYE( "BIRD_EYE" ); static const trait_id trait_CEPH_EYES( "CEPH_EYES" ); -static const trait_id trait_CHLOROMORPH( "CHLOROMORPH" ); static const trait_id trait_DEAF( "DEAF" ); static const trait_id trait_DEBUG_CLOAK( "DEBUG_CLOAK" ); static const trait_id trait_DEBUG_LS( "DEBUG_LS" ); @@ -261,8 +262,8 @@ static const trait_id trait_DISORGANIZED( "DISORGANIZED" ); static const trait_id trait_DOWN( "DOWN" ); static const trait_id trait_ELECTRORECEPTORS( "ELECTRORECEPTORS" ); static const trait_id trait_FASTLEARNER( "FASTLEARNER" ); -static const trait_id trait_GILLS( "GILLS" ); static const trait_id trait_GILLS_CEPH( "GILLS_CEPH" ); +static const trait_id trait_GILLS( "GILLS" ); static const trait_id trait_HEAVYSLEEPER( "HEAVYSLEEPER" ); static const trait_id trait_HEAVYSLEEPER2( "HEAVYSLEEPER2" ); static const trait_id trait_HIBERNATE( "HIBERNATE" ); @@ -278,6 +279,9 @@ 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_MEMBRANE( "MEMBRANE" ); +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_MYOPIC( "MYOPIC" ); static const trait_id trait_NO_THIRST( "NO_THIRST" ); static const trait_id trait_NOMAD( "NOMAD" ); @@ -286,10 +290,12 @@ static const trait_id trait_NOMAD3( "NOMAD3" ); static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_PACKMULE( "PACKMULE" ); static const trait_id trait_PADDED_FEET( "PADDED_FEET" ); -static const trait_id trait_PAWS( "PAWS" ); +static const trait_id trait_PAINRESIST_TROGLO( "PAINRESIST_TROGLO" ); +static const trait_id trait_PAINRESIST( "PAINRESIST" ); static const trait_id trait_PAWS_LARGE( "PAWS_LARGE" ); -static const trait_id trait_PER_SLIME( "PER_SLIME" ); +static const trait_id trait_PAWS( "PAWS" ); static const trait_id trait_PER_SLIME_OK( "PER_SLIME_OK" ); +static const trait_id trait_PER_SLIME( "PER_SLIME" ); static const trait_id trait_PROF_FOODP( "PROF_FOODP" ); static const trait_id trait_PYROMANIA( "PYROMANIA" ); static const trait_id trait_RADIOGENIC( "RADIOGENIC" ); @@ -312,11 +318,7 @@ static const trait_id trait_THRESH_SPIDER( "THRESH_SPIDER" ); static const trait_id trait_TRANSPIRATION( "TRANSPIRATION" ); static const trait_id trait_URSINE_EYE( "URSINE_EYE" ); static const trait_id trait_VISCOUS( "VISCOUS" ); -static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); 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 std::string flag_ACTIVE_CLOAKING( "ACTIVE_CLOAKING" ); static const std::string flag_ALLOWS_NATURAL_ATTACKS( "ALLOWS_NATURAL_ATTACKS" ); @@ -353,7 +355,6 @@ static const std::string flag_SPEEDLOADER( "SPEEDLOADER" ); static const std::string flag_SPLINT( "SPLINT" ); static const std::string flag_STR_DRAW( "STR_DRAW" ); static const std::string flag_STURDY( "STURDY" ); -static const std::string flag_SWIMMABLE( "SWIMMABLE" ); static const std::string flag_SWIM_GOGGLES( "SWIM_GOGGLES" ); static const std::string flag_UNDERSIZE( "UNDERSIZE" ); static const std::string flag_USE_UPS( "USE_UPS" ); @@ -479,6 +480,12 @@ character_id Character::getID() const return this->id; } +bool Character::is_dead_state() const +{ + return get_part_hp_cur( bodypart_id( "head" ) ) <= 0 || + get_part_hp_cur( bodypart_id( "torso" ) ) <= 0; +} + field_type_id Character::bloodType() const { if( has_trait( trait_ACIDBLOOD ) ) { @@ -822,7 +829,7 @@ bool Character::sight_impaired() const return ( ( ( has_effect( effect_boomered ) || has_effect( effect_no_sight ) || has_effect( effect_darkness ) ) && ( !( has_trait( trait_PER_SLIME_OK ) ) ) ) || - ( underwater && !has_bionic( bio_membrane ) && !has_trait( trait_MEMBRANE ) && + ( is_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 ) ) && @@ -874,6 +881,54 @@ void Character::react_to_felt_pain( int intensity ) } } +void Character::mod_pain( int npain ) +{ + if( npain > 0 ) { + if( has_trait( trait_NOPAIN ) || has_effect( effect_narcosis ) ) { + 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 Character::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 Character::get_perceived_pain() const +{ + if( get_effect_int( effect_adrenaline ) > 1 ) { + return 0; + } + + return std::max( get_pain() - get_painkiller(), 0 ); +} + void Character::action_taken() { nv_cached = false; @@ -940,7 +995,7 @@ int Character::swim_speed() const ret = std::max( ret, 200 ); } // If (ret > 500), we can not swim; so do not apply the underwater bonus. - if( underwater && ret < 500 ) { + if( is_underwater() && ret < 500 ) { ret -= 50; } @@ -1639,20 +1694,6 @@ void Character::expose_to_disease( const diseasetype_id dis_type ) } } -void Character::process_turn() -{ - for( bionic &i : *my_bionics ) { - if( i.incapacitated_time > 0_turns ) { - i.incapacitated_time -= 1_turns; - if( i.incapacitated_time == 0_turns ) { - add_msg_if_player( m_bad, _( "Your %s bionic comes back online." ), i.info().name ); - } - } - } - - Creature::process_turn(); -} - void Character::recalc_hp() { int str_boost_val = 0; @@ -1713,7 +1754,7 @@ void Character::recalc_sight_limits() sight_max = 1; vision_mode_cache.set( BOOMERED ); } else if( has_effect( effect_in_pit ) || has_effect( effect_no_sight ) || - ( underwater && !has_bionic( bio_membrane ) && + ( is_underwater() && !has_bionic( bio_membrane ) && !has_trait( trait_MEMBRANE ) && !worn_with_flag( flag_SWIM_GOGGLES ) && !has_trait( trait_CEPH_EYES ) && !has_trait( trait_PER_SLIME_OK ) ) ) { sight_max = 1; @@ -1874,6 +1915,17 @@ std::vector Character::get_bionics() const return result; } +bionic &Character::get_bionic_state( const bionic_id &id ) +{ + for( bionic &b : *my_bionics ) { + if( id == b.id ) { + return b; + } + } + debugmsg( "tried to get state of non-existent bionic with id \"%s\"", id ); + std::abort(); +} + bool Character::has_bionic( const bionic_id &b ) const { for( const bionic_id &bid : get_bionics() ) { @@ -2340,7 +2392,7 @@ item &Character::i_add( item it, bool should_stack ) } auto &item_in_inv = inv.add_item( it, keep_invlet, true, should_stack ); item_in_inv.on_pickup( *this ); - cached_info.erase( "reloadables" ); + clear_npc_ai_info_cache( "reloadables" ); return item_in_inv; } @@ -2569,10 +2621,15 @@ item Character::remove_weapon() { item tmp = weapon; weapon = item(); - cached_info.erase( "weapon_value" ); + clear_npc_ai_info_cache( "weapon_value" ); return tmp; } +bool Character::has_mission_item( int mission_id ) const +{ + return mission_id != -1 && has_item_with( has_mission_item_filter{ mission_id } ); +} + void Character::remove_mission_items( int mission_id ) { if( mission_id == -1 ) { @@ -3303,7 +3360,7 @@ std::vector Character::get_overlay_ids() const } // then get mutations - for( const std::pair &mut : my_mutations ) { + for( const std::pair &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( order, overlay_id ) ); @@ -3574,51 +3631,6 @@ void Character::do_skill_rust() } } -void Character::reset_stats() -{ - // Bionic buffs - if( has_active_bionic( bio_hydraulics ) ) { - mod_str_bonus( 20 ); - } - - mod_str_bonus( get_mod_stat_from_bionic( character_stat::STRENGTH ) ); - mod_dex_bonus( get_mod_stat_from_bionic( character_stat::DEXTERITY ) ); - mod_per_bonus( get_mod_stat_from_bionic( character_stat::PERCEPTION ) ); - mod_int_bonus( get_mod_stat_from_bionic( character_stat::INTELLIGENCE ) ); - - // Trait / mutation buffs - mod_str_bonus( std::floor( mutation_value( "str_modifier" ) ) ); - mod_dodge_bonus( std::floor( mutation_value( "dodge_modifier" ) ) ); - - apply_skill_boost(); - - nv_cached = false; - - // Reset our stats to normal levels - // Any persistent buffs/debuffs will take place in effects, - // player::suffer(), etc. - - // repopulate the stat fields - str_cur = str_max + get_str_bonus(); - dex_cur = dex_max + get_dex_bonus(); - per_cur = per_max + get_per_bonus(); - int_cur = int_max + get_int_bonus(); - - // Floor for our stats. No stat changes should occur after this! - if( dex_cur < 0 ) { - dex_cur = 0; - } - if( str_cur < 0 ) { - str_cur = 0; - } - if( per_cur < 0 ) { - per_cur = 0; - } - if( int_cur < 0 ) { - int_cur = 0; - } -} - void Character::reset() { recalculate_enchantment_cache(); @@ -4834,14 +4846,15 @@ void Character::update_needs( int rate_multiplier ) rest_modifier += 0.2f; } - const comfort_level comfort = base_comfort_value( pos() ).level; + const character_funcs::comfort_level comfort = + character_funcs::base_comfort_value( *this, pos() ).level; // Best possible bed increases recovery by 30% of base - if( comfort >= comfort_level::very_comfortable ) { + if( comfort >= character_funcs::comfort_level::very_comfortable ) { rest_modifier += 0.3f; - } else if( comfort >= comfort_level::comfortable ) { + } else if( comfort >= character_funcs::comfort_level::comfortable ) { rest_modifier += 0.2f; - } else if( comfort >= comfort_level::slightly_comfortable ) { + } else if( comfort >= character_funcs::comfort_level::slightly_comfortable ) { rest_modifier += 0.1f; } @@ -5684,137 +5697,6 @@ void Character::temp_equalizer( const bodypart_id &bp1, const bodypart_id &bp2 ) temp_cur[bp2->token] -= diff; } -Character::comfort_response_t Character::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; - - comfort_response_t comfort_response; - - 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 ); - - map &here = get_map(); - const optional_vpart_position vp = here.veh_at( p ); - const maptile tile = here.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 = here.get_field_intensity( p, fd_web ); - - // Some mutants have different comfort needs - if( !plantsleep && !webforce ) { - if( in_shell ) { - comfort += 1 + static_cast( comfort_level::slightly_comfortable ); - // Note: shelled individuals can still use sleeping aids! - } else if( vp ) { - const cata::optional carg = vp.part_with_feature( "CARGO", false ); - const cata::optional board = vp.part_with_feature( "BOARDABLE", true ); - if( carg ) { - const vehicle_stack items = vp->vehicle().get_items( carg->part_index() ); - for( const item &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( comfort_level::slightly_comfortable ); - comfort_response.aid = &items_it; - break; // prevents using more than 1 sleep aid - } - } - } - if( board ) { - comfort += board->info().comfort; - } else { - comfort -= here.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; - } - // Web sleepers can use their webs if better furniture isn't available - else if( websleep && web >= 3 ) { - comfort += 1 + static_cast( comfort_level::slightly_comfortable ); - } else if( ter_at_pos == t_improvised_shelter ) { - comfort += 0 + static_cast( 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( comfort_level::neutral ); - } else if( !trap_at_pos.is_null() ) { - comfort += 0 + trap_at_pos.comfort; - } else { - // Not a comfortable sleeping spot - comfort -= here.move_cost( p ); - } - - if( comfort_response.aid == nullptr ) { - const map_stack items = here.i_at( p ); - for( const item &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( comfort_level::slightly_comfortable ); - comfort_response.aid = &items_it; - break; // prevents using more than 1 sleep aid - } - } - } - if( fungaloid_cosplay && here.has_flag_ter_or_furn( flag_FUNGUS, pos() ) ) { - comfort += static_cast( comfort_level::very_comfortable ); - } else if( watersleep && here.has_flag_ter( flag_SWIMMABLE, pos() ) ) { - comfort += static_cast( 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( comfort_level::impossible ); - } 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( 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( comfort_level::comfortable ); - } - // Sleep ain't happening - else { - comfort = static_cast( comfort_level::impossible ); - } - } - // Has webforce - } else { - if( web >= 3 ) { - // Thick Web and you're good to go - comfort += static_cast( comfort_level::very_comfortable ); - } else { - comfort = static_cast( comfort_level::impossible ); - } - } - - if( comfort > static_cast( comfort_level::comfortable ) ) { - comfort_response.level = comfort_level::very_comfortable; - } else if( comfort > static_cast( comfort_level::slightly_comfortable ) ) { - comfort_response.level = comfort_level::comfortable; - } else if( comfort > static_cast( comfort_level::neutral ) ) { - comfort_response.level = comfort_level::slightly_comfortable; - } else if( comfort == static_cast( comfort_level::neutral ) ) { - comfort_response.level = comfort_level::neutral; - } else { - comfort_response.level = comfort_level::uncomfortable; - } - return comfort_response; -} - int Character::blood_loss( const bodypart_id &bp ) const { int hp_cur_sum = get_part_hp_cur( bp ); @@ -6106,6 +5988,36 @@ std::vector Character::get_grammatical_genders() const } } +nc_color Character::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( is_underwater() ) { + return c_blue; + } + if( has_active_bionic( bio_cloak ) || has_artifact_with( AEP_INVISIBLE ) || + is_wearing_active_optcloak() || has_trait( trait_DEBUG_CLOAK ) ) { + return c_dark_gray; + } + if( move_mode == CMM_RUN ) { + return c_yellow; + } + if( move_mode == CMM_CROUCH ) { + return c_light_gray; + } + return c_white; +} + nc_color Character::symbol_color() const { nc_color basic = basic_symbol_color(); @@ -6390,7 +6302,7 @@ float Character::active_light() const lumination = static_cast( maxlum ); float mut_lum = 0.0f; - for( const std::pair &mut : my_mutations ) { + for( const std::pair &mut : my_mutations ) { if( mut.second.powered ) { float curr_lum = 0.0f; for( const auto elem : mut.first->lumination ) { @@ -7464,7 +7376,8 @@ bool Character::has_enough_charges( const item &it, bool show_msg ) const return true; } if( it.is_power_armor() ) { - if( ( as_player()->can_interface_armor() && has_charges( itype_bio_armor, it.ammo_required() ) ) || + if( ( character_funcs::can_interface_armor( *this ) && + has_charges( itype_bio_armor, it.ammo_required() ) ) || ( it.has_flag( flag_USE_UPS ) && has_charges( itype_UPS, it.ammo_required() ) ) || it.ammo_sufficient() ) { return true; @@ -7547,7 +7460,7 @@ bool Character::consume_charges( item &used, int qty ) if( used.is_power_armor() ) { if( used.charges >= qty ) { used.ammo_consume( qty, pos() ); - } else if( as_player()->can_interface_armor() && has_charges( itype_bio_armor, qty ) ) { + } else if( character_funcs::can_interface_armor( *this ) && has_charges( itype_bio_armor, qty ) ) { use_charges( itype_bio_armor, qty ); } else { use_charges( itype_UPS, qty ); @@ -7720,7 +7633,7 @@ int Character::get_shout_volume() const } // Screaming underwater is not good for oxygen and harder to do overall - if( underwater ) { + if( is_underwater() ) { noise = std::max( minimum_noise, noise / 2 ); } return noise; @@ -7770,7 +7683,7 @@ void Character::shout( std::string msg, bool order ) } // Screaming underwater is not good for oxygen and harder to do overall - if( underwater ) { + if( is_underwater() ) { if( !has_trait( trait_GILLS ) && !has_trait( trait_GILLS_CEPH ) ) { mod_stat( "oxygen", -noise ); } @@ -7998,7 +7911,7 @@ void Character::recalculate_enchantment_cache() } ); // get from traits/ mutations - for( const std::pair &mut_map : my_mutations ) { + for( const std::pair &mut_map : my_mutations ) { const mutation_branch &mut = mut_map.first.obj(); for( const enchantment_id &ench_id : mut.enchantments ) { @@ -8027,7 +7940,7 @@ void Character::recalculate_enchantment_cache() void Character::rebuild_mutation_cache() { cached_mutations.clear(); - for( const std::pair &mut : my_mutations ) { + for( const std::pair &mut : my_mutations ) { cached_mutations.push_back( &mut.first.obj() ); } for( const trait_id &mut : enchantment_cache->get_mutations() ) { @@ -8164,15 +8077,7 @@ void Character::absorb_hit( const bodypart_id &bp, damage_instance &dam ) if( has_active_bionic( bio_ads ) && ( elem.amount > 0 ) && ( elem.type == DT_BASH || elem.type == DT_CUT || elem.type == DT_STAB || elem.type == DT_BULLET ) ) { float elem_multi = 1; - // HACK: In the future this hopefully gets streamlined. - const auto &all_bionics = get_bionics(); - size_t index; - for( index = 0; index < all_bionics.size(); index++ ) { - if( all_bionics[index] == bio_ads ) { - break; - } - } - bionic &bio = bionic_at_index( index ); + bionic &bio = get_bionic_state( bio_ads ); // HACK: Halves charge rate when hit for the next 3 turns, doesn't stack. See bionics.cpp for more information. bio.charge_timer = 6; // Bullet affected significantly more than stab, stab more than cut, cut more than bash. @@ -8194,7 +8099,7 @@ void Character::absorb_hit( const bodypart_id &bp, damage_instance &dam ) // Either way you still get protection. dam.mult_damage( elem_multi ); bio.energy_stored = 0_kJ; - deactivate_bionic( index ); + deactivate_bionic( bio ); const units::energy shatter_thresh = ( elem.type == DT_BULLET ) ? 20_kJ : 15_kJ; if( ads_cost >= shatter_thresh ) { if( bio.incapacitated_time == 0_turns ) { @@ -8209,7 +8114,7 @@ void Character::absorb_hit( const bodypart_id &bp, damage_instance &dam ) } } else { //You tried to (re)activate it and immediately enter combat, no mitigation for you. - deactivate_bionic( index ); + deactivate_bionic( bio ); add_msg_if_player( m_bad, _( "The %s is interrupted and powers down." ), bio.info().name ); } } @@ -9920,7 +9825,7 @@ bool Character::has_opposite_trait( const trait_id &flag ) const return true; } } - for( const std::pair &mut : my_mutations ) { + for( const std::pair &mut : my_mutations ) { for( const trait_id &canceled_trait : mut.first->cancels ) { if( canceled_trait == flag ) { return true; @@ -10260,6 +10165,22 @@ void Character::add_known_trap( const tripoint &pos, const trap &t ) } } +bool Character::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() ); + } + + return myroll >= traproll; +} + bool Character::can_hear( const tripoint &source, const int volume ) const { if( is_deaf() ) { @@ -10599,3 +10520,43 @@ bool has_psy_protection( const Character &c, int partial_chance ) return c.has_artifact_with( AEP_PSYSHIELD ) || ( c.worn_with_flag( "PSYSHIELD_PARTIAL" ) && one_in( partial_chance ) ); } + +void Character::set_underwater( bool x ) +{ + if( is_underwater() != x ) { + Creature::set_underwater( x ); + recalc_sight_limits(); + } +} + +void Character::clear_npc_ai_info_cache( const std::string &key ) const +{ + npc_ai_info_cache.erase( key ); +} + +void Character::set_npc_ai_info_cache( const std::string &key, double val ) const +{ + npc_ai_info_cache[key] = val; +} + +cata::optional Character::get_npc_ai_info_cache( const std::string &key ) const +{ + auto it = npc_ai_info_cache.find( key ); + if( it == npc_ai_info_cache.end() ) { + return cata::nullopt; + } else { + return it->second; + } +} + +float Character::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 ); +} diff --git a/src/character.h b/src/character.h index 862664e98ea2..e49f15299153 100644 --- a/src/character.h +++ b/src/character.h @@ -57,6 +57,7 @@ class SkillLevel; class SkillLevelMap; class bionic_collection; class character_martial_arts; +class dispersion_sources; class faction; class ma_technique; class known_magic; @@ -74,7 +75,6 @@ struct dealt_projectile_attack; struct islot_comestible; struct itype; struct mutation_branch; -struct needs_rates; struct pathfinding_settings; struct points_left; struct trap; @@ -199,6 +199,30 @@ struct special_attack { damage_instance damage; }; +struct needs_rates { + float thirst = 0.0f; + float hunger = 0.0f; + float fatigue = 0.0f; + float recovery = 0.0f; +}; + +struct char_trait_data { + /** Whether the mutation is activated. */ + bool powered = false; + /** Key to select the mutation in the UI. */ + char key = ' '; + /** + * Time (in turns) until the mutation increase hunger/thirst/fatigue according + * to its cost (@ref mutation_branch::cost). When those costs have been paid, this + * is reset to @ref mutation_branch::cooldown. + */ + int charge = 0; + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); +}; + +struct mutation_collection : std::unordered_map {}; + class Character : public Creature, public visitable { public: @@ -218,6 +242,9 @@ class Character : public Creature, public visitable // allows forcing a -1 id which is required for templates to not throw errors void setID( character_id i, bool force = false ); + /** Returns true if the character should be dead */ + bool is_dead_state() const override; + field_type_id bloodType() const override; field_type_id gibType() const override; bool is_warm() const override; @@ -228,15 +255,6 @@ class Character : public Creature, public visitable bool controlling_vehicle = false; const std::string &symbol() const override; - enum class comfort_level { - impossible = -999, - uncomfortable = -7, - neutral = 0, - slightly_comfortable = 3, - comfortable = 5, - very_comfortable = 10 - }; - // Character stats // TODO: Make those protected int str_max = 0; @@ -336,6 +354,9 @@ class Character : public Creature, public visitable /** Recalculate size class of character **/ void recalculate_size(); + /** Calculates the various speed bonuses we will get from mutations, etc. */ + void recalc_speed_bonus(); + /** Returns either "you" or the player's name. capitalize_first assumes that the character's name is already upper case and uses it only for possessive "your" and "you" @@ -353,6 +374,12 @@ class Character : public Creature, public visitable /* Adjusts provided sight dispersion to account for player stats */ int effective_dispersion( int dispersion ) const; + /** + * Returns a weapon's modified dispersion value. + * @param obj Weapon to check dispersion on + */ + dispersion_sources get_weapon_dispersion( const item &obj ) const; + /* Accessors for aspects of aim speed. */ std::vector get_aim_types( const item &gun ) const; std::pair get_fastest_sight( const item &gun, double recoil ) const; @@ -377,6 +404,7 @@ class Character : public Creature, public visitable float get_dodge_base() const override; float get_hit_base() const override; float get_dodge() const override; + float dodge_roll() override; const tripoint &pos() const override; /** Returns the player's sight range */ @@ -447,13 +475,6 @@ class Character : public Creature, public visitable /** Equalizes heat between body parts */ void temp_equalizer( const bodypart_id &bp1, const bodypart_id &bp2 ); - struct comfort_response_t { - comfort_level level = comfort_level::neutral; - const item *aid = nullptr; - }; - /** Rate point's ability to serve as a bed. Only takes certain mutations into account, and not fatigue nor stimulants. */ - comfort_response_t base_comfort_value( const tripoint &p ) const; - /** Define blood loss (in percents) */ int blood_loss( const bodypart_id &bp ) const; @@ -464,6 +485,8 @@ class Character : public Creature, public visitable /** Handles stat and bonus reset. */ void reset() override; + void environmental_revert_effect(); + /** Recalculates encumbrance cache. */ void reset_encumbrance(); /** Returns ENC provided by armor, etc. */ @@ -521,6 +544,14 @@ class Character : public Creature, public visitable * Handles end-of-turn processing. */ void process_turn() override; + /** Processes human-specific effects of effects before calling Creature::process_effects(). */ + void process_effects_internal() override; + /** Handles the still hard-coded effects. */ + void hardcoded_effects( effect &it ); + /** Processes human-specific effects of an effect. */ + void process_one_effect( effect &it, bool is_new ) override; + /** Process active items */ + void process_items(); /** Recalculates HP after a change to max strength */ void recalc_hp(); @@ -567,6 +598,8 @@ class Character : public Creature, public visitable bool can_miss_recovery( const item &weap ) const; /** Returns true if the player has quiet melee attacks */ bool is_quiet() const; + /** Returns true if the player has stealthy movement */ + bool is_stealthy() const; // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ @@ -585,22 +618,25 @@ class Character : public Creature, public visitable * @param t Creature to attack * @param allow_special whether non-forced martial art technique or mutation attack should be * possible with this attack. - * @param force_technique special technique to use in attack. + * @param force_technique special technique to use in attack (leave as nullptr to use random technique). * @param allow_unarmed always uses the wielded weapon regardless of martialarts style */ - void melee_attack( Creature &t, bool allow_special, const matec_id &force_technique, + void melee_attack( Creature &t, bool allow_special, const matec_id *force_technique = nullptr, bool allow_unarmed = true ); - /** - * Calls the to other melee_attack function with an empty technique id (meaning no specific - * technique should be used). - */ - void melee_attack( Creature &t, bool allow_special ); + /** Handles combat effects, returns a string of any valid combat effect messages */ std::string melee_special_effects( Creature &t, damage_instance &d, item &weap ); /** Performs special attacks and their effects (poisonous, stinger, etc.) */ void perform_special_attacks( Creature &t, dealt_damage_instance &dealt_dam ); + + /** Handles reach melee attack on point p */ + void reach_attack( const tripoint &p ); + // HACK for mdefense::zapback bool reach_attacking = false; + /** Returns value of player's stable footing */ + float stability_roll() const override; + /** Returns a vector of valid mutation attacks */ std::vector mutation_attacks( Creature &t ) const; /** Returns the bonus bashing damage the player deals based on their stats */ @@ -686,8 +722,6 @@ class Character : public Creature, public visitable bool has_trait_flag( const std::string &b ) const; /** Returns true if character has a trait which cancels the entered trait. */ bool has_opposite_trait( const trait_id &flag ) const; - /** Returns the trait id with the given invlet, or an empty string if no trait has that invlet */ - trait_id trait_by_invlet( int ch ) const; /** Toggles a trait on the player and in their mutation list */ void toggle_trait( const trait_id & ); @@ -936,8 +970,10 @@ class Character : public Creature, public visitable // --------------- Bionic Stuff --------------- /** Handles bionic activation effects of the entered bionic, returns if anything activated */ - bool activate_bionic( int b, bool eff_only = false ); + bool activate_bionic( bionic &bio, bool eff_only = false ); std::vector get_bionics() const; + /** Get state of bionic with given id */ + bionic &get_bionic_state( const bionic_id &id ); /** Returns amount of Storage CBMs in the corpse **/ std::pair amount_of_storage_bionics() const; /** Returns true if the player has the entered bionic id */ @@ -966,18 +1002,13 @@ class Character : public Creature, public visitable void update_fuel_storage( const itype_id &fuel ); /**Get stat bonus from bionic*/ int get_mod_stat_from_bionic( const character_stat &Stat ) const; - // route for overmap-scale traveling - std::vector omt_path; - /** Handles bionic effects over time of the entered bionic */ - void process_bionic( int b ); + void process_bionic( bionic &bio ); /** Handles bionic deactivation effects of the entered bionic, returns if anything * deactivated */ - bool deactivate_bionic( int b, bool eff_only = false ); - /** Returns the size of my_bionics[] */ - int num_bionics() const; - /** Returns the bionic at a given index in my_bionics[] */ - bionic &bionic_at_index( int i ); + bool deactivate_bionic( bionic &bio, bool eff_only = false ); + /** Whether character has any bionics installed */ + bool has_bionics() const; /** Remove all bionics */ void clear_bionics(); int get_used_bionics_slots( const bodypart_id &bp ) const; @@ -1040,18 +1071,18 @@ class Character : public Creature, public visitable float adjusted_skill ); /**Convert fuel to bionic power*/ - bool burn_fuel( int b, bool start = false ); + bool burn_fuel( bionic &bio, bool start = false ); /**Passively produce power from PERPETUAL fuel*/ - void passive_power_gen( int b ); + void passive_power_gen( bionic &bio ); /**Find fuel used by remote powered bionic*/ itype_id find_remote_fuel( bool look_only = false ); /**Consume fuel used by remote powered bionic, return amount of request unfulfilled (0 if totally successful).*/ int consume_remote_fuel( int amount ); void reset_remote_fuel(); /**Handle heat from exothermic power generation*/ - void heat_emission( int b, int fuel_energy ); + void heat_emission( bionic &bio, int fuel_energy ); /**Applies modifier to fuel_efficiency and returns the resulting efficiency*/ - float get_effective_efficiency( int b, float fuel_efficiency ); + float get_effective_efficiency( bionic &bio, float fuel_efficiency ); units::energy get_power_level() const; units::energy get_max_power_level() const; @@ -1242,6 +1273,7 @@ class Character : public Creature, public visitable */ bool has_active_item( const itype_id &id ) const; item remove_weapon(); + bool has_mission_item( int mission_id ) const; void remove_mission_items( int mission_id ); /** @@ -1477,7 +1509,7 @@ class Character : public Creature, public visitable */ social_modifiers get_mutation_social_mods() const; - /** Color's character's tile's background */ + nc_color basic_symbol_color() const override; nc_color symbol_color() const override; std::string extended_description() const override; @@ -1733,6 +1765,13 @@ class Character : public Creature, public visitable int get_painkiller() const; void react_to_felt_pain( int intensity ); + /** Modifies a pain value by player traits before passing it to Creature::mod_pain() */ + void mod_pain( int npain ) override; + /** Sets new intensity of pain an reacts to it */ + void set_pain( int npain ) override; + /** Returns perceived pain (reduced with painkillers)*/ + int get_perceived_pain() const override; + void spores(); void blossoms(); @@ -1766,6 +1805,8 @@ class Character : public Creature, public visitable int run_cost( int base_cost, bool diag = false ) const; const pathfinding_settings &get_pathfinding_settings() const override; std::set get_path_avoid() const override; + /** Route for overmap scale traveling */ + std::vector omt_path; /** * Get all hostile creatures currently visible to this player. */ @@ -1994,6 +2035,10 @@ class Character : public Creature, public visitable using trap_map = std::map; bool knows_trap( const tripoint &pos ) const; void add_known_trap( const tripoint &pos, const trap &t ); + + /** Called when character triggers a trap, returns true if they don't set it off */ + bool avoid_trap( const tripoint &pos, const trap &tr ) const override; + /** Define color for displaying the body temperature */ nc_color bodytemp_color( int bp ) const; @@ -2030,20 +2075,6 @@ class Character : public Creature, public visitable Character(); Character( Character && ); Character &operator=( Character && ); - struct trait_data { - /** Whether the mutation is activated. */ - bool powered = false; - /** Key to select the mutation in the UI. */ - char key = ' '; - /** - * Time (in turns) until the mutation increase hunger/thirst/fatigue according - * to its cost (@ref mutation_branch::cost). When those costs have been paid, this - * is reset to @ref mutation_branch::cooldown. - */ - int charge = 0; - void serialize( JsonOut &json ) const; - void deserialize( JsonIn &jsin ); - }; // The player's position on the local map. tripoint position; @@ -2067,19 +2098,19 @@ class Character : public Creature, public visitable trap_map known_traps; pimpl encumbrance_cache; - mutable std::map cached_info; - bool bio_soporific_powered_at_last_sleep_check = false; - /** last time we checked for sleep */ - time_point last_sleep_check = calendar::turn_zero; /** warnings from a faction about bad behavior */ std::map> warning_record; + public: /** * Traits / mutations of the character. Key is the mutation id (it's also a valid * key into @ref mutation_data), the value describes the status of the mutation. * If there is not entry for a mutation, the character does not have it. If the map * contains the entry, the character has the mutation. */ - std::unordered_map my_mutations; + mutation_collection my_mutations; + time_point last_sleep_check = calendar::turn_zero; + bool bio_soporific_powered_at_last_sleep_check = false; + protected: /** * Contains mutation ids of the base traits. */ @@ -2141,7 +2172,7 @@ class Character : public Creature, public visitable private: /** suffer() subcalls */ void suffer_water_damage( const mutation_branch &mdata ); - void suffer_mutation_power( const mutation_branch &mdata, Character::trait_data &tdata ); + void suffer_mutation_power( const mutation_branch &mdata, char_trait_data &tdata ); void suffer_while_underwater(); void suffer_from_addictions(); void suffer_while_awake( int current_stim ); @@ -2197,6 +2228,8 @@ class Character : public Creature, public visitable tripoint cached_position; inventory cached_crafting_inventory; + mutable std::map npc_ai_info_cache; + protected: // a cache of all active enchantment values. // is recalculated every turn in Character::recalculate_enchantment_cache @@ -2213,6 +2246,12 @@ class Character : public Creature, public visitable time_point next_climate_control_check; bool last_climate_control_ret = false; + + void set_underwater( bool x ) override; + + void clear_npc_ai_info_cache( const std::string &key ) const; + void set_npc_ai_info_cache( const std::string &key, double val ) const; + cata::optional get_npc_ai_info_cache( const std::string &key ) const; }; Character &get_player_character(); diff --git a/src/character_effects.cpp b/src/character_effects.cpp new file mode 100644 index 000000000000..a59c44718304 --- /dev/null +++ b/src/character_effects.cpp @@ -0,0 +1,271 @@ +#include "character_effects.h" + +#include "bionics.h" +#include "calendar.h" +#include "character_martial_arts.h" +#include "character.h" +#include "creature.h" +#include "handle_liquid.h" +#include "itype.h" +#include "make_static.h" +#include "map_iterator.h" +#include "player.h" +#include "rng.h" +#include "skill.h" +#include "submap.h" +#include "trap.h" +#include "veh_type.h" +#include "vehicle.h" +#include "vpart_position.h" +#include "weather_gen.h" +#include "weather.h" + +static const activity_id ACT_READ( "ACT_READ" ); + +static const trait_id trait_CENOBITE( "CENOBITE" ); +static const trait_id trait_INT_SLIME( "INT_SLIME" ); +static const trait_id trait_NAUSEA( "NAUSEA" ); +static const trait_id trait_STRONGSTOMACH( "STRONGSTOMACH" ); +static const trait_id trait_VOMITOUS( "VOMITOUS" ); +static const trait_id trait_WEAKSTOMACH( "WEAKSTOMACH" ); + +static const efftype_id effect_drunk( "drunk" ); +static const efftype_id effect_nausea( "nausea" ); +static const efftype_id effect_weed_high( "weed_high" ); + + +namespace character_effects +{ + +stat_mod get_pain_penalty( const Character &ch ) +{ + stat_mod ret; + int pain = ch.get_perceived_pain(); + if( pain <= 0 ) { + return ret; + } + + int stat_penalty = std::floor( std::pow( pain, 0.8f ) / 10.0f ); + + bool ceno = ch.has_trait( trait_CENOBITE ); + if( !ceno ) { + ret.strength = stat_penalty; + ret.dexterity = stat_penalty; + } + + if( !ch.has_trait( trait_INT_SLIME ) ) { + ret.intelligence = stat_penalty; + } else { + ret.intelligence = 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, 30 ); + return ret; +} + +int get_kcal_speed_penalty( float kcal_percent ) +{ + static const std::vector> starv_thresholds = { { + std::make_pair( 0.0f, -90.0f ), + std::make_pair( 0.1f, -50.f ), + std::make_pair( 0.3f, -25.0f ), + std::make_pair( 0.5f, 0.0f ) + } + }; + if( kcal_percent > 0.95f ) { + return 0; + } else { + return std::round( multi_lerp( starv_thresholds, kcal_percent ) ); + } +} + +int get_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> thirst_thresholds = {{ + std::make_pair( static_cast( thirst_levels::very_thirsty ), 0.0f ), + std::make_pair( static_cast( thirst_levels::dehydrated ), -25.0f ), + std::make_pair( static_cast( thirst_levels::parched ), -50.0f ), + std::make_pair( static_cast( thirst_levels::dead ), -75.0f ) + } + }; + return static_cast( multi_lerp( thirst_thresholds, thirst ) ); +} + +int calc_morale_fatigue_cap( int fatigue ) +{ + if( fatigue >= fatigue_levels::massive ) { + return 20; + } else if( fatigue >= fatigue_levels::exhausted ) { + return 40; + } else if( fatigue >= fatigue_levels::dead_tired ) { + return 60; + } else if( fatigue >= fatigue_levels::tired ) { + return 80; + } + return 0; +} + +double vomit_mod( const Character &ch ) +{ + double mod = 1; + if( ch.has_effect( effect_weed_high ) ) { + mod *= .1; + } + if( ch.has_trait( trait_STRONGSTOMACH ) ) { + mod *= .5; + } + if( ch.has_trait( trait_WEAKSTOMACH ) ) { + mod *= 2; + } + if( ch.has_trait( trait_NAUSEA ) ) { + mod *= 3; + } + if( ch.has_trait( trait_VOMITOUS ) ) { + mod *= 3; + } + // If you're already nauseous, any food in your stomach greatly + // increases chance of vomiting. Water doesn't provoke vomiting, though. + if( ch.stomach.get_calories() > 0 && ch.has_effect( effect_nausea ) ) { + mod *= 5 * ch.get_effect_int( effect_nausea ); + } + return mod; +} + +int talk_skill( const Character &ch ) +{ + /** @EFFECT_INT slightly increases talking skill */ + + /** @EFFECT_PER slightly increases talking skill */ + + /** @EFFECT_SPEECH increases talking skill */ + int ret = ch.get_int() + ch.get_per() + ch.get_skill_level( skill_id( "speech" ) ) * 3; + return ret; +} + +int intimidation( const Character &ch ) +{ + /** @EFFECT_STR increases intimidation factor */ + int ret = ch.get_str() * 2; + if( ch.weapon.is_gun() ) { + ret += 10; + } + if( ch.weapon.damage_melee( DT_BASH ) >= 12 || + ch.weapon.damage_melee( DT_CUT ) >= 12 || + ch.weapon.damage_melee( DT_STAB ) >= 12 ) { + ret += 5; + } + + if( ch.get_stim() > 20 ) { + ret += 2; + } + if( ch.has_effect( effect_drunk ) ) { + ret -= 4; + } + + return ret; +} + +int calc_focus_equilibrium( const Character &who ) +{ + int focus_equilibrium = 100; + + if( who.activity.id() == ACT_READ ) { + item_location loc = who.activity.targets[0]; + if( loc && loc->is_book() ) { + auto &bt = *loc->type->book; + // apply a penalty when we're actually learning something + const SkillLevel &skill_level = who.get_skill_level_object( bt.skill ); + if( skill_level.can_train() && skill_level < bt.level ) { + focus_equilibrium -= 50; + } + } + } + + int eff_morale = who.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( !who.has_trait( trait_CENOBITE ) ) { + eff_morale = eff_morale - who.get_perceived_pain(); + } + + // as baseline morale is 100, calc_fatigue_cap() has to -100 to apply accurate penalties. + if( calc_morale_fatigue_cap( who.get_fatigue() ) != 0 && + eff_morale > calc_morale_fatigue_cap( who.get_fatigue() ) - 100 ) { + eff_morale = calc_morale_fatigue_cap( who.get_fatigue() ) - 100; + } + + if( eff_morale < -99 ) { + // At very low morale, focus is at it's minimum + focus_equilibrium = 1; + } else if( eff_morale <= 50 ) { + // At -99 to +50 morale, each point of morale gives or takes 1 point of focus + focus_equilibrium += 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 at 50 morale (as before) + * 200 focus at 150 morale (100 more morale) + * 250 focus 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_equilibrium < 400 ) { + if( morale_left > 50 * block_multiplier ) { + // We can afford the entire block. Get it and continue. + morale_left -= 50 * block_multiplier; + focus_equilibrium += 50; + block_multiplier *= 2; + } else { + // We can't afford the entire block. Each block_multiplier morale + // points give 1 focus, and then we're done. + focus_equilibrium += morale_left / block_multiplier; + break; + } + } + } + + // This should be redundant, but just in case... + if( focus_equilibrium < 1 ) { + focus_equilibrium = 1; + } else if( focus_equilibrium > 400 ) { + focus_equilibrium = 400; + } + return focus_equilibrium; +} + +int calc_focus_change( const Character &who ) +{ + int focus_gap = calc_focus_equilibrium( who ) - who.focus_pool; + + // handle negative gain rates in a symmetric manner + int base_change = 1; + if( focus_gap < 0 ) { + base_change = -1; + focus_gap = -focus_gap; + } + + // 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_gap / 100; + if( rng( 1, 100 ) <= focus_gap % 100 ) { + gain++; + } + + gain *= base_change; + + return gain; +} + +} // namespace character_effects diff --git a/src/character_effects.h b/src/character_effects.h index 789970993378..fb1a2b25502e 100644 --- a/src/character_effects.h +++ b/src/character_effects.h @@ -25,6 +25,24 @@ int get_kcal_speed_penalty( float kcal_percent ); /** Returns the penalty to speed from thirst */ int get_thirst_speed_penalty( int thirst ); +/** Calculates character's morale cap due to fatigue */ +int calc_morale_fatigue_cap( int fatigue ); + +/** Returns the modifier value used for vomiting effects */ +double vomit_mod( const Character &ch ); + +/** Returns a value used when attempting to convince NPC's of something */ +int talk_skill( const Character &ch ); + +/** Returns a value used when attempting to intimidate NPC's */ +int intimidation( const Character &ch ); + +/** Uses morale, pain and fatigue to return the player's focus target goto value */ +int calc_focus_equilibrium( const Character &who ); + +/** Calculates actual focus gain/loss value from focus equilibrium*/ +int calc_focus_change( const Character &who ); + } // namespace character_effects #endif // CATA_SRC_CHARACTER_EFFECTS_H diff --git a/src/character_functions.cpp b/src/character_functions.cpp index b2f98a16f13b..dc8be46b918f 100644 --- a/src/character_functions.cpp +++ b/src/character_functions.cpp @@ -1,25 +1,51 @@ #include "character_functions.h" -#include "character_effects.h" +#include "bionics.h" #include "calendar.h" +#include "character_martial_arts.h" #include "character.h" #include "creature.h" #include "handle_liquid.h" #include "itype.h" +#include "make_static.h" +#include "map_iterator.h" +#include "player.h" #include "rng.h" +#include "submap.h" +#include "trap.h" +#include "veh_type.h" #include "vehicle.h" +#include "vpart_position.h" +#include "weather_gen.h" +#include "weather.h" static const trait_id trait_CANNIBAL( "CANNIBAL" ); -static const trait_id trait_CENOBITE( "CENOBITE" ); -static const trait_id trait_INT_SLIME( "INT_SLIME" ); +static const trait_id trait_CHLOROMORPH( "CHLOROMORPH" ); +static const trait_id trait_EASYSLEEPER( "EASYSLEEPER" ); +static const trait_id trait_EASYSLEEPER2( "EASYSLEEPER2" ); +static const trait_id trait_INSOMNIA( "INSOMNIA" ); static const trait_id trait_LOVES_BOOKS( "LOVES_BOOKS" ); +static const trait_id trait_M_SKIN3( "M_SKIN3" ); +static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_PER_SLIME_OK( "PER_SLIME_OK" ); static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" ); static const trait_id trait_SAPIOVORE( "SAPIOVORE" ); +static const trait_id trait_SHELL2( "SHELL2" ); static const trait_id trait_SPIRITUAL( "SPIRITUAL" ); +static const trait_id trait_THRESH_SPIDER( "THRESH_SPIDER" ); +static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); +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 std::string flag_FUNGUS( "FUNGUS" ); +static const std::string flag_SWIMMABLE( "SWIMMABLE" ); static const efftype_id effect_boomered( "boomered" ); static const efftype_id effect_darkness( "darkness" ); +static const efftype_id effect_meth( "meth" ); + +static const bionic_id bio_soporific( "bio_soporific" ); static const itype_id itype_cookbook_human( "cookbook_human" ); @@ -136,72 +162,308 @@ bool can_see_fine_details( const Character &who, const tripoint &p ) return fine_detail_vision_mod( who, p ) <= FINE_VISION_THRESHOLD; } -} // namespace character_funcs - -namespace character_effects +comfort_response_t base_comfort_value( const Character &who, const tripoint &p ) { + // 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; + + comfort_response_t comfort_response; + + bool plantsleep = who.has_trait( trait_CHLOROMORPH ); + bool fungaloid_cosplay = who.has_trait( trait_M_SKIN3 ); + bool websleep = who.has_trait( trait_WEB_WALKER ); + bool webforce = who.has_trait( trait_THRESH_SPIDER ) && ( who.has_trait( trait_WEB_SPINNER ) || + ( who.has_trait( trait_WEB_WEAVER ) ) ); + bool in_shell = who.has_active_mutation( trait_SHELL2 ); + bool watersleep = who.has_trait( trait_WATERSLEEP ); + + map &here = get_map(); + const optional_vpart_position vp = here.veh_at( p ); + const maptile tile = here.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 = here.get_field_intensity( p, fd_web ); + + // Some mutants have different comfort needs + if( !plantsleep && !webforce ) { + if( in_shell ) { + comfort += 1 + static_cast( comfort_level::slightly_comfortable ); + // Note: shelled individuals can still use sleeping aids! + } else if( vp ) { + const cata::optional carg = vp.part_with_feature( "CARGO", false ); + const cata::optional board = vp.part_with_feature( "BOARDABLE", true ); + if( carg ) { + const vehicle_stack items = vp->vehicle().get_items( carg->part_index() ); + for( const item &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( comfort_level::slightly_comfortable ); + comfort_response.aid = &items_it; + break; // prevents using more than 1 sleep aid + } + } + } + if( board ) { + comfort += board->info().comfort; + } else { + comfort -= here.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; + } + // Web sleepers can use their webs if better furniture isn't available + else if( websleep && web >= 3 ) { + comfort += 1 + static_cast( comfort_level::slightly_comfortable ); + } else if( ter_at_pos == t_improvised_shelter ) { + comfort += 0 + static_cast( 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( comfort_level::neutral ); + } else if( !trap_at_pos.is_null() ) { + comfort += 0 + trap_at_pos.comfort; + } else { + // Not a comfortable sleeping spot + comfort -= here.move_cost( p ); + } + + if( comfort_response.aid == nullptr ) { + const map_stack items = here.i_at( p ); + for( const item &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( comfort_level::slightly_comfortable ); + comfort_response.aid = &items_it; + break; // prevents using more than 1 sleep aid + } + } + } + if( fungaloid_cosplay && here.has_flag_ter_or_furn( flag_FUNGUS, p ) ) { + comfort += static_cast( comfort_level::very_comfortable ); + } else if( watersleep && here.has_flag_ter( flag_SWIMMABLE, p ) ) { + comfort += static_cast( 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( comfort_level::impossible ); + } 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( 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( comfort_level::comfortable ); + } + // Sleep ain't happening + else { + comfort = static_cast( comfort_level::impossible ); + } + } + // Has webforce + } else { + if( web >= 3 ) { + // Thick Web and you're good to go + comfort += static_cast( comfort_level::very_comfortable ); + } else { + comfort = static_cast( comfort_level::impossible ); + } + } + + if( comfort > static_cast( comfort_level::comfortable ) ) { + comfort_response.level = comfort_level::very_comfortable; + } else if( comfort > static_cast( comfort_level::slightly_comfortable ) ) { + comfort_response.level = comfort_level::comfortable; + } else if( comfort > static_cast( comfort_level::neutral ) ) { + comfort_response.level = comfort_level::slightly_comfortable; + } else if( comfort == static_cast( comfort_level::neutral ) ) { + comfort_response.level = comfort_level::neutral; + } else { + comfort_response.level = comfort_level::uncomfortable; + } + return comfort_response; +} -stat_mod get_pain_penalty( const Character &ch ) +int rate_sleep_spot( const Character &who, const tripoint &p ) { - stat_mod ret; - int pain = ch.get_perceived_pain(); - if( pain <= 0 ) { - return ret; + const int current_stim = who.get_stim(); + const comfort_response_t comfort_info = base_comfort_value( who, p ); + if( comfort_info.aid != nullptr ) { + who.add_msg_if_player( m_info, _( "You use your %s for comfort." ), comfort_info.aid->tname() ); } - int stat_penalty = std::floor( std::pow( pain, 0.8f ) / 10.0f ); + int sleepy = static_cast( comfort_info.level ); + bool watersleep = who.has_trait( trait_WATERSLEEP ); - bool ceno = ch.has_trait( trait_CENOBITE ); - if( !ceno ) { - ret.strength = stat_penalty; - ret.dexterity = stat_penalty; + if( who.has_addiction( add_type::SLEEP ) ) { + sleepy -= 4; + } + if( who.has_trait( trait_INSOMNIA ) ) { + // 12.5 points is the difference between "tired" and "dead tired" + sleepy -= 12; + } + if( who.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 += 40; + } + if( who.has_active_bionic( bio_soporific ) ) { + sleepy += 30; + } + if( who.has_trait( trait_EASYSLEEPER2 ) ) { + // At this point, the only limit to sleep is tiredness + sleepy += 100; + } + if( watersleep && get_map().has_flag_ter( "SWIMMABLE", p ) ) { + sleepy += 10; //comfy water! } - if( !ch.has_trait( trait_INT_SLIME ) ) { - ret.intelligence = stat_penalty; + if( who.get_fatigue() < fatigue_levels::tired + 1 ) { + sleepy -= static_cast( ( fatigue_levels::tired + 1 - who.get_fatigue() ) / 4 ); } else { - ret.intelligence = pain / 5; + sleepy += static_cast( ( who.get_fatigue() - fatigue_levels::tired + 1 ) / 16 ); } - ret.perception = stat_penalty * 2 / 3; + if( current_stim > 0 || !who.has_trait( trait_INSOMNIA ) ) { + sleepy -= 2 * current_stim; + } else { + // Make it harder for insomniac to get around the trait + sleepy -= current_stim; + } + + return sleepy; +} + +bool roll_can_sleep( Character &who ) +{ + if( who.has_effect( effect_meth ) ) { + // Sleep ain't happening until that meth wears off completely. + return false; + } - ret.speed = std::pow( pain, 0.7f ); - if( ceno ) { - ret.speed /= 2; + // 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 - who.last_sleep_check; + if( dur >= 0_turns && dur < 30_minutes ) { + return false; } + who.last_sleep_check = now; - ret.speed = std::min( ret.speed, 30 ); - return ret; + int sleepy = character_funcs::rate_sleep_spot( who, who.pos() ); + sleepy += rng( -8, 8 ); + bool result = sleepy > 0; + + if( who.has_active_bionic( bio_soporific ) ) { + if( who.bio_soporific_powered_at_last_sleep_check && !who.has_power() ) { + who.add_msg_if_player( m_bad, _( "Your soporific inducer runs out of power!" ) ); + } else if( !who.bio_soporific_powered_at_last_sleep_check && who.has_power() ) { + who.add_msg_if_player( m_good, _( "Your soporific inducer starts back up." ) ); + } + who.bio_soporific_powered_at_last_sleep_check = who.has_power(); + } + + return result; } -int get_kcal_speed_penalty( float kcal_percent ) +bool can_interface_armor( const Character &who ) { - static const std::vector> starv_thresholds = { { - std::make_pair( 0.0f, -90.0f ), - std::make_pair( 0.1f, -50.f ), - std::make_pair( 0.3f, -25.0f ), - std::make_pair( 0.5f, 0.0f ) + bool okay = std::any_of( who.my_bionics->begin(), who.my_bionics->end(), + []( const bionic & b ) { + return b.powered && b.info().has_flag( STATIC( flag_str_id( "BIONIC_ARMOR_INTERFACE" ) ) ); + } ); + return okay; +} + +std::string fmt_wielded_weapon( const Character &who ) +{ + if( !who.is_armed() ) { + return _( "fists" ); + } + const item &weapon = who.weapon; + if( weapon.is_gun() ) { + std::string str = string_format( "(%d) [%s] %s", weapon.ammo_remaining(), + weapon.gun_current_mode().tname(), 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 ) { + 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 += ")"; + } + } } - }; - if( kcal_percent > 0.95f ) { - return 0; + return str; + + } else if( weapon.is_container() && weapon.contents.num_item_stacks() == 1 ) { + return string_format( "%s (%d)", weapon.tname(), + weapon.contents.front().charges ); + } else { - return std::round( multi_lerp( starv_thresholds, kcal_percent ) ); + return weapon.tname(); } } -int get_thirst_speed_penalty( int thirst ) +void add_pain_msg( const Character &who, int val, body_part bp ) { - // We die at 1200 thirst - // Start by dropping speed really fast, but then level it off a bit - static const std::vector> thirst_thresholds = {{ - std::make_pair( static_cast( thirst_levels::very_thirsty ), 0.0f ), - std::make_pair( static_cast( thirst_levels::dehydrated ), -25.0f ), - std::make_pair( static_cast( thirst_levels::parched ), -50.0f ), - std::make_pair( static_cast( thirst_levels::dead ), -75.0f ) + if( who.has_trait( trait_NOPAIN ) ) { + return; + } + if( bp == num_bp ) { + if( val > 20 ) { + who.add_msg_if_player( _( "Your body is wracked with excruciating pain!" ) ); + } else if( val > 10 ) { + who.add_msg_if_player( _( "Your body is wracked with terrible pain!" ) ); + } else if( val > 5 ) { + who.add_msg_if_player( _( "Your body is wracked with pain!" ) ); + } else if( val > 1 ) { + who.add_msg_if_player( _( "Your body pains you!" ) ); + } else { + who.add_msg_if_player( _( "Your body aches." ) ); + } + } else { + if( val > 20 ) { + who.add_msg_if_player( _( "Your %s is wracked with excruciating pain!" ), + body_part_name_accusative( bp ) ); + } else if( val > 10 ) { + who.add_msg_if_player( _( "Your %s is wracked with terrible pain!" ), + body_part_name_accusative( bp ) ); + } else if( val > 5 ) { + who.add_msg_if_player( _( "Your %s is wracked with pain!" ), + body_part_name_accusative( bp ) ); + } else if( val > 1 ) { + who.add_msg_if_player( _( "Your %s pains you!" ), + body_part_name_accusative( bp ) ); + } else { + who.add_msg_if_player( _( "Your %s aches." ), + body_part_name_accusative( bp ) ); } - }; - return static_cast( multi_lerp( thirst_thresholds, thirst ) ); + } } -} // namespace character_effects +} // namespace character_funcs diff --git a/src/character_functions.h b/src/character_functions.h index 34af0893a494..fb3c69b31443 100644 --- a/src/character_functions.h +++ b/src/character_functions.h @@ -2,14 +2,17 @@ #ifndef CATA_SRC_CHARACTER_FUNCTIONS_H #define CATA_SRC_CHARACTER_FUNCTIONS_H -#include "point.h" #include "type_id.h" +#include + +enum body_part : int; class Character; class Creature; class item; class time_duration; class vehicle; +struct tripoint; namespace character_funcs { @@ -71,6 +74,43 @@ bool can_see_fine_details( const Character &who ); bool can_see_fine_details( const Character &who, const tripoint &p ); /** @} */ +enum class comfort_level { + impossible = -999, + uncomfortable = -7, + neutral = 0, + slightly_comfortable = 3, + comfortable = 5, + very_comfortable = 10 +}; + +struct comfort_response_t { + comfort_level level = comfort_level::neutral; + const item *aid = nullptr; +}; + +/** Rate point's ability to serve as a bed. Only takes certain mutations into account, and not fatigue nor stimulants. */ +comfort_response_t base_comfort_value( const Character &who, const tripoint &p ); + +/** Rate point's ability to serve as a bed. Takes all mutations, fatigue and stimulants into account. */ +int rate_sleep_spot( const Character &who, const tripoint &p ); + +/** Checked each turn during "lying_down", returns true if the avatar falls asleep */ +bool roll_can_sleep( Character &who ); + +/** Check whether character has an active bionic capable of interfacing with power armor. */ +bool can_interface_armor( const Character &who ); + +/** Get the formatted name of the currently wielded item (if any) with current gun mode (if gun) */ +std::string fmt_wielded_weapon( const Character &who ); + +/** + * Add message describing how character feels pain. + * @param who Character that feels the pain + * @param val Amount of pain + * @param bp Target body part, use num_bp if no specific body part. + */ +void add_pain_msg( const Character &who, int val, body_part bp ); + } // namespace character_funcs #endif // CATA_SRC_CHARACTER_FUNCTIONS_H diff --git a/src/character_turn.cpp b/src/character_turn.cpp new file mode 100644 index 000000000000..9aadba353510 --- /dev/null +++ b/src/character_turn.cpp @@ -0,0 +1,1109 @@ +#include "character_turn.h" + +#include "bionics.h" +#include "calendar.h" +#include "character_effects.h" +#include "character_functions.h" +#include "character_stat.h" +#include "character_martial_arts.h" +#include "character.h" +#include "creature.h" +#include "game.h" +#include "handle_liquid.h" +#include "itype.h" +#include "magic_enchantment.h" +#include "mutation.h" +#include "overmapbuffer.h" +#include "make_static.h" +#include "map_iterator.h" +#include "morale.h" +#include "player.h" +#include "rng.h" +#include "submap.h" +#include "trap.h" +#include "veh_type.h" +#include "vehicle.h" +#include "vpart_position.h" +#include "weather_gen.h" +#include "weather.h" + +static const trait_id trait_ACIDBLOOD( "ACIDBLOOD" ); +static const trait_id trait_ARACHNID_ARMS_OK( "ARACHNID_ARMS_OK" ); +static const trait_id trait_ARACHNID_ARMS( "ARACHNID_ARMS" ); +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_CHITIN2( "CHITIN2" ); +static const trait_id trait_CHITIN3( "CHITIN3" ); +static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); +static const trait_id trait_COMPOUND_EYES( "COMPOUND_EYES" ); +static const trait_id trait_DEBUG_BIONIC_POWER( "DEBUG_BIONIC_POWER" ); +static const trait_id trait_EATHEALTH( "EATHEALTH" ); +static const trait_id trait_FAT( "FAT" ); +static const trait_id trait_FELINE_FUR( "FELINE_FUR" ); +static const trait_id trait_FUR( "FUR" ); +static const trait_id trait_INSECT_ARMS_OK( "INSECT_ARMS_OK" ); +static const trait_id trait_INSECT_ARMS( "INSECT_ARMS" ); +static const trait_id trait_LIGHTFUR( "LIGHTFUR" ); +static const trait_id trait_LUPINE_FUR( "LUPINE_FUR" ); +static const trait_id trait_M_IMMUNE( "M_IMMUNE" ); +static const trait_id trait_NOMAD( "NOMAD" ); +static const trait_id trait_NOMAD2( "NOMAD2" ); +static const trait_id trait_NOMAD3( "NOMAD3" ); +static const trait_id trait_PARAIMMUNE( "PARAIMMUNE" ); +static const trait_id trait_SLIMY( "SLIMY" ); +static const trait_id trait_STIMBOOST( "STIMBOOST" ); +static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); +static const trait_id trait_THICK_SCALES( "THICK_SCALES" ); +static const trait_id trait_URSINE_FUR( "URSINE_FUR" ); +static const trait_id trait_WEBBED( "WEBBED" ); +static const trait_id trait_WHISKERS_RAT( "WHISKERS_RAT" ); +static const trait_id trait_WHISKERS( "WHISKERS" ); + +static const efftype_id effect_bloodworms( "bloodworms" ); +static const efftype_id effect_brainworms( "brainworms" ); +static const efftype_id effect_darkness( "darkness" ); +static const efftype_id effect_depressants( "depressants" ); +static const efftype_id effect_dermatik( "dermatik" ); +static const efftype_id effect_downed( "downed" ); +static const efftype_id effect_fungus( "fungus" ); +static const efftype_id effect_happy( "happy" ); +static const efftype_id effect_irradiated( "irradiated" ); +static const efftype_id effect_masked_scent( "masked_scent" ); +static const efftype_id effect_narcosis( "narcosis" ); +static const efftype_id effect_onfire( "onfire" ); +static const efftype_id effect_paincysts( "paincysts" ); +static const efftype_id effect_pkill( "pkill" ); +static const efftype_id effect_sad( "sad" ); +static const efftype_id effect_sleep_deprived( "sleep_deprived" ); +static const efftype_id effect_sleep( "sleep" ); +static const efftype_id effect_stim_overdose( "stim_overdose" ); +static const efftype_id effect_stim( "stim" ); +static const efftype_id effect_tapeworm( "tapeworm" ); +static const efftype_id effect_thirsty( "thirsty" ); + +static const skill_id skill_swimming( "swimming" ); + +static const bionic_id bio_ground_sonar( "bio_ground_sonar" ); +static const bionic_id bio_hydraulics( "bio_hydraulics" ); +static const bionic_id bio_speed( "bio_speed" ); + +static const itype_id itype_adv_UPS_off( "adv_UPS_off" ); +static const itype_id itype_UPS_off( "UPS_off" ); +static const itype_id itype_UPS( "UPS" ); + +void Character::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( -character_effects::get_pain_penalty( *this ).speed ); + + if( get_thirst() > thirst_levels::very_thirsty ) { + mod_speed_bonus( character_effects::get_thirst_speed_penalty( get_thirst() ) ); + } + // when underweight, you get slower. cumulative with hunger + mod_speed_bonus( character_effects::get_kcal_speed_penalty( get_kcal_percent() ) ); + + for( const auto &maps : *effects ) { + for( auto &i : maps.second ) { + if( i.second.is_removed() ) { + continue; + } + 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 ) ); + } + const float temperature_speed_modifier = mutation_value( "temperature_speed_modifier" ); + if( temperature_speed_modifier != 0 ) { + const auto player_local_temp = get_weather().get_temperature( pos() ); + if( has_trait( trait_COLDBLOOD4 ) || player_local_temp < 65 ) { + mod_speed_bonus( ( player_local_temp - 65 ) * temperature_speed_modifier ); + } + } + } + + if( has_artifact_with( AEP_SPEED_UP ) ) { + mod_speed_bonus( 20 ); + } + if( has_artifact_with( AEP_SPEED_DOWN ) ) { + mod_speed_bonus( -20 ); + } + + float speed_modifier = Character::mutation_value( "speed_modifier" ); + set_speed_bonus( static_cast( get_speed() * speed_modifier ) - get_speed_base() ); + + if( has_bionic( bio_speed ) ) { // multiply by 1.1 + set_speed_bonus( static_cast( get_speed() * 1.1 ) - get_speed_base() ); + } + + double ench_bonus = enchantment_cache->calc_bonus( enchant_vals::mod::SPEED, get_speed() ); + set_speed_bonus( get_speed() + ench_bonus - 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 = static_cast( -0.75 * get_speed_base() ); + if( get_speed_bonus() < min_speed_bonus ) { + set_speed_bonus( min_speed_bonus ); + } +} + +void Character::process_turn() +{ + // Has to happen before reset_stats + clear_miss_reasons(); + + for( bionic &i : *my_bionics ) { + if( i.incapacitated_time > 0_turns ) { + i.incapacitated_time -= 1_turns; + if( i.incapacitated_time == 0_turns ) { + add_msg_if_player( m_bad, _( "Your %s bionic comes back online." ), i.info().name ); + } + } + } + + Creature::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(); + } + process_items(); + // Didn't just pick something up + last_item = itype_id( "null" ); + + if( !is_npc() && has_trait( trait_DEBUG_BIONIC_POWER ) ) { + mod_power_level( get_max_power_level() ); + } + + visit_items( [this]( item * e ) { + e->process_artifact( as_player(), pos() ); + e->process_relic( as_player() ); + return VisitResponse::NEXT; + } ); + + suffer(); + // NPCs currently don't make any use of their scent, pointless to calculate it + // TODO: make use of NPC scent. + if( !is_npc() ) { + if( !has_effect( effect_masked_scent ) ) { + restore_scent(); + } + const int mask_intensity = get_effect_int( effect_masked_scent ); + + // Set our scent towards the norm + int norm_scent = 500; + int temp_norm_scent = INT_MIN; + bool found_intensity = false; + for( const trait_id &mut : get_mutations() ) { + const cata::optional &scent_intensity = mut->scent_intensity; + if( scent_intensity ) { + found_intensity = true; + temp_norm_scent = std::max( temp_norm_scent, *scent_intensity ); + } + } + if( found_intensity ) { + norm_scent = temp_norm_scent; + } + + for( const trait_id &mut : get_mutations() ) { + const cata::optional &scent_mask = mut->scent_mask; + if( scent_mask ) { + norm_scent += *scent_mask; + } + } + + //mask from scent altering items; + norm_scent += mask_intensity; + + // 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--; + } + + for( const trait_id &mut : get_mutations() ) { + scent *= mut.obj().scent_modifier; + } + } + + // We can dodge again! Assuming we can actually move... + if( in_sleep_state() ) { + blocks_left = 0; + dodges_left = 0; + } else if( moves > 0 ) { + blocks_left = get_num_blocks(); + dodges_left = get_num_dodges(); + } + + // 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, ... + // Check for spontaneous discovery of martial art styles + for( auto &style : autolearn_martialart_types() ) { + const matype_id &ma( style ); + + if( !martial_arts_data->has_martialart( ma ) && can_autolearn_martial_art( *this, ma ) ) { + martial_arts_data->add_martialart( ma ); + add_msg_if_player( m_info, _( "You have learned a new style: %s!" ), ma.obj().name ); + } + } + + // Update time spent conscious in this overmap tile for the Nomad traits. + if( !is_npc() && ( has_trait( trait_NOMAD ) || has_trait( trait_NOMAD2 ) || + has_trait( trait_NOMAD3 ) ) && + !has_effect( effect_sleep ) && !has_effect( effect_narcosis ) ) { + const tripoint_abs_omt ompos = global_omt_location(); + const point_abs_omt pos = ompos.xy(); + if( overmap_time.find( pos ) == overmap_time.end() ) { + overmap_time[pos] = 1_turns; + } else { + overmap_time[pos] += 1_turns; + } + } + // Decay time spent in other overmap tiles. + if( !is_npc() && calendar::once_every( 1_hours ) ) { + const tripoint_abs_omt ompos = global_omt_location(); + const time_point now = calendar::turn; + time_duration decay_time = 0_days; + if( has_trait( trait_NOMAD ) ) { + decay_time = 7_days; + } else if( has_trait( trait_NOMAD2 ) ) { + decay_time = 14_days; + } else if( has_trait( trait_NOMAD3 ) ) { + decay_time = 28_days; + } + auto it = overmap_time.begin(); + while( it != overmap_time.end() ) { + if( it->first == ompos.xy() ) { + it++; + continue; + } + // Find the amount of time passed since the player touched any of the overmap tile's submaps. + const tripoint_abs_omt tpt( it->first, 0 ); + const time_point last_touched = overmap_buffer.scent_at( tpt ).creation_time; + const time_duration since_visit = now - last_touched; + // If the player has spent little time in this overmap tile, let it decay after just an hour instead of the usual extended decay time. + const time_duration modified_decay_time = it->second > 5_minutes ? decay_time : 1_hours; + if( since_visit > modified_decay_time ) { + // Reduce the tracked time spent in this overmap tile. + const time_duration decay_amount = std::min( since_visit - modified_decay_time, 1_hours ); + const time_duration updated_value = it->second - decay_amount; + if( updated_value <= 0_turns ) { + // We can stop tracking this tile if there's no longer any time recorded there. + it = overmap_time.erase( it ); + continue; + } else { + it->second = updated_value; + } + } + it++; + } + } +} + +void Character::process_one_effect( effect &it, bool is_new ) +{ + bool reduced = resists_effect( it ); + double mod = 1; + body_part bp = it.get_bp()->token; + 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( const auto &i : msgs ) { + add_miss_reason( _( i.first ), static_cast( 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 ) ) { + mod_stim( bound_mod_to_vals( get_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_stored_kcal( -10 * bound_mod_to_vals( ( max_stored_kcal() - get_stored_kcal() ) / 10, + val, it.get_max_val( "HUNGER", reduced ), it.get_min_val( "HUNGER", 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 ) ) { + mod_rad( bound_mod_to_vals( get_rad(), val, it.get_max_val( "RAD", reduced ), 0 ) ); + // Radiation can't go negative + if( get_rad() < 0 ) { + set_rad( 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( get_size() == MS_LARGE ) { + mod *= 2; + } + if( get_size() == MS_HUGE ) { + 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 ) { + character_funcs::add_pain_msg( *this, 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( get_size() == MS_LARGE ) { + mod *= 2; + } + if( get_size() == MS_HUGE ) { + 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 ) ); + } else { + add_msg_if_player( _( "Your %s hurts!" ), body_part_name_accusative( bp_torso ) ); + } + apply_damage( nullptr, bodypart_id( "torso" ), val, true ); + } else { + if( val > 5 ) { + add_msg_if_player( _( "Your %s HURTS!" ), body_part_name_accusative( bp ) ); + } else { + add_msg_if_player( _( "Your %s hurts!" ), body_part_name_accusative( bp ) ); + } + apply_damage( nullptr, convert_bp( bp ).id(), val, true ); + } + } + } + + // Handle Sleep + val = get_effect( "SLEEP", reduced ); + if( val != 0 ) { + mod = 1; + if( ( is_new || it.activated( calendar::turn, "SLEEP", val, reduced, mod ) ) && + !has_effect( efftype_id( "sleep" ) ) ) { + 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_type::PKILLER ) ); + if( is_new || it.activated( calendar::turn, "PKILL", val, reduced, mod ) ) { + mod_painkiller( bound_mod_to_vals( get_painkiller(), 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 = character_effects::vomit_mod( *this ); + 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 ) ) { + mod_stamina( bound_mod_to_vals( get_stamina(), val, + it.get_max_val( "STAMINA", reduced ), + it.get_min_val( "STAMINA", reduced ) ) ); + } + } + + // Speed and stats are handled in recalc_speed_bonus and reset_stats respectively +} + +void Character::process_effects_internal() +{ + //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." ) ); + } + + //Human only effects + for( auto &elem : *effects ) { + for( auto &_effect_it : elem.second ) { + if( !_effect_it.second.is_removed() ) { + process_one_effect( _effect_it.second, false ); + } + } + } +} + +void Character::reset_stats() +{ + const int current_stim = get_stim(); + + // 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( bodypart_id( "eyes" ) ) ) { + mod_per_bonus( 2 ); + } + 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( bodypart_id( "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( bodypart_id( "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 ); + } else if( dur > 0_turns ) { + eff.set_duration( dur ); + } else { + remove_effect( type, num_bp ); + } + }; + // Painkiller + set_fake_effect_dur( effect_pkill, 1_turns * get_painkiller() ); + + // Pain + if( get_perceived_pain() > 0 ) { + const stat_mod ppen = character_effects::get_pain_penalty( *this ); + 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!" ), static_cast( ppen.dexterity ) ); + } + } + + // Radiation + set_fake_effect_dur( effect_irradiated, 1_turns * get_rad() ); + // 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 * current_stim ); + set_fake_effect_dur( effect_depressants, 1_turns * -current_stim ); + if( has_trait( trait_STIMBOOST ) ) { + set_fake_effect_dur( effect_stim_overdose, 1_turns * ( current_stim - 60 ) ); + } else { + set_fake_effect_dur( effect_stim_overdose, 1_turns * ( current_stim - 30 ) ); + } + // Starvation + if( get_kcal_percent() < 0.95f ) { + // kcal->percentage of base str + static const std::vector> starv_thresholds = { { + std::make_pair( 0.0f, 0.5f ), + std::make_pair( 0.8f, 0.1f ), + std::make_pair( 0.95f, 0.0f ) + } + }; + + const int str_penalty = std::floor( multi_lerp( starv_thresholds, get_kcal_percent() ) ); + add_miss_reason( _( "You're weak from hunger." ), + static_cast( str_penalty / 2 ) ); + mod_str_bonus( -str_penalty ); + mod_dex_bonus( -( str_penalty / 2 ) ); + mod_int_bonus( -( str_penalty / 2 ) ); + } + // Thirst + set_fake_effect_dur( effect_thirsty, 1_turns * ( get_thirst() - thirst_levels::very_thirsty ) ); + if( get_sleep_deprivation() >= sleep_deprivation_levels::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( bodypart_id( "mouth" ) ) ) { + mod_dodge_bonus( 1.5 ); + } + if( has_trait( trait_WHISKERS_RAT ) && !wearing_something_on( bodypart_id( "mouth" ) ) ) { + mod_dodge_bonus( 3 ); + } + // depending on mounts size, attacks will hit the mount and use their dodge rating. + // if they hit the player, the player cannot dodge as effectively. + if( is_mounted() ) { + mod_dodge_bonus( -4 ); + } + // 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 parts{ { bodypart_id( "head" ), bodypart_id( "arm_r" ), bodypart_id( "arm_l" ), bodypart_id( "leg_r" ), bodypart_id( "leg_l" ) } }; + for( const bodypart_id &bp : parts ) { + if( !wearing_something_on( bp ) ) { + mod_dodge_bonus( +1 ); + } + } + // Torso handled separately, bigger bonus + if( !wearing_something_on( bodypart_id( "torso" ) ) ) { + mod_dodge_bonus( 4 ); + } + } + + // Apply static martial arts buffs + martial_arts_data->ma_static_effects( *this ); + + if( calendar::once_every( 1_minutes ) ) { + character_funcs::update_mental_focus( *this ); + } + + // Effects + for( const auto &maps : *effects ) { + for( auto i : maps.second ) { + const auto &it = i.second; + if( it.is_removed() ) { + continue; + } + 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 ) ); + } + } + + // Bionic buffs + if( has_active_bionic( bio_hydraulics ) ) { + mod_str_bonus( 20 ); + } + + mod_str_bonus( get_mod_stat_from_bionic( character_stat::STRENGTH ) ); + mod_dex_bonus( get_mod_stat_from_bionic( character_stat::DEXTERITY ) ); + mod_per_bonus( get_mod_stat_from_bionic( character_stat::PERCEPTION ) ); + mod_int_bonus( get_mod_stat_from_bionic( character_stat::INTELLIGENCE ) ); + + // Trait / mutation buffs + mod_str_bonus( std::floor( mutation_value( "str_modifier" ) ) ); + mod_dodge_bonus( std::floor( mutation_value( "dodge_modifier" ) ) ); + + apply_skill_boost(); + + nv_cached = false; + + // Reset our stats to normal levels + // Any persistent buffs/debuffs will take place in effects, + // player::suffer(), etc. + + // repopulate the stat fields + str_cur = str_max + get_str_bonus(); + dex_cur = dex_max + get_dex_bonus(); + per_cur = per_max + get_per_bonus(); + int_cur = int_max + get_int_bonus(); + + // Floor for our stats. No stat changes should occur after this! + if( dex_cur < 0 ) { + dex_cur = 0; + } + if( str_cur < 0 ) { + str_cur = 0; + } + if( per_cur < 0 ) { + per_cur = 0; + } + if( int_cur < 0 ) { + int_cur = 0; + } + + recalc_sight_limits(); + recalc_speed_bonus(); +} + +void Character::environmental_revert_effect() +{ + addictions.clear(); + morale->clear(); + + set_all_parts_hp_to_max(); + set_stored_kcal( max_stored_kcal() ); + set_thirst( 0 ); + set_fatigue( 0 ); + set_healthy( 0 ); + set_healthy_mod( 0 ); + set_stim( 0 ); + set_pain( 0 ); + set_painkiller( 0 ); + set_rad( 0 ); + set_sleep_deprivation( 0 ); + + recalc_sight_limits(); + reset_encumbrance(); +} + +void Character::process_items() +{ + if( weapon.needs_processing() && weapon.process( as_player(), pos(), false ) ) { + weapon = item(); + } + + std::vector inv_active = inv.active_items(); + for( item *tmp_it : inv_active ) { + if( tmp_it->process( as_player(), pos(), false ) ) { + inv.remove_item( tmp_it ); + } + } + + // worn items + remove_worn_items_with( [this]( item & itm ) { + return itm.needs_processing() && itm.process( as_player(), pos(), false ); + } ); + + // Active item processing done, now we're recharging. + std::vector active_worn_items; + bool weapon_active = weapon.has_flag( "USE_UPS" ) && + weapon.charges < weapon.type->maximum_charges(); + std::vector active_held_items; + int ch_UPS = 0; + for( size_t index = 0; index < inv.size(); index++ ) { + item &it = inv.find_item( index ); + itype_id identifier = it.type->get_id(); + if( identifier == itype_UPS_off ) { + ch_UPS += it.ammo_remaining(); + } else if( identifier == itype_adv_UPS_off ) { + ch_UPS += it.ammo_remaining() / 0.6; + } + if( it.has_flag( "USE_UPS" ) && it.charges < it.type->maximum_charges() ) { + active_held_items.push_back( index ); + } + } + bool update_required = get_check_encumbrance(); + for( item &w : worn ) { + if( w.has_flag( "USE_UPS" ) && + w.charges < w.type->maximum_charges() ) { + active_worn_items.push_back( &w ); + } + // Necessary for UPS in Aftershock - check worn items for charge + const itype_id &identifier = w.typeId(); + if( identifier == itype_UPS_off ) { + ch_UPS += w.ammo_remaining(); + } else if( identifier == itype_adv_UPS_off ) { + ch_UPS += w.ammo_remaining() / 0.6; + } + if( !update_required && w.encumbrance_update_ ) { + update_required = true; + } + w.encumbrance_update_ = false; + } + if( update_required ) { + reset_encumbrance(); + } + if( has_active_bionic( bionic_id( "bio_ups" ) ) ) { + ch_UPS += units::to_kilojoule( get_power_level() ); + } + int ch_UPS_used = 0; + + // 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 + for( size_t index : active_held_items ) { + if( ch_UPS_used >= ch_UPS ) { + break; + } + item &it = inv.find_item( index ); + ch_UPS_used++; + it.charges++; + } + if( weapon_active && ch_UPS_used < ch_UPS ) { + ch_UPS_used++; + weapon.charges++; + } + for( item *worn_item : active_worn_items ) { + if( ch_UPS_used >= ch_UPS ) { + break; + } + ch_UPS_used++; + worn_item->charges++; + } + if( ch_UPS_used > 0 ) { + use_charges( itype_UPS, ch_UPS_used ); + } +} + +namespace character_funcs +{ + +void update_body_wetness( Character &who, const w_point &weather ) +{ + // Average number of turns to go from completely soaked to fully dry + // assuming average temperature and humidity + constexpr time_duration average_drying = 2_hours; + + // A modifier on drying time + double delay = 1.0; + // Weather slows down drying + delay += ( ( weather.humidity - 66 ) - ( units::to_fahrenheit( weather.temperature ) - 65 ) ) / 100; + delay = std::max( 0.1, delay ); + // Fur/slime retains moisture + if( who.has_trait( trait_LIGHTFUR ) || + who.has_trait( trait_FUR ) || + who.has_trait( trait_FELINE_FUR ) || + who.has_trait( trait_LUPINE_FUR ) || + who.has_trait( trait_CHITIN_FUR ) || + who.has_trait( trait_CHITIN_FUR2 ) || + who.has_trait( trait_CHITIN_FUR3 ) ) { + delay = delay * 6 / 5; + } + if( who.has_trait( trait_URSINE_FUR ) || who.has_trait( trait_SLIMY ) ) { + delay *= 1.5; + } + + if( !x_in_y( 1, to_turns( 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, 80 ); + + for( const body_part bp : all_body_parts ) { + if( who.body_wetness[bp] == 0 ) { + continue; + } + // This is to normalize drying times + int drying_chance = who.drench_capacity[bp]; + // Body temperature affects duration of wetness + // Note: Using temp_conv rather than temp_cur, to better approximate environment + if( who.temp_conv[bp] >= BODYTEMP_SCORCHING ) { + drying_chance *= 2; + } else if( who.temp_conv[bp] >= BODYTEMP_VERY_HOT ) { + drying_chance = drying_chance * 3 / 2; + } else if( who.temp_conv[bp] >= BODYTEMP_HOT ) { + drying_chance = drying_chance * 4 / 3; + } else if( who.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 ) { + who.body_wetness[bp] -= 1; + if( who.body_wetness[bp] < 0 ) { + who.body_wetness[bp] = 0; + } + } + } + // TODO: Make clothing slow down drying +} + +void do_pause( Character &who ) +{ + map &here = get_map(); + + who.moves = 0; + who.recoil = MAX_RECOIL; + + // Train swimming if underwater + if( !who.in_vehicle ) { + if( who.is_underwater() ) { + who.as_player()->practice( skill_swimming, 1 ); + who.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( here.has_flag( TFLAG_DEEP_WATER, who.pos() ) ) { + who.as_player()->practice( skill_swimming, 1 ); + // Same as above, except no head/eyes/mouth + who.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( here.has_flag( "SWIMMABLE", who.pos() ) ) { + who.drench( 40, { { bp_foot_l, bp_foot_r, bp_leg_l, bp_leg_r } }, false ); + } + } + + // Try to put out clothing/hair fire + if( who.has_effect( effect_onfire ) ) { + time_duration total_removed = 0_turns; + time_duration total_left = 0_turns; + bool on_ground = who.has_effect( effect_downed ); + for( const body_part bp : all_body_parts ) { + effect &eff = who.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 && !who.is_dangerous_fields( here.field_at( who.pos() ) ) ) { + who.add_effect( effect_downed, 2_turns, num_bp, 0, true ); + who.add_msg_player_or_npc( m_warning, + _( "You roll on the ground, trying to smother the fire!" ), + _( " rolls on the ground!" ) ); + } else if( total_removed > 0_turns ) { + who.add_msg_player_or_npc( m_warning, + _( "You attempt to put out the fire on you!" ), + _( " attempts to put out the fire on them!" ) ); + } + } + + // on-pause effects for martial arts + who.martial_arts_data->ma_onpause_effects( who ); + + if( who.is_npc() ) { + // The stuff below doesn't apply to NPCs + // search_surroundings should eventually do, though + return; + } + + if( who.in_vehicle && one_in( 8 ) ) { + VehicleList vehs = here.get_vehicles(); + vehicle *veh = nullptr; + for( auto &v : vehs ) { + veh = v.v; + if( veh && veh->is_moving() && veh->player_in_control( who ) ) { + double exp_temp = 1 + veh->total_mass() / 400.0_kilogram + + std::abs( veh->velocity / 3200.0 ); + int experience = static_cast( exp_temp ); + if( exp_temp - experience > 0 && x_in_y( exp_temp - experience, 1.0 ) ) { + experience++; + } + who.as_player()->practice( skill_id( "driving" ), experience ); + break; + } + } + } + + search_surroundings( who ); + who.wait_effects(); +} + +void search_surroundings( Character &who ) +{ + if( who.controlling_vehicle ) { + return; + } + const map &here = get_map(); + // 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 : here.points_in_radius( who.pos(), 5 ) ) { + const trap &tr = here.tr_at( tp ); + if( tr.is_null() || tp == who.pos() ) { + continue; + } + if( who.has_active_bionic( bio_ground_sonar ) && !who.knows_trap( tp ) && + ( tr.loadid == tr_beartrap_buried || + tr.loadid == tr_landmine_buried || tr.loadid == tr_sinkhole ) ) { + const std::string direction = direction_name( direction_from( who.pos(), tp ) ); + who.add_msg_if_player( m_warning, _( "Your ground sonar detected a %1$s to the %2$s!" ), + tr.name(), direction ); + who.add_known_trap( tp, tr ); + } + if( !who.sees( tp ) ) { + continue; + } + if( tr.is_always_invisible() || tr.can_see( tp, who ) ) { + // Already seen, or can never be seen + continue; + } + // Chance to detect traps we haven't yet seen. + if( tr.detect_trap( tp, who ) ) { + if( tr.get_visibility() > 0 ) { + // Only bug player about traps that aren't trivial to spot. + const std::string direction = direction_name( + direction_from( who.pos(), tp ) ); + who.add_msg_if_player( _( "You've spotted a %1$s to the %2$s!" ), + tr.name(), direction ); + } + who.add_known_trap( tp, tr ); + } + } +} + +void update_mental_focus( Character &who ) +{ + who.focus_pool += character_effects::calc_focus_change( who ); +} + +} // namespace character_funcs diff --git a/src/character_turn.h b/src/character_turn.h new file mode 100644 index 000000000000..9249d5dc05ba --- /dev/null +++ b/src/character_turn.h @@ -0,0 +1,25 @@ +#pragma once +#ifndef CATA_SRC_CHARACTER_TURN_H +#define CATA_SRC_CHARACTER_TURN_H + +class Character; +struct w_point; + +namespace character_funcs +{ + +/** Maintains body wetness and handles the rate at which the player dries */ +void update_body_wetness( Character &who, const w_point &weather ); + +/** Do pause action ('.' key). */ +void do_pause( Character &who ); + +/** Search surrounding squares for traps (and maybe other things in the future). */ +void search_surroundings( Character &who ); + +/** Uses calc_focus_change to update the player's current focus */ +void update_mental_focus( Character &who ); + +} // namespace character_funcs + +#endif // CATA_SRC_CHARACTER_TURN_H diff --git a/src/condition.cpp b/src/condition.cpp index 8ab9d4db6e63..8bbf0600ce86 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -326,7 +326,7 @@ void conditional_t::set_has_bionics( const JsonObject &jo, const std::string actor = dynamic_cast( d.beta ); } if( bionics_id == "ANY" ) { - return actor->num_bionics() > 0 || actor->has_max_power(); + return actor->has_bionics(); } return actor->has_bionic( bionic_id( bionics_id ) ); }; diff --git a/src/creature.cpp b/src/creature.cpp index a0af29e95994..f5403494f768 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -176,6 +176,11 @@ bool Creature::is_underwater() const return underwater; } +void Creature::set_underwater( bool x ) +{ + underwater = x; +} + bool Creature::digging() const { return false; diff --git a/src/creature.h b/src/creature.h index 5d02ce1820c2..22a6c67c681f 100644 --- a/src/creature.h +++ b/src/creature.h @@ -301,6 +301,7 @@ class Creature virtual bool digging() const; virtual bool is_on_ground() const = 0; virtual bool is_underwater() const; + virtual void set_underwater( bool x ); virtual bool is_warm() const; // is this creature warm, for IR vision, heat drain, etc virtual bool in_species( const species_id & ) const; @@ -567,7 +568,6 @@ class Creature virtual std::set get_path_avoid() const = 0; int moves = 0; - bool underwater = false; void draw( const catacurses::window &w, const point &origin, bool inverted ) const; void draw( const catacurses::window &w, const tripoint &origin, bool inverted ) const; /** @@ -886,6 +886,7 @@ class Creature private: int pain = 0; + bool underwater = false; }; #endif // CATA_SRC_CREATURE_H diff --git a/src/game.cpp b/src/game.cpp index a50ac117fd58..9d0097a7fa87 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -50,6 +50,7 @@ #include "character_display.h" #include "character_functions.h" #include "character_martial_arts.h" +#include "character_turn.h" #include "clzones.h" #include "colony.h" #include "color.h" @@ -1566,7 +1567,7 @@ bool game::do_turn() } u.update_bodytemp( m, weather ); - u.update_body_wetness( get_weather().get_precise() ); + character_funcs::update_body_wetness( u, get_weather().get_precise() ); u.apply_wetness_morale( weather.temperature ); if( calendar::once_every( 1_minutes ) ) { @@ -9259,7 +9260,7 @@ point game::place_player( const tripoint &dest_loc ) // Traps! // Try to detect. - u.search_surroundings(); + character_funcs::search_surroundings( u ); if( u.is_mounted() ) { m.creature_on_trap( *u.mounted_creature ); } else { @@ -9741,7 +9742,7 @@ void game::fling_creature( Creature *c, const units::angle &dir, float flvel, bo bool force_next = false; tripoint next_forced; while( range > 0 ) { - c->underwater = false; + c->set_underwater( false ); // TODO: Check whenever it is actually in the viewport // or maybe even just redraw the changed tiles bool seen = is_u || u.sees( *c ); // To avoid redrawing when not seen @@ -9864,7 +9865,7 @@ void game::fling_creature( Creature *c, const units::angle &dir, float flvel, bo m.creature_on_trap( *c, false ); } } else { - c->underwater = true; + c->set_underwater( true ); if( is_u ) { if( controlled ) { add_msg( _( "You dive into water." ) ); @@ -11778,7 +11779,9 @@ void game::add_artifact_dreams( ) { //If player is sleeping, get a dream from a carried artifact //Don't need to check that player is sleeping here, that's done before calling - std::list art_items = g->u.get_artifact_items(); + std::vector art_items = u.items_with( []( const item & it ) -> bool { + return it.is_artifact(); + } ); std::vector valid_arts; std::vector> valid_dreams; // Tracking separately so we only need to check its req once diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 5c2c27f53c52..2a967b294c87 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -14,12 +14,15 @@ #include "auto_pickup.h" #include "avatar.h" #include "avatar_action.h" +#include "avatar_functions.h" #include "bionics.h" +#include "bionics_ui.h" #include "calendar.h" #include "catacharset.h" #include "character.h" #include "character_display.h" #include "character_martial_arts.h" +#include "character_turn.h" #include "clzones.h" #include "color.h" #include "construction.h" @@ -59,6 +62,7 @@ #include "monster.h" #include "mtype.h" #include "mutation.h" +#include "mutation_ui.h" #include "options.h" #include "output.h" #include "overmap_ui.h" @@ -1000,7 +1004,7 @@ static void wait() static void sleep() { - player &u = g->u; + avatar &u = get_avatar(); if( u.is_mounted() ) { u.add_msg_if_player( m_info, _( "You cannot sleep while mounted." ) ); return; @@ -1027,8 +1031,7 @@ static void sleep() active.push_back( it->tname() ); } } - for( int i = 0; i < g->u.num_bionics(); i++ ) { - const bionic &bio = u.bionic_at_index( i ); + for( const bionic &bio : *u.my_bionics ) { if( !bio.powered ) { continue; } @@ -1119,7 +1122,7 @@ static void sleep() } u.moves = 0; - u.try_to_sleep( try_sleep_dur ); + avatar_funcs::try_to_sleep( u, try_sleep_dur ); } static void loot() @@ -1732,13 +1735,13 @@ bool game::handle_action() case ACTION_TIMEOUT: if( check_safe_mode_allowed( false ) ) { - u.pause(); + character_funcs::do_pause( u ); } break; case ACTION_PAUSE: if( check_safe_mode_allowed() ) { - u.pause(); + character_funcs::do_pause( u ); } break; @@ -2097,10 +2100,10 @@ bool game::handle_action() } break; case ACTION_BIONICS: - u.power_bionics(); + show_bionics_ui( u ); break; case ACTION_MUTATIONS: - u.power_mutations(); + show_mutations_ui( u ); break; case ACTION_SORT_ARMOR: diff --git a/src/item.cpp b/src/item.cpp index 0fbcd29bca42..a52f50b3cba3 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -7871,10 +7871,10 @@ int item::units_remaining( const Character &ch, int limit ) const int res = ammo_remaining(); if( res < limit && is_power_armor() ) { - if( ch.as_player()->can_interface_armor() && has_flag( flag_USE_UPS ) ) { + if( character_funcs::can_interface_armor( ch ) && has_flag( flag_USE_UPS ) ) { res += std::max( ch.charges_of( itype_UPS, limit - res ), ch.charges_of( itype_bio_armor, limit - res ) ); - } else if( ch.as_player()->can_interface_armor() ) { + } else if( character_funcs::can_interface_armor( ch ) ) { res += ch.charges_of( itype_bio_armor, limit - res ); } else { res += ch.charges_of( itype_UPS, limit - res ); @@ -9387,7 +9387,7 @@ bool item::process_tool( player *carrier, const tripoint &pos ) energy -= ammo_consume( energy, pos ); // for power armor pieces, try to use power armor interface first. - if( carrier && is_power_armor() && carrier->can_interface_armor() ) { + if( carrier && is_power_armor() && character_funcs::can_interface_armor( *carrier ) ) { if( carrier->use_charges_if_avail( itype_bio_armor, energy ) ) { energy = 0; } diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 5ea7f1241915..e346e74c0500 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -227,7 +227,7 @@ int iuse_transform::use( player &p, item &it, bool t, const tripoint &pos ) cons return 0; } if( need_charges ) { - if( it.has_flag( flag_POWERARMOR_MOD ) && p.can_interface_armor() ) { + if( it.has_flag( flag_POWERARMOR_MOD ) && character_funcs::can_interface_armor( p ) ) { if( !p.has_power() ) { if( possess ) { p.add_msg_if_player( m_info, need_charges_msg, it.tname() ); @@ -921,7 +921,7 @@ int set_transform_iuse::use( player &p, item &it, bool t, const tripoint &pos ) ( it.has_flag( "ALLOWS_REMOTE_USE" ) && square_dist( p.pos(), pos ) == 1 ); if( set_charges ) { - if( it.is_power_armor() && p.can_interface_armor() ) { + if( it.is_power_armor() && character_funcs::can_interface_armor( p ) ) { if( !p.has_power() ) { if( possess ) { p.add_msg_if_player( m_info, set_charges_msg, it.tname() ); diff --git a/src/martialarts.cpp b/src/martialarts.cpp index b6b420f481fe..4b185cf74c6a 100644 --- a/src/martialarts.cpp +++ b/src/martialarts.cpp @@ -692,11 +692,6 @@ bool ma_buff::is_stealthy() const return stealthy; } -bool ma_buff::can_melee() const -{ - return melee_allowed; -} - std::string ma_buff::get_description( bool passive ) const { std::string dump; @@ -1185,20 +1180,13 @@ bool Character::is_quiet() const return b.is_quiet(); } ); } -bool player::is_stealthy() const +bool Character::is_stealthy() const { return search_ma_buff_effect( *effects, []( const ma_buff & b, const effect & ) { return b.is_stealthy(); } ); } -bool player::can_melee() const -{ - return search_ma_buff_effect( *effects, []( const ma_buff & b, const effect & ) { - return b.can_melee(); - } ); -} - bool Character::has_mabuff( const mabuff_id &id ) const { return search_ma_buff_effect( *effects, [&id]( const ma_buff & b, const effect & ) { @@ -1224,7 +1212,7 @@ void character_martial_arts::add_martialart( const matype_id &ma_id ) ma_styles.emplace_back( ma_id ); } -bool player::can_autolearn( const matype_id &ma_id ) const +bool can_autolearn_martial_art( const Character &who, const matype_id &ma_id ) { if( ma_id.obj().autolearn_skills.empty() ) { return false; @@ -1234,7 +1222,7 @@ bool player::can_autolearn( const matype_id &ma_id ) const const skill_id skill_req( elem.first ); const int required_level = elem.second; - if( required_level > get_skill_level( skill_req ) ) { + if( required_level > who.get_skill_level( skill_req ) ) { return false; } } diff --git a/src/martialarts.h b/src/martialarts.h index 403677241611..71701087a2a2 100644 --- a/src/martialarts.h +++ b/src/martialarts.h @@ -163,7 +163,6 @@ class ma_buff // returns various boolean flags bool is_throw_immune() const; bool is_quiet() const; - bool can_melee() const; bool is_stealthy() const; // The ID of the effect that is used to store this buff @@ -191,7 +190,6 @@ class ma_buff bonus_container bonuses; bool quiet = false; - bool melee_allowed = false; bool throw_immune = false; // are we immune to throws/grabs? bool strictly_melee = false; // can we only use it with weapons? bool stealthy = false; // do we make less noise when moving? @@ -297,4 +295,7 @@ std::string martialart_difficulty( const matype_id &mstyle ); std::vector all_martialart_types(); std::vector autolearn_martialart_types(); +/** Returns true if the character can learn the entered martial art */ +bool can_autolearn_martial_art( const Character &who, const matype_id &ma_id ); + #endif // CATA_SRC_MARTIALARTS_H diff --git a/src/melee.cpp b/src/melee.cpp index 17338d4c2610..c27fe788a93a 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -379,15 +379,9 @@ static void melee_train( Character &p, int lo, int hi, const item &weap ) } } -void Character::melee_attack( Creature &t, bool allow_special ) -{ - static const matec_id no_technique_id( "" ); - melee_attack( t, allow_special, no_technique_id ); -} - // Melee calculation is in parts. This sets up the attack, then in deal_melee_attack, // we calculate if we would hit. In Creature::deal_melee_hit, we calculate if the target dodges. -void Character::melee_attack( Creature &t, bool allow_special, const matec_id &force_technique, +void Character::melee_attack( Creature &t, bool allow_special, const matec_id *force_technique, bool allow_unarmed ) { melee::melee_stats.attack_count += 1; @@ -482,14 +476,14 @@ void Character::melee_attack( Creature &t, bool allow_special, const matec_id &f damage_instance d; roll_all_damage( critical_hit, d, false, cur_weapon ); - const bool has_force_technique = !force_technique.str().empty(); + const bool has_force_technique = force_technique; // Pick one or more special attacks matec_id technique_id; if( allow_special && !has_force_technique ) { technique_id = pick_technique( t, cur_weapon, critical_hit, false, false ); } else if( has_force_technique ) { - technique_id = force_technique; + technique_id = *force_technique; } else { technique_id = tec_none; } @@ -620,7 +614,7 @@ void Character::melee_attack( Creature &t, bool allow_special, const matec_id &f return; } -void player::reach_attack( const tripoint &p ) +void Character::reach_attack( const tripoint &p ) { matec_id force_technique = tec_none; /** @EFFECT_MELEE >5 allows WHIP_DISARM technique */ @@ -706,7 +700,7 @@ void player::reach_attack( const tripoint &p ) } reach_attacking = true; - melee_attack( *critter, false, force_technique, false ); + melee_attack( *critter, false, &force_technique, false ); reach_attacking = false; } @@ -852,7 +846,7 @@ float Character::get_dodge() const return std::max( 0.0f, ret ); } -float player::dodge_roll() +float Character::dodge_roll() { return get_dodge() * 5; } @@ -1778,7 +1772,7 @@ bool Character::block_hit( Creature *source, bodypart_id &bp_hit, damage_instanc } else if( weapon.made_of( material_id( "glass" ) ) ) { add_msg( m_bad, _( "The item you are wielding is too fragile to counterattack with!" ) ); } else { - melee_attack( *source, false, tec ); + melee_attack( *source, false, &tec ); } } @@ -2253,16 +2247,16 @@ int Character::attack_cost( const item &weap ) const return move_cost; } -double player::weapon_value( const item &weap, int ammo ) const +double npc_ai::weapon_value( const Character &who, const item &weap, int ammo ) { - if( is_wielding( weap ) ) { - auto cached_value = cached_info.find( "weapon_value" ); - if( cached_value != cached_info.end() ) { - return cached_value->second; + if( who.is_wielding( weap ) ) { + auto cached = who.get_npc_ai_info_cache( "weapon_value" ); + if( cached ) { + return *cached; } } - const double val_gun = gun_value( weap, ammo ); - const double val_melee = melee_value( weap ); + const double val_gun = gun_value( who, weap, ammo ); + const double val_melee = melee_value( who, weap ); const double more = std::max( val_gun, val_melee ); const double less = std::min( val_gun, val_melee ); @@ -2275,29 +2269,29 @@ double player::weapon_value( const item &weap, int ammo ) const // A small bonus for guns you can also use to hit stuff with (bayonets etc.) const double my_val = ( more + ( less / 2.0 ) ) * armor_penalty; add_msg( m_debug, "%s (%ld ammo) sum value: %.1f", weap.type->get_id().str(), ammo, my_val ); - if( is_wielding( weap ) ) { - cached_info.emplace( "weapon_value", my_val ); + if( who.is_wielding( weap ) ) { + who.set_npc_ai_info_cache( "weapon_value", my_val ); } return my_val; } -double player::melee_value( const item &weap ) const +double npc_ai::melee_value( const Character &who, const item &weap ) { // start with average effective dps against a range of enemies - double my_value = weap.average_dps( *this ); + double my_value = weap.average_dps( *who.as_player() ); - float reach = weap.reach_range( *this ); + float reach = weap.reach_range( who ); // value reach weapons more if( reach > 1.0f ) { my_value *= 1.0f + 0.5f * ( std::sqrt( reach ) - 1.0f ); } // value polearms less to account for the trickiness of keeping the right range - if( weapon.has_flag( "POLEARM" ) ) { + if( weap.has_flag( "POLEARM" ) ) { my_value *= 0.8; } // value style weapons more - if( !martial_arts_data->enumerate_known_styles( weap.type->get_id() ).empty() ) { + if( !who.martial_arts_data->enumerate_known_styles( weap.type->get_id() ).empty() ) { my_value *= 1.5; } @@ -2306,10 +2300,10 @@ double player::melee_value( const item &weap ) const return std::max( 0.0, my_value ); } -double player::unarmed_value() const +double npc_ai::unarmed_value( const Character &who ) { // TODO: Martial arts - return melee_value( item() ); + return melee_value( who, item() ); } void player::disarm( npc &target ) diff --git a/src/monmove.cpp b/src/monmove.cpp index f9844873a06a..2999c452e190 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -1646,7 +1646,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, setpos( destination ); footsteps( destination ); - underwater = will_be_water; + set_underwater( will_be_water ); if( is_hallucination() ) { //Hallucinations don't do any of the stuff after this point return true; @@ -1682,7 +1682,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, return true; } if( !will_be_water && ( digs() || can_dig() ) ) { - underwater = g->m.has_flag( "DIGGABLE", pos() ); + set_underwater( g->m.has_flag( "DIGGABLE", pos() ) ); } // Diggers turn the dirt into dirtmound if( digging() && g->m.has_flag( "DIGGABLE", pos() ) ) { diff --git a/src/monster.cpp b/src/monster.cpp index 36d351909e62..16db85977ab6 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -920,7 +920,7 @@ bool monster::can_climb() const bool monster::digging() const { - return digs() || ( can_dig() && underwater ); + return digs() || ( can_dig() && is_underwater() ); } bool monster::can_dig() const @@ -959,7 +959,7 @@ int monster::sight_range( const int light_level ) const { // Non-aquatic monsters can't see much when submerged if( !can_see() || effect_cache[VISION_IMPAIRED] || - ( underwater && !swims() && !has_flag( MF_AQUATIC ) && !digging() ) ) { + ( is_underwater() && !swims() && !has_flag( MF_AQUATIC ) && !digging() ) ) { return 1; } static const int default_daylight = default_daylight_level(); @@ -1277,7 +1277,7 @@ void monster::process_trigger( mon_trigger trig, const std::function &amo bool monster::is_underwater() const { - return underwater && can_submerge(); + return Creature::is_underwater() && can_submerge(); } bool monster::is_on_ground() const diff --git a/src/monster.h b/src/monster.h index c77ee9c566db..68d8e54e4b32 100644 --- a/src/monster.h +++ b/src/monster.h @@ -136,7 +136,6 @@ class monster : public Creature, public visitable std::pair get_attitude() const; int print_info( const catacurses::window &w, int vStart, int vLines, int column ) const override; - // Information on how our symbol should appear nc_color basic_symbol_color() const override; nc_color symbol_color() const override; const std::string &symbol() const override; diff --git a/src/mutation.cpp b/src/mutation.cpp index 472de7b7d4d8..cdcf94434a1b 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -157,7 +157,7 @@ void Character::set_mutation( const trait_id &trait ) if( iter != my_mutations.end() ) { return; } - my_mutations.emplace( trait, trait_data{} ); + my_mutations.emplace( trait, char_trait_data{} ); rebuild_mutation_cache(); mutation_effect( trait ); recalc_sight_limits(); @@ -470,7 +470,7 @@ bool Character::can_install_cbm_on_bp( const std::vector &bps ) con void Character::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); - trait_data &tdata = my_mutations[mut]; + char_trait_data &tdata = my_mutations[mut]; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if( !can_use_mutation_warn( mut, *this ) ) { @@ -626,16 +626,6 @@ void Character::deactivate_mutation( const trait_id &mut ) } } -trait_id Character::trait_by_invlet( const int ch ) const -{ - for( const std::pair &mut : my_mutations ) { - if( mut.second.key == ch ) { - return mut.first; - } - } - return trait_id::NULL_ID(); -} - bool Character::mutation_ok( const trait_id &mutation, bool force_good, bool force_bad ) const { if( !is_category_allowed( mutation->category ) ) { @@ -1723,7 +1713,7 @@ bool can_use_mutation_warn( const trait_id &mut, const Character &character ) void Character::mutation_spend_resources( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); - trait_data &tdata = my_mutations[mut]; + char_trait_data &tdata = my_mutations[mut]; int cost = mdata.cost; if( tdata.powered && tdata.charge > 0 ) { // Already-on units just lose a bit of charge diff --git a/src/mutation_ui.cpp b/src/mutation_ui.cpp index e45588c4f70e..ddba7c256620 100644 --- a/src/mutation_ui.cpp +++ b/src/mutation_ui.cpp @@ -1,10 +1,11 @@ -#include "player.h" // IWYU pragma: associated +#include "mutation_ui.h" #include //std::min #include #include #include +#include "character.h" #include "enums.h" #include "input.h" #include "inventory.h" @@ -86,9 +87,19 @@ static void show_mutations_titlebar( const catacurses::window &window, wnoutrefresh( window ); } -void player::power_mutations() +static cata::optional trait_by_invlet( const mutation_collection &mutations, int ch ) { - if( !is_player() ) { + for( const std::pair &mut : mutations ) { + if( mut.second.key == ch ) { + return mut.first; + } + } + return cata::nullopt; +} + +void show_mutations_ui( Character &who ) +{ + if( !who.is_avatar() ) { // TODO: Implement NPCs activating mutations return; } @@ -96,25 +107,25 @@ void player::power_mutations() // Mutation selection UI obscures the terrain, // but some mutations may ask to select a tile when activated. // As such, we must (de-)activate only after destroying the UI. - power_mut_ui_result res = power_mutations_ui(); + detail::mutations_ui_result res = detail::show_mutations_ui_internal( who ); switch( res.cmd ) { - case power_mut_ui_cmd::Exit: + case detail::mutations_ui_cmd::exit: break; - case power_mut_ui_cmd::Deactivate: - deactivate_mutation( res.mut ); + case detail::mutations_ui_cmd::deactivate: + who.deactivate_mutation( res.mut ); break; - case power_mut_ui_cmd::Activate: - activate_mutation( res.mut ); + case detail::mutations_ui_cmd::activate: + who.activate_mutation( res.mut ); break; } } -player::power_mut_ui_result player::power_mutations_ui() +detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) { std::vector passive; std::vector active; - for( std::pair &mut : my_mutations ) { + for( std::pair &mut : who.my_mutations ) { if( !mut.first->activated && ! mut.first->transform ) { passive.push_back( mut.first ); } else { @@ -123,7 +134,7 @@ player::power_mut_ui_result player::power_mutations_ui() // New mutations are initialized with no key at all, so we have to do this here. if( mut.second.key == ' ' ) { for( const auto &letter : mutation_chars ) { - if( trait_by_invlet( letter ).is_null() ) { + if( !trait_by_invlet( who.my_mutations, letter ) ) { mut.second.key = letter; break; } @@ -262,15 +273,15 @@ player::power_mut_ui_result player::power_mutations_ui() } else { for( int i = scroll_position; static_cast( i ) < passive.size(); i++ ) { const mutation_branch &md = passive[i].obj(); - const trait_data &td = my_mutations[passive[i]]; + const char_trait_data &td = who.my_mutations[passive[i]]; const bool is_highlighted = cursor == static_cast( i ); if( i - scroll_position == list_height ) { break; } if( is_highlighted && tab_mode == mutation_tab_mode::passive ) { - type = has_base_trait( passive[i] ) ? c_cyan_yellow : c_light_cyan_yellow; + type = who.has_base_trait( passive[i] ) ? c_cyan_yellow : c_light_cyan_yellow; } else { - type = has_base_trait( passive[i] ) ? c_cyan : c_light_cyan; + type = who.has_base_trait( passive[i] ) ? c_cyan : c_light_cyan; } mvwprintz( wBio, point( 2, list_start_y + i - scroll_position ), type, "%c %s", td.key, md.name() ); @@ -282,22 +293,22 @@ player::power_mut_ui_result player::power_mutations_ui() } else { for( int i = scroll_position; static_cast( i ) < active.size(); i++ ) { const mutation_branch &md = active[i].obj(); - const trait_data &td = my_mutations[active[i]]; + const char_trait_data &td = who.my_mutations[active[i]]; const bool is_highlighted = cursor == static_cast( i ); if( i - scroll_position == list_height ) { break; } if( td.powered ) { if( is_highlighted && tab_mode == mutation_tab_mode::active ) { - type = has_base_trait( active[i] ) ? c_green_yellow : c_light_green_yellow; + type = who.has_base_trait( active[i] ) ? c_green_yellow : c_light_green_yellow; } else { - type = has_base_trait( active[i] ) ? c_green : c_light_green; + type = who.has_base_trait( active[i] ) ? c_green : c_light_green; } } else { if( is_highlighted && tab_mode == mutation_tab_mode::active ) { - type = has_base_trait( active[i] ) ? c_red_yellow : c_light_red_yellow; + type = who.has_base_trait( active[i] ) ? c_red_yellow : c_light_red_yellow; } else { - type = has_base_trait( active[i] ) ? c_red : c_light_red; + type = who.has_base_trait( active[i] ) ? c_red : c_light_red; } } // TODO: track resource(s) used and specify @@ -335,7 +346,7 @@ player::power_mut_ui_result player::power_mutations_ui() } } ); - power_mut_ui_result ret; + mutations_ui_result ret; bool exit = false; while( !exit ) { recalc_max_scroll_position(); @@ -348,14 +359,14 @@ player::power_mut_ui_result player::power_mutations_ui() if( ch == ' ' ) { //skip if space is pressed (space is used as an empty hotkey) continue; } - const trait_id mut_id = trait_by_invlet( ch ); - if( !mut_id.is_null() ) { - const mutation_branch &mut_data = mut_id.obj(); + cata::optional mut_id = trait_by_invlet( who.my_mutations, ch ); + if( mut_id ) { + const mutation_branch &mut_data = mut_id->obj(); switch( menu_mode ) { case mutation_menu_mode::reassigning: { query_popup pop; pop.message( _( "%s; enter new letter." ), - mutation_branch::get_name( mut_id ) ) + mutation_branch::get_name( *mut_id ) ) .context( "POPUP_WAIT" ) .allow_cancel( true ) .allow_anykey( true ); @@ -367,11 +378,11 @@ player::power_mut_ui_result player::power_mutations_ui() if( ret.evt.type == CATA_INPUT_KEYBOARD && !ret.evt.sequence.empty() ) { const int newch = ret.evt.get_first_input(); if( mutation_chars.valid( newch ) ) { - const trait_id other_mut_id = trait_by_invlet( newch ); - if( !other_mut_id.is_null() ) { - std::swap( my_mutations[mut_id].key, my_mutations[other_mut_id].key ); + const cata::optional other_mut_id = trait_by_invlet( who.my_mutations, newch ); + if( other_mut_id ) { + std::swap( who.my_mutations[*mut_id].key, who.my_mutations[*other_mut_id].key ); } else { - my_mutations[mut_id].key = newch; + who.my_mutations[*mut_id].key = newch; } pop_exit = true; pop_handled = true; @@ -396,23 +407,23 @@ player::power_mut_ui_result player::power_mutations_ui() case mutation_menu_mode::activating: { const cata::value_ptr &trans = mut_data.transform; if( mut_data.activated || trans ) { - if( my_mutations[mut_id].powered ) { + if( who.my_mutations[*mut_id].powered ) { if( trans && !trans->msg_transform.empty() ) { - add_msg_if_player( m_neutral, trans->msg_transform ); + who.add_msg_if_player( m_neutral, trans->msg_transform ); } else { - add_msg_if_player( m_neutral, _( "You stop using your %s." ), mut_data.name() ); + who.add_msg_if_player( m_neutral, _( "You stop using your %s." ), mut_data.name() ); } - ret.cmd = power_mut_ui_cmd::Deactivate; - ret.mut = mut_id; + ret.cmd = mutations_ui_cmd::deactivate; + ret.mut = *mut_id; exit = true; - } else if( can_use_mutation_warn( mut_id, *this ) ) { + } else if( can_use_mutation_warn( *mut_id, who ) ) { if( trans && !trans->msg_transform.empty() ) { - add_msg_if_player( m_neutral, trans->msg_transform ); + who.add_msg_if_player( m_neutral, trans->msg_transform ); } else { - add_msg_if_player( m_neutral, _( "You activate your %s." ), mut_data.name() ); + who.add_msg_if_player( m_neutral, _( "You activate your %s." ), mut_data.name() ); } - ret.cmd = power_mut_ui_cmd::Activate; - ret.mut = mut_id; + ret.cmd = mutations_ui_cmd::activate; + ret.mut = *mut_id; exit = true; } else { popup( _( "You feel like using your %s would kill you!" ), @@ -421,7 +432,7 @@ player::power_mut_ui_result player::power_mutations_ui() } else { popup( _( "You cannot activate %s! To read a description of " "%s, press '!', then '%c'." ), - mut_data.name(), mut_data.name(), my_mutations[mut_id].key ); + mut_data.name(), mut_data.name(), who.my_mutations[*mut_id].key ); } break; } @@ -531,16 +542,16 @@ player::power_mut_ui_result player::power_mutations_ui() if( ret.evt.type == CATA_INPUT_KEYBOARD && !ret.evt.sequence.empty() ) { const int newch = ret.evt.get_first_input(); if( mutation_chars.valid( newch ) ) { - const trait_id other_mut_id = trait_by_invlet( newch ); - if( !other_mut_id.is_null() ) { - std::swap( my_mutations[mut_id].key, my_mutations[other_mut_id].key ); + const cata::optional other_mut_id = trait_by_invlet( who.my_mutations, newch ); + if( other_mut_id ) { + std::swap( who.my_mutations[mut_id].key, who.my_mutations[*other_mut_id].key ); } else { - my_mutations[mut_id].key = newch; + who.my_mutations[mut_id].key = newch; } pop_exit = true; pop_handled = true; } else if( newch == ' ' ) { - my_mutations[mut_id].key = newch; + who.my_mutations[mut_id].key = newch; pop_exit = true; pop_handled = true; } @@ -563,18 +574,18 @@ player::power_mut_ui_result player::power_mutations_ui() } case mutation_menu_mode::activating: { if( mut_data.activated ) { - if( my_mutations[mut_id].powered ) { - add_msg_if_player( m_neutral, _( "You stop using your %s." ), mut_data.name() ); + if( who.my_mutations[mut_id].powered ) { + who.add_msg_if_player( m_neutral, _( "You stop using your %s." ), mut_data.name() ); - deactivate_mutation( mut_id ); + who.deactivate_mutation( mut_id ); // Action done, leave screen exit = true; - } else if( ( !mut_data.hunger || get_kcal_percent() >= 0.8f ) && - ( !mut_data.thirst || get_thirst() <= 400 ) && - ( !mut_data.fatigue || get_fatigue() <= 400 ) ) { - add_msg_if_player( m_neutral, _( "You activate your %s." ), mut_data.name() ); + } else if( ( !mut_data.hunger || who.get_kcal_percent() >= 0.8f ) && + ( !mut_data.thirst || who.get_thirst() <= 400 ) && + ( !mut_data.fatigue || who.get_fatigue() <= 400 ) ) { + who.add_msg_if_player( m_neutral, _( "You activate your %s." ), mut_data.name() ); - activate_mutation( mut_id ); + who.activate_mutation( mut_id ); // Action done, leave screen exit = true; } else { @@ -583,7 +594,7 @@ player::power_mut_ui_result player::power_mutations_ui() } else { popup( _( "You cannot activate %s! To read a description of " "%s, press '!', then '%c'." ), - mut_data.name(), mut_data.name(), my_mutations[mut_id].key ); + mut_data.name(), mut_data.name(), who.my_mutations[mut_id].key ); } break; } @@ -602,7 +613,7 @@ player::power_mut_ui_result player::power_mutations_ui() mutation_menu_mode::examining : mutation_menu_mode::activating; examine_id = cata::nullopt; } else if( action == "QUIT" ) { - ret.cmd = power_mut_ui_cmd::Exit; + ret.cmd = mutations_ui_cmd::exit; exit = true; } } diff --git a/src/mutation_ui.h b/src/mutation_ui.h new file mode 100644 index 000000000000..9eeea61ed143 --- /dev/null +++ b/src/mutation_ui.h @@ -0,0 +1,28 @@ +#pragma once +#ifndef CATA_SRC_MUTATION_UI_H +#define CATA_SRC_MUTATION_UI_H + +#include "type_id.h" + +class Character; + +namespace detail +{ +enum class mutations_ui_cmd { + exit, + activate, + deactivate, +}; + +struct mutations_ui_result { + mutations_ui_cmd cmd; + trait_id mut; +}; + +mutations_ui_result show_mutations_ui_internal( Character &who ); + +} // namespace detail + +void show_mutations_ui( Character &who ); + +#endif // CATA_SRC_MUTATION_UI_H diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index b20cedad57ac..4f9628bfd4eb 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -2857,7 +2857,7 @@ std::vector Character::get_base_traits() const std::vector Character::get_mutations( bool include_hidden ) const { std::vector result; - for( const std::pair &t : my_mutations ) { + for( const std::pair &t : my_mutations ) { if( include_hidden || t.first.obj().player_display ) { result.push_back( t.first ); } diff --git a/src/npc.cpp b/src/npc.cpp index 41948907ccff..2f32b582ae42 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -1150,7 +1150,7 @@ void npc::stow_item( item &it ) bool npc::wield( item &it ) { - cached_info.erase( "weapon_value" ); + clear_npc_ai_info_cache( "weapon_value" ); if( is_armed() ) { stow_item( weapon ); } @@ -1220,7 +1220,7 @@ void npc::form_opinion( const player &u ) } else { op_of_u.fear += 6; } - } else if( u.weapon_value( u.weapon ) > 20 ) { + } else if( npc_ai::weapon_value( u, u.weapon ) > 20 ) { op_of_u.fear += 2; } else if( !u.is_armed() ) { // Unarmed, but actually unarmed ("unarmed weapons" are not unarmed) @@ -1504,7 +1504,7 @@ void npc::decide_needs() needrank[need_safety] = 1; } - needrank[need_weapon] = weapon_value( weapon ); + needrank[need_weapon] = npc_ai::weapon_value( *this, weapon ); needrank[need_food] = 15.0f - ( max_stored_kcal() - get_stored_kcal() ) / 10.0f; needrank[need_drink] = 15 - get_thirst(); invslice slice = inv.slice(); @@ -1755,7 +1755,7 @@ int npc::value( const item &it, int market_price ) const 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 ); + double weapon_val = npc_ai::weapon_value( *this, it ) - npc_ai::weapon_value( *this, weapon ); if( weapon_val > 0 ) { ret += weapon_val; } @@ -2172,7 +2172,7 @@ float npc::danger_assessment() float npc::average_damage_dealt() { - return static_cast( melee_value( weapon ) ); + return static_cast( npc_ai::melee_value( *this, weapon ) ); } bool npc::bravery_check( int diff ) diff --git a/src/npc.h b/src/npc.h index d2280e87e4a0..7712c675db2d 100644 --- a/src/npc.h +++ b/src/npc.h @@ -847,7 +847,6 @@ class npc : public player void deserialize( JsonIn &jsin ) override; void serialize( JsonOut &json ) const override; - // Display nc_color basic_symbol_color() const override; int print_info( const catacurses::window &w, int line, int vLines, int column ) const override; std::string opinion_text() const; @@ -1425,4 +1424,17 @@ std::ostream &operator<< ( std::ostream &os, const npc_need &need ); /** Opens a menu and allows player to select a friendly NPC. */ npc *pick_follower(); +namespace npc_ai +{ +/** Evaluate item as weapon (melee or gun) */ +double weapon_value( const Character &who, const item &weap, int ammo = 10 ); +/** Evaluates item as a gun */ +double gun_value( const Character &who, const item &weap, int ammo = 10 ); +/** Evaluate item as a melee weapon */ +double melee_value( const Character &who, const item &weap ); +/** Evaluate unarmed melee value */ +double unarmed_value( const Character &who ); + +} // namespace npc_ai + #endif // CATA_SRC_NPC_H diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 71b0f0e28572..a97810fbaf10 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -18,6 +18,8 @@ #include "bodypart.h" #include "cata_algo.h" #include "character.h" +#include "character_functions.h" +#include "character_turn.h" #include "character_id.h" #include "clzones.h" #include "coordinate_conversions.h" @@ -634,14 +636,12 @@ void npc::assess_danger() ai_cache.danger_assessment = assessment; } -float npc::character_danger( const Character &uc ) const +float npc::character_danger( const Character &u ) const { - // TODO: Remove this when possible - const player &u = dynamic_cast( uc ); float ret = 0.0; bool u_gun = u.weapon.is_gun(); bool my_gun = weapon.is_gun(); - double u_weap_val = u.weapon_value( u.weapon ); + double u_weap_val = npc_ai::weapon_value( u, u.weapon ); const double &my_weap_val = ai_cache.my_weapon_value; if( u_gun && !my_gun ) { u_weap_val *= 1.5f; @@ -679,7 +679,7 @@ void npc::regen_ai_cache() ai_cache.can_heal.clear_all(); ai_cache.danger = 0.0f; ai_cache.total_danger = 0.0f; - ai_cache.my_weapon_value = weapon_value( weapon ); + ai_cache.my_weapon_value = npc_ai::weapon_value( *this, weapon ); ai_cache.dangerous_explosives = find_dangerous_explosives(); assess_danger(); @@ -1012,7 +1012,7 @@ void npc::execute_action( npc_action action ) case npc_sleep: { // TODO: Allow stims when not too tired // Find a nice spot to sleep - int best_sleepy = sleep_spot( pos() ); + int best_sleepy = character_funcs::rate_sleep_spot( *this, pos() ); tripoint best_spot = pos(); for( const tripoint &p : closest_points_first( pos(), 6 ) ) { if( !could_move_onto( p ) || !g->is_empty( p ) ) { @@ -1020,7 +1020,7 @@ void npc::execute_action( npc_action action ) } // TODO: Blankets when it's cold - const int sleepy = sleep_spot( p ); + const int sleepy = character_funcs::rate_sleep_spot( *this, p ); if( sleepy > best_sleepy ) { best_sleepy = sleepy; best_spot = p; @@ -1485,8 +1485,7 @@ static bool wants_to_reload_with( const item &weap, const item &ammo ) item &npc::find_reloadable() { - auto cached_value = cached_info.find( "reloadables" ); - if( cached_value != cached_info.end() ) { + if( get_npc_ai_info_cache( "reloadables" ) ) { return null_item_reference(); } // Check wielded gun, non-wielded guns, mags and tools @@ -1513,7 +1512,7 @@ item &npc::find_reloadable() return *reloadable; } - cached_info.emplace( "reloadables", 0.0 ); + set_npc_ai_info_cache( "reloadables", 0.0 ); return null_item_reference(); } @@ -1586,48 +1585,35 @@ void npc::deactivate_combat_cbms() bool npc::activate_bionic_by_id( const bionic_id &cbm_id, bool eff_only ) { - int index = 0; - for( const bionic &i : *my_bionics ) { - if( i.id == cbm_id ) { - if( !i.powered ) { - return activate_bionic( index, eff_only ); - } else { - return false; - } + if( has_bionic( cbm_id ) ) { + bionic &bio = get_bionic_state( cbm_id ); + if( !bio.powered ) { + return activate_bionic( bio, eff_only ); } - index += 1; } return false; } bool npc::use_bionic_by_id( const bionic_id &cbm_id, bool eff_only ) { - int index = 0; - for( const bionic &i : *my_bionics ) { - if( i.id == cbm_id ) { - if( !i.powered ) { - return activate_bionic( index, eff_only ); - } else { - return true; - } + if( has_bionic( cbm_id ) ) { + bionic &bio = get_bionic_state( cbm_id ); + if( bio.powered ) { + return true; + } else { + return activate_bionic( bio, eff_only ); } - index += 1; } return false; } bool npc::deactivate_bionic_by_id( const bionic_id &cbm_id, bool eff_only ) { - int index = 0; - for( const bionic &i : *my_bionics ) { - if( i.id == cbm_id ) { - if( i.powered ) { - return deactivate_bionic( index, eff_only ); - } else { - return false; - } + if( has_bionic( cbm_id ) ) { + bionic &bio = get_bionic_state( cbm_id ); + if( bio.powered ) { + return deactivate_bionic( bio, eff_only ); } - index += 1; } return false; } @@ -2682,13 +2668,13 @@ void npc::move_pause() // NPCs currently always aim when using a gun, even with no target // This simulates them aiming at stuff just at the edge of their range if( !weapon.is_gun() ) { - pause(); + character_funcs::do_pause( *this ); return; } // Stop, drop, and roll if( has_effect( effect_onfire ) ) { - pause(); + character_funcs::do_pause( *this ); } else { aim(); moves = std::min( moves, 0 ); @@ -3418,7 +3404,7 @@ bool npc::wield_better_weapon() bool allowed = can_use_gun && it.is_gun() && ( !use_silent || it.is_silent() ); double val; if( !allowed ) { - val = weapon_value( it, 0 ); + val = npc_ai::weapon_value( *this, it, 0 ); } else { int ammo_count = it.ammo_remaining(); int ups_drain = it.get_gun_ups_drain(); @@ -3426,7 +3412,7 @@ bool npc::wield_better_weapon() ammo_count = std::min( ammo_count, ups_charges / ups_drain ); } - val = weapon_value( it, ammo_count ); + val = npc_ai::weapon_value( *this, it, ammo_count ); } if( val > best_value ) { diff --git a/src/npctalk.cpp b/src/npctalk.cpp index c83d25fb0e67..73d45f703bd6 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -21,6 +21,7 @@ #include "calendar.h" #include "cata_utility.h" #include "character.h" +#include "character_effects.h" #include "character_functions.h" #include "character_id.h" #include "clzones.h" @@ -1394,9 +1395,9 @@ static int parse_mod( const dialogue &d, const std::string &attribute, const int } else if( attribute == "MISSIONS" ) { modifier = p.assigned_missions_value() / OWED_VAL; } else if( attribute == "U_INTIMIDATE" ) { - modifier = u.intimidation(); + modifier = character_effects::intimidation( u ); } else if( attribute == "NPC_INTIMIDATE" ) { - modifier = p.intimidation(); + modifier = character_effects::intimidation( u ); } modifier *= factor; return modifier; @@ -1417,7 +1418,9 @@ int talk_trial::calc_chance( const dialogue &d ) const debugmsg( "Called calc_chance with invalid talk_trial value %d", type ); break; case TALK_TRIAL_LIE: - chance += u.talk_skill() - p.talk_skill() + p.op_of_u.trust * 3; + chance += character_effects::talk_skill( u ) - + character_effects::talk_skill( p ) + + p.op_of_u.trust * 3; chance += u_mods.lie; //come on, who would suspect a robot of lying? @@ -1429,7 +1432,8 @@ int talk_trial::calc_chance( const dialogue &d ) const } break; case TALK_TRIAL_PERSUADE: - chance += u.talk_skill() - static_cast( p.talk_skill() / 2 ) + + chance += character_effects::talk_skill( u ) - + character_effects::talk_skill( p ) / 2 + p.op_of_u.trust * 2 + p.op_of_u.value; chance += u_mods.persuade; @@ -1444,8 +1448,9 @@ int talk_trial::calc_chance( const dialogue &d ) const } break; case TALK_TRIAL_INTIMIDATE: - chance += u.intimidation() - p.intimidation() + p.op_of_u.fear * 2 - - p.personality.bravery * 2; + chance += character_effects::intimidation( u ) - + character_effects::intimidation( p ) + + p.op_of_u.fear * 2 - p.personality.bravery * 2; chance += u_mods.intimidate; if( u.has_bionic( bio_face_mask ) ) { @@ -3358,8 +3363,8 @@ std::string give_item_to( npc &p, bool allow_use ) std::string reason = _( "Nope." ); int our_ammo = p.ammo_count_for( p.weapon ); int new_ammo = p.ammo_count_for( given ); - const double new_weapon_value = p.weapon_value( given, new_ammo ); - const double cur_weapon_value = p.weapon_value( p.weapon, our_ammo ); + const double new_weapon_value = npc_ai::weapon_value( p, given, new_ammo ); + const double cur_weapon_value = npc_ai::weapon_value( p, p.weapon, our_ammo ); add_msg( m_debug, "NPC evaluates own %s (%d ammo): %0.1f", p.weapon.typeId().str(), our_ammo, cur_weapon_value ); add_msg( m_debug, "NPC evaluates your %s (%d ammo): %0.1f", diff --git a/src/panels.cpp b/src/panels.cpp index b27909a0d324..9b3add0b84ef 100644 --- a/src/panels.cpp +++ b/src/panels.cpp @@ -20,6 +20,7 @@ #include "cata_utility.h" #include "catacharset.h" #include "character.h" +#include "character_effects.h" #include "character_functions.h" #include "character_martial_arts.h" #include "character_oracle.h" @@ -1197,9 +1198,9 @@ static void draw_char_narrow( avatar &u, const catacurses::window &w ) } mvwprintz( w, point( 8, 2 ), focus_color( u.focus_pool ), "%s", u.focus_pool ); - if( u.focus_pool < u.calc_focus_equilibrium() ) { + if( u.focus_pool < character_effects::calc_focus_equilibrium( u ) ) { mvwprintz( w, point( 11, 2 ), c_light_green, "↥" ); - } else if( u.focus_pool > u.calc_focus_equilibrium() ) { + } else if( u.focus_pool > character_effects::calc_focus_equilibrium( u ) ) { mvwprintz( w, point( 11, 2 ), c_light_red, "↧" ); } mvwprintz( w, point( 26, 0 ), morale_pair.first, "%s", smiley ); @@ -1386,7 +1387,8 @@ static void draw_weapon_labels( const avatar &u, const catacurses::window &w ) mvwprintz( w, point( 1, 0 ), c_light_gray, _( "Wield:" ) ); // NOLINTNEXTLINE(cata-use-named-point-constants) mvwprintz( w, point( 1, 1 ), c_light_gray, _( "Style:" ) ); - trim_and_print( w, point( 8, 0 ), getmaxx( w ) - 8, c_light_gray, u.weapname() ); + trim_and_print( w, point( 8, 0 ), getmaxx( w ) - 8, c_light_gray, + character_funcs::fmt_wielded_weapon( u ) ); mvwprintz( w, point( 8, 1 ), c_light_gray, "%s", u.martial_arts_data->selected_style_name( u ) ); wnoutrefresh( w ); } @@ -1498,7 +1500,8 @@ static void draw_env_compact( avatar &u, const catacurses::window &w ) draw_minimap( u, w ); // wielded item - trim_and_print( w, point( 8, 0 ), getmaxx( w ) - 8, c_light_gray, u.weapname() ); + trim_and_print( w, point( 8, 0 ), getmaxx( w ) - 8, c_light_gray, + character_funcs::fmt_wielded_weapon( u ) ); // style mvwprintz( w, point( 8, 1 ), c_light_gray, "%s", u.martial_arts_data->selected_style_name( u ) ); // location @@ -1882,7 +1885,8 @@ static void draw_weapon_classic( const avatar &u, const catacurses::window &w ) werase( w ); mvwprintz( w, point_zero, c_light_gray, _( "Weapon :" ) ); - trim_and_print( w, point( 10, 0 ), getmaxx( w ) - 24, c_light_gray, u.weapname() ); + trim_and_print( w, point( 10, 0 ), getmaxx( w ) - 24, c_light_gray, + character_funcs::fmt_wielded_weapon( u ) ); // Print in sidebar currently used martial style. const std::string style = u.martial_arts_data->selected_style_name( u ); @@ -1901,7 +1905,8 @@ static void draw_weapon_classic_alt( const avatar &u, const catacurses::window & werase( w ); mvwprintz( w, point_zero, c_light_gray, _( "Weapon:" ) ); - trim_and_print( w, point( 8, 0 ), getmaxx( w ) - 2, c_light_gray, u.weapname() ); + trim_and_print( w, point( 8, 0 ), getmaxx( w ) - 2, c_light_gray, + character_funcs::fmt_wielded_weapon( u ) ); // Print in sidebar currently used martial style. const std::string style = u.martial_arts_data->selected_style_name( u ); diff --git a/src/player.cpp b/src/player.cpp index a4e14e7cc1b5..4d9da3c699e1 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -18,8 +18,10 @@ #include "bionics.h" #include "cata_utility.h" #include "catacharset.h" +#include "character_functions.h" #include "character_effects.h" #include "character_martial_arts.h" +#include "character_turn.h" #include "clzones.h" #include "craft_command.h" #include "damage.h" @@ -85,129 +87,39 @@ #include "weather.h" #include "weather_gen.h" -static const activity_id ACT_READ( "ACT_READ" ); - -static const efftype_id effect_adrenaline( "adrenaline" ); static const efftype_id effect_blind( "blind" ); -static const efftype_id effect_bloodworms( "bloodworms" ); -static const efftype_id effect_boomered( "boomered" ); -static const efftype_id effect_brainworms( "brainworms" ); -static const efftype_id effect_darkness( "darkness" ); -static const efftype_id effect_depressants( "depressants" ); -static const efftype_id effect_dermatik( "dermatik" ); static const efftype_id effect_downed( "downed" ); -static const efftype_id effect_drunk( "drunk" ); -static const efftype_id effect_fungus( "fungus" ); -static const efftype_id effect_happy( "happy" ); -static const efftype_id effect_irradiated( "irradiated" ); -static const efftype_id effect_masked_scent( "masked_scent" ); -static const efftype_id effect_meth( "meth" ); -static const efftype_id effect_narcosis( "narcosis" ); -static const efftype_id effect_nausea( "nausea" ); -static const efftype_id effect_onfire( "onfire" ); -static const efftype_id effect_paincysts( "paincysts" ); -static const efftype_id effect_pkill( "pkill" ); -static const efftype_id effect_sad( "sad" ); -static const efftype_id effect_sleep_deprived( "sleep_deprived" ); -static const efftype_id effect_sleep( "sleep" ); -static const efftype_id effect_stim_overdose( "stim_overdose" ); -static const efftype_id effect_stim( "stim" ); static const efftype_id effect_stunned( "stunned" ); -static const efftype_id effect_tapeworm( "tapeworm" ); -static const efftype_id effect_thirsty( "thirsty" ); -static const efftype_id effect_weed_high( "weed_high" ); -static const itype_id itype_adv_UPS_off( "adv_UPS_off" ); static const itype_id itype_battery( "battery" ); static const itype_id itype_brass_catcher( "brass_catcher" ); static const itype_id itype_large_repairkit( "large_repairkit" ); static const itype_id itype_plut_cell( "plut_cell" ); static const itype_id itype_small_repairkit( "small_repairkit" ); static const itype_id itype_syringe( "syringe" ); -static const itype_id itype_UPS( "UPS" ); -static const itype_id itype_UPS_off( "UPS_off" ); -static const trait_id trait_ACIDBLOOD( "ACIDBLOOD" ); static const trait_id trait_DEBUG_NODMG( "DEBUG_NODMG" ); -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_CENOBITE( "CENOBITE" ); static const trait_id trait_CF_HAIR( "CF_HAIR" ); -static const trait_id trait_CHITIN2( "CHITIN2" ); -static const trait_id trait_CHITIN3( "CHITIN3" ); -static const trait_id trait_CHITIN_FUR2( "CHITIN_FUR2" ); -static const trait_id trait_CHITIN_FUR3( "CHITIN_FUR3" ); -static const trait_id trait_CHITIN_FUR( "CHITIN_FUR" ); -static const trait_id trait_CHLOROMORPH( "CHLOROMORPH" ); -static const trait_id trait_COLDBLOOD4( "COLDBLOOD4" ); -static const trait_id trait_COMPOUND_EYES( "COMPOUND_EYES" ); -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_DEFT( "DEFT" ); -static const trait_id trait_EASYSLEEPER2( "EASYSLEEPER2" ); -static const trait_id trait_EASYSLEEPER( "EASYSLEEPER" ); -static const trait_id trait_EATHEALTH( "EATHEALTH" ); -static const trait_id trait_FAT( "FAT" ); -static const trait_id trait_FELINE_FUR( "FELINE_FUR" ); -static const trait_id trait_FUR( "FUR" ); -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_LIGHTFUR( "LIGHTFUR" ); -static const trait_id trait_LUPINE_FUR( "LUPINE_FUR" ); -static const trait_id trait_M_IMMUNE( "M_IMMUNE" ); -static const trait_id trait_MOREPAIN2( "MORE_PAIN2" ); -static const trait_id trait_MOREPAIN3( "MORE_PAIN3" ); -static const trait_id trait_MOREPAIN( "MORE_PAIN" ); -static const trait_id trait_M_SKIN3( "M_SKIN3" ); -static const trait_id trait_NAUSEA( "NAUSEA" ); -static const trait_id trait_NOMAD2( "NOMAD2" ); -static const trait_id trait_NOMAD3( "NOMAD3" ); -static const trait_id trait_NOMAD( "NOMAD" ); -static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_PACIFIST( "PACIFIST" ); -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_PROF_SKATER( "PROF_SKATER" ); static const trait_id trait_QUILLS( "QUILLS" ); static const trait_id trait_SAVANT( "SAVANT" ); -static const trait_id trait_SHELL2( "SHELL2" ); -static const trait_id trait_SLIMY( "SLIMY" ); static const trait_id trait_SPINES( "SPINES" ); -static const trait_id trait_STIMBOOST( "STIMBOOST" ); -static const trait_id trait_STRONGSTOMACH( "STRONGSTOMACH" ); -static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); -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_URSINE_FUR( "URSINE_FUR" ); -static const trait_id trait_VOMITOUS( "VOMITOUS" ); static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); -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_RAT( "WHISKERS_RAT" ); -static const trait_id trait_WHISKERS( "WHISKERS" ); static const std::string flag_SPLINT( "SPLINT" ); static const std::string flag_RESTRICT_HAND( "RESTRICT_HANDS" ); static const skill_id skill_dodge( "dodge" ); static const skill_id skill_gun( "gun" ); -static const skill_id skill_swimming( "swimming" ); static const skill_id skill_weapon( "weapon" ); -static const bionic_id bio_cloak( "bio_cloak" ); static const bionic_id bio_cqb( "bio_cqb" ); -static const bionic_id bio_ground_sonar( "bio_ground_sonar" ); -static const bionic_id bio_soporific( "bio_soporific" ); -static const bionic_id bio_speed( "bio_speed" ); static const bionic_id bio_syringe( "bio_syringe" ); static const bionic_id bio_uncanny_dodge( "bio_uncanny_dodge" ); @@ -274,283 +186,6 @@ void player::normalize() set_stamina( get_stamina_max() ); } -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(); - } - process_items(); - // Didn't just pick something up - last_item = itype_id( "null" ); - - if( !is_npc() && has_trait( trait_DEBUG_BIONIC_POWER ) ) { - mod_power_level( get_max_power_level() ); - } - - visit_items( [this]( item * e ) { - e->process_artifact( this, pos() ); - e->process_relic( this ); - return VisitResponse::NEXT; - } ); - - suffer(); - // NPCs currently don't make any use of their scent, pointless to calculate it - // TODO: make use of NPC scent. - if( !is_npc() ) { - if( !has_effect( effect_masked_scent ) ) { - restore_scent(); - } - const int mask_intensity = get_effect_int( effect_masked_scent ); - - // Set our scent towards the norm - int norm_scent = 500; - int temp_norm_scent = INT_MIN; - bool found_intensity = false; - for( const trait_id &mut : get_mutations() ) { - const cata::optional &scent_intensity = mut->scent_intensity; - if( scent_intensity ) { - found_intensity = true; - temp_norm_scent = std::max( temp_norm_scent, *scent_intensity ); - } - } - if( found_intensity ) { - norm_scent = temp_norm_scent; - } - - for( const trait_id &mut : get_mutations() ) { - const cata::optional &scent_mask = mut->scent_mask; - if( scent_mask ) { - norm_scent += *scent_mask; - } - } - - //mask from scent altering items; - norm_scent += mask_intensity; - - // 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--; - } - - for( const trait_id &mut : get_mutations() ) { - scent *= mut.obj().scent_modifier; - } - } - - // We can dodge again! Assuming we can actually move... - if( in_sleep_state() ) { - blocks_left = 0; - dodges_left = 0; - } else if( moves > 0 ) { - blocks_left = get_num_blocks(); - dodges_left = get_num_dodges(); - } - - // 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, ... - // Check for spontaneous discovery of martial art styles - for( auto &style : autolearn_martialart_types() ) { - const matype_id &ma( style ); - - if( !martial_arts_data->has_martialart( ma ) && can_autolearn( ma ) ) { - martial_arts_data->add_martialart( ma ); - add_msg_if_player( m_info, _( "You have learned a new style: %s!" ), ma.obj().name ); - } - } - - // Update time spent conscious in this overmap tile for the Nomad traits. - if( !is_npc() && ( has_trait( trait_NOMAD ) || has_trait( trait_NOMAD2 ) || - has_trait( trait_NOMAD3 ) ) && - !has_effect( effect_sleep ) && !has_effect( effect_narcosis ) ) { - const tripoint_abs_omt ompos = global_omt_location(); - const point_abs_omt pos = ompos.xy(); - if( overmap_time.find( pos ) == overmap_time.end() ) { - overmap_time[pos] = 1_turns; - } else { - overmap_time[pos] += 1_turns; - } - } - // Decay time spent in other overmap tiles. - if( !is_npc() && calendar::once_every( 1_hours ) ) { - const tripoint_abs_omt ompos = global_omt_location(); - const time_point now = calendar::turn; - time_duration decay_time = 0_days; - if( has_trait( trait_NOMAD ) ) { - decay_time = 7_days; - } else if( has_trait( trait_NOMAD2 ) ) { - decay_time = 14_days; - } else if( has_trait( trait_NOMAD3 ) ) { - decay_time = 28_days; - } - auto it = overmap_time.begin(); - while( it != overmap_time.end() ) { - if( it->first == ompos.xy() ) { - it++; - continue; - } - // Find the amount of time passed since the player touched any of the overmap tile's submaps. - const tripoint_abs_omt tpt( it->first, 0 ); - const time_point last_touched = overmap_buffer.scent_at( tpt ).creation_time; - const time_duration since_visit = now - last_touched; - // If the player has spent little time in this overmap tile, let it decay after just an hour instead of the usual extended decay time. - const time_duration modified_decay_time = it->second > 5_minutes ? decay_time : 1_hours; - if( since_visit > modified_decay_time ) { - // Reduce the tracked time spent in this overmap tile. - const time_duration decay_amount = std::min( since_visit - modified_decay_time, 1_hours ); - const time_duration updated_value = it->second - decay_amount; - if( updated_value <= 0_turns ) { - // We can stop tracking this tile if there's no longer any time recorded there. - it = overmap_time.erase( it ); - continue; - } else { - it->second = updated_value; - } - } - it++; - } - } -} - -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( -character_effects::get_pain_penalty( *this ).speed ); - - if( get_thirst() > thirst_levels::very_thirsty ) { - mod_speed_bonus( character_effects::get_thirst_speed_penalty( get_thirst() ) ); - } - // when underweight, you get slower. cumulative with hunger - mod_speed_bonus( character_effects::get_kcal_speed_penalty( get_kcal_percent() ) ); - - for( const auto &maps : *effects ) { - for( auto &i : maps.second ) { - if( i.second.is_removed() ) { - continue; - } - 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 ) ); - } - const float temperature_speed_modifier = mutation_value( "temperature_speed_modifier" ); - if( temperature_speed_modifier != 0 ) { - const auto player_local_temp = get_weather().get_temperature( pos() ); - if( has_trait( trait_COLDBLOOD4 ) || player_local_temp < 65 ) { - mod_speed_bonus( ( player_local_temp - 65 ) * temperature_speed_modifier ); - } - } - } - - if( has_artifact_with( AEP_SPEED_UP ) ) { - mod_speed_bonus( 20 ); - } - if( has_artifact_with( AEP_SPEED_DOWN ) ) { - mod_speed_bonus( -20 ); - } - - float speed_modifier = Character::mutation_value( "speed_modifier" ); - set_speed_bonus( static_cast( get_speed() * speed_modifier ) - get_speed_base() ); - - if( has_bionic( bio_speed ) ) { // multiply by 1.1 - set_speed_bonus( static_cast( get_speed() * 1.1 ) - get_speed_base() ); - } - - double ench_bonus = enchantment_cache->calc_bonus( enchant_vals::mod::SPEED, get_speed() ); - set_speed_bonus( get_speed() + ench_bonus - 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 = static_cast( -0.75 * get_speed_base() ); - if( get_speed_bonus() < min_speed_bonus ) { - set_speed_bonus( min_speed_bonus ); - } -} - -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_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 ) || - is_wearing_active_optcloak() || has_trait( trait_DEBUG_CLOAK ) ) { - return c_dark_gray; - } - if( move_mode == CMM_RUN ) { - return c_yellow; - } - if( move_mode == CMM_CROUCH ) { - return c_light_gray; - } - return c_white; -} - void player::mod_stat( const std::string &stat, float modifier ) { if( stat == "thirst" ) { @@ -567,219 +202,6 @@ void player::mod_stat( const std::string &stat, float modifier ) } } -std::list player::get_artifact_items() -{ - std::list 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::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() ); - } - - return myroll >= traproll; -} - -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, 0, true ); - add_msg_player_or_npc( m_warning, - _( "You roll on the ground, trying to smother the fire!" ), - _( " 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!" ), - _( " attempts to put out the fire on them!" ) ); - } - } - - // on-pause effects for martial arts - martial_arts_data->ma_onpause_effects( *this ); - - if( is_npc() ) { - // The stuff below doesn't apply to NPCs - // search_surroundings should eventually do, though - return; - } - - if( in_vehicle && one_in( 8 ) ) { - 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 ) ) { - double exp_temp = 1 + veh->total_mass() / 400.0_kilogram + - std::abs( veh->velocity / 3200.0 ); - int experience = static_cast( exp_temp ); - if( exp_temp - experience > 0 && x_in_y( exp_temp - experience, 1.0 ) ) { - experience++; - } - practice( skill_id( "driving" ), experience ); - break; - } - } - } - - search_surroundings(); - wait_effects(); -} - -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( has_active_bionic( bio_ground_sonar ) && !knows_trap( tp ) && - ( tr.loadid == tr_beartrap_buried || - tr.loadid == tr_landmine_buried || tr.loadid == tr_sinkhole ) ) { - const std::string direction = direction_name( direction_from( pos(), tp ) ); - add_msg_if_player( m_warning, _( "Your ground sonar detected a %1$s to the %2$s!" ), - tr.name(), direction ); - add_known_trap( tp, tr ); - } - if( !sees( tp ) ) { - continue; - } - if( tr.is_always_invisible() || tr.can_see( tp, *this ) ) { - // Already seen, or 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(), direction ); - } - add_known_trap( tp, tr ); - } - } -} - -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( get_stim() > 20 ) { - ret += 2; - } - if( has_effect( effect_drunk ) ) { - ret -= 4; - } - - return ret; -} - -bool player::is_dead_state() const -{ - return get_part_hp_cur( bodypart_id( "head" ) ) <= 0 || - get_part_hp_cur( bodypart_id( "torso" ) ) <= 0; -} - void player::on_dodge( Creature *source, float difficulty ) { static const matec_id tec_none( "tec_none" ); @@ -808,7 +230,7 @@ void player::on_dodge( Creature *source, float difficulty ) if( get_stamina() < get_stamina_max() / 3 ) { add_msg( m_bad, _( "You try to counterattack but you are too exhausted!" ) ); } else { - melee_attack( *source, false, tec ); + melee_attack( *source, false, &tec ); } } } @@ -942,54 +364,6 @@ bool player::immune_to( body_part bp, damage_unit dam ) const return dam.amount <= 0; } -void player::mod_pain( int npain ) -{ - if( npain > 0 ) { - if( has_trait( trait_NOPAIN ) || has_effect( effect_narcosis ) ) { - 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 ); -} - float player::fall_damage_mod() const { if( has_effect_with_flag( "EFFECT_FEATHER_FALL" ) ) { @@ -1245,481 +619,11 @@ int player::hp_percentage() const return ( 100 * total_cur ) / total_max; } -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 ) ); - } else if( val > 10 ) { - add_msg_if_player( _( "Your %s is wracked with terrible pain!" ), - body_part_name_accusative( bp ) ); - } else if( val > 5 ) { - add_msg_if_player( _( "Your %s is wracked with pain!" ), - body_part_name_accusative( bp ) ); - } else if( val > 1 ) { - add_msg_if_player( _( "Your %s pains you!" ), - body_part_name_accusative( bp ) ); - } else { - add_msg_if_player( _( "Your %s aches." ), - body_part_name_accusative( bp ) ); - } - } -} - -void player::process_one_effect( effect &it, bool is_new ) -{ - bool reduced = resists_effect( it ); - double mod = 1; - body_part bp = it.get_bp()->token; - 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( const auto &i : msgs ) { - add_miss_reason( _( i.first ), static_cast( 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 ) ) { - mod_stim( bound_mod_to_vals( get_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_stored_kcal( -10 * bound_mod_to_vals( ( max_stored_kcal() - get_stored_kcal() ) / 10, - val, it.get_max_val( "HUNGER", reduced ), it.get_min_val( "HUNGER", 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 ) ) { - mod_rad( bound_mod_to_vals( get_rad(), val, it.get_max_val( "RAD", reduced ), 0 ) ); - // Radiation can't go negative - if( get_rad() < 0 ) { - set_rad( 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( get_size() == MS_LARGE ) { - mod *= 2; - } - if( get_size() == MS_HUGE ) { - 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( get_size() == MS_LARGE ) { - mod *= 2; - } - if( get_size() == MS_HUGE ) { - 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 ) ); - } else { - add_msg_if_player( _( "Your %s hurts!" ), body_part_name_accusative( bp_torso ) ); - } - apply_damage( nullptr, bodypart_id( "torso" ), val, true ); - } else { - if( val > 5 ) { - add_msg_if_player( _( "Your %s HURTS!" ), body_part_name_accusative( bp ) ); - } else { - add_msg_if_player( _( "Your %s hurts!" ), body_part_name_accusative( bp ) ); - } - apply_damage( nullptr, convert_bp( bp ).id(), val, true ); - } - } - } - - // Handle Sleep - val = get_effect( "SLEEP", reduced ); - if( val != 0 ) { - mod = 1; - if( ( is_new || it.activated( calendar::turn, "SLEEP", val, reduced, mod ) ) && - !has_effect( efftype_id( "sleep" ) ) ) { - 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_type::PKILLER ) ); - if( is_new || it.activated( calendar::turn, "PKILL", val, reduced, mod ) ) { - mod_painkiller( bound_mod_to_vals( get_painkiller(), 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 ) ) { - mod_stamina( bound_mod_to_vals( get_stamina(), val, - it.get_max_val( "STAMINA", reduced ), - it.get_min_val( "STAMINA", reduced ) ) ); - } - } - - // Speed and stats are handled in recalc_speed_bonus and reset_stats respectively -} - -void player::process_effects_internal() -{ - //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." ) ); - } - - //Human only effects - for( auto &elem : *effects ) { - for( auto &_effect_it : elem.second ) { - if( !_effect_it.second.is_removed() ) { - process_one_effect( _effect_it.second, false ); - } - } - } -} - -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. Water doesn't provoke vomiting, though. - if( stomach.get_calories() > 0 && has_effect( effect_nausea ) ) { - mod *= 5 * get_effect_int( effect_nausea ); - } - return mod; -} - -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 time_duration average_drying = 2_hours; - - // A modifier on drying time - double delay = 1.0; - // Weather slows down drying - delay += ( ( weather.humidity - 66 ) - ( units::to_fahrenheit( weather.temperature ) - 65 ) ) / 100; - 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 *= 1.5; - } - - if( !x_in_y( 1, to_turns( 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, 80 ); - - 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 -} - void player::on_worn_item_transform( const item &old_it, const item &new_it ) { morale->on_worn_item_transform( old_it, new_it ); } -void player::process_items() -{ - if( weapon.needs_processing() && weapon.process( this, pos(), false ) ) { - weapon = item(); - } - - std::vector inv_active = inv.active_items(); - for( item *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 ); - } ); - - // Active item processing done, now we're recharging. - std::vector active_worn_items; - bool weapon_active = weapon.has_flag( "USE_UPS" ) && - weapon.charges < weapon.type->maximum_charges(); - std::vector active_held_items; - int ch_UPS = 0; - for( size_t index = 0; index < inv.size(); index++ ) { - item &it = inv.find_item( index ); - itype_id identifier = it.type->get_id(); - if( identifier == itype_UPS_off ) { - ch_UPS += it.ammo_remaining(); - } else if( identifier == itype_adv_UPS_off ) { - ch_UPS += it.ammo_remaining() / 0.6; - } - if( it.has_flag( "USE_UPS" ) && it.charges < it.type->maximum_charges() ) { - active_held_items.push_back( index ); - } - } - bool update_required = get_check_encumbrance(); - for( item &w : worn ) { - if( w.has_flag( "USE_UPS" ) && - w.charges < w.type->maximum_charges() ) { - active_worn_items.push_back( &w ); - } - // Necessary for UPS in Aftershock - check worn items for charge - const itype_id &identifier = w.typeId(); - if( identifier == itype_UPS_off ) { - ch_UPS += w.ammo_remaining(); - } else if( identifier == itype_adv_UPS_off ) { - ch_UPS += w.ammo_remaining() / 0.6; - } - if( !update_required && w.encumbrance_update_ ) { - update_required = true; - } - w.encumbrance_update_ = false; - } - if( update_required ) { - reset_encumbrance(); - } - if( has_active_bionic( bionic_id( "bio_ups" ) ) ) { - ch_UPS += units::to_kilojoule( get_power_level() ); - } - int ch_UPS_used = 0; - - // 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 - for( size_t index : active_held_items ) { - if( ch_UPS_used >= ch_UPS ) { - break; - } - item &it = inv.find_item( index ); - ch_UPS_used++; - it.charges++; - } - if( weapon_active && ch_UPS_used < ch_UPS ) { - ch_UPS_used++; - weapon.charges++; - } - for( item *worn_item : active_worn_items ) { - if( ch_UPS_used >= ch_UPS ) { - break; - } - ch_UPS_used++; - worn_item->charges++; - } - if( ch_UPS_used > 0 ) { - use_charges( itype_UPS, ch_UPS_used ); - } -} - item player::reduce_charges( int position, int quantity ) { item &it = i_at( position ); @@ -1751,20 +655,6 @@ item player::reduce_charges( item *it, int quantity ) return result; } -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().has_flag( STATIC( flag_str_id( "BIONIC_ARMOR_INTERFACE" ) ) ); - } ); - return okay; -} - -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 ) { @@ -2354,7 +1244,7 @@ ret_val player::can_wield( const item &it ) const if( is_armed() && weapon.has_flag( "NO_UNWIELD" ) ) { return ret_val::make_failure( _( "The %s is preventing you from wielding the %s." ), - weapname(), it.tname() ); + character_funcs::fmt_wielded_weapon( *this ), it.tname() ); } monster *mount = mounted_creature.get(); @@ -3436,217 +2326,6 @@ recipe_subset player::get_available_recipes( const inventory &crafting_inv, 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_intensity( 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( comfort_level::neutral ) || - ter_at_pos == t_improvised_shelter || - trap_at_pos.comfort > static_cast( comfort_level::neutral ) || - 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( !plantsleep && !fungaloid_cosplay && !watersleep ) { - if( !vp && ter_at_pos != t_floor ) { - 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() ); - } else if( vp ) { - if( vp->part_with_feature( VPFLAG_AISLE, true ) ) { - add_msg_if_player( - //~ %1$s: vehicle name, %2$s: vehicle part name - _( "It's a little hard to get to sleep on this %2$s in %1$s." ), - vp->vehicle().disp_name(), - vp->part_with_feature( VPFLAG_AISLE, true )->part().name( false ) ); - } else { - add_msg_if_player( - //~ %1$s: vehicle name - _( "It's hard to get to sleep in %1$s." ), - vp->vehicle().disp_name() ); - } - } - } - add_msg_if_player( _( "You start trying to fall asleep." ) ); - if( has_active_bionic( bio_soporific ) ) { - bio_soporific_powered_at_last_sleep_check = has_power(); - if( bio_soporific_powered_at_last_sleep_check ) { - // The actual bonus is applied in sleep_spot( p ). - add_msg_if_player( m_good, _( "Your soporific inducer starts working its magic." ) ); - } else { - add_msg_if_player( m_bad, _( "Your soporific inducer doesn't have enough power to operate." ) ); - } - } - assign_activity( activity_id( "ACT_TRY_SLEEP" ), to_moves( dur ) ); -} - -int player::sleep_spot( const tripoint &p ) const -{ - const int current_stim = get_stim(); - const comfort_response_t comfort_info = base_comfort_value( p ); - if( comfort_info.aid != nullptr ) { - add_msg_if_player( m_info, _( "You use your %s for comfort." ), comfort_info.aid->tname() ); - } - - int sleepy = static_cast( comfort_info.level ); - bool watersleep = has_trait( trait_WATERSLEEP ); - - if( has_addiction( add_type::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 += 40; - } - if( has_active_bionic( bio_soporific ) ) { - sleepy += 30; - } - if( has_trait( trait_EASYSLEEPER2 ) ) { - // At this point, the only limit to sleep is tiredness - sleepy += 100; - } - if( watersleep && g->m.has_flag_ter( "SWIMMABLE", pos() ) ) { - sleepy += 10; //comfy water! - } - - if( get_fatigue() < fatigue_levels::tired + 1 ) { - sleepy -= static_cast( ( fatigue_levels::tired + 1 - get_fatigue() ) / 4 ); - } else { - sleepy += static_cast( ( get_fatigue() - fatigue_levels::tired + 1 ) / 16 ); - } - - if( current_stim > 0 || !has_trait( trait_INSOMNIA ) ) { - sleepy -= 2 * current_stim; - } else { - // Make it harder for insomniac to get around the trait - sleepy -= current_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 >= 0_turns && dur < 30_minutes ) { - return false; - } - last_sleep_check = now; - - int sleepy = sleep_spot( pos() ); - sleepy += rng( -8, 8 ); - bool result = sleepy > 0; - - if( has_active_bionic( bio_soporific ) ) { - if( bio_soporific_powered_at_last_sleep_check && !has_power() ) { - add_msg_if_player( m_bad, _( "Your soporific inducer runs out of power!" ) ); - } else if( !bio_soporific_powered_at_last_sleep_check && has_power() ) { - add_msg_if_player( m_good, _( "Your soporific inducer starts back up." ) ); - } - bio_soporific_powered_at_last_sleep_check = has_power(); - } - - return result; -} - void player::practice( const skill_id &id, int amount, int cap, bool suppress_warning ) { SkillLevel &level = get_skill_level_object( id ); @@ -3783,44 +2462,6 @@ bool player::has_magazine_for_ammo( const ammotype &at ) const } ); } -std::string player::weapname() const -{ - if( weapon.is_gun() ) { - std::string str = string_format( "(%d) [%s] %s", weapon.ammo_remaining(), - weapon.gun_current_mode().tname(), 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 ) { - 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 str; - - } else if( weapon.is_container() && weapon.contents.num_item_stacks() == 1 ) { - return string_format( "%s (%d)", weapon.tname(), - weapon.contents.front().charges ); - - } else if( !is_armed() ) { - return _( "fists" ); - - } else { - return weapon.tname(); - } -} - bool player::wield_contents( item &container, item *internal_item, bool penalties, int base_cost ) { @@ -3948,27 +2589,6 @@ bool player::uncanny_dodge() return false; } -void player::environmental_revert_effect() -{ - addictions.clear(); - morale->clear(); - - set_all_parts_hp_to_max(); - set_stored_kcal( max_stored_kcal() ); - set_thirst( 0 ); - set_fatigue( 0 ); - set_healthy( 0 ); - set_healthy_mod( 0 ); - set_stim( 0 ); - set_pain( 0 ); - set_painkiller( 0 ); - set_rad( 0 ); - set_sleep_deprivation( 0 ); - - recalc_sight_limits(); - reset_encumbrance(); -} - //message related stuff void player::add_msg_if_player( const std::string &msg ) const { @@ -4012,285 +2632,6 @@ bool player::query_yn( const std::string &mes ) const return ::query_yn( mes ); } -int calc_fatigue_cap( int fatigue ) -{ - if( fatigue >= fatigue_levels::massive ) { - return 20; - } else if( fatigue >= fatigue_levels::exhausted ) { - return 40; - } else if( fatigue >= fatigue_levels::dead_tired ) { - return 60; - } else if( fatigue >= fatigue_levels::tired ) { - return 80; - } - return 0; -} - -int player::calc_focus_equilibrium() const -{ - int focus_equilibrium = 100; - - if( activity.id() == ACT_READ ) { - item_location loc = activity.targets[0]; - if( loc && loc->is_book() ) { - auto &bt = *loc->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_equilibrium -= 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(); - } - - // as baseline morale is 100, calc_fatigue_cap() has to -100 to apply accurate penalties. - if( calc_fatigue_cap( this->get_fatigue() ) != 0 && - eff_morale > calc_fatigue_cap( this->get_fatigue() ) - 100 ) { - eff_morale = calc_fatigue_cap( this->get_fatigue() ) - 100; - } - - if( eff_morale < -99 ) { - // At very low morale, focus is at it's minimum - focus_equilibrium = 1; - } else if( eff_morale <= 50 ) { - // At -99 to +50 morale, each point of morale gives or takes 1 point of focus - focus_equilibrium += 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 at 50 morale (as before) - * 200 focus at 150 morale (100 more morale) - * 250 focus 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_equilibrium < 400 ) { - if( morale_left > 50 * block_multiplier ) { - // We can afford the entire block. Get it and continue. - morale_left -= 50 * block_multiplier; - focus_equilibrium += 50; - block_multiplier *= 2; - } else { - // We can't afford the entire block. Each block_multiplier morale - // points give 1 focus, and then we're done. - focus_equilibrium += morale_left / block_multiplier; - break; - } - } - } - - // This should be redundant, but just in case... - if( focus_equilibrium < 1 ) { - focus_equilibrium = 1; - } else if( focus_equilibrium > 400 ) { - focus_equilibrium = 400; - } - return focus_equilibrium; -} - -int player::calc_focus_change() const -{ - int focus_gap = calc_focus_equilibrium() - focus_pool; - - // handle negative gain rates in a symmetric manner - int base_change = 1; - if( focus_gap < 0 ) { - base_change = -1; - focus_gap = -focus_gap; - } - - // 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_gap / 100; - if( rng( 1, 100 ) <= focus_gap % 100 ) { - gain++; - } - - gain *= base_change; - - return gain; -} - -void player::update_mental_focus() -{ - focus_pool += calc_focus_change(); -} - -void player::reset_stats() -{ - const int current_stim = get_stim(); - - // 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( bodypart_id( "eyes" ) ) ) { - mod_per_bonus( 2 ); - } - 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( bodypart_id( "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( bodypart_id( "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 ); - } else if( dur > 0_turns ) { - eff.set_duration( dur ); - } else { - remove_effect( type, num_bp ); - } - }; - // Painkiller - set_fake_effect_dur( effect_pkill, 1_turns * get_painkiller() ); - - // Pain - if( get_perceived_pain() > 0 ) { - const stat_mod ppen = character_effects::get_pain_penalty( *this ); - 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!" ), static_cast( ppen.dexterity ) ); - } - } - - // Radiation - set_fake_effect_dur( effect_irradiated, 1_turns * get_rad() ); - // 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 * current_stim ); - set_fake_effect_dur( effect_depressants, 1_turns * -current_stim ); - if( has_trait( trait_STIMBOOST ) ) { - set_fake_effect_dur( effect_stim_overdose, 1_turns * ( current_stim - 60 ) ); - } else { - set_fake_effect_dur( effect_stim_overdose, 1_turns * ( current_stim - 30 ) ); - } - // Starvation - if( get_kcal_percent() < 0.95f ) { - // kcal->percentage of base str - static const std::vector> starv_thresholds = { { - std::make_pair( 0.0f, 0.5f ), - std::make_pair( 0.8f, 0.1f ), - std::make_pair( 0.95f, 0.0f ) - } - }; - - const int str_penalty = std::floor( multi_lerp( starv_thresholds, get_kcal_percent() ) ); - add_miss_reason( _( "You're weak from hunger." ), - static_cast( str_penalty / 2 ) ); - mod_str_bonus( -str_penalty ); - mod_dex_bonus( -( str_penalty / 2 ) ); - mod_int_bonus( -( str_penalty / 2 ) ); - } - // Thirst - set_fake_effect_dur( effect_thirsty, 1_turns * ( get_thirst() - thirst_levels::very_thirsty ) ); - if( get_sleep_deprivation() >= sleep_deprivation_levels::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( bodypart_id( "mouth" ) ) ) { - mod_dodge_bonus( 1.5 ); - } - if( has_trait( trait_WHISKERS_RAT ) && !wearing_something_on( bodypart_id( "mouth" ) ) ) { - mod_dodge_bonus( 3 ); - } - // depending on mounts size, attacks will hit the mount and use their dodge rating. - // if they hit the player, the player cannot dodge as effectively. - if( is_mounted() ) { - mod_dodge_bonus( -4 ); - } - // 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 parts{ { bodypart_id( "head" ), bodypart_id( "arm_r" ), bodypart_id( "arm_l" ), bodypart_id( "leg_r" ), bodypart_id( "leg_l" ) } }; - for( const bodypart_id &bp : parts ) { - if( !wearing_something_on( bp ) ) { - mod_dodge_bonus( +1 ); - } - } - // Torso handled separately, bigger bonus - if( !wearing_something_on( bodypart_id( "torso" ) ) ) { - mod_dodge_bonus( 4 ); - } - } - - // Apply static martial arts buffs - martial_arts_data->ma_static_effects( *this ); - - if( calendar::once_every( 1_minutes ) ) { - update_mental_focus(); - } - - // Effects - for( const auto &maps : *effects ) { - for( auto i : maps.second ) { - const auto &it = i.second; - if( it.is_removed() ) { - continue; - } - 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(); - -} - safe_reference player::get_safe_reference() { return anchor.reference_to( this ); diff --git a/src/player.h b/src/player.h index 017772d66785..da28826f3d7d 100644 --- a/src/player.h +++ b/src/player.h @@ -80,13 +80,6 @@ template<> struct ret_val::default_failure : public std::integral_constant {}; -struct needs_rates { - float thirst = 0.0f; - float hunger = 0.0f; - float fatigue = 0.0f; - float recovery = 0.0f; -}; - class player : public Character { public: @@ -113,70 +106,22 @@ class player : public Character return this; } - /** Processes human-specific effects of effects before calling Creature::process_effects(). */ - void process_effects_internal() override; - /** Handles the still hard-coded effects. */ - void hardcoded_effects( effect &it ); - /** Returns the modifier value used for vomiting effects. */ - double vomit_mod(); - bool is_npc() const override { return false; // Overloaded for NPCs in npc.h } - /** Returns what color the player should be drawn as */ - nc_color basic_symbol_color() const override; - // populate variables, inventory items, and misc from json object virtual void deserialize( JsonIn &jsin ) = 0; // by default save all contained info virtual void serialize( JsonOut &jsout ) const = 0; - /** Resets movement points and applies other non-idempotent changes */ - void process_turn() override; - /** Calculates the various speed bonuses we will get from mutations, etc. */ - void recalc_speed_bonus(); - - /** Maintains body wetness and handles the rate at which the player dries */ - void update_body_wetness( const w_point &weather ); - - /** Generates and handles the UI for player interaction with installed bionics */ - void power_bionics(); - void power_mutations(); - - /** Returns the bionic with the given invlet, or NULL if no bionic has that invlet */ - bionic *bionic_by_invlet( int ch ); - - /** Called when a player triggers a trap, returns true if they don't set it off */ - bool avoid_trap( const tripoint &pos, const trap &tr ) const override; - - void pause(); // '.' command; pauses & resets recoil - // martialarts.cpp - /** Returns true if the player can learn the entered martial art */ - bool can_autolearn( const matype_id &ma_id ) const; - - /** Returns value of player's stable footing */ - float stability_roll() const override; - /** Returns true if the player has stealthy movement */ - bool is_stealthy() const; - /** Returns true if the current martial art works with the player's current weapon */ - bool can_melee() const; - /** Returns true if the player should be dead */ - bool is_dead_state() const override; - /** Returns true if the player is able to use a grab breaking technique */ bool can_grab_break( const item &weap ) const; // melee.cpp - /** - * Returns a weapon's modified dispersion value. - * @param obj Weapon to check dispersion on - */ - dispersion_sources get_weapon_dispersion( const item &obj ) const; - /** How many moves does it take to aim gun to the target accuracy. */ int gun_engagement_moves( const item &gun, int target = 0, int start = MAX_RECOIL ) const; @@ -197,25 +142,12 @@ class player : public Character */ int fire_gun( const tripoint &target, int shots, item &gun ); - /** Handles reach melee attacks */ - void reach_attack( const tripoint &p ); - /** Called after the player has successfully dodged an attack */ void on_dodge( Creature *source, float difficulty ) override; /** Handles special defenses from an attack that hit us (source can be null) */ void on_hit( Creature *source, bodypart_id bp_hit, float difficulty = INT_MIN, dealt_projectile_attack const *proj = nullptr ) override; - - /** NPC-related item rating functions */ - double weapon_value( const item &weap, int ammo = 10 ) const; // Evaluates item as a weapon - double gun_value( const item &weap, int ammo = 10 ) const; // Evaluates item as a gun - double melee_value( const item &weap ) const; // As above, but only as melee - double unarmed_value() const; // Evaluate yourself! - - /** Returns the player's dodge_roll to be compared against an aggressor's hit_roll() */ - float dodge_roll() override; - /** Returns melee skill level, to be used to throttle dodge practice. **/ float get_melee() const override; @@ -227,12 +159,6 @@ class player : public Character dealt_projectile_attack throw_item( const tripoint &target, const item &to_throw, const cata::optional &blind_throw_from_pos = cata::nullopt ); - // Mental skills and stats - /** Returns a value used when attempting to convince NPC's of something */ - int talk_skill() const; - /** Returns a value used when attempting to intimidate NPC's */ - int intimidation() const; - /** * Check if a given body part is immune to a given damage type * @@ -245,14 +171,6 @@ class player : public Character * @returns true if given damage can not reduce hp of given body part */ bool immune_to( body_part bp, damage_unit dam ) const; - /** Modifies a pain value by player traits before passing it to Creature::mod_pain() */ - void mod_pain( int npain ) override; - /** Sets new intensity of pain an reacts to it */ - void set_pain( int npain ) override; - /** Returns perceived pain (reduced with painkillers)*/ - int get_perceived_pain() const override; - - void add_pain_msg( int val, body_part bp ) const; /** Knocks the player to a specified tile */ void knock_back_to( const tripoint &to ) override; @@ -265,9 +183,6 @@ class player : public Character /** Returns overall % of HP remaining */ int hp_percentage() const override; - /** Returns list of artifacts in player inventory. **/ - std::list get_artifact_items(); - /** used for drinking from hands, returns how many charges were consumed */ int drink_from_hands( item &water ); /** Used for eating object at pos, returns true if object is removed from inventory (last charge was consumed) */ @@ -397,38 +312,8 @@ class player : public Character /** Note that we've read a book at least once. **/ virtual bool has_identified( const itype_id &item_id ) const = 0; - /** Handles sleep attempts by the player, starts ACT_TRY_SLEEP activity */ - void try_to_sleep( const time_duration &dur = 30_minutes ); - /** Rate point's ability to serve as a bed. Takes all mutations, fatigue and stimulants into account. */ - int sleep_spot( const tripoint &p ) const; - /** Checked each turn during "lying_down", returns true if the player falls asleep */ - bool can_sleep(); - - /** Uses morale, pain and fatigue to return the player's focus target goto value */ - int calc_focus_equilibrium() const; - /** Calculates actual focus gain/loss value from focus equilibrium*/ - int calc_focus_change() const; - /** Uses calc_focus_change to update the player's current focus */ - void update_mental_focus(); - /** Resets stats, and applies effects in an idempotent manner */ - void reset_stats() override; - private: safe_reference_anchor anchor; - enum class power_mut_ui_cmd { - Exit, - Activate, - Deactivate, - }; - struct power_mut_ui_result { - power_mut_ui_cmd cmd; - trait_id mut; - }; - power_mut_ui_result power_mutations_ui(); - - /** last time we checked for sleep */ - time_point last_sleep_check = calendar::turn_zero; - bool bio_soporific_powered_at_last_sleep_check = false; public: safe_reference get_safe_reference(); @@ -444,10 +329,6 @@ class player : public Character void on_worn_item_transform( const item &old_it, const item &new_it ); - /** Get the formatted name of the currently wielded item (if any) with current gun mode (if gun) */ - std::string weapname() const; - - void process_items(); /** * Remove charges from a specific item (given by its item position). * The item must exist and it must be counted by charges. @@ -466,13 +347,6 @@ class player : public Character */ item reduce_charges( item *it, int quantity ); - /** - * Check whether player has a bionic power armor interface. - * @return true if player has an active bionic capable of powering armor, false otherwise. - */ - bool can_interface_armor() const; - - bool has_mission_item( int mission_id ) const; // Has item with mission_id /** * Check whether the player has a gun that uses the given type of ammo. */ @@ -617,10 +491,6 @@ class player : public Character std::set follower_ids; void mod_stat( const std::string &stat, float modifier ) override; - void set_underwater( bool ); - bool is_hallucination() const override; - void environmental_revert_effect(); - //message related stuff using Character::add_msg_if_player; void add_msg_if_player( const std::string &msg ) const override; @@ -636,9 +506,6 @@ class player : public Character void add_msg_player_or_say( const game_message_params ¶ms, const std::string &player_msg, const std::string &npc_speech ) const override; - /** Search surrounding squares for traps (and maybe other things in the future). */ - void search_surroundings(); - using Character::query_yn; bool query_yn( const std::string &mes ) const override; @@ -655,9 +522,6 @@ class player : public Character void store( JsonOut &json ) const; void load( const JsonObject &data ); - /** Processes human-specific effects of an effect. */ - void process_one_effect( effect &it, bool is_new ) override; - private: /** @@ -673,7 +537,4 @@ class player : public Character std::map> warning_record; }; -/** Calculates the player's morale cap due to fatigue */ -int calc_fatigue_cap( int fatigue ); - #endif // CATA_SRC_PLAYER_H diff --git a/src/player_activity.cpp b/src/player_activity.cpp index c70ec9bcde1f..16d92e9da6f1 100644 --- a/src/player_activity.cpp +++ b/src/player_activity.cpp @@ -12,6 +12,7 @@ #include "avatar.h" #include "calendar.h" #include "character.h" +#include "character_turn.h" #include "color.h" #include "construction.h" #include "construction_partial.h" @@ -345,7 +346,7 @@ void player_activity::do_turn( player &p ) } if( *this && type->rooted() ) { p.rooted(); - p.pause(); + character_funcs::do_pause( p ); } if( *this && moves_left <= 0 ) { diff --git a/src/player_hardcoded_effects.cpp b/src/player_hardcoded_effects.cpp index b98adea95e23..346ade7e9b52 100644 --- a/src/player_hardcoded_effects.cpp +++ b/src/player_hardcoded_effects.cpp @@ -7,6 +7,8 @@ #include "activity_handlers.h" #include "avatar.h" #include "character.h" +#include "character_effects.h" +#include "character_functions.h" #include "damage.h" #include "effect.h" #include "enums.h" @@ -155,7 +157,8 @@ static void eff_fun_fungus( player &u, effect &it ) u.moves -= 100; u.apply_damage( nullptr, bodypart_id( "torso" ), 5 ); } - if( x_in_y( u.vomit_mod(), ( 4800 + bonus * 24 ) ) || one_in( 12000 + bonus * 60 ) ) { + if( x_in_y( character_effects::vomit_mod( u ), ( 4800 + bonus * 24 ) ) || + one_in( 12000 + bonus * 60 ) ) { u.add_msg_player_or_npc( m_bad, _( "You vomit a thick, gray goop." ), _( " vomits a thick, gray goop." ) ); @@ -243,7 +246,8 @@ static void eff_fun_hallu( player &u, effect &it ) u.add_msg_if_player( m_warning, _( "Something feels very, very wrong." ) ); } } else if( dur > peakTime && dur < comeupTime ) { - if( u.stomach.get_calories() > 0 && ( one_in( 1200 ) || x_in_y( u.vomit_mod(), 300 ) ) ) { + if( u.stomach.get_calories() > 0 && + ( one_in( 1200 ) || x_in_y( character_effects::vomit_mod( u ), 300 ) ) ) { u.add_msg_if_player( m_bad, _( "You feel sick to your stomach." ) ); u.mod_stored_kcal( 20 ); if( one_in( 6 ) ) { @@ -465,7 +469,7 @@ static void eff_fun_mutating( player &u, effect &it ) } } -void player::hardcoded_effects( effect &it ) +void Character::hardcoded_effects( effect &it ) { if( auto buff = ma_buff::from_effect( it ) ) { if( buff->is_valid_character( *this ) ) { @@ -494,7 +498,7 @@ void player::hardcoded_effects( effect &it ) const efftype_id &id = it.get_id(); const auto &iter = hc_effect_map.find( id ); if( iter != hc_effect_map.end() ) { - iter->second( *this, it ); + iter->second( *as_player(), it ); return; } @@ -858,7 +862,7 @@ void player::hardcoded_effects( effect &it ) _( "You're experiencing loss of basic motor skills and blurred vision. Your mind recoils in horror, unable to communicate with your spinal column." ) ); add_msg_if_player( m_bad, _( "You stagger and fall!" ) ); add_effect( effect_downed, rng( 1_turns, 4_turns ), num_bp, 0, true ); - if( one_in( 8 ) || x_in_y( vomit_mod(), 10 ) ) { + if( one_in( 8 ) || x_in_y( character_effects::vomit_mod( *this ), 10 ) ) { vomit(); } } @@ -886,7 +890,7 @@ void player::hardcoded_effects( effect &it ) if( one_in( 8 ) ) { add_msg_if_player( m_bad, _( "The possibility of physical and mental collapse is now very real." ) ); - if( one_in( 2 ) || x_in_y( vomit_mod(), 10 ) ) { + if( one_in( 2 ) || x_in_y( character_effects::vomit_mod( *this ), 10 ) ) { add_msg_if_player( m_bad, _( "No one should be asked to handle this trip." ) ); vomit(); mod_pain( rng( 8, 40 ) ); @@ -1045,7 +1049,7 @@ void player::hardcoded_effects( effect &it ) } } else if( id == effect_lying_down ) { set_moves( 0 ); - if( can_sleep() ) { + if( character_funcs::roll_can_sleep( *this ) ) { fall_asleep(); // Set ourselves up for removal it.set_duration( 0_turns ); diff --git a/src/ranged.cpp b/src/ranged.cpp index 8cd7f244455f..e0bcdfab0cc6 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1853,7 +1853,7 @@ item::sound_data item::gun_noise( const bool burst ) const return { 0, "" }; // silent weapons } -static bool is_driving( const player &p ) +static bool is_driving( const Character &p ) { const optional_vpart_position vp = get_map().veh_at( p.pos() ); return vp && vp->vehicle().is_moving() && vp->vehicle().player_in_control( p ); @@ -1882,7 +1882,7 @@ static double dispersion_from_skill( double skill, double weapon_dispersion ) } // utility functions for projectile_attack -dispersion_sources player::get_weapon_dispersion( const item &obj ) const +dispersion_sources Character::get_weapon_dispersion( const item &obj ) const { int weapon_dispersion = obj.gun_dispersion(); dispersion_sources dispersion( weapon_dispersion ); @@ -1932,7 +1932,7 @@ dispersion_sources player::get_weapon_dispersion( const item &obj ) const return dispersion; } -double player::gun_value( const item &weap, int ammo ) const +double npc_ai::gun_value( const Character &who, const item &weap, int ammo ) { // TODO: Mods // TODO: Allow using a specified type of ammo rather than default or current @@ -1958,8 +1958,8 @@ double player::gun_value( const item &weap, int ammo ) const damage_instance gun_damage = weap.gun_damage(); item tmp = weap; tmp.ammo_set( ammo_type ); - int total_dispersion = get_weapon_dispersion( tmp ).max() + - effective_dispersion( tmp.sight_dispersion() ); + int total_dispersion = who.get_weapon_dispersion( tmp ).max() + + who.effective_dispersion( tmp.sight_dispersion() ); if( def_ammo_i != nullptr && def_ammo_i->ammo ) { const islot_ammo &def_ammo = *def_ammo_i->ammo; @@ -1973,10 +1973,10 @@ double player::gun_value( const item &weap, int ammo ) const damage_factor += 0.5f * gun_damage.damage_units.front().res_pen; } - int move_cost = time_to_attack( *this, *weap.type ); + int move_cost = time_to_attack( who, *weap.type ); if( gun.clip != 0 && gun.clip < 10 ) { // TODO: RELOAD_ONE should get a penalty here - int reload_cost = gun.reload_time + encumb( bp_hand_l ) + encumb( bp_hand_r ); + int reload_cost = gun.reload_time + who.encumb( bp_hand_l ) + who.encumb( bp_hand_r ); reload_cost /= gun.clip; move_cost += reload_cost; } @@ -2015,7 +2015,7 @@ double player::gun_value( const item &weap, int ammo ) const // Penalty for dodging in melee makes the gun unusable in melee // Until NPCs get proper kiting, at least - int melee_penalty = weapon.volume() / 250_ml - get_skill_level( skill_dodge ); + int melee_penalty = who.weapon.volume() / 250_ml - who.get_skill_level( skill_dodge ); if( melee_penalty <= 0 ) { // Dispersion matters less if you can just use the gun in melee total_dispersion = std::min( total_dispersion / move_cost_factor, total_dispersion ); diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 4d31ebadedf2..bf7d22a8fae3 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -332,7 +332,7 @@ void character_id::deserialize( JsonIn &jsin ) //////////////////////////////////////////////////////////////////////////////////////////////////// ///// Character.h, avatar + npc -void Character::trait_data::serialize( JsonOut &json ) const +void char_trait_data::serialize( JsonOut &json ) const { json.start_object(); json.member( "key", key ); @@ -341,7 +341,7 @@ void Character::trait_data::serialize( JsonOut &json ) const json.end_object(); } -void Character::trait_data::deserialize( JsonIn &jsin ) +void char_trait_data::deserialize( JsonIn &jsin ) { JsonObject data = jsin.get_object(); data.allow_omitted_members(); @@ -501,8 +501,6 @@ void Character::load( const JsonObject &data ) data.read( "magic", magic ); JsonArray parray; - data.read( "underwater", underwater ); - data.read( "traits", my_traits ); for( auto it = my_traits.begin(); it != my_traits.end(); ) { const auto &tid = *it; @@ -721,7 +719,6 @@ void Character::store( JsonOut &json ) const json.member( "type_of_scent", type_of_scent ); // breathing - json.member( "underwater", underwater ); json.member( "oxygen", oxygen ); // traits: permanent 'mutations' more or less @@ -1989,7 +1986,6 @@ void monster::store( JsonOut &json ) const // Store the relative position of the goal so it loads correctly after a map shift. json.member( "destination", goal - pos() ); json.member( "ammo", ammo ); - json.member( "underwater", underwater ); json.member( "upgrades", upgrades ); json.member( "upgrade_time", upgrade_time ); json.member( "last_updated", last_updated ); @@ -3049,6 +3045,8 @@ void Creature::store( JsonOut &jsout ) const jsout.member( "block_bonus", block_bonus ); jsout.member( "hit_bonus", hit_bonus ); + jsout.member( "underwater", underwater ); + jsout.member( "body", body ); // fake is not stored, it's temporary anyway, only used to fire with a gun. diff --git a/src/suffer.cpp b/src/suffer.cpp index e218cf4cc878..e4621c837ae9 100644 --- a/src/suffer.cpp +++ b/src/suffer.cpp @@ -14,6 +14,7 @@ #include "addiction.h" #include "avatar.h" +#include "bionics.h" #include "bodypart.h" #include "calendar.h" #include "cata_utility.h" @@ -199,8 +200,7 @@ void Character::suffer_water_damage( const mutation_branch &mdata ) } } -void Character::suffer_mutation_power( const mutation_branch &mdata, - Character::trait_data &tdata ) +void Character::suffer_mutation_power( const mutation_branch &mdata, char_trait_data &tdata ) { if( tdata.powered && tdata.charge > 0 ) { // Already-on units just lose a bit of charge @@ -627,7 +627,7 @@ void Character::suffer_from_asthma( const int current_stim ) bool auto_use = has_charges( itype_inhaler, 1 ) || has_charges( itype_oxygen_tank, 1 ) || has_charges( itype_smoxygen_tank, 1 ); bool oxygenator = has_bionic( bio_gills ) && get_power_level() >= 3_kJ; - if( underwater ) { + if( is_underwater() ) { oxygen = oxygen / 2; auto_use = false; } @@ -1477,22 +1477,22 @@ void Character::suffer() } } - for( size_t i = 0; i < get_bionics().size(); i++ ) { - process_bionic( i ); + for( bionic &bio : *my_bionics ) { + process_bionic( bio ); } - for( std::pair &mut : my_mutations ) { + for( std::pair &mut : my_mutations ) { const mutation_branch &mdata = mut.first.obj(); if( calendar::once_every( 1_minutes ) ) { suffer_water_damage( mdata ); } - Character::trait_data &tdata = mut.second; + char_trait_data &tdata = mut.second; if( tdata.powered ) { suffer_mutation_power( mdata, tdata ); } } - if( underwater ) { + if( is_underwater() ) { suffer_while_underwater(); } diff --git a/tests/mondefense_test.cpp b/tests/mondefense_test.cpp index 26363f0587ac..77160e91b56a 100644 --- a/tests/mondefense_test.cpp +++ b/tests/mondefense_test.cpp @@ -1,5 +1,6 @@ #include "catch/catch.hpp" +#include "bionics.h" #include "creature.h" #include "item.h" #include "mondefense.h" @@ -97,7 +98,7 @@ TEST_CASE( "zapback_npc_electricity_immune", "[mondefense]" ) // Don't forget to turn it on... test_zapback( attacker, true ); // Wow this is a raw index? - attacker.activate_bionic( 0 ); + attacker.activate_bionic( *attacker.my_bionics->begin() ); test_zapback( attacker, false ); } diff --git a/tests/player_helpers.cpp b/tests/player_helpers.cpp index 9b1399c1e4b2..06f0d5911bdf 100644 --- a/tests/player_helpers.cpp +++ b/tests/player_helpers.cpp @@ -149,7 +149,7 @@ npc &spawn_npc( const point &p, const std::string &npc_class ) return *guy; } -void give_and_activate_bionic( player &p, bionic_id const &bioid ) +void give_and_activate_bionic( player &p, const bionic_id &bioid ) { INFO( "bionic " + bioid.str() + " is valid" ); REQUIRE( bioid.is_valid() ); @@ -158,17 +158,7 @@ void give_and_activate_bionic( player &p, bionic_id const &bioid ) INFO( "dummy has gotten " + bioid.str() + " bionic " ); REQUIRE( p.has_bionic( bioid ) ); - // get bionic's index - might not be "last added" due to "integrated" ones - int bioindex = -1; - for( size_t i = 0; i < p.my_bionics->size(); i++ ) { - const auto &bio = ( *p.my_bionics )[ i ]; - if( bio.id == bioid ) { - bioindex = i; - } - } - REQUIRE( bioindex != -1 ); - - const bionic &bio = p.bionic_at_index( bioindex ); + bionic &bio = p.get_bionic_state( bioid ); REQUIRE( bio.id == bioid ); // turn on if possible @@ -177,8 +167,8 @@ void give_and_activate_bionic( player &p, bionic_id const &bioid ) if( !fuel_opts.empty() ) { p.set_value( fuel_opts.front().str(), "2" ); } - p.activate_bionic( bioindex ); - INFO( "bionic " + bio.id.str() + " with index " + std::to_string( bioindex ) + " is active " ); + p.activate_bionic( bio ); + INFO( "bionic " + bio.id.str() + " is active " ); REQUIRE( p.has_active_bionic( bioid ) ); if( !fuel_opts.empty() ) { p.remove_value( fuel_opts.front().str() ); diff --git a/tests/player_helpers.h b/tests/player_helpers.h index a38aaacdac92..27cd943d29ee 100644 --- a/tests/player_helpers.h +++ b/tests/player_helpers.h @@ -18,7 +18,7 @@ void clear_avatar(); void process_activity( player &dummy ); npc &spawn_npc( const point &, const std::string &npc_class ); -void give_and_activate_bionic( player &, bionic_id const & ); +void give_and_activate_bionic( player &p, const bionic_id &bioid ); void arm_character( player &shooter, const std::string &gun_type, const std::vector &mods = {},