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