1 #include "player.h" // IWYU pragma: associated
2
3 #include <algorithm>
4 #include <array>
5 #include <cmath>
6 #include <cstddef>
7 #include <cstdlib>
8 #include <functional>
9 #include <memory>
10 #include <string>
11
12 #include "addiction.h"
13 #include "avatar.h"
14 #include "bionics.h"
15 #include "bodypart.h"
16 #include "calendar.h"
17 #include "cata_utility.h"
18 #include "catacharset.h"
19 #include "color.h"
20 #include "cursesdef.h"
21 #include "debug.h"
22 #include "effect.h"
23 #include "enum_conversions.h"
24 #include "game.h"
25 #include "input.h"
26 #include "mutation.h"
27 #include "options.h"
28 #include "output.h"
29 #include "pimpl.h"
30 #include "pldata.h"
31 #include "profession.h"
32 #include "proficiency.h"
33 #include "skill.h"
34 #include "string_formatter.h"
35 #include "string_input_popup.h"
36 #include "translations.h"
37 #include "ui_manager.h"
38 #include "units.h"
39 #include "units_utility.h"
40 #include "weather.h"
41 #include "weather_type.h"
42
43 static const skill_id skill_swimming( "swimming" );
44
45 static const std::string title_STATS = translate_marker( "STATS" );
46 static const std::string title_ENCUMB = translate_marker( "ENCUMBRANCE AND WARMTH" );
47 static const std::string title_EFFECTS = translate_marker( "EFFECTS" );
48 static const std::string title_SPEED = translate_marker( "SPEED" );
49 static const std::string title_SKILLS = translate_marker( "SKILLS" );
50 static const std::string title_BIONICS = translate_marker( "BIONICS" );
51 static const std::string title_TRAITS = translate_marker( "TRAITS" );
52 static const std::string title_PROFICIENCIES = translate_marker( "PROFICIENCIES" );
53
54 // use this instead of having to type out 26 spaces like before
55 static const std::string header_spaces( 26, ' ' );
56 static const unsigned int grid_width = 26;
57
58 // Rescale temperature value to one that the player sees
temperature_print_rescaling(int temp)59 static int temperature_print_rescaling( int temp )
60 {
61 return ( temp / 100.0 ) * 2 - 100;
62 }
63
should_combine_bps(const player & p,const bodypart_id & l,const bodypart_id & r,const item * selected_clothing)64 static bool should_combine_bps( const player &p, const bodypart_id &l, const bodypart_id &r,
65 const item *selected_clothing )
66 {
67 const encumbrance_data &enc_l = p.get_part_encumbrance_data( l );
68 const encumbrance_data &enc_r = p.get_part_encumbrance_data( r );
69
70 return l != r && // are different parts
71 l == r->opposite_part && r == l->opposite_part && // are complementary parts
72 // same encumbrance & temperature
73 enc_l == enc_r &&
74 temperature_print_rescaling( p.get_part_temp_conv( l ) ) == temperature_print_rescaling(
75 p.get_part_temp_conv( r ) ) &&
76 // selected_clothing covers both or neither parts
77 ( !selected_clothing || ( selected_clothing->covers( l ) == selected_clothing->covers( r ) ) );
78
79 }
80
list_and_combine_bps(const player & p,const item * selected_clothing)81 static std::vector<std::pair<bodypart_id, bool>> list_and_combine_bps( const player &p,
82 const item *selected_clothing )
83 {
84 // bool represents whether the part has been combined with its other half
85 std::vector<std::pair<bodypart_id, bool>> bps;
86 for( const bodypart_id &bp : p.get_all_body_parts( get_body_part_flags::sorted ) ) {
87 // assuming that a body part has at most one other half
88 if( should_combine_bps( p, bp, bp->opposite_part.id(), selected_clothing ) ) {
89 if( std::find( bps.begin(), bps.end(), std::pair<bodypart_id, bool>( bp->opposite_part.id(),
90 true ) ) == bps.end() ) {
91 // only add one
92 bps.emplace_back( bp, true );
93 }
94 } else {
95 bps.emplace_back( bp, false );
96 }
97 }
98 return bps;
99 }
100
print_encumbrance(const catacurses::window & win,const int line,const item * const selected_clothing) const101 void player::print_encumbrance( const catacurses::window &win, const int line,
102 const item *const selected_clothing ) const
103 {
104 // bool represents whether the part has been combined with its other half
105 const std::vector<std::pair<bodypart_id, bool>> bps = list_and_combine_bps( *this,
106 selected_clothing );
107
108 // width/height excluding title & scrollbar
109 const int height = getmaxy( win ) - 1;
110 bool draw_scrollbar = height < static_cast<int>( bps.size() );
111 const int width = getmaxx( win ) - ( draw_scrollbar ? 1 : 0 );
112 // index of the first printed bodypart from `bps`
113 const int firstline = clamp( line - height / 2, 0, std::max( 0,
114 static_cast<int>( bps.size() ) - height ) );
115
116 /*** I chose to instead only display X+Y instead of X+Y=Z. More room was needed ***
117 *** for displaying triple digit encumbrance, due to new encumbrance system. ***
118 *** If the player wants to see the total without having to do them maths, the ***
119 *** armor layers ui shows everything they want :-) -Davek ***/
120 for( int i = 0; i < height; ++i ) {
121 int thisline = firstline + i;
122 if( thisline < 0 ) {
123 continue;
124 }
125 if( static_cast<size_t>( thisline ) >= bps.size() ) {
126 break;
127 }
128
129 const bodypart_id &bp = bps[thisline].first;
130 const bool combine = bps[thisline].second;
131 const encumbrance_data &e = get_part_encumbrance_data( bp );
132
133 const bool highlighted = selected_clothing ? selected_clothing->covers( bp ) : false;
134 std::string out = body_part_name_as_heading( bp, combine ? 2 : 1 );
135 if( utf8_width( out ) > 7 ) {
136 out = utf8_truncate( out, 7 );
137 }
138
139 // Two different highlighting schemes, highlight if the line is selected as per line being set.
140 // Make the text green if this part is covered by the passed in item.
141 nc_color limb_color = thisline == line ?
142 ( highlighted ? h_green : h_light_gray ) :
143 ( highlighted ? c_green : c_light_gray );
144 mvwprintz( win, point( 1, 1 + i ), limb_color, "%s", out );
145 // accumulated encumbrance from clothing, plus extra encumbrance from layering
146 mvwprintz( win, point( 8, 1 + i ), encumb_color( e.encumbrance ), "%3d",
147 e.encumbrance - e.layer_penalty );
148 // separator in low toned color
149 mvwprintz( win, point( 11, 1 + i ), c_light_gray, "+" );
150 // take into account the new encumbrance system for layers
151 mvwprintz( win, point( 12, 1 + i ), encumb_color( e.encumbrance ), "%-3d", e.layer_penalty );
152 // print warmth, tethered to right hand side of the window
153 mvwprintz( win, point( width - 6, 1 + i ), bodytemp_color( bp ), "(% 3d)",
154 temperature_print_rescaling( get_part_temp_conv( bp ) ) );
155 }
156
157 if( draw_scrollbar ) {
158 scrollbar().
159 offset_x( width ).
160 offset_y( 1 ).
161 content_size( bps.size() ).
162 viewport_pos( firstline ).
163 viewport_size( height ).
164 scroll_to_last( false ).
165 apply( win );
166 }
167 }
168
swim_cost_text(int moves)169 static std::string swim_cost_text( int moves )
170 {
171 return string_format( _( "Swimming movement point cost: <color_white>%+d</color>\n" ), moves );
172 }
173
run_cost_text(int moves)174 static std::string run_cost_text( int moves )
175 {
176 return string_format( _( "Movement point cost: <color_white>%+d</color>\n" ), moves );
177 }
178
reload_cost_text(int moves)179 static std::string reload_cost_text( int moves )
180 {
181 return string_format( _( "Reloading movement point cost: <color_white>%+d</color>\n" ), moves );
182 }
183
melee_cost_text(int moves)184 static std::string melee_cost_text( int moves )
185 {
186 return string_format(
187 _( "Melee and thrown attack movement point cost: <color_white>%+d</color>\n" ), moves );
188 }
melee_stamina_cost_text(int cost)189 static std::string melee_stamina_cost_text( int cost )
190 {
191 return string_format( _( "Melee stamina cost: <color_white>%+d</color>\n" ), cost );
192 }
mouth_stamina_cost_text(int cost)193 static std::string mouth_stamina_cost_text( int cost )
194 {
195 return string_format( _( "Stamina Regeneration: <color_white>%+d</color>\n" ), cost );
196 }
ranged_cost_text(double disp)197 static std::string ranged_cost_text( double disp )
198 {
199 return string_format( _( "Dispersion when using ranged attacks: <color_white>%+.1f</color>\n" ),
200 disp );
201 }
dodge_skill_text(double mod)202 static std::string dodge_skill_text( double mod )
203 {
204 return string_format( _( "Dodge skill: <color_white>%+.1f</color>\n" ), mod );
205 }
206
get_encumbrance(const player & p,const bodypart_id & bp,bool combine)207 static int get_encumbrance( const player &p, const bodypart_id &bp, bool combine )
208 {
209 // Body parts that can't combine with anything shouldn't print double values on combine
210 // This shouldn't happen, but handle this, just in case
211 const bool combines_with_other = bp->opposite_part != bp.id();
212 return p.encumb( bp ) * ( ( combine && combines_with_other ) ? 2 : 1 );
213 }
214
get_encumbrance_description(const player & p,const bodypart_id & bp,bool combine)215 static std::string get_encumbrance_description( const player &p, const bodypart_id &bp,
216 bool combine )
217 {
218 std::string s;
219
220 const int eff_encumbrance = get_encumbrance( p, bp, combine );
221
222 switch( bp->token ) {
223 case bp_torso: {
224 const int melee_roll_pen = std::max( -eff_encumbrance, -80 );
225 s += string_format( _( "Melee attack rolls: <color_white>%+d%%</color>\n" ), melee_roll_pen );
226 s += dodge_skill_text( -( eff_encumbrance / 10.0 ) );
227 s += swim_cost_text( ( eff_encumbrance / 10.0 ) * ( 80 - p.get_skill_level(
228 skill_swimming ) * 3 ) );
229 s += melee_cost_text( eff_encumbrance );
230 break;
231 }
232 case bp_head:
233 s += _( "<color_magenta>Head encumbrance has no effect; it simply limits how much you can put on.</color>" );
234 break;
235 case bp_eyes:
236 s += string_format(
237 _( "Perception when checking traps or firing ranged weapons: <color_white>%+d</color>\n"
238 "Dispersion when throwing items: <color_white>%+d</color>" ),
239 -( eff_encumbrance / 10 ),
240 eff_encumbrance * 10 );
241 break;
242 case bp_mouth:
243 s += _( "<color_magenta>Covering your mouth will make it more difficult to breathe and catch your breath.</color>\n" );
244 s += mouth_stamina_cost_text( -( eff_encumbrance / 5 ) );
245 break;
246 case bp_arm_l:
247 case bp_arm_r:
248 s += _( "<color_magenta>Arm encumbrance affects stamina cost of melee attacks and accuracy with ranged weapons.</color>\n" );
249 s += melee_stamina_cost_text( eff_encumbrance );
250 s += ranged_cost_text( eff_encumbrance / 5.0 );
251 break;
252 case bp_hand_l:
253 case bp_hand_r:
254 s += _( "<color_magenta>Reduces the speed at which you can handle or manipulate items.</color>\n\n" );
255 s += reload_cost_text( ( eff_encumbrance / 10 ) * 15 );
256 s += string_format( _( "Dexterity when throwing items: <color_white>%+.1f</color>\n" ),
257 -( eff_encumbrance / 10.0f ) );
258 s += melee_cost_text( eff_encumbrance / 2 );
259 s += string_format( _( "Reduced gun aim speed: <color_white>%.1f</color>" ),
260 p.aim_speed_encumbrance_modifier() );
261 break;
262 case bp_leg_l:
263 case bp_leg_r:
264 s += run_cost_text( static_cast<int>( eff_encumbrance * 0.15 ) );
265 s += swim_cost_text( ( eff_encumbrance / 10 ) * ( 50 - p.get_skill_level(
266 skill_swimming ) * 2 ) / 2 );
267 s += dodge_skill_text( -eff_encumbrance / 10.0 / 4.0 );
268 break;
269 case bp_foot_l:
270 case bp_foot_r:
271 s += run_cost_text( static_cast<int>( eff_encumbrance * 0.25 ) );
272 break;
273 case num_bp:
274 break;
275 }
276
277 return s;
278 }
279
is_cqb_skill(const skill_id & id)280 static bool is_cqb_skill( const skill_id &id )
281 {
282 // TODO: this skill list here is used in other places as well. Useless redundancy and
283 // dependency. Maybe change it into a flag of the skill that indicates it's a skill used
284 // by the bionic?
285 static const std::array<skill_id, 5> cqb_skills = { {
286 skill_id( "melee" ), skill_id( "unarmed" ), skill_id( "cutting" ),
287 skill_id( "bashing" ), skill_id( "stabbing" ),
288 }
289 };
290 return std::find( cqb_skills.begin(), cqb_skills.end(), id ) != cqb_skills.end();
291 }
292
293 namespace
294 {
295 enum class player_display_tab : int {
296 stats,
297 encumbrance,
298 skills,
299 traits,
300 bionics,
301 effects,
302 proficiencies,
303 num_tabs,
304 };
305 } // namespace
306
next_tab(const player_display_tab tab)307 static player_display_tab next_tab( const player_display_tab tab )
308 {
309 if( static_cast<int>( tab ) + 1 < static_cast<int>( player_display_tab::num_tabs ) ) {
310 return static_cast<player_display_tab>( static_cast<int>( tab ) + 1 );
311 } else {
312 return static_cast<player_display_tab>( 0 );
313 }
314 }
315
prev_tab(const player_display_tab tab)316 static player_display_tab prev_tab( const player_display_tab tab )
317 {
318 if( static_cast<int>( tab ) > 0 ) {
319 return static_cast<player_display_tab>( static_cast<int>( tab ) - 1 );
320 } else {
321 return static_cast<player_display_tab>( static_cast<int>( player_display_tab::num_tabs ) - 1 );
322 }
323 }
324
draw_proficiencies_tab(const catacurses::window & win,const unsigned line,const Character & guy,const player_display_tab curtab)325 static void draw_proficiencies_tab( const catacurses::window &win, const unsigned line,
326 const Character &guy, const player_display_tab curtab )
327 {
328 werase( win );
329 const std::vector<display_proficiency> profs = guy.display_proficiencies();
330 bool focused = curtab == player_display_tab::proficiencies;
331 const nc_color title_color = focused ? h_light_gray : c_light_gray;
332 center_print( win, 0, title_color, _( title_PROFICIENCIES ) );
333 const int height = getmaxy( win ) - 1;
334 const int width = getmaxx( win ) - 1;
335 bool draw_scrollbar = profs.size() > static_cast<size_t>( height );
336 int y = 1;
337 int start = draw_scrollbar ? line : 0;
338 for( size_t i = start; i < profs.size(); ++i ) {
339 if( y > height ) {
340 break;
341 }
342 std::string name;
343 const display_proficiency &cur = profs[i];
344 if( !cur.known && cur.id->can_learn() ) {
345 static_assert( grid_width == 26, "Reminder to update formatting"
346 "for this string when grid width changes" );
347 name = string_format( "%s %2.0f%%",
348 left_justify( trim_by_length( cur.id->name(), width - 4 ), 21 ),
349 std::floor( cur.practice * 100 ) );
350 } else {
351 name = trim_by_length( cur.id->name(), width );
352 }
353 const nc_color col = focused && i == line ? hilite( cur.color ) : cur.color;
354 y += fold_and_print( win, point( 0, y ), width, col, name );
355 }
356
357 if( draw_scrollbar ) {
358 scrollbar()
359 .offset_x( width )
360 .offset_y( 1 )
361 .content_size( profs.size() )
362 .viewport_pos( line )
363 .viewport_size( height )
364 .scroll_to_last( false )
365 .apply( win );
366 }
367
368 wnoutrefresh( win );
369 }
370
draw_proficiencies_info(const catacurses::window & w_info,const unsigned line,const Character & guy)371 static void draw_proficiencies_info( const catacurses::window &w_info, const unsigned line,
372 const Character &guy )
373 {
374 werase( w_info );
375 const std::vector<display_proficiency> profs = guy.display_proficiencies();
376 if( line < profs.size() ) {
377 const display_proficiency &cur = profs[line];
378 std::string progress;
379 if( cur.known ) {
380 progress = _( "You know this proficiency." );
381 } else {
382 progress = string_format( _( "You are %2.1f%% of the way towards learning this proficiency." ),
383 cur.practice * 100 );
384 if( debug_mode ) {
385 progress += string_format( "\nYou have spent %s practicing this proficiency.",
386 to_string( cur.spent ) );
387 }
388 }
389 int y = 0;
390 y += fold_and_print( w_info, point( 1, y ), getmaxx( w_info ) - 1, cur.color, cur.id->name() );
391 y += fold_and_print( w_info, point( 1, y ), getmaxx( w_info ) - 1, c_cyan, progress );
392 fold_and_print( w_info, point( 1, y ), getmaxx( w_info ) - 1, c_white, cur.id->description() );
393 }
394 wnoutrefresh( w_info );
395 }
396
draw_stats_tab(const catacurses::window & w_stats,const player & you,const unsigned int line,const player_display_tab curtab)397 static void draw_stats_tab( const catacurses::window &w_stats,
398 const player &you, const unsigned int line, const player_display_tab curtab )
399 {
400 werase( w_stats );
401 const nc_color title_col = curtab == player_display_tab::stats ? h_light_gray : c_light_gray;
402 center_print( w_stats, 0, title_col, _( title_STATS ) );
403
404 const auto line_color = [curtab, line]( const unsigned int line_to_draw ) {
405 if( curtab == player_display_tab::stats && line == line_to_draw ) {
406 return h_light_gray;
407 } else {
408 return c_light_gray;
409 }
410 };
411
412 // Stats
413 const auto display_stat = [&w_stats]( const char *name, int cur, int max, int line_n,
414 const nc_color & col ) {
415 nc_color cstatus;
416 if( cur <= 0 ) {
417 cstatus = c_dark_gray;
418 } else if( cur < max / 2 ) {
419 cstatus = c_red;
420 } else if( cur < max ) {
421 cstatus = c_light_red;
422 } else if( cur == max ) {
423 cstatus = c_white;
424 } else if( cur < max * 1.5 ) {
425 cstatus = c_light_green;
426 } else {
427 cstatus = c_green;
428 }
429
430 mvwprintz( w_stats, point( 1, line_n ), col, name );
431 mvwprintz( w_stats, point( 18, line_n ), cstatus, "%2d", cur );
432 mvwprintz( w_stats, point( 21, line_n ), c_light_gray, "(%2d)", max );
433 };
434
435 display_stat( _( "Strength:" ), you.get_str(), you.get_str_base(), 1, line_color( 0 ) );
436 display_stat( _( "Dexterity:" ), you.get_dex(), you.get_dex_base(), 2, line_color( 1 ) );
437 display_stat( _( "Intelligence:" ), you.get_int(), you.get_int_base(), 3, line_color( 2 ) );
438 display_stat( _( "Perception:" ), you.get_per(), you.get_per_base(), 4, line_color( 3 ) );
439 mvwprintz( w_stats, point( 1, 5 ), line_color( 4 ), _( "Weight:" ) );
440 right_print( w_stats, 5, 1, c_light_gray, you.get_weight_string() );
441 mvwprintz( w_stats, point( 1, 6 ), line_color( 5 ), _( "Height:" ) );
442 mvwprintz( w_stats, point( 25 - utf8_width( you.height_string() ), 6 ), c_light_gray,
443 you.height_string() );
444 mvwprintz( w_stats, point( 1, 7 ), line_color( 6 ), _( "Age:" ) );
445 mvwprintz( w_stats, point( 25 - utf8_width( you.age_string() ), 7 ), c_light_gray,
446 you.age_string() );
447 mvwprintz( w_stats, point( 1, 8 ), line_color( 7 ), _( "Blood type:" ) );
448 mvwprintz( w_stats, point( 25 - utf8_width( io::enum_to_string( you.my_blood_type ) +
449 ( you.blood_rh_factor ? "+" : "-" ) ), 8 ),
450 c_light_gray,
451 io::enum_to_string( you.my_blood_type ) + ( you.blood_rh_factor ? "+" : "-" ) );
452
453 wnoutrefresh( w_stats );
454 }
455
draw_stats_info(const catacurses::window & w_info,const player & you,const unsigned int line)456 static void draw_stats_info( const catacurses::window &w_info,
457 const player &you, const unsigned int line )
458 {
459 werase( w_info );
460 nc_color col_temp = c_light_gray;
461
462 if( line == 0 ) {
463 // NOLINTNEXTLINE(cata-use-named-point-constants)
464 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
465 _( "Strength affects your melee damage, the amount of weight you can carry, your total HP, "
466 "your resistance to many diseases, and the effectiveness of actions which require brute force." ) );
467 print_colored_text( w_info, point( 1, 3 ), col_temp, c_light_gray,
468 string_format( _( "Base HP: <color_white>%d</color>" ),
469 you.get_part_hp_max( bodypart_id( "torso" ) ) ) );
470 print_colored_text( w_info, point( 1, 4 ), col_temp, c_light_gray,
471 string_format( _( "Carry weight (%s): <color_white>%.1f</color>" ), weight_units(),
472 convert_weight( you.weight_capacity() ) ) );
473 print_colored_text( w_info, point( 1, 5 ), col_temp, c_light_gray,
474 string_format( _( "Bash damage: <color_white>%.1f</color>" ), you.bonus_damage( false ) ) );
475 } else if( line == 1 ) {
476 // NOLINTNEXTLINE(cata-use-named-point-constants)
477 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
478 _( "Dexterity affects your chance to hit in melee combat, helps you steady your "
479 "gun for ranged combat, and enhances many actions that require finesse." ) );
480 print_colored_text( w_info, point( 1, 3 ), col_temp, c_light_gray,
481 string_format( _( "Melee to-hit bonus: <color_white>%+.1lf</color>" ), you.get_melee_hit_base() ) );
482 print_colored_text( w_info, point( 1, 4 ), col_temp, c_light_gray,
483 string_format( _( "Ranged penalty: <color_white>%+d</color>" ),
484 -std::abs( you.ranged_dex_mod() ) ) );
485 print_colored_text( w_info, point( 1, 5 ), col_temp, c_light_gray,
486 string_format( _( "Throwing penalty per target's dodge: <color_white>%+d</color>" ),
487 you.throw_dispersion_per_dodge( false ) ) );
488 } else if( line == 2 ) {
489 // NOLINTNEXTLINE(cata-use-named-point-constants)
490 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
491 _( "Intelligence is less important in most situations, but it is vital for more complex tasks like "
492 "electronics crafting. It also affects how much skill you can pick up from reading a book." ) );
493 if( you.rust_rate() ) {
494 print_colored_text( w_info, point( 1, 3 ), col_temp, c_light_gray,
495 string_format( _( "Skill rust delay: <color_white>%d%%</color>" ), you.rust_rate() ) );
496 }
497 print_colored_text( w_info, point( 1, 4 ), col_temp, c_light_gray,
498 string_format( _( "Read times: <color_white>%d%%</color>" ), you.read_speed( false ) ) );
499 print_colored_text( w_info, point( 1, 5 ), col_temp, c_light_gray,
500 string_format( _( "Crafting bonus: <color_white>%d%%</color>" ), you.get_int() ) );
501 } else if( line == 3 ) {
502 // NOLINTNEXTLINE(cata-use-named-point-constants)
503 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
504 _( "Perception is the most important stat for ranged combat. It's also used for "
505 "detecting traps and other things of interest." ) );
506 print_colored_text( w_info, point( 1, 4 ), col_temp, c_light_gray,
507 string_format( _( "Trap detection level: <color_white>%d</color>" ), you.get_per() ) );
508 if( you.ranged_per_mod() > 0 ) {
509 print_colored_text( w_info, point( 1, 5 ), col_temp, c_light_gray,
510 string_format( _( "Aiming penalty: <color_white>%+d</color>" ), -you.ranged_per_mod() ) );
511 }
512 } else if( line == 4 ) {
513 // NOLINTNEXTLINE(cata-use-named-point-constants)
514 const int lines = fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
515 _( "Your weight is a general indicator of how much fat your body has stored up,"
516 " which in turn shows how prepared you are to survive for a time without food."
517 " Having too much, or too little, can be unhealthy." ) );
518 fold_and_print( w_info, point( 1, 1 + lines ), FULL_SCREEN_WIDTH - 2, c_light_gray,
519 you.get_weight_long_description() );
520 } else if( line == 5 ) {
521 // NOLINTNEXTLINE(cata-use-named-point-constants)
522 const int lines = fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
523 _( "Your height. Simply how tall you are." ) );
524 fold_and_print( w_info, point( 1, 1 + lines ), FULL_SCREEN_WIDTH - 2, c_light_gray,
525 you.height_string() );
526 } else if( line == 6 ) {
527 // NOLINTNEXTLINE(cata-use-named-point-constants)
528 const int lines = fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
529 _( "This is how old you are." ) );
530 fold_and_print( w_info, point( 1, 1 + lines ), FULL_SCREEN_WIDTH - 2, c_light_gray,
531 you.age_string() );
532 } else if( line == 7 ) {
533 // NOLINTNEXTLINE(cata-use-named-point-constants)
534 const int lines = fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_magenta,
535 _( "This is your blood type and Rh factor." ) );
536 fold_and_print( w_info, point( 1, 1 + lines ), FULL_SCREEN_WIDTH - 2, c_light_gray,
537 string_format( _( "Blood type: %s" ), io::enum_to_string( you.my_blood_type ) ) );
538 fold_and_print( w_info, point( 1, 2 + lines ), FULL_SCREEN_WIDTH - 2, c_light_gray,
539 string_format( _( "Rh factor: %s" ),
540 you.blood_rh_factor ? _( "positive (+)" ) : _( "negative (-)" ) ) );
541 }
542 wnoutrefresh( w_info );
543 }
544
draw_encumbrance_tab(const catacurses::window & w_encumb,const player & you,const unsigned int line,const player_display_tab curtab)545 static void draw_encumbrance_tab( const catacurses::window &w_encumb,
546 const player &you, const unsigned int line, const player_display_tab curtab )
547 {
548 werase( w_encumb );
549 const bool is_current_tab = curtab == player_display_tab::encumbrance;
550 const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray;
551 center_print( w_encumb, 0, title_col, _( title_ENCUMB ) );
552 if( is_current_tab ) {
553 you.print_encumbrance( w_encumb, line );
554 } else {
555 you.print_encumbrance( w_encumb );
556 }
557 wnoutrefresh( w_encumb );
558 }
559
draw_encumbrance_info(const catacurses::window & w_info,const player & you,const unsigned int line)560 static void draw_encumbrance_info( const catacurses::window &w_info,
561 const player &you, const unsigned int line )
562 {
563 const std::vector<std::pair<bodypart_id, bool>> bps = list_and_combine_bps( you, nullptr );
564
565 werase( w_info );
566 bodypart_id bp;
567 bool combined_here = false;
568 if( line < bps.size() ) {
569 bp = bps[line].first;
570 combined_here = bps[line].second;
571 }
572 const std::string s = get_encumbrance_description( you, bp, combined_here );
573 // NOLINTNEXTLINE(cata-use-named-point-constants)
574 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_light_gray, s );
575 wnoutrefresh( w_info );
576 }
577
draw_traits_tab(const catacurses::window & w_traits,const unsigned int line,const player_display_tab curtab,const std::vector<trait_id> & traitslist,const size_t trait_win_size_y)578 static void draw_traits_tab( const catacurses::window &w_traits,
579 const unsigned int line, const player_display_tab curtab,
580 const std::vector<trait_id> &traitslist,
581 const size_t trait_win_size_y )
582 {
583 werase( w_traits );
584 const bool is_current_tab = curtab == player_display_tab::traits;
585 const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray;
586 center_print( w_traits, 0, title_col, _( title_TRAITS ) );
587
588 size_t min = 0;
589 size_t max = 0;
590
591 if( !is_current_tab || line <= ( trait_win_size_y - 2 ) / 2 ) {
592 min = 0;
593 max = trait_win_size_y - 1;
594 if( traitslist.size() < max ) {
595 max = traitslist.size();
596 }
597 } else if( line >= traitslist.size() - trait_win_size_y / 2 ) {
598 min = ( traitslist.size() < trait_win_size_y - 1 ? 0 : traitslist.size() - trait_win_size_y + 1 );
599 max = traitslist.size();
600 } else {
601 min = line - ( trait_win_size_y - 2 ) / 2;
602 max = line + ( trait_win_size_y + 1 ) / 2;
603 if( traitslist.size() < max ) {
604 max = traitslist.size();
605 }
606 }
607
608 for( size_t i = min; i < max; i++ ) {
609 const auto &mdata = traitslist[i].obj();
610 const nc_color color = mdata.get_display_color();
611 trim_and_print( w_traits, point( 1, static_cast<int>( 1 + i - min ) ), getmaxx( w_traits ) - 1,
612 is_current_tab && i == line ? hilite( color ) : color, mdata.name() );
613 }
614 wnoutrefresh( w_traits );
615 }
616
draw_traits_info(const catacurses::window & w_info,const unsigned int line,const std::vector<trait_id> & traitslist)617 static void draw_traits_info( const catacurses::window &w_info, const unsigned int line,
618 const std::vector<trait_id> &traitslist )
619 {
620 werase( w_info );
621 if( line < traitslist.size() ) {
622 const auto &mdata = traitslist[line].obj();
623 // NOLINTNEXTLINE(cata-use-named-point-constants)
624 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_light_gray, string_format(
625 "%s: %s", colorize( mdata.name(), mdata.get_display_color() ), traitslist[line]->desc() ) );
626 }
627 wnoutrefresh( w_info );
628 }
629
draw_bionics_tab(const catacurses::window & w_bionics,const player & you,const unsigned int line,const player_display_tab curtab,const std::vector<bionic> & bionicslist,const size_t bionics_win_size_y)630 static void draw_bionics_tab( const catacurses::window &w_bionics,
631 const player &you, const unsigned int line, const player_display_tab curtab,
632 const std::vector<bionic> &bionicslist, const size_t bionics_win_size_y )
633 {
634 werase( w_bionics );
635 const bool is_current_tab = curtab == player_display_tab::bionics;
636 const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray;
637 center_print( w_bionics, 0, title_col, _( title_BIONICS ) );
638 int power_amount;
639 std::string power_unit;
640 if( you.get_power_level() < 1_J ) {
641 power_amount = units::to_millijoule( you.get_power_level() );
642 power_unit = pgettext( "energy unit: millijoule", "mJ" );
643 } else if( you.get_power_level() < 1_kJ ) {
644 power_amount = units::to_joule( you.get_power_level() );
645 power_unit = pgettext( "energy unit: joule", "J" );
646 } else {
647 power_amount = units::to_kilojoule( you.get_power_level() );
648 power_unit = pgettext( "energy unit: kilojoule", "kJ" );
649 }
650 // NOLINTNEXTLINE(cata-use-named-point-constants)
651 trim_and_print( w_bionics, point( 1, 1 ), getmaxx( w_bionics ) - 1, c_white,
652 string_format( _( "Power: <color_light_blue>%1$d %2$s</color>"
653 " / <color_light_blue>%3$d kJ</color>" ),
654 power_amount, power_unit, units::to_kilojoule( you.get_max_power_level() ) ) );
655
656 const size_t useful_y = bionics_win_size_y - 2;
657 const size_t half_y = useful_y / 2;
658
659 size_t min = 0;
660 size_t max = 0;
661
662 if( !is_current_tab || line <= half_y ) { // near the top
663 min = 0;
664 max = std::min( bionicslist.size(), useful_y );
665 } else if( line >= bionicslist.size() - half_y ) { // near the bottom
666 min = ( bionicslist.size() <= useful_y ? 0 : bionicslist.size() - useful_y );
667 max = bionicslist.size();
668 } else { // scrolling
669 min = line - half_y;
670 max = std::min( bionicslist.size(), line + useful_y - half_y );
671 }
672
673 for( size_t i = min; i < max; i++ ) {
674 trim_and_print( w_bionics, point( 1, static_cast<int>( 2 + i - min ) ), getmaxx( w_bionics ) - 1,
675 is_current_tab && i == line ? hilite( c_white ) : c_white, "%s", bionicslist[i].info().name );
676 }
677 wnoutrefresh( w_bionics );
678 }
679
draw_bionics_info(const catacurses::window & w_info,const unsigned int line,const std::vector<bionic> & bionicslist)680 static void draw_bionics_info( const catacurses::window &w_info, const unsigned int line,
681 const std::vector<bionic> &bionicslist )
682 {
683 werase( w_info );
684 if( line < bionicslist.size() ) {
685 // NOLINTNEXTLINE(cata-use-named-point-constants)
686 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_light_gray, "%s",
687 bionicslist[line].info().description );
688 }
689 wnoutrefresh( w_info );
690 }
691
draw_effects_tab(const catacurses::window & w_effects,const unsigned int line,const player_display_tab curtab,const std::vector<std::pair<std::string,std::string>> & effect_name_and_text,const size_t effect_win_size_y)692 static void draw_effects_tab( const catacurses::window &w_effects,
693 const unsigned int line, const player_display_tab curtab,
694 const std::vector<std::pair<std::string, std::string>> &effect_name_and_text,
695 const size_t effect_win_size_y )
696 {
697 werase( w_effects );
698 const bool is_current_tab = curtab == player_display_tab::effects;
699 const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray;
700 center_print( w_effects, 0, title_col, _( title_EFFECTS ) );
701
702 const size_t half_y = ( effect_win_size_y - 1 ) / 2;
703
704 size_t min = 0;
705 size_t max = 0;
706
707 const size_t actual_size = effect_name_and_text.size();
708
709 if( !is_current_tab || line <= half_y ) {
710 min = 0;
711 max = effect_win_size_y - 1;
712 if( actual_size < max ) {
713 max = actual_size;
714 }
715 } else if( line >= actual_size - half_y ) {
716 min = ( actual_size < effect_win_size_y - 1 ? 0 : actual_size - effect_win_size_y + 1 );
717 max = actual_size;
718 } else {
719 min = line - half_y;
720 max = line - half_y + effect_win_size_y - 1;
721 if( actual_size < max ) {
722 max = actual_size;
723 }
724 }
725
726 for( size_t i = min; i < max; i++ ) {
727 trim_and_print( w_effects, point( 0, static_cast<int>( 1 + i - min ) ), getmaxx( w_effects ) - 1,
728 is_current_tab && i == line ? h_light_gray : c_light_gray, effect_name_and_text[i].first );
729 }
730 wnoutrefresh( w_effects );
731 }
732
draw_effects_info(const catacurses::window & w_info,const unsigned int line,const std::vector<std::pair<std::string,std::string>> & effect_name_and_text)733 static void draw_effects_info( const catacurses::window &w_info, const unsigned int line,
734 const std::vector<std::pair<std::string, std::string>> &effect_name_and_text )
735 {
736 werase( w_info );
737 const size_t actual_size = effect_name_and_text.size();
738 if( line < actual_size ) {
739 // NOLINTNEXTLINE(cata-use-named-point-constants)
740 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_light_gray,
741 effect_name_and_text[line].second );
742 }
743 wnoutrefresh( w_info );
744 }
745
746 struct HeaderSkill {
747 const Skill *skill;
748 bool is_header;
HeaderSkillHeaderSkill749 HeaderSkill( const Skill *skill, bool is_header ): skill( skill ), is_header( is_header ) {
750 }
751 };
752
draw_skills_tab(const catacurses::window & w_skills,player & you,unsigned int line,const player_display_tab curtab,std::vector<HeaderSkill> & skillslist,const size_t skill_win_size_y)753 static void draw_skills_tab( const catacurses::window &w_skills,
754 player &you, unsigned int line, const player_display_tab curtab,
755 std::vector<HeaderSkill> &skillslist,
756 const size_t skill_win_size_y )
757 {
758 const int col_width = 25;
759 if( line == 0 ) { //can't point to a header;
760 line = 1;
761 }
762
763 werase( w_skills );
764 const bool is_current_tab = curtab == player_display_tab::skills;
765 nc_color cstatus = is_current_tab ? h_light_gray : c_light_gray;
766 center_print( w_skills, 0, cstatus, _( title_SKILLS ) );
767
768 size_t min = 0;
769 size_t max = 0;
770
771 const size_t half_y = ( skill_win_size_y - 1 ) / 2;
772
773 if( !is_current_tab || line <= half_y ) {
774 min = 0;
775 } else if( line >= skillslist.size() - half_y ) {
776 min = ( skillslist.size() < skill_win_size_y - 1 ? 0 : skillslist.size() -
777 skill_win_size_y + 1 );
778 } else {
779 min = line - half_y;
780 }
781 max = std::min( min + skill_win_size_y - 1, skillslist.size() );
782
783 int y_pos = 1;
784 for( size_t i = min; i < max; i++, y_pos++ ) {
785 const Skill *aSkill = skillslist[i].skill;
786 const SkillLevel &level = you.get_skill_level_object( aSkill->ident() );
787
788 if( skillslist[i].is_header ) {
789 const SkillDisplayType t = SkillDisplayType::get_skill_type( aSkill->display_category() );
790 std::string type_name = t.display_string();
791 mvwprintz( w_skills, point( 0, y_pos ), c_light_gray, header_spaces );
792 center_print( w_skills, y_pos, c_yellow, type_name );
793 } else {
794 const bool can_train = level.can_train();
795 const bool training = level.isTraining();
796 const bool rusting = level.isRusting();
797 int exercise = level.exercise();
798 int level_num = level.level();
799 bool locked = false;
800 if( you.has_active_bionic( bionic_id( "bio_cqb" ) ) && is_cqb_skill( aSkill->ident() ) ) {
801 level_num = 5;
802 exercise = 0;
803 locked = true;
804 }
805 if( is_current_tab && i == line ) {
806 if( locked ) {
807 cstatus = h_yellow;
808 } else if( !can_train ) {
809 cstatus = rusting ? h_light_red : h_white;
810 } else if( exercise >= 100 ) {
811 cstatus = training ? h_pink : h_magenta;
812 } else if( rusting ) {
813 cstatus = training ? h_light_red : h_red;
814 } else {
815 cstatus = training ? h_light_blue : h_blue;
816 }
817 mvwprintz( w_skills, point( 1, y_pos ), cstatus, std::string( col_width, ' ' ) );
818 } else {
819 if( locked ) {
820 cstatus = c_yellow;
821 } else if( rusting ) {
822 cstatus = training ? c_light_red : c_red;
823 } else if( !can_train ) {
824 cstatus = c_white;
825 } else {
826 cstatus = training ? c_light_blue : c_blue;
827 }
828 mvwprintz( w_skills, point( 1, y_pos ), c_light_gray, std::string( col_width, ' ' ) );
829 }
830 mvwprintz( w_skills, point( 1, y_pos ), cstatus, "%s:", aSkill->name() );
831 if( aSkill->ident() == skill_id( "dodge" ) ) {
832 mvwprintz( w_skills, point( 14, y_pos ), cstatus, "%4.1f/%-2d(%2d%%)",
833 you.get_dodge(), level_num, exercise < 0 ? 0 : exercise );
834 } else {
835 mvwprintz( w_skills, point( 19, y_pos ), cstatus, "%-2d(%2d%%)",
836 level_num,
837 ( exercise < 0 ? 0 : exercise ) );
838 }
839 }
840 }
841
842 if( is_current_tab && skillslist.size() > skill_win_size_y - 1 ) {
843 draw_scrollbar( w_skills, line - 1, skill_win_size_y - 1, skillslist.size() - 1,
844 // NOLINTNEXTLINE(cata-use-named-point-constants)
845 point( 0, 1 ) );
846 }
847 wnoutrefresh( w_skills );
848 }
849
draw_skills_info(const catacurses::window & w_info,unsigned int line,const std::vector<HeaderSkill> & skillslist)850 static void draw_skills_info( const catacurses::window &w_info, unsigned int line,
851 const std::vector<HeaderSkill> &skillslist )
852 {
853 werase( w_info );
854 if( line < 1 ) {
855 line = 1;
856 }
857 const Skill *selectedSkill = nullptr;
858 if( line < skillslist.size() && !skillslist[line].is_header ) {
859 selectedSkill = skillslist[line].skill;
860 }
861
862 werase( w_info );
863
864 if( selectedSkill ) {
865 // NOLINTNEXTLINE(cata-use-named-point-constants)
866 fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_light_gray,
867 selectedSkill->description() );
868 }
869 wnoutrefresh( w_info );
870 }
871
draw_speed_tab(const catacurses::window & w_speed,const player & you,const std::map<std::string,int> & speed_effects)872 static void draw_speed_tab( const catacurses::window &w_speed,
873 const player &you,
874 const std::map<std::string, int> &speed_effects )
875 {
876 werase( w_speed );
877 // Finally, draw speed.
878 center_print( w_speed, 0, c_light_gray, _( title_SPEED ) );
879 // NOLINTNEXTLINE(cata-use-named-point-constants)
880 mvwprintz( w_speed, point( 1, 1 ), c_light_gray, _( "Base Move Cost:" ) );
881 mvwprintz( w_speed, point( 1, 2 ), c_light_gray, _( "Current Speed:" ) );
882 int newmoves = you.get_speed();
883 int pen = 0;
884 unsigned int line = 3;
885 if( you.weight_carried() > you.weight_capacity() ) {
886 pen = 25 * ( you.weight_carried() - you.weight_capacity() ) / ( you.weight_capacity() );
887 mvwprintz( w_speed, point( 1, line ), c_red,
888 pgettext( "speed penalty", "Overburdened -%2d%%" ), pen );
889 line++;
890 }
891 pen = you.get_pain_penalty().speed;
892 if( pen >= 1 ) {
893 mvwprintz( w_speed, point( 1, line ), c_red,
894 pgettext( "speed penalty", "Pain -%2d%%" ), pen );
895 line++;
896 }
897 if( you.get_thirst() > 40 ) {
898 pen = std::abs( player::thirst_speed_penalty( you.get_thirst() ) );
899 mvwprintz( w_speed, point( 1, line ), c_red,
900 pgettext( "speed penalty", "Thirst -%2d%%" ), pen );
901 line++;
902 }
903 if( you.kcal_speed_penalty() < 0 ) {
904 pen = std::abs( you.kcal_speed_penalty() );
905 const std::string inanition = you.get_bmi() < character_weight_category::underweight ?
906 _( "Starving" ) : _( "Underfed" );
907 //~ %s: Starving/Underfed (already left-justified), %2d: speed penalty
908 mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ),
909 left_justify( inanition, 20 ), pen );
910 line++;
911 }
912 if( you.has_trait( trait_id( "SUNLIGHT_DEPENDENT" ) ) && !g->is_in_sunlight( you.pos() ) ) {
913 pen = ( g->light_level( you.posz() ) >= 12 ? 5 : 10 );
914 mvwprintz( w_speed, point( 1, line ), c_red,
915 pgettext( "speed penalty", "Out of Sunlight -%2d%%" ), pen );
916 line++;
917 }
918
919 const float temperature_speed_modifier = you.mutation_value( "temperature_speed_modifier" );
920 if( temperature_speed_modifier != 0 ) {
921 nc_color pen_color;
922 std::string pen_sign;
923 const int player_local_temp = get_weather().get_temperature( you.pos() );
924 if( you.has_trait( trait_id( "COLDBLOOD4" ) ) && player_local_temp > 65 ) {
925 pen_color = c_green;
926 pen_sign = "+";
927 } else if( player_local_temp < 65 ) {
928 pen_color = c_red;
929 pen_sign = "-";
930 }
931 if( !pen_sign.empty() ) {
932 pen = ( player_local_temp - 65 ) * temperature_speed_modifier;
933 mvwprintz( w_speed, point( 1, line ), pen_color,
934 //~ %s: sign of bonus/penalty, %2d: speed bonus/penalty
935 pgettext( "speed modifier", "Cold-Blooded %s%2d%%" ), pen_sign, std::abs( pen ) );
936 line++;
937 }
938 }
939
940 int quick_bonus = static_cast<int>( newmoves - ( newmoves / 1.1 ) );
941 int bio_speed_bonus = quick_bonus;
942 if( you.has_trait( trait_id( "QUICK" ) ) && you.has_bionic( bionic_id( "bio_speed" ) ) ) {
943 bio_speed_bonus = static_cast<int>( newmoves / 1.1 - ( newmoves / 1.1 / 1.1 ) );
944 std::swap( quick_bonus, bio_speed_bonus );
945 }
946 if( you.has_trait( trait_id( "QUICK" ) ) ) {
947 mvwprintz( w_speed, point( 1, line ), c_green,
948 pgettext( "speed bonus", "Quick +%2d%%" ), quick_bonus );
949 line++;
950 }
951 if( you.has_bionic( bionic_id( "bio_speed" ) ) ) {
952 mvwprintz( w_speed, point( 1, line ), c_green,
953 pgettext( "speed bonus", "Bionic Speed +%2d%%" ), bio_speed_bonus );
954 line++;
955 }
956
957 for( const std::pair<const std::string, int> &speed_effect : speed_effects ) {
958 nc_color col = ( speed_effect.second > 0 ? c_green : c_red );
959 mvwprintz( w_speed, point( 1, line ), col, "%s", speed_effect.first );
960 mvwprintz( w_speed, point( 21, line ), col, ( speed_effect.second > 0 ? "+" : "-" ) );
961 mvwprintz( w_speed, point( std::abs( speed_effect.second ) >= 10 ? 22 : 23, line ), col, "%d%%",
962 std::abs( speed_effect.second ) );
963 line++;
964 }
965
966 int runcost = you.run_cost( 100 );
967 nc_color col = ( runcost <= 100 ? c_green : c_red );
968 mvwprintz( w_speed, point( 21 + ( runcost >= 100 ? 0 : ( runcost < 10 ? 2 : 1 ) ), 1 ), col,
969 "%d", runcost );
970 col = ( newmoves >= 100 ? c_green : c_red );
971 mvwprintz( w_speed, point( 21 + ( newmoves >= 100 ? 0 : ( newmoves < 10 ? 2 : 1 ) ), 2 ), col,
972 "%d", newmoves );
973 wnoutrefresh( w_speed );
974 }
975
draw_info_window(const catacurses::window & w_info,const player & you,const unsigned int line,const player_display_tab curtab,const std::vector<trait_id> & traitslist,const std::vector<bionic> & bionicslist,const std::vector<std::pair<std::string,std::string>> & effect_name_and_text,const std::vector<HeaderSkill> & skillslist)976 static void draw_info_window( const catacurses::window &w_info, const player &you,
977 const unsigned int line, const player_display_tab curtab,
978 const std::vector<trait_id> &traitslist,
979 const std::vector<bionic> &bionicslist,
980 const std::vector<std::pair<std::string, std::string>> &effect_name_and_text,
981 const std::vector<HeaderSkill> &skillslist )
982 {
983 switch( curtab ) {
984 case player_display_tab::stats:
985 draw_stats_info( w_info, you, line );
986 break;
987 case player_display_tab::encumbrance:
988 draw_encumbrance_info( w_info, you, line );
989 break;
990 case player_display_tab::skills:
991 draw_skills_info( w_info, line, skillslist );
992 break;
993 case player_display_tab::traits:
994 draw_traits_info( w_info, line, traitslist );
995 break;
996 case player_display_tab::bionics:
997 draw_bionics_info( w_info, line, bionicslist );
998 break;
999 case player_display_tab::effects:
1000 draw_effects_info( w_info, line, effect_name_and_text );
1001 break;
1002 case player_display_tab::proficiencies:
1003 draw_proficiencies_info( w_info, line, you );
1004 break;
1005 case player_display_tab::num_tabs:
1006 abort();
1007 }
1008 }
1009
draw_tip(const catacurses::window & w_tip,const player & you,const std::string & race,const input_context & ctxt)1010 static void draw_tip( const catacurses::window &w_tip, const player &you,
1011 const std::string &race, const input_context &ctxt )
1012 {
1013 werase( w_tip );
1014
1015 // Print name and header
1016 if( you.custom_profession.empty() ) {
1017 if( you.crossed_threshold() ) {
1018 //~ player info window: 1s - name, 2s - gender, 3s - Prof or Mutation name
1019 mvwprintz( w_tip, point_zero, c_white, _( " %1$s | %2$s | %3$s" ), you.name,
1020 you.male ? _( "Male" ) : _( "Female" ), race );
1021 } else if( you.prof == nullptr || you.prof == profession::generic() ) {
1022 // Regular person. Nothing interesting.
1023 //~ player info window: 1s - name, 2s - gender '|' - field separator.
1024 mvwprintz( w_tip, point_zero, c_white, _( " %1$s | %2$s" ), you.name,
1025 you.male ? _( "Male" ) : _( "Female" ) );
1026 } else {
1027 mvwprintz( w_tip, point_zero, c_white, _( " %1$s | %2$s | %3$s" ), you.name,
1028 you.male ? _( "Male" ) : _( "Female" ), you.prof->gender_appropriate_name( you.male ) );
1029 }
1030 } else {
1031 mvwprintz( w_tip, point_zero, c_white, _( " %1$s | %2$s | %3$s" ), you.name,
1032 you.male ? _( "Male" ) : _( "Female" ), you.custom_profession );
1033 }
1034
1035 right_print( w_tip, 0, 1, c_light_gray, string_format(
1036 _( "[<color_yellow>%s</color>]" ),
1037 ctxt.get_desc( "HELP_KEYBINDINGS" ) ) );
1038
1039 right_print( w_tip, 0, 0, c_light_gray, string_format( "%s", LINE_XOXO_S ) );
1040
1041 wnoutrefresh( w_tip );
1042 }
1043
handle_player_display_action(player & you,unsigned int & line,player_display_tab & curtab,input_context & ctxt,const ui_adaptor & ui_tip,const ui_adaptor & ui_info,const ui_adaptor & ui_stats,const ui_adaptor & ui_encumb,const ui_adaptor & ui_traits,const ui_adaptor & ui_bionics,const ui_adaptor & ui_effects,const ui_adaptor & ui_skills,const ui_adaptor & ui_proficiencies,const std::vector<trait_id> & traitslist,const std::vector<bionic> & bionicslist,const std::vector<std::pair<std::string,std::string>> & effect_name_and_text,const std::vector<HeaderSkill> & skillslist)1044 static bool handle_player_display_action( player &you, unsigned int &line,
1045 player_display_tab &curtab, input_context &ctxt, const ui_adaptor &ui_tip,
1046 const ui_adaptor &ui_info, const ui_adaptor &ui_stats, const ui_adaptor &ui_encumb,
1047 const ui_adaptor &ui_traits, const ui_adaptor &ui_bionics, const ui_adaptor &ui_effects,
1048 const ui_adaptor &ui_skills, const ui_adaptor &ui_proficiencies,
1049 const std::vector<trait_id> &traitslist, const std::vector<bionic> &bionicslist,
1050 const std::vector<std::pair<std::string, std::string>> &effect_name_and_text,
1051 const std::vector<HeaderSkill> &skillslist )
1052 {
1053 const auto invalidate_tab = [&]( const player_display_tab tab ) {
1054 switch( tab ) {
1055 case player_display_tab::stats:
1056 ui_stats.invalidate_ui();
1057 break;
1058 case player_display_tab::encumbrance:
1059 ui_encumb.invalidate_ui();
1060 break;
1061 case player_display_tab::traits:
1062 ui_traits.invalidate_ui();
1063 break;
1064 case player_display_tab::bionics:
1065 ui_bionics.invalidate_ui();
1066 break;
1067 case player_display_tab::effects:
1068 ui_effects.invalidate_ui();
1069 break;
1070 case player_display_tab::skills:
1071 ui_skills.invalidate_ui();
1072 break;
1073 case player_display_tab::proficiencies:
1074 ui_proficiencies.invalidate_ui();
1075 break;
1076 case player_display_tab::num_tabs:
1077 abort();
1078 }
1079 };
1080
1081 unsigned int line_beg = 0;
1082 unsigned int line_end = 0;
1083 switch( curtab ) {
1084 case player_display_tab::stats:
1085 line_end = 8;
1086 break;
1087 case player_display_tab::encumbrance: {
1088 const std::vector<std::pair<bodypart_id, bool>> bps = list_and_combine_bps( you, nullptr );
1089 line_end = bps.size();
1090 break;
1091 }
1092 case player_display_tab::traits:
1093 line_end = traitslist.size();
1094 break;
1095 case player_display_tab::bionics:
1096 line_end = bionicslist.size();
1097 break;
1098 case player_display_tab::effects:
1099 line_end = effect_name_and_text.size();
1100 break;
1101 case player_display_tab::skills:
1102 line_beg = 1; // skip first header
1103 line_end = skillslist.size();
1104 break;
1105 case player_display_tab::proficiencies:
1106 line_end = you.display_proficiencies().size();
1107 break;
1108 case player_display_tab::num_tabs:
1109 abort();
1110 }
1111 if( line_beg >= line_end || line < line_beg ) {
1112 line = line_beg;
1113 } else if( line > line_end - 1 ) {
1114 line = line_end - 1;
1115 }
1116
1117 bool done = false;
1118 std::string action = ctxt.handle_input();
1119
1120 if( action == "UP" ) {
1121 if( line > line_beg ) {
1122 --line;
1123 } else {
1124 line = line_end - 1;
1125 }
1126 if( curtab == player_display_tab::skills && skillslist[line].is_header ) {
1127 --line;
1128 }
1129 invalidate_tab( curtab );
1130 ui_info.invalidate_ui();
1131 } else if( action == "DOWN" ) {
1132 if( line + 1 < line_end ) {
1133 ++line;
1134 } else {
1135 line = line_beg;
1136 }
1137 if( curtab == player_display_tab::skills && skillslist[line].is_header ) {
1138 ++line;
1139 }
1140 invalidate_tab( curtab );
1141 ui_info.invalidate_ui();
1142 } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) {
1143 line = 0;
1144 invalidate_tab( curtab );
1145 curtab = action == "NEXT_TAB" ? next_tab( curtab ) : prev_tab( curtab );
1146 invalidate_tab( curtab );
1147 ui_info.invalidate_ui();
1148 } else if( action == "QUIT" ) {
1149 done = true;
1150 } else if( action == "CONFIRM" ) {
1151 switch( curtab ) {
1152 default:
1153 break;
1154 case player_display_tab::stats:
1155 if( line < 4 && get_option<bool>( "STATS_THROUGH_KILLS" ) && you.is_avatar() ) {
1156 you.as_avatar()->upgrade_stat_prompt( static_cast<character_stat>( line ) );
1157 }
1158 invalidate_tab( curtab );
1159 break;
1160 case player_display_tab::skills: {
1161 const Skill *selectedSkill = nullptr;
1162 if( line < skillslist.size() && !skillslist[line].is_header ) {
1163 selectedSkill = skillslist[line].skill;
1164 }
1165 if( selectedSkill ) {
1166 you.get_skill_level_object( selectedSkill->ident() ).toggleTraining();
1167 }
1168 invalidate_tab( curtab );
1169 break;
1170 }
1171 }
1172 } else if( action == "CHANGE_PROFESSION_NAME" ) {
1173 string_input_popup popup;
1174 popup.title( _( "Profession Name: " ) )
1175 .width( 25 )
1176 .text( "" )
1177 .max_length( 25 )
1178 .query();
1179
1180 you.custom_profession = popup.text();
1181 ui_tip.invalidate_ui();
1182 }
1183 return done;
1184 }
1185
disp_info()1186 void player::disp_info()
1187 {
1188 std::vector<std::pair<std::string, std::string>> effect_name_and_text;
1189 for( auto &elem : *effects ) {
1190 for( auto &_effect_it : elem.second ) {
1191 const std::string tmp = _effect_it.second.disp_name();
1192 if( tmp.empty() ) {
1193 continue;
1194 }
1195 effect_name_and_text.push_back( { tmp, _effect_it.second.disp_desc() } );
1196 }
1197 }
1198 if( get_perceived_pain() > 0 ) {
1199 const stat_mod ppen = get_pain_penalty();
1200 std::string pain_text;
1201 const auto add_if = [&]( const int amount, const char *const name ) {
1202 if( amount > 0 ) {
1203 pain_text += string_format( name, amount ) + " ";
1204 }
1205 };
1206 add_if( ppen.strength, _( "Strength -%d" ) );
1207 add_if( ppen.dexterity, _( "Dexterity -%d" ) );
1208 add_if( ppen.intelligence, _( "Intelligence -%d" ) );
1209 add_if( ppen.perception, _( "Perception -%d" ) );
1210 add_if( ppen.speed, _( "Speed -%d %%" ) );
1211 effect_name_and_text.push_back( { _( "Pain" ), pain_text } );
1212 }
1213
1214 const float bmi = get_bmi();
1215
1216 if( bmi < character_weight_category::underweight ) {
1217 std::string starvation_name;
1218 std::string starvation_text;
1219
1220 if( bmi < character_weight_category::emaciated ) {
1221 starvation_name = _( "Severely Malnourished" );
1222 starvation_text =
1223 _( "Your body is severely weakened by starvation. You might die if you don't start eating regular meals!\n\n" );
1224 } else {
1225 starvation_name = _( "Malnourished" );
1226 starvation_text =
1227 _( "Your body is weakened by starvation. Only time and regular meals will help you recover.\n\n" );
1228 }
1229
1230 if( bmi < character_weight_category::underweight ) {
1231 const float str_penalty = 1.0f - ( ( bmi - 13.0f ) / 3.0f );
1232 starvation_text += std::string( _( "Strength" ) ) + " -" + string_format( "%2.0f%%\n",
1233 str_penalty * 100.0f );
1234 starvation_text += std::string( _( "Dexterity" ) ) + " -" + string_format( "%2.0f%%\n",
1235 str_penalty * 50.0f );
1236 starvation_text += std::string( _( "Intelligence" ) ) + " -" + string_format( "%2.0f%%",
1237 str_penalty * 50.0f );
1238 }
1239
1240 effect_name_and_text.push_back( { starvation_name, starvation_text } );
1241 }
1242
1243 if( has_trait( trait_id( "TROGLO" ) ) && g->is_in_sunlight( pos() ) &&
1244 get_weather().weather_id->sun_intensity >= sun_intensity_type::high ) {
1245 effect_name_and_text.push_back( { _( "In Sunlight" ),
1246 _( "The sunlight irritates you.\n"
1247 "Strength - 1; Dexterity - 1; Intelligence - 1; Perception - 1" )
1248 } );
1249 } else if( has_trait( trait_id( "TROGLO2" ) ) && g->is_in_sunlight( pos() ) ) {
1250 effect_name_and_text.push_back( { _( "In Sunlight" ),
1251 _( "The sunlight irritates you badly.\n"
1252 "Strength - 2; Dexterity - 2; Intelligence - 2; Perception - 2" )
1253 } );
1254 } else if( has_trait( trait_id( "TROGLO3" ) ) && g->is_in_sunlight( pos() ) ) {
1255 effect_name_and_text.push_back( { _( "In Sunlight" ),
1256 _( "The sunlight irritates you terribly.\n"
1257 "Strength - 4; Dexterity - 4; Intelligence - 4; Perception - 4" )
1258 } );
1259 }
1260
1261 for( auto &elem : addictions ) {
1262 if( elem.sated < 0_turns && elem.intensity >= MIN_ADDICTION_LEVEL ) {
1263 effect_name_and_text.push_back( { addiction_name( elem ), addiction_text( elem ) } );
1264 }
1265 }
1266
1267 const unsigned int effect_win_size_y = 1 + static_cast<unsigned>( effect_name_and_text.size() );
1268
1269 std::vector<trait_id> traitslist = get_mutations( false );
1270 std::sort( traitslist.begin(), traitslist.end(), trait_display_sort );
1271 const unsigned int trait_win_size_y_max = 1 + static_cast<unsigned>( traitslist.size() );
1272
1273 std::vector<bionic> bionicslist = *my_bionics;
1274 const unsigned int bionics_win_size_y_max = 2 + bionicslist.size();
1275
1276 const std::vector<const Skill *> player_skill = Skill::get_skills_sorted_by(
1277 [&]( const Skill & a, const Skill & b ) {
1278 skill_displayType_id type_a = a.display_category();
1279 skill_displayType_id type_b = b.display_category();
1280
1281 return localized_compare( std::make_pair( type_a, a.name() ),
1282 std::make_pair( type_b, b.name() ) );
1283 } );
1284
1285 std::vector<HeaderSkill> skillslist;
1286 skill_displayType_id prev_type = skill_displayType_id::NULL_ID();
1287 for( const auto &s : player_skill ) {
1288 if( s->display_category() != prev_type ) {
1289 prev_type = s->display_category();
1290 skillslist.emplace_back( s, true );
1291 }
1292 skillslist.emplace_back( s, false );
1293 }
1294 const unsigned int skill_win_size_y_max = 1 + skillslist.size();
1295 const unsigned int info_win_size_y = 6;
1296
1297 const unsigned int grid_height = 9;
1298
1299 const unsigned int infooffsetytop = grid_height + 2;
1300 unsigned int infooffsetybottom = infooffsetytop + 1 + info_win_size_y;
1301
1302 const auto calculate_trait_and_bionic_height = [&]() {
1303 const unsigned int maxy = static_cast<unsigned>( TERMY );
1304 unsigned int trait_win_size_y = trait_win_size_y_max;
1305 unsigned int bionics_win_size_y = bionics_win_size_y_max;
1306 if( ( bionics_win_size_y_max + 1 + trait_win_size_y_max + infooffsetybottom ) > maxy ) {
1307 // maximum space for either window if they're both the same size
1308 unsigned max_shared_y = ( maxy - infooffsetybottom - 1 ) / 2;
1309 if( std::min( bionics_win_size_y_max, trait_win_size_y_max ) > max_shared_y ) {
1310 // both are larger than the shared size
1311 bionics_win_size_y = max_shared_y;
1312 trait_win_size_y = maxy - infooffsetybottom - 1 - bionics_win_size_y;
1313 } else if( trait_win_size_y_max <= max_shared_y ) {
1314 // trait window is less than the shared size, so give space to bionics
1315 bionics_win_size_y = maxy - infooffsetybottom - 1 - trait_win_size_y_max;
1316 } else {
1317 // bionics window is less than the shared size
1318 trait_win_size_y = maxy - infooffsetybottom - 1 - bionics_win_size_y;
1319 }
1320 }
1321 return std::make_pair( trait_win_size_y, bionics_win_size_y );
1322 };
1323
1324 // Print name and header
1325 // Post-humanity trumps your pre-Cataclysm life
1326 // Unless you have a custom profession.
1327 std::string race;
1328 if( custom_profession.empty() ) {
1329 if( crossed_threshold() ) {
1330 for( const trait_id &mut : get_mutations() ) {
1331 const mutation_branch &mdata = mut.obj();
1332 if( mdata.threshold ) {
1333 race = mdata.name();
1334 break;
1335 }
1336 }
1337 }
1338 }
1339
1340 input_context ctxt( "PLAYER_INFO" );
1341 ctxt.register_updown();
1342 ctxt.register_action( "NEXT_TAB", to_translation( "Cycle to next category" ) );
1343 ctxt.register_action( "PREV_TAB", to_translation( "Cycle to previous category" ) );
1344 ctxt.register_action( "QUIT" );
1345 ctxt.register_action( "CONFIRM", to_translation( "Toggle skill training / Upgrade stat" ) );
1346 ctxt.register_action( "CHANGE_PROFESSION_NAME", to_translation( "Change profession name" ) );
1347 ctxt.register_action( "HELP_KEYBINDINGS" );
1348
1349 std::map<std::string, int> speed_effects;
1350 for( auto &elem : *effects ) {
1351 for( std::pair<const bodypart_id, effect> &_effect_it : elem.second ) {
1352 effect &it = _effect_it.second;
1353 bool reduced = resists_effect( it );
1354 int move_adjust = it.get_mod( "SPEED", reduced );
1355 if( move_adjust != 0 ) {
1356 const std::string dis_text = it.get_speed_name();
1357 speed_effects[dis_text] += move_adjust;
1358 }
1359 }
1360 }
1361
1362 border_helper borders;
1363
1364 player_display_tab curtab = player_display_tab::stats;
1365 unsigned int line = 0;
1366
1367 catacurses::window w_tip;
1368 ui_adaptor ui_tip;
1369 ui_tip.on_screen_resize( [&]( ui_adaptor & ui_tip ) {
1370 w_tip = catacurses::newwin( 1, FULL_SCREEN_WIDTH + 1, point_zero );
1371 ui_tip.position_from_window( w_tip );
1372 } );
1373 ui_tip.mark_resize();
1374 ui_tip.on_redraw( [&]( const ui_adaptor & ) {
1375 draw_tip( w_tip, *this, race, ctxt );
1376 } );
1377
1378 catacurses::window w_stats;
1379 catacurses::window w_stats_border;
1380 border_helper::border_info &border_stats = borders.add_border();
1381 ui_adaptor ui_stats;
1382 ui_stats.on_screen_resize( [&]( ui_adaptor & ui_stats ) {
1383 // NOLINTNEXTLINE(cata-use-named-point-constants)
1384 w_stats = catacurses::newwin( grid_height, grid_width, point( 0, 1 ) );
1385 // Every grid draws the bottom and right borders. The top and left borders
1386 // are either not displayed or drawn by another grid.
1387 // NOLINTNEXTLINE(cata-use-named-point-constants)
1388 w_stats_border = catacurses::newwin( grid_height + 1, grid_width + 1, point( 0, 1 ) );
1389 // But we need to specify the full border for border_helper to calculate the
1390 // border connection.
1391 // NOLINTNEXTLINE(cata-use-named-point-constants)
1392 border_stats.set( point( -1, 0 ), point( grid_width + 2, grid_height + 2 ) );
1393 ui_stats.position_from_window( w_stats_border );
1394 } );
1395 ui_stats.mark_resize();
1396 ui_stats.on_redraw( [&]( const ui_adaptor & ) {
1397 borders.draw_border( w_stats_border );
1398 wnoutrefresh( w_stats_border );
1399 draw_stats_tab( w_stats, *this, line, curtab );
1400 } );
1401
1402 unsigned int trait_win_size_y = 0;
1403 catacurses::window w_traits;
1404 catacurses::window w_traits_border;
1405 border_helper::border_info &border_traits = borders.add_border();
1406 ui_adaptor ui_traits;
1407 ui_traits.on_screen_resize( [&]( ui_adaptor & ui_traits ) {
1408 trait_win_size_y = calculate_trait_and_bionic_height().first;
1409 w_traits = catacurses::newwin( trait_win_size_y, grid_width,
1410 point( grid_width + 1, infooffsetybottom ) );
1411 w_traits_border = catacurses::newwin( trait_win_size_y + 1, grid_width + 1,
1412 point( grid_width + 1, infooffsetybottom ) );
1413 border_traits.set( point( grid_width, infooffsetybottom - 1 ),
1414 point( grid_width + 2, trait_win_size_y + 2 ) );
1415 ui_traits.position_from_window( w_traits_border );
1416 } );
1417 ui_traits.mark_resize();
1418 ui_traits.on_redraw( [&]( const ui_adaptor & ) {
1419 borders.draw_border( w_traits_border );
1420 wnoutrefresh( w_traits_border );
1421 draw_traits_tab( w_traits, line, curtab, traitslist, trait_win_size_y );
1422 } );
1423
1424 unsigned int bionics_win_size_y = 0;
1425 catacurses::window w_bionics;
1426 catacurses::window w_bionics_border;
1427 border_helper::border_info &border_bionics = borders.add_border();
1428 ui_adaptor ui_bionics;
1429 ui_bionics.on_screen_resize( [&]( ui_adaptor & ui_bionics ) {
1430 bionics_win_size_y = calculate_trait_and_bionic_height().second;
1431 w_bionics = catacurses::newwin( bionics_win_size_y, grid_width,
1432 point( grid_width + 1,
1433 infooffsetybottom + trait_win_size_y + 1 ) );
1434 w_bionics_border = catacurses::newwin( bionics_win_size_y + 1, grid_width + 1,
1435 point( grid_width + 1,
1436 infooffsetybottom + trait_win_size_y + 1 ) );
1437 border_bionics.set( point( grid_width, infooffsetybottom + trait_win_size_y ),
1438 point( grid_width + 2, bionics_win_size_y + 2 ) );
1439 ui_bionics.position_from_window( w_bionics_border );
1440 } );
1441 ui_bionics.mark_resize();
1442 ui_bionics.on_redraw( [&]( const ui_adaptor & ) {
1443 borders.draw_border( w_bionics_border );
1444 wnoutrefresh( w_bionics_border );
1445 draw_bionics_tab( w_bionics, *this, line, curtab, bionicslist, bionics_win_size_y );
1446 } );
1447
1448 catacurses::window w_encumb;
1449 catacurses::window w_encumb_border;
1450 border_helper::border_info &border_encumb = borders.add_border();
1451 ui_adaptor ui_encumb;
1452 ui_encumb.on_screen_resize( [&]( ui_adaptor & ui_encumb ) {
1453 w_encumb = catacurses::newwin( grid_height, grid_width, point( grid_width + 1, 1 ) );
1454 w_encumb_border = catacurses::newwin( grid_height + 1, grid_width + 1, point( grid_width + 1, 1 ) );
1455 border_encumb.set( point( grid_width, 0 ), point( grid_width + 2, grid_height + 2 ) );
1456 ui_encumb.position_from_window( w_encumb_border );
1457 } );
1458 ui_encumb.mark_resize();
1459 ui_encumb.on_redraw( [&]( const ui_adaptor & ) {
1460 borders.draw_border( w_encumb_border );
1461 wnoutrefresh( w_encumb_border );
1462 draw_encumbrance_tab( w_encumb, *this, line, curtab );
1463 } );
1464
1465 catacurses::window w_effects;
1466 catacurses::window w_effects_border;
1467 border_helper::border_info &border_effects = borders.add_border();
1468 ui_adaptor ui_effects;
1469 ui_effects.on_screen_resize( [&]( ui_adaptor & ui_effects ) {
1470 w_effects = catacurses::newwin( effect_win_size_y, grid_width,
1471 point( grid_width * 2 + 2, infooffsetybottom ) );
1472 w_effects_border = catacurses::newwin( effect_win_size_y + 1, grid_width + 2,
1473 point( grid_width * 2 + 1, infooffsetybottom ) );
1474 border_effects.set( point( grid_width * 2 + 1, infooffsetybottom - 1 ),
1475 point( grid_width + 2, effect_win_size_y + 2 ) );
1476 ui_effects.position_from_window( w_effects_border );
1477 } );
1478 ui_effects.mark_resize();
1479 ui_effects.on_redraw( [&]( const ui_adaptor & ) {
1480 borders.draw_border( w_effects_border );
1481 wnoutrefresh( w_effects_border );
1482 draw_effects_tab( w_effects, line, curtab, effect_name_and_text, effect_win_size_y );
1483 } );
1484
1485 unsigned int proficiency_win_size_y = 0;
1486 const point profstart = point( grid_width * 2 + 2, infooffsetybottom + effect_win_size_y + 1 );
1487 catacurses::window w_proficiencies;
1488 catacurses::window w_proficiencies_border;
1489 border_helper::border_info &border_proficiencies = borders.add_border();
1490 ui_adaptor ui_proficiencies;
1491 ui_proficiencies.on_screen_resize( [&]( ui_adaptor & ui_proficiencies ) {
1492 const unsigned int maxy = static_cast<unsigned>( TERMY );
1493 proficiency_win_size_y = std::min( display_proficiencies().size(),
1494 static_cast<size_t>( maxy - ( infooffsetybottom + effect_win_size_y ) ) ) + 1;
1495 w_proficiencies = catacurses::newwin( proficiency_win_size_y, grid_width,
1496 profstart );
1497 w_proficiencies_border = catacurses::newwin( proficiency_win_size_y + 1, grid_width + 2,
1498 profstart + point_west );
1499 border_proficiencies.set( profstart + point_north_west, point( grid_width + 2,
1500 proficiency_win_size_y + 2 ) );
1501 ui_proficiencies.position_from_window( w_proficiencies_border );
1502 } );
1503 ui_proficiencies.mark_resize();
1504 ui_proficiencies.on_redraw( [&]( const ui_adaptor & ) {
1505 borders.draw_border( w_proficiencies_border );
1506 wnoutrefresh( w_proficiencies_border );
1507 draw_proficiencies_tab( w_proficiencies, line, *this, curtab );
1508 } );
1509
1510 unsigned int skill_win_size_y = 0;
1511 catacurses::window w_skills;
1512 catacurses::window w_skills_border;
1513 border_helper::border_info &border_skills = borders.add_border();
1514 ui_adaptor ui_skills;
1515 ui_skills.on_screen_resize( [&]( ui_adaptor & ui_skills ) {
1516 const unsigned int maxy = static_cast<unsigned>( TERMY );
1517 skill_win_size_y = skill_win_size_y_max;
1518 if( skill_win_size_y + infooffsetybottom > maxy ) {
1519 skill_win_size_y = maxy - infooffsetybottom;
1520 }
1521 w_skills = catacurses::newwin( skill_win_size_y, grid_width,
1522 point( 0, infooffsetybottom ) );
1523 w_skills_border = catacurses::newwin( skill_win_size_y + 1, grid_width + 1,
1524 point( 0, infooffsetybottom ) );
1525 border_skills.set( point( -1, infooffsetybottom - 1 ),
1526 point( grid_width + 2, skill_win_size_y + 2 ) );
1527 ui_skills.position_from_window( w_skills_border );
1528 } );
1529 ui_skills.mark_resize();
1530 ui_skills.on_redraw( [&]( const ui_adaptor & ) {
1531 borders.draw_border( w_skills_border );
1532 wnoutrefresh( w_skills_border );
1533 draw_skills_tab( w_skills, *this, line, curtab, skillslist, skill_win_size_y );
1534 } );
1535
1536 catacurses::window w_info;
1537 catacurses::window w_info_border;
1538 border_helper::border_info &border_info = borders.add_border();
1539 ui_adaptor ui_info;
1540 ui_info.on_screen_resize( [&]( ui_adaptor & ui_info ) {
1541 w_info = catacurses::newwin( info_win_size_y, FULL_SCREEN_WIDTH,
1542 point( 0, infooffsetytop ) );
1543 w_info_border = catacurses::newwin( info_win_size_y + 1, FULL_SCREEN_WIDTH + 1,
1544 point( 0, infooffsetytop ) );
1545 border_info.set( point( -1, infooffsetytop - 1 ),
1546 point( FULL_SCREEN_WIDTH + 2, info_win_size_y + 2 ) );
1547 ui_info.position_from_window( w_info_border );
1548 } );
1549 ui_info.mark_resize();
1550 ui_info.on_redraw( [&]( const ui_adaptor & ) {
1551 borders.draw_border( w_info_border );
1552 wnoutrefresh( w_info_border );
1553 draw_info_window( w_info, *this, line, curtab,
1554 traitslist, bionicslist, effect_name_and_text, skillslist );
1555 } );
1556
1557 catacurses::window w_speed;
1558 catacurses::window w_speed_border;
1559 border_helper::border_info &border_speed = borders.add_border();
1560 ui_adaptor ui_speed;
1561 ui_speed.on_screen_resize( [&]( ui_adaptor & ui_speed ) {
1562 w_speed = catacurses::newwin( grid_height, grid_width, point( grid_width * 2 + 2, 1 ) );
1563 w_speed_border = catacurses::newwin( grid_height + 1, grid_width + 1,
1564 point( grid_width * 2 + 2, 1 ) );
1565 border_speed.set( point( grid_width * 2 + 1, 0 ),
1566 point( grid_width + 2, grid_height + 2 ) );
1567 ui_speed.position_from_window( w_speed_border );
1568 } );
1569 ui_speed.mark_resize();
1570 ui_speed.on_redraw( [&]( const ui_adaptor & ) {
1571 borders.draw_border( w_speed_border );
1572 wnoutrefresh( w_speed_border );
1573 draw_speed_tab( w_speed, *this, speed_effects );
1574 } );
1575
1576 bool done = false;
1577
1578 do {
1579 ui_manager::redraw_invalidated();
1580
1581 done = handle_player_display_action( *this, line, curtab, ctxt, ui_tip, ui_info, ui_stats,
1582 ui_encumb, ui_traits, ui_bionics, ui_effects, ui_skills, ui_proficiencies, traitslist, bionicslist,
1583 effect_name_and_text, skillslist );
1584 } while( !done );
1585 }
1586