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; } -