From 3ee43ddf24d74f9642c228746320e9d116e27f1c Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:45:55 -0500 Subject: [PATCH 1/9] move ACT_MULTIPLE_CONSTRUCTION to activity_actor --- src/activity_actor.cpp | 29 +++++++++++++++++++++++++++++ src/activity_actor_definitions.h | 19 +++++++++++++++++++ src/activity_handlers.cpp | 2 -- src/activity_item_handling.cpp | 2 +- src/construction.cpp | 2 +- src/handle_action.cpp | 3 +-- src/npcmove.cpp | 4 ++++ src/npctalk_funcs.cpp | 3 +-- tests/act_build_test.cpp | 2 +- 9 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 7dddc1a2a158e..60e9b9c40939e 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -192,6 +192,7 @@ static const activity_id ACT_MOVE_ITEMS( "ACT_MOVE_ITEMS" ); static const activity_id ACT_MOVE_LOOT( "ACT_MOVE_LOOT" ); static const activity_id ACT_MULTIPLE_CHOP_PLANKS( "ACT_MULTIPLE_CHOP_PLANKS" ); static const activity_id ACT_MULTIPLE_CHOP_TREES( "ACT_MULTIPLE_CHOP_TREES" ); +static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const activity_id ACT_MULTIPLE_CRAFT( "ACT_MULTIPLE_CRAFT" ); static const activity_id ACT_MULTIPLE_DIS( "ACT_MULTIPLE_DIS" ); static const activity_id ACT_MULTIPLE_FARM( "ACT_MULTIPLE_FARM" ); @@ -8113,6 +8114,33 @@ std::unique_ptr build_construction_activity_actor::deserialize( return actor.clone(); } +activity_reason_info multi_build_construction_activity_actor::multi_activity_can_do( + Character &you, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::construction_can_do( ACT_MULTIPLE_CONSTRUCTION, you, + src_loc ); +} + +std::unordered_set +multi_build_construction_activity_actor::multi_activity_locations( + Character &you ) +{ + return multi_activity_actor::construction_locations( you, get_type() ); +} + +bool multi_build_construction_activity_actor::multi_activity_do( Character &you, + const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::construction_do( you, act_info, src, src_loc ); +} + +std::unique_ptr multi_build_construction_activity_actor::deserialize( JsonValue & ) +{ + multi_build_construction_activity_actor actor; + return actor.clone(); +} + void reel_cable_activity_actor::start( player_activity &act, Character & ) { act.moves_total = moves_total; @@ -12927,6 +12955,7 @@ deserialize_functions = { { ACT_MOVE_LOOT, &zone_sort_activity_actor::deserialize }, { ACT_MULTIPLE_CHOP_PLANKS, &multi_chop_planks_activity_actor::deserialize }, { ACT_MULTIPLE_CHOP_TREES, &multi_chop_trees_activity_actor::deserialize }, + { ACT_MULTIPLE_CONSTRUCTION, &multi_build_construction_activity_actor::deserialize }, { ACT_MULTIPLE_CRAFT, &multi_craft_activity_actor::deserialize }, { ACT_MULTIPLE_DIS, &multi_disassemble_activity_actor::deserialize }, { ACT_MULTIPLE_FARM, &multi_farm_activity_actor::deserialize }, diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 8bd9a5c45c7d5..08297882f2374 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -387,6 +387,25 @@ class multi_disassemble_activity_actor : public multi_zone_activity_actor static std::unique_ptr deserialize( JsonValue & ); }; +class multi_build_construction_activity_actor : public multi_zone_activity_actor +{ + public: + using multi_zone_activity_actor::multi_zone_activity_actor; + const activity_id &get_type() const override { + static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); + return ACT_MULTIPLE_CONSTRUCTION; + } + std::unordered_set multi_activity_locations( Character &you ) override; + activity_reason_info multi_activity_can_do( Character &you, + const tripoint_bub_ms &src_loc ) override; + bool multi_activity_do( Character &you, const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + static std::unique_ptr deserialize( JsonValue & ); +}; + class aim_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index b9fa3f317ed23..6e7214d10aecf 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -64,7 +64,6 @@ static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" ); static const activity_id ACT_FILL_LIQUID( "ACT_FILL_LIQUID" ); static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); -static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const activity_id ACT_REPAIR_ITEM( "ACT_REPAIR_ITEM" ); static const activity_id ACT_START_FIRE( "ACT_START_FIRE" ); static const activity_id ACT_TIDY_UP( "ACT_TIDY_UP" ); @@ -102,7 +101,6 @@ const std::map< activity_id, std::functionas_character(), true ) ) { assign_activity( elem ); return true; diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index dd1b87113520a..d8a928aa2e69f 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -65,7 +65,6 @@ static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); -static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const efftype_id effect_allow_sleep( "allow_sleep" ); static const efftype_id effect_asked_for_item( "asked_for_item" ); @@ -238,7 +237,7 @@ void talk_function::sort_loot( npc &p ) void talk_function::do_construction( npc &p ) { - p.assign_activity( ACT_MULTIPLE_CONSTRUCTION ); + p.assign_activity( multi_build_construction_activity_actor() ); } void talk_function::do_mining( npc &p ) diff --git a/tests/act_build_test.cpp b/tests/act_build_test.cpp index 583985191dd04..9d691870716f5 100644 --- a/tests/act_build_test.cpp +++ b/tests/act_build_test.cpp @@ -79,7 +79,7 @@ void run_activities( Character &u, int max_moves ) { map &here = get_map(); - u.assign_activity( ACT_MULTIPLE_CONSTRUCTION ); + u.assign_activity( multi_build_construction_activity_actor() ); int turns = 0; while( ( !u.activity.is_null() || u.is_auto_moving() ) && turns < max_moves ) { u.set_moves( u.get_speed() ); From 84987dd8686f66e3ab442004374851e2bf3fad76 Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Fri, 30 Jan 2026 19:05:19 -0500 Subject: [PATCH 2/9] move ACT_MULTIPLE_BUTCHER to activity_actor --- src/activity_actor.cpp | 22 ++++++++++++++++++++++ src/activity_actor_definitions.h | 18 ++++++++++++++++++ src/activity_handlers.cpp | 2 -- src/activity_item_handling.cpp | 2 +- src/handle_action.cpp | 3 +-- src/npcmove.cpp | 4 ++++ src/npctalk_funcs.cpp | 3 +-- 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 60e9b9c40939e..bfeb9e360fb67 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -190,6 +190,7 @@ static const activity_id ACT_MILK( "ACT_MILK" ); static const activity_id ACT_MOP( "ACT_MOP" ); static const activity_id ACT_MOVE_ITEMS( "ACT_MOVE_ITEMS" ); static const activity_id ACT_MOVE_LOOT( "ACT_MOVE_LOOT" ); +static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); static const activity_id ACT_MULTIPLE_CHOP_PLANKS( "ACT_MULTIPLE_CHOP_PLANKS" ); static const activity_id ACT_MULTIPLE_CHOP_TREES( "ACT_MULTIPLE_CHOP_TREES" ); static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); @@ -12286,6 +12287,26 @@ std::unique_ptr butchery_activity_actor::deserialize( JsonValue return actor.clone(); } +activity_reason_info multi_butchery_activity_actor::multi_activity_can_do( + Character &you, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::butcher_can_do( ACT_MULTIPLE_BUTCHER, you, + src_loc ); +} + +bool multi_butchery_activity_actor::multi_activity_do( Character &you, + const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::butcher_do( you, act_info, src, src_loc ); +} + +std::unique_ptr multi_butchery_activity_actor::deserialize( JsonValue & ) +{ + multi_butchery_activity_actor actor; + return actor.clone(); +} + void wait_activity_actor::start( player_activity &act, Character & ) { act.moves_total = to_moves( initial_wait_time ); @@ -12953,6 +12974,7 @@ deserialize_functions = { { ACT_MOP, &mop_activity_actor::deserialize }, { ACT_MOVE_ITEMS, &move_items_activity_actor::deserialize }, { ACT_MOVE_LOOT, &zone_sort_activity_actor::deserialize }, + { ACT_MULTIPLE_BUTCHER, &multi_butchery_activity_actor::deserialize }, { ACT_MULTIPLE_CHOP_PLANKS, &multi_chop_planks_activity_actor::deserialize }, { ACT_MULTIPLE_CHOP_TREES, &multi_chop_trees_activity_actor::deserialize }, { ACT_MULTIPLE_CONSTRUCTION, &multi_build_construction_activity_actor::deserialize }, diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 08297882f2374..6c357657a2b5d 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -406,6 +406,24 @@ class multi_build_construction_activity_actor : public multi_zone_activity_actor static std::unique_ptr deserialize( JsonValue & ); }; +class multi_butchery_activity_actor : public multi_zone_activity_actor +{ + public: + using multi_zone_activity_actor::multi_zone_activity_actor; + const activity_id &get_type() const override { + static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); + return ACT_MULTIPLE_BUTCHER; + } + activity_reason_info multi_activity_can_do( Character &you, + const tripoint_bub_ms &src_loc ) override; + bool multi_activity_do( Character &you, const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + static std::unique_ptr deserialize( JsonValue & ); +}; + class aim_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 6e7214d10aecf..4f304b61de433 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -63,7 +63,6 @@ static const activity_id ACT_FERTILIZE_PLOT( "ACT_FERTILIZE_PLOT" ); static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" ); static const activity_id ACT_FILL_LIQUID( "ACT_FILL_LIQUID" ); static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); -static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); static const activity_id ACT_REPAIR_ITEM( "ACT_REPAIR_ITEM" ); static const activity_id ACT_START_FIRE( "ACT_START_FIRE" ); static const activity_id ACT_TIDY_UP( "ACT_TIDY_UP" ); @@ -101,7 +100,6 @@ const std::map< activity_id, std::functionas_character(), true ) ) { assign_activity( elem ); return true; diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index d8a928aa2e69f..821463e29c725 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -64,7 +64,6 @@ #include "viewer.h" static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); -static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); static const efftype_id effect_allow_sleep( "allow_sleep" ); static const efftype_id effect_asked_for_item( "asked_for_item" ); @@ -295,7 +294,7 @@ void talk_function::find_mount( npc &p ) void talk_function::do_butcher( npc &p ) { - p.assign_activity( ACT_MULTIPLE_BUTCHER ); + p.assign_activity( multi_butchery_activity_actor() ); } void talk_function::do_chop_plank( npc &p ) From 11b47fe88db6d4eb5fb4173ccd0720a8190002da Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Fri, 30 Jan 2026 20:43:22 -0500 Subject: [PATCH 3/9] move ACT_FETCH_REQUIRED to activity_actor --- src/activity_actor.cpp | 27 +++++++++++++++++++++++++++ src/activity_actor_definitions.h | 21 +++++++++++++++++++++ src/activity_handlers.cpp | 2 -- src/activity_item_handling.cpp | 2 +- tests/act_build_test.cpp | 1 + 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index bfeb9e360fb67..f73507c9b163b 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -5009,6 +5009,32 @@ void multi_zone_activity_actor::serialize( JsonOut &jsout ) const jsout.end_object(); } +activity_reason_info fetch_required_activity_actor::multi_activity_can_do( + Character &you, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::fetch_can_do( ACT_FETCH_REQUIRED, you, + src_loc ); +} + +std::unordered_set fetch_required_activity_actor::multi_activity_locations( + Character &you ) +{ + return multi_activity_actor::fetch_locations( you, get_type() ); +} + +bool fetch_required_activity_actor::multi_activity_do( Character &you, + const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::fetch_do( you, act_info, src, src_loc ); +} + +std::unique_ptr fetch_required_activity_actor::deserialize( JsonValue & ) +{ + fetch_required_activity_actor actor; + return actor.clone(); +} + std::unique_ptr multi_mine_activity_actor::deserialize( JsonValue & ) { multi_mine_activity_actor actor; @@ -12945,6 +12971,7 @@ deserialize_functions = { { ACT_DROP, &drop_activity_actor::deserialize }, { ACT_E_FILE, &efile_activity_actor::deserialize }, { ACT_EBOOKSAVE, &ebooksave_activity_actor::deserialize }, + { ACT_FETCH_REQUIRED, &fetch_required_activity_actor::deserialize }, { ACT_FIELD_DRESS, &butchery_activity_actor::deserialize }, { ACT_FIRSTAID, &firstaid_activity_actor::deserialize }, { ACT_FISH, &fish_activity_actor::deserialize }, diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 6c357657a2b5d..0f5aeda86613f 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -424,6 +424,27 @@ class multi_butchery_activity_actor : public multi_zone_activity_actor static std::unique_ptr deserialize( JsonValue & ); }; +// find items for a multi-activity +// NOTE: ACT_FETCH_REQUIRED is not a multi-activity but still uses the framework +class fetch_required_activity_actor : public multi_zone_activity_actor +{ + public: + using multi_zone_activity_actor::multi_zone_activity_actor; + const activity_id &get_type() const override { + static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); + return ACT_MULTIPLE_CONSTRUCTION; + } + std::unordered_set multi_activity_locations( Character &you ) override; + activity_reason_info multi_activity_can_do( Character &you, + const tripoint_bub_ms &src_loc ) override; + bool multi_activity_do( Character &you, const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + static std::unique_ptr deserialize( JsonValue & ); +}; + class aim_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 4f304b61de433..b71002fcd4704 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -60,7 +60,6 @@ #include "weather.h" static const activity_id ACT_FERTILIZE_PLOT( "ACT_FERTILIZE_PLOT" ); -static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" ); static const activity_id ACT_FILL_LIQUID( "ACT_FILL_LIQUID" ); static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); static const activity_id ACT_REPAIR_ITEM( "ACT_REPAIR_ITEM" ); @@ -100,7 +99,6 @@ const std::map< activity_id, std::function( act_info.reason ) ); diff --git a/tests/act_build_test.cpp b/tests/act_build_test.cpp index 9d691870716f5..2f3b63a0a694e 100644 --- a/tests/act_build_test.cpp +++ b/tests/act_build_test.cpp @@ -9,6 +9,7 @@ #include #include +#include "activity_actor_definitions.h" #include "activity_handlers.h" #include "avatar.h" #include "build_reqs.h" From 9ef5afdce944f916858f61261c2b9ba178005d1d Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:25:22 -0500 Subject: [PATCH 4/9] add missing multi-activity requirement functions --- src/activity_actor.cpp | 14 ++++++++++++++ src/activity_actor_definitions.h | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index f73507c9b163b..df833b6098573 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -8148,6 +8148,13 @@ activity_reason_info multi_build_construction_activity_actor::multi_activity_can src_loc ); } +std::optional multi_build_construction_activity_actor::multi_activity_requirements( + Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, const zone_data * ) +{ + return multi_activity_actor::construction_requirements( you, act_info, src_loc ); +} + std::unordered_set multi_build_construction_activity_actor::multi_activity_locations( Character &you ) @@ -12320,6 +12327,13 @@ activity_reason_info multi_butchery_activity_actor::multi_activity_can_do( src_loc ); } +std::optional multi_butchery_activity_actor::multi_activity_requirements( + Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, const zone_data * ) +{ + return multi_activity_actor::butcher_requirements( you, act_info, src_loc ); +} + bool multi_butchery_activity_actor::multi_activity_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 0f5aeda86613f..33ad2c9a5435e 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -398,6 +398,9 @@ class multi_build_construction_activity_actor : public multi_zone_activity_actor std::unordered_set multi_activity_locations( Character &you ) override; activity_reason_info multi_activity_can_do( Character &you, const tripoint_bub_ms &src_loc ) override; + std::optional multi_activity_requirements( Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, + const zone_data *zone = nullptr ) override; bool multi_activity_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; std::unique_ptr clone() const override { @@ -416,6 +419,9 @@ class multi_butchery_activity_actor : public multi_zone_activity_actor } activity_reason_info multi_activity_can_do( Character &you, const tripoint_bub_ms &src_loc ) override; + std::optional multi_activity_requirements( Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, + const zone_data *zone = nullptr ) override; bool multi_activity_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; std::unique_ptr clone() const override { From fab651af31ddb6170f96f436ad11e4c7da1e6fcc Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Sun, 1 Feb 2026 19:52:20 -0500 Subject: [PATCH 5/9] infinite loop checks for multi-activities --- src/character.cpp | 3 +++ tests/act_build_test.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/character.cpp b/src/character.cpp index 6e21b58bf38a5..9e6d6ae2d4ded 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -5215,6 +5215,9 @@ void Character::assign_activity( const player_activity &act ) } else { if( activity ) { backlog.push_front( activity ); + if( backlog.size() > 100 ) { + debugmsg( "activity backlog exceeded 100, likely an infinite loop" ); + } } activity = act; diff --git a/tests/act_build_test.cpp b/tests/act_build_test.cpp index 2f3b63a0a694e..b1816ce3a7aad 100644 --- a/tests/act_build_test.cpp +++ b/tests/act_build_test.cpp @@ -82,7 +82,11 @@ void run_activities( Character &u, int max_moves ) u.assign_activity( multi_build_construction_activity_actor() ); int turns = 0; - while( ( !u.activity.is_null() || u.is_auto_moving() ) && turns < max_moves ) { + while( ( !u.activity.is_null() || u.is_auto_moving() ) ) { + if( turns == max_moves ) { + FAIL( "turn count exceeded, infinite loop possible" ); + return; + } u.set_moves( u.get_speed() ); if( u.is_auto_moving() ) { u.setpos( here, here.get_bub( *u.destination_point ) ); @@ -166,6 +170,7 @@ void run_test_case( Character &u ) u.wear_item( item( itype_test_backpack ), false, false ); u.wear_item( item( itype_wearable_test_lamp ), false, true ); + //TODO: this test assumes that tools are on-person, but it should also test without tools on-person u.i_add( item( itype_test_multitool ) ); u.i_add( item( itype_hammer ) ); u.i_add( item( itype_bow_saw ) ); From ba01a27f846c50544fad893a36882731dddf27d7 Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:44:26 -0500 Subject: [PATCH 6/9] handling multi-activity backlog and fetching --- src/activity_actor.cpp | 125 +++++++++++++++++++++++++++---- src/activity_actor_definitions.h | 36 ++++++++- src/activity_item_handling.cpp | 121 +++++++++++++----------------- src/activity_item_handling.h | 2 +- 4 files changed, 194 insertions(+), 90 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index df833b6098573..46d2b739d3309 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -4764,10 +4764,13 @@ std::optional multi_zone_activity_actor::multi_activity_requirem void multi_zone_activity_actor::do_turn( player_activity &act, Character &you ) { + activity_id prior_act = get_type(); simulate_turn( act, you, false ); // If this activity still exists, end it - if( !act.is_null() && act.is_multi_type() ) { - act.set_to_null(); + if( !act.is_null() && ( prior_act == you.activity.id() ) ) { + // Nuke the current activity, leaving the backlog alone + // No multi_zone_activity_actor functions will be called from this point, so `this` can be nullptr + you.activity = player_activity(); } } @@ -4785,19 +4788,16 @@ bool multi_zone_activity_actor::simulate_turn( player_activity &act, Character & // now loop through the work-spot tiles and judge whether its worth traveling to it yet // or if we need to fetch something first. - // check: if a fetch activity was assigned but there's nothing to fetch, restore previous activity from backlog + // check: if a fetch activity was assigned but there's nothing to fetch, end fetch activity (restoring backlog) // may cause infinite loop if something goes wrong - //TODO: check whether a fetch activity should be assigned before it is + // TODO: this could be moved to a multi_zone_activity_actor virtual function for post-location checking if( current_activity == ACT_FETCH_REQUIRED && src_sorted.empty() ) { // remind what you failed to fetch if( !check_only ) { - if( !you.backlog.empty() ) { - player_activity &act_prev = you.backlog.front(); - if( !act_prev.str_values.empty() && you.as_npc() ) { - you.as_npc()->job.fetch_history[act_prev.str_values.back()] = calendar::turn; - } - } + fetch_required_activity_actor *as_fetch = static_cast( this ); + as_fetch->set_npc_fetch_history( you ); } + act.set_to_null(); return true; } @@ -4835,7 +4835,9 @@ bool multi_zone_activity_actor::simulate_turn( player_activity &act, Character & continue; } - //route to destination if needed + // route to destination if needed + // this stores a destination_activity that is resumed via + // can_resume_with_internal() in Character::assign_activity() std::optional route_result = multi_activity_actor::route( you, current_act_copy, src_bub, req_fail_reason, check_only ); if( !route_result ) { @@ -4994,8 +4996,7 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte return requirement_check_result::SKIP_LOCATION; } else { if( !check_only ) { - return multi_activity_actor::fetch_requirements( you, what_we_need, act_id, - act_info, src, src_loc, src_set ); + return fetch_requirements( you, what_we_need, act_info, src, src_loc, src_set ); } return requirement_check_result::RETURN_EARLY; } @@ -5003,6 +5004,55 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte return requirement_check_result::SKIP_LOCATION_NO_MATCH; } +bool multi_zone_activity_actor::can_resume_with_internal( const activity_actor &other_act, + const Character & ) const +{ + return true; +} + +requirement_check_result multi_zone_activity_actor::fetch_requirements( Character &you, + requirement_id what_we_need, activity_reason_info &act_info, const tripoint_abs_ms &src, + const tripoint_bub_ms &src_loc, const std::unordered_set &src_set ) +{ + + map &here = get_map(); + + if( you.as_npc() && you.as_npc()->job.fetch_history.count( what_we_need.str() ) != 0 && + you.as_npc()->job.fetch_history[what_we_need.str()] == calendar::turn ) { + // this may be a failed fetch already. Quit task to avoid infinite loop. + you.activity = player_activity(); + you.backlog.clear(); + multi_activity_actor::check_npc_revert( you ); + return requirement_check_result::SKIP_LOCATION; + } + // come back here after successfully fetching your stuff + std::vector local_src_set; + local_src_set.reserve( src_set.size() ); + for( const tripoint_abs_ms &elem : src_set ) { + local_src_set.push_back( here.get_bub( elem ) ); + } + std::vector candidates; + for( const tripoint_bub_ms &point_elem : + here.points_in_radius( src_loc, PICKUP_RANGE - 1, 0 ) ) { + // we don't want to place the components where they could interfere with our ( or someone else's ) construction spots + if( ( std::find( local_src_set.begin(), local_src_set.end(), + point_elem ) != local_src_set.end() ) || !here.can_put_items_ter_furn( point_elem ) ) { + continue; + } + candidates.push_back( point_elem ); + } + if( candidates.empty() ) { + you.activity = player_activity(); + you.backlog.clear(); + multi_activity_actor::check_npc_revert( you ); + return requirement_check_result::SKIP_LOCATION_NO_LOCATION; + } + + you.assign_activity( fetch_required_activity_actor( what_we_need, act_info.reason, here.get_abs( + candidates[std::max( 0, static_cast( candidates.size() / 2 ) )] ), src ) ); + return requirement_check_result::RETURN_EARLY; +} + void multi_zone_activity_actor::serialize( JsonOut &jsout ) const { jsout.start_object(); @@ -5016,17 +5066,62 @@ activity_reason_info fetch_required_activity_actor::multi_activity_can_do( src_loc ); } +void fetch_required_activity_actor::set_npc_fetch_history( Character &you ) +{ + if( !you.backlog.empty() ) { + player_activity &act_prev = you.backlog.front(); + if( !fetch_requirements.is_empty() && you.as_npc() ) { + you.as_npc()->job.fetch_history[fetch_requirements.str()] = calendar::turn; + } + } +} + +bool fetch_required_activity_actor::fetch_activity_valid( const Character &you ) const +{ + if( you.backlog.empty() ) { + debugmsg( "fetch activity assigned without a corresponding multi-activity" ); + return false; + } + return true; +} + std::unordered_set fetch_required_activity_actor::multi_activity_locations( Character &you ) { - return multi_activity_actor::fetch_locations( you, get_type() ); + map &here = get_map(); + std::unordered_set src_set; + + // get the right zones for the items in the requirements. + // we previously checked if the items are nearby before we set the fetch task + // but we will check again later, to be sure nothings changed. + std::vector> mental_map = + requirements_map( you, MAX_VIEW_DISTANCE ); + for( const auto &elem : mental_map ) { + const tripoint_bub_ms &elem_point = std::get<0>( elem ); + src_set.insert( here.get_abs( elem_point ) ); + } + multi_activity_actor::prune_dangerous_field_locations( src_set ); + + return src_set; } bool fetch_required_activity_actor::multi_activity_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) { - return multi_activity_actor::fetch_do( you, act_info, src, src_loc ); + const do_activity_reason &reason = act_info.reason; + + if( reason == do_activity_reason::CAN_DO_FETCH ) { + if( fetch_activity( you, src_loc, ACT_FETCH_REQUIRED, MAX_VIEW_DISTANCE ) ) { + if( !you.is_npc() ) { + // Npcs will automatically start the next thing in the backlog, players need to be manually prompted + // Because some player activities are necessarily not marked as auto-resume. + activity_handlers::resume_for_multi_activities( you ); + } + return false; + } + } + return true; } std::unique_ptr fetch_required_activity_actor::deserialize( JsonValue & ) diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 33ad2c9a5435e..e6046100e0243 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -120,6 +120,13 @@ class multi_zone_activity_actor : public activity_actor requirement_check_result check_requirements( Character &you, activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc, const std::unordered_set &src_set, bool check_only = false ); + // assigns fetch activity to find provided requirements + requirement_check_result fetch_requirements( Character &you, requirement_id what_we_need, + activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc, + const std::unordered_set &src_set ); + // @return equal types + bool can_resume_with_internal( const activity_actor &other_act, + const Character & ) const override; // BEGIN interface block virtual std::unordered_set multi_activity_locations( Character &you ); @@ -430,16 +437,32 @@ class multi_butchery_activity_actor : public multi_zone_activity_actor static std::unique_ptr deserialize( JsonValue & ); }; -// find items for a multi-activity +// find and retrieve items for the front multi-activity of the backlog // NOTE: ACT_FETCH_REQUIRED is not a multi-activity but still uses the framework class fetch_required_activity_actor : public multi_zone_activity_actor { public: using multi_zone_activity_actor::multi_zone_activity_actor; + explicit fetch_required_activity_actor( requirement_id fetch_requirements, + do_activity_reason act_reason, tripoint_abs_ms fetched_item_destination, + tripoint_abs_ms fetch_for_activity_position ) : + fetch_requirements( fetch_requirements ), act_reason( act_reason ), + fetched_item_destination( fetched_item_destination ), + fetch_for_activity_position( fetch_for_activity_position ) { + }; const activity_id &get_type() const override { - static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); - return ACT_MULTIPLE_CONSTRUCTION; + static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" ); + return ACT_FETCH_REQUIRED; } + + void set_npc_fetch_history( Character &you ); + std::vector> requirements_map( Character &you, + const int distance = MAX_VIEW_DISTANCE ); + bool fetch_activity( Character &you, const tripoint_bub_ms &src_loc, + const activity_id &activity_to_restore, const int distance = MAX_VIEW_DISTANCE ); + // is the front of the activity backlog a multi-activity? + bool fetch_activity_valid( const Character &you ) const; + std::unordered_set multi_activity_locations( Character &you ) override; activity_reason_info multi_activity_can_do( Character &you, const tripoint_bub_ms &src_loc ) override; @@ -449,6 +472,13 @@ class fetch_required_activity_actor : public multi_zone_activity_actor return std::make_unique( *this ); } static std::unique_ptr deserialize( JsonValue & ); + private: + requirement_id fetch_requirements; + do_activity_reason act_reason; + // where should this item be placed + tripoint_abs_ms fetched_item_destination; + // what zone tile was the fetching multi-activity currently processing? + tripoint_abs_ms fetch_for_activity_position; }; class aim_activity_actor : public activity_actor diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 43fbac3761094..8b5efe082900f 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -242,7 +242,9 @@ struct act_item { consumed_moves( consumed_moves ) {} }; -static void check_npc_revert( Character &you ) +namespace multi_activity_actor +{ +void check_npc_revert( Character &you ) { if( you.is_npc() ) { npc *guy = dynamic_cast( &you ); @@ -251,6 +253,7 @@ static void check_npc_revert( Character &you ) } } } +} //namespace multi_activity_actor // TODO: Deliberately unified with multidrop. Unify further. static bool same_type( const std::list &items ) @@ -2177,7 +2180,7 @@ requirement_check_result fetch_requirements( Character &you, requirement_id what return requirement_check_result::SKIP_LOCATION; } you.backlog.emplace_front( act_id ); - you.assign_activity( fetch_required_activity_actor() ); + you.assign_activity( ACT_FETCH_REQUIRED ); player_activity &act_prev = you.backlog.front(); act_prev.str_values.push_back( what_we_need.str() ); act_prev.values.push_back( static_cast( act_info.reason ) ); @@ -2286,7 +2289,10 @@ requirement_id remove_met_requirements( requirement_id base_req_id, Character &y requirement_data::alter_tool_comp_vector reduced_tool_reqs_vector; requirement_data::alter_quali_req_vector reduced_quality_reqs_vector; - const inventory &inv = you.crafting_inventory(); + // We cannot assume that required items on the ground when the multi-activity starts + // will *always* be in the multi-activity work area to meet requirements. + // Therefore, items on the ground aren't counted here for met requirements. + const inventory &inv = you.crafting_inventory( tripoint_bub_ms::zero, -1 ); for( std::vector &tools : tool_reqs_vector ) { bool found = false; @@ -2532,29 +2538,23 @@ void add_basecamp_storage_to_loot_zone_list( } } //namespace multi_activity_actor -static std::vector> requirements_map( Character &you, - const int distance = MAX_VIEW_DISTANCE ) +std::vector> + fetch_required_activity_actor::requirements_map( Character &you, + const int distance ) { std::vector> requirement_map; - if( you.backlog.empty() || you.backlog.front().str_values.empty() ) { + if( !fetch_activity_valid( you ) ) { return requirement_map; } - const requirement_data things_to_fetch = requirement_id( you.backlog.front().str_values[0] ).obj(); - const activity_id activity_to_restore = you.backlog.front().id(); // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - requirement_id things_to_fetch_id = things_to_fetch.id(); + requirement_id things_to_fetch_id = fetch_requirements; + const requirement_data things_to_fetch = *fetch_requirements; + const activity_id activity_to_restore = you.backlog.front().id(); std::vector> req_comps = things_to_fetch.get_components(); std::vector> tool_comps = things_to_fetch.get_tools(); std::vector> quality_comps = things_to_fetch.get_qualities(); zone_manager &mgr = zone_manager::get_manager(); - const bool pickup_task = you.backlog.front().id() == ACT_MULTIPLE_FARM || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_PLANKS || - you.backlog.front().id() == ACT_MULTIPLE_BUTCHER || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_TREES || - you.backlog.front().id() == ACT_VEHICLE_DECONSTRUCTION || - you.backlog.front().id() == ACT_VEHICLE_REPAIR || - you.backlog.front().id() == ACT_MULTIPLE_FISH || - you.backlog.front().id() == ACT_MULTIPLE_MINE; + const bool pickup_task = activity_to_restore->fetch_items_to_zone(); // where it is, what it is, how much of it, and how much in total is required of that item. std::vector> final_map; std::vector loot_spots; @@ -2562,7 +2562,7 @@ static std::vector> requirements_map( std::vector combined_spots; std::map total_map; map &here = get_map(); - tripoint_bub_ms src_loc = here.get_bub( you.backlog.front().placement ); + const tripoint_bub_ms &src_loc = here.get_bub( fetch_for_activity_position ); for( const tripoint_bub_ms &elem : here.points_in_radius( src_loc, PICKUP_RANGE - 1 ) ) { already_there_spots.push_back( elem ); @@ -2918,29 +2918,35 @@ static bool tidy_activity( Character &you, const tripoint_bub_ms &src_loc, return true; } -static bool fetch_activity( +bool fetch_required_activity_actor::fetch_activity( Character &you, const tripoint_bub_ms &src_loc, const activity_id &activity_to_restore, - const int distance = MAX_VIEW_DISTANCE ) + int distance ) { + if( !fetch_activity_valid( you ) ) { + return false; + } + const activity_id fetch_for_activity = you.backlog.front().id(); map &here = get_map(); - if( !here.can_put_items_ter_furn( here.get_bub( - you.backlog.front().coords.back() ) ) ) { + if( !here.can_put_items_ter_furn( here.get_bub( fetched_item_destination ) ) ) { return false; } - const std::vector> mental_map_2 = + const std::vector> mental_item_map = requirements_map( you, distance ); int pickup_count = 1; map_stack items_there = here.i_at( src_loc ); const units::volume volume_allowed = you.free_space(); const units::mass weight_allowed = you.weight_capacity() - you.weight_carried(); + + // if the items are in vehicle cargo, move them to their position const std::optional ovp = here.veh_at( src_loc ).cargo(); if( ovp ) { for( item &veh_elem : ovp->items() ) { - for( auto elem : mental_map_2 ) { + for( auto elem : mental_item_map ) { if( std::get<0>( elem ) == src_loc && veh_elem.typeId() == std::get<1>( elem ) ) { - if( !you.backlog.empty() && you.backlog.front().id() == ACT_MULTIPLE_CONSTRUCTION ) { + if( fetch_for_activity == ACT_MULTIPLE_CONSTRUCTION ) { + //TODO: this teleports the item, assign an item moving activity move_item( you, veh_elem, veh_elem.count_by_charges() ? std::get<2>( elem ) : 1, src_loc, - here.get_bub( you.backlog.front().coords.back() ), ovp, + here.get_bub( fetched_item_destination ), ovp, activity_to_restore ); return true; } @@ -2950,26 +2956,19 @@ static bool fetch_activity( } for( auto item_iter = items_there.begin(); item_iter != items_there.end(); item_iter++ ) { item &it = *item_iter; - for( auto elem : mental_map_2 ) { + for( auto elem : mental_item_map ) { if( std::get<0>( elem ) == src_loc && it.typeId() == std::get<1>( elem ) ) { - // construction/crafting tasks want the required item moved near the work spot. - if( !you.backlog.empty() && ( you.backlog.front().id() == ACT_MULTIPLE_CONSTRUCTION || - you.backlog.front().id() == ACT_MULTIPLE_DIS ) ) { + // some tasks (construction/crafting) want the required item moved near the work spot. + if( !fetch_for_activity->fetch_items_to_zone() ) { + //TODO: this teleports the item, assign an item moving activity move_item( you, it, it.count_by_charges() ? std::get<2>( elem ) : 1, src_loc, - here.get_bub( you.backlog.front().coords.back() ), ovp, - activity_to_restore ); + here.get_bub( fetched_item_destination ), ovp, activity_to_restore ); return true; - // other tasks want the tool picked up - } else if( !you.backlog.empty() && ( you.backlog.front().id() == ACT_MULTIPLE_FARM || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_PLANKS || - you.backlog.front().id() == ACT_VEHICLE_DECONSTRUCTION || - you.backlog.front().id() == ACT_VEHICLE_REPAIR || - you.backlog.front().id() == ACT_MULTIPLE_BUTCHER || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_TREES || - you.backlog.front().id() == ACT_MULTIPLE_FISH || - you.backlog.front().id() == ACT_MULTIPLE_MINE || - you.backlog.front().id() == ACT_MULTIPLE_MOP ) ) { + // other tasks want the item picked up + } else { + // TODO: most of this `else` block should be handled by exactly one function call + if( it.volume() > volume_allowed || it.weight() > weight_allowed ) { add_msg_if_player_sees( you, _( "%1s failed to fetch tools." ), you.name ); continue; @@ -3004,7 +3003,7 @@ static bool fetch_activity( } } } - // if we got here, then the fetch failed for reasons that weren't predicted before setting it. + // if we got here, then the fetch failed for reasons that weren't predicted before assigning it. // nothing was moved or picked up, and nothing can be moved or picked up // so call the whole thing off to stop it looping back to this point ad nauseum. you.set_moves( 0 ); @@ -3430,26 +3429,6 @@ std::unordered_set craft_locations( Character &you, const activ return src_set; } -std::unordered_set fetch_locations( Character &you, const activity_id & ) -{ - - map &here = get_map(); - std::unordered_set src_set; - - // get the right zones for the items in the requirements. - // we previously checked if the items are nearby before we set the fetch task - // but we will check again later, to be sure nothings changed. - std::vector> mental_map = - requirements_map( you, MAX_VIEW_DISTANCE ); - for( const auto &elem : mental_map ) { - const tripoint_bub_ms &elem_point = std::get<0>( elem ); - src_set.insert( here.get_abs( elem_point ) ); - } - multi_activity_actor::prune_dangerous_field_locations( src_set ); - - return src_set; -} - std::unordered_set fish_locations( Character &you, const activity_id &act_id ) { @@ -3652,14 +3631,14 @@ bool fetch_do( Character &you, const activity_reason_info &act_info, const do_activity_reason &reason = act_info.reason; if( reason == do_activity_reason::CAN_DO_FETCH ) { - if( fetch_activity( you, src_loc, ACT_FETCH_REQUIRED, MAX_VIEW_DISTANCE ) ) { - if( !you.is_npc() ) { - // Npcs will automatically start the next thing in the backlog, players need to be manually prompted - // Because some player activities are necessarily not marked as auto-resume. - activity_handlers::resume_for_multi_activities( you ); - } - return false; + //if( fetch_activity( you, src_loc, ACT_FETCH_REQUIRED, MAX_VIEW_DISTANCE ) ) { + if( !you.is_npc() ) { + // Npcs will automatically start the next thing in the backlog, players need to be manually prompted + // Because some player activities are necessarily not marked as auto-resume. + activity_handlers::resume_for_multi_activities( you ); } + return false; + //} } return true; } @@ -3949,7 +3928,7 @@ static std::unordered_set generic_multi_activity_locations( } else if( act_id == ACT_MULTIPLE_CRAFT ) { src_set = multi_activity_actor::craft_locations( you, act_id ); } else if( act_id == ACT_FETCH_REQUIRED ) { - src_set = multi_activity_actor::fetch_locations( you, act_id ); + //src_set = multi_activity_actor::fetch_locations( you, act_id ); } else if( act_id == ACT_MULTIPLE_MOP ) { src_set = multi_activity_actor::mop_locations( you, act_id ); } else if( act_id == ACT_MULTIPLE_CONSTRUCTION ) { diff --git a/src/activity_item_handling.h b/src/activity_item_handling.h index b0dbaf802c779..b8f5f5a4e5cf7 100644 --- a/src/activity_item_handling.h +++ b/src/activity_item_handling.h @@ -134,6 +134,7 @@ bool activity_reason_continue( do_activity_reason reason ); bool activity_reason_quit( do_activity_reason reason ); // the activity (reason) requires picking up tools bool activity_reason_picks_up_tools( do_activity_reason reason ); +void check_npc_revert( Character &you ); // assigns fetch activity to find requirements requirement_check_result fetch_requirements( Character &you, requirement_id what_we_need, const activity_id &act_id, @@ -183,7 +184,6 @@ std::unordered_set tidy_up_locations( Character &you, const act std::unordered_set read_locations( Character &you, const activity_id &act_id ); std::unordered_set study_locations( Character &you, const activity_id &act_id ); std::unordered_set craft_locations( Character &you, const activity_id &act_id ); -std::unordered_set fetch_locations( Character &you, const activity_id & ); std::unordered_set fish_locations( Character &you, const activity_id &act_id ); std::unordered_set mop_locations( Character &you, const activity_id &act_id ); void prune_same_tile_locations( Character &you, std::unordered_set &src_set ); From 06db84a31162d17d83ed1b8c8d0d279592ff5be8 Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:54:08 -0500 Subject: [PATCH 7/9] fetch flag for multi-activity JSON --- data/json/player_activities.json | 2 ++ src/activity_type.cpp | 1 + src/activity_type.h | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 343578aeee888..c0422e9f07b2e 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -51,6 +51,7 @@ "based_on": "neither", "can_resume": false, "multi_activity": true, + "fetch_items_to_zone": false, "auto_needs": true }, { @@ -199,6 +200,7 @@ "based_on": "neither", "can_resume": false, "multi_activity": true, + "fetch_items_to_zone": false, "auto_needs": true }, { diff --git a/src/activity_type.cpp b/src/activity_type.cpp index 8332d46e33c14..0b45c3c8cc917 100644 --- a/src/activity_type.cpp +++ b/src/activity_type.cpp @@ -93,6 +93,7 @@ void activity_type::load( const JsonObject &jo ) optional( jo, was_loaded, "interruptable_with_kb", interruptable_with_kb_, true ); optional( jo, was_loaded, "can_resume", can_resume_, true ); optional( jo, was_loaded, "multi_activity", multi_activity_, false ); + optional( jo, was_loaded, "fetch_items_to_zone", fetch_items_to_zone_, true ); optional( jo, was_loaded, "refuel_fires", refuel_fires, false ); optional( jo, was_loaded, "auto_needs", auto_needs, false ); optional( jo, was_loaded, "completion_eoc", completion_EOC ); diff --git a/src/activity_type.h b/src/activity_type.h index ce9b14d47edd1..ab58be6446a3b 100644 --- a/src/activity_type.h +++ b/src/activity_type.h @@ -46,6 +46,7 @@ class activity_type based_on_type based_on_ = based_on_type::SPEED; bool can_resume_ = true; bool multi_activity_ = false; + bool fetch_items_to_zone_ = true; bool refuel_fires = false; bool auto_needs = false; float activity_level = NO_EXERCISE; @@ -82,6 +83,9 @@ class activity_type bool multi_activity() const { return multi_activity_; } + bool fetch_items_to_zone() const { + return fetch_items_to_zone_; + } /** * If true, player will refuel one adjacent fire if there is firewood spot adjacent. */ From e9d426c4cc81356d46c4f4988731544b12748eaf Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:36:05 -0500 Subject: [PATCH 8/9] fix string formatting error for nearby multi-activity requirements --- src/activity_actor.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 46d2b739d3309..0754dd4174720 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -4975,18 +4975,16 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte bool tool_pickup = multi_activity_actor::activity_reason_picks_up_tools( reason ); // is it even worth fetching anything if there isn't enough nearby? if( !multi_activity_actor::are_requirements_nearby( tool_pickup ? loot_zone_spots : combined_spots, - what_we_need, you, - act_id, tool_pickup, src_loc ) ) { + what_we_need, you, act_id, tool_pickup, src_loc ) ) { + const tripoint_bub_ms you_pos_bub = you.pos_bub(); if( zone ) { - you.add_msg_player_or_npc( m_info, - _( "The required items are not available to complete the %s task at zone %s." ), act_id.c_str(), - zone->get_name(), - _( "The required items are not available to complete the %s task at zone %s." ), act_id.c_str(), - zone->get_name() ); + add_msg_if_player_sees( you_pos_bub, m_info, string_format( + _( "The required items are not available to complete the %s task at zone %s." ), + act_id.c_str(), zone->get_name() ) ); } else { - you.add_msg_player_or_npc( m_info, - _( "The required items are not available to complete the %s task." ), act_id.c_str(), - _( "The required items are not available to complete the %s task." ), act_id.c_str() ); + add_msg_if_player_sees( you_pos_bub, m_info, string_format( + _( "The required items are not available to complete the %s task." ), + act_id.c_str() ) ); } //TODO: this is hacky, move it if( reason == do_activity_reason::NEEDS_VEH_DECONST || From 9978377d3cee16deaba8e56428ed0e899acda5fe Mon Sep 17 00:00:00 2001 From: ShnitzelX2 <65314588+ShnitzelX2@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:48:11 -0500 Subject: [PATCH 9/9] multi-activity CI --- src/activity_actor.cpp | 12 +++++------- src/activity_actor_definitions.h | 10 +++++----- src/activity_item_handling.cpp | 3 ++- src/activity_item_handling.h | 2 +- src/npcmove.cpp | 2 +- tests/act_build_test.cpp | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 0754dd4174720..368151e013a5d 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -5002,7 +5003,7 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte return requirement_check_result::SKIP_LOCATION_NO_MATCH; } -bool multi_zone_activity_actor::can_resume_with_internal( const activity_actor &other_act, +bool multi_zone_activity_actor::can_resume_with_internal( const activity_actor &, const Character & ) const { return true; @@ -5066,11 +5067,8 @@ activity_reason_info fetch_required_activity_actor::multi_activity_can_do( void fetch_required_activity_actor::set_npc_fetch_history( Character &you ) { - if( !you.backlog.empty() ) { - player_activity &act_prev = you.backlog.front(); - if( !fetch_requirements.is_empty() && you.as_npc() ) { - you.as_npc()->job.fetch_history[fetch_requirements.str()] = calendar::turn; - } + if( !fetch_requirements.is_empty() && you.as_npc() ) { + you.as_npc()->job.fetch_history[fetch_requirements.str()] = calendar::turn; } } @@ -5105,7 +5103,7 @@ std::unordered_set fetch_required_activity_actor::multi_activit bool fetch_required_activity_actor::multi_activity_do( Character &you, const activity_reason_info &act_info, - const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) + const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ) { const do_activity_reason &reason = act_info.reason; diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index e6046100e0243..df0ccc5fab544 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -125,7 +125,7 @@ class multi_zone_activity_actor : public activity_actor activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc, const std::unordered_set &src_set ); // @return equal types - bool can_resume_with_internal( const activity_actor &other_act, + bool can_resume_with_internal( const activity_actor &, const Character & ) const override; // BEGIN interface block @@ -457,9 +457,9 @@ class fetch_required_activity_actor : public multi_zone_activity_actor void set_npc_fetch_history( Character &you ); std::vector> requirements_map( Character &you, - const int distance = MAX_VIEW_DISTANCE ); + int distance = MAX_VIEW_DISTANCE ); bool fetch_activity( Character &you, const tripoint_bub_ms &src_loc, - const activity_id &activity_to_restore, const int distance = MAX_VIEW_DISTANCE ); + const activity_id &activity_to_restore, int distance = MAX_VIEW_DISTANCE ); // is the front of the activity backlog a multi-activity? bool fetch_activity_valid( const Character &you ) const; @@ -467,14 +467,14 @@ class fetch_required_activity_actor : public multi_zone_activity_actor activity_reason_info multi_activity_can_do( Character &you, const tripoint_bub_ms &src_loc ) override; bool multi_activity_do( Character &you, const activity_reason_info &act_info, - const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; + const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ) override; std::unique_ptr clone() const override { return std::make_unique( *this ); } static std::unique_ptr deserialize( JsonValue & ); private: requirement_id fetch_requirements; - do_activity_reason act_reason; + do_activity_reason act_reason = do_activity_reason::UNKNOWN_ACTIVITY; // where should this item be placed tripoint_abs_ms fetched_item_destination; // what zone tile was the fetching multi-activity currently processing? diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 8b5efe082900f..27b7b6823a961 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -16,6 +16,7 @@ #include #include "activity_actor_definitions.h" +#include "activity_type.h" #include "avatar.h" #include "butchery.h" #include "calendar.h" @@ -3626,7 +3627,7 @@ bool tidy_up_do( Character &you, const activity_reason_info &act_info, } bool fetch_do( Character &you, const activity_reason_info &act_info, - const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ) + const tripoint_abs_ms &, const tripoint_bub_ms & ) { const do_activity_reason &reason = act_info.reason; diff --git a/src/activity_item_handling.h b/src/activity_item_handling.h index b8f5f5a4e5cf7..610fe97a0febb 100644 --- a/src/activity_item_handling.h +++ b/src/activity_item_handling.h @@ -253,7 +253,7 @@ bool construction_do( Character &you, const activity_reason_info &act_info, bool farm_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ); bool fetch_do( Character &you, const activity_reason_info &act_info, - const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ); + const tripoint_abs_ms &, const tripoint_bub_ms & ); bool craft_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &, const tripoint_bub_ms & ); bool disassemble_do( Character &you, const activity_reason_info &act_info, diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 608274bee5f30..802c36bf4f75c 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -3445,7 +3445,7 @@ bool npc::find_job_to_perform() assign_activity( multi_disassemble_activity_actor() ); return true; } else if( elem == ACT_MULTIPLE_CONSTRUCTION ) { - assign_activity( multi_disassemble_activity_actor() ); + assign_activity( multi_build_construction_activity_actor() ); return true; } else if( elem == ACT_MULTIPLE_BUTCHER ) { assign_activity( multi_butchery_activity_actor() ); diff --git a/tests/act_build_test.cpp b/tests/act_build_test.cpp index b1816ce3a7aad..eda1346e77bcd 100644 --- a/tests/act_build_test.cpp +++ b/tests/act_build_test.cpp @@ -82,7 +82,7 @@ void run_activities( Character &u, int max_moves ) u.assign_activity( multi_build_construction_activity_actor() ); int turns = 0; - while( ( !u.activity.is_null() || u.is_auto_moving() ) ) { + while( !u.activity.is_null() || u.is_auto_moving() ) { if( turns == max_moves ) { FAIL( "turn count exceeded, infinite loop possible" ); return;