1 #include "avatar.h"
2 
3 #include <algorithm>
4 #include <array>
5 #include <climits>
6 #include <cmath>
7 #include <cstdlib>
8 #include <iterator>
9 #include <list>
10 #include <map>
11 #include <memory>
12 #include <set>
13 #include <string>
14 #include <utility>
15 
16 #include "action.h"
17 #include "activity_type.h"
18 #include "bodypart.h"
19 #include "calendar.h"
20 #include "cata_assert.h"
21 #include "catacharset.h"
22 #include "character.h"
23 #include "character_id.h"
24 #include "character_martial_arts.h"
25 #include "clzones.h"
26 #include "color.h"
27 #include "cursesdef.h"
28 #include "debug.h"
29 #include "effect.h"
30 #include "enums.h"
31 #include "event.h"
32 #include "event_bus.h"
33 #include "faction.h"
34 #include "game.h"
35 #include "game_constants.h"
36 #include "help.h"
37 #include "inventory.h"
38 #include "item.h"
39 #include "item_location.h"
40 #include "itype.h"
41 #include "iuse.h"
42 #include "kill_tracker.h"
43 #include "make_static.h"
44 #include "map.h"
45 #include "martialarts.h"
46 #include "messages.h"
47 #include "mission.h"
48 #include "morale.h"
49 #include "morale_types.h"
50 #include "move_mode.h"
51 #include "npc.h"
52 #include "optional.h"
53 #include "options.h"
54 #include "output.h"
55 #include "overmap.h"
56 #include "pathfinding.h"
57 #include "pimpl.h"
58 #include "player.h"
59 #include "player_activity.h"
60 #include "ret_val.h"
61 #include "rng.h"
62 #include "skill.h"
63 #include "stomach.h"
64 #include "string_formatter.h"
65 #include "talker.h"
66 #include "talker_avatar.h"
67 #include "translations.h"
68 #include "type_id.h"
69 #include "ui.h"
70 #include "units.h"
71 #include "value_ptr.h"
72 #include "vehicle.h"
73 #include "vpart_position.h"
74 
75 static const activity_id ACT_READ( "ACT_READ" );
76 
77 static const bionic_id bio_cloak( "bio_cloak" );
78 static const bionic_id bio_memory( "bio_memory" );
79 
80 static const efftype_id effect_alarm_clock( "alarm_clock" );
81 static const efftype_id effect_boomered( "boomered" );
82 static const efftype_id effect_contacts( "contacts" );
83 static const efftype_id effect_depressants( "depressants" );
84 static const efftype_id effect_happy( "happy" );
85 static const efftype_id effect_irradiated( "irradiated" );
86 static const efftype_id effect_onfire( "onfire" );
87 static const efftype_id effect_pkill( "pkill" );
88 static const efftype_id effect_sad( "sad" );
89 static const efftype_id effect_sleep( "sleep" );
90 static const efftype_id effect_sleep_deprived( "sleep_deprived" );
91 static const efftype_id effect_slept_through_alarm( "slept_through_alarm" );
92 static const efftype_id effect_stim( "stim" );
93 static const efftype_id effect_stim_overdose( "stim_overdose" );
94 static const efftype_id effect_stunned( "stunned" );
95 
96 static const itype_id itype_guidebook( "guidebook" );
97 
98 static const trait_id trait_ARACHNID_ARMS( "ARACHNID_ARMS" );
99 static const trait_id trait_ARACHNID_ARMS_OK( "ARACHNID_ARMS_OK" );
100 static const trait_id trait_CENOBITE( "CENOBITE" );
101 static const trait_id trait_CHITIN2( "CHITIN2" );
102 static const trait_id trait_CHITIN3( "CHITIN3" );
103 static const trait_id trait_CHITIN_FUR3( "CHITIN_FUR3" );
104 static const trait_id trait_COMPOUND_EYES( "COMPOUND_EYES" );
105 static const trait_id trait_DEBUG_CLOAK( "DEBUG_CLOAK" );
106 static const trait_id trait_HYPEROPIC( "HYPEROPIC" );
107 static const trait_id trait_ILLITERATE( "ILLITERATE" );
108 static const trait_id trait_INSECT_ARMS( "INSECT_ARMS" );
109 static const trait_id trait_INSECT_ARMS_OK( "INSECT_ARMS_OK" );
110 static const trait_id trait_PROF_DICEMASTER( "PROF_DICEMASTER" );
111 static const trait_id trait_SCHIZOPHRENIC( "SCHIZOPHRENIC" );
112 static const trait_id trait_STIMBOOST( "STIMBOOST" );
113 static const trait_id trait_THICK_SCALES( "THICK_SCALES" );
114 static const trait_id trait_WEBBED( "WEBBED" );
115 static const trait_id trait_WHISKERS( "WHISKERS" );
116 static const trait_id trait_WHISKERS_RAT( "WHISKERS_RAT" );
117 static const trait_id trait_MASOCHIST( "MASOCHIST" );
118 
119 static const json_character_flag json_flag_ALARMCLOCK( "ALARMCLOCK" );
120 
avatar()121 avatar::avatar()
122 {
123     show_map_memory = true;
124     active_mission = nullptr;
125     grab_type = object_type::NONE;
126     calorie_diary.push_front( daily_calories{} );
127 }
128 
toggle_map_memory()129 void avatar::toggle_map_memory()
130 {
131     show_map_memory = !show_map_memory;
132 }
133 
should_show_map_memory()134 bool avatar::should_show_map_memory()
135 {
136     return show_map_memory;
137 }
138 
serialize_map_memory(JsonOut & jsout) const139 void avatar::serialize_map_memory( JsonOut &jsout ) const
140 {
141     player_map_memory.store( jsout );
142 }
143 
deserialize_map_memory(JsonIn & jsin)144 void avatar::deserialize_map_memory( JsonIn &jsin )
145 {
146     player_map_memory.load( jsin );
147 }
148 
get_memorized_tile(const tripoint & pos) const149 memorized_terrain_tile avatar::get_memorized_tile( const tripoint &pos ) const
150 {
151     return player_map_memory.get_tile( pos );
152 }
153 
memorize_tile(const tripoint & pos,const std::string & ter,const int subtile,const int rotation)154 void avatar::memorize_tile( const tripoint &pos, const std::string &ter, const int subtile,
155                             const int rotation )
156 {
157     player_map_memory.memorize_tile( max_memorized_tiles(), pos, ter, subtile, rotation );
158 }
159 
memorize_symbol(const tripoint & pos,const int symbol)160 void avatar::memorize_symbol( const tripoint &pos, const int symbol )
161 {
162     player_map_memory.memorize_symbol( max_memorized_tiles(), pos, symbol );
163 }
164 
get_memorized_symbol(const tripoint & p) const165 int avatar::get_memorized_symbol( const tripoint &p ) const
166 {
167     return player_map_memory.get_symbol( p );
168 }
169 
max_memorized_tiles() const170 size_t avatar::max_memorized_tiles() const
171 {
172     // Only check traits once a turn since this is called a huge number of times.
173     if( current_map_memory_turn != calendar::turn ) {
174         current_map_memory_turn = calendar::turn;
175         float map_memory_capacity_multiplier =
176             mutation_value( "map_memory_capacity_multiplier" );
177         if( has_active_bionic( bio_memory ) ) {
178             map_memory_capacity_multiplier = 50;
179         }
180         current_map_memory_capacity = 2 * SEEX * 2 * SEEY * 100 * map_memory_capacity_multiplier;
181     }
182     return current_map_memory_capacity;
183 }
184 
clear_memorized_tile(const tripoint & pos)185 void avatar::clear_memorized_tile( const tripoint &pos )
186 {
187     player_map_memory.clear_memorized_tile( pos );
188 }
189 
get_active_missions() const190 std::vector<mission *> avatar::get_active_missions() const
191 {
192     return active_missions;
193 }
194 
get_completed_missions() const195 std::vector<mission *> avatar::get_completed_missions() const
196 {
197     return completed_missions;
198 }
199 
get_failed_missions() const200 std::vector<mission *> avatar::get_failed_missions() const
201 {
202     return failed_missions;
203 }
204 
get_active_mission() const205 mission *avatar::get_active_mission() const
206 {
207     return active_mission;
208 }
209 
reset_all_missions()210 void avatar::reset_all_missions()
211 {
212     active_mission = nullptr;
213     active_missions.clear();
214     completed_missions.clear();
215     failed_missions.clear();
216 }
217 
get_active_mission_target() const218 tripoint_abs_omt avatar::get_active_mission_target() const
219 {
220     if( active_mission == nullptr ) {
221         return overmap::invalid_tripoint;
222     }
223     return active_mission->get_target();
224 }
225 
set_active_mission(mission & cur_mission)226 void avatar::set_active_mission( mission &cur_mission )
227 {
228     const auto iter = std::find( active_missions.begin(), active_missions.end(), &cur_mission );
229     if( iter == active_missions.end() ) {
230         debugmsg( "new active mission %d is not in the active_missions list", cur_mission.get_id() );
231     } else {
232         active_mission = &cur_mission;
233     }
234 }
235 
on_mission_assignment(mission & new_mission)236 void avatar::on_mission_assignment( mission &new_mission )
237 {
238     active_missions.push_back( &new_mission );
239     set_active_mission( new_mission );
240 }
241 
on_mission_finished(mission & cur_mission)242 void avatar::on_mission_finished( mission &cur_mission )
243 {
244     if( cur_mission.has_failed() ) {
245         failed_missions.push_back( &cur_mission );
246         add_msg_if_player( m_bad, _( "Mission \"%s\" is failed." ), cur_mission.name() );
247     } else {
248         completed_missions.push_back( &cur_mission );
249         add_msg_if_player( m_good, _( "Mission \"%s\" is successfully completed." ),
250                            cur_mission.name() );
251     }
252     const auto iter = std::find( active_missions.begin(), active_missions.end(), &cur_mission );
253     if( iter == active_missions.end() ) {
254         debugmsg( "completed mission %d was not in the active_missions list", cur_mission.get_id() );
255     } else {
256         active_missions.erase( iter );
257     }
258     if( &cur_mission == active_mission ) {
259         if( active_missions.empty() ) {
260             active_mission = nullptr;
261         } else {
262             active_mission = active_missions.front();
263         }
264     }
265 }
266 
get_book_reader(const item & book,std::vector<std::string> & reasons) const267 const player *avatar::get_book_reader( const item &book, std::vector<std::string> &reasons ) const
268 {
269     const player *reader = nullptr;
270 
271     if( !book.is_book() ) {
272         reasons.push_back( string_format( _( "Your %s is not good reading material." ), book.tname() ) );
273         return nullptr;
274     }
275 
276     const cata::value_ptr<islot_book> &type = book.type->book;
277     const skill_id &book_skill = type->skill;
278     const int book_skill_requirement = type->req;
279     const bool book_requires_intelligence = type->intel > 0;
280 
281     // Check for conditions that immediately disqualify the player from reading:
282     const optional_vpart_position vp = get_map().veh_at( pos() );
283     if( vp && vp->vehicle().player_in_control( *this ) ) {
284         reasons.emplace_back( _( "It's a bad idea to read while driving!" ) );
285         return nullptr;
286     }
287     if( !fun_to_read( book ) && !has_morale_to_read() && has_identified( book.typeId() ) ) {
288         // Low morale still permits skimming
289         reasons.emplace_back( _( "What's the point of studying?  (Your morale is too low!)" ) );
290         return nullptr;
291     }
292     if( get_book_mastery( book ) == book_mastery::CANT_UNDERSTAND ) {
293         reasons.push_back( string_format( _( "%s %d needed to understand.  You have %d" ),
294                                           book_skill->name(), book_skill_requirement, get_skill_level( book_skill ) ) );
295         return nullptr;
296     }
297 
298     // Check for conditions that disqualify us only if no NPCs can read to us
299     if( book_requires_intelligence && has_trait( trait_ILLITERATE ) ) {
300         reasons.emplace_back( _( "You're illiterate!" ) );
301     } else if( has_trait( trait_HYPEROPIC ) &&
302                !worn_with_flag( STATIC( flag_id( "FIX_FARSIGHT" ) ) ) &&
303                !has_effect( effect_contacts ) &&
304                !has_flag( STATIC( json_character_flag( "ENHANCED_VISION" ) ) ) ) {
305         reasons.emplace_back( _( "Your eyes won't focus without reading glasses." ) );
306     } else if( fine_detail_vision_mod() > 4 ) {
307         // Too dark to read only applies if the player can read to himself
308         reasons.emplace_back( _( "It's too dark to read!" ) );
309         return nullptr;
310     } else {
311         return this;
312     }
313 
314     //Check for NPCs to read for you, negates Illiterate and Far Sighted
315     //The fastest-reading NPC is chosen
316     if( is_deaf() ) {
317         reasons.emplace_back( _( "Maybe someone could read that to you, but you're deaf!" ) );
318         return nullptr;
319     }
320 
321     int time_taken = INT_MAX;
322     auto candidates = get_crafting_helpers();
323 
324     for( const npc *elem : candidates ) {
325         // Check for disqualifying factors:
326         if( book_requires_intelligence && elem->has_trait( trait_ILLITERATE ) ) {
327             reasons.push_back( string_format( _( "%s is illiterate!" ),
328                                               elem->disp_name() ) );
329         } else if( elem->get_book_mastery( book ) == book_mastery::CANT_UNDERSTAND ) {
330             reasons.push_back( string_format( _( "%s %d needed to understand.  %s has %d" ),
331                                               book_skill->name(), book_skill_requirement, elem->disp_name(),
332                                               elem->get_skill_level( book_skill ) ) );
333         } else if( elem->has_trait( trait_HYPEROPIC ) &&
334                    !elem->worn_with_flag( STATIC( flag_id( "FIX_FARSIGHT" ) ) ) &&
335                    !elem->has_effect( effect_contacts ) ) {
336             reasons.push_back( string_format( _( "%s needs reading glasses!" ),
337                                               elem->disp_name() ) );
338         } else if( std::min( fine_detail_vision_mod(), elem->fine_detail_vision_mod() ) > 4 ) {
339             reasons.push_back( string_format(
340                                    _( "It's too dark for %s to read!" ),
341                                    elem->disp_name() ) );
342         } else if( !elem->sees( *this ) ) {
343             reasons.push_back( string_format( _( "%s could read that to you, but they can't see you." ),
344                                               elem->disp_name() ) );
345         } else if( !elem->fun_to_read( book ) && !elem->has_morale_to_read() &&
346                    has_identified( book.typeId() ) ) {
347             // Low morale still permits skimming
348             reasons.push_back( string_format( _( "%s morale is too low!" ), elem->disp_name( true ) ) );
349         } else if( elem->is_blind() ) {
350             reasons.push_back( string_format( _( "%s is blind." ), elem->disp_name() ) );
351         } else {
352             int proj_time = time_to_read( book, *elem );
353             if( proj_time < time_taken ) {
354                 reader = elem;
355                 time_taken = proj_time;
356             }
357         }
358     }
359     //end for all candidates
360     return reader;
361 }
362 
time_to_read(const item & book,const player & reader,const player * learner) const363 int avatar::time_to_read( const item &book, const player &reader, const player *learner ) const
364 {
365     const auto &type = book.type->book;
366     const skill_id &skill = type->skill;
367     // The reader's reading speed has an effect only if they're trying to understand the book as they read it
368     // Reading speed is assumed to be how well you learn from books (as opposed to hands-on experience)
369     const bool try_understand = reader.fun_to_read( book ) ||
370                                 reader.get_skill_level( skill ) < type->level;
371     int reading_speed = try_understand ? std::max( reader.read_speed(), read_speed() ) : read_speed();
372     if( learner ) {
373         reading_speed = std::max( reading_speed, learner->read_speed() );
374     }
375 
376     int retval = type->time * reading_speed;
377     retval *= std::min( fine_detail_vision_mod(), reader.fine_detail_vision_mod() );
378 
379     const int effective_int = std::min( { get_int(), reader.get_int(), learner ? learner->get_int() : INT_MAX } );
380     if( type->intel > effective_int && !reader.has_trait( trait_PROF_DICEMASTER ) ) {
381         retval += type->time * ( type->intel - effective_int ) * 100;
382     }
383     if( !has_identified( book.typeId() ) ) {
384         //skimming
385         retval /= 10;
386     }
387     return retval;
388 }
389 
390 /**
391  * Explanation of ACT_READ activity values:
392  *
393  * position: ID of the reader
394  * targets: 1-element vector with the item_location (always in inventory/wielded) of the book being read
395  * index: We are studying until the player with this ID gains a level; 0 indicates reading once
396  * values: IDs of the NPCs who will learn something
397  * str_values: Parallel to values, these contain the learning penalties (as doubles in string form) as follows:
398  *             Experience gained = Experience normally gained * penalty
399  */
read(item & it,const bool continuous)400 bool avatar::read( item &it, const bool continuous )
401 {
402     if( it.is_null() ) {
403         add_msg( m_info, _( "Never mind." ) );
404         return false;
405     }
406     std::vector<std::string> fail_messages;
407     const player *reader = get_book_reader( it, fail_messages );
408     if( reader == nullptr ) {
409         // We can't read, and neither can our followers
410         for( const std::string &reason : fail_messages ) {
411             add_msg( m_bad, reason );
412         }
413         return false;
414     }
415 
416     if( it.get_use( "learn_spell" ) ) {
417         it.get_use( "learn_spell" )->call( *this, it, it.active, pos() );
418         return true;
419     }
420 
421     const int time_taken = time_to_read( it, *reader );
422 
423     add_msg_debug( "avatar::read: time_taken = %d", time_taken );
424     player_activity act( ACT_READ, time_taken, continuous ? activity.index : 0,
425                          reader->getID().get_value() );
426     act.targets.emplace_back( item_location( *this, &it ) );
427 
428     // If the player hasn't read this book before, skim it to get an idea of what's in it.
429     if( !has_identified( it.typeId() ) ) {
430         if( reader != this ) {
431             add_msg( m_info, fail_messages[0] );
432             add_msg( m_info, _( "%s reads aloud…" ), reader->disp_name() );
433         }
434         assign_activity( act );
435         get_event_bus().send<event_type::reads_book>( getID(), it.typeId() );
436         return true;
437     }
438 
439     if( it.typeId() == itype_guidebook ) {
440         // special guidebook effect: print a misc. hint when read
441         if( reader != this ) {
442             add_msg( m_info, fail_messages[0] );
443             dynamic_cast<const npc &>( *reader ).say( get_hint() );
444         } else {
445             add_msg( m_info, get_hint() );
446         }
447         get_event_bus().send<event_type::reads_book>( getID(), it.typeId() );
448         mod_moves( -100 );
449         return false;
450     }
451 
452     const auto &type = it.type->book;
453     const skill_id &skill = type->skill;
454     const std::string skill_name = skill ? skill.obj().name() : "";
455 
456     // Find NPCs to join the study session:
457     std::map<npc *, std::string> learners;
458     //reading only for fun
459     std::map<npc *, std::string> fun_learners;
460     std::map<npc *, std::string> nonlearners;
461     auto candidates = get_crafting_helpers();
462     for( npc *elem : candidates ) {
463         const book_mastery mastery = elem->get_book_mastery( it );
464         const bool morale_req = elem->fun_to_read( it ) || elem->has_morale_to_read();
465 
466         // Note that the reader cannot be a nonlearner
467         // since a reader should always have enough morale to read
468         // and at the very least be able to understand the book
469 
470         if( elem->is_deaf() && elem != reader ) {
471             nonlearners.insert( { elem, _( " (deaf)" ) } );
472         } else if( mastery == book_mastery::MASTERED && elem->fun_to_read( it ) ) {
473             fun_learners.insert( {elem, elem == reader ? _( " (reading aloud to you)" ) : "" } );
474             act.values.push_back( elem->getID().get_value() );
475             act.str_values.emplace_back( "1" );
476         } else if( mastery == book_mastery::LEARNING && morale_req ) {
477             learners.insert( {elem, elem == reader ? _( " (reading aloud to you)" ) : ""} );
478             const double penalty = static_cast<double>( time_taken ) / time_to_read( it, *reader, elem );
479             act.values.push_back( elem->getID().get_value() );
480             act.str_values.push_back( std::to_string( penalty ) );
481         } else {
482             std::string reason = _( " (uninterested)" );
483             if( !morale_req ) {
484                 reason = _( " (too sad)" );
485             } else if( mastery == book_mastery::CANT_UNDERSTAND ) {
486                 reason = string_format( _( " (needs %d %s)" ), type->req, skill_name );
487             } else if( mastery == book_mastery::MASTERED ) {
488                 reason = string_format( _( " (already has %d %s)" ), type->level, skill_name );
489             }
490             nonlearners.insert( { elem, reason } );
491         }
492     }
493 
494     if( !continuous ) {
495         //only show the menu if there's useful information or multiple options
496         if( skill || !nonlearners.empty() || !fun_learners.empty() ) {
497             uilist menu;
498 
499             // Some helpers to reduce repetition:
500             auto length = []( const std::pair<npc *, std::string> &elem ) {
501                 return utf8_width( elem.first->disp_name() ) + utf8_width( elem.second );
502             };
503 
504             auto max_length = [&length]( const std::map<npc *, std::string> &m ) {
505                 auto max_ele = std::max_element( m.begin(),
506                                                  m.end(), [&length]( const std::pair<npc *, std::string> &left,
507                 const std::pair<npc *, std::string> &right ) {
508                     return length( left ) < length( right );
509                 } );
510                 return max_ele == m.end() ? 0 : length( *max_ele );
511             };
512 
513             auto get_text =
514             [&]( const std::map<npc *, std::string> &m, const std::pair<npc *, std::string> &elem ) {
515                 const int lvl = elem.first->get_skill_level( skill );
516                 const std::string lvl_text = skill ? string_format( _( " | current level: %d" ), lvl ) : "";
517                 const std::string name_text = elem.first->disp_name() + elem.second;
518                 return string_format( "%s%s", left_justify( name_text, max_length( m ) ), lvl_text );
519             };
520 
521             auto add_header = [&menu]( const std::string & str ) {
522                 menu.addentry( -1, false, -1, "" );
523                 uilist_entry header( -1, false, -1, str, c_yellow, c_yellow );
524                 header.force_color = true;
525                 menu.entries.push_back( header );
526             };
527 
528             menu.title = !skill ? string_format( _( "Reading %s" ), it.type_name() ) :
529                          //~ %1$s: book name, %2$s: skill name, %3$d and %4$d: skill levels
530                          string_format( _( "Reading %1$s (can train %2$s from %3$d to %4$d)" ), it.type_name(),
531                                         skill_name, type->req, type->level );
532 
533             if( skill ) {
534                 const int lvl = get_skill_level( skill );
535                 menu.addentry( getID().get_value(), lvl < type->level, '0',
536                                string_format( _( "Read until you gain a level | current level: %d" ), lvl ) );
537             } else {
538                 menu.addentry( -1, false, '0', _( "Read until you gain a level" ) );
539             }
540             menu.addentry( 0, true, '1', _( "Read once" ) );
541 
542             if( skill && !learners.empty() ) {
543                 add_header( _( "Read until this NPC gains a level:" ) );
544                 for( const auto &elem : learners ) {
545                     menu.addentry( elem.first->getID().get_value(), true, -1,
546                                    get_text( learners, elem ) );
547                 }
548             }
549             if( !fun_learners.empty() ) {
550                 add_header( _( "Reading for fun:" ) );
551                 for( const auto &elem : fun_learners ) {
552                     menu.addentry( -1, false, -1, get_text( fun_learners, elem ) );
553                 }
554             }
555             if( !nonlearners.empty() ) {
556                 add_header( _( "Not participating:" ) );
557                 for( const auto &elem : nonlearners ) {
558                     menu.addentry( -1, false, -1, get_text( nonlearners, elem ) );
559                 }
560             }
561 
562             menu.query( true );
563             if( menu.ret == UILIST_CANCEL ) {
564                 add_msg( m_info, _( "Never mind." ) );
565                 return false;
566             }
567             act.index = menu.ret;
568         }
569         if( it.type->use_methods.count( "MA_MANUAL" ) ) {
570 
571             if( martial_arts_data->has_martialart( martial_art_learned_from( *it.type ) ) ) {
572                 add_msg_if_player( m_info, _( "You already know all this book has to teach." ) );
573                 activity.set_to_null();
574                 return false;
575             }
576 
577             uilist menu;
578             menu.title = string_format( _( "Train %s from manual:" ),
579                                         martial_art_learned_from( *it.type )->name );
580             menu.addentry( -1, true, '1', _( "Train once" ) );
581             menu.addentry( getID().get_value(), true, '0', _( "Train until tired or success" ) );
582             menu.query( true );
583             if( menu.ret == UILIST_CANCEL ) {
584                 add_msg( m_info, _( "Never mind." ) );
585                 return false;
586             }
587             act.index = menu.ret;
588         }
589         add_msg( m_info, _( "Now reading %s, %s to stop early." ),
590                  it.type_name(), press_x( ACTION_PAUSE ) );
591     }
592 
593     // Print some informational messages, but only the first time or if the information changes
594 
595     if( !continuous || activity.position != act.position ) {
596         if( reader != this ) {
597             add_msg( m_info, fail_messages[0] );
598             add_msg( m_info, _( "%s reads aloud…" ), reader->disp_name() );
599         } else if( !learners.empty() || !fun_learners.empty() ) {
600             add_msg( m_info, _( "You read aloud…" ) );
601         }
602     }
603 
604     if( !continuous ||
605     !std::all_of( learners.begin(), learners.end(), [&]( const std::pair<npc *, std::string> &elem ) {
606     return std::count( activity.values.begin(), activity.values.end(),
607                        elem.first->getID().get_value() ) != 0;
608     } ) ||
609     !std::all_of( activity.values.begin(), activity.values.end(), [&]( int elem ) {
610         return learners.find( g->find_npc( character_id( elem ) ) ) != learners.end();
611     } ) ) {
612 
613         if( learners.size() == 1 ) {
614             add_msg( m_info, _( "%s studies with you." ), learners.begin()->first->disp_name() );
615         } else if( !learners.empty() ) {
616             const std::string them = enumerate_as_string( learners.begin(),
617             learners.end(), [&]( const std::pair<npc *, std::string> &elem ) {
618                 return elem.first->disp_name();
619             } );
620             add_msg( m_info, _( "%s study with you." ), them );
621         }
622 
623         // Don't include the reader as it would be too redundant.
624         std::set<std::string> readers;
625         for( const auto &elem : fun_learners ) {
626             if( elem.first != reader ) {
627                 readers.insert( elem.first->disp_name() );
628             }
629         }
630         if( readers.size() == 1 ) {
631             add_msg( m_info, _( "%s reads with you for fun." ), readers.begin()->c_str() );
632         } else if( !readers.empty() ) {
633             const std::string them = enumerate_as_string( readers );
634             add_msg( m_info, _( "%s read with you for fun." ), them );
635         }
636     }
637 
638     if( std::min( fine_detail_vision_mod(), reader->fine_detail_vision_mod() ) > 1.0 ) {
639         add_msg( m_warning,
640                  _( "It's difficult for %s to see fine details right now.  Reading will take longer than usual." ),
641                  reader->disp_name() );
642     }
643 
644     const int intelligence = get_int();
645     const bool complex_penalty = type->intel > std::min( intelligence, reader->get_int() ) &&
646                                  !reader->has_trait( trait_PROF_DICEMASTER );
647     const player *complex_player = reader->get_int() < intelligence ? reader : this;
648     if( complex_penalty && !continuous ) {
649         add_msg( m_warning,
650                  _( "This book is too complex for %s to easily understand.  It will take longer to read." ),
651                  complex_player->disp_name() );
652     }
653 
654     // push an identifier of martial art book to the action handling
655     if( it.type->use_methods.count( "MA_MANUAL" ) ) {
656 
657         if( get_stamina() < get_stamina_max() / 10 ) {
658             add_msg( m_info, _( "You are too exhausted to train martial arts." ) );
659             return false;
660         }
661         act.str_values.clear();
662         act.str_values.emplace_back( "martial_art" );
663     }
664 
665     assign_activity( act );
666 
667     // Reinforce any existing morale bonus/penalty, so it doesn't decay
668     // away while you read more.
669     const time_duration decay_start = 1_turns * time_taken / 1000;
670     std::set<player *> apply_morale = { this };
671     for( const auto &elem : learners ) {
672         apply_morale.insert( elem.first );
673     }
674     for( const auto &elem : fun_learners ) {
675         apply_morale.insert( elem.first );
676     }
677     for( player *elem : apply_morale ) {
678         //Fun bonuses for spiritual and To Serve Man are no longer calculated here.
679         elem->add_morale( MORALE_BOOK, 0, book_fun_for( it, *elem ) * 15, decay_start + 30_minutes,
680                           decay_start, false, it.type );
681     }
682     get_event_bus().send<event_type::reads_book>( getID(), it.typeId() );
683     return true;
684 }
685 
grab(object_type grab_type,const tripoint & grab_point)686 void avatar::grab( object_type grab_type, const tripoint &grab_point )
687 {
688     const auto update_memory =
689     [this]( const object_type gtype, const tripoint & gpoint, const bool erase ) {
690         map &m = get_map();
691         if( gtype == object_type::VEHICLE ) {
692             const optional_vpart_position vp = m.veh_at( pos() + gpoint );
693             if( vp ) {
694                 const vehicle &veh = vp->vehicle();
695                 for( const tripoint &target : veh.get_points() ) {
696                     if( erase ) {
697                         clear_memorized_tile( m.getabs( target ) );
698                     }
699                     m.set_memory_seen_cache_dirty( target );
700                 }
701             }
702         } else if( gtype != object_type::NONE ) {
703             if( erase ) {
704                 clear_memorized_tile( m.getabs( pos() + gpoint ) );
705             }
706             m.set_memory_seen_cache_dirty( pos() + gpoint );
707         }
708     };
709     // Mark the area covered by the previous vehicle/furniture/etc for re-memorizing.
710     update_memory( this->grab_type, this->grab_point, false );
711     // Clear the map memory for the area covered by the vehicle/furniture/etc to
712     // eliminate ghost vehicles/furnitures/etc.
713     // FIXME: change map memory to memorize all memorizable objects and only erase vehicle part memory.
714     update_memory( grab_type, grab_point, true );
715 
716     this->grab_type = grab_type;
717     this->grab_point = grab_point;
718 
719     path_settings->avoid_rough_terrain = grab_type != object_type::NONE;
720 }
721 
get_grab_type() const722 object_type avatar::get_grab_type() const
723 {
724     return grab_type;
725 }
726 
do_read(item & book)727 void avatar::do_read( item &book )
728 {
729     const auto &reading = book.type->book;
730     if( !reading ) {
731         activity.set_to_null();
732         return;
733     }
734     const skill_id &skill = reading->skill;
735 
736     if( !has_identified( book.typeId() ) ) {
737         identify( book );
738         activity.set_to_null();
739         return;
740     }
741 
742     //learners and their penalties
743     std::vector<std::pair<player *, double>> learners;
744     for( size_t i = 0; i < activity.values.size(); i++ ) {
745         player *n = g->find_npc( character_id( activity.values[i] ) );
746         if( n != nullptr ) {
747             const std::string &s = activity.get_str_value( i, "1" );
748             learners.push_back( { n, strtod( s.c_str(), nullptr ) } );
749         }
750         // Otherwise they must have died/teleported or something
751     }
752     learners.push_back( { this, 1.0 } );
753     //whether to continue reading or not
754     bool continuous = false;
755     // NPCs who learned a little about the skill
756     std::set<std::string> little_learned;
757     std::set<std::string> cant_learn;
758     std::list<std::string> out_of_chapters;
759 
760     for( auto &elem : learners ) {
761         player *learner = elem.first;
762 
763         if( book_fun_for( book, *learner ) != 0 ) {
764             //Fun bonus is no longer calculated here.
765             learner->add_morale( MORALE_BOOK, book_fun_for( book, *learner ) * 5, book_fun_for( book,
766                                  *learner ) * 15, 1_hours, 30_minutes, true,
767                                  book.type );
768         }
769 
770         book.mark_chapter_as_read( *learner );
771 
772         if( skill && learner->get_skill_level( skill ) < reading->level &&
773             learner->get_skill_level_object( skill ).can_train() ) {
774             SkillLevel &skill_level = learner->get_skill_level_object( skill );
775             const int originalSkillLevel = skill_level.level();
776 
777             // Calculate experience gained
778             /** @EFFECT_INT increases reading comprehension */
779             // Enhanced Memory Banks modestly boosts experience
780             int min_ex = std::max( 1, reading->time / 10 + learner->get_int() / 4 );
781             int max_ex = reading->time /  5 + learner->get_int() / 2 - originalSkillLevel;
782             if( has_active_bionic( bio_memory ) ) {
783                 min_ex += 2;
784             }
785 
786             min_ex = adjust_for_focus( min_ex ) / 100;
787             max_ex = adjust_for_focus( max_ex ) / 100;
788 
789             if( max_ex < 2 ) {
790                 max_ex = 2;
791             }
792             if( max_ex > 10 ) {
793                 max_ex = 10;
794             }
795             if( max_ex < min_ex ) {
796                 max_ex = min_ex;
797             }
798 
799             min_ex *= ( originalSkillLevel + 1 ) * elem.second;
800             min_ex = std::max( min_ex, 1 );
801             max_ex *= ( originalSkillLevel + 1 ) * elem.second;
802             max_ex = std::max( min_ex, max_ex );
803 
804             skill_level.readBook( min_ex, max_ex, reading->level );
805 
806             std::string skill_name = skill.obj().name();
807 
808             if( skill_level != originalSkillLevel ) {
809                 get_event_bus().send<event_type::gains_skill_level>(
810                     learner->getID(), skill, skill_level.level() );
811                 if( learner->is_player() ) {
812                     add_msg( m_good, _( "You increase %s to level %d." ), skill.obj().name(),
813                              originalSkillLevel + 1 );
814                 } else {
815                     add_msg( m_good, _( "%s increases their %s level." ), learner->disp_name(), skill_name );
816                 }
817             } else {
818                 //skill_level == originalSkillLevel
819                 if( activity.index == learner->getID().get_value() ) {
820                     continuous = true;
821                 }
822                 if( learner->is_player() ) {
823                     add_msg( m_info, _( "You learn a little about %s!  (%d%%)" ), skill_name, skill_level.exercise() );
824                 } else {
825                     little_learned.insert( learner->disp_name() );
826                 }
827             }
828 
829             if( ( skill_level == reading->level || !skill_level.can_train() ) ||
830                 ( learner->has_trait( trait_SCHIZOPHRENIC ) && one_in( 25 ) ) ) {
831                 if( learner->is_player() ) {
832                     add_msg( m_info, _( "You can no longer learn from %s." ), book.type_name() );
833                 } else {
834                     cant_learn.insert( learner->disp_name() );
835                 }
836             }
837         } else if( skill ) {
838             if( learner->is_player() ) {
839                 add_msg( m_info, _( "You can no longer learn from %s." ), book.type_name() );
840             } else {
841                 cant_learn.insert( learner->disp_name() );
842             }
843         }
844     }
845     //end for all learners
846 
847     if( little_learned.size() == 1 ) {
848         add_msg( m_info, _( "%s learns a little about %s!" ), little_learned.begin()->c_str(),
849                  skill.obj().name() );
850     } else if( !little_learned.empty() ) {
851         const std::string little_learned_msg = enumerate_as_string( little_learned );
852         add_msg( m_info, _( "%s learn a little about %s!" ), little_learned_msg, skill.obj().name() );
853     }
854 
855     if( !cant_learn.empty() ) {
856         const std::string names = enumerate_as_string( cant_learn );
857         add_msg( m_info, _( "%s can no longer learn from %s." ), names, book.type_name() );
858     }
859     if( !out_of_chapters.empty() ) {
860         const std::string names = enumerate_as_string( out_of_chapters );
861         add_msg( m_info, _( "Rereading the %s isn't as much fun for %s." ),
862                  book.type_name(), names );
863         if( out_of_chapters.front() == disp_name() && one_in( 6 ) ) {
864             add_msg( m_info, _( "Maybe you should find something new to read…" ) );
865         }
866     }
867 
868     // NPCs can't learn martial arts from manuals (yet).
869     auto m = book.type->use_methods.find( "MA_MANUAL" );
870     if( m != book.type->use_methods.end() ) {
871         const matype_id style_to_learn = martial_art_learned_from( *book.type );
872         skill_id skill_used = style_to_learn->primary_skill;
873         int difficulty = std::max( 1, style_to_learn->learn_difficulty );
874         difficulty = std::max( 1, 20 + difficulty * 2 - get_skill_level( skill_used ) * 2 );
875         add_msg_debug( _( "Chance to learn one in: %d" ), difficulty );
876 
877         if( one_in( difficulty ) ) {
878             m->second.call( *this, book, false, pos() );
879             continuous = false;
880         } else {
881             if( activity.index == getID().get_value() ) {
882                 continuous = true;
883                 switch( rng( 1, 5 ) ) {
884                     case 1:
885                         add_msg( m_info,
886                                  _( "You train the moves according to the book, but can't get a grasp of the style, so you start from the beginning." ) );
887                         break;
888                     case 2:
889                         add_msg( m_info,
890                                  _( "This martial art is not easy to grasp.  You start training the moves from the beginning." ) );
891                         break;
892                     case 3:
893                         add_msg( m_info,
894                                  _( "You decide to read the manual and train even more.  In martial arts, patience leads to mastery." ) );
895                         break;
896                     case 4:
897                     case 5:
898                         add_msg( m_info, _( "You try again.  This training will finally pay off." ) );
899                         break;
900                 }
901             } else {
902                 add_msg( m_info, _( "You train for a while." ) );
903             }
904         }
905     }
906 
907     if( continuous ) {
908         activity.set_to_null();
909         read( book, true );
910         if( activity ) {
911             return;
912         }
913     }
914 
915     activity.set_to_null();
916 }
917 
has_identified(const itype_id & item_id) const918 bool avatar::has_identified( const itype_id &item_id ) const
919 {
920     return items_identified.count( item_id ) > 0;
921 }
922 
identify(const item & item)923 void avatar::identify( const item &item )
924 {
925     if( has_identified( item.typeId() ) ) {
926         return;
927     }
928     if( !item.is_book() ) {
929         debugmsg( "tried to identify non-book item" );
930         return;
931     }
932 
933     const auto &book = item; // alias
934     cata_assert( !has_identified( item.typeId() ) );
935     items_identified.insert( item.typeId() );
936     cata_assert( has_identified( item.typeId() ) );
937 
938     const auto &reading = item.type->book;
939     const skill_id &skill = reading->skill;
940 
941     add_msg( _( "You skim %s to find out what's in it." ), book.type_name() );
942     if( skill && get_skill_level_object( skill ).can_train() ) {
943         add_msg( m_info, _( "Can bring your %s skill to %d." ),
944                  skill.obj().name(), reading->level );
945         if( reading->req != 0 ) {
946             add_msg( m_info, _( "Requires %s level %d to understand." ),
947                      skill.obj().name(), reading->req );
948         }
949     }
950 
951     if( reading->intel != 0 ) {
952         add_msg( m_info, _( "Requires intelligence of %d to easily read." ), reading->intel );
953     }
954     //It feels wrong to use a pointer to *this, but I can't find any other player pointers in this method.
955     if( book_fun_for( book, *this ) != 0 ) {
956         add_msg( m_info, _( "Reading this book affects your morale by %d." ), book_fun_for( book, *this ) );
957     }
958 
959     if( book.type->use_methods.count( "MA_MANUAL" ) ) {
960         const matype_id style_to_learn = martial_art_learned_from( *book.type );
961         add_msg( m_info, _( "You can learn %s style from it." ), style_to_learn->name );
962         add_msg( m_info, _( "This fighting style is %s to learn." ),
963                  martialart_difficulty( style_to_learn ) );
964         add_msg( m_info, _( "It would be easier to master if you'd have skill expertise in %s." ),
965                  style_to_learn->primary_skill->name() );
966         add_msg( m_info, _( "A training session with this book takes %s." ),
967                  to_string( time_duration::from_minutes( reading->time ) ) );
968     } else {
969         add_msg( m_info, ngettext( "A chapter of this book takes %d minute to read.",
970                                    "A chapter of this book takes %d minutes to read.", reading->time ),
971                  reading->time );
972     }
973 
974     std::vector<std::string> recipe_list;
975     for( const auto &elem : reading->recipes ) {
976         // If the player knows it, they recognize it even if it's not clearly stated.
977         if( elem.is_hidden() && !knows_recipe( elem.recipe ) ) {
978             continue;
979         }
980         recipe_list.push_back( elem.name() );
981     }
982     if( !recipe_list.empty() ) {
983         std::string recipe_line =
984             string_format( ngettext( "This book contains %1$zu crafting recipe: %2$s",
985                                      "This book contains %1$zu crafting recipes: %2$s",
986                                      recipe_list.size() ),
987                            recipe_list.size(),
988                            enumerate_as_string( recipe_list ) );
989         add_msg( m_info, recipe_line );
990     }
991     if( recipe_list.size() != reading->recipes.size() ) {
992         add_msg( m_info, _( "It might help you figuring out some more recipes." ) );
993     }
994 }
995 
clear_identified()996 void avatar::clear_identified()
997 {
998     items_identified.clear();
999 }
1000 
wake_up()1001 void avatar::wake_up()
1002 {
1003     if( has_effect( effect_sleep ) ) {
1004         if( calendar::turn - get_effect( effect_sleep ).get_start_time() > 2_hours ) {
1005             print_health();
1006         }
1007         // alarm was set and player hasn't slept through the alarm.
1008         if( has_effect( effect_alarm_clock ) && !has_effect( effect_slept_through_alarm ) ) {
1009             add_msg( _( "It looks like you woke up before your alarm." ) );
1010             remove_effect( effect_alarm_clock );
1011         } else if( has_effect( effect_slept_through_alarm ) ) {
1012             if( has_flag( json_flag_ALARMCLOCK ) ) {
1013                 add_msg( m_warning, _( "It looks like you've slept through your internal alarm…" ) );
1014             } else {
1015                 add_msg( m_warning, _( "It looks like you've slept through the alarm…" ) );
1016             }
1017         }
1018     }
1019     Character::wake_up();
1020 }
1021 
vomit()1022 void avatar::vomit()
1023 {
1024     if( stomach.contains() != 0_ml ) {
1025         // Remove all joy from previously eaten food and apply the penalty
1026         rem_morale( MORALE_FOOD_GOOD );
1027         rem_morale( MORALE_FOOD_HOT );
1028         // bears must suffer too
1029         rem_morale( MORALE_HONEY );
1030         // 1.5 times longer
1031         add_morale( MORALE_VOMITED, -2 * units::to_milliliter( stomach.contains() / 50 ), -40, 90_minutes,
1032                     45_minutes, false );
1033 
1034     } else {
1035         add_msg( m_warning, _( "You retched, but your stomach is empty." ) );
1036     }
1037     Character::vomit();
1038 }
1039 
basic_symbol_color() const1040 nc_color avatar::basic_symbol_color() const
1041 {
1042     if( has_effect( effect_onfire ) ) {
1043         return c_red;
1044     }
1045     if( has_effect( effect_stunned ) ) {
1046         return c_light_blue;
1047     }
1048     if( has_effect( effect_boomered ) ) {
1049         return c_pink;
1050     }
1051     if( has_active_mutation( trait_id( "SHELL2" ) ) ) {
1052         return c_magenta;
1053     }
1054     if( underwater ) {
1055         return c_blue;
1056     }
1057     if( has_active_bionic( bio_cloak ) ||
1058         is_wearing_active_optcloak() || has_trait( trait_DEBUG_CLOAK ) ) {
1059         return c_dark_gray;
1060     }
1061     return move_mode->symbol_color();
1062 }
1063 
print_info(const catacurses::window & w,int vStart,int,int column) const1064 int avatar::print_info( const catacurses::window &w, int vStart, int, int column ) const
1065 {
1066     return vStart + fold_and_print( w, point( column, vStart ), getmaxx( w ) - column - 1, c_dark_gray,
1067                                     _( "You (%s)" ),
1068                                     name ) - 1;
1069 }
1070 
disp_morale()1071 void avatar::disp_morale()
1072 {
1073     int equilibrium = calc_focus_equilibrium();
1074 
1075     int fatigue_penalty = 0;
1076     if( get_fatigue() >= fatigue_levels::MASSIVE_FATIGUE && equilibrium > 20 ) {
1077         fatigue_penalty = equilibrium - 20;
1078         equilibrium = 20;
1079     } else if( get_fatigue() >= fatigue_levels::EXHAUSTED && equilibrium > 40 ) {
1080         fatigue_penalty = equilibrium - 40;
1081         equilibrium = 40;
1082     } else if( get_fatigue() >= fatigue_levels::DEAD_TIRED && equilibrium > 60 ) {
1083         fatigue_penalty = equilibrium - 60;
1084         equilibrium = 60;
1085     } else if( get_fatigue() >= fatigue_levels::TIRED && equilibrium > 80 ) {
1086         fatigue_penalty = equilibrium - 80;
1087         equilibrium = 80;
1088     }
1089 
1090     int pain_penalty = 0;
1091     if( get_perceived_pain() && !has_trait( trait_CENOBITE ) ) {
1092         pain_penalty = calc_focus_equilibrium( true ) - equilibrium - fatigue_penalty;
1093     }
1094 
1095     morale->display( equilibrium, pain_penalty, fatigue_penalty );
1096 }
1097 
calc_focus_equilibrium(bool ignore_pain) const1098 int avatar::calc_focus_equilibrium( bool ignore_pain ) const
1099 {
1100     int focus_equilibrium = 100;
1101 
1102     if( activity.id() == ACT_READ ) {
1103         const item *book = activity.targets[0].get_item();
1104         if( book && book->is_book() && get_item_position( book ) != INT_MIN ) {
1105             auto &bt = book->type->book;
1106             // apply a penalty when we're actually learning something
1107             const SkillLevel &skill_level = get_skill_level_object( bt->skill );
1108             if( skill_level.can_train() && skill_level < bt->level ) {
1109                 focus_equilibrium -= 50;
1110             }
1111         }
1112     }
1113 
1114     int eff_morale = get_morale_level();
1115     // Factor in perceived pain, since it's harder to rest your mind while your body hurts.
1116     // Cenobites don't mind, though
1117     if( !ignore_pain && !has_trait( trait_CENOBITE ) ) {
1118         int perceived_pain = get_perceived_pain();
1119         if( has_trait( trait_MASOCHIST ) ) {
1120             if( perceived_pain > 20 ) {
1121                 eff_morale = eff_morale - ( perceived_pain - 20 );
1122             }
1123         } else {
1124             eff_morale = eff_morale - perceived_pain;
1125         }
1126     }
1127 
1128     if( eff_morale < -99 ) {
1129         // At very low morale, focus is at it's minimum
1130         focus_equilibrium = 1;
1131     } else if( eff_morale <= 50 ) {
1132         // At -99 to +50 morale, each point of morale gives or takes 1 point of focus
1133         focus_equilibrium += eff_morale;
1134     } else {
1135         /* Above 50 morale, we apply strong diminishing returns.
1136         * Each block of 50 takes twice as many morale points as the previous one:
1137         * 150 focus at 50 morale (as before)
1138         * 200 focus at 150 morale (100 more morale)
1139         * 250 focus at 350 morale (200 more morale)
1140         * ...
1141         * Cap out at 400% focus gain with 3,150+ morale, mostly as a sanity check.
1142         */
1143 
1144         int block_multiplier = 1;
1145         int morale_left = eff_morale;
1146         while( focus_equilibrium < 400 ) {
1147             if( morale_left > 50 * block_multiplier ) {
1148                 // We can afford the entire block.  Get it and continue.
1149                 morale_left -= 50 * block_multiplier;
1150                 focus_equilibrium += 50;
1151                 block_multiplier *= 2;
1152             } else {
1153                 // We can't afford the entire block.  Each block_multiplier morale
1154                 // points give 1 focus, and then we're done.
1155                 focus_equilibrium += morale_left / block_multiplier;
1156                 break;
1157             }
1158         }
1159     }
1160 
1161     // This should be redundant, but just in case...
1162     if( focus_equilibrium < 1 ) {
1163         focus_equilibrium = 1;
1164     } else if( focus_equilibrium > 400 ) {
1165         focus_equilibrium = 400;
1166     }
1167     return focus_equilibrium;
1168 }
1169 
calc_focus_change() const1170 int avatar::calc_focus_change() const
1171 {
1172     int focus_gap = calc_focus_equilibrium() - get_focus();
1173 
1174     // handle negative gain rates in a symmetric manner
1175     int base_change = 1;
1176     if( focus_gap < 0 ) {
1177         base_change = -1;
1178         focus_gap = -focus_gap;
1179     }
1180 
1181     int gain = focus_gap * base_change;
1182 
1183     // Fatigue will incrementally decrease any focus above related cap
1184     if( ( get_fatigue() >= fatigue_levels::TIRED && get_focus() > 80 ) ||
1185         ( get_fatigue() >= fatigue_levels::DEAD_TIRED && get_focus() > 60 ) ||
1186         ( get_fatigue() >= fatigue_levels::EXHAUSTED && get_focus() > 40 ) ||
1187         ( get_fatigue() >= fatigue_levels::MASSIVE_FATIGUE && get_focus() > 20 ) ) {
1188 
1189         //it can fall faster then 1
1190         if( gain > -1 ) {
1191             gain = -1;
1192         }
1193     }
1194     return gain;
1195 }
1196 
update_mental_focus()1197 void avatar::update_mental_focus()
1198 {
1199     // calc_focus_change() returns percentile focus, applying it directly
1200     // to focus pool is an implicit / 100.
1201     focus_pool += 10 * calc_focus_change();
1202 
1203     // Moved from calc_focus_equilibrium, because it is now const
1204     if( activity.id() == ACT_READ ) {
1205         const item *book = activity.targets[0].get_item();
1206         if( get_item_position( book ) == INT_MIN || !book->is_book() ) {
1207             add_msg_if_player( m_bad, _( "You lost your book!  You stop reading." ) );
1208             activity.set_to_null();
1209         }
1210     }
1211 }
1212 
reset_stats()1213 void avatar::reset_stats()
1214 {
1215     const int current_stim = get_stim();
1216 
1217     // Trait / mutation buffs
1218     if( has_trait( trait_THICK_SCALES ) ) {
1219         add_miss_reason( _( "Your thick scales get in the way." ), 2 );
1220     }
1221     if( has_trait( trait_CHITIN2 ) || has_trait( trait_CHITIN3 ) || has_trait( trait_CHITIN_FUR3 ) ) {
1222         add_miss_reason( _( "Your chitin gets in the way." ), 1 );
1223     }
1224     if( has_trait( trait_COMPOUND_EYES ) && !wearing_something_on( bodypart_id( "eyes" ) ) ) {
1225         mod_per_bonus( 2 );
1226     }
1227     if( has_trait( trait_INSECT_ARMS ) ) {
1228         add_miss_reason( _( "Your insect limbs get in the way." ), 2 );
1229     }
1230     if( has_trait( trait_INSECT_ARMS_OK ) ) {
1231         if( !wearing_something_on( bodypart_id( "torso" ) ) ) {
1232             mod_dex_bonus( 1 );
1233         } else {
1234             mod_dex_bonus( -1 );
1235             add_miss_reason( _( "Your clothing restricts your insect arms." ), 1 );
1236         }
1237     }
1238     if( has_trait( trait_WEBBED ) ) {
1239         add_miss_reason( _( "Your webbed hands get in the way." ), 1 );
1240     }
1241     if( has_trait( trait_ARACHNID_ARMS ) ) {
1242         add_miss_reason( _( "Your arachnid limbs get in the way." ), 4 );
1243     }
1244     if( has_trait( trait_ARACHNID_ARMS_OK ) ) {
1245         if( !wearing_something_on( bodypart_id( "torso" ) ) ) {
1246             mod_dex_bonus( 2 );
1247         } else if( !exclusive_flag_coverage( STATIC( flag_id( "OVERSIZE" ) ) )
1248                    .test( body_part_torso ) ) {
1249             mod_dex_bonus( -2 );
1250             add_miss_reason( _( "Your clothing constricts your arachnid limbs." ), 2 );
1251         }
1252     }
1253     const auto set_fake_effect_dur = [this]( const efftype_id & type, const time_duration & dur ) {
1254         effect &eff = get_effect( type );
1255         if( eff.get_duration() == dur ) {
1256             return;
1257         }
1258 
1259         if( eff.is_null() && dur > 0_turns ) {
1260             add_effect( type, dur, true );
1261         } else if( dur > 0_turns ) {
1262             eff.set_duration( dur );
1263         } else {
1264             remove_effect( type );
1265         }
1266     };
1267     // Painkiller
1268     set_fake_effect_dur( effect_pkill, 1_turns * get_painkiller() );
1269 
1270     // Pain
1271     if( get_perceived_pain() > 0 ) {
1272         const stat_mod ppen = get_pain_penalty();
1273         mod_str_bonus( -ppen.strength );
1274         mod_dex_bonus( -ppen.dexterity );
1275         mod_int_bonus( -ppen.intelligence );
1276         mod_per_bonus( -ppen.perception );
1277         if( ppen.dexterity > 0 ) {
1278             add_miss_reason( _( "Your pain distracts you!" ), static_cast<unsigned>( ppen.dexterity ) );
1279         }
1280     }
1281 
1282     // Radiation
1283     set_fake_effect_dur( effect_irradiated, 1_turns * get_rad() );
1284     // Morale
1285     const int morale = get_morale_level();
1286     set_fake_effect_dur( effect_happy, 1_turns * morale );
1287     set_fake_effect_dur( effect_sad, 1_turns * -morale );
1288 
1289     // Stimulants
1290     set_fake_effect_dur( effect_stim, 1_turns * current_stim );
1291     set_fake_effect_dur( effect_depressants, 1_turns * -current_stim );
1292     if( has_trait( trait_STIMBOOST ) ) {
1293         set_fake_effect_dur( effect_stim_overdose, 1_turns * ( current_stim - 60 ) );
1294     } else {
1295         set_fake_effect_dur( effect_stim_overdose, 1_turns * ( current_stim - 30 ) );
1296     }
1297     // Starvation
1298     const float bmi = get_bmi();
1299     if( bmi < character_weight_category::underweight ) {
1300         const int str_penalty = std::floor( ( 1.0f - ( bmi - 13.0f ) / 3.0f ) * get_str_base() );
1301         add_miss_reason( _( "You're weak from hunger." ),
1302                          static_cast<unsigned>( ( get_starvation() + 300 ) / 1000 ) );
1303         mod_str_bonus( -str_penalty );
1304         mod_dex_bonus( -( str_penalty / 2 ) );
1305         mod_int_bonus( -( str_penalty / 2 ) );
1306     }
1307     // Thirst
1308     if( get_thirst() >= 200 ) {
1309         // We die at 1200
1310         const int dex_mod = -get_thirst() / 200;
1311         add_miss_reason( _( "You're weak from thirst." ), static_cast<unsigned>( -dex_mod ) );
1312         mod_str_bonus( -get_thirst() / 200 );
1313         mod_dex_bonus( dex_mod );
1314         mod_int_bonus( -get_thirst() / 200 );
1315         mod_per_bonus( -get_thirst() / 200 );
1316     }
1317     if( get_sleep_deprivation() >= SLEEP_DEPRIVATION_HARMLESS ) {
1318         set_fake_effect_dur( effect_sleep_deprived, 1_turns * get_sleep_deprivation() );
1319     } else if( has_effect( effect_sleep_deprived ) ) {
1320         remove_effect( effect_sleep_deprived );
1321     }
1322 
1323     // Dodge-related effects
1324     mod_dodge_bonus( mabuff_dodge_bonus() -
1325                      ( encumb( bodypart_id( "leg_l" ) ) + encumb( bodypart_id( "leg_r" ) ) ) / 20.0f - encumb(
1326                          bodypart_id( "torso" ) ) / 10.0f );
1327     // Whiskers don't work so well if they're covered
1328     if( has_trait( trait_WHISKERS ) && !natural_attack_restricted_on( bodypart_id( "mouth" ) ) ) {
1329         mod_dodge_bonus( 1 );
1330     }
1331     if( has_trait( trait_WHISKERS_RAT ) && !natural_attack_restricted_on( bodypart_id( "mouth" ) ) ) {
1332         mod_dodge_bonus( 2 );
1333     }
1334     // depending on mounts size, attacks will hit the mount and use their dodge rating.
1335     // if they hit the player, the player cannot dodge as effectively.
1336     if( is_mounted() ) {
1337         mod_dodge_bonus( -4 );
1338     }
1339     // Spider hair is basically a full-body set of whiskers, once you get the brain for it
1340     if( has_trait( trait_CHITIN_FUR3 ) ) {
1341         static const bodypart_str_id parts[] {
1342             body_part_head, body_part_arm_r, body_part_arm_l,
1343             body_part_leg_r, body_part_leg_l
1344         };
1345         for( const bodypart_str_id &bp : parts ) {
1346             if( !wearing_something_on( bp ) ) {
1347                 mod_dodge_bonus( +1 );
1348             }
1349         }
1350         // Torso handled separately, bigger bonus
1351         if( !wearing_something_on( bodypart_id( "torso" ) ) ) {
1352             mod_dodge_bonus( 4 );
1353         }
1354     }
1355 
1356     // Apply static martial arts buffs
1357     martial_arts_data->ma_static_effects( *this );
1358 
1359     if( calendar::once_every( 1_minutes ) ) {
1360         update_mental_focus();
1361     }
1362 
1363     // Effects
1364     for( const auto &maps : *effects ) {
1365         for( const auto &i : maps.second ) {
1366             const auto &it = i.second;
1367             bool reduced = resists_effect( it );
1368             mod_str_bonus( it.get_mod( "STR", reduced ) );
1369             mod_dex_bonus( it.get_mod( "DEX", reduced ) );
1370             mod_per_bonus( it.get_mod( "PER", reduced ) );
1371             mod_int_bonus( it.get_mod( "INT", reduced ) );
1372         }
1373     }
1374 
1375     Character::reset_stats();
1376 
1377     recalc_sight_limits();
1378     recalc_speed_bonus();
1379 
1380 }
1381 
get_str_base() const1382 int avatar::get_str_base() const
1383 {
1384     return Character::get_str_base() + std::max( 0, str_upgrade );
1385 }
1386 
get_dex_base() const1387 int avatar::get_dex_base() const
1388 {
1389     return Character::get_dex_base() + std::max( 0, dex_upgrade );
1390 }
1391 
get_int_base() const1392 int avatar::get_int_base() const
1393 {
1394     return Character::get_int_base() + std::max( 0, int_upgrade );
1395 }
1396 
get_per_base() const1397 int avatar::get_per_base() const
1398 {
1399     return Character::get_per_base() + std::max( 0, per_upgrade );
1400 }
1401 
kill_xp() const1402 int avatar::kill_xp() const
1403 {
1404     return g->get_kill_tracker().kill_xp();
1405 }
1406 
1407 // based on  D&D 5e level progression
1408 static const std::array<int, 20> xp_cutoffs = { {
1409         300, 900, 2700, 6500, 14000,
1410         23000, 34000, 48000, 64000, 85000,
1411         100000, 120000, 140000, 165000, 195000,
1412         225000, 265000, 305000, 355000, 405000
1413     }
1414 };
1415 
free_upgrade_points() const1416 int avatar::free_upgrade_points() const
1417 {
1418     const int xp = kill_xp();
1419     int lvl = 0;
1420     for( const int &xp_lvl : xp_cutoffs ) {
1421         if( xp >= xp_lvl ) {
1422             lvl++;
1423         } else {
1424             break;
1425         }
1426     }
1427     return lvl - str_upgrade - dex_upgrade - int_upgrade - per_upgrade;
1428 }
1429 
upgrade_stat_prompt(const character_stat & stat)1430 void avatar::upgrade_stat_prompt( const character_stat &stat )
1431 {
1432     const int free_points = free_upgrade_points();
1433 
1434     if( free_points <= 0 ) {
1435         std::array<int, 20>::const_iterator xp_next_level = std::lower_bound( xp_cutoffs.begin(),
1436                 xp_cutoffs.end(), kill_xp() );
1437         if( xp_next_level == xp_cutoffs.end() ) {
1438             popup( _( "You've already reached maximum level." ) );
1439         } else {
1440             popup( _( "Needs %d more experience to gain next level." ),
1441                    *xp_next_level - kill_xp() );
1442         }
1443         return;
1444     }
1445 
1446     std::string stat_string;
1447     switch( stat ) {
1448         case character_stat::STRENGTH:
1449             stat_string = _( "strength" );
1450             break;
1451         case character_stat::DEXTERITY:
1452             stat_string = _( "dexterity" );
1453             break;
1454         case character_stat::INTELLIGENCE:
1455             stat_string = _( "intelligence" );
1456             break;
1457         case character_stat::PERCEPTION:
1458             stat_string = _( "perception" );
1459             break;
1460         case character_stat::DUMMY_STAT:
1461             stat_string = _( "invalid stat" );
1462             debugmsg( "Tried to use invalid stat" );
1463             break;
1464         default:
1465             return;
1466     }
1467 
1468     if( query_yn( _( "Are you sure you want to raise %s?  %d points available." ), stat_string,
1469                   free_points ) ) {
1470         switch( stat ) {
1471             case character_stat::STRENGTH:
1472                 str_upgrade++;
1473                 break;
1474             case character_stat::DEXTERITY:
1475                 dex_upgrade++;
1476                 break;
1477             case character_stat::INTELLIGENCE:
1478                 int_upgrade++;
1479                 break;
1480             case character_stat::PERCEPTION:
1481                 per_upgrade++;
1482                 break;
1483             case character_stat::DUMMY_STAT:
1484                 debugmsg( "Tried to use invalid stat" );
1485                 break;
1486         }
1487     }
1488     recalc_hp();
1489 }
1490 
get_faction() const1491 faction *avatar::get_faction() const
1492 {
1493     return g->faction_manager_ptr->get( faction_id( "your_followers" ) );
1494 }
1495 
set_movement_mode(const move_mode_id & new_mode)1496 void avatar::set_movement_mode( const move_mode_id &new_mode )
1497 {
1498     if( can_switch_to( new_mode ) ) {
1499         if( is_hauling() && new_mode->stop_hauling() ) {
1500             stop_hauling();
1501         }
1502         add_msg( new_mode->change_message( true, get_steed_type() ) );
1503         move_mode = new_mode;
1504         // crouching affects visibility
1505         get_map().set_seen_cache_dirty( pos().z );
1506     } else {
1507         add_msg( new_mode->change_message( false, get_steed_type() ) );
1508     }
1509 }
1510 
toggle_run_mode()1511 void avatar::toggle_run_mode()
1512 {
1513     if( is_running() ) {
1514         set_movement_mode( move_mode_id( "walk" ) );
1515     } else {
1516         set_movement_mode( move_mode_id( "run" ) );
1517     }
1518 }
1519 
toggle_crouch_mode()1520 void avatar::toggle_crouch_mode()
1521 {
1522     if( is_crouching() ) {
1523         set_movement_mode( move_mode_id( "walk" ) );
1524     } else {
1525         set_movement_mode( move_mode_id( "crouch" ) );
1526     }
1527 }
1528 
reset_move_mode()1529 void avatar::reset_move_mode()
1530 {
1531     if( !is_walking() ) {
1532         set_movement_mode( move_mode_id( "walk" ) );
1533     }
1534 }
1535 
cycle_move_mode()1536 void avatar::cycle_move_mode()
1537 {
1538     const move_mode_id next = current_movement_mode()->cycle();
1539     set_movement_mode( next );
1540     // if a movemode is disabled then just cycle to the next one
1541     if( !movement_mode_is( next ) ) {
1542         set_movement_mode( next->cycle() );
1543     }
1544 }
1545 
wield(item_location target)1546 bool avatar::wield( item_location target )
1547 {
1548     return wield( *target, target.obtain_cost( *this ) );
1549 }
1550 
wield(item & target)1551 bool avatar::wield( item &target )
1552 {
1553     invalidate_inventory_validity_cache();
1554     return wield( target,
1555                   item_handling_cost( target, true,
1556                                       is_worn( target ) ? INVENTORY_HANDLING_PENALTY / 2 :
1557                                       INVENTORY_HANDLING_PENALTY ) );
1558 }
1559 
wield(item & target,const int obtain_cost)1560 bool avatar::wield( item &target, const int obtain_cost )
1561 {
1562     if( is_wielding( target ) ) {
1563         return true;
1564     }
1565 
1566     if( weapon.has_item( target ) ) {
1567         add_msg( m_info, _( "You need to put the bag away before trying to wield something from it." ) );
1568         return false;
1569     }
1570 
1571     if( !can_wield( target ).success() ) {
1572         return false;
1573     }
1574 
1575     bool combine_stacks = target.can_combine( weapon );
1576     if( !combine_stacks && !unwield() ) {
1577         return false;
1578     }
1579     cached_info.erase( "weapon_value" );
1580     if( target.is_null() ) {
1581         return true;
1582     }
1583 
1584     // Wielding from inventory is relatively slow and does not improve with increasing weapon skill.
1585     // Worn items (including guns with shoulder straps) are faster but still slower
1586     // than a skilled player with a holster.
1587     // There is an additional penalty when wielding items from the inventory whilst currently grabbed.
1588 
1589     bool worn = is_worn( target );
1590     const int mv = obtain_cost;
1591 
1592     if( worn ) {
1593         target.on_takeoff( *this );
1594     }
1595 
1596     add_msg_debug( "wielding took %d moves", mv );
1597     moves -= mv;
1598 
1599     if( has_item( target ) ) {
1600         item removed = i_rem( &target );
1601         if( combine_stacks ) {
1602             weapon.combine( removed );
1603         } else {
1604             weapon = removed;
1605         }
1606     } else {
1607         if( combine_stacks ) {
1608             weapon.combine( target );
1609         } else {
1610             weapon = target;
1611         }
1612     }
1613 
1614     last_item = weapon.typeId();
1615     recoil = MAX_RECOIL;
1616 
1617     weapon.on_wield( *this );
1618 
1619     get_event_bus().send<event_type::character_wields_item>( getID(), last_item );
1620 
1621     inv->update_invlet( weapon );
1622     inv->update_cache_with_item( weapon );
1623 
1624     return true;
1625 }
1626 
invoke_item(item * used,const tripoint & pt,int pre_obtain_moves)1627 bool avatar::invoke_item( item *used, const tripoint &pt, int pre_obtain_moves )
1628 {
1629     const std::map<std::string, use_function> &use_methods = used->type->use_methods;
1630     const int num_methods = use_methods.size();
1631 
1632     const bool has_relic = used->has_relic_activation();
1633     if( use_methods.empty() && !has_relic ) {
1634         return false;
1635     } else if( num_methods == 1 && !has_relic ) {
1636         return invoke_item( used, use_methods.begin()->first, pt, pre_obtain_moves );
1637     } else if( num_methods == 0 && has_relic ) {
1638         return used->use_relic( *this, pt );
1639     }
1640 
1641     uilist umenu;
1642 
1643     umenu.text = string_format( _( "What to do with your %s?" ), used->tname() );
1644     umenu.hilight_disabled = true;
1645 
1646     for( const auto &e : use_methods ) {
1647         const auto res = e.second.can_call( *this, *used, false, pt );
1648         umenu.addentry_desc( MENU_AUTOASSIGN, res.success(), MENU_AUTOASSIGN, e.second.get_name(),
1649                              res.str() );
1650     }
1651     if( has_relic ) {
1652         umenu.addentry_desc( MENU_AUTOASSIGN, true, MENU_AUTOASSIGN, _( "Use relic" ),
1653                              _( "Activate this relic." ) );
1654     }
1655 
1656     umenu.desc_enabled = std::any_of( umenu.entries.begin(),
1657     umenu.entries.end(), []( const uilist_entry & elem ) {
1658         return !elem.desc.empty();
1659     } );
1660 
1661     umenu.query();
1662 
1663     int choice = umenu.ret;
1664     // Use the relic
1665     if( choice == num_methods ) {
1666         return used->use_relic( *this, pt );
1667     }
1668     if( choice < 0 || choice >= num_methods ) {
1669         return false;
1670     }
1671 
1672     const std::string &method = std::next( use_methods.begin(), choice )->first;
1673 
1674     return invoke_item( used, method, pt, pre_obtain_moves );
1675 }
1676 
invoke_item(item * used)1677 bool avatar::invoke_item( item *used )
1678 {
1679     return Character::invoke_item( used );
1680 }
1681 
invoke_item(item * used,const std::string & method,const tripoint & pt,int pre_obtain_moves)1682 bool avatar::invoke_item( item *used, const std::string &method, const tripoint &pt,
1683                           int pre_obtain_moves )
1684 {
1685     if( pre_obtain_moves == -1 ) {
1686         pre_obtain_moves = moves;
1687     }
1688     return Character::invoke_item( used, method, pt, pre_obtain_moves );
1689 }
1690 
invoke_item(item * used,const std::string & method)1691 bool avatar::invoke_item( item *used, const std::string &method )
1692 {
1693     return Character::invoke_item( used, method );
1694 }
1695 
advance_daily_calories()1696 void avatar::advance_daily_calories()
1697 {
1698     calorie_diary.push_front( daily_calories{} );
1699     if( calorie_diary.size() > 30 ) {
1700         calorie_diary.pop_back();
1701     }
1702 }
1703 
add_spent_calories(int cal)1704 void avatar::add_spent_calories( int cal )
1705 {
1706     calorie_diary.front().spent += cal;
1707 }
1708 
add_gained_calories(int cal)1709 void avatar::add_gained_calories( int cal )
1710 {
1711     calorie_diary.front().gained += cal;
1712 }
1713 
log_activity_level(float level)1714 void avatar::log_activity_level( float level )
1715 {
1716     calorie_diary.front().activity_levels[level]++;
1717 }
1718 
save_activity(JsonOut & json) const1719 void avatar::daily_calories::save_activity( JsonOut &json ) const
1720 {
1721     json.member( "activity" );
1722     json.start_array();
1723     for( const std::pair<const float, int> &level : activity_levels ) {
1724         if( level.second > 0 ) {
1725             json.start_array();
1726             json.write( level.first );
1727             json.write( level.second );
1728             json.end_array();
1729         }
1730     }
1731     json.end_array();
1732 }
1733 
read_activity(JsonObject & data)1734 void avatar::daily_calories::read_activity( JsonObject &data )
1735 {
1736     if( data.has_array( "activity" ) ) {
1737         double act_level;
1738         for( JsonArray ja : data.get_array( "activity" ) ) {
1739             act_level = ja.next_float();
1740             activity_levels[ act_level ] = ja.next_int();
1741         }
1742         return;
1743     }
1744     // Fallback to legacy format for backward compatibility
1745     JsonObject jo = data.get_object( "activity" );
1746     for( const std::pair<const std::string, float> &member : activity_levels_map ) {
1747         int times;
1748         jo.read( member.first, times );
1749         activity_levels.at( member.second ) = times;
1750     }
1751 }
1752 
total_daily_calories_string() const1753 std::string avatar::total_daily_calories_string() const
1754 {
1755     const std::string header_string =
1756         colorize( "       Minutes at each exercise level            Calories per day", c_white ) + "\n" +
1757         colorize( "  Day  None Light Moderate Brisk Active Extra    Gained  Spent  Total",
1758                   c_yellow ) + "\n";
1759     const std::string format_string =
1760         " %4d  %4d  %4d     %4d  %4d   %4d  %4d    %6d %6d";
1761 
1762     const float light_ex_thresh = ( NO_EXERCISE + LIGHT_EXERCISE ) / 2.0f;
1763     const float mod_ex_thresh = ( LIGHT_EXERCISE + MODERATE_EXERCISE ) / 2.0f;
1764     const float brisk_ex_thresh = ( MODERATE_EXERCISE + BRISK_EXERCISE ) / 2.0f;
1765     const float active_ex_thresh = ( BRISK_EXERCISE + ACTIVE_EXERCISE ) / 2.0f;
1766     const float extra_ex_thresh = ( ACTIVE_EXERCISE + EXTRA_EXERCISE ) / 2.0f;
1767 
1768     std::string ret = header_string;
1769 
1770     // Start with today in the first row, day number from start of cataclysm
1771     int today = day_of_season<int>( calendar::turn ) + 1;
1772     int day_offset = 0;
1773     for( const daily_calories &day : calorie_diary ) {
1774         // Yes, this is clunky.
1775         // Perhaps it should be done in log_activity_level? But that's called a lot more often.
1776         int no_exercise = 0;
1777         int light_exercise = 0;
1778         int moderate_exercise = 0;
1779         int brisk_exercise = 0;
1780         int active_exercise = 0;
1781         int extra_exercise = 0;
1782         for( const std::pair<const float, int> &level : day.activity_levels ) {
1783             if( level.second > 0 ) {
1784                 if( level.first < light_ex_thresh ) {
1785                     no_exercise += level.second;
1786                 } else if( level.first < mod_ex_thresh ) {
1787                     light_exercise += level.second;
1788                 } else if( level.first < brisk_ex_thresh ) {
1789                     moderate_exercise += level.second;
1790                 } else if( level.first < active_ex_thresh ) {
1791                     brisk_exercise += level.second;
1792                 } else if( level.first < extra_ex_thresh ) {
1793                     active_exercise += level.second;
1794                 } else {
1795                     extra_exercise += level.second;
1796                 }
1797             }
1798         }
1799         std::string row_data = string_format( format_string, today + day_offset--,
1800                                               5 * no_exercise,
1801                                               5 * light_exercise,
1802                                               5 * moderate_exercise,
1803                                               5 * brisk_exercise,
1804                                               5 * active_exercise,
1805                                               5 * extra_exercise,
1806                                               day.gained, day.spent );
1807         // Alternate gray and white text for row data
1808         if( day_offset % 2 == 0 ) {
1809             ret += colorize( row_data, c_white );
1810         } else {
1811             ret += colorize( row_data, c_light_gray );
1812         }
1813 
1814         // Color-code each day's net calories
1815         std::string total_kcals = string_format( " %6d", day.total() );
1816         if( day.total() > 4000 ) {
1817             ret += colorize( total_kcals, c_light_cyan );
1818         } else if( day.total() > 2000 ) {
1819             ret += colorize( total_kcals, c_cyan );
1820         } else if( day.total() > 250 ) {
1821             ret += colorize( total_kcals, c_light_blue );
1822         } else if( day.total() < -4000 ) {
1823             ret += colorize( total_kcals, c_pink );
1824         } else if( day.total() < -2000 ) {
1825             ret += colorize( total_kcals, c_red );
1826         } else if( day.total() < -250 ) {
1827             ret += colorize( total_kcals, c_light_red );
1828         } else {
1829             ret += colorize( total_kcals, c_light_gray );
1830         }
1831         ret += "\n";
1832     }
1833     return ret;
1834 }
1835 
get_talker_for(avatar & me)1836 std::unique_ptr<talker> get_talker_for( avatar &me )
1837 {
1838     return std::make_unique<talker_avatar>( &me );
1839 }
get_talker_for(avatar * me)1840 std::unique_ptr<talker> get_talker_for( avatar *me )
1841 {
1842     return std::make_unique<talker_avatar>( me );
1843 }
1844 
points_left()1845 points_left::points_left()
1846 {
1847     limit = MULTI_POOL;
1848     init_from_options();
1849 }
1850 
init_from_options()1851 void points_left::init_from_options()
1852 {
1853     stat_points = get_option<int>( "INITIAL_STAT_POINTS" );
1854     trait_points = get_option<int>( "INITIAL_TRAIT_POINTS" );
1855     skill_points = get_option<int>( "INITIAL_SKILL_POINTS" );
1856 }
1857 
1858 // Highest amount of points to spend on stats without points going invalid
stat_points_left() const1859 int points_left::stat_points_left() const
1860 {
1861     switch( limit ) {
1862         case FREEFORM:
1863         case ONE_POOL:
1864             return stat_points + trait_points + skill_points;
1865         case MULTI_POOL:
1866             return std::min( trait_points_left(),
1867                              stat_points + std::min( 0, trait_points + skill_points ) );
1868         case TRANSFER:
1869             return 0;
1870     }
1871 
1872     return 0;
1873 }
1874 
trait_points_left() const1875 int points_left::trait_points_left() const
1876 {
1877     switch( limit ) {
1878         case FREEFORM:
1879         case ONE_POOL:
1880             return stat_points + trait_points + skill_points;
1881         case MULTI_POOL:
1882             return stat_points + trait_points + std::min( 0, skill_points );
1883         case TRANSFER:
1884             return 0;
1885     }
1886 
1887     return 0;
1888 }
1889 
skill_points_left() const1890 int points_left::skill_points_left() const
1891 {
1892     return stat_points + trait_points + skill_points;
1893 }
1894 
is_freeform()1895 bool points_left::is_freeform()
1896 {
1897     return limit == FREEFORM;
1898 }
1899 
is_valid()1900 bool points_left::is_valid()
1901 {
1902     return is_freeform() ||
1903            ( stat_points_left() >= 0 && trait_points_left() >= 0 &&
1904              skill_points_left() >= 0 );
1905 }
1906 
has_spare()1907 bool points_left::has_spare()
1908 {
1909     return !is_freeform() && is_valid() && skill_points_left() > 0;
1910 }
1911 
to_string()1912 std::string points_left::to_string()
1913 {
1914     if( limit == MULTI_POOL ) {
1915         return string_format(
1916                    _( "Points left: <color_%s>%d</color>%c<color_%s>%d</color>%c<color_%s>%d</color>=<color_%s>%d</color>" ),
1917                    stat_points_left() >= 0 ? "light_gray" : "red", stat_points,
1918                    trait_points >= 0 ? '+' : '-',
1919                    trait_points_left() >= 0 ? "light_gray" : "red", std::abs( trait_points ),
1920                    skill_points >= 0 ? '+' : '-',
1921                    skill_points_left() >= 0 ? "light_gray" : "red", std::abs( skill_points ),
1922                    is_valid() ? "light_gray" : "red", stat_points + trait_points + skill_points );
1923     } else if( limit == ONE_POOL ) {
1924         return string_format( _( "Points left: %4d" ), skill_points_left() );
1925     } else if( limit == TRANSFER ) {
1926         return _( "Character Transfer: No changes can be made." );
1927     } else {
1928         return _( "Freeform" );
1929     }
1930 }
1931