From 46e2ec79da4ad652f7e9f44c0b24f4b146aeb153 Mon Sep 17 00:00:00 2001 From: davidpwbrown <39344466+davidpwbrown@users.noreply.github.com> Date: Thu, 14 Mar 2019 03:35:38 +0000 Subject: [PATCH] Follower / Faction camp summary menu and migrate to overmap (#28498) * added NPC summary window added ability to chat to NPC from menu added mission description to NPC list added camp labels and camp names moved camps to overmap get camp info works from overmapbuffer now made the om_camps accept pointers added mission description to camp menu --- src/basecamp.cpp | 83 ++++++++-- src/basecamp.h | 24 +-- src/character.cpp | 81 ++++++++++ src/character.h | 3 + src/faction.cpp | 331 +++++++++++++++++++++++++++++++++++++- src/faction.h | 8 + src/faction_camp.cpp | 28 ++-- src/faction_camp.h | 2 + src/game.cpp | 68 ++++++++ src/game.h | 13 ++ src/handle_action.cpp | 2 +- src/map.cpp | 13 +- src/mission_companion.cpp | 1 + src/mission_start.cpp | 1 - src/npc.cpp | 51 ++++++ src/npc.h | 3 + src/npctalk.cpp | 6 + src/npctalk_funcs.cpp | 2 + src/overmap.cpp | 11 ++ src/overmap.h | 5 +- src/overmap_ui.cpp | 44 ++++- src/overmapbuffer.cpp | 68 ++++++++ src/overmapbuffer.h | 15 +- src/savegame_json.cpp | 5 +- src/sidebar.cpp | 72 +-------- 25 files changed, 826 insertions(+), 114 deletions(-) diff --git a/src/basecamp.cpp b/src/basecamp.cpp index d3a961b0ef46a..c6db3c8093cab 100644 --- a/src/basecamp.cpp +++ b/src/basecamp.cpp @@ -11,11 +11,13 @@ #include "string_formatter.h" #include "translations.h" #include "enums.h" +#include "game.h" #include "item_group.h" #include "map.h" #include "map_iterator.h" #include "mapbuffer.h" #include "mapdata.h" +#include "messages.h" #include "overmap.h" #include "overmap_ui.h" #include "overmapbuffer.h" @@ -24,6 +26,7 @@ #include "recipe_groups.h" #include "requirements.h" #include "skill.h" +#include "string_input_popup.h" #include "faction_camp.h" static const std::string base_dir = "[B]"; @@ -54,16 +57,15 @@ basecamp::basecamp(): bb_pos( tripoint_zero ) { } -basecamp::basecamp( const std::string &name_, const tripoint &pos_ ): name( name_ ), pos( pos_ ) +basecamp::basecamp( const std::string &name_, const tripoint &omt_pos_ ): name( name_ ), + omt_pos( omt_pos_ ) { } -basecamp::basecamp( const std::string &name_, const tripoint &bb_pos_, const tripoint &pos_, - std::vector sort_points_, - std::vector directions_, - std::map expansions_ ): - sort_points( sort_points_ ), directions( directions_ ), name( name_ ), - pos( pos_ ), bb_pos( bb_pos_ ), expansions( expansions_ ) +basecamp::basecamp( const std::string &name_, const tripoint &bb_pos_, + std::vector sort_points_, std::vector directions_, + std::map expansions_ ): sort_points( sort_points_ ), + directions( directions_ ), name( name_ ), bb_pos( bb_pos_ ), expansions( expansions_ ) { } @@ -93,32 +95,63 @@ void basecamp::add_expansion( const std::string &terrain, const tripoint &new_po return; } - const std::string dir = talk_function::om_simple_dir( pos, new_pos ); + const std::string dir = talk_function::om_simple_dir( omt_pos, new_pos ); expansions[ dir ] = parse_expansion( terrain, new_pos ); directions.push_back( dir ); } void basecamp::define_camp( npc &p ) { - pos = p.global_omt_location(); + query_new_name(); + omt_pos = p.global_omt_location(); sort_points = p.companion_mission_points; // purging the regions guarantees all entries will start with faction_base_ - for( std::pair expansion : talk_function::om_building_region( pos, 1, + for( std::pair expansion : talk_function::om_building_region( omt_pos, 1, true ) ) { add_expansion( expansion.first, expansion.second ); } - const std::string om_cur = overmap_buffer.ter( pos ).id().c_str(); + const std::string om_cur = overmap_buffer.ter( omt_pos ).id().c_str(); if( om_cur.find( prefix ) == std::string::npos ) { expansion_data e; e.type = "camp"; e.cur_level = 0; - e.pos = pos; + e.pos = omt_pos; expansions[ base_dir ] = e; } else { - expansions[ base_dir ] = parse_expansion( om_cur, pos ); + expansions[ base_dir ] = parse_expansion( om_cur, omt_pos ); } } +/// Returns the description for the recipe of the next building @ref bldg +std::string basecamp::om_upgrade_description( const std::string &bldg, bool trunc ) +{ + const recipe &making = recipe_id( bldg ).obj(); + const inventory &total_inv = g->u.crafting_inventory(); + + std::vector component_print_buffer; + const int pane = FULL_SCREEN_WIDTH; + const auto tools = making.requirements().get_folded_tools_list( pane, c_white, total_inv, 1 ); + const auto comps = making.requirements().get_folded_components_list( pane, c_white, total_inv, 1 ); + component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() ); + component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() ); + + std::string comp; + for( auto &elem : component_print_buffer ) { + comp = comp + elem + "\n"; + } + time_duration duration = time_duration::from_turns( making.time / 100 ); + if( trunc ) { + comp = string_format( _( "Notes:\n%s\n\nSkill used: %s\n%s\n" ), + making.description, making.skill_used.obj().name(), comp ); + } else { + comp = string_format( _( "Notes:\n%s\n\nSkill used: %s\n" + "Difficulty: %d\n%s \nRisk: None\nTime: %s\n" ), + making.description, making.skill_used.obj().name(), + making.difficulty, comp, to_string( duration ) ); + } + return comp; +} + // upgrade levels bool basecamp::has_level( const std::string &type, int min_level, const std::string &dir ) const { @@ -204,7 +237,7 @@ void basecamp::reset_camp_workers() camp_workers.clear(); for( const auto &elem : overmap_buffer.get_companion_mission_npcs() ) { npc_companion_mission c_mission = elem->get_companion_mission(); - if( c_mission.position == pos && c_mission.role_id == "FACTION_CAMP" ) { + if( c_mission.position == omt_pos && c_mission.role_id == "FACTION_CAMP" ) { camp_workers.push_back( elem ); } } @@ -224,6 +257,28 @@ comp_list basecamp::get_mission_workers( const std::string &mission_id, bool con return available; } +void basecamp::query_new_name() +{ + std::string camp_name; + string_input_popup popup; + popup.title( string_format( _( "Name this camp" ) ) ) + .width( 40 ) + .text( "" ) + .max_length( 25 ) + .query(); + if( popup.canceled() || popup.text() == "" ) { + camp_name = "faction_camp"; + } else { + camp_name = popup.text(); + } + name = camp_name; +} + +void basecamp::set_name( const std::string &new_name ) +{ + name = new_name; +} + // display names std::string basecamp::expansion_tab( const std::string &dir ) const { diff --git a/src/basecamp.h b/src/basecamp.h index 57462087d261b..d41ca0e075f5e 100644 --- a/src/basecamp.h +++ b/src/basecamp.h @@ -32,13 +32,12 @@ class basecamp { public: basecamp(); - basecamp( const std::string &name_, const tripoint &pos_ ); - basecamp( const std::string &name_, const tripoint &bb_pos_, const tripoint &pos_, - std::vector sort_points_, std::vector directions_, - std::map expansions_ ); + basecamp( const std::string &name_, const tripoint &omt_pos ); + basecamp( const std::string &name_, const tripoint &bb_pos_, std::vector sort_points_, + std::vector directions_, std::map expansions_ ); inline bool is_valid() const { - return !name.empty() && pos != tripoint_zero; + return !name.empty() && omt_pos != tripoint_zero; } inline int board_x() const { return bb_pos.x; @@ -46,8 +45,8 @@ class basecamp inline int board_y() const { return bb_pos.y; } - tripoint camp_pos() const { - return pos; + inline tripoint camp_omt_pos() const { + return omt_pos; } inline const std::string &camp_name() const { return name; @@ -55,7 +54,10 @@ class basecamp std::string board_name() const; std::vector sort_points; std::vector directions; - + std::string name; + //change name of camp + void set_name( const std::string &new_name ); + void query_new_name(); void add_expansion( const std::string &terrain, const tripoint &new_pos ); void define_camp( npc &p ); @@ -120,6 +122,7 @@ class basecamp const std::vector &equipment, const std::string &skill_tested, int skill_level ); void start_upgrade( const std::string &bldg, const std::string &key ); + std::string om_upgrade_description( const std::string &bldg, bool trunc ); /// Called when a companion is sent to cut logs void start_cut_logs(); void start_clearcut(); @@ -172,9 +175,8 @@ class basecamp void deserialize( JsonIn &jsin ); void load_data( const std::string &data ); private: - std::string name; - // location of the camp in the overmap - tripoint pos; + // omt pos + tripoint omt_pos; // location of associated bulletin board tripoint bb_pos; std::map expansions; diff --git a/src/character.cpp b/src/character.cpp index 48c1dcd91b1f6..26acd5156d6f3 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1909,6 +1909,87 @@ int Character::get_thirst() const return thirst; } +std::pair Character::get_thirst_description() const +{ + int thirst = get_thirst(); + std::string hydration_string; + nc_color hydration_color = c_white; + if( thirst > 520 ) { + hydration_color = c_light_red; + hydration_string = _( "Parched" ); + } else if( thirst > 240 ) { + hydration_color = c_light_red; + hydration_string = _( "Dehydrated" ); + } else if( thirst > 80 ) { + hydration_color = c_yellow; + hydration_string = _( "Very thirsty" ); + } else if( thirst > 40 ) { + hydration_color = c_yellow; + hydration_string = _( "Thirsty" ); + } else if( thirst < -60 ) { + hydration_color = c_green; + hydration_string = _( "Turgid" ); + } else if( thirst < -20 ) { + hydration_color = c_green; + hydration_string = _( "Hydrated" ); + } else if( thirst < 0 ) { + hydration_color = c_green; + hydration_string = _( "Slaked" ); + } + return std::make_pair( hydration_string, hydration_color ); +} + +std::pair Character::get_hunger_description() const +{ + int hunger = get_hunger(); + std::string hunger_string; + nc_color hunger_color = c_white; + if( hunger >= 300 && get_starvation() > 2500 ) { + hunger_color = c_red; + hunger_string = _( "Starving!" ); + } else if( hunger >= 300 && get_starvation() > 1100 ) { + hunger_color = c_light_red; + hunger_string = _( "Near starving" ); + } else if( hunger > 250 ) { + hunger_color = c_light_red; + hunger_string = _( "Famished" ); + } else if( hunger > 100 ) { + hunger_color = c_yellow; + hunger_string = _( "Very hungry" ); + } else if( hunger > 40 ) { + hunger_color = c_yellow; + hunger_string = _( "Hungry" ); + } else if( hunger < -60 ) { + hunger_color = c_green; + hunger_string = _( "Engorged" ); + } else if( hunger < -20 ) { + hunger_color = c_green; + hunger_string = _( "Sated" ); + } else if( hunger < 0 ) { + hunger_color = c_green; + hunger_string = _( "Full" ); + } + return std::make_pair( hunger_string, hunger_color ); +} + +std::pair Character::get_fatigue_description() const +{ + int fatigue = get_fatigue(); + std::string fatigue_string; + nc_color fatigue_color = c_white; + if( fatigue > EXHAUSTED ) { + fatigue_color = c_red; + fatigue_string = _( "Exhausted" ); + } else if( fatigue > DEAD_TIRED ) { + fatigue_color = c_light_red; + fatigue_string = _( "Dead Tired" ); + } else if( fatigue > TIRED ) { + fatigue_color = c_yellow; + fatigue_string = _( "Tired" ); + } + return std::make_pair( fatigue_string, fatigue_color ); +} + void Character::mod_thirst( int nthirst ) { set_thirst( thirst + nthirst ); diff --git a/src/character.h b/src/character.h index 364db73b3d7a1..1647243d87b01 100644 --- a/src/character.h +++ b/src/character.h @@ -220,6 +220,9 @@ class Character : public Creature, public visitable virtual int get_hunger() const; virtual int get_starvation() const; virtual int get_thirst() const; + virtual std::pair get_thirst_description() const; + virtual std::pair get_hunger_description() const; + virtual std::pair get_fatigue_description() const; virtual int get_fatigue() const; virtual int get_sleep_deprivation() const; virtual int get_stomach_food() const; diff --git a/src/faction.cpp b/src/faction.cpp index daca35a892b5b..ff7db5677e31c 100644 --- a/src/faction.cpp +++ b/src/faction.cpp @@ -1,19 +1,36 @@ #include "faction.h" +#include #include #include #include +#include #include +#include "basecamp.h" #include "catacharset.h" +#include "coordinate_conversions.h" #include "cursesdef.h" #include "debug.h" #include "enums.h" +#include "faction_camp.h" +#include "game.h" #include "game_constants.h" #include "input.h" #include "json.h" +#include "line.h" +#include "map.h" +#include "messages.h" +#include "mission.h" +#include "npc.h" +#include "npctalk.h" +#include "omdata.h" #include "output.h" +#include "overmap.h" +#include "overmapbuffer.h" +#include "player.h" #include "rng.h" +#include "skill.h" #include "string_formatter.h" #include "translations.h" @@ -924,6 +941,24 @@ std::string fac_food_supply_text( int val, int size ) return _( "Starving" ); } +nc_color get_food_supply_color( int val, int size ) +{ + nc_color col; + val = val / ( size * 288 ); + if( val >= 30 ) { + col = c_green; + } else if( val >= 14 ) { + col = c_light_green; + } else if( val >= 6 ) { + col = c_yellow; + } else if( val >= 3 ) { + col = c_light_red; + } else { + col = c_red; + } + return col; +} + std::string fac_combat_ability_text( int val ) { if( val >= 150 ) { @@ -978,7 +1013,7 @@ faction *faction_manager::get( const faction_id &id ) debugmsg( "Requested non-existing faction '%s'", id.str() ); return nullptr; } - +// this is legacy and un-used, but will be incorporated into proper factions void faction_manager::display() const { std::vector valfac; // Factions that we know of. @@ -1053,3 +1088,297 @@ void faction_manager::display() const } } } +void new_faction_manager::display() const +{ + catacurses::window w_missions = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, + ( TERMY > FULL_SCREEN_HEIGHT ) ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0, + ( TERMX > FULL_SCREEN_WIDTH ) ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ); + + enum class tab_mode : int { + TAB_MYFACTION = 0, + TAB_FOLLOWERS, + TAB_OTHERFACTIONS, + NUM_TABS, + FIRST_TAB = 0, + LAST_TAB = NUM_TABS - 1 + }; + g->validate_camps(); + tab_mode tab = tab_mode::FIRST_TAB; + const int entries_per_page = FULL_SCREEN_HEIGHT - 4; + size_t selection = 0; + input_context ctxt( "FACTION MANAGER" ); + ctxt.register_cardinal(); + ctxt.register_updown(); + ctxt.register_action( "ANY_INPUT" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "QUIT" ); + const tripoint player_abspos = g->u.global_omt_location(); + while( true ) { + werase( w_missions ); + // create a list of NPCs, visible and the ones on overmapbuffer + g->validate_npc_followers(); + std::vector followers; + for( auto &elem : g->get_follower_list() ) { + std::shared_ptr npc_to_get = overmap_buffer.find_npc( elem ); + npc *npc_to_add = npc_to_get.get(); + followers.push_back( npc_to_add ); + } + npc *guy = nullptr; + bool interactable = false; + basecamp *camp = nullptr; + // create a list of faction camps + std::vector camps; + for( auto elem : g->u.camps ) { + cata::optional p = overmap_buffer.find_camp( elem.x, elem.y ); + if( !p ) { + continue; + } + basecamp *temp_camp = *p; + camps.push_back( temp_camp ); + } + if( tab < tab_mode::FIRST_TAB || tab >= tab_mode::NUM_TABS ) { + debugmsg( "The sanity check failed because tab=%d", ( int )tab ); + tab = tab_mode::FIRST_TAB; + } + int active_vec_size; + // entries_per_page * page number + const int top_of_page = entries_per_page * ( selection / entries_per_page ); + if( tab == tab_mode::TAB_FOLLOWERS ) { + if( followers.size() > 0 ) { + guy = followers[selection]; + } + active_vec_size = followers.size(); + } else if( tab == tab_mode::TAB_MYFACTION ) { + if( camps.size() > 0 ) { + camp = camps[selection]; + } + active_vec_size = camps.size(); + } else { + active_vec_size = camps.size(); + } + for( int i = 1; i < FULL_SCREEN_WIDTH - 1; i++ ) { + mvwputch( w_missions, 2, i, BORDER_COLOR, LINE_OXOX ); + mvwputch( w_missions, FULL_SCREEN_HEIGHT - 1, i, BORDER_COLOR, LINE_OXOX ); + + if( i > 2 && i < FULL_SCREEN_HEIGHT - 1 ) { + mvwputch( w_missions, i, 30, BORDER_COLOR, LINE_XOXO ); + mvwputch( w_missions, i, FULL_SCREEN_WIDTH - 1, BORDER_COLOR, LINE_XOXO ); + } + } + draw_tab( w_missions, 7, _( "YOUR FACTION" ), tab == tab_mode::TAB_MYFACTION ); + draw_tab( w_missions, 30, _( "YOUR FOLLOWERS" ), tab == tab_mode::TAB_FOLLOWERS ); + draw_tab( w_missions, 56, _( "OTHER FACTIONS" ), tab == tab_mode::TAB_OTHERFACTIONS ); + + mvwputch( w_missions, 2, 0, BORDER_COLOR, LINE_OXXO ); // |^ + mvwputch( w_missions, 2, FULL_SCREEN_WIDTH - 1, BORDER_COLOR, LINE_OOXX ); // ^| + + mvwputch( w_missions, FULL_SCREEN_HEIGHT - 1, 0, BORDER_COLOR, LINE_XXOO ); // | + mvwputch( w_missions, FULL_SCREEN_HEIGHT - 1, FULL_SCREEN_WIDTH - 1, BORDER_COLOR, + LINE_XOOX ); // _| + mvwputch( w_missions, 2, 30, BORDER_COLOR, + tab == tab_mode::TAB_FOLLOWERS ? LINE_XOXX : LINE_XXXX ); // + || -| + mvwputch( w_missions, FULL_SCREEN_HEIGHT - 1, 30, BORDER_COLOR, LINE_XXOX ); // _|_ + const nc_color col = c_white; + switch( tab ) { + case tab_mode::TAB_MYFACTION: + if( active_vec_size > 0 ) { + draw_scrollbar( w_missions, selection, entries_per_page, active_vec_size, 3, 0 ); + for( int i = top_of_page; i <= ( active_vec_size - 1 ); i++ ) { + const auto camp = camps[i]; + std::string camp_name = camp->camp_name(); + const int y = i - top_of_page + 3; + trim_and_print( w_missions, y, 1, 28, static_cast( selection ) == i ? hilite( col ) : col, + camp_name ); + } + if( selection < camps.size() ) { + int y = 2; + tripoint camp_pos = camp->camp_omt_pos(); + std::string direction = direction_name( direction_from( + player_abspos, camp_pos ) ); + std::string centerstring = "center"; + if( ( !direction.compare( centerstring ) ) == 0 ) { + mvwprintz( w_missions, ++y, 31, c_light_gray, + _( "Direction : to the " ) + direction ); + } + mvwprintz( w_missions, ++y, 31, col, _( "Location : (%d, %d)" ), camp_pos.x, camp_pos.y ); + faction *yours = g->faction_manager_ptr->get( faction_id( "your_followers" ) ); + std::string calorie_value = ( std::to_string( yours->food_supply ) ) + _( " calories" ); + std::string food_supply = fac_food_supply_text( yours->food_supply, yours->size ); + std::string food_supply_text = _( "Food Supply : " ) + food_supply; + std::string food_supply_full = food_supply_text + " " + calorie_value; + nc_color food_col = get_food_supply_color( yours->food_supply, yours->size ); + mvwprintz( w_missions, ++y, 31, food_col, food_supply_full ); + const std::string base_dir = "[B]"; + std::string bldg = camp->next_upgrade( base_dir ); + std::string bldg_full = _( "Next Upgrade : " ) + bldg; + mvwprintz( w_missions, ++y, 31, col, bldg_full ); + std::string requirements = camp->om_upgrade_description( bldg, true ); + fold_and_print( w_missions, ++y, 31, getmaxx( w_missions ) - 33, col, + ( requirements ) ); + } else { + static const std::string nope = _( "You have no camps" ); + mvwprintz( w_missions, 4, 31, c_light_red, nope ); + } + break; + } else { + static const std::string nope = _( "You have no camps" ); + mvwprintz( w_missions, 4, 31, c_light_red, nope ); + } + break; + case tab_mode::TAB_FOLLOWERS: + if( followers.size() > 0 ) { + draw_scrollbar( w_missions, selection, entries_per_page, active_vec_size, 3, 0 ); + for( int i = top_of_page; i <= ( active_vec_size - 1 ); i++ ) { + const auto guy = followers[i]; + const int y = i - top_of_page + 3; + trim_and_print( w_missions, y, 1, 28, static_cast( selection ) == i ? hilite( col ) : col, + guy->disp_name() ); + } + if( selection < followers.size() ) { + int y = 2; + //get NPC followers, status, direction, location, needs, weapon, etc. + mvwprintz( w_missions, ++y, 31, c_light_gray, _( "Press enter to talk to this follower " ) ); + std::string mission_string; + if( guy->has_companion_mission() ) { + npc_companion_mission c_mission = guy->get_companion_mission(); + mission_string = _( "Current Mision : " ) + get_mission_action_string( c_mission.mission_id ); + } + fold_and_print( w_missions, ++y, 31, getmaxx( w_missions ) - 33, col, + mission_string ); + tripoint guy_abspos = guy->global_omt_location(); + std::string direction = direction_name( direction_from( + player_abspos, guy_abspos ) ); + std::string centerstring = "center"; + if( ( !direction.compare( centerstring ) ) == 0 ) { + mvwprintz( w_missions, ++y, 31, col, + _( "Direction : to the " ) + direction ); + } else { + mvwprintz( w_missions, ++y, 31, col, + _( "Direction : Nearby" ) ); + } + mvwprintz( w_missions, ++y, 31, col, _( "Location : (%d, %d)" ), guy_abspos.x, guy_abspos.y ); + std::string can_see; + nc_color see_color; + const std::vector interactable_followers = g->get_npcs_if( [&]( const npc & guy ) { + return ( ( guy.is_friend() && guy.is_following() ) || guy.mission == NPC_MISSION_GUARD_ALLY ) && + g->u.posz() == guy.posz() && + g->u.sees( guy.pos() ) && rl_dist( g->u.pos(), guy.pos() ) <= SEEX * 2; + } ); + if( std::find( interactable_followers.begin(), interactable_followers.end(), + guy ) != interactable_followers.end() ) { + interactable = true; + can_see = "Within interaction range"; + see_color = c_light_green; + } else { + interactable = false; + can_see = "Not within interaction range"; + see_color = c_light_red; + } + mvwprintz( w_missions, ++y, 31, see_color, can_see ); + nc_color status_col = col; + std::string current_status = _( "Status : " ); + if( guy->current_target() != nullptr ) { + current_status += _( "In Combat!" ); + status_col = c_light_red; + } else if( guy->in_sleep_state() ) { + current_status += _( "Sleeping" ); + } else if( guy->is_following() ) { + current_status += _( "Following" ); + } else if( guy->is_leader() ) { + current_status += _( "Leading" ); + } else if( guy->is_guarding() ) { + current_status += _( "Guarding" ); + } + mvwprintz( w_missions, ++y, 31, status_col, current_status ); + const std::pair condition = guy->hp_description(); + const std::string condition_string = _( "Condition : " ) + condition.first; + mvwprintz( w_missions, ++y, 31, condition.second, condition_string ); + const std::pair hunger_pair = guy->get_hunger_description(); + const std::pair thirst_pair = guy->get_thirst_description(); + const std::pair fatigue_pair = guy->get_fatigue_description(); + mvwprintz( w_missions, ++y, 31, hunger_pair.second, + _( "Hunger : " ) + ( ( hunger_pair.first == "" ) ? "Nominal" : hunger_pair.first ) ); + mvwprintz( w_missions, ++y, 31, thirst_pair.second, + _( "Thirst : " ) + ( ( thirst_pair.first == "" ) ? "Nominal" : thirst_pair.first ) ); + mvwprintz( w_missions, ++y, 31, fatigue_pair.second, + _( "Fatigue : " ) + ( ( fatigue_pair.first == "" ) ? "Nominal" : fatigue_pair.first ) ); + const std::string wield_str = _( "Wielding : " ) + guy->weapon.tname(); + int lines = fold_and_print( w_missions, ++y, 31, getmaxx( w_missions ) - 33, c_white, + ( wield_str ) ); + y += lines; + const auto skillslist = Skill::get_skills_sorted_by( [&]( const Skill & a, const Skill & b ) { + const int level_a = guy->get_skill_level( a.ident() ); + const int level_b = guy->get_skill_level( b.ident() ); + return level_a > level_b || ( level_a == level_b && a.name() < b.name() ); + } ); + size_t count = 0; + std::vector skill_strs; + for( int i = 0; i < static_cast( skillslist.size() ) && count < 3; i++ ) { + if( !skillslist[ i ]->is_combat_skill() ) { + std::string skill_str = skillslist[i]->name() + " : " + std::to_string( guy->get_skill_level( + skillslist[i]->ident() ) ); + skill_strs.push_back( skill_str ); + count += 1; + } + } + std::string best_three_noncombat = _( "Best other skills : " ); + std::string best_skill = _( "Best combat skill : " ) + guy->best_skill().obj().name() + " : " + + std::to_string( guy->best_skill_level() ); + mvwprintz( w_missions, ++y, 31, col, best_skill ); + mvwprintz( w_missions, ++y, 31, col, best_three_noncombat + skill_strs[0] ); + mvwprintz( w_missions, ++y, 51, col, skill_strs[1] ); + mvwprintz( w_missions, ++y, 51, col, skill_strs[2] ); + } else { + static const std::string nope = _( "You have no followers" ); + mvwprintz( w_missions, 4, 31, c_light_red, nope ); + } + break; + } else { + static const std::string nope = _( "You have no followers" ); + mvwprintz( w_missions, 4, 31, c_light_red, nope ); + } + break; + case tab_mode::TAB_OTHERFACTIONS: + // Currently the info on factions is incomplete. + break; + default: + break; + } + wrefresh( w_missions ); + const std::string action = ctxt.handle_input(); + if( action == "NEXT_TAB" || action == "RIGHT" ) { + tab = static_cast( static_cast( tab ) + 1 ); + if( tab >= tab_mode::NUM_TABS ) { + tab = tab_mode::FIRST_TAB; + } + selection = 0; + } else if( action == "PREV_TAB" || action == "LEFT" ) { + tab = static_cast( static_cast( tab ) - 1 ); + if( tab < tab_mode::FIRST_TAB ) { + tab = tab_mode::LAST_TAB; + } + selection = 0; + } else if( action == "DOWN" ) { + selection++; + if( selection >= static_cast( active_vec_size ) ) { + selection = 0; + } + } else if( action == "UP" ) { + if( selection == 0 ) { + selection = active_vec_size == 0 ? 0 : active_vec_size - 1; + } else { + selection--; + } + } else if( action == "CONFIRM" ) { + if( tab == tab_mode::TAB_FOLLOWERS && interactable && guy ) { + guy->talk_to_u(); + } + } else if( action == "QUIT" ) { + break; + } + } + + g->refresh_all(); +} diff --git a/src/faction.h b/src/faction.h index abeab4d60b60b..f07e44809466f 100644 --- a/src/faction.h +++ b/src/faction.h @@ -4,6 +4,7 @@ #include +#include "color.h" #include "string_id.h" // TODO: Redefine? @@ -13,6 +14,7 @@ std::string fac_ranking_text( int val ); std::string fac_respect_text( int val ); std::string fac_wealth_text( int val, int size ); std::string fac_food_supply_text( int val, int size ); +nc_color get_food_supply_color( int val, int size ); std::string fac_combat_ability_text( int val ); class player; @@ -161,4 +163,10 @@ class faction_manager void display() const; }; +class new_faction_manager +{ + public: + void display() const; +}; + #endif diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index 32c45ee4df2fc..e8982008a56af 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -97,6 +97,7 @@ struct bcp_miss_data { std::string ret_miss_id; std::string ret_desc; }; + // enventually this will move to JSON std::map basecamp_missions_info = {{ { @@ -583,7 +584,7 @@ void talk_function::basecamp_mission( npc &p ) void basecamp::get_available_missions( mission_data &mission_key ) { std::string entry; - g->u.camps.insert( pos ); + g->u.camps.insert( omt_pos ); const std::string camp_ctr = "camp"; const std::string base_dir = "[B]"; @@ -989,7 +990,7 @@ void basecamp::get_available_missions( mission_data &mission_key ) //This starts all of the expansion missions for( const std::string &dir : directions ) { const std::string bldg_exp = next_upgrade( dir ); - const tripoint omt_trg = pos + talk_function::om_dir_to_offset( dir ); + const tripoint omt_trg = omt_pos + talk_function::om_dir_to_offset( dir ); if( bldg_exp != "null" ) { comp_list npc_list = get_mission_workers( "_faction_upgrade_exp_" + dir ); const bcp_miss_data &miss_info = basecamp_missions_info[ "_faction_upgrade_exp_" ]; @@ -1383,7 +1384,7 @@ npc_ptr basecamp::start_mission( const std::string &miss_id, time_duration durat popup( _( "You don't have enough food stored to feed your companion." ) ); return nullptr; } - npc_ptr comp = talk_function::individual_mission( pos, basecamp_id, desc, miss_id, false, + npc_ptr comp = talk_function::individual_mission( omt_pos, basecamp_id, desc, miss_id, false, equipment, skill_tested, skill_level ); if( comp != nullptr ) { comp->companion_mission_time_ret = calendar::turn + duration; @@ -1419,7 +1420,6 @@ void basecamp::start_cut_logs() { std::vector log_sources = { "forest", "forest_thick", "forest_water" }; popup( _( "Forests and swamps are the only valid cutting locations." ) ); - const tripoint omt_pos = pos; tripoint forest = om_target_tile( omt_pos, 1, 50, log_sources ); if( forest != tripoint( -999, -999, -999 ) ) { standard_npc sample_npc( "Temp" ); @@ -1477,7 +1477,6 @@ void basecamp::start_clearcut() { std::vector log_sources = { "forest", "forest_thick" }; popup( _( "Forests are the only valid cutting locations." ) ); - const tripoint omt_pos = pos; tripoint forest = om_target_tile( omt_pos, 1, 50, log_sources ); if( forest != tripoint( -999, -999, -999 ) ) { standard_npc sample_npc( "Temp" ); @@ -1516,7 +1515,6 @@ void basecamp::start_setup_hide_site() "field" }; popup( _( "Forests, swamps, and fields are valid hide site locations." ) ); - const tripoint omt_pos = pos; tripoint forest = om_target_tile( omt_pos, 10, 90, hide_locations, true, true, omt_pos, true ); if( forest != tripoint( -999, -999, -999 ) ) { int dist = rl_dist( forest.x, forest.y, omt_pos.x, omt_pos.y ); @@ -1556,7 +1554,6 @@ void basecamp::start_relay_hide_site() { std::vector hide_locations = { "faction_hide_site_0" }; popup( _( "You must select an existing hide site." ) ); - const tripoint omt_pos = pos; tripoint forest = om_target_tile( omt_pos, 10, 90, hide_locations, true, true, omt_pos, true ); if( forest != tripoint( -999, -999, -999 ) ) { int dist = rl_dist( forest.x, forest.y, omt_pos.x, omt_pos.y ); @@ -1633,7 +1630,6 @@ void basecamp::start_fortifications( std::string &bldg_exp ) popup( _( "Select a start and end point. Line must be straight. Fields, forests, and " "swamps are valid fortification locations. In addition to existing fortification " "constructions." ) ); - const tripoint omt_pos = pos; tripoint start = om_target_tile( omt_pos, 2, 90, allowed_locations ); popup( _( "Select an end point." ) ); tripoint stop = om_target_tile( omt_pos, 2, 90, allowed_locations, true, false, start ); @@ -1709,7 +1705,7 @@ void basecamp::start_combat_mission( const std::string &miss ) { popup( _( "Select checkpoints until you reach maximum range or select the last point again " "to end." ) ); - tripoint start = pos; + tripoint start = omt_pos; std::vector scout_points = om_companion_path( start, 90, true ); if( scout_points.empty() ) { return; @@ -2013,7 +2009,7 @@ bool basecamp::start_garage_chop( const std::string &dir, const tripoint &omt_tg // camp faction companion mission recovery functions npc_ptr basecamp::companion_choose_return( const std::string &miss_id, time_duration min_duration ) { - return talk_function::companion_choose_return( pos, basecamp_id, miss_id, + return talk_function::companion_choose_return( omt_pos, basecamp_id, miss_id, calendar::turn - min_duration ); } void basecamp::finish_return( npc &comp, bool fixed_time, const std::string &return_msg, @@ -2464,7 +2460,6 @@ bool basecamp::survey_return() return false; } editmap edit; - tripoint omt_pos = pos; if( !edit.mapgen_set( pos_expansion_name_id[expan], omt_pos, 1 ) ) { return false; } @@ -2964,6 +2959,13 @@ void om_line_mark( const tripoint &origin, const tripoint &dest, bool add_notes, } } +std::string get_mission_action_string( const std::string &input_mission ) +{ + const bcp_miss_data &miss_info = basecamp_missions_info[ input_mission ]; + return miss_info.action; + +} + bool om_set_hide_site( npc &comp, const tripoint &omt_tgt, const std::vector &itms, const std::vector &itms_rem ) @@ -3121,7 +3123,7 @@ bool basecamp::set_sort_points( bool reset_pts, bool choose_pts ) { std::vector new_pts; for( std::pair sort_pt : sort_point_data ) { - tripoint default_pt = pos + sort_pt.second; + tripoint default_pt = omt_pos + sort_pt.second; new_pts.push_back( default_pt ); } if( reset_pts ) { @@ -3491,7 +3493,7 @@ bool basecamp::distribute_food() validate_sort_points(); tripoint p_food_stock = sort_points[ static_cast( sort_pt_ids::cfood ) ]; tripoint p_trash = sort_points[ static_cast( sort_pt_ids::trash ) ]; - tripoint p_litter = pos + point( -7, 0 ); + tripoint p_litter = bb_pos + point( -7, 0 ); tripoint p_tool = sort_points[ static_cast( sort_pt_ids::tools ) ]; if( g->m.i_at( p_food_stock ).empty() ) { diff --git a/src/faction_camp.h b/src/faction_camp.h index be146f8df1a77..4e2a1df31d8f1 100644 --- a/src/faction_camp.h +++ b/src/faction_camp.h @@ -46,6 +46,8 @@ inline bool operator&( const farm_ops &rhs, const farm_ops &lhs ) return static_cast( rhs ) & static_cast( lhs ); } +std::string get_mission_action_string( const std::string &input_mission ); + namespace talk_function { void basecamp_mission( npc & ); diff --git a/src/game.cpp b/src/game.cpp index 8c936e907c59e..cb9748f3a966e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -797,6 +797,8 @@ void game::setup() // reset kill counts kills.clear(); npc_kills.clear(); + // reset follower list + follower_ids.clear(); scent.reset(); remoteveh_cache_time = calendar::before_time_starts; @@ -2006,6 +2008,69 @@ void game::record_npc_kill( const npc &p ) npc_kills.push_back( p.get_name() ); } +void game::add_npc_follower( const int &id ) +{ + if( !std::any_of( follower_ids.begin(), follower_ids.end(), [id]( int i ) { + return i == id; +} ) ) + follower_ids.push_back( id ); +} + +void game::remove_npc_follower( const int &id ) +{ + follower_ids.erase( std::remove( follower_ids.begin(), follower_ids.end(), id ), + follower_ids.end() ); +} + +void game::validate_npc_followers() +{ + // Make sure visible followers are in the list. + const std::vector visible_followers = g->get_npcs_if( [&]( const npc & guy ) { + return ( guy.is_friend() && guy.is_following() ) || guy.mission == NPC_MISSION_GUARD_ALLY; + } ); + for( auto &elem : visible_followers ) { + add_npc_follower( elem->getID() ); + } + // Make sure overmapbuffered NPC followers are in the list. + for( const auto &temp_guy : overmap_buffer.get_npcs_near_player( 200 ) ) { + npc *guy = temp_guy.get(); + if( ( guy->is_friend() && guy->is_following() ) || guy->has_companion_mission() ) { + add_npc_follower( guy->getID() ); + } + } +} + +void game::validate_camps() +{ + // Make sure camps already present are added to the overmap list + basecamp *bcp = m.camp_at( u.pos(), 60 ); + if( bcp ) { + int count = 1; + if( u.camps.empty() ) { + u.camps.insert( bcp->camp_omt_pos() ); + } + for( auto camp : u.camps ) { + // check if already on the overmapbuffer list + cata::optional p = overmap_buffer.find_camp( camp.x, camp.y ); + if( !p ) { + //if not on overmap buffer list + if( camp.x == bcp->camp_omt_pos().x && camp.y == bcp->camp_omt_pos().y ) { + // if this local camp is the one that needs adding + std::string camp_name = _( "Faction Camp " ) + std::to_string( count ); + bcp->set_name( camp_name ); + overmap_buffer.add_camp( bcp ); + count += 1; + } + } + } + } +} + +std::vector game::get_follower_list() +{ + return follower_ids; +} + std::list game::get_npc_kill() { return npc_kills; @@ -2694,6 +2759,8 @@ void game::load( const save_t &name ) } ); reload_npcs(); + validate_npc_followers(); + validate_camps(); update_map( u ); // legacy, needs to be here as we access the map. @@ -4712,6 +4779,7 @@ void game::cleanup_dead() if( npc_is_dead ) { for( auto it = active_npc.begin(); it != active_npc.end(); ) { if( ( *it )->is_dead() ) { + remove_npc_follower( ( *it )->getID() ); overmap_buffer.remove_npc( ( *it )->getID() ); it = active_npc.erase( it ); } else { diff --git a/src/game.h b/src/game.h index 2b6446a7c0c17..60fe11227d528 100644 --- a/src/game.h +++ b/src/game.h @@ -82,6 +82,7 @@ class zone_type; using zone_type_id = string_id; class Character; class faction_manager; +class new_faction_manager; class player; class npc; class monster; @@ -248,6 +249,7 @@ class game pimpl critter_tracker; pimpl faction_manager_ptr; + pimpl new_faction_manager_ptr; /** Create explosion at p of intensity (power) with (shrapnel) chunks of shrapnel. Explosion intensity formula is roughly power*factor^distance. @@ -516,6 +518,16 @@ class game void increase_kill_count( const mtype_id &id ); /** Record the fact that the player murdered an NPC. */ void record_npc_kill( const npc &p ); + /** Add follower id to list. */ + void add_npc_follower( const int &id ); + /** Remove follower id from follower list. */ + void remove_npc_follower( const int &id ); + /** Get list of followers. */ + std::vector get_follower_list(); + /** validate list of followers to account for overmap buffers */ + void validate_npc_followers(); + /** validate camps to ensure they are on the overmap list */ + void validate_camps(); /** Return list of killed NPC */ std::list get_npc_kill(); @@ -1093,6 +1105,7 @@ class game bool bVMonsterLookFire; time_point nextweather; // The time on which weather will shift next. int next_npc_id, next_mission_id; // Keep track of UIDs + std::vector follower_ids; // Keep track of follower NPC IDs std::map kills; // Player's kill count std::list npc_kills; // names of NPCs the player killed int moves_since_last_save; diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 88ff05c2d6b7e..ed0938865d562 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1823,7 +1823,7 @@ bool game::handle_action() break; case ACTION_FACTIONS: - faction_manager_ptr->display(); + new_faction_manager_ptr->display(); refresh_all(); break; diff --git a/src/map.cpp b/src/map.cpp index e94db059e2412..95658fbffb193 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -36,6 +36,8 @@ #include "npc.h" #include "options.h" #include "output.h" +#include "overmap.h" +#include "overmapbuffer.h" #include "pathfinding.h" #include "projectile.h" #include "rng.h" @@ -5466,8 +5468,15 @@ void map::add_camp( const tripoint &p, const std::string &name ) dbg( D_ERROR ) << "map::add_camp: Attempting to add camp when one in local area."; return; } - - get_submap_at( p )->camp = basecamp( name, p ); + point omt = ms_to_omt_copy( g->m.getabs( p.x, p.y ) ); + tripoint omt_tri = tripoint( omt.x, omt.y, p.z ); + basecamp temp_camp = basecamp( name, omt_tri ); + get_submap_at( p )->camp = temp_camp; + basecamp *pointer_camp; + pointer_camp = &get_submap_at( p )->camp; + overmap_buffer.add_camp( pointer_camp ); + g->u.camps.insert( omt_tri ); + g->validate_camps(); } void map::update_visibility_cache( const int zlev ) diff --git a/src/mission_companion.cpp b/src/mission_companion.cpp index 365dd0901a8f2..41820bd9c1bdd 100644 --- a/src/mission_companion.cpp +++ b/src/mission_companion.cpp @@ -611,6 +611,7 @@ npc_ptr talk_function::individual_mission( const tripoint &omt_pos, comp->companion_mission_time = calendar::turn; } g->reload_npcs(); + g->validate_npc_followers(); assert( !comp->is_active() ); return comp; } diff --git a/src/mission_start.cpp b/src/mission_start.cpp index f3992b01584f1..145288ce4ead3 100644 --- a/src/mission_start.cpp +++ b/src/mission_start.cpp @@ -2052,4 +2052,3 @@ void mission_type::parse_start( JsonObject &jo ) mission_start_fun.apply( miss ); }; } - diff --git a/src/npc.cpp b/src/npc.cpp index a95ece40e2aea..c37430bc64975 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -871,6 +871,24 @@ skill_id npc::best_skill() const return highest_skill; } +int npc::best_skill_level() const +{ + int highest_level = std::numeric_limits::min(); + skill_id highest_skill( skill_id::NULL_ID() ); + + for( const auto &p : *_skills ) { + if( p.first.obj().is_combat_skill() ) { + const int level = p.second.level(); + if( level > highest_level ) { + highest_level = level; + highest_skill = p.first; + } + } + } + + return highest_level; +} + void npc::starting_weapon( const npc_class_id &type ) { if( item_group::group_is_defined( type->weapon_override ) ) { @@ -2380,6 +2398,39 @@ void npc::set_companion_mission( npc &p, const std::string &mission_id ) const tripoint omt_pos = p.global_omt_location(); set_companion_mission( omt_pos, p.companion_mission_role_id, mission_id ); } + +std::pair npc::hp_description() const +{ + int cur_hp = hp_percentage(); + std::string damage_info; + std::string pronoun; + if( male ) { + pronoun = _( "He " ); + } else { + pronoun = _( "She " ); + } + nc_color col; + if( cur_hp == 100 ) { + damage_info = pronoun + _( "is uninjured." ); + col = c_green; + } else if( cur_hp >= 80 ) { + damage_info = pronoun + _( "is lightly injured." ); + col = c_light_green; + } else if( cur_hp >= 60 ) { + damage_info = pronoun + _( "is moderately injured." ); + col = c_yellow; + } else if( cur_hp >= 30 ) { + damage_info = pronoun + _( "is heavily injured." ); + col = c_yellow; + } else if( cur_hp >= 10 ) { + damage_info = pronoun + _( "is severely injured." ); + col = c_light_red; + } else { + damage_info = pronoun + _( "is nearly dead!" ); + col = c_red; + } + return std::make_pair( damage_info, col ); +} void npc::set_companion_mission( const tripoint &omt_pos, const std::string &role_id, const std::string &mission_id ) { diff --git a/src/npc.h b/src/npc.h index cc2713d93b6cc..e1a75b16759d4 100644 --- a/src/npc.h +++ b/src/npc.h @@ -528,6 +528,7 @@ class npc : public player */ void add_new_mission( mission *miss ); skill_id best_skill() const; + int best_skill_level() const; void starting_weapon( const npc_class_id &type ); // Save & load @@ -799,6 +800,8 @@ class npc : public player std::string extended_description() const override; + std::pair hp_description() const; + // Note: NPCs use a different speed rating than players // Because they can't run yet float speed_rating() const override; diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 174341b78b830..d243ac8170870 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -621,6 +621,12 @@ std::string dialogue::dynamic_line( const talk_topic &the_topic ) const } else if( topic == "TALK_MIND_CONTROL" ) { p->companion_mission_role_id.clear(); p->set_attitude( NPCATT_FOLLOW ); + std::vector followerlist = g->get_follower_list(); + int npc_id = p->getID(); + if( !std::any_of( followerlist.begin(), followerlist.end(), [npc_id]( int i ) { + return i == npc_id; + } ) ) + g->add_npc_follower( npc_id ); return _( "YES, MASTER!" ); } diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index 5afd44e17306c..188be9d22a90a 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -471,6 +471,7 @@ void talk_function::buy_100_logs( npc &p ) void talk_function::follow( npc &p ) { + g->add_npc_follower( p.getID() ); p.set_attitude( NPCATT_FOLLOW ); g->u.cash += p.cash; p.cash = 0; @@ -526,6 +527,7 @@ void talk_function::flee( npc &p ) void talk_function::leave( npc &p ) { add_msg( _( "%s leaves." ), p.name ); + g->remove_npc_follower( p.getID() ); p.set_attitude( NPCATT_NULL ); } diff --git a/src/overmap.cpp b/src/overmap.cpp index 3f137c4a2d829..95e48fdc26bd8 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -30,6 +30,7 @@ #include "mtype.h" #include "name.h" #include "npc.h" +#include "optional.h" #include "options.h" #include "output.h" #include "overmap_connection.h" @@ -3976,6 +3977,16 @@ std::shared_ptr overmap::find_npc( const int id ) const return nullptr; } +cata::optional overmap::find_camp( const int x, const int y ) const +{ + for( const auto &v : camps ) { + if( v->camp_omt_pos().x == x && v->camp_omt_pos().y == y ) { + return v; + } + } + return cata::nullopt; +} + bool overmap::is_omt_generated( const tripoint &loc ) const { if( !inbounds( loc ) ) { diff --git a/src/overmap.h b/src/overmap.h index add89a950b042..313f987f6a438 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -12,6 +12,7 @@ #include #include +#include "basecamp.h" #include "game_constants.h" #include "monster.h" #include "omdata.h" @@ -19,6 +20,7 @@ #include "regional_settings.h" #include "weighted_list.h" +class basecamp; class input_context; class JsonObject; class npc; @@ -248,9 +250,10 @@ class overmap // TODO: make private std::vector radios; std::map vehicles; + std::vector camps; std::vector cities; std::vector roads_out; - + cata::optional find_camp( const int x, const int y ) const; /// Adds the npc to the contained list of npcs ( @ref npcs ). void insert_npc( std::shared_ptr who ); /// Removes the npc and returns it ( or returns nullptr if not found ). diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 47d0efebf36c3..fe54e88b29b11 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -1,15 +1,18 @@ #include "overmap_ui.h" +#include "basecamp.h" #include "cata_utility.h" #include "clzones.h" #include "coordinate_conversions.h" #include "cursesdef.h" +#include "faction_camp.h" #include "game.h" #include "input.h" #include "line.h" #include "map_iterator.h" #include "map.h" #include "mapbuffer.h" +#include "messages.h" #include "mongroup.h" #include "npc.h" #include "options.h" @@ -241,6 +244,44 @@ void draw_city_labels( const catacurses::window &w, const tripoint ¢er ) } } +void draw_camp_labels( const catacurses::window &w, const tripoint ¢er ) +{ + const int win_x_max = getmaxx( w ); + const int win_y_max = getmaxy( w ); + const int sm_radius = std::max( win_x_max, win_y_max ); + + const point screen_center_pos( win_x_max / 2, win_y_max / 2 ); + + for( const auto &element : overmap_buffer.get_camps_near( omt_to_sm_copy( center ), sm_radius ) ) { + const point camp_pos( element->camp_omt_pos().x, element->camp_omt_pos().y ); + const point screen_pos( camp_pos - point( center.x, center.y ) + screen_center_pos ); + const int text_width = utf8_width( element->name, true ); + const int text_x_min = screen_pos.x - text_width / 2; + const int text_x_max = text_x_min + text_width; + const int text_y = screen_pos.y; + const std::string camp_name = element->name; + if( text_x_min < 0 || + text_x_max > win_x_max || + text_y < 0 || + text_y > win_y_max ) { + continue; // outside of the window bounds. + } + + if( screen_center_pos.x >= ( text_x_min - 1 ) && + screen_center_pos.x <= ( text_x_max ) && + screen_center_pos.y >= ( text_y - 1 ) && + screen_center_pos.y <= ( text_y + 1 ) ) { + continue; // right under the cursor. + } + + if( !overmap_buffer.seen( camp_pos.x, camp_pos.y, center.z ) ) { + continue; // haven't seen it. + } + + mvwprintz( w, text_y, text_x_min, i_white, camp_name ); + } +} + point draw_notes( int z ) { const overmapbuffer::t_notes_vector notes = overmap_buffer.get_all_notes( z ); @@ -611,6 +652,7 @@ void draw( const catacurses::window &w, const catacurses::window &wbar, const tr if( z == 0 && uistate.overmap_show_city_labels ) { draw_city_labels( w, tripoint( cursx, cursy, z ) ); + draw_camp_labels( w, tripoint( cursx, cursy, z ) ); } if( has_target && blink && @@ -808,7 +850,7 @@ void draw( const catacurses::window &w, const catacurses::window &wbar, const tr print_hint( "TOGGLE_BLINKING", uistate.overmap_blinking ? c_pink : c_magenta ); print_hint( "TOGGLE_OVERLAYS", show_overlays ? c_pink : c_magenta ); print_hint( "TOGGLE_LAND_USE_CODES", uistate.overmap_land_use_codes ? c_pink : c_magenta ); - print_hint( "TOGGLE_CITY_LABELS", uistate.overmap_show_city_labels ? c_pink : c_magenta ); + print_hint( "TOGGLE_LOCATION_LABELS", uistate.overmap_show_city_labels ? c_pink : c_magenta ); print_hint( "TOGGLE_HORDES", uistate.overmap_show_hordes ? c_pink : c_magenta ); print_hint( "TOGGLE_EXPLORED", is_explored ? c_pink : c_magenta ); print_hint( "TOGGLE_FAST_SCROLL", fast_scroll ? c_pink : c_magenta ); diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 7902e6c162579..e742f7095ca7c 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -5,6 +5,7 @@ #include #include +#include "basecamp.h" #include "cata_utility.h" #include "coordinate_conversions.h" #include "debug.h" @@ -12,9 +13,11 @@ #include "game.h" #include "line.h" #include "map.h" +#include "messages.h" #include "mongroup.h" #include "monster.h" #include "npc.h" +#include "optional.h" #include "overmap.h" #include "overmap_connection.h" #include "overmap_types.h" @@ -382,6 +385,26 @@ int overmapbuffer::get_horde_size( const int x, const int y, const int z ) return horde_size; } +bool overmapbuffer::has_camp( int x, int y, int z ) +{ + if( z ) { + return false; + } + + const overmap *const om = get_existing_om_global( x, y ); + if( !om ) { + return false; + } + + for( const auto &v : om->camps ) { + if( v->camp_omt_pos().x == x && v->camp_omt_pos().y == y ) { + return true; + } + } + + return false; +} + bool overmapbuffer::has_vehicle( int x, int y, int z ) { if( z ) { @@ -536,6 +559,21 @@ void overmapbuffer::move_vehicle( vehicle *veh, const point &old_msp ) } } +void overmapbuffer::remove_camp( const basecamp *camp ) +{ + const point omt = point( camp->camp_omt_pos().x, camp->camp_omt_pos().y ); + overmap &om = get_om_global( omt ); + int index = 0; + for( const auto &v : om.camps ) { + if( v->camp_omt_pos().x == camp->camp_omt_pos().x && + v->camp_omt_pos().y == camp->camp_omt_pos().y ) { + om.camps.erase( om.camps.begin() + index ); + return; + } + index += 1; + } +} + void overmapbuffer::remove_vehicle( const vehicle *veh ) { const point omt = ms_to_omt_copy( g->m.getabs( veh->global_pos3().x, veh->global_pos3().y ) ); @@ -559,6 +597,13 @@ void overmapbuffer::add_vehicle( vehicle *veh ) veh->om_id = id; } +void overmapbuffer::add_camp( basecamp *camp ) +{ + point omt = point( camp->camp_omt_pos().x, camp->camp_omt_pos().y ); + overmap &om = get_om_global( omt.x, omt.y ); + om.camps.push_back( camp ); +} + bool overmapbuffer::seen( int x, int y, int z ) { const overmap *om = get_existing_om_global( x, y ); @@ -862,6 +907,16 @@ std::shared_ptr overmapbuffer::find_npc( int id ) return nullptr; } +cata::optional overmapbuffer::find_camp( const int x, const int y ) +{ + for( auto &it : overmaps ) { + if( auto p = it.second->find_camp( x, y ) ) { + return p; + } + } + return cata::nullopt; +} + void overmapbuffer::insert_npc( const std::shared_ptr &who ) { assert( who ); @@ -1019,6 +1074,19 @@ std::vector overmapbuffer::find_all_radio_stations() return result; } +std::vector overmapbuffer::get_camps_near( const tripoint &location, int radius ) +{ + std::vector result; + + for( const auto om : get_overmaps_near( location, radius ) ) { + for( const auto camp : om->camps ) { + result.push_back( camp ); + } + + } + return result; +} + std::vector overmapbuffer::get_cities_near( const tripoint &location, int radius ) { std::vector result; diff --git a/src/overmapbuffer.h b/src/overmapbuffer.h index 5b619099e102b..69cfbc2bd0212 100644 --- a/src/overmapbuffer.h +++ b/src/overmapbuffer.h @@ -17,13 +17,13 @@ struct mongroup; class monster; class npc; struct om_vehicle; - struct oter_t; using oter_id = int_id; class overmap; struct radio_tower; struct regional_settings; class vehicle; +class basecamp; struct radio_tower_reference { /** The radio tower itself, points into @ref overmap::radios */ @@ -106,6 +106,7 @@ class overmapbuffer void toggle_explored( int x, int y, int z ); bool seen( int x, int y, int z ); void set_seen( int x, int y, int z, bool seen = true ); + bool has_camp( int x, int y, int z ); bool has_vehicle( int x, int y, int z ); bool has_horde( int x, int y, int z ); int get_horde_size( int x, int y, int z ); @@ -154,10 +155,20 @@ class overmapbuffer * Add the vehicle to be tracked in the overmap. */ void add_vehicle( vehicle *veh ); + /** + * Remove basecamp + */ + void remove_camp( const basecamp *camp ); /** * Remove the vehicle from being tracked in the overmap. */ void remove_vehicle( const vehicle *veh ); + /** + * Add Basecamp to overmapbuffer + */ + void add_camp( basecamp *camp ); + + cata::optional find_camp( const int x, const int y ); /** * Get all npcs in a area with given radius around (x, y). * Only npcs on the given z-level are considered. @@ -168,6 +179,7 @@ class overmapbuffer * A radius of 0 returns only those npcs that are on the * specific submap. */ + std::vector> get_npcs_near( int x, int y, int z, int radius ); /** * Get all (currently loaded!) npcs that have a companion @@ -389,6 +401,7 @@ class overmapbuffer * All entries in the returned vector are valid (have a valid tower pointer). */ std::vector find_all_radio_stations(); + std::vector get_camps_near( const tripoint &location, int radius ); /** * Find all cities within the specified @ref radius. * Result is sorted by proximity to @ref location in ascending order. diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index cc876bc422e22..80505c78c4fb8 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -2872,7 +2872,7 @@ void basecamp::serialize( JsonOut &json ) const { json.start_object(); json.member( "name", name ); - json.member( "pos", pos ); + json.member( "pos", omt_pos ); json.member( "bb_pos", bb_pos ); json.member( "sort_points" ); json.start_array(); @@ -2900,7 +2900,7 @@ void basecamp::deserialize( JsonIn &jsin ) { JsonObject data = jsin.get_object(); data.read( "name", name ); - data.read( "pos", pos ); + data.read( "pos", omt_pos ); data.read( "bb_pos", bb_pos ); JsonArray ja = data.get_array( "sort_points" ); while( ja.has_more() ) { @@ -2923,4 +2923,3 @@ void basecamp::deserialize( JsonIn &jsin ) } } } - diff --git a/src/sidebar.cpp b/src/sidebar.cpp index 455df11938d94..d309695f697ab 100644 --- a/src/sidebar.cpp +++ b/src/sidebar.cpp @@ -293,36 +293,9 @@ void player::disp_status( const catacurses::window &w, const catacurses::window const int x = sideStyle ? ( getmaxx( weapwin ) - 13 ) : ( getmaxx( weapwin ) - 12 ); mvwprintz( weapwin, 0, x, style_color, style ); } - - std::string hunger_string = ""; - nc_color hunger_color = c_yellow; - if( get_hunger() >= 300 && get_starvation() > 2500 ) { - hunger_color = c_red; - hunger_string = _( "Starving!" ); - } else if( get_hunger() >= 300 && get_starvation() > 1100 ) { - hunger_color = c_light_red; - hunger_string = _( "Near starving" ); - } else if( get_hunger() > 250 ) { - hunger_color = c_light_red; - hunger_string = _( "Famished" ); - } else if( get_hunger() > 100 ) { - hunger_color = c_yellow; - hunger_string = _( "Very hungry" ); - } else if( get_hunger() > 40 ) { - hunger_color = c_yellow; - hunger_string = _( "Hungry" ); - } else if( get_hunger() < -60 ) { - hunger_color = c_green; - hunger_string = _( "Engorged" ); - } else if( get_hunger() < -20 ) { - hunger_color = c_green; - hunger_string = _( "Sated" ); - } else if( get_hunger() < 0 ) { - hunger_color = c_green; - hunger_string = _( "Full" ); - } + std::pair hunger_pair = get_hunger_description(); mvwprintz( sideStyle ? w : g->w_location_wider, - sideStyle ? 1 : 2, sideStyle ? 0 : 22, hunger_color, hunger_string ); + sideStyle ? 1 : 2, sideStyle ? 0 : 22, hunger_pair.second, hunger_pair.first ); /// Find hottest/coldest bodypart // Calculate the most extreme body temperatures @@ -379,44 +352,14 @@ void player::disp_status( const catacurses::window &w, const catacurses::window } else if( temp_cur[current_bp_extreme] <= BODYTEMP_FREEZING ) { wprintz( w, c_blue, _( "Freezing!%s" ), temp_message ); } - - std::string hydration_string = ""; - nc_color hydration_color = c_yellow; - if( get_thirst() > 520 ) { - hydration_color = c_light_red; - hydration_string = _( "Parched" ); - } else if( get_thirst() > 240 ) { - hydration_color = c_light_red; - hydration_string = _( "Dehydrated" ); - } else if( get_thirst() > 80 ) { - hydration_color = c_yellow; - hydration_string = _( "Very thirsty" ); - } else if( get_thirst() > 40 ) { - hydration_color = c_yellow; - hydration_string = _( "Thirsty" ); - } else if( get_thirst() < -60 ) { - hydration_color = c_green; - hydration_string = _( "Turgid" ); - } else if( get_thirst() < -20 ) { - hydration_color = c_green; - hydration_string = _( "Hydrated" ); - } else if( get_thirst() < 0 ) { - hydration_color = c_green; - hydration_string = _( "Slaked" ); - } + std::pair thirst_pair = get_thirst_description(); mvwprintz( sideStyle ? w : g->w_location_wider, - sideStyle ? 2 : 1, sideStyle ? 0 : 22, hydration_color, hydration_string ); + sideStyle ? 2 : 1, sideStyle ? 0 : 22, thirst_pair.second, thirst_pair.first ); wrefresh( sideStyle ? w : g->w_location_wider ); - wmove( w, sideStyle ? 3 : 2, sideStyle ? 0 : 22 ); - if( get_fatigue() > EXHAUSTED ) { - wprintz( w, c_red, _( "Exhausted" ) ); - } else if( get_fatigue() > DEAD_TIRED ) { - wprintz( w, c_light_red, _( "Dead tired" ) ); - } else if( get_fatigue() > TIRED ) { - wprintz( w, c_yellow, _( "Tired" ) ); - } - + wmove( w, sideStyle ? 3 : 2, 0 ); + std::pair fatigue_pair = get_fatigue_description(); + wprintz( w, fatigue_pair.second, fatigue_pair.first ); wmove( w, sideStyle ? 4 : 2, sideStyle ? 0 : 43 ); wprintz( w, c_white, _( "Focus" ) ); nc_color col_xp = c_dark_gray; @@ -634,4 +577,3 @@ int get_int_digits( const int &digits ) int offset = digits > 0 ? ( int ) log10( ( double ) digits ) + 1 : 1; return offset; } -