From f2abd4a9ac42a85c03f2c8eb640cf70cc3c92d35 Mon Sep 17 00:00:00 2001 From: davidpwbrown Date: Mon, 13 Jan 2020 11:59:59 +0000 Subject: [PATCH 1/5] work log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit first attempt at making an NPC work log serializing and expanding reasons sent to log clear folded list on display from review : shortern inserts Update src/npc.cpp Co-Authored-By: Jianxiang Wang (王健翔) Update src/npc.cpp Co-Authored-By: Jianxiang Wang (王健翔) Update src/npc.h Co-Authored-By: Jianxiang Wang (王健翔) Update src/npc.cpp Co-Authored-By: Jianxiang Wang (王健翔) remove unneeded lines test refactor to use enum strings move enum to string definitions to cpp from header Update src/savegame_json.cpp Co-Authored-By: Jianxiang Wang (王健翔) Update src/savegame_json.cpp Co-Authored-By: Jianxiang Wang (王健翔) fix up enum to string conversoin and default constructor added work log result for no candidates spaces to move components / cant see Update activity_item_handling.cpp shortern is_npc() checks commit to trigger rebuild --- data/json/npcs/TALK_COMMON_ALLY.json | 5 + src/activity_handlers.cpp | 41 +++++ src/activity_handlers.h | 23 ++- src/activity_item_handling.cpp | 220 +++++++++++++++++++++++++-- src/basecamp.h | 1 + src/character.cpp | 1 + src/faction_camp.cpp | 24 ++- src/game.cpp | 1 + src/game.h | 2 + src/npc.cpp | 199 ++++++++++++++++++++++++ src/npc.h | 185 ++++++++++++++++++++++ src/npctalk.cpp | 1 + src/npctalk.h | 1 + src/npctalk_funcs.cpp | 5 + src/savegame.cpp | 13 ++ src/savegame_json.cpp | 47 ++++++ 16 files changed, 753 insertions(+), 16 deletions(-) diff --git a/data/json/npcs/TALK_COMMON_ALLY.json b/data/json/npcs/TALK_COMMON_ALLY.json index 0d81881186451..02468aa6bf279 100644 --- a/data/json/npcs/TALK_COMMON_ALLY.json +++ b/data/json/npcs/TALK_COMMON_ALLY.json @@ -868,6 +868,11 @@ "topic": "TALK_DONE", "condition": { "not": "npc_has_activity" }, "effect": "do_farming" + }, + { + "text": "Please show me your work log, so I can see what you've been doing.", + "topic": "TALK_DONE", + "effect": "show_work_log" } ] }, diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index eadcc9e2b3f87..88d30c496bfe1 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -274,6 +274,47 @@ static const std::string flag_TREE( "TREE" ); using namespace activity_handlers; +namespace io +{ + +template<> +std::string enum_to_string( const do_activity_reason act ) +{ + switch( act ) { + // *INDENT-OFF* + case CAN_DO_CONSTRUCTION: return "CAN_DO_CONSTRUCTION"; + case CAN_DO_FETCH: return "CAN_DO_FETCH"; + case CAN_DO_PREREQ: return "CAN_DO_PREREQ"; + case CAN_DO_PREREQ_2: return "CAN_DO_PREREQ_2"; + case NO_COMPONENTS: return "NO_COMPONENTS"; + case NO_COMPONENTS_PREREQ: return "NO_COMPONENTS_PREREQ"; + case NO_COMPONENTS_PREREQ_2: return "NO_COMPONENTS_PREREQ_2"; + case DONT_HAVE_SKILL: return "DONT_HAVE_SKILL"; + case NO_ZONE: return "NO_ZONE"; + case ALREADY_DONE: return "ALREADY_DONE"; + case UNKNOWN_ACTIVITY: return "UNKNOWN_ACTIVITY"; + case NEEDS_HARVESTING: return "NEEDS_HARVESTING"; + case NEEDS_PLANTING: return "NEEDS_PLANTING"; + case NEEDS_TILLING: return "NEEDS_TILLING"; + case BLOCKING_TILE: return "BLOCKING_TILE"; + case NEEDS_CHOPPING: return "NEEDS_CHOPPING"; + case NEEDS_TREE_CHOPPING: return "NEEDS_TREE_CHOPPING"; + case NEEDS_BIG_BUTCHERING: return "NEEDS_BIG_BUTCHERING"; + case NEEDS_BUTCHERING: return "NEEDS_BUTCHERING"; + case ALREADY_WORKING: return "ALREADY_WORKING"; + case NEEDS_VEH_DECONST: return "NEEDS_VEH_DECONST"; + case NEEDS_VEH_REPAIR: return "NEEDS_VEH_REPAIR"; + case NEEDS_FISHING: return "NEEDS_FISHING"; + // *INDENT-ON* + case NUM_DO_ACTIVITY_REASON: + break; + } + debugmsg( "Invalid do_activity_reason" ); + abort(); +} + +} // namespace io + const std::map< activity_id, std::function > activity_handlers::do_turn_functions = { { ACT_BURROW, burrow_do_turn }, diff --git a/src/activity_handlers.h b/src/activity_handlers.h index 3faf67d96b31e..2b63cce630383 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -8,6 +8,7 @@ #include #include +#include "activity_type.h" #include "player_activity.h" class player; @@ -58,8 +59,14 @@ enum do_activity_reason : int { ALREADY_WORKING, // somebody is already working there NEEDS_VEH_DECONST, // There is a vehicle part there that we can deconstruct, given the right tools. NEEDS_VEH_REPAIR, // There is a vehicle part there that can be repaired, given the right tools. + NEEDS_FISHING, // This spot can be fished, if the right tool is present. NEEDS_MINING, // This spot can be mined, if the right tool is present. - NEEDS_FISHING // This spot can be fished, if the right tool is present. + NUM_DO_ACTIVITY_REASON +}; + +template<> +struct enum_traits { + static constexpr do_activity_reason last = NUM_DO_ACTIVITY_REASON; }; struct activity_reason_info { @@ -69,12 +76,18 @@ struct activity_reason_info { bool can_do; //construction index cata::optional con_idx; - + //string usage to store specific reaosn strings. + std::string added_reason; + activity_reason_info() : reason( NUM_DO_ACTIVITY_REASON ), can_do( false ), + con_idx( cata::nullopt ), added_reason( std::string() ) { + } activity_reason_info( do_activity_reason reason_, bool can_do_, - const cata::optional &con_idx_ = cata::nullopt ) : + const cata::optional &con_idx_ = cata::nullopt, + std::string added_reason_ = std::string() ) : reason( reason_ ), can_do( can_do_ ), - con_idx( con_idx_ ) + con_idx( con_idx_ ), + added_reason( added_reason_ ) { } static activity_reason_info ok( const do_activity_reason &reason_ ) { @@ -89,6 +102,8 @@ struct activity_reason_info { static activity_reason_info fail( const do_activity_reason &reason_ ) { return activity_reason_info( reason_, false ); } + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); }; int butcher_time_to_cut( const player &u, const item &corpse_item, butcher_type action ); diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index bcf8636fd9f7c..53f918cb72fa4 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -1497,6 +1497,7 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe } else if( act == ACT_MULTIPLE_FARM ) { zones = mgr.get_zones( zone_type_FARM_PLOT, g->m.getabs( src_loc ) ); + std::string seed; for( const zone_data &zone : zones ) { if( g->m.has_flag_furn( flag_GROWTH_HARVEST, src_loc ) ) { // simple work, pulling up plants, nothing else required. @@ -1516,7 +1517,7 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe } else { // do we have the required seed on our person? const plot_options &options = dynamic_cast( zone.get_options() ); - const std::string seed = options.get_seed(); + seed = options.get_seed(); // If its a farm zone with no specified seed, and we've checked for tilling and harvesting. // then it means no further work can be done here if( seed.empty() ) { @@ -1527,7 +1528,9 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe } ); for( const auto elem : seed_inv ) { if( elem->typeId() == itype_id( seed ) ) { - return activity_reason_info::ok( NEEDS_PLANTING ); + activity_reason_info reason = activity_reason_info::ok( NEEDS_PLANTING ); + reason.added_reason = seed; + return reason; } } // didn't find the seed, but maybe there are overlapping farm zones @@ -1542,7 +1545,9 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe } // looped through all zones, and only got here if its plantable, but have no seeds. - return activity_reason_info::fail( NEEDS_PLANTING ); + activity_reason_info reason = activity_reason_info::fail( NEEDS_PLANTING ); + reason.added_reason = seed; + return reason; } else if( act == ACT_FETCH_REQUIRED ) { // we check if its possible to get all the requirements for fetching at two other places. // 1. before we even assign the fetch activity and; @@ -2311,8 +2316,7 @@ void activity_on_turn_move_loot( player_activity &act, player &p ) // If we got here without restarting the activity, it means we're done add_msg( m_info, _( "%s sorted out every item possible." ), p.disp_name( false, true ) ); - if( p.is_npc() ) { - npc *guy = dynamic_cast( &p ); + if( npc *const guy = dynamic_cast( &p ) ) { guy->revert_after_activity(); } p.activity.set_to_null(); @@ -2403,11 +2407,8 @@ static bool chop_tree_activity( player &p, const tripoint &src_loc ) static void check_npc_revert( player &p ) { - if( p.is_npc() ) { - npc *guy = dynamic_cast( &p ); - if( guy ) { - guy->revert_after_activity(); - } + if( npc *const guy = dynamic_cast( &p ) ) { + guy->revert_after_activity(); } } @@ -2565,6 +2566,11 @@ static std::unordered_set generic_multi_activity_locations( player &p, const bool post_dark_check = src_set.empty(); if( !pre_dark_check && post_dark_check ) { p.add_msg_if_player( m_info, _( "It is too dark to do this activity." ) ); + if( npc *const guy = dynamic_cast( &p ) ) { + const std::string text = string_format( _( "finished %s due to being too dark" ), + act_id.obj().verb() ); + guy->add_to_work_log( ENTRY_WORK_RESULT, text ); + } } return src_set; } @@ -2705,6 +2711,10 @@ static requirement_check_result generic_multi_activity_check_requirement( player if( reason == NEEDS_VEH_DECONST || reason == NEEDS_VEH_REPAIR ) { p.activity_vehicle_part_index = -1; } + if( npc *guy = dynamic_cast( &p ) ) { + std::string desc = _( "The required items are not available to complete this task." ); + guy->add_to_work_log( ENTRY_WORK_RESULT, src_loc, act_info, desc ); + } return SKIP_LOCATION; } else { if( !check_only ) { @@ -2732,6 +2742,12 @@ static requirement_check_result generic_multi_activity_check_requirement( player p.activity = player_activity(); p.backlog.clear(); check_npc_revert( p ); + if( npc *const guy = dynamic_cast( &p ) ) { + const std::string text = string_format( + _( "cannot complete %s due as I cannot see where to move components" ), + act_id.obj().verb() ); + guy->add_to_work_log( ENTRY_WORK_RESULT, text ); + } return SKIP_LOCATION; } act_prev.coords.push_back( g->m.getabs( candidates[std::max( 0, @@ -2853,6 +2869,174 @@ static bool generic_multi_activity_do( player &p, const activity_id &act_id, return true; } +static void report_work_log_result( player &p, activity_id &act_id, activity_reason_info &info, + const tripoint &src_loc ) +{ + if( !p.is_npc() ) { + return; + } + ( void )act_id; + npc *guy = dynamic_cast( &p ); + std::string construction_desc = ""; + std::string tripoint_string = string_format( _( "x:%d,y:%d" ), src_loc.x, src_loc.y ); + bool ignore = false; + if( info.con_idx ) { + construction_desc = info.con_idx->obj().description; + } + std::string desc; + switch( info.reason ) { + case CAN_DO_CONSTRUCTION: { + desc = string_format( _( "can do construction %s, at %s, with current inventory." ), + construction_desc, tripoint_string ); + break; + } + case CAN_DO_FETCH: { + desc = _( "fetching components and tools." ); + break; + } + case CAN_DO_PREREQ_2: + case CAN_DO_PREREQ: { + desc = string_format( _( "can do prerequisite construction %s, at %s with current inventory." ), + construction_desc, tripoint_string ); + break; + } + case NO_COMPONENTS: { + desc = string_format( + _( "can't do construction %s at %s, need to see if I can fetch required components and tools." ), + construction_desc, tripoint_string ); + break; + } + case NO_COMPONENTS_PREREQ: + case NO_COMPONENTS_PREREQ_2: { + desc = string_format( + _( "can't do prerequisite construction %s at %s, will try and fetch required components and tools." ), + construction_desc, tripoint_string ); + break; + } + case DONT_HAVE_SKILL: { + desc = string_format( _( "not skilled enough for task %s at %s." ), construction_desc, + tripoint_string ); + break; + } + case NO_ZONE: { + if( act_id == activity_id( "ACT_MULTIPLE_BUTCHER" ) ) { + for( const auto &i : g->m.i_at( src_loc ) ) { + // make sure nobody else is working on that corpse right now + if( i.is_corpse() ) { + desc = string_format( + _( "the corpse is too big at %s, I need a flat surface and rack or rope and tree." ), + tripoint_string ); + break; + } + } + ignore = true; + } else if( act_id == activity_id( "ACT_TIDY_UP" ) ) { + desc = _( "trying to tidy up, but there is no unsorted zones nearby." ); + } + break; + } + case ALREADY_DONE: { + desc = string_format( _( "the work is already done at %s." ), tripoint_string ); + break; + } + case NEEDS_HARVESTING: { + desc = string_format( _( "can harvest plant at %s." ), tripoint_string ); + break; + } + case NEEDS_PLANTING: { + if( info.can_do ) { + desc = string_format( _( "can plant %s at %s." ), info.added_reason, tripoint_string ); + } else { + desc = string_format( + _( "can plant %s at %s, but dont have the right seeds with me, will search for them." ), + info.added_reason, tripoint_string ); + } + break; + } + case NEEDS_TILLING: { + if( info.can_do ) { + desc = string_format( _( "can till soil at %s." ), tripoint_string ); + } else if( info.can_do ) { + desc = string_format( _( "I want to till soil at %s, but need to fetch tools first, if possible." ), + tripoint_string ); + } + break; + } + case BLOCKING_TILE: { + desc = string_format( _( "there is something blocking the work at %s." ), tripoint_string ); + break; + } + case NEEDS_CHOPPING: { + if( info.can_do ) { + desc = string_format( _( "can chop wood at %s." ), tripoint_string ); + } else { + desc = string_format( _( "want to chop wood at %s, but need to fetch tools first, if possible." ), + tripoint_string ); + } + break; + } + case NEEDS_TREE_CHOPPING: { + if( info.can_do ) { + desc = string_format( _( "I can chop down a tree at %s." ), tripoint_string ); + } else { + desc = string_format( + _( "I can chop down a tree at %s, but need to fetch tools first, if possible." ), tripoint_string ); + } + break; + } + case NEEDS_BUTCHERING: + case NEEDS_BIG_BUTCHERING: { + if( info.can_do ) { + desc = string_format( _( "I can butcher this corpse at %s." ), tripoint_string ); + } else { + desc = string_format( + _( "I can butcher this corpse at %s, but need to fetch some tools first, if possible." ), + tripoint_string ); + } + break; + } + case ALREADY_WORKING: { + desc = string_format( _( "somebody else is already working at %s." ), tripoint_string ); + break; + } + case NEEDS_VEH_DECONST: { + if( info.can_do ) { + desc = string_format( _( "I can deconstruct a vehicle part at %s." ), tripoint_string ); + } else { + desc = string_format( + _( "I can deconstruct a vehicle part at %s, but I need to fetch some tools first, if possible." ), + tripoint_string ); + } + break; + } + case NEEDS_VEH_REPAIR: { + if( info.can_do ) { + desc = string_format( _( "I can repair a vehicle part at %s." ), tripoint_string ); + } else { + desc = string_format( + _( "I can repair a vehicle part at %s, but I need to fetch some tools first, if possible." ), + tripoint_string ); + } + break; + } + case NEEDS_FISHING: { + if( info.can_do ) { + desc = string_format( + _( "I can do some fishing at %s, but I need to fetch some tools first, if possible." ), + tripoint_string ); + } + break; + } + case UNKNOWN_ACTIVITY: + default: + ignore = true; + break; + } + if( !ignore ) { + guy->add_to_work_log( ENTRY_WORK_RESULT, src_loc, info, desc ); + } +} + bool generic_multi_activity_handler( player_activity &act, player &p, bool check_only ) { const tripoint abspos = g->m.getabs( p.pos() ); @@ -2869,6 +3053,21 @@ bool generic_multi_activity_handler( player_activity &act, player &p, bool check std::vector src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); // now loop through the work-spot tiles and judge whether its worth travelling to it yet // or if we need to fetch something first. + if( src_set.empty() && p.is_npc() ) { + npc *guy = dynamic_cast( &p ); + if( guy && activity_to_restore != activity_id( "ACT_TIDY_UP" ) ) { + std::string text; + if( activity_to_restore == activity_id( "ACT_FETCH_REQUIRED" ) ) { + text = string_format( _( "started %s but cannot find the required tools and components for %s." ), + activity_to_restore.obj().verb(), + p.backlog.empty() ? to_translation( "the previous activity" ) : p.backlog.front().get_verb() ); + } else { + text = string_format( _( "started %s but there are no valid locations" ), + activity_to_restore.obj().verb() ); + } + guy->add_to_work_log( ENTRY_WORK_RESULT, text ); + } + } for( const tripoint &src : src_sorted ) { const tripoint &src_loc = g->m.getlocal( src ); if( !g->m.inbounds( src_loc ) && !check_only ) { @@ -2891,6 +3090,7 @@ bool generic_multi_activity_handler( player_activity &act, player &p, bool check } activity_reason_info act_info = can_do_activity_there( activity_to_restore, p, src_loc, ACTIVITY_SEARCH_DISTANCE ); + report_work_log_result( p, activity_to_restore, act_info, src_loc ); // see activity_handlers.h enum for requirement_check_result const requirement_check_result req_res = generic_multi_activity_check_requirement( p, activity_to_restore, act_info, src, src_loc, src_set, check_only ); diff --git a/src/basecamp.h b/src/basecamp.h index 04498f34945fb..1044296f1b2b9 100644 --- a/src/basecamp.h +++ b/src/basecamp.h @@ -273,6 +273,7 @@ class basecamp std::string om_upgrade_description( const std::string &bldg, bool trunc = false ) const; void start_menial_labor(); void worker_assignment_ui(); + void show_work_log(); void job_assignment_ui(); void start_crafting( const std::string &cur_id, const point &cur_dir, const std::string &type, const std::string &miss_id ); diff --git a/src/character.cpp b/src/character.cpp index 2c06077d73f41..46a0a7982f204 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -8804,6 +8804,7 @@ void Character::assign_activity( const player_activity &act, bool allow_resume ) guy->set_attitude( NPCATT_ACTIVITY ); guy->set_mission( NPC_MISSION_ACTIVITY ); guy->current_activity_id = activity.id(); + guy->add_to_work_log( ENTRY_CHANGED_ACTIVITY, string_format( _( "%s " ), activity.get_verb() ) ); } } diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index 02a86d092a70e..9291d21ef503e 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -1274,7 +1274,12 @@ void basecamp::get_available_missions( mission_data &mission_key ) base_camps::base_dir, entry, avail ); } } - + validate_assignees(); + std::vector assigned_npcs = get_npcs_assigned(); + if( !assigned_npcs.empty() ) { + entry = _( "Notes:\nShow workers log" ); + mission_key.add( "Show Work Log", _( "Show Work Log" ), entry ); + } if( !by_radio ) { entry = string_format( _( "Notes:\n" "Distribute food to your follower and fill you larders. " @@ -1293,7 +1298,6 @@ void basecamp::get_available_missions( mission_data &mission_key ) "Total faction food stock: %d kcal\nor %d day's rations" ), camp_food_supply(), camp_food_supply( 0, true ) ); mission_key.add( "Distribute Food", _( "Distribute Food" ), entry ); - validate_assignees(); entry = string_format( _( "Notes:\n" "Assign repeating job duties to NPCs stationed here.\n" "Difficulty: N/A\n" @@ -1366,6 +1370,9 @@ bool basecamp::handle_mission( const std::string &miss_id, if( miss_id == "Abandon Camp" ) { abandon_camp(); } + if( miss_id == "Show Work Log" ) { + show_work_log(); + } if( miss_id == "Expand Base" ) { start_mission( "_faction_camp_expansion", 3_hours, true, @@ -1713,6 +1720,19 @@ void basecamp::worker_assignment_ui() g->refresh_all(); } +void basecamp::show_work_log() +{ + std::vector char_ids; + for( const npc_ptr guy_ptr : get_npcs_assigned() ) { + char_ids.push_back( guy_ptr.get()->getID().get_value() ); + } + if( char_ids.empty() ) { + return; + } + g->npc_log_manager_ptr->display( char_ids ); + g->refresh_all(); +} + void basecamp::job_assignment_ui() { int term_x = TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0; diff --git a/src/game.cpp b/src/game.cpp index 5545646807ab6..7561207ce04be 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -653,6 +653,7 @@ void game::setup() coming_to_stairs.clear(); active_npc.clear(); faction_manager_ptr->clear(); + npc_log_manager_ptr->clear(); mission::clear_all(); Messages::clear_messages(); timed_events = timed_event_manager(); diff --git a/src/game.h b/src/game.h index e06e3cda3f29e..299fa828ff0fa 100644 --- a/src/game.h +++ b/src/game.h @@ -101,6 +101,7 @@ class map; class tripoint_range; class memorial_logger; class faction_manager; +class npc_log_manager; class npc; class player; class stats_tracker; @@ -944,6 +945,7 @@ class game pimpl critter_tracker; pimpl faction_manager_ptr; + pimpl npc_log_manager_ptr; /** Used in main.cpp to determine what type of quit is being performed. */ quit_status uquit; diff --git a/src/npc.cpp b/src/npc.cpp index 6246f2cbec736..2a529a81d55af 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -117,6 +117,31 @@ class monfaction; void starting_clothes( npc &who, const npc_class_id &type, bool male ); void starting_inv( npc &who, const npc_class_id &type ); +namespace io +{ + +template<> +std::string enum_to_string( const log_entry_type type ) +{ + switch( type ) { + // *INDENT-OFF* + case ENTRY_CHANGED_MISSION: return "ENTRY_CHANGED_MISSION"; + case ENTRY_CHANGED_ATTITUDE: return "ENTRY_CHANGED_ATTITUDE"; + case ENTRY_CHANGED_ACTIVITY: return "ENTRY_CHANGED_ACTIVITY"; + case ENTRY_WORK_RESULT: return "ENTRY_WORK_RESULT"; + // *INDENT-ON* + case NUM_ENTRY_TYPE: + break; + } + debugmsg( "Invalid log_entry_type" ); + abort(); +} + +} //namespace io + +void starting_clothes( npc &who, const npc_class_id &type, bool male ); +void starting_inv( npc &who, const npc_class_id &type ); + npc::npc() : restock( calendar::turn_zero ) , companion_mission_time( calendar::before_time_starts ) @@ -633,10 +658,144 @@ void starting_inv( npc &who, const npc_class_id &type ) who.inv += res; } +void npc_log_manager::display( std::vector filter_ids ) +{ + std::vector res; + std::vector log_vec; + if( filter_ids.empty() ) { + log_vec = get_log_as_vector(); + } else if( static_cast( filter_ids.size() ) == 1 ) { + log_vec = get_log_filtered_by_id( filter_ids[0] ); + } else { + log_vec = get_log_filtered_by_ids( filter_ids ); + } + for( const npc_work_log_entry entry : log_vec ) { + // work log does double duty as a debug log of sorts. + // and also feedback as to why NPCs fail to do activities etc. + if( debug_mode ) { + res.push_back( entry.entry_to_string() ); + } else { + if( entry.get_entry_type() == ENTRY_WORK_RESULT || + entry.get_entry_type() == ENTRY_CHANGED_ACTIVITY ) { + res.push_back( entry.entry_to_string() ); + } + } + } + display( res ); +} + +void npc_log_manager::display( character_id filter_id ) +{ + std::vector res; + res.push_back( filter_id.get_value() ); + display( res ); +} + +void npc_log_manager::display( std::vector entries ) +{ + if( entries.empty() ) { + return; + } + folded.clear(); + w_width = std::min( TERMX, FULL_SCREEN_WIDTH ); + w_height = std::min( TERMY, FULL_SCREEN_HEIGHT ); + w_x = ( TERMX - w_width ) / 2; + w_y = ( TERMY - w_height ) / 2; + + w = catacurses::newwin( w_height, w_width, point( w_x, w_y ) ); + + ctxt = input_context( "NPC_LOG_MANAGER" ); + ctxt.register_action( "UP" ); + ctxt.register_action( "DOWN" ); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "PAGE_UP" ); + ctxt.register_action( "PAGE_DOWN" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + + max_width = w_width - 2; + max_height = w_height - 2; + + offset = 0; + + for( size_t i = 0; i < entries.size(); ++i ) { + const std::vector lines = foldstring( entries[i], max_width - 1 ); + folded.insert( folded.end(), lines.begin(), lines.end() ); + } + run(); +} + +void npc_log_manager::show() +{ + werase( w ); + draw_border( w, c_white, _( "Work Log" ) ); + + scrollbar() + .offset_x( 0 ) + .offset_y( 1 ) + .content_size( folded.size() ) + .viewport_pos( offset ) + .viewport_size( max_height ) + .apply( w ); + + nc_color col = c_light_gray; + + for( int i = 0; i < std::min( static_cast( folded.size() ), max_height ); ++i ) { + print_colored_text( w, point( 2, 1 + i ), col, col, + folded[offset + i] ); + } + + wrefresh( w ); +} + +void npc_log_manager::run() +{ + bool done = false; + while( !done ) { + show(); + const std::string &action = ctxt.handle_input(); + if( action == "UP" ) { + offset = std::max( 0, offset - 1 ); + } else if( action == "DOWN" ) { + offset = clamp( offset + 1, 0, static_cast( folded.size() ) - max_height ); + } else if( action == "QUIT" ) { + done = true; + } else if( action == "PAGE_DOWN" ) { + offset = clamp( offset + 10, 0, static_cast( folded.size() ) - max_height ); + } else if( action == "PAGE_UP" ) { + offset = std::max( 0, offset - 10 ); + } + } +} + +void npc::add_to_work_log( const log_entry_type &event_type, const std::string &description ) +{ + npc_work_log_entry new_entry = npc_work_log_entry( *this, event_type, description ); + g->npc_log_manager_ptr->add_entry( new_entry ); +} + +void npc::add_to_work_log( const log_entry_type &event_type, const tripoint &work_spot, + const std::string &description ) +{ + npc_work_log_entry new_entry = npc_work_log_entry( *this, event_type, description ); + new_entry.set_entry_position( work_spot ); + g->npc_log_manager_ptr->add_entry( new_entry ); +} + +void npc::add_to_work_log( const log_entry_type &event_type, const tripoint &work_spot, + const activity_reason_info &reason, const std::string &description ) +{ + npc_work_log_entry new_entry = npc_work_log_entry( *this, event_type, description ); + new_entry.set_entry_position( work_spot ); + new_entry.set_entry_reason( reason ); + g->npc_log_manager_ptr->add_entry( new_entry ); +} + void npc::revert_after_activity() { mission = previous_mission; attitude = previous_attitude; + add_to_work_log( ENTRY_CHANGED_MISSION, get_mission_conversion_string() ); + add_to_work_log( ENTRY_CHANGED_ATTITUDE, npc_attitude_name( attitude ) ); activity = player_activity(); current_activity_id = activity_id::NULL_ID(); clear_destination(); @@ -3096,11 +3255,50 @@ void npc::set_mission( npc_mission new_mission ) previous_mission = mission; mission = new_mission; } + add_to_work_log( ENTRY_CHANGED_MISSION, get_mission_conversion_string() ); if( mission == NPC_MISSION_ACTIVITY ) { current_activity_id = activity.id(); } } +std::string npc::get_mission_conversion_string() +{ + std::string ret; + switch( mission ) { + case NPC_MISSION_SHELTER: + ret = _( "Mission - Shelter" ); + break; + case NPC_MISSION_SHOPKEEP: + ret = _( "Mission - Shopkeep" ); + break; + case NPC_MISSION_GUARD: + ret = _( "Mission - Guard" ); + break; + case NPC_MISSION_GUARD_ALLY: + ret = _( "Mission - Guard Ally" ); + break; + case NPC_MISSION_GUARD_PATROL: + ret = _( "Mission - Guard Patrol" ); + break; + case NPC_MISSION_ACTIVITY: + ret = _( "Mission - Activity" ); + break; + case NPC_MISSION_TRAVELLING: + ret = _( "Mission - Travelling" ); + break; + case NPC_MISSION_NULL: + ret = _( "Mission - Null" ); + break; + case NPC_MISSION_ASSIGNED_CAMP: + ret = _( "Mission - Working at Camp" ); + break; + default: + ret = _( "Mission - Legacy or error" ); + break; + } + return ret; +} + bool npc::has_activity() const { return mission == NPC_MISSION_ACTIVITY && attitude == NPCATT_ACTIVITY; @@ -3126,6 +3324,7 @@ void npc::set_attitude( npc_attitude new_attitude ) add_msg( m_debug, "%s changes attitude from %s to %s", name, npc_attitude_id( attitude ), npc_attitude_id( new_attitude ) ); + add_to_work_log( ENTRY_CHANGED_ATTITUDE, npc_attitude_name( new_attitude ) ); attitude_group new_group = get_attitude_group( new_attitude ); attitude_group old_group = get_attitude_group( attitude ); if( new_group != old_group && !is_fake() && g->u.sees( *this ) ) { diff --git a/src/npc.h b/src/npc.h index 434cf3dfce3fa..884b48b66945a 100644 --- a/src/npc.h +++ b/src/npc.h @@ -14,8 +14,11 @@ #include #include +#include "activity_handlers.h" #include "calendar.h" #include "faction.h" +#include "ime.h" +#include "input.h" #include "line.h" #include "lru_cache.h" #include "optional.h" @@ -28,6 +31,7 @@ #include "item_location.h" #include "string_formatter.h" #include "string_id.h" +#include "string_input_popup.h" #include "material.h" #include "type_id.h" #include "int_id.h" @@ -52,7 +56,10 @@ struct pathfinding_settings; class monfaction; class npc_class; struct mission_type; +struct activity_reason_info; +class window; +enum log_entry_type : int; enum game_message_type : int; class gun_mode; @@ -175,6 +182,19 @@ class job_data void deserialize( JsonIn &jsin ); }; +enum log_entry_type : int { + ENTRY_CHANGED_MISSION = 0, + ENTRY_CHANGED_ATTITUDE, + ENTRY_CHANGED_ACTIVITY, + ENTRY_WORK_RESULT, + NUM_ENTRY_TYPE +}; + +template<> +struct enum_traits { + static constexpr log_entry_type last = NUM_ENTRY_TYPE; +}; + enum npc_mission : int { NPC_MISSION_NULL = 0, // Nothing in particular NPC_MISSION_LEGACY_1, @@ -1239,6 +1259,7 @@ class npc : public player npc_attitude get_attitude() const; void set_attitude( npc_attitude new_attitude ); void set_mission( npc_mission new_mission ); + std::string get_mission_conversion_string(); bool has_activity() const; bool has_job() const { return job.has_job(); @@ -1246,6 +1267,11 @@ class npc : public player npc_attitude get_previous_attitude(); npc_mission get_previous_mission(); void revert_after_activity(); + void add_to_work_log( const log_entry_type &event_type, const std::string &description ); + void add_to_work_log( const log_entry_type &event_type, const tripoint &work_spot, + const std::string &description ); + void add_to_work_log( const log_entry_type &event_type, const tripoint &work_spot, + const activity_reason_info &reason, const std::string &description ); // ############# VALUES ################ activity_id current_activity_id = activity_id::NULL_ID(); @@ -1422,6 +1448,165 @@ class npc_template static void check_consistency(); }; +class npc_work_log_entry +{ + private: + // required + std::string entry_npc_name; + time_point entry_timestamp; + int entry_npc_id; + log_entry_type entry_type; + std::string entry_description; + // not required + cata::optional entry_position; + cata::optional entry_reason; + public: + npc_work_log_entry() { + entry_npc_name = "nullname"; + entry_timestamp = calendar::before_time_starts; + entry_npc_id = -1; + entry_type = NUM_ENTRY_TYPE; + entry_description = "nulldesc"; + } + npc_work_log_entry( npc &p, log_entry_type new_entry_type, std::string new_entry_description ) { + entry_timestamp = calendar::turn; + entry_npc_name = p.name; + entry_npc_id = p.getID().get_value(); + entry_type = new_entry_type; + entry_description = new_entry_description; + } + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); + std::string get_entry_npc_name() const { + return entry_npc_name; + } + time_point get_entry_timestamp() const { + return entry_timestamp; + } + int get_entry_npc_id() const { + return entry_npc_id; + } + log_entry_type get_entry_type() const { + return entry_type; + } + std::string get_entry_description() const { + return entry_description; + } + cata::optional get_entry_position() const { + return entry_position; + } + cata::optional get_entry_reason() const { + return entry_reason; + } + void set_entry_position( const tripoint &work_spot ) { + entry_position = work_spot; + } + void set_entry_reason( const activity_reason_info &reason ) { + entry_reason = reason; + } + std::string get_type_description() const { + std::string ret; + switch( entry_type ) { + case ENTRY_CHANGED_MISSION: + ret = _( "mission is now:" ); + break; + case ENTRY_CHANGED_ATTITUDE: + ret = _( "attitude is now:" ); + break; + case ENTRY_CHANGED_ACTIVITY: + ret = _( "activity is now:" ); + break; + case ENTRY_WORK_RESULT: + ret = _( "work result:" ); + break; + default: + ret = ""; + break; + } + return ret; + } + std::string entry_to_string( bool timestamp = true ) const { + std::string ret; + if( timestamp ) { + ret = string_format( _( "%s: %s %s %s" ), to_string( entry_timestamp ), entry_npc_name, + get_type_description(), entry_description ); + } else { + ret = string_format( _( "%s %s %s" ), entry_npc_name, get_type_description(), entry_description ); + } + return ret; + } +}; + +class npc_log_manager +{ + public: + void clear() { + log.clear(); + } + void display( std::vector text_entries ); + void display( character_id filter_id ); + void display( std::vector filter_ids = std::vector() ); + void run(); + void show(); + void serialize( JsonOut &jsout ) const; + void deserialize( JsonIn &jsin ); + void add_entry( npc_work_log_entry new_entry ) { + log.push_back( new_entry ); + if( log.size() > 5000 ) { + log.pop_front(); + } + } + std::deque get_log() const { + return log; + } + std::vector get_log_as_vector() const { + return std::vector( log.begin(), log.end() ); + } + cata::optional get_most_recent_for_id( const character_id id ) { + for( auto i = log.rbegin(); i != log.rend(); ++i ) { + if( i->get_entry_timestamp() != calendar::turn ) { + return cata::nullopt; + } + if( i->get_entry_npc_id() != id.get_value() ) { + continue; + } + return *i; + } + return cata::nullopt; + } + std::vector get_log_filtered_by_id( int id ) const { + std::vector ret; + for( const npc_work_log_entry entry : log ) { + if( entry.get_entry_npc_id() == id ) { + ret.push_back( entry ); + } + } + return ret; + } + std::vector get_log_filtered_by_ids( const std::vector ids ) const { + std::vector ret; + if( ids.empty() ) { + return ret; + } + for( const npc_work_log_entry entry : log ) { + if( std::find( ids.begin(), ids.end(), entry.get_entry_npc_id() ) != ids.end() ) { + ret.push_back( entry ); + } + } + return ret; + } + private: + std::deque log; + int w_x, w_y, w_width, w_height; + int max_width, max_height; + int offset; + + input_context ctxt; + catacurses::window w; + + std::vector folded; +}; + std::ostream &operator<< ( std::ostream &os, const npc_need &need ); /** Opens a menu and allows player to select a friendly NPC. */ diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 37e5805b15558..7aa3bc342e706 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -2580,6 +2580,7 @@ void talk_effect_t::parse_string_effect( const std::string &effect_id, const Jso WRAP( do_farming ), WRAP( assign_guard ), WRAP( assign_camp ), + WRAP( show_work_log ), WRAP( abandon_camp ), WRAP( stop_guard ), WRAP( start_camp ), diff --git a/src/npctalk.h b/src/npctalk.h index 4aa558fc29679..49a194fbe26f6 100644 --- a/src/npctalk.h +++ b/src/npctalk.h @@ -51,6 +51,7 @@ void revert_activity( npc & ); void goto_location( npc & ); void assign_base( npc & ); void assign_guard( npc & ); +void show_work_log( npc & ); void assign_camp( npc & ); void abandon_camp( npc & ); void stop_guard( npc & ); diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index 4d089bac27979..83147d190078f 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -379,6 +379,11 @@ void talk_function::abandon_camp( npc &p ) } } +void talk_function::show_work_log( npc &p ) +{ + g->npc_log_manager_ptr->display( p.getID() ); +} + void talk_function::assign_camp( npc &p ) { cata::optional bcp = overmap_buffer.find_camp( p.global_omt_location().xy() ); diff --git a/src/savegame.cpp b/src/savegame.cpp index 3a6fedb4980f3..62de949d14e5b 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -1636,6 +1636,8 @@ void game::unserialize_master( std::istream &fin ) mission::unserialize_all( jsin ); } else if( name == "factions" ) { jsin.read( *faction_manager_ptr ); + } else if( name == "work_log" ) { + jsin.read( *npc_log_manager_ptr ); } else if( name == "seed" ) { jsin.read( seed ); } else if( name == "weather" ) { @@ -1674,6 +1676,7 @@ void game::serialize_master( std::ostream &fout ) mission::serialize_all( json ); json.member( "factions", *faction_manager_ptr ); + json.member( "work_log", *npc_log_manager_ptr ); json.member( "seed", seed ); json.member( "weather" ); @@ -1687,6 +1690,16 @@ void game::serialize_master( std::ostream &fout ) } } +void npc_log_manager::serialize( JsonOut &jsout ) const +{ + jsout.write( log ); +} + +void npc_log_manager::deserialize( JsonIn &jsin ) +{ + jsin.read( log ); +} + void faction_manager::serialize( JsonOut &jsout ) const { std::vector local_facs; diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 44581e31d3c2e..12c153a69a481 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -26,6 +26,7 @@ #include #include "activity_actor.h" +#include "activity_handlers.h" #include "auto_pickup.h" #include "assign.h" #include "avatar.h" @@ -3023,6 +3024,52 @@ void mission::serialize( JsonOut &json ) const json.end_object(); } +void activity_reason_info::serialize( JsonOut &json ) const +{ + json.start_object(); + json.member( "reason" ); + json.write_as_string( reason ); + json.member( "can_do", can_do ); + json.member( "con_idx", con_idx ); + json.member( "added_reason", added_reason ); + json.end_object(); +} + +void activity_reason_info::deserialize( JsonIn &jsin ) +{ + JsonObject jo = jsin.get_object(); + reason = jo.get_enum_value( "reason" ); + jo.read( "can_do", can_do ); + jo.read( "con_idx", con_idx ); + jo.read( "added_reason", added_reason ); +} + +void npc_work_log_entry::serialize( JsonOut &json ) const +{ + json.start_object(); + json.member( "entry_npc_name", entry_npc_name ); + json.member( "entry_timestamp", entry_timestamp ); + json.member( "entry_npc_id", entry_npc_id ); + json.member( "entry_type" ); + json.write_as_string( entry_type ); + json.member( "entry_description", entry_description ); + json.member( "entry_position", entry_position ); + json.member( "entry_reason", entry_reason ); + json.end_object(); +} + +void npc_work_log_entry::deserialize( JsonIn &jsin ) +{ + JsonObject jo = jsin.get_object(); + jo.read( "entry_npc_name", entry_npc_name ); + jo.read( "entry_timestamp", entry_timestamp ); + jo.read( "entry_npc_id", entry_npc_id ); + entry_type = jo.get_enum_value( "entry_type" ); + jo.read( "entry_description", entry_description ); + jo.read( "entry_position", entry_position ); + jo.read( "entry_reason", entry_reason ); +} + ////////////////// faction.h //// void faction::deserialize( JsonIn &jsin ) From fab446982efab3cb86a17c4874624bcdff3c1146 Mon Sep 17 00:00:00 2001 From: dpwb Date: Mon, 10 Feb 2020 10:50:13 +0000 Subject: [PATCH 2/5] con idx as construction_id --- src/activity_handlers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activity_handlers.h b/src/activity_handlers.h index 2b63cce630383..b99a4d44e42e6 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -82,7 +82,7 @@ struct activity_reason_info { con_idx( cata::nullopt ), added_reason( std::string() ) { } activity_reason_info( do_activity_reason reason_, bool can_do_, - const cata::optional &con_idx_ = cata::nullopt, + cata::optional con_idx_ = cata::nullopt, std::string added_reason_ = std::string() ) : reason( reason_ ), can_do( can_do_ ), From 8335c1f4e5d6a39320326f3789d10d7fa80a0aca Mon Sep 17 00:00:00 2001 From: dpwb Date: Mon, 10 Feb 2020 11:23:21 +0000 Subject: [PATCH 3/5] serialize new construction_id from activity_reason_info --- src/savegame_json.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 12c153a69a481..2a1aa30ab5eea 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -3030,7 +3030,11 @@ void activity_reason_info::serialize( JsonOut &json ) const json.member( "reason" ); json.write_as_string( reason ); json.member( "can_do", can_do ); - json.member( "con_idx", con_idx ); + if( con_idx ) { + json.member( "con_idx", con_idx->id() ); + } else { + json.member( "con_idx", -1 ); + } json.member( "added_reason", added_reason ); json.end_object(); } @@ -3040,7 +3044,12 @@ void activity_reason_info::deserialize( JsonIn &jsin ) JsonObject jo = jsin.get_object(); reason = jo.get_enum_value( "reason" ); jo.read( "can_do", can_do ); - jo.read( "con_idx", con_idx ); + if( jo.has_int( "index " ) ) { + // optional index not there + con_idx = cata::nullopt; + } else { + con_idx = construction_str_id( jo.get_string( "con_idx" ) ).id(); + } jo.read( "added_reason", added_reason ); } From 16a248192d83a8220943f1edb683fc43591a3f8b Mon Sep 17 00:00:00 2001 From: dpwb Date: Fri, 6 Mar 2020 16:39:22 +0000 Subject: [PATCH 4/5] redundant redeclaration removal --- src/npc.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/npc.cpp b/src/npc.cpp index 2a529a81d55af..9f808fa7b7c97 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -139,9 +139,6 @@ std::string enum_to_string( const log_entry_type type ) } //namespace io -void starting_clothes( npc &who, const npc_class_id &type, bool male ); -void starting_inv( npc &who, const npc_class_id &type ); - npc::npc() : restock( calendar::turn_zero ) , companion_mission_time( calendar::before_time_starts ) From ef1b38e9baef809dace65bc388c2470ff5f2a892 Mon Sep 17 00:00:00 2001 From: dpwb Date: Sat, 4 Apr 2020 19:00:25 +0100 Subject: [PATCH 5/5] fix up, clean up little bugs, optimize scanning --- src/activity_handlers.cpp | 1 + src/activity_item_handling.cpp | 27 ++++++++++++++++++++++++--- src/faction_camp.cpp | 4 ++++ src/item_factory.cpp | 2 +- src/npc.cpp | 14 +++----------- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 88d30c496bfe1..7b90743be8c42 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -305,6 +305,7 @@ std::string enum_to_string( const do_activity_reason act ) case NEEDS_VEH_DECONST: return "NEEDS_VEH_DECONST"; case NEEDS_VEH_REPAIR: return "NEEDS_VEH_REPAIR"; case NEEDS_FISHING: return "NEEDS_FISHING"; + case NEEDS_MINING: return "NEEDS_MINING"; // *INDENT-ON* case NUM_DO_ACTIVITY_REASON: break; diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 53f918cb72fa4..2a041f3422a02 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -2744,7 +2744,7 @@ static requirement_check_result generic_multi_activity_check_requirement( player check_npc_revert( p ); if( npc *const guy = dynamic_cast( &p ) ) { const std::string text = string_format( - _( "cannot complete %s due as I cannot see where to move components" ), + _( "cannot complete %s as I cannot see where to move components" ), act_id.obj().verb() ); guy->add_to_work_log( ENTRY_WORK_RESULT, text ); } @@ -2875,7 +2875,6 @@ static void report_work_log_result( player &p, activity_id &act_id, activity_rea if( !p.is_npc() ) { return; } - ( void )act_id; npc *guy = dynamic_cast( &p ); std::string construction_desc = ""; std::string tripoint_string = string_format( _( "x:%d,y:%d" ), src_loc.x, src_loc.y ); @@ -2932,6 +2931,11 @@ static void report_work_log_result( player &p, activity_id &act_id, activity_rea ignore = true; } else if( act_id == activity_id( "ACT_TIDY_UP" ) ) { desc = _( "trying to tidy up, but there is no unsorted zones nearby." ); + } else { + player_activity test_act = player_activity( act_id ); + desc = string_format( + _( "the job is %s, but there is no applicable zone or object to work on at %s" ), + test_act.get_verb(), tripoint_string ); } break; } @@ -3042,6 +3046,10 @@ bool generic_multi_activity_handler( player_activity &act, player &p, bool check const tripoint abspos = g->m.getabs( p.pos() ); // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) activity_id activity_to_restore = act.id(); + tripoint prioritised_spot = tripoint_zero; + if( act.placement != tripoint_zero && act.placement != tripoint_min ) { + prioritised_spot = act.placement; + } // Nuke the current activity, leaving the backlog alone if( !check_only ) { p.activity = player_activity(); @@ -3051,6 +3059,15 @@ bool generic_multi_activity_handler( player_activity &act, player &p, bool check std::unordered_set src_set = generic_multi_activity_locations( p, activity_to_restore ); // now we have our final set of points std::vector src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); + if( prioritised_spot != tripoint_zero && activity_to_restore != ACT_FETCH_REQUIRED && + activity_to_restore != ACT_TIDY_UP ) { + std::vector::iterator it = std::find( src_sorted.begin(), src_sorted.end(), + prioritised_spot ); + if( it != src_sorted.end() ) { + src_sorted.erase( it ); + } + src_sorted.insert( src_sorted.begin(), prioritised_spot ); + } // now loop through the work-spot tiles and judge whether its worth travelling to it yet // or if we need to fetch something first. if( src_set.empty() && p.is_npc() ) { @@ -3118,7 +3135,11 @@ bool generic_multi_activity_handler( player_activity &act, player &p, bool check // we don't need to check for safe mode, // activity will be restarted only if // player arrives on destination tile - p.set_destination( route, player_activity( activity_to_restore ) ); + player_activity new_activity_to_restore( activity_to_restore ); + // this process identified the spot that we want to work at. + // so prioritise to recheck that one first next time around when we arrive. + new_activity_to_restore.placement = src; + p.set_destination( route, new_activity_to_restore ); return true; } } diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index 9291d21ef503e..6aaad144ae38a 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -1723,6 +1723,10 @@ void basecamp::worker_assignment_ui() void basecamp::show_work_log() { std::vector char_ids; + validate_assignees(); + if( get_npcs_assigned().empty() ) { + return; + } for( const npc_ptr guy_ptr : get_npcs_assigned() ) { char_ids.push_back( guy_ptr.get()->getID().get_value() ); } diff --git a/src/item_factory.cpp b/src/item_factory.cpp index c0681d4cccbce..2fc7e1ec7b7c6 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -1848,7 +1848,7 @@ void Item_factory::load( islot_comestible &slot, const JsonObject &jo, const std assign( jo, "spoils_in", slot.spoils, strict, 1_hours ); assign( jo, "cooks_like", slot.cooks_like, strict ); assign( jo, "smoking_result", slot.smoking_result, strict ); - + for( const JsonObject &jsobj : jo.get_array( "contamination" ) ) { slot.contamination.emplace( diseasetype_id( jsobj.get_string( "disease" ) ), jsobj.get_int( "probability" ) ); diff --git a/src/npc.cpp b/src/npc.cpp index 9f808fa7b7c97..2ecd34b3a3936 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -690,9 +690,6 @@ void npc_log_manager::display( character_id filter_id ) void npc_log_manager::display( std::vector entries ) { - if( entries.empty() ) { - return; - } folded.clear(); w_width = std::min( TERMX, FULL_SCREEN_WIDTH ); w_height = std::min( TERMY, FULL_SCREEN_HEIGHT ); @@ -713,7 +710,6 @@ void npc_log_manager::display( std::vector entries ) max_height = w_height - 2; offset = 0; - for( size_t i = 0; i < entries.size(); ++i ) { const std::vector lines = foldstring( entries[i], max_width - 1 ); folded.insert( folded.end(), lines.begin(), lines.end() ); @@ -725,7 +721,6 @@ void npc_log_manager::show() { werase( w ); draw_border( w, c_white, _( "Work Log" ) ); - scrollbar() .offset_x( 0 ) .offset_y( 1 ) @@ -733,20 +728,20 @@ void npc_log_manager::show() .viewport_pos( offset ) .viewport_size( max_height ) .apply( w ); - nc_color col = c_light_gray; for( int i = 0; i < std::min( static_cast( folded.size() ), max_height ); ++i ) { + const int index = std::min( static_cast( folded.size() ), offset + i ); print_colored_text( w, point( 2, 1 + i ), col, col, - folded[offset + i] ); + folded[static_cast( index )] ); } - wrefresh( w ); } void npc_log_manager::run() { bool done = false; + offset = std::min( static_cast( folded.size() ), max_height ); while( !done ) { show(); const std::string &action = ctxt.handle_input(); @@ -3286,9 +3281,6 @@ std::string npc::get_mission_conversion_string() case NPC_MISSION_NULL: ret = _( "Mission - Null" ); break; - case NPC_MISSION_ASSIGNED_CAMP: - ret = _( "Mission - Working at Camp" ); - break; default: ret = _( "Mission - Legacy or error" ); break;