diff --git a/data/json/player_activities.json b/data/json/player_activities.json index d20201eb1e197..5128cfe151bb0 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -23,7 +23,8 @@ "verb": "reading", "rooted": true, "based_on": "speed", - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_MULTIPLE_CONSTRUCTION", @@ -33,7 +34,8 @@ "based_on": "neither", "suspendable": false, "no_resume": true, - "multi_activity": true + "multi_activity": true, + "auto_needs": true }, { "id": "ACT_MULTIPLE_MINE", @@ -80,7 +82,8 @@ "based_on": "neither", "suspendable": false, "no_resume": true, - "multi_activity": true + "multi_activity": true, + "auto_needs": true }, { "id": "ACT_MULTIPLE_BUTCHER", @@ -90,7 +93,8 @@ "based_on": "neither", "suspendable": false, "no_resume": true, - "multi_activity": true + "multi_activity": true, + "auto_needs": true }, { "id": "ACT_MULTIPLE_CHOP_TREES", @@ -100,14 +104,16 @@ "based_on": "neither", "suspendable": false, "no_resume": true, - "multi_activity": true + "multi_activity": true, + "auto_needs": true }, { "id": "ACT_MULTIPLE_FISH", "type": "activity_type", "activity_level": "LIGHT_EXERCISE", "verb": "fishing", - "based_on": "neither" + "based_on": "neither", + "auto_needs": true }, { "id": "ACT_GENERIC_GAME", @@ -133,7 +139,8 @@ "rooted": true, "based_on": "time", "no_resume": true, - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_CRAFT", @@ -141,7 +148,8 @@ "activity_level": "MODERATE_EXERCISE", "verb": "crafting", "based_on": "neither", - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_DISASSEMBLE", @@ -151,7 +159,8 @@ "suspendable": false, "rooted": true, "based_on": "speed", - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_BUTCHER", @@ -209,7 +218,8 @@ "verb": "salvaging", "based_on": "speed", "no_resume": true, - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_FORAGE", @@ -224,6 +234,7 @@ "activity_level": "ACTIVE_EXERCISE", "based_on": "neither", "refuel_fires": true, + "auto_needs": true, "verb": "constructing" }, { @@ -255,7 +266,8 @@ "verb": "waiting", "rooted": true, "based_on": "time", - "no_resume": true + "no_resume": true, + "auto_needs": true }, { "id": "ACT_FIRSTAID", @@ -403,7 +415,8 @@ "suspendable": false, "based_on": "neither", "no_resume": true, - "multi_activity": true + "multi_activity": true, + "auto_needs": true }, { "id": "ACT_FERTILIZE_PLOT", @@ -412,7 +425,8 @@ "verb": "fertilizing plots", "suspendable": false, "based_on": "neither", - "no_resume": true + "no_resume": true, + "auto_needs": true }, { "id": "ACT_ADV_INVENTORY", @@ -533,7 +547,8 @@ "verb": "repairing", "rooted": true, "based_on": "neither", - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_MEND_ITEM", @@ -542,7 +557,8 @@ "verb": "mending", "rooted": true, "based_on": "speed", - "refuel_fires": true + "refuel_fires": true, + "auto_needs": true }, { "id": "ACT_GUNMOD_ADD", @@ -791,6 +807,7 @@ "verb": "studying", "suspendable": false, "refuel_fires": true, + "auto_needs": true, "rooted": true, "based_on": "time", "no_resume": true diff --git a/doc/PLAYER_ACTIVITY.md b/doc/PLAYER_ACTIVITY.md index a43603335ee09..0042576ac93cd 100644 --- a/doc/PLAYER_ACTIVITY.md +++ b/doc/PLAYER_ACTIVITY.md @@ -20,7 +20,9 @@ Activities are long term actions, that can be interrupted and (optionally) conti * speed: `player_activity::moves_left` may be decremented faster or slower, depending on the character's speed. * neither: `moves_left` will not be decremented. Thus you must define a do_turn function; otherwise the activity will never end! * no_resume (false): Rather than resuming, you must always restart the activity from scratch. -* multi_activity(false): This activity will repeat until it cannot do any more work, used for NPC and player zone activities. +* multi_activity(false): This activity will repeat until it cannot do any more work, used for NPC and player zone activities. +* refuel_fires( false ): If true, the player will automatically refuel fires during the long activity. +* auto_needs( false ) : If true, the player will automatically eat and drink from specific auto_consume zones during long activities. ## Termination diff --git a/src/activity_handlers.h b/src/activity_handlers.h index cefc045d2eeda..3faf67d96b31e 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -101,6 +101,7 @@ bool generic_multi_activity_handler( player_activity &act, player &p, bool check void activity_on_turn_fetch( player_activity &, player *p ); void activity_on_turn_pickup(); void activity_on_turn_wear( player_activity &act, player &p ); +bool find_auto_consume( player &p, bool food ); void try_fuel_fire( player_activity &act, player &p, bool starting_fire = false ); enum class item_drop_reason { diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 1c425e126d045..bcf8636fd9f7c 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -11,6 +11,7 @@ #include #include "avatar.h" +#include "avatar_action.h" #include "construction.h" #include "clzones.h" #include "debug.h" @@ -73,6 +74,7 @@ static const activity_id ACT_VEHICLE( "ACT_VEHICLE" ); static const activity_id ACT_VEHICLE_DECONSTRUCTION( "ACT_VEHICLE_DECONSTRUCTION" ); static const activity_id ACT_VEHICLE_REPAIR( "ACT_VEHICLE_REPAIR" ); static const efftype_id effect_pet( "pet" ); +static const efftype_id effect_nausea( "nausea" ); static const trap_str_id tr_firewood_source( "tr_firewood_source" ); static const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" ); @@ -3032,6 +3034,79 @@ static cata::optional find_refuel_spot_trap( const std::vector &dest_set = mgr.get_near( consume_type_zone, g->m.getabs( pos ), + ACTIVITY_SEARCH_DISTANCE ); + if( dest_set.empty() ) { + return false; + } + for( const tripoint loc : dest_set ) { + if( loc.z != p.pos().z ) { + continue; + } + map_stack food_there = g->m.i_at( g->m.getlocal( loc ) ); + for( item &it : food_there ) { + item &comest = p.get_consumable_from( it ); + if( comest.is_null() || comest.is_craft() || !comest.is_food() || + p.fun_for( comest ).first < -5 ) { + // not good eatings. + continue; + } + if( !p.can_consume( it ) ) { + continue; + } + if( food && p.compute_effective_nutrients( comest ).kcal < 50 ) { + // not filling enough + continue; + } + if( !p.will_eat( comest, false ).success() ) { + // wont like it, cannibal meat etc + continue; + } + if( !it.is_owned_by( p, true ) ) { + // it aint ours. + continue; + } + if( !food && comest.get_comestible()->quench < 15 ) { + // not quenching enough + continue; + } + if( !food && it.is_watertight_container() && it.contents_made_of( SOLID ) ) { + // its frozen + continue; + } + p.mod_moves( -Pickup::cost_to_move_item( p, it ) * std::max( rl_dist( p.pos(), + g->m.getlocal( loc ) ), 1 ) ); + item_location item_loc( map_cursor( g->m.getlocal( loc ) ), &it ); + avatar_action::eat( g->u, item_loc ); + // eat() may have removed the item, so check its still there. + if( item_loc.get_item() && item_loc->is_container() ) { + item_loc->on_contents_changed(); + } + return true; + } + } + return false; +} + void try_fuel_fire( player_activity &act, player &p, const bool starting_fire ) { const tripoint pos = p.pos(); diff --git a/src/activity_type.cpp b/src/activity_type.cpp index 30e72cb3ce475..ebbb45c7b3524 100644 --- a/src/activity_type.cpp +++ b/src/activity_type.cpp @@ -57,6 +57,7 @@ void activity_type::load( const JsonObject &jo ) assign( jo, "no_resume", result.no_resume_, true ); assign( jo, "multi_activity", result.multi_activity_, false ); assign( jo, "refuel_fires", result.refuel_fires, false ); + assign( jo, "auto_needs", result.auto_needs, false ); std::string activity_level = jo.get_string( "activity_level", "" ); if( activity_level.empty() ) { diff --git a/src/activity_type.h b/src/activity_type.h index c74cc217e2deb..3ac801165ee94 100644 --- a/src/activity_type.h +++ b/src/activity_type.h @@ -37,6 +37,7 @@ class activity_type bool no_resume_ = false; bool multi_activity_ = false; bool refuel_fires = false; + bool auto_needs = false; float activity_level = NO_EXERCISE; public: @@ -68,7 +69,12 @@ class activity_type bool will_refuel_fires() const { return refuel_fires; } - + /** + * If true, player will automatically consume from relevant auto-eat/drink zones during activity + */ + bool valid_auto_needs() const { + return auto_needs; + } void call_do_turn( player_activity *, player * ) const; /** Returns whether it had a finish function or not */ bool call_finish( player_activity *, player * ) const; diff --git a/src/clzones.cpp b/src/clzones.cpp index 5ef71b73a54f3..ef7a2c35c6990 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -93,6 +93,12 @@ zone_manager::zone_manager() types.emplace( zone_type_id( "CAMP_FOOD" ), zone_type( translate_marker( "Basecamp: Food" ), translate_marker( "Items in this zone will be added to a basecamp's food supply in the Distribute Food mission." ) ) ); + types.emplace( zone_type_id( "AUTO_EAT" ), + zone_type( translate_marker( "Auto Eat" ), + translate_marker( "Items in this zone will be automatically eaten during a long activity if you get hungry." ) ) ); + types.emplace( zone_type_id( "AUTO_DRINK" ), + zone_type( translate_marker( "Auto Drink" ), + translate_marker( "Items in this zone will be automatically consumed during a long activity if you get thirsty." ) ) ); } diff --git a/src/player_activity.cpp b/src/player_activity.cpp index 05fcdc210e08b..c3f2dd354d16b 100644 --- a/src/player_activity.cpp +++ b/src/player_activity.cpp @@ -137,6 +137,22 @@ void player_activity::do_turn( player &p ) if( *this && type->will_refuel_fires() ) { try_fuel_fire( *this, p ); } + if( calendar::once_every( 30_minutes ) ) { + no_food_nearby_for_auto_consume = false; + no_drink_nearby_for_auto_consume = false; + } + if( *this && !p.is_npc() && type->valid_auto_needs() && !no_food_nearby_for_auto_consume ) { + if( p.stomach.contains() <= p.stomach.capacity( p ) / 4 && p.get_kcal_percent() < 0.95f ) { + if( !find_auto_consume( p, true ) ) { + no_food_nearby_for_auto_consume = true; + } + } + if( p.get_thirst() > 130 && !no_drink_nearby_for_auto_consume ) { + if( !find_auto_consume( p, false ) ) { + no_drink_nearby_for_auto_consume = true; + } + } + } if( type->based_on() == based_on_type::TIME ) { if( moves_left >= 100 ) { moves_left -= 100; diff --git a/src/player_activity.h b/src/player_activity.h index 18aacb216652b..7b5c486cd36de 100644 --- a/src/player_activity.h +++ b/src/player_activity.h @@ -53,6 +53,8 @@ class player_activity std::unordered_set coord_set; std::vector> monsters; tripoint placement; + bool no_drink_nearby_for_auto_consume = false; + bool no_food_nearby_for_auto_consume = false; /** If true, the activity will be auto-resumed next time the player attempts * an identical activity. This value is set dynamically. */