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