1 #include "player_activity.h"
2 
3 #include <algorithm>
4 #include <memory>
5 #include <new>
6 
7 #include "activity_handlers.h"
8 #include "activity_type.h"
9 #include "avatar.h"
10 #include "calendar.h"
11 #include "character.h"
12 #include "construction.h"
13 #include "item.h"
14 #include "itype.h"
15 #include "map.h"
16 #include "player.h"
17 #include "rng.h"
18 #include "skill.h"
19 #include "sounds.h"
20 #include "stomach.h"
21 #include "string_formatter.h"
22 #include "translations.h"
23 #include "ui.h"
24 #include "units.h"
25 #include "value_ptr.h"
26 
27 static const activity_id ACT_ATM( "ACT_ATM" );
28 static const activity_id ACT_FIRSTAID( "ACT_FIRSTAID" );
29 static const activity_id ACT_FISH( "ACT_FISH" );
30 static const activity_id ACT_GAME( "ACT_GAME" );
31 static const activity_id ACT_GUNMOD_ADD( "ACT_GUNMOD_ADD" );
32 static const activity_id ACT_HAND_CRANK( "ACT_HAND_CRANK" );
33 static const activity_id ACT_OXYTORCH( "ACT_OXYTORCH" );
34 static const activity_id ACT_PICKAXE( "ACT_PICKAXE" );
35 static const activity_id ACT_START_FIRE( "ACT_START_FIRE" );
36 static const activity_id ACT_TRAVELLING( "ACT_TRAVELLING" );
37 static const activity_id ACT_VIBE( "ACT_VIBE" );
38 
39 static const efftype_id effect_nausea( "nausea" );
40 
player_activity()41 player_activity::player_activity() : type( activity_id::NULL_ID() ) { }
42 
player_activity(activity_id t,int turns,int Index,int pos,const std::string & name_in)43 player_activity::player_activity( activity_id t, int turns, int Index, int pos,
44                                   const std::string &name_in ) :
45     type( t ), moves_total( turns ), moves_left( turns ),
46     index( Index ),
47     position( pos ), name( name_in ),
48     placement( tripoint_min )
49 {
50 }
51 
player_activity(const activity_actor & actor)52 player_activity::player_activity( const activity_actor &actor ) : type( actor.get_type() ),
53     actor( actor.clone() )
54 {
55 }
56 
migrate_item_position(Character & guy)57 void player_activity::migrate_item_position( Character &guy )
58 {
59     const bool simple_action_replace =
60         type == ACT_FIRSTAID || type == ACT_GAME ||
61         type == ACT_PICKAXE || type == ACT_START_FIRE ||
62         type == ACT_HAND_CRANK || type == ACT_VIBE ||
63         type == ACT_OXYTORCH || type == ACT_FISH ||
64         type == ACT_ATM;
65 
66     if( simple_action_replace ) {
67         targets.push_back( item_location( guy, &guy.i_at( position ) ) );
68     } else if( type == ACT_GUNMOD_ADD ) {
69         // this activity has two indices; "position" = gun and "values[0]" = mod
70         targets.push_back( item_location( guy, &guy.i_at( position ) ) );
71         targets.push_back( item_location( guy, &guy.i_at( values[0] ) ) );
72     }
73 }
74 
set_to_null()75 void player_activity::set_to_null()
76 {
77     type = activity_id::NULL_ID();
78     sfx::end_activity_sounds(); // kill activity sounds when activity is nullified
79 }
80 
synchronize_type_with_actor()81 void player_activity::synchronize_type_with_actor()
82 {
83     if( actor && type != activity_id::NULL_ID() ) {
84         type = actor->get_type();
85     }
86 }
87 
rooted() const88 bool player_activity::rooted() const
89 {
90     return type->rooted();
91 }
92 
get_stop_phrase() const93 std::string player_activity::get_stop_phrase() const
94 {
95     return type->stop_phrase();
96 }
97 
get_verb() const98 const translation &player_activity::get_verb() const
99 {
100     return type->verb();
101 }
102 
get_value(size_t index,int def) const103 int player_activity::get_value( size_t index, int def ) const
104 {
105     return index < values.size() ? values[index] : def;
106 }
107 
is_suspendable() const108 bool player_activity::is_suspendable() const
109 {
110     return type->suspendable();
111 }
112 
is_multi_type() const113 bool player_activity::is_multi_type() const
114 {
115     return type->multi_activity();
116 }
117 
get_str_value(size_t index,const std::string & def) const118 std::string player_activity::get_str_value( size_t index, const std::string &def ) const
119 {
120     return index < str_values.size() ? str_values[index] : def;
121 }
122 
get_progress_message(const avatar & u) const123 cata::optional<std::string> player_activity::get_progress_message( const avatar &u ) const
124 {
125     if( type == activity_id( "ACT_NULL" ) || get_verb().empty() ) {
126         return cata::optional<std::string>();
127     }
128 
129     if( type == activity_id( "ACT_ADV_INVENTORY" ) ||
130         type == activity_id( "ACT_AIM" ) ||
131         type == activity_id( "ACT_ARMOR_LAYERS" ) ||
132         type == activity_id( "ACT_ATM" ) ||
133         type == activity_id( "ACT_CONSUME_DRINK_MENU" ) ||
134         type == activity_id( "ACT_CONSUME_FOOD_MENU" ) ||
135         type == activity_id( "ACT_CONSUME_MEDS_MENU" ) ||
136         type == activity_id( "ACT_EAT_MENU" ) ) {
137         return cata::nullopt;
138     }
139 
140     std::string extra_info;
141     if( type == activity_id( "ACT_READ" ) ) {
142         if( const item *book = targets.front().get_item() ) {
143             if( const auto &reading = book->type->book ) {
144                 const skill_id &skill = reading->skill;
145                 if( skill && u.get_skill_level( skill ) < reading->level &&
146                     u.get_skill_level_object( skill ).can_train() && u.has_identified( book->typeId() ) ) {
147                     const SkillLevel &skill_level = u.get_skill_level_object( skill );
148                     //~ skill_name current_skill_level -> next_skill_level (% to next level)
149                     extra_info = string_format( pgettext( "reading progress", "%s %d -> %d (%d%%)" ),
150                                                 skill.obj().name(),
151                                                 skill_level.level(),
152                                                 skill_level.level() + 1,
153                                                 skill_level.exercise() );
154                 }
155             }
156         }
157     } else if( moves_total > 0 ) {
158         if( type == activity_id( "ACT_HACKSAW" ) ||
159             type == activity_id( "ACT_JACKHAMMER" ) ||
160             type == activity_id( "ACT_PICKAXE" ) ||
161             type == activity_id( "ACT_VEHICLE" ) ||
162             type == activity_id( "ACT_FILL_PIT" ) ||
163             type == activity_id( "ACT_CHOP_TREE" ) ||
164             type == activity_id( "ACT_CHOP_LOGS" ) ||
165             type == activity_id( "ACT_CHOP_PLANKS" )
166           ) {
167             const int percentage = ( ( moves_total - moves_left ) * 100 ) / moves_total;
168 
169             extra_info = string_format( "%d%%", percentage );
170         }
171 
172         if( type == activity_id( "ACT_BUILD" ) ) {
173             partial_con *pc = get_map().partial_con_at( get_map().getlocal( u.activity.placement ) );
174             if( pc ) {
175                 int counter = std::min( pc->counter, 10000000 );
176                 const int percentage = counter / 100000;
177 
178                 extra_info = string_format( "%d%%", percentage );
179             }
180         }
181     }
182 
183     if( actor ) {
184         extra_info = actor->get_progress_message( *this );
185     }
186 
187     return extra_info.empty() ? string_format( _( "%s…" ),
188             get_verb().translated() ) : string_format( _( "%s: %s" ),
189                     get_verb().translated(), extra_info );
190 }
191 
start_or_resume(Character & who,bool resuming)192 void player_activity::start_or_resume( Character &who, bool resuming )
193 {
194     if( actor && !resuming ) {
195         actor->start( *this, who );
196     }
197     if( !type.is_null() && rooted() ) {
198         who.rooted_message();
199     }
200     // last, as start function may have changed the type
201     synchronize_type_with_actor();
202 }
203 
do_turn(player & p)204 void player_activity::do_turn( player &p )
205 {
206     // Specifically call the do turn function for the cancellation activity early
207     // This is because the game can get stuck trying to fuel a fire when it's not...
208     if( type == activity_id( "ACT_MIGRATION_CANCEL" ) ) {
209         actor->do_turn( *this, p );
210         return;
211     }
212     // first to ensure sync with actor
213     synchronize_type_with_actor();
214     // Should happen before activity or it may fail due to 0 moves
215     if( *this && type->will_refuel_fires() && have_fire ) {
216         have_fire = try_fuel_fire( *this, p );
217     }
218     if( calendar::once_every( 30_minutes ) ) {
219         no_food_nearby_for_auto_consume = false;
220         no_drink_nearby_for_auto_consume = false;
221     }
222     // Only do once every two minutes to loosely simulate consume times,
223     // the exact amount of time is added correctly below, here we just want to prevent eating something every second
224     if( calendar::once_every( 2_minutes ) && *this && !p.is_npc() && type->valid_auto_needs() &&
225         !p.has_effect( effect_nausea ) ) {
226         if( p.stomach.contains() <= p.stomach.capacity( p ) / 4 && p.get_kcal_percent() < 0.95f &&
227             !no_food_nearby_for_auto_consume ) {
228             int consume_moves = get_auto_consume_moves( p, true );
229             moves_left += consume_moves;
230             if( consume_moves == 0 ) {
231                 no_food_nearby_for_auto_consume = true;
232             }
233         }
234         if( p.get_thirst() > 130 && !no_drink_nearby_for_auto_consume ) {
235             int consume_moves = get_auto_consume_moves( p, false );
236             moves_left += consume_moves;
237             if( consume_moves == 0 ) {
238                 no_drink_nearby_for_auto_consume = true;
239             }
240         }
241     }
242     const float activity_mult = p.exertion_adjusted_move_multiplier( exertion_level() );
243     if( type->based_on() == based_on_type::TIME ) {
244         if( moves_left >= 100 ) {
245             moves_left -= 100 * activity_mult;
246             p.moves = 0;
247         } else {
248             p.moves -= p.moves * moves_left / 100;
249             moves_left = 0;
250         }
251     } else if( type->based_on() == based_on_type::SPEED ) {
252         if( p.moves <= moves_left ) {
253             moves_left -= p.moves * activity_mult;
254             p.moves = 0;
255         } else {
256             p.moves -= moves_left;
257             moves_left = 0;
258         }
259     }
260     int previous_stamina = p.get_stamina();
261     if( p.is_npc() && p.check_outbounds_activity( *this ) ) {
262         // npc might be operating at the edge of the reality bubble.
263         // or just now reloaded back into it, and their activity target might
264         // be still unloaded, can cause infinite loops.
265         set_to_null();
266         p.drop_invalid_inventory();
267         return;
268     }
269     const bool travel_activity = id() == ACT_TRAVELLING;
270     p.set_activity_level( exertion_level() );
271     // This might finish the activity (set it to null)
272     if( actor ) {
273         actor->do_turn( *this, p );
274     } else {
275         // Use the legacy turn function
276         type->call_do_turn( this, &p );
277     }
278     // Activities should never excessively drain stamina.
279     // adjusted stamina because
280     // autotravel doesn't reduce stamina after do_turn()
281     // it just sets a destination, clears the activity, then moves afterwards
282     // so set stamina -1 if that is the case
283     // to simulate that the next step will surely use up some stamina anyway
284     // this is to ensure that resting will occur when traveling overburdened
285     const int adjusted_stamina = travel_activity ? p.get_stamina() - 1 : p.get_stamina();
286     activity_id act_id = actor ? actor->get_type() : type;
287     bool excluded = act_id == activity_id( "ACT_WORKOUT_HARD" ) ||
288                     act_id == activity_id( "ACT_WORKOUT_ACTIVE" ) ||
289                     act_id == activity_id( "ACT_WORKOUT_MODERATE" ) ||
290                     act_id == activity_id( "ACT_WORKOUT_LIGHT" );
291     if( !excluded && adjusted_stamina < previous_stamina &&
292         p.get_stamina() < p.get_stamina_max() / 3 ) {
293         if( one_in( 50 ) ) {
294             p.add_msg_if_player( _( "You pause for a moment to catch your breath." ) );
295         }
296 
297         auto_resume = true;
298         player_activity new_act( activity_id( "ACT_WAIT_STAMINA" ), to_moves<int>( 5_minutes ) );
299         new_act.values.push_back( p.get_stamina_max() );
300         if( p.is_avatar() && !ignoreQuery ) {
301             uilist tired_query;
302             tired_query.text = _( "You struggle to continue.  Keep trying?" );
303             tired_query.addentry( 1, true, 'c', _( "Continue after a break." ) );
304             tired_query.addentry( 2, true, 'm', _( "Maybe later." ) );
305             tired_query.addentry( 3, true, 'f', _( "Finish it." ) );
306             tired_query.query();
307             switch( tired_query.ret ) {
308                 case UILIST_CANCEL:
309                 case 2:
310                     auto_resume = false;
311                     break;
312                 case 3:
313                     ignoreQuery = true;
314                     break;
315                 default:
316                     break;
317             }
318         }
319         p.assign_activity( new_act );
320         return;
321     }
322     if( *this && type->rooted() ) {
323         p.rooted();
324         p.pause();
325     }
326 
327     if( *this && moves_left <= 0 ) {
328         // Note: For some activities "finish" is a misnomer; that's why we explicitly check if the
329         // type is ACT_NULL below.
330         if( actor ) {
331             actor->finish( *this, p );
332         } else {
333             if( !type->call_finish( this, &p ) ) {
334                 // "Finish" is never a misnomer for any activity without a finish function
335                 set_to_null();
336             }
337         }
338     }
339     if( !*this ) {
340         // Make sure data of previous activity is cleared
341         p.activity = player_activity();
342         p.resume_backlog_activity();
343 
344         // If whatever activity we were doing forced us to pick something up to
345         // handle it, drop any overflow that may have caused
346         p.drop_invalid_inventory();
347     }
348 }
349 
canceled(Character & who)350 void player_activity::canceled( Character &who )
351 {
352     if( *this && actor ) {
353         actor->canceled( *this, who );
354     }
355 }
356 
exertion_level() const357 float player_activity::exertion_level() const
358 {
359     if( actor ) {
360         return actor->exertion_level();
361     }
362     return type->exertion_level();
363 }
364 
365 template <typename T>
containers_equal(const T & left,const T & right)366 bool containers_equal( const T &left, const T &right )
367 {
368     if( left.size() != right.size() ) {
369         return false;
370     }
371 
372     return std::equal( left.begin(), left.end(), right.begin() );
373 }
374 
can_resume_with(const player_activity & other,const Character & who) const375 bool player_activity::can_resume_with( const player_activity &other, const Character &who ) const
376 {
377     // Should be used for relative positions
378     // And to forbid resuming now-invalid crafting
379 
380     if( !*this || !other || type->no_resume() ) {
381         return false;
382     }
383 
384     if( id() != other.id() ) {
385         return false;
386     }
387 
388     // if actor XOR other.actor then id() != other.id() so
389     // we will correctly return false based on final return statement
390     if( actor && other.actor ) {
391         return actor->can_resume_with( *other.actor, who );
392     }
393 
394     if( id() == activity_id( "ACT_CLEAR_RUBBLE" ) ) {
395         if( other.coords.empty() || other.coords[0] != coords[0] ) {
396             return false;
397         }
398     } else if( id() == activity_id( "ACT_READ" ) ) {
399         // Return false if any NPCs joined or left the study session
400         // the vector {1, 2} != {2, 1}, so we'll have to check manually
401         if( values.size() != other.values.size() ) {
402             return false;
403         }
404         for( int foo : other.values ) {
405             if( std::find( values.begin(), values.end(), foo ) == values.end() ) {
406                 return false;
407             }
408         }
409         if( targets.empty() || other.targets.empty() || targets[0] != other.targets[0] ) {
410             return false;
411         }
412     } else if( id() == activity_id( "ACT_VEHICLE" ) ) {
413         if( values != other.values || str_values != other.str_values ) {
414             return false;
415         }
416     }
417 
418     return !auto_resume && index == other.index &&
419            position == other.position && name == other.name && targets == other.targets;
420 }
421 
is_interruptible() const422 bool player_activity::is_interruptible() const
423 {
424     return ( type.is_null() || type->interruptable() ) && interruptable;
425 }
426 
is_distraction_ignored(distraction_type distraction) const427 bool player_activity::is_distraction_ignored( distraction_type distraction ) const
428 {
429     return !is_interruptible() ||
430            ignored_distractions.find( distraction ) != ignored_distractions.end();
431 }
432 
ignore_distraction(distraction_type type)433 void player_activity::ignore_distraction( distraction_type type )
434 {
435     ignored_distractions.emplace( type );
436 }
437 
allow_distractions()438 void player_activity::allow_distractions()
439 {
440     ignored_distractions.clear();
441 }
442 
inherit_distractions(const player_activity & other)443 void player_activity::inherit_distractions( const player_activity &other )
444 {
445     for( const distraction_type &type : other.ignored_distractions ) {
446         ignore_distraction( type );
447     }
448 }
449