1 #include "debug_menu.h" // IWYU pragma: associated
2
3 #include <algorithm>
4 #include <cstddef>
5 #include <iterator>
6 #include <map>
7 #include <memory>
8 #include <new>
9 #include <set>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "calendar.h"
15 #include "catacharset.h"
16 #include "color.h"
17 #include "cursesdef.h"
18 #include "debug.h"
19 #include "enums.h"
20 #include "game.h"
21 #include "input.h"
22 #include "item.h"
23 #include "item_factory.h"
24 #include "itype.h"
25 #include "map.h"
26 #include "memory_fast.h"
27 #include "monster.h"
28 #include "monstergenerator.h"
29 #include "mtype.h"
30 #include "mutation.h"
31 #include "optional.h"
32 #include "output.h"
33 #include "player.h"
34 #include "point.h"
35 #include "proficiency.h"
36 #include "skill.h"
37 #include "string_formatter.h"
38 #include "string_input_popup.h"
39 #include "translations.h"
40 #include "type_id.h"
41 #include "ui.h"
42 #include "uistate.h"
43
44 class ui_adaptor;
45
46 class wish_mutate_callback: public uilist_callback
47 {
48 public:
49 // Last menu entry
50 int lastlen = 0;
51 // Feedback message
52 std::string msg;
53 bool started = false;
54 bool only_active = false;
55 std::vector<trait_id> vTraits;
56 std::map<trait_id, bool> pTraits;
57 player *p;
58
mcolor(const trait_id & m)59 nc_color mcolor( const trait_id &m ) {
60 if( pTraits[ m ] ) {
61 return c_green;
62 }
63 return c_light_gray;
64 }
65
66 wish_mutate_callback() = default;
key(const input_context &,const input_event & event,int entnum,uilist * menu)67 bool key( const input_context &, const input_event &event, int entnum, uilist *menu ) override {
68 if( event.get_first_input() == 't' && p->has_trait( vTraits[ entnum ] ) ) {
69 if( !p->has_base_trait( vTraits[ entnum ] ) ) {
70 p->unset_mutation( vTraits[ entnum ] );
71 }
72
73 p->toggle_trait( vTraits[ entnum ] );
74 p->set_mutation( vTraits[ entnum ] );
75
76 menu->entries[ entnum ].text_color = p->has_trait( vTraits[ entnum ] ) ? c_green : menu->text_color;
77 menu->entries[ entnum ].extratxt.txt = p->has_base_trait( vTraits[ entnum ] ) ? "T" : "";
78 return true;
79 } else if( event.get_first_input() == 'a' ) {
80 only_active = !only_active;
81
82 for( size_t i = 0; i < vTraits.size(); i++ ) {
83 if( !p->has_trait( vTraits[ i ] ) ) {
84 menu->entries[ i ].enabled = !only_active;
85 }
86 }
87
88 return true;
89 }
90 return false;
91 }
92
refresh(uilist * menu)93 void refresh( uilist *menu ) override {
94 if( !started ) {
95 started = true;
96 for( const mutation_branch &traits_iter : mutation_branch::get_all() ) {
97 vTraits.push_back( traits_iter.id );
98 pTraits[traits_iter.id] = p->has_trait( traits_iter.id );
99 }
100 }
101
102 const std::string padding = std::string( menu->pad_right - 1, ' ' );
103
104 const int startx = menu->w_width - menu->pad_right;
105 for( int i = 2; i < lastlen; i++ ) {
106 mvwprintw( menu->window, point( startx, i ), padding );
107 }
108
109 int line2 = 4;
110
111 if( menu->selected >= 0 && static_cast<size_t>( menu->selected ) < vTraits.size() ) {
112 const mutation_branch &mdata = vTraits[menu->selected].obj();
113
114 mvwprintw( menu->window, point( startx, 3 ),
115 mdata.valid ? _( "Valid" ) : _( "Nonvalid" ) );
116
117 if( !mdata.prereqs.empty() ) {
118 line2++;
119 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Prereqs:" ) );
120 for( const trait_id &j : mdata.prereqs ) {
121 mvwprintz( menu->window, point( startx + 11, line2 ), mcolor( j ),
122 mutation_branch::get_name( j ) );
123 line2++;
124 }
125 }
126
127 if( !mdata.prereqs2.empty() ) {
128 line2++;
129 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Prereqs, 2d:" ) );
130 for( const trait_id &j : mdata.prereqs2 ) {
131 mvwprintz( menu->window, point( startx + 15, line2 ), mcolor( j ),
132 mutation_branch::get_name( j ) );
133 line2++;
134 }
135 }
136
137 if( !mdata.threshreq.empty() ) {
138 line2++;
139 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Thresholds required:" ) );
140 for( const trait_id &j : mdata.threshreq ) {
141 mvwprintz( menu->window, point( startx + 21, line2 ), mcolor( j ),
142 mutation_branch::get_name( j ) );
143 line2++;
144 }
145 }
146
147 if( !mdata.cancels.empty() ) {
148 line2++;
149 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Cancels:" ) );
150 for( const trait_id &j : mdata.cancels ) {
151 mvwprintz( menu->window, point( startx + 11, line2 ), mcolor( j ),
152 mutation_branch::get_name( j ) );
153 line2++;
154 }
155 }
156
157 if( !mdata.replacements.empty() ) {
158 line2++;
159 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Becomes:" ) );
160 for( const trait_id &j : mdata.replacements ) {
161 mvwprintz( menu->window, point( startx + 11, line2 ), mcolor( j ),
162 mutation_branch::get_name( j ) );
163 line2++;
164 }
165 }
166
167 if( !mdata.additions.empty() ) {
168 line2++;
169 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Add-ons:" ) );
170 for( const string_id<mutation_branch> &j : mdata.additions ) {
171 mvwprintz( menu->window, point( startx + 11, line2 ), mcolor( j ),
172 mutation_branch::get_name( j ) );
173 line2++;
174 }
175 }
176
177 if( !mdata.types.empty() ) {
178 line2++;
179 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Type:" ) );
180 for( const std::string &j : mdata.types ) {
181 mvwprintw( menu->window, point( startx + 11, line2 ), j );
182 line2++;
183 }
184 }
185
186 if( !mdata.category.empty() ) {
187 line2++;
188 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Category:" ) );
189 for( const mutation_category_id &j : mdata.category ) {
190 mvwprintw( menu->window, point( startx + 11, line2 ), j.str() );
191 line2++;
192 }
193 }
194 line2 += 2;
195
196 //~ pts: points, vis: visibility, ugly: ugliness
197 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "pts: %d vis: %d ugly: %d" ),
198 mdata.points,
199 mdata.visibility,
200 mdata.ugliness
201 );
202 line2 += 2;
203
204 std::vector<std::string> desc = foldstring( mdata.desc(),
205 menu->pad_right - 1 );
206 for( auto &elem : desc ) {
207 mvwprintz( menu->window, point( startx, line2 ), c_light_gray, elem );
208 line2++;
209 }
210 }
211
212 lastlen = line2 + 1;
213
214 mvwprintz( menu->window, point( startx, menu->w_height - 4 ), c_green, msg );
215 msg.clear();
216 input_context ctxt( menu->input_category, keyboard_mode::keycode );
217 mvwprintw( menu->window, point( startx, menu->w_height - 3 ),
218 _( "[%s] find, [%s] quit, [t] toggle base trait" ),
219 ctxt.get_desc( "FILTER" ), ctxt.get_desc( "QUIT" ) );
220
221 if( only_active ) {
222 mvwprintz( menu->window, point( startx, menu->w_height - 2 ), c_green,
223 _( "[a] show active traits (active)" ) );
224 } else {
225 mvwprintz( menu->window, point( startx, menu->w_height - 2 ), c_white,
226 _( "[a] show active traits" ) );
227 }
228
229 wnoutrefresh( menu->window );
230 }
231
232 ~wish_mutate_callback() override = default;
233 };
234
wishmutate(player * p)235 void debug_menu::wishmutate( player *p )
236 {
237 uilist wmenu;
238 int c = 0;
239
240 for( const mutation_branch &traits_iter : mutation_branch::get_all() ) {
241 wmenu.addentry( -1, true, -2, traits_iter.name() );
242 wmenu.entries[ c ].extratxt.left = 1;
243 wmenu.entries[ c ].extratxt.txt.clear();
244 wmenu.entries[ c ].extratxt.color = c_light_green;
245 if( p->has_trait( traits_iter.id ) ) {
246 wmenu.entries[ c ].txt = string_format( _( "%s (active)" ), traits_iter.name() );
247 wmenu.entries[ c ].text_color = c_green;
248 if( p->has_base_trait( traits_iter.id ) ) {
249 wmenu.entries[ c ].extratxt.txt = "T";
250 }
251 } else {
252 wmenu.entries[ c ].txt = traits_iter.name();
253 }
254 c++;
255 }
256 wmenu.w_x_setup = 0;
257 wmenu.w_width_setup = []() -> int {
258 return TERMX;
259 };
260 wmenu.pad_right_setup = []() -> int {
261 return TERMX - 40;
262 };
263 wmenu.selected = uistate.wishmutate_selected;
264 wish_mutate_callback cb;
265 cb.p = p;
266 wmenu.callback = &cb;
267 do {
268 wmenu.query();
269 if( wmenu.ret >= 0 ) {
270 int rc = 0;
271 const trait_id mstr = cb.vTraits[ wmenu.ret ];
272 const auto &mdata = mstr.obj();
273 const bool threshold = mdata.threshold;
274 const bool profession = mdata.profession;
275 // Manual override for the threshold-gaining
276 if( threshold || profession ) {
277 if( p->has_trait( mstr ) ) {
278 do {
279 p->remove_mutation( mstr );
280 rc++;
281 } while( p->has_trait( mstr ) && rc < 10 );
282 } else {
283 do {
284 p->set_mutation( mstr );
285 rc++;
286 } while( !p->has_trait( mstr ) && rc < 10 );
287 }
288 } else if( p->has_trait( mstr ) ) {
289 do {
290 p->remove_mutation( mstr );
291 rc++;
292 } while( p->has_trait( mstr ) && rc < 10 );
293 } else {
294 do {
295 p->mutate_towards( mstr );
296 rc++;
297 } while( !p->has_trait( mstr ) && rc < 10 );
298 }
299 cb.msg = string_format( _( "%s Mutation changes: %d" ), mstr.c_str(), rc );
300 uistate.wishmutate_selected = wmenu.selected;
301 if( rc != 0 ) {
302 for( size_t i = 0; i < cb.vTraits.size(); i++ ) {
303 uilist_entry &entry = wmenu.entries[ i ];
304 entry.extratxt.txt.clear();
305 if( p->has_trait( cb.vTraits[ i ] ) ) {
306 entry.txt = string_format( _( "%s (active)" ), cb.vTraits[ i ].obj().name() );
307 entry.enabled = true;
308 entry.text_color = c_green;
309 cb.pTraits[ cb.vTraits[ i ] ] = true;
310 if( p->has_base_trait( cb.vTraits[ i ] ) ) {
311 entry.extratxt.txt = "T";
312 }
313 } else {
314 entry.txt = cb.vTraits[ i ].obj().name();
315 entry.enabled = entry.enabled ? true : !cb.only_active;
316 entry.text_color = wmenu.text_color;
317 cb.pTraits[ cb.vTraits[ i ] ] = false;
318 }
319 }
320 }
321 wmenu.filterlist();
322 }
323 } while( wmenu.ret >= 0 );
324 }
325
326 class wish_monster_callback: public uilist_callback
327 {
328 public:
329 // last menu entry
330 int lastent;
331 // feedback message
332 std::string msg;
333 // spawn friendly critter?
334 bool friendly;
335 bool hallucination;
336 // Number of monsters to spawn.
337 int group;
338 // scrap critter for monster::print_info
339 monster tmp;
340 const std::vector<const mtype *> &mtypes;
341
wish_monster_callback(const std::vector<const mtype * > & mtypes)342 explicit wish_monster_callback( const std::vector<const mtype *> &mtypes )
343 : mtypes( mtypes ) {
344 friendly = false;
345 hallucination = false;
346 group = 0;
347 lastent = -2;
348 }
349
key(const input_context &,const input_event & event,int,uilist *)350 bool key( const input_context &, const input_event &event, int /*entnum*/,
351 uilist * /*menu*/ ) override {
352 if( event.get_first_input() == 'f' ) {
353 friendly = !friendly;
354 // Force tmp monster regen
355 lastent = -2;
356 // Tell menu we handled keypress
357 return true;
358 } else if( event.get_first_input() == 'i' ) {
359 group++;
360 return true;
361 } else if( event.get_first_input() == 'h' ) {
362 hallucination = !hallucination;
363 return true;
364 } else if( event.get_first_input() == 'd' && group != 0 ) {
365 group--;
366 return true;
367 }
368 return false;
369 }
370
refresh(uilist * menu)371 void refresh( uilist *menu ) override {
372 catacurses::window w_info = catacurses::newwin( menu->w_height - 2, menu->pad_right,
373 point( menu->w_x + menu->w_width - 1 - menu->pad_right, 1 ) );
374
375 const int entnum = menu->selected;
376 const bool valid_entnum = entnum >= 0 && static_cast<size_t>( entnum ) < mtypes.size();
377 if( entnum != lastent ) {
378 lastent = entnum;
379 if( valid_entnum ) {
380 tmp = monster( mtypes[ entnum ]->id );
381 if( friendly ) {
382 tmp.friendly = -1;
383 }
384 } else {
385 tmp = monster();
386 }
387 }
388
389 werase( w_info );
390 if( valid_entnum ) {
391 tmp.print_info( w_info, 2, 5, 1 );
392
393 std::string header = string_format( "#%d: %s (%d)%s", entnum, tmp.type->nname(),
394 group, hallucination ? _( " (hallucination)" ) : "" );
395 mvwprintz( w_info, point( ( getmaxx( w_info ) - utf8_width( header ) ) / 2, 0 ), c_cyan, header );
396 }
397
398 mvwprintz( w_info, point( 0, getmaxy( w_info ) - 3 ), c_green, msg );
399 msg.clear();
400 input_context ctxt( menu->input_category, keyboard_mode::keycode );
401 mvwprintw( w_info, point( 0, getmaxy( w_info ) - 2 ),
402 _( "[%s] find, [f]riendly, [h]allucination, [i]ncrease group, [d]ecrease group, [%s] quit" ),
403 ctxt.get_desc( "FILTER" ), ctxt.get_desc( "QUIT" ) );
404
405 wnoutrefresh( w_info );
406 }
407
408 ~wish_monster_callback() override = default;
409 };
410
wishmonster(const cata::optional<tripoint> & p)411 void debug_menu::wishmonster( const cata::optional<tripoint> &p )
412 {
413 std::vector<const mtype *> mtypes;
414
415 uilist wmenu;
416 wmenu.w_x_setup = 0;
417 wmenu.w_width_setup = []() -> int {
418 return TERMX;
419 };
420 wmenu.pad_right_setup = []() -> int {
421 return TERMX - 30;
422 };
423 wmenu.selected = uistate.wishmonster_selected;
424 wish_monster_callback cb( mtypes );
425 wmenu.callback = &cb;
426
427 int i = 0;
428 for( const mtype &montype : MonsterGenerator::generator().get_all_mtypes() ) {
429 wmenu.addentry( i, true, 0, montype.nname() );
430 wmenu.entries[i].extratxt.txt = montype.sym;
431 wmenu.entries[i].extratxt.color = montype.color;
432 wmenu.entries[i].extratxt.left = 1;
433 ++i;
434 mtypes.push_back( &montype );
435 }
436
437 do {
438 wmenu.query();
439 if( wmenu.ret >= 0 ) {
440 const mtype_id &mon_type = mtypes[ wmenu.ret ]->id;
441 if( cata::optional<tripoint> spawn = p ? p : g->look_around() ) {
442 int num_spawned = 0;
443 for( const tripoint &destination : closest_points_first( *spawn, cb.group ) ) {
444 monster *const mon = g->place_critter_at( mon_type, destination );
445 if( !mon ) {
446 continue;
447 }
448 if( cb.friendly ) {
449 mon->friendly = -1;
450 }
451 if( cb.hallucination ) {
452 mon->hallucination = true;
453 }
454 ++num_spawned;
455 }
456 input_context ctxt( wmenu.input_category, keyboard_mode::keycode );
457 cb.msg = string_format( _( "Spawned %d monsters, choose another or [%s] to quit." ),
458 num_spawned, ctxt.get_desc( "QUIT" ) );
459 if( num_spawned == 0 ) {
460 cb.msg += _( "\nTarget location is not suitable for placing this kind of monster. Choose a different target or [i]ncrease the groups size." );
461 }
462 uistate.wishmonster_selected = wmenu.selected;
463 }
464 }
465 } while( wmenu.ret >= 0 );
466 }
467
wishitem_produce(const itype & type,std::string & flags,bool incontainer)468 static item wishitem_produce( const itype &type, std::string &flags, bool incontainer )
469 {
470 item granted( &type, calendar::turn );
471
472 granted.unset_flags();
473 for( const auto &tag : debug_menu::string_to_iterable<std::vector<std::string>>( flags, " " ) ) {
474 const flag_id flag( tag );
475 if( flag.is_valid() ) {
476 granted.set_flag( flag_id( tag ) );
477 }
478 }
479
480 if( incontainer ) {
481 granted = granted.in_its_container();
482 }
483 // If the item has an ammunition, this loads it to capacity, including magazines.
484 if( !granted.ammo_default().is_null() ) {
485 granted.ammo_set( granted.ammo_default(), -1 );
486 }
487
488 return granted;
489 }
490
491 class wish_item_callback: public uilist_callback
492 {
493 public:
494 bool incontainer;
495 bool spawn_everything;
496 std::string msg;
497 std::string flags;
498 std::string itype_flags;
499 const std::vector<const itype *> &standard_itype_ids;
wish_item_callback(const std::vector<const itype * > & ids)500 explicit wish_item_callback( const std::vector<const itype *> &ids ) :
501 incontainer( false ), spawn_everything( false ), standard_itype_ids( ids ) {
502 }
503
select(uilist * menu)504 void select( uilist *menu ) override {
505 if( menu->selected < 0 ) {
506 return;
507 }
508 const itype &selected_itype = *standard_itype_ids[menu->selected];
509 // Make liquids "contained" by default (toggled with CONTAINER action)
510 incontainer = selected_itype.phase == phase_id::LIQUID;
511 // Clear instance flags when switching items
512 flags.clear();
513 // Grab default flags for the itype (added with the FLAG action)
514 itype_flags = debug_menu::iterable_to_string( selected_itype.get_flags(), " ",
515 []( const flag_id & f ) {
516 return f.str();
517 } );
518 }
519
key(const input_context & ctxt,const input_event & event,int,uilist *)520 bool key( const input_context &ctxt, const input_event &event, int /*entnum*/,
521 uilist * /*menu*/ ) override {
522
523 const std::string &action = ctxt.input_to_action( event );
524 if( action == "CONTAINER" ) {
525 incontainer = !incontainer;
526 return true;
527 }
528 if( action == "FLAG" ) {
529 std::string edit_flags;
530 if( flags.empty() ) {
531 // If this is the first time using the FLAG action on this item, start with itype flags
532 edit_flags = itype_flags;
533 } else {
534 // Otherwise, edit the existing list of user-defined instance flags
535 edit_flags = flags;
536 }
537 string_input_popup popup;
538 popup
539 .title( _( "Flags:" ) )
540 .description( _( "UPPERCASE, no quotes, separate with spaces" ) )
541 .max_length( 100 )
542 .text( edit_flags )
543 .query();
544 // Save instance flags on this item (will be reset when selecting another item)
545 if( popup.confirmed() ) {
546 flags = popup.text();
547 return true;
548 }
549 }
550 if( action == "EVERYTHING" ) {
551 spawn_everything = !spawn_everything;
552 return true;
553 }
554 return false;
555 }
556
refresh(uilist * menu)557 void refresh( uilist *menu ) override {
558 const int starty = 3;
559 const int startx = menu->w_width - menu->pad_right;
560 const std::string padding( menu->pad_right, ' ' );
561 for( int y = 2; y < menu->w_height - 1; y++ ) {
562 mvwprintw( menu->window, point( startx - 1, y ), padding );
563 }
564 mvwhline( menu->window, point( startx, 1 ), ' ', menu->pad_right - 1 );
565 const int entnum = menu->selected;
566 if( entnum >= 0 && static_cast<size_t>( entnum ) < standard_itype_ids.size() ) {
567 item tmp = wishitem_produce( *standard_itype_ids[entnum], flags, false );
568 const std::string header = string_format( "#%d: %s%s%s", entnum,
569 standard_itype_ids[entnum]->get_id().c_str(),
570 incontainer ? _( " (contained)" ) : "",
571 flags.empty() ? "" : _( " (flagged)" ) );
572 mvwprintz( menu->window, point( startx + ( menu->pad_right - 1 - utf8_width( header ) ) / 2, 1 ),
573 c_cyan, header );
574
575 fold_and_print( menu->window, point( startx, starty ), menu->pad_right - 1, c_light_gray,
576 tmp.info( true ) );
577 }
578
579 mvwprintz( menu->window, point( startx, menu->w_height - 3 ), c_green, msg );
580 msg.erase();
581 input_context ctxt( menu->input_category, keyboard_mode::keycode );
582 mvwprintw( menu->window, point( startx, menu->w_height - 2 ),
583 _( "[%s] find, [%s] container, [%s] flag, [%s] everything, [%s] quit" ),
584 ctxt.get_desc( "FILTER" ), ctxt.get_desc( "CONTAINER" ),
585 ctxt.get_desc( "FLAG" ), ctxt.get_desc( "EVERYTHING" ),
586 ctxt.get_desc( "QUIT" ) );
587 wnoutrefresh( menu->window );
588 }
589 };
590
wishitem(player * p)591 void debug_menu::wishitem( player *p )
592 {
593 wishitem( p, tripoint( -1, -1, -1 ) );
594 }
595
wishitem(player * p,const tripoint & pos)596 void debug_menu::wishitem( player *p, const tripoint &pos )
597 {
598 if( p == nullptr && pos.x <= 0 ) {
599 debugmsg( "game::wishitem(): invalid parameters" );
600 return;
601 }
602 std::vector<std::pair<std::string, const itype *>> opts;
603 for( const itype *i : item_controller->all() ) {
604 opts.emplace_back( item( i, calendar::turn_zero ).tname( 1, false ), i );
605 }
606 std::sort( opts.begin(), opts.end(), localized_compare );
607 std::vector<const itype *> itypes;
608 std::transform( opts.begin(), opts.end(), std::back_inserter( itypes ),
609 []( const auto & pair ) {
610 return pair.second;
611 } );
612
613 int prev_amount = 1;
614 int amount = 1;
615 uilist wmenu;
616 wmenu.input_category = "WISH_ITEM";
617 wmenu.additional_actions = {
618 { "CONTAINER", translation() },
619 { "FLAG", translation() },
620 { "EVERYTHING", translation() }
621 };
622 wmenu.w_x_setup = 0;
623 wmenu.w_width_setup = []() -> int {
624 return TERMX;
625 };
626 wmenu.pad_right_setup = []() -> int {
627 return std::max( TERMX / 2, TERMX - 50 );
628 };
629 wmenu.selected = uistate.wishitem_selected;
630 wish_item_callback cb( itypes );
631 wmenu.callback = &cb;
632
633 for( size_t i = 0; i < opts.size(); i++ ) {
634 item ity( opts[i].second, calendar::turn_zero );
635 wmenu.addentry( i, true, 0, opts[i].first );
636 mvwzstr &entry_extra_text = wmenu.entries[i].extratxt;
637 entry_extra_text.txt = ity.symbol();
638 entry_extra_text.color = ity.color();
639 entry_extra_text.left = 1;
640 }
641 do {
642 wmenu.query();
643 if( cb.spawn_everything ) {
644 wmenu.ret = opts.size() - 1;
645 }
646 bool did_amount_prompt = false;
647 while( wmenu.ret >= 0 ) {
648 item granted = wishitem_produce( *opts[wmenu.ret].second, cb.flags, cb.incontainer ) ;
649
650 prev_amount = amount;
651 bool canceled = false;
652 if( p != nullptr && !did_amount_prompt ) {
653 string_input_popup popup;
654 popup
655 .title( _( "How many?" ) )
656 .width( 20 )
657 .description( granted.tname() )
658 .edit( amount );
659 canceled = popup.canceled();
660 }
661 if( !canceled ) {
662 did_amount_prompt = true;
663 if( p != nullptr ) {
664 if( granted.count_by_charges() ) {
665 if( amount > 0 ) {
666 granted.charges = amount;
667 if( p->can_stash( granted ) ) {
668 p->i_add( granted );
669 } else {
670 get_map().add_item_or_charges( p->pos(), granted );
671 }
672 }
673 } else {
674 for( int i = 0; i < amount; i++ ) {
675 if( p->can_stash( granted ) ) {
676 p->i_add( granted );
677 } else {
678 get_map().add_item_or_charges( p->pos(), granted );
679 }
680 }
681 }
682 p->invalidate_crafting_inventory();
683 } else if( pos.x >= 0 && pos.y >= 0 ) {
684 get_map().add_item_or_charges( pos, granted );
685 wmenu.ret = -1;
686 }
687 if( amount > 0 ) {
688 input_context ctxt( wmenu.input_category, keyboard_mode::keycode );
689 cb.msg = string_format( _( "Wish granted. Wish for more or hit [%s] to quit." ),
690 ctxt.get_desc( "QUIT" ) );
691 }
692 }
693 uistate.wishitem_selected = wmenu.selected;
694 if( canceled || amount <= 0 ) {
695 amount = prev_amount;
696 }
697 if( cb.spawn_everything ) {
698 wmenu.ret--;
699 } else {
700 break;
701 }
702 }
703 } while( wmenu.ret >= 0 );
704 }
705
706 /*
707 * Set skill on any player object; player character or NPC
708 */
wishskill(player * p)709 void debug_menu::wishskill( player *p )
710 {
711 const int skoffset = 1;
712 uilist skmenu;
713 skmenu.text = _( "Select a skill to modify" );
714 skmenu.allow_anykey = true;
715 skmenu.additional_actions = {
716 { "LEFT", to_translation( "Decrease skill" ) },
717 { "RIGHT", to_translation( "Increase skill" ) }
718 };
719 skmenu.addentry( 0, true, '1', _( "Modify all skills…" ) );
720
721 auto sorted_skills = Skill::get_skills_sorted_by( []( const Skill & a, const Skill & b ) {
722 return localized_compare( a.name(), b.name() );
723 } );
724
725 std::vector<int> origskills;
726 origskills.reserve( sorted_skills.size() );
727
728 for( const auto &s : sorted_skills ) {
729 const int level = p->get_skill_level( s->ident() );
730 skmenu.addentry( origskills.size() + skoffset, true, -2, _( "@ %d: %s " ), level,
731 s->name() );
732 origskills.push_back( level );
733 }
734
735 shared_ptr_fast<ui_adaptor> skmenu_ui = skmenu.create_or_get_ui_adaptor();
736
737 do {
738 skmenu.query();
739 int skill_id = -1;
740 int skset = -1;
741 const int sksel = skmenu.selected - skoffset;
742 if( skmenu.ret == UILIST_UNBOUND && ( skmenu.ret_act == "LEFT" ||
743 skmenu.ret_act == "RIGHT" ) ) {
744 if( sksel >= 0 && sksel < static_cast<int>( sorted_skills.size() ) ) {
745 skill_id = sksel;
746 skset = p->get_skill_level( sorted_skills[skill_id]->ident() ) +
747 ( skmenu.ret_act == "LEFT" ? -1 : 1 );
748 }
749 } else if( skmenu.ret >= 0 && sksel >= 0 &&
750 sksel < static_cast<int>( sorted_skills.size() ) ) {
751 skill_id = sksel;
752 const Skill &skill = *sorted_skills[skill_id];
753 const int NUM_SKILL_LVL = 21;
754 uilist sksetmenu;
755 sksetmenu.w_height_setup = NUM_SKILL_LVL + 4;
756 sksetmenu.w_x_setup = [&]( int ) -> int {
757 return skmenu.w_x + skmenu.w_width + 1;
758 };
759 sksetmenu.w_y_setup = [&]( const int height ) {
760 return std::max( 0, skmenu.w_y + ( skmenu.w_height - height ) / 2 );
761 };
762 sksetmenu.settext( string_format( _( "Set '%s' to…" ), skill.name() ) );
763 const int skcur = p->get_skill_level( skill.ident() );
764 sksetmenu.selected = skcur;
765 for( int i = 0; i < NUM_SKILL_LVL; i++ ) {
766 sksetmenu.addentry( i, true, i + 48, "%d%s", i, skcur == i ? _( " (current)" ) : "" );
767 }
768 sksetmenu.query();
769 skset = sksetmenu.ret;
770 }
771
772 if( skill_id >= 0 && skset >= 0 ) {
773 const Skill &skill = *sorted_skills[skill_id];
774 p->set_skill_level( skill.ident(), skset );
775 skmenu.textformatted[0] = string_format( _( "%s set to %d " ),
776 skill.name(),
777 p->get_skill_level( skill.ident() ) ).substr( 0, skmenu.w_width - 4 );
778 skmenu.entries[skill_id + skoffset].txt = string_format( _( "@ %d: %s " ),
779 p->get_skill_level( skill.ident() ),
780 skill.name() );
781 skmenu.entries[skill_id + skoffset].text_color =
782 p->get_skill_level( skill.ident() ) == origskills[skill_id] ?
783 skmenu.text_color : c_yellow;
784 } else if( skmenu.ret == 0 && sksel == -1 ) {
785 const int ret = uilist( _( "Alter all skill values" ), {
786 _( "Add 3" ), _( "Add 1" ),
787 _( "Subtract 1" ), _( "Subtract 3" ), _( "Set to 0" ),
788 _( "Set to 5" ), _( "Set to 10" ), _( "(Reset changes)" )
789 } );
790 if( ret >= 0 ) {
791 int skmod = 0;
792 int skset = -1;
793 if( ret < 4 ) {
794 skmod = 3 - ret * 2;
795 } else if( ret < 7 ) {
796 skset = ( ret - 4 ) * 5;
797 }
798 for( size_t skill_id = 0; skill_id < sorted_skills.size(); skill_id++ ) {
799 const Skill &skill = *sorted_skills[skill_id];
800 int changeto = skmod != 0 ? p->get_skill_level( skill.ident() ) + skmod :
801 skset != -1 ? skset : origskills[skill_id];
802 p->set_skill_level( skill.ident(), std::max( 0, changeto ) );
803 skmenu.entries[skill_id + skoffset].txt = string_format( _( "@ %d: %s " ),
804 p->get_skill_level( skill.ident() ),
805 skill.name() );
806 p->get_skill_level_object( skill.ident() ).practice();
807 skmenu.entries[skill_id + skoffset].text_color =
808 p->get_skill_level( skill.ident() ) == origskills[skill_id] ? skmenu.text_color : c_yellow;
809 }
810 }
811 }
812 } while( skmenu.ret != UILIST_CANCEL );
813 }
814
815 /*
816 * Set proficiency on any player object; player character or NPC
817 */
wishproficiency(player * p)818 void debug_menu::wishproficiency( player *p )
819 {
820 bool know_all = true;
821 const int proffset = 1;
822
823 uilist prmenu;
824 prmenu.text = _( "Select proficiency to toggle" );
825 prmenu.allow_anykey = true;
826 prmenu.addentry( 0, true, '1', _( "Toggle all proficiencies" ) );
827
828 const std::vector<proficiency_id> &known_profs = p->known_proficiencies();
829 std::vector<std::pair<proficiency_id, bool>> sorted_profs;
830
831 for( const proficiency &cur : proficiency::get_all() ) {
832
833 const auto iterator = std::find_if( known_profs.begin(), known_profs.end(),
834 [&cur]( proficiency_id prof_id ) {
835 return cur.prof_id() == prof_id;
836 } );
837
838 const bool player_know = iterator != known_profs.end();
839
840 // Does the player know all proficiencies
841 if( know_all ) {
842 know_all = player_know;
843 }
844
845 sorted_profs.push_back( { cur.prof_id(), player_know } );
846 }
847
848 std::sort( sorted_profs.begin(), sorted_profs.end(), localized_compare );
849
850 for( size_t i = 0; i < sorted_profs.size(); ++i ) {
851 if( sorted_profs[i].second ) {
852 prmenu.addentry( i + proffset, true, -2, _( "(known) %s" ),
853 sorted_profs[i].first->name() );
854 prmenu.entries[i + proffset].text_color = c_yellow;
855 } else {
856 prmenu.addentry( i + proffset, true, -2, _( "%s" ),
857 sorted_profs[i].first->name() );
858 prmenu.entries[i + proffset].text_color = prmenu.text_color;
859 }
860 }
861
862 do {
863 prmenu.query();
864 const int prsel = prmenu.ret;
865 if( prsel == 0 ) {
866 // if the player knows everything, unlearn everything
867 if( know_all ) {
868 for( size_t i = 0; i < sorted_profs.size(); ++i ) {
869 std::pair<proficiency_id, bool> &cur = sorted_profs[i];
870 cur.second = false;
871 prmenu.entries[i + proffset].txt = string_format( "%s", cur.first->name() );
872 prmenu.entries[i + proffset].text_color = prmenu.text_color;
873 p->lose_proficiency( cur.first, true );
874 }
875 know_all = false;
876 } else {
877 for( size_t i = 0; i < sorted_profs.size(); ++i ) {
878 std::pair<proficiency_id, bool> &cur = sorted_profs[i];
879
880 if( !cur.second ) {
881 cur.second = true;
882 prmenu.entries[i + proffset].txt = string_format( _( "(known) %s" ), cur.first->name() );
883 prmenu.entries[i + proffset].text_color = c_yellow;
884 p->add_proficiency( cur.first, true );
885 }
886 }
887 know_all = true;
888 }
889 } else if( prsel > 0 ) {
890 std::pair<proficiency_id, bool> &cur = sorted_profs[prsel - proffset];
891 // if the player didn't know it before now it does
892 // if the player knew it before, unlearn proficiency
893 bool know_prof = !cur.second;
894 proficiency_id &prof = cur.first;
895
896 cur.second = know_prof;
897
898 if( know_prof ) {
899 prmenu.entries[prmenu.selected].txt = string_format( _( "(known) %s" ), cur.first->name() );
900 prmenu.entries[prmenu.selected].text_color = c_yellow;
901 p->add_msg_if_player( m_good, _( "You are now proficient in %s!" ), prof->name() );
902 p->add_proficiency( prof, true );
903 continue;
904 }
905
906 know_all = false;
907 prmenu.entries[prmenu.selected].txt = string_format( "%s", cur.first->name() );
908 prmenu.entries[prmenu.selected].text_color = prmenu.text_color;
909 p->add_msg_if_player( m_bad, _( "You are no longer proficient in %s." ), prof->name() );
910 p->lose_proficiency( prof, true );
911 }
912 } while( prmenu.ret != UILIST_CANCEL );
913 }
914