1 #include "debug_menu.h"
2 
3 #include <cstdint>
4 // IWYU pragma: no_include <sys/signal.h>
5 // IWYU pragma: no_include <cxxabi.h>
6 
7 #include <algorithm>
8 #include <array>
9 #include <chrono>
10 #include <csignal>
11 #include <cstdlib>
12 #include <iomanip> // IWYU pragma: keep
13 #include <iostream>
14 #include <iterator>
15 #include <limits>
16 #include <list>
17 #include <map>
18 #include <memory>
19 #include <new>
20 #include <sstream>
21 #include <string>
22 #include <type_traits>
23 #include <unordered_map>
24 #include <unordered_set>
25 #include <utility>
26 #include <vector>
27 
28 #include "achievement.h"
29 #include "action.h"
30 #include "avatar.h"
31 #include "bodypart.h"
32 #include "calendar.h"
33 #include "cata_utility.h"
34 #include "catacharset.h"
35 #include "character.h"
36 #include "character_id.h"
37 #include "character_martial_arts.h"
38 #include "clzones.h"
39 #include "color.h"
40 #include "coordinates.h"
41 #include "creature.h"
42 #include "debug.h"
43 #include "dialogue_chatbin.h"
44 #include "effect.h"
45 #include "effect_source.h"
46 #include "enum_conversions.h"
47 #include "enums.h"
48 #include "event.h"
49 #include "event_bus.h"
50 #include "faction.h"
51 #include "filesystem.h" // IWYU pragma: keep
52 #include "game.h"
53 #include "game_constants.h"
54 #include "game_inventory.h"
55 #include "input.h"
56 #include "inventory.h"
57 #include "item.h"
58 #include "item_group.h"
59 #include "item_location.h"
60 #include "itype.h"
61 #include "location.h"
62 #include "magic.h"
63 #include "map.h"
64 #include "map_extras.h"
65 #include "mapgen.h"
66 #include "mapgendata.h"
67 #include "martialarts.h"
68 #include "memory_fast.h"
69 #include "messages.h"
70 #include "mission.h"
71 #include "monster.h"
72 #include "monstergenerator.h"
73 #include "morale_types.h"
74 #include "mtype.h"
75 #include "npc.h"
76 #include "npc_class.h"
77 #include "omdata.h"
78 #include "optional.h"
79 #include "options.h"
80 #include "output.h"
81 #include "overmap.h"
82 #include "overmap_ui.h"
83 #include "overmapbuffer.h"
84 #include "path_info.h" // IWYU pragma: keep
85 #include "pimpl.h"
86 #include "player.h"
87 #include "point.h"
88 #include "popup.h"
89 #include "recipe_dictionary.h"
90 #include "rng.h"
91 #include "sounds.h"
92 #include "stomach.h"
93 #include "string_formatter.h"
94 #include "string_input_popup.h"
95 #include "trait_group.h"
96 #include "translations.h"
97 #include "type_id.h"
98 #include "ui.h"
99 #include "ui_manager.h"
100 #include "units.h"
101 #include "veh_type.h"
102 #include "vehicle.h"
103 #include "vitamin.h"
104 #include "vpart_position.h"
105 #include "weather.h"
106 #include "weather_gen.h"
107 #include "weather_type.h"
108 #include "weighted_list.h"
109 
110 static const efftype_id effect_asthma( "asthma" );
111 static const efftype_id effect_flu( "flu" );
112 
113 static const mtype_id mon_generator( "mon_generator" );
114 
115 static const trait_id trait_ASTHMA( "ASTHMA" );
116 
117 #if defined(TILES)
118 #include "sdl_wrappers.h"
119 #endif
120 
121 #define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": "
122 
123 namespace io
124 {
125 
126 template<>
enum_to_string(debug_menu::debug_menu_index v)127 std::string enum_to_string<debug_menu::debug_menu_index>( debug_menu::debug_menu_index v )
128 {
129     switch( v ) {
130         // *INDENT-OFF*
131         case debug_menu::debug_menu_index::WISH: return "WISH";
132         case debug_menu::debug_menu_index::SHORT_TELEPORT: return "SHORT_TELEPORT";
133         case debug_menu::debug_menu_index::LONG_TELEPORT: return "LONG_TELEPORT";
134         case debug_menu::debug_menu_index::REVEAL_MAP: return "REVEAL_MAP";
135         case debug_menu::debug_menu_index::SPAWN_NPC: return "SPAWN_NPC";
136         case debug_menu::debug_menu_index::SPAWN_MON: return "SPAWN_MON";
137         case debug_menu::debug_menu_index::GAME_STATE: return "GAME_STATE";
138         case debug_menu::debug_menu_index::KILL_NPCS: return "KILL_NPCS";
139         case debug_menu::debug_menu_index::MUTATE: return "MUTATE";
140         case debug_menu::debug_menu_index::SPAWN_VEHICLE: return "SPAWN_VEHICLE";
141         case debug_menu::debug_menu_index::CHANGE_SKILLS: return "CHANGE_SKILLS";
142         case debug_menu::debug_menu_index::LEARN_MA: return "LEARN_MA";
143         case debug_menu::debug_menu_index::UNLOCK_RECIPES: return "UNLOCK_RECIPES";
144         case debug_menu::debug_menu_index::EDIT_PLAYER: return "EDIT_PLAYER";
145         case debug_menu::debug_menu_index::SPAWN_ARTIFACT: return "SPAWN_ARTIFACT";
146         case debug_menu::debug_menu_index::SPAWN_CLAIRVOYANCE: return "SPAWN_CLAIRVOYANCE";
147         case debug_menu::debug_menu_index::MAP_EDITOR: return "MAP_EDITOR";
148         case debug_menu::debug_menu_index::CHANGE_WEATHER: return "CHANGE_WEATHER";
149         case debug_menu::debug_menu_index::WIND_DIRECTION: return "WIND_DIRECTION";
150         case debug_menu::debug_menu_index::WIND_SPEED: return "WIND_SPEED";
151         case debug_menu::debug_menu_index::GEN_SOUND: return "GEN_SOUND";
152         case debug_menu::debug_menu_index::KILL_MONS: return "KILL_MONS";
153         case debug_menu::debug_menu_index::DISPLAY_HORDES: return "DISPLAY_HORDES";
154         case debug_menu::debug_menu_index::TEST_IT_GROUP: return "TEST_IT_GROUP";
155         case debug_menu::debug_menu_index::DAMAGE_SELF: return "DAMAGE_SELF";
156         case debug_menu::debug_menu_index::BLEED_SELF: return "BLEED_SELF";
157         case debug_menu::debug_menu_index::SHOW_SOUND: return "SHOW_SOUND";
158         case debug_menu::debug_menu_index::DISPLAY_WEATHER: return "DISPLAY_WEATHER";
159         case debug_menu::debug_menu_index::DISPLAY_SCENTS: return "DISPLAY_SCENTS";
160         case debug_menu::debug_menu_index::CHANGE_TIME: return "CHANGE_TIME";
161         case debug_menu::debug_menu_index::SET_AUTOMOVE: return "SET_AUTOMOVE";
162         case debug_menu::debug_menu_index::SHOW_MUT_CAT: return "SHOW_MUT_CAT";
163         case debug_menu::debug_menu_index::OM_EDITOR: return "OM_EDITOR";
164         case debug_menu::debug_menu_index::BENCHMARK: return "BENCHMARK";
165         case debug_menu::debug_menu_index::OM_TELEPORT: return "OM_TELEPORT";
166         case debug_menu::debug_menu_index::OM_TELEPORT_COORDINATES: return "OM_TELEPORT_COORDINATES";
167         case debug_menu::debug_menu_index::TRAIT_GROUP: return "TRAIT_GROUP";
168         case debug_menu::debug_menu_index::ENABLE_ACHIEVEMENTS: return "ENABLE_ACHIEVEMENTS";
169         case debug_menu::debug_menu_index::SHOW_MSG: return "SHOW_MSG";
170         case debug_menu::debug_menu_index::CRASH_GAME: return "CRASH_GAME";
171         case debug_menu::debug_menu_index::MAP_EXTRA: return "MAP_EXTRA";
172         case debug_menu::debug_menu_index::DISPLAY_NPC_PATH: return "DISPLAY_NPC_PATH";
173         case debug_menu::debug_menu_index::PRINT_FACTION_INFO: return "PRINT_FACTION_INFO";
174         case debug_menu::debug_menu_index::PRINT_NPC_MAGIC: return "PRINT_NPC_MAGIC";
175         case debug_menu::debug_menu_index::QUIT_NOSAVE: return "QUIT_NOSAVE";
176         case debug_menu::debug_menu_index::TEST_WEATHER: return "TEST_WEATHER";
177         case debug_menu::debug_menu_index::SAVE_SCREENSHOT: return "SAVE_SCREENSHOT";
178         case debug_menu::debug_menu_index::GAME_REPORT: return "GAME_REPORT";
179         case debug_menu::debug_menu_index::DISPLAY_SCENTS_LOCAL: return "DISPLAY_SCENTS_LOCAL";
180         case debug_menu::debug_menu_index::DISPLAY_SCENTS_TYPE_LOCAL: return "DISPLAY_SCENTS_TYPE_LOCAL";
181         case debug_menu::debug_menu_index::DISPLAY_TEMP: return "DISPLAY_TEMP";
182         case debug_menu::debug_menu_index::DISPLAY_VEHICLE_AI: return "DISPLAY_VEHICLE_AI";
183         case debug_menu::debug_menu_index::DISPLAY_VISIBILITY: return "DISPLAY_VISIBILITY";
184         case debug_menu::debug_menu_index::DISPLAY_LIGHTING: return "DISPLAY_LIGHTING";
185         case debug_menu::debug_menu_index::DISPLAY_TRANSPARENCY: return "DISPLAY_TRANSPARENCY";
186         case debug_menu::debug_menu_index::DISPLAY_REACHABILITY_ZONES: return "DISPLAY_REACHABILITY_ZONES";
187         case debug_menu::debug_menu_index::DISPLAY_RADIATION: return "DISPLAY_RADIATION";
188         case debug_menu::debug_menu_index::HOUR_TIMER: return "HOUR_TIMER";
189         case debug_menu::debug_menu_index::LEARN_SPELLS: return "LEARN_SPELLS";
190         case debug_menu::debug_menu_index::LEVEL_SPELLS: return "LEVEL_SPELLS";
191         case debug_menu::debug_menu_index::TEST_MAP_EXTRA_DISTRIBUTION: return "TEST_MAP_EXTRA_DISTRIBUTION";
192         case debug_menu::debug_menu_index::NESTED_MAPGEN: return "NESTED_MAPGEN";
193         case debug_menu::debug_menu_index::VEHICLE_BATTERY_CHARGE: return "VEHICLE_BATTERY_CHARGE";
194         case debug_menu::debug_menu_index::GENERATE_EFFECT_LIST: return "GENERATE_EFFECT_LIST";
195         // *INDENT-ON*
196         case debug_menu::debug_menu_index::last:
197             break;
198     }
199     debugmsg( "unknown debug_menu::debug_menu_index %d", static_cast<int>( v ) );
200     return "";
201 }
202 
203 } // namespace io
204 
205 namespace debug_menu
206 {
207 
208 class mission_debug
209 {
210     private:
211         // Doesn't actually "destroy" the mission, just removes assignments
212         static void remove_mission( mission &m );
213     public:
214         static void edit_mission( mission &m );
215         static void edit( player &who );
216         static void edit_player();
217         static void edit_npc( npc &who );
218         static std::string describe( const mission &m );
219 };
220 
player_uilist()221 static int player_uilist()
222 {
223     std::vector<uilist_entry> uilist_initializer = {
224         { uilist_entry( debug_menu_index::MUTATE, true, 'M', _( "Mutate" ) ) },
225         { uilist_entry( debug_menu_index::CHANGE_SKILLS, true, 's', _( "Change all skills" ) ) },
226         { uilist_entry( debug_menu_index::LEARN_MA, true, 'l', _( "Learn all melee styles" ) ) },
227         { uilist_entry( debug_menu_index::UNLOCK_RECIPES, true, 'r', _( "Unlock all recipes" ) ) },
228         { uilist_entry( debug_menu_index::EDIT_PLAYER, true, 'p', _( "Edit player/NPC" ) ) },
229         { uilist_entry( debug_menu_index::DAMAGE_SELF, true, 'd', _( "Damage self" ) ) },
230         { uilist_entry( debug_menu_index::BLEED_SELF, true, 'b', _( "Bleed self" ) ) },
231         { uilist_entry( debug_menu_index::SET_AUTOMOVE, true, 'a', _( "Set automove route" ) ) },
232     };
233     if( !spell_type::get_all().empty() ) {
234         uilist_initializer.emplace_back( uilist_entry( debug_menu_index::LEARN_SPELLS, true, 'S',
235                                          _( "Learn all spells" ) ) );
236         uilist_initializer.emplace_back( uilist_entry( debug_menu_index::LEVEL_SPELLS, true, 'L',
237                                          _( "Level a spell" ) ) );
238     }
239 
240     return uilist( _( "Player…" ), uilist_initializer );
241 }
242 
info_uilist(bool display_all_entries=true)243 static int info_uilist( bool display_all_entries = true )
244 {
245     // always displayed
246     std::vector<uilist_entry> uilist_initializer = {
247         { uilist_entry( debug_menu_index::SAVE_SCREENSHOT, true, 'H', _( "Take screenshot" ) ) },
248         { uilist_entry( debug_menu_index::GAME_REPORT, true, 'r', _( "Generate game report" ) ) },
249     };
250 
251     if( display_all_entries ) {
252         const std::vector<uilist_entry> debug_only_options = {
253             { uilist_entry( debug_menu_index::GAME_STATE, true, 'g', _( "Check game state" ) ) },
254             { uilist_entry( debug_menu_index::DISPLAY_HORDES, true, 'h', _( "Display hordes" ) ) },
255             { uilist_entry( debug_menu_index::TEST_IT_GROUP, true, 'i', _( "Test item group" ) ) },
256             { uilist_entry( debug_menu_index::SHOW_SOUND, true, 'c', _( "Show sound clustering" ) ) },
257             { uilist_entry( debug_menu_index::DISPLAY_WEATHER, true, 'w', _( "Display weather" ) ) },
258             { uilist_entry( debug_menu_index::DISPLAY_SCENTS, true, 'S', _( "Display overmap scents" ) ) },
259             { uilist_entry( debug_menu_index::DISPLAY_SCENTS_LOCAL, true, 's', _( "Toggle display local scents" ) ) },
260             { uilist_entry( debug_menu_index::DISPLAY_SCENTS_TYPE_LOCAL, true, 'y', _( "Toggle display local scents type" ) ) },
261             { uilist_entry( debug_menu_index::DISPLAY_TEMP, true, 'T', _( "Toggle display temperature" ) ) },
262             { uilist_entry( debug_menu_index::DISPLAY_VEHICLE_AI, true, 'V', _( "Toggle display vehicle autopilot overlay" ) ) },
263             { uilist_entry( debug_menu_index::DISPLAY_VISIBILITY, true, 'v', _( "Toggle display visibility" ) ) },
264             { uilist_entry( debug_menu_index::DISPLAY_LIGHTING, true, 'l', _( "Toggle display lighting" ) ) },
265             { uilist_entry( debug_menu_index::DISPLAY_TRANSPARENCY, true, 'p', _( "Toggle display transparency" ) ) },
266             { uilist_entry( debug_menu_index::DISPLAY_REACHABILITY_ZONES, true, 'z', _( "Toggle display reachability zones" ) ) },
267             { uilist_entry( debug_menu_index::DISPLAY_RADIATION, true, 'R', _( "Toggle display radiation" ) ) },
268             { uilist_entry( debug_menu_index::SHOW_MUT_CAT, true, 'm', _( "Show mutation category levels" ) ) },
269             { uilist_entry( debug_menu_index::BENCHMARK, true, 'b', _( "Draw benchmark (X seconds)" ) ) },
270             { uilist_entry( debug_menu_index::HOUR_TIMER, true, 'E', _( "Toggle hour timer" ) ) },
271             { uilist_entry( debug_menu_index::TRAIT_GROUP, true, 't', _( "Test trait group" ) ) },
272             { uilist_entry( debug_menu_index::DISPLAY_NPC_PATH, true, 'n', _( "Toggle NPC pathfinding on map" ) ) },
273             { uilist_entry( debug_menu_index::PRINT_FACTION_INFO, true, 'f', _( "Print faction info to console" ) ) },
274             { uilist_entry( debug_menu_index::PRINT_NPC_MAGIC, true, 'M', _( "Print NPC magic info to console" ) ) },
275             { uilist_entry( debug_menu_index::TEST_WEATHER, true, 'W', _( "Test weather" ) ) },
276             { uilist_entry( debug_menu_index::TEST_MAP_EXTRA_DISTRIBUTION, true, 'e', _( "Test map extra list" ) ) },
277             { uilist_entry( debug_menu_index::GENERATE_EFFECT_LIST, true, 'L', _( "Generate effect list" ) ) },
278         };
279         uilist_initializer.insert( uilist_initializer.begin(), debug_only_options.begin(),
280                                    debug_only_options.end() );
281     }
282 
283     return uilist( _( "Info…" ), uilist_initializer );
284 }
285 
game_uilist()286 static int game_uilist()
287 {
288     std::vector<uilist_entry> uilist_initializer = {
289         { uilist_entry( debug_menu_index::ENABLE_ACHIEVEMENTS, true, 'a', _( "Enable achievements" ) ) },
290         { uilist_entry( debug_menu_index::SHOW_MSG, true, 'd', _( "Show debug message" ) ) },
291         { uilist_entry( debug_menu_index::CRASH_GAME, true, 'C', _( "Crash game (test crash handling)" ) ) },
292         { uilist_entry( debug_menu_index::QUIT_NOSAVE, true, 'Q', _( "Quit to main menu" ) )  },
293     };
294 
295     return uilist( _( "Game…" ), uilist_initializer );
296 }
297 
vehicle_uilist()298 static int vehicle_uilist()
299 {
300     std::vector<uilist_entry> uilist_initializer = {
301         { uilist_entry( debug_menu_index::VEHICLE_BATTERY_CHARGE, true, 'b', _( "Change [b]attery charge" ) ) },
302     };
303 
304     return uilist( _( "Vehicle…" ), uilist_initializer );
305 }
306 
teleport_uilist()307 static int teleport_uilist()
308 {
309     const std::vector<uilist_entry> uilist_initializer = {
310         { uilist_entry( debug_menu_index::SHORT_TELEPORT, true, 's', _( "Teleport - short range" ) ) },
311         { uilist_entry( debug_menu_index::LONG_TELEPORT, true, 'l', _( "Teleport - long range" ) ) },
312         { uilist_entry( debug_menu_index::OM_TELEPORT, true, 'o', _( "Teleport - adjacent overmap" ) ) },
313         { uilist_entry( debug_menu_index::OM_TELEPORT_COORDINATES, true, 'p', _( "Teleport - specific overmap coordinates" ) ) },
314     };
315 
316     return uilist( _( "Teleport…" ), uilist_initializer );
317 }
318 
spawning_uilist()319 static int spawning_uilist()
320 {
321     const std::vector<uilist_entry> uilist_initializer = {
322         { uilist_entry( debug_menu_index::WISH, true, 'w', _( "Spawn an item" ) ) },
323         { uilist_entry( debug_menu_index::SPAWN_NPC, true, 'n', _( "Spawn NPC" ) ) },
324         { uilist_entry( debug_menu_index::SPAWN_MON, true, 'm', _( "Spawn monster" ) ) },
325         { uilist_entry( debug_menu_index::SPAWN_VEHICLE, true, 'v', _( "Spawn a vehicle" ) ) },
326         { uilist_entry( debug_menu_index::SPAWN_ARTIFACT, true, 'a', _( "Spawn artifact" ) ) },
327         { uilist_entry( debug_menu_index::SPAWN_CLAIRVOYANCE, true, 'c', _( "Spawn clairvoyance artifact" ) ) },
328     };
329 
330     return uilist( _( "Spawning…" ), uilist_initializer );
331 }
332 
map_uilist()333 static int map_uilist()
334 {
335     const std::vector<uilist_entry> uilist_initializer = {
336         { uilist_entry( debug_menu_index::REVEAL_MAP, true, 'r', _( "Reveal map" ) ) },
337         { uilist_entry( debug_menu_index::KILL_NPCS, true, 'k', _( "Kill NPCs" ) ) },
338         { uilist_entry( debug_menu_index::MAP_EDITOR, true, 'M', _( "Map editor" ) ) },
339         { uilist_entry( debug_menu_index::CHANGE_WEATHER, true, 'w', _( "Change weather" ) ) },
340         { uilist_entry( debug_menu_index::WIND_DIRECTION, true, 'd', _( "Change wind direction" ) ) },
341         { uilist_entry( debug_menu_index::WIND_SPEED, true, 's', _( "Change wind speed" ) ) },
342         { uilist_entry( debug_menu_index::GEN_SOUND, true, 'S', _( "Generate sound" ) ) },
343         { uilist_entry( debug_menu_index::KILL_MONS, true, 'K', _( "Kill all monsters" ) ) },
344         { uilist_entry( debug_menu_index::CHANGE_TIME, true, 't', _( "Change time" ) ) },
345         { uilist_entry( debug_menu_index::OM_EDITOR, true, 'O', _( "Overmap editor" ) ) },
346         { uilist_entry( debug_menu_index::MAP_EXTRA, true, 'm', _( "Spawn map extra" ) ) },
347         { uilist_entry( debug_menu_index::NESTED_MAPGEN, true, 'n', _( "Spawn nested mapgen" ) ) },
348     };
349 
350     return uilist( _( "Map…" ), uilist_initializer );
351 }
352 
353 /**
354  * Create the debug menu UI list.
355  * @param display_all_entries: `true` if all entries should be displayed, `false` is some entries should be hidden (for ex. when the debug menu is called from the main menu).
356  *   This allows to have some menu elements at the same time in the main menu and in the debug menu.
357  * @returns The chosen action.
358  */
debug_menu_uilist(bool display_all_entries=true)359 static cata::optional<debug_menu_index> debug_menu_uilist( bool display_all_entries = true )
360 {
361     std::vector<uilist_entry> menu = {
362         { uilist_entry( 1, true, 'i', _( "Info…" ) ) },
363     };
364 
365     if( display_all_entries ) {
366         const std::vector<uilist_entry> debug_menu = {
367             { uilist_entry( 6, true, 'g', _( "Game…" ) ) },
368             { uilist_entry( 2, true, 's', _( "Spawning…" ) ) },
369             { uilist_entry( 3, true, 'p', _( "Player…" ) ) },
370             { uilist_entry( 7, true, 'v', _( "Vehicle…" ) ) },
371             { uilist_entry( 4, true, 't', _( "Teleport…" ) ) },
372             { uilist_entry( 5, true, 'm', _( "Map…" ) ) },
373         };
374 
375         // insert debug-only menu right after "Info".
376         menu.insert( menu.begin() + 1, debug_menu.begin(), debug_menu.end() );
377     }
378 
379     std::string msg;
380     if( display_all_entries ) {
381         msg = _( "Debug Functions - Using these will cheat not only the game, but yourself.\nYou won't grow.  You won't improve.\nTaking this shortcut will gain you nothing.  Your victory will be hollow.\nNothing will be risked and nothing will be gained." );
382     } else {
383         msg = _( "Debug Functions" );
384     }
385 
386     while( true ) {
387         const int group = uilist( msg, menu );
388 
389         int action;
390 
391         switch( group ) {
392             case 1:
393                 action = info_uilist( display_all_entries );
394                 break;
395             case 2:
396                 action = spawning_uilist();
397                 break;
398             case 3:
399                 action = player_uilist();
400                 break;
401             case 4:
402                 action = teleport_uilist();
403                 break;
404             case 5:
405                 action = map_uilist();
406                 break;
407             case 6:
408                 action = game_uilist();
409                 break;
410             case 7:
411                 action = vehicle_uilist();
412                 break;
413 
414             default:
415                 return cata::nullopt;
416         }
417         if( action >= 0 ) {
418             return static_cast<debug_menu_index>( action );
419         } else {
420             return cata::nullopt;
421         }
422     }
423 }
424 
teleport_short()425 void teleport_short()
426 {
427     const cata::optional<tripoint> where = g->look_around();
428     location &player_location = get_player_location();
429     if( !where || *where == player_location.pos() ) {
430         return;
431     }
432     g->place_player( *where );
433     const tripoint new_pos( player_location.pos() );
434     add_msg( _( "You teleport to point (%d,%d,%d)." ), new_pos.x, new_pos.y, new_pos.z );
435 }
436 
teleport_long()437 void teleport_long()
438 {
439     const tripoint_abs_omt where( ui::omap::choose_point() );
440     if( where == overmap::invalid_tripoint ) {
441         return;
442     }
443     g->place_player_overmap( where );
444     add_msg( _( "You teleport to submap (%s)." ), where.to_string() );
445 }
446 
teleport_overmap(bool specific_coordinates)447 void teleport_overmap( bool specific_coordinates )
448 {
449     Character &player_character = get_player_character();
450     tripoint_abs_omt where;
451     if( specific_coordinates ) {
452         const std::string text = string_input_popup()
453                                  .title( "Teleport where?" )
454                                  .width( 20 )
455                                  .query_string();
456         if( text.empty() ) {
457             return;
458         }
459         const std::vector<std::string> coord_strings = string_split( text, ',' );
460         tripoint coord;
461         coord.x = !coord_strings.empty() ? std::atoi( coord_strings[0].c_str() ) : 0;
462         coord.y = coord_strings.size() >= 2 ? std::atoi( coord_strings[1].c_str() ) : 0;
463         coord.z = coord_strings.size() >= 3 ? std::atoi( coord_strings[2].c_str() ) : 0;
464         where = tripoint_abs_omt( OMAPX * coord.x, OMAPY * coord.y, coord.z );
465     } else {
466         const cata::optional<tripoint> dir_ = choose_direction( _( "Where is the desired overmap?" ) );
467         if( !dir_ ) {
468             return;
469         }
470         const tripoint offset = tripoint( OMAPX * dir_->x, OMAPY * dir_->y, dir_->z );
471         where = player_character.global_omt_location() + offset;
472     }
473     g->place_player_overmap( where );
474 
475     const tripoint_abs_om new_pos =
476         project_to<coords::om>( player_character.global_omt_location() );
477     add_msg( _( "You teleport to overmap %s." ), new_pos.to_string() );
478 }
479 
spawn_nested_mapgen()480 void spawn_nested_mapgen()
481 {
482     uilist nest_menu;
483     std::vector<std::string> nest_str;
484     for( auto &nested : nested_mapgen ) {
485         nest_menu.addentry( -1, true, -1, nested.first );
486         nest_str.push_back( nested.first );
487     }
488     nest_menu.query();
489     const int nest_choice = nest_menu.ret;
490     if( nest_choice >= 0 && nest_choice < static_cast<int>( nest_str.size() ) ) {
491         const cata::optional<tripoint> where = g->look_around();
492         if( !where ) {
493             return;
494         }
495 
496         map &here = get_map();
497         const tripoint_abs_ms abs_ms( here.getabs( *where ) );
498         const tripoint_abs_omt abs_omt = project_to<coords::omt>( abs_ms );
499         const tripoint_abs_sm abs_sub = project_to<coords::sm>( abs_ms );
500 
501         map target_map;
502         target_map.load( abs_sub, true );
503         // TODO: fix point types
504         const tripoint local_ms = target_map.getlocal( abs_ms.raw() );
505         mapgendata md( abs_omt, target_map, 0.0f, calendar::turn, nullptr );
506         const auto &ptr = nested_mapgen[nest_str[nest_choice]].pick();
507         if( ptr == nullptr ) {
508             return;
509         }
510         ( *ptr )->nest( md, local_ms.xy() );
511         target_map.save();
512         g->load_npcs();
513         here.invalidate_map_cache( here.get_abs_sub().z );
514     }
515 }
516 
character_edit_menu()517 void character_edit_menu()
518 {
519     std::vector< tripoint > locations;
520     uilist charmenu;
521     int charnum = 0;
522     avatar &player_character = get_avatar();
523     charmenu.addentry( charnum++, true, MENU_AUTOASSIGN, "%s", _( "You" ) );
524     locations.emplace_back( player_character.pos() );
525     for( const npc &guy : g->all_npcs() ) {
526         charmenu.addentry( charnum++, true, MENU_AUTOASSIGN, guy.name );
527         locations.emplace_back( guy.pos() );
528     }
529 
530     pointmenu_cb callback( locations );
531     charmenu.callback = &callback;
532     charmenu.w_y_setup = 0;
533     charmenu.query();
534     if( charmenu.ret < 0 || static_cast<size_t>( charmenu.ret ) >= locations.size() ) {
535         return;
536     }
537     const size_t index = charmenu.ret;
538     // The NPC is also required for "Add mission", so has to be in this scope
539     npc *np = g->critter_at<npc>( locations[index], false );
540     player &p = np ? *np->as_player() : *player_character.as_player();
541     uilist nmenu;
542 
543     if( np != nullptr ) {
544         std::stringstream data;
545         data << np->name << " " << ( np->male ? _( "Male" ) : _( "Female" ) ) << std::endl;
546         data << np->myclass.obj().get_name() << "; " <<
547              npc_attitude_name( np->get_attitude() ) << "; " <<
548              ( np->get_faction() ? np->get_faction()->name : _( "no faction" ) ) << "; " <<
549              ( np->get_faction() ? np->get_faction()->currency->nname( 1 ) : _( "no currency" ) )
550              << "; " <<
551              "api: " << np->get_faction_ver() << std::endl;
552         if( np->has_destination() ) {
553             data << string_format(
554                      _( "Destination: %s %s" ), np->goal.to_string(),
555                      overmap_buffer.ter( np->goal )->get_name() ) << std::endl;
556         } else {
557             data << _( "No destination." ) << std::endl;
558         }
559         data << string_format( _( "Trust: %d" ), np->op_of_u.trust ) << " "
560              << string_format( _( "Fear: %d" ), np->op_of_u.fear ) << " "
561              << string_format( _( "Value: %d" ), np->op_of_u.value ) << " "
562              << string_format( _( "Anger: %d" ), np->op_of_u.anger ) << " "
563              << string_format( _( "Owed: %d" ), np->op_of_u.owed ) << std::endl;
564 
565         data << string_format( _( "Aggression: %d" ),
566                                static_cast<int>( np->personality.aggression ) ) << " "
567              << string_format( _( "Bravery: %d" ), static_cast<int>( np->personality.bravery ) ) << " "
568              << string_format( _( "Collector: %d" ), static_cast<int>( np->personality.collector ) ) << " "
569              << string_format( _( "Altruism: %d" ), static_cast<int>( np->personality.altruism ) ) << std::endl;
570 
571         data << _( "Needs:" ) << std::endl;
572         for( const auto &need : np->needs ) {
573             data << need << std::endl;
574         }
575         data << string_format( _( "Total morale: %d" ), np->get_morale_level() ) << std::endl;
576 
577         nmenu.text = data.str();
578     } else {
579         nmenu.text = _( "Player" );
580     }
581 
582     enum {
583         D_DESC, D_SKILLS, D_PROF, D_STATS, D_ITEMS, D_DELETE_ITEMS, D_ITEM_WORN,
584         D_HP, D_STAMINA, D_MORALE, D_PAIN, D_NEEDS, D_HEALTHY, D_STATUS, D_MISSION_ADD, D_MISSION_EDIT,
585         D_TELE, D_MUTATE, D_CLASS, D_ATTITUDE, D_OPINION, D_ADD_EFFECT, D_ASTHMA
586     };
587     nmenu.addentry( D_DESC, true, 'D', "%s",
588                     _( "Edit [D]escription - Name, Age, Height or Blood type" ) );
589     nmenu.addentry( D_SKILLS, true, 's', "%s", _( "Edit [s]kills" ) );
590     nmenu.addentry( D_PROF, true, 'P', "%s", _( "Edit [P]roficiencies" ) );
591     nmenu.addentry( D_STATS, true, 't', "%s", _( "Edit s[t]ats" ) );
592     nmenu.addentry( D_ITEMS, true, 'i', "%s", _( "Grant [i]tems" ) );
593     nmenu.addentry( D_DELETE_ITEMS, true, 'd', "%s", _( "[d]elete (all) items" ) );
594     nmenu.addentry( D_ITEM_WORN, true, 'w', "%s",
595                     _( "[w]ear/[w]ield an item from player's inventory" ) );
596     nmenu.addentry( D_HP, true, 'h', "%s", _( "Set [h]it points" ) );
597     nmenu.addentry( D_STAMINA, true, 'S', "%s", _( "Set [S]tamina" ) );
598     nmenu.addentry( D_MORALE, true, 'o', "%s", _( "Set m[o]rale" ) );
599     nmenu.addentry( D_PAIN, true, 'p', "%s", _( "Cause [p]ain" ) );
600     nmenu.addentry( D_HEALTHY, true, 'a', "%s", _( "Set he[a]lth" ) );
601     nmenu.addentry( D_NEEDS, true, 'n', "%s", _( "Set [n]eeds" ) );
602     nmenu.addentry( D_MUTATE, true, 'u', "%s", _( "M[u]tate" ) );
603     nmenu.addentry( D_STATUS, true,
604                     hotkey_for_action( ACTION_PL_INFO, /*maximum_modifier_count=*/1 ),
605                     "%s", _( "Status Window [@]" ) );
606     nmenu.addentry( D_TELE, true, 'e', "%s", _( "t[e]leport" ) );
607     nmenu.addentry( D_ADD_EFFECT, true, 'E', "%s", _( "Add an [E]ffect" ) );
608     nmenu.addentry( D_ASTHMA, true, 'k', "%s", _( "Cause asthma attac[k]" ) );
609     nmenu.addentry( D_MISSION_EDIT, true, 'M', "%s", _( "Edit [M]issions (WARNING: Unstable!)" ) );
610     if( p.is_npc() ) {
611         nmenu.addentry( D_MISSION_ADD, true, 'm', "%s", _( "Add [m]ission" ) );
612         nmenu.addentry( D_CLASS, true, 'c', "%s", _( "Randomize with [c]lass" ) );
613         nmenu.addentry( D_ATTITUDE, true, 'A', "%s", _( "Set [A]ttitude" ) );
614         nmenu.addentry( D_OPINION, true, 'O', "%s", _( "Set [O]pinion" ) );
615     }
616     nmenu.query();
617     switch( nmenu.ret ) {
618         case D_SKILLS:
619             wishskill( &p );
620             break;
621         case D_STATS: {
622             uilist smenu;
623             smenu.addentry( 0, true, 'S', "%s: %d", _( "Maximum strength" ), p.str_max );
624             smenu.addentry( 1, true, 'D', "%s: %d", _( "Maximum dexterity" ), p.dex_max );
625             smenu.addentry( 2, true, 'I', "%s: %d", _( "Maximum intelligence" ), p.int_max );
626             smenu.addentry( 3, true, 'P', "%s: %d", _( "Maximum perception" ), p.per_max );
627             smenu.query();
628             int *bp_ptr = nullptr;
629             switch( smenu.ret ) {
630                 case 0:
631                     bp_ptr = &p.str_max;
632                     break;
633                 case 1:
634                     bp_ptr = &p.dex_max;
635                     break;
636                 case 2:
637                     bp_ptr = &p.int_max;
638                     break;
639                 case 3:
640                     bp_ptr = &p.per_max;
641                     break;
642                 default:
643                     break;
644             }
645 
646             if( bp_ptr != nullptr ) {
647                 int value;
648                 if( query_int( value, _( "Set the stat to?  Currently: %d" ), *bp_ptr ) && value >= 0 ) {
649                     *bp_ptr = value;
650                     p.reset_stats();
651                 }
652             }
653         }
654         break;
655         case D_PROF:
656             wishproficiency( &p );
657             break;
658         case D_ITEMS:
659             wishitem( &p );
660             break;
661         case D_DELETE_ITEMS:
662             if( !query_yn( _( "Delete all items from the target?" ) ) ) {
663                 break;
664             }
665             for( auto &it : p.worn ) {
666                 it.on_takeoff( p );
667             }
668             p.worn.clear();
669             p.inv->clear();
670             p.remove_weapon();
671             break;
672         case D_ITEM_WORN: {
673             item_location loc = game_menus::inv::titled_menu( player_character, _( "Make target equip" ) );
674             if( !loc ) {
675                 break;
676             }
677             item &to_wear = *loc;
678             if( to_wear.is_armor() ) {
679                 p.on_item_wear( to_wear );
680                 p.worn.push_back( to_wear );
681             } else if( !to_wear.is_null() ) {
682                 p.weapon = to_wear;
683                 get_event_bus().send<event_type::character_wields_item>( p.getID(), p.weapon.typeId() );
684             }
685         }
686         break;
687         case D_HP: {
688             const int torso_hp = p.get_part_hp_cur( bodypart_id( "torso" ) );
689             const int head_hp = p.get_part_hp_cur( bodypart_id( "head" ) );
690             const int arm_l_hp = p.get_part_hp_cur( bodypart_id( "arm_l" ) );
691             const int arm_r_hp = p.get_part_hp_cur( bodypart_id( "arm_r" ) );
692             const int leg_l_hp = p.get_part_hp_cur( bodypart_id( "leg_l" ) );
693             const int leg_r_hp = p.get_part_hp_cur( bodypart_id( "leg_r" ) );
694             uilist smenu;
695             smenu.addentry( 0, true, 'q', "%s: %d", _( "Torso" ), torso_hp );
696             smenu.addentry( 1, true, 'w', "%s: %d", _( "Head" ), head_hp );
697             smenu.addentry( 2, true, 'a', "%s: %d", _( "Left arm" ), arm_l_hp );
698             smenu.addentry( 3, true, 's', "%s: %d", _( "Right arm" ), arm_r_hp );
699             smenu.addentry( 4, true, 'z', "%s: %d", _( "Left leg" ), leg_l_hp );
700             smenu.addentry( 5, true, 'x', "%s: %d", _( "Right leg" ), leg_r_hp );
701             smenu.addentry( 6, true, 'e', "%s: %d", _( "All" ), p.get_lowest_hp() );
702             smenu.query();
703             bodypart_str_id bp = bodypart_str_id( "no_a_real_part" );
704             int bp_ptr = -1;
705             bool all_select = false;
706 
707             switch( smenu.ret ) {
708                 case 0:
709                     bp = body_part_torso;
710                     bp_ptr = torso_hp;
711                     break;
712                 case 1:
713                     bp = body_part_head;
714                     bp_ptr = head_hp;
715                     break;
716                 case 2:
717                     bp = body_part_arm_l;
718                     bp_ptr = arm_l_hp;
719                     break;
720                 case 3:
721                     bp = body_part_arm_r;
722                     bp_ptr = arm_r_hp;
723                     break;
724                 case 4:
725                     bp = body_part_leg_l;
726                     bp_ptr = leg_l_hp;
727                     break;
728                 case 5:
729                     bp = body_part_leg_r;
730                     bp_ptr = leg_r_hp;
731                     break;
732                 case 6:
733                     all_select = true;
734                     break;
735                 default:
736                     break;
737             }
738 
739             if( bp.is_valid() ) {
740                 int value;
741                 if( query_int( value, _( "Set the hitpoints to?  Currently: %d" ), bp_ptr ) && value >= 0 ) {
742                     p.set_part_hp_cur( bp.id(), value );
743                     p.reset_stats();
744                 }
745             } else if( all_select ) {
746                 int value;
747                 if( query_int( value, _( "Set the hitpoints to?  Currently: %d" ), p.get_lowest_hp() ) &&
748                     value >= 0 ) {
749                     for( bodypart_id part_id : p.get_all_body_parts( get_body_part_flags::only_main ) ) {
750                         p.set_part_hp_cur( part_id, value );
751                     }
752                     p.reset_stats();
753                 }
754             }
755         }
756         break;
757         case D_STAMINA:
758             int value;
759             if( query_int( value, _( "Set stamina to?  Current: %d. Max: %d." ), p.get_stamina(),
760                            p.get_stamina_max() ) ) {
761                 if( value >= 0 && value <= p.get_stamina_max() ) {
762                     p.set_stamina( value );
763                 } else {
764                     add_msg( m_bad, _( "Target stamina value out of bounds!" ) );
765                 }
766             }
767             break;
768         case D_MORALE: {
769             int current_morale_level = p.get_morale_level();
770             int value;
771             if( query_int( value, _( "Set the morale to?  Currently: %d" ), current_morale_level ) ) {
772                 int morale_level_delta = value - current_morale_level;
773                 p.add_morale( MORALE_PERM_DEBUG, morale_level_delta );
774                 p.apply_persistent_morale();
775             }
776         }
777         break;
778         case D_OPINION: {
779             uilist smenu;
780             smenu.addentry( 0, true, 'h', "%s: %d", _( "trust" ), np->op_of_u.trust );
781             smenu.addentry( 1, true, 's', "%s: %d", _( "fear" ), np->op_of_u.fear );
782             smenu.addentry( 2, true, 't', "%s: %d", _( "value" ), np->op_of_u.value );
783             smenu.addentry( 3, true, 'f', "%s: %d", _( "anger" ), np->op_of_u.anger );
784             smenu.addentry( 4, true, 'd', "%s: %d", _( "owed" ), np->op_of_u.owed );
785 
786             smenu.query();
787             int value;
788             switch( smenu.ret ) {
789                 case 0:
790                     if( query_int( value, _( "Set trust to?  Currently: %d" ),
791                                    np->op_of_u.trust ) ) {
792                         np->op_of_u.trust = value;
793                     }
794                     break;
795                 case 1:
796                     if( query_int( value, _( "Set fear to?  Currently: %d" ), np->op_of_u.fear ) ) {
797                         np->op_of_u.fear = value;
798                     }
799                     break;
800                 case 2:
801                     if( query_int( value, _( "Set value to?  Currently: %d" ),
802                                    np->op_of_u.value ) ) {
803                         np->op_of_u.value = value;
804                     }
805                     break;
806                 case 3:
807                     if( query_int( value, _( "Set anger to?  Currently: %d" ),
808                                    np->op_of_u.anger ) ) {
809                         np->op_of_u.anger = value;
810                     }
811                     break;
812                 case 4:
813                     if( query_int( value, _( "Set owed to?  Currently: %d" ), np->op_of_u.owed ) ) {
814                         np->op_of_u.owed = value;
815                     }
816                     break;
817             }
818         }
819         break;
820         case D_DESC: {
821             uilist smenu;
822             std::string current_bloodt = io::enum_to_string( p.my_blood_type ) + ( p.blood_rh_factor ? "+" :
823                                          "-" );
824             smenu.text = _( "Select a value and press enter to change it." );
825             smenu.addentry( 0, true, 'n', "%s: %s", _( "Current name" ), p.get_name() );
826             smenu.addentry( 1, true, 'a', "%s: %d", _( "Current age" ), p.base_age() );
827             smenu.addentry( 2, true, 'h', "%s: %d", _( "Current height in cm" ), p.base_height() );
828             smenu.addentry( 3, true, 'b', "%s: %s", _( "Current blood type:" ), current_bloodt );
829             smenu.query();
830             switch( smenu.ret ) {
831                 case 0: {
832                     std::string filterstring = p.name;
833                     string_input_popup popup;
834                     popup
835                     .title( _( "Rename:" ) )
836                     .width( 85 )
837                     .edit( filterstring );
838                     if( popup.confirmed() ) {
839                         p.name = filterstring;
840                     }
841                 }
842                 break;
843                 case 1: {
844                     string_input_popup popup;
845                     popup.title( _( "Enter age in years.  Minimum 16, maximum 55" ) )
846                     .text( string_format( "%d", p.base_age() ) )
847                     .only_digits( true );
848                     const int result = popup.query_int();
849                     if( result != 0 ) {
850                         p.set_base_age( clamp( result, 16, 55 ) );
851                     }
852                 }
853                 break;
854                 case 2: {
855                     string_input_popup popup;
856                     popup.title( _( "Enter height in centimeters.  Minimum 145, maximum 200" ) )
857                     .text( string_format( "%d", p.base_height() ) )
858                     .only_digits( true );
859                     const int result = popup.query_int();
860                     if( result != 0 ) {
861                         p.set_base_height( clamp( result, 145, 200 ) );
862                     }
863                 }
864                 break;
865                 case 3: {
866                     uilist btype;
867                     btype.text = _( "Select blood type" );
868                     btype.addentry( static_cast<int>( blood_type::blood_O ), true, '1', "O" );
869                     btype.addentry( static_cast<int>( blood_type::blood_A ), true, '2', "A" );
870                     btype.addentry( static_cast<int>( blood_type::blood_B ), true, '3', "B" );
871                     btype.addentry( static_cast<int>( blood_type::blood_AB ), true, '4', "AB" );
872                     btype.query();
873                     if( btype.ret < 0 ) {
874                         break;
875                     }
876                     uilist bfac;
877                     bfac.text = _( "Select Rh factor" );
878                     bfac.addentry( 0, true, '-', _( "negative" ) );
879                     bfac.addentry( 1, true, '+', _( "positive" ) );
880                     bfac.query();
881                     if( bfac.ret < 0 ) {
882                         break;
883                     }
884                     p.my_blood_type = static_cast<blood_type>( btype.ret );
885                     p.blood_rh_factor = static_cast<bool>( bfac.ret );
886                     break;
887                 }
888             }
889         }
890         break;
891         case D_PAIN: {
892             int value;
893             if( query_int( value, _( "Cause how much pain?  pain: %d" ), p.get_pain() ) ) {
894                 p.mod_pain( value );
895             }
896         }
897         break;
898         case D_NEEDS: {
899             uilist smenu;
900             smenu.addentry( 0, true, 'h', "%s: %d", _( "Hunger" ), p.get_hunger() );
901             smenu.addentry( 1, true, 's', "%s: %d", _( "Stored kCal" ), p.get_stored_kcal() );
902             smenu.addentry( 2, true, 't', "%s: %d", _( "Thirst" ), p.get_thirst() );
903             smenu.addentry( 3, true, 'f', "%s: %d", _( "Fatigue" ), p.get_fatigue() );
904             smenu.addentry( 4, true, 'd', "%s: %d", _( "Sleep Deprivation" ), p.get_sleep_deprivation() );
905             smenu.addentry( 5, true, 'a', _( "Reset all basic needs" ) );
906 
907             const auto &vits = vitamin::all();
908             for( const auto &v : vits ) {
909                 smenu.addentry( -1, true, 0, "%s: %d", v.second.name(), p.vitamin_get( v.first ) );
910             }
911 
912             smenu.query();
913             int value;
914             switch( smenu.ret ) {
915                 case 0:
916                     if( query_int( value, _( "Set hunger to?  Currently: %d" ), p.get_hunger() ) ) {
917                         p.set_hunger( value );
918                     }
919                     break;
920 
921                 case 1:
922                     if( query_int( value, _( "Set stored kCal to?  Currently: %d" ), p.get_stored_kcal() ) ) {
923                         p.set_stored_kcal( value );
924                     }
925                     break;
926 
927                 case 2:
928                     if( query_int( value, _( "Set thirst to?  Currently: %d" ), p.get_thirst() ) ) {
929                         p.set_thirst( value );
930                     }
931                     break;
932 
933                 case 3:
934                     if( query_int( value, _( "Set fatigue to?  Currently: %d" ), p.get_fatigue() ) ) {
935                         p.set_fatigue( value );
936                     }
937                     break;
938 
939                 case 4:
940                     if( query_int( value, _( "Set sleep deprivation to?  Currently: %d" ),
941                                    p.get_sleep_deprivation() ) ) {
942                         p.set_sleep_deprivation( value );
943                     }
944                     break;
945                 case 5:
946                     p.initialize_stomach_contents();
947                     p.set_hunger( 0 );
948                     p.set_thirst( 0 );
949                     p.set_fatigue( 0 );
950                     p.set_sleep_deprivation( 0 );
951                     p.set_stored_kcal( p.get_healthy_kcal() );
952                     break;
953                 default:
954                     if( smenu.ret >= 6 && smenu.ret < static_cast<int>( vits.size() + 6 ) ) {
955                         auto iter = std::next( vits.begin(), smenu.ret - 6 );
956                         if( query_int( value, _( "Set %s to?  Currently: %d" ),
957                                        iter->second.name(), p.vitamin_get( iter->first ) ) ) {
958                             p.vitamin_set( iter->first, value );
959                         }
960                     }
961             }
962 
963         }
964         break;
965         case D_MUTATE:
966             wishmutate( &p );
967             break;
968         case D_HEALTHY: {
969             uilist smenu;
970             smenu.addentry( 0, true, 'h', "%s: %d", _( "Health" ), p.get_healthy() );
971             smenu.addentry( 1, true, 'm', "%s: %d", _( "Health modifier" ), p.get_healthy_mod() );
972             smenu.addentry( 2, true, 'r', "%s: %d", _( "Radiation" ), p.get_rad() );
973             smenu.query();
974             int value;
975             switch( smenu.ret ) {
976                 case 0:
977                     if( query_int( value, _( "Set the value to?  Currently: %d" ), p.get_healthy() ) ) {
978                         p.set_healthy( value );
979                     }
980                     break;
981                 case 1:
982                     if( query_int( value, _( "Set the value to?  Currently: %d" ), p.get_healthy_mod() ) ) {
983                         p.set_healthy_mod( value );
984                     }
985                     break;
986                 case 2:
987                     if( query_int( value, _( "Set the value to?  Currently: %d" ), p.get_rad() ) ) {
988                         p.set_rad( value );
989                     }
990                     break;
991                 default:
992                     break;
993             }
994         }
995         break;
996         case D_STATUS:
997             p.disp_info();
998             break;
999         case D_MISSION_ADD: {
1000             uilist types;
1001             types.text = _( "Choose mission type" );
1002             const auto all_missions = mission_type::get_all();
1003             std::vector<const mission_type *> mts;
1004             for( size_t i = 0; i < all_missions.size(); i++ ) {
1005                 types.addentry( i, true, -1, all_missions[i].tname() );
1006                 mts.push_back( &all_missions[i] );
1007             }
1008 
1009             types.query();
1010             if( types.ret >= 0 && types.ret < static_cast<int>( mts.size() ) ) {
1011                 np->add_new_mission( mission::reserve_new( mts[types.ret]->id, np->getID() ) );
1012             }
1013         }
1014         break;
1015         case D_MISSION_EDIT:
1016             mission_debug::edit( p );
1017             break;
1018         case D_TELE: {
1019             if( const cata::optional<tripoint> newpos = g->look_around() ) {
1020                 p.setpos( *newpos );
1021                 if( p.is_player() ) {
1022                     if( p.is_mounted() ) {
1023                         p.mounted_creature->setpos( *newpos );
1024                     }
1025                     g->update_map( player_character );
1026                 }
1027             }
1028         }
1029         break;
1030         case D_CLASS: {
1031             uilist classes;
1032             classes.text = _( "Choose new class" );
1033             std::vector<npc_class_id> ids;
1034             size_t i = 0;
1035             for( const npc_class &cl : npc_class::get_all() ) {
1036                 ids.push_back( cl.id );
1037                 classes.addentry( i, true, -1, cl.get_name() );
1038                 i++;
1039             }
1040 
1041             classes.query();
1042             if( classes.ret < static_cast<int>( ids.size() ) && classes.ret >= 0 ) {
1043                 np->randomize( ids[classes.ret] );
1044             }
1045         }
1046         break;
1047         case D_ATTITUDE: {
1048             uilist attitudes_ui;
1049             attitudes_ui.text = _( "Choose new attitude" );
1050             std::vector<npc_attitude> attitudes;
1051             for( int i = NPCATT_NULL; i < NPCATT_END; i++ ) {
1052                 npc_attitude att_id = static_cast<npc_attitude>( i );
1053                 std::string att_name = npc_attitude_name( att_id );
1054                 attitudes.push_back( att_id );
1055                 if( att_name == _( "Unknown attitude" ) ) {
1056                     continue;
1057                 }
1058 
1059                 attitudes_ui.addentry( i, true, -1, att_name );
1060             }
1061 
1062             attitudes_ui.query();
1063             if( attitudes_ui.ret < static_cast<int>( attitudes.size() ) && attitudes_ui.ret >= 0 ) {
1064                 np->set_attitude( attitudes[attitudes_ui.ret] );
1065             }
1066         }
1067         break;
1068         case D_ADD_EFFECT: {
1069             const auto text = string_input_popup()
1070                               .title( _( "Choose an effect to add." ) )
1071                               .width( 20 )
1072                               .text( "" )
1073                               .only_digits( false )
1074                               .query_string();
1075             efftype_id effect( text );
1076             int intensity = 0;
1077             int seconds = 0;
1078             query_int( intensity, _( "What intensity?" ) );
1079             query_int( seconds, _( "How many seconds?" ), 600 );
1080 
1081             if( effect.is_valid() ) {
1082                 p.add_effect( effect, time_duration::from_seconds( seconds ), false, intensity );
1083             } else {
1084                 add_msg( _( "Invalid effect" ) );
1085             }
1086             break;
1087         }
1088         case D_ASTHMA: {
1089             p.set_mutation( trait_ASTHMA );
1090             p.add_effect( effect_asthma, 10_minutes );
1091             break;
1092         }
1093     }
1094 }
1095 
mission_status_string(mission::mission_status status)1096 static std::string mission_status_string( mission::mission_status status )
1097 {
1098     static const std::map<mission::mission_status, std::string> desc{ {
1099             { mission::mission_status::yet_to_start, translate_marker( "Yet to start" ) },
1100             { mission::mission_status::in_progress, translate_marker( "In progress" ) },
1101             { mission::mission_status::success, translate_marker( "Success" ) },
1102             { mission::mission_status::failure, translate_marker( "Failure" ) }
1103         }
1104     };
1105 
1106     const auto &iter = desc.find( status );
1107     if( iter != desc.end() ) {
1108         return _( iter->second );
1109     }
1110 
1111     return _( "Bugged" );
1112 }
1113 
describe(const mission & m)1114 std::string mission_debug::describe( const mission &m )
1115 {
1116     std::stringstream data;
1117     data << _( "Type:" ) << m.type->id.str();
1118     data << _( " Status:" ) << mission_status_string( m.status );
1119     data << _( " ID:" ) << m.uid;
1120     data << _( " NPC ID:" ) << m.npc_id;
1121     data << _( " Target:" ) << m.target.to_string();
1122     data << _( "Player ID:" ) << m.player_id;
1123 
1124     return data.str();
1125 }
1126 
add_header(uilist & mmenu,const std::string & str)1127 static void add_header( uilist &mmenu, const std::string &str )
1128 {
1129     if( !mmenu.entries.empty() ) {
1130         mmenu.addentry( -1, false, -1, "" );
1131     }
1132     uilist_entry header( -1, false, -1, str, c_yellow, c_yellow );
1133     header.force_color = true;
1134     mmenu.entries.push_back( header );
1135 }
1136 
edit(player & who)1137 void mission_debug::edit( player &who )
1138 {
1139     if( who.is_player() ) {
1140         edit_player();
1141     } else if( who.is_npc() ) {
1142         edit_npc( dynamic_cast<npc &>( who ) );
1143     }
1144 }
1145 
edit_npc(npc & who)1146 void mission_debug::edit_npc( npc &who )
1147 {
1148     dialogue_chatbin &bin = who.chatbin;
1149     std::vector<mission *> all_missions;
1150 
1151     uilist mmenu;
1152     mmenu.text = _( "Select mission to edit" );
1153 
1154     add_header( mmenu, _( "Currently assigned missions:" ) );
1155     for( mission *m : bin.missions_assigned ) {
1156         mmenu.addentry( all_missions.size(), true, MENU_AUTOASSIGN, "%s", m->type->id.c_str() );
1157         all_missions.emplace_back( m );
1158     }
1159 
1160     add_header( mmenu, _( "Not assigned missions:" ) );
1161     for( mission *m : bin.missions ) {
1162         mmenu.addentry( all_missions.size(), true, MENU_AUTOASSIGN, "%s", m->type->id.c_str() );
1163         all_missions.emplace_back( m );
1164     }
1165 
1166     mmenu.query();
1167     if( mmenu.ret < 0 || mmenu.ret >= static_cast<int>( all_missions.size() ) ) {
1168         return;
1169     }
1170 
1171     edit_mission( *all_missions[mmenu.ret] );
1172 }
1173 
edit_player()1174 void mission_debug::edit_player()
1175 {
1176     std::vector<mission *> all_missions;
1177 
1178     uilist mmenu;
1179     mmenu.text = _( "Select mission to edit" );
1180 
1181     avatar &player_character = get_avatar();
1182     add_header( mmenu, _( "Active missions:" ) );
1183     for( mission *m : player_character.active_missions ) {
1184         mmenu.addentry( all_missions.size(), true, MENU_AUTOASSIGN, "%s", m->type->id.c_str() );
1185         all_missions.emplace_back( m );
1186     }
1187 
1188     add_header( mmenu, _( "Completed missions:" ) );
1189     for( mission *m : player_character.completed_missions ) {
1190         mmenu.addentry( all_missions.size(), true, MENU_AUTOASSIGN, "%s", m->type->id.c_str() );
1191         all_missions.emplace_back( m );
1192     }
1193 
1194     add_header( mmenu, _( "Failed missions:" ) );
1195     for( mission *m : player_character.failed_missions ) {
1196         mmenu.addentry( all_missions.size(), true, MENU_AUTOASSIGN, "%s", m->type->id.c_str() );
1197         all_missions.emplace_back( m );
1198     }
1199 
1200     mmenu.query();
1201     if( mmenu.ret < 0 || mmenu.ret >= static_cast<int>( all_missions.size() ) ) {
1202         return;
1203     }
1204 
1205     edit_mission( *all_missions[mmenu.ret] );
1206 }
1207 
remove_from_vec(std::vector<mission * > & vec,mission * m)1208 static bool remove_from_vec( std::vector<mission *> &vec, mission *m )
1209 {
1210     auto iter = std::remove( vec.begin(), vec.end(), m );
1211     bool ret = iter != vec.end();
1212     vec.erase( iter, vec.end() );
1213     return ret;
1214 }
1215 
remove_mission(mission & m)1216 void mission_debug::remove_mission( mission &m )
1217 {
1218     avatar &player_character = get_avatar();
1219     if( remove_from_vec( player_character.active_missions, &m ) ) {
1220         add_msg( _( "Removing from active_missions" ) );
1221     }
1222     if( remove_from_vec( player_character.completed_missions, &m ) ) {
1223         add_msg( _( "Removing from completed_missions" ) );
1224     }
1225     if( remove_from_vec( player_character.failed_missions, &m ) ) {
1226         add_msg( _( "Removing from failed_missions" ) );
1227     }
1228 
1229     if( player_character.active_mission == &m ) {
1230         player_character.active_mission = nullptr;
1231         add_msg( _( "Unsetting active mission" ) );
1232     }
1233 
1234     npc *giver = g->find_npc( m.npc_id );
1235     if( giver != nullptr ) {
1236         if( remove_from_vec( giver->chatbin.missions_assigned, &m ) ) {
1237             add_msg( _( "Removing from %s missions_assigned" ), giver->name );
1238         }
1239         if( remove_from_vec( giver->chatbin.missions, &m ) ) {
1240             add_msg( _( "Removing from %s missions" ), giver->name );
1241         }
1242     }
1243 }
1244 
edit_mission(mission & m)1245 void mission_debug::edit_mission( mission &m )
1246 {
1247     uilist mmenu;
1248     mmenu.text = describe( m );
1249 
1250     enum {
1251         M_FAIL, M_SUCCEED, M_REMOVE
1252     };
1253 
1254     mmenu.addentry( M_FAIL, true, 'f', "%s", _( "Fail mission" ) );
1255     mmenu.addentry( M_SUCCEED, true, 'c', "%s", _( "Mark as complete" ) );
1256     mmenu.addentry( M_REMOVE, true, 'r', "%s", _( "Remove mission without proper cleanup" ) );
1257 
1258     mmenu.query();
1259     switch( mmenu.ret ) {
1260         case M_FAIL:
1261             m.fail();
1262             break;
1263         case M_SUCCEED:
1264             m.status = mission::mission_status::success;
1265             break;
1266         case M_REMOVE:
1267             remove_mission( m );
1268             break;
1269     }
1270 }
1271 
draw_benchmark(const int max_difference)1272 void draw_benchmark( const int max_difference )
1273 {
1274     // call the draw procedure as many times as possible in max_difference milliseconds
1275     auto start_tick = std::chrono::steady_clock::now();
1276     auto end_tick = std::chrono::steady_clock::now();
1277     int64_t difference = 0;
1278     int draw_counter = 0;
1279 
1280     static_popup popup;
1281     popup.on_top( true ).message( "%s", _( "Benchmark in progress…" ) );
1282 
1283     while( true ) {
1284         end_tick = std::chrono::steady_clock::now();
1285         difference = std::chrono::duration_cast<std::chrono::milliseconds>( end_tick - start_tick ).count();
1286         if( difference >= max_difference ) {
1287             break;
1288         }
1289         g->invalidate_main_ui_adaptor();
1290         ui_manager::redraw_invalidated();
1291         refresh_display();
1292         draw_counter++;
1293     }
1294 
1295     DebugLog( D_INFO, DC_ALL ) << "Draw benchmark:\n" <<
1296                                "\n| USE_TILES |  RENDERER | FRAMEBUFFER_ACCEL | USE_COLOR_MODULATED_TEXTURES | FPS |" <<
1297                                "\n|:---:|:---:|:---:|:---:|:---:|\n| " <<
1298                                get_option<bool>( "USE_TILES" ) << " | " <<
1299 #if !defined(__ANDROID__)
1300                                get_option<std::string>( "RENDERER" ) << " | " <<
1301 #else
1302                                get_option<bool>( "SOFTWARE_RENDERING" ) << " | " <<
1303 #endif
1304                                get_option<bool>( "FRAMEBUFFER_ACCEL" ) << " | " <<
1305                                get_option<bool>( "USE_COLOR_MODULATED_TEXTURES" ) << " | " <<
1306                                static_cast<int>( 1000.0 * draw_counter / static_cast<double>( difference ) ) << " |\n";
1307 
1308     add_msg( m_info, _( "Drew %d times in %.3f seconds.  (%.3f fps average)" ), draw_counter,
1309              difference / 1000.0, 1000.0 * draw_counter / static_cast<double>( difference ) );
1310 }
1311 
debug()1312 void debug()
1313 {
1314     bool debug_menu_has_hotkey = hotkey_for_action( ACTION_DEBUG,
1315                                  /*maximum_modifier_count=*/ -1, false ).has_value();
1316     cata::optional<debug_menu_index> action = debug_menu_uilist( debug_menu_has_hotkey );
1317 
1318     // For the "cheaty" options, disable achievements when used
1319     achievements_tracker &achievements = get_achievements();
1320     static const std::unordered_set<debug_menu_index> non_cheaty_options = {
1321         debug_menu_index::SAVE_SCREENSHOT,
1322         debug_menu_index::GAME_REPORT,
1323         debug_menu_index::ENABLE_ACHIEVEMENTS,
1324         debug_menu_index::BENCHMARK,
1325         debug_menu_index::SHOW_MSG,
1326     };
1327     bool should_disable_achievements = action && !non_cheaty_options.count( *action );
1328     if( should_disable_achievements && achievements.is_enabled() ) {
1329         static const std::string query(
1330             translate_marker(
1331                 "Using this will disable achievements.  Proceed?"
1332                 "\nThey can be reenabled in the 'game' section of the menu." ) );
1333         if( query_yn( _( query ) ) ) {
1334             achievements.set_enabled( false );
1335         } else {
1336             action = cata::nullopt;
1337         }
1338     }
1339 
1340     if( !action ) {
1341         return;
1342     }
1343 
1344     get_event_bus().send<event_type::uses_debug_menu>( *action );
1345 
1346     avatar &player_character = get_avatar();
1347     map &here = get_map();
1348     tripoint abs_sub = here.get_abs_sub();
1349     switch( *action ) {
1350         case debug_menu_index::WISH:
1351             debug_menu::wishitem( &player_character );
1352             break;
1353 
1354         case debug_menu_index::SHORT_TELEPORT:
1355             debug_menu::teleport_short();
1356             break;
1357 
1358         case debug_menu_index::LONG_TELEPORT:
1359             debug_menu::teleport_long();
1360             break;
1361 
1362         case debug_menu_index::REVEAL_MAP: {
1363             auto &cur_om = g->get_cur_om();
1364             for( int i = 0; i < OMAPX; i++ ) {
1365                 for( int j = 0; j < OMAPY; j++ ) {
1366                     for( int k = -OVERMAP_DEPTH; k <= OVERMAP_HEIGHT; k++ ) {
1367                         cur_om.seen( { i, j, k } ) = true;
1368                     }
1369                 }
1370             }
1371             add_msg( m_good, _( "Current overmap revealed." ) );
1372         }
1373         break;
1374 
1375         case debug_menu_index::SPAWN_NPC: {
1376             shared_ptr_fast<npc> temp = make_shared_fast<npc>();
1377             temp->normalize();
1378             temp->randomize();
1379             temp->spawn_at_precise( abs_sub.xy(), player_character.pos() + point( -4, -4 ) );
1380             overmap_buffer.insert_npc( temp );
1381             temp->form_opinion( player_character );
1382             temp->mission = NPC_MISSION_NULL;
1383             temp->add_new_mission( mission::reserve_random( ORIGIN_ANY_NPC, temp->global_omt_location(),
1384                                    temp->getID() ) );
1385             std::string new_fac_id = "solo_";
1386             new_fac_id += temp->name;
1387             // create a new "lone wolf" faction for this one NPC
1388             faction *new_solo_fac = g->faction_manager_ptr->add_new_faction( temp->name,
1389                                     faction_id( new_fac_id ), faction_id( "no_faction" ) );
1390             temp->set_fac( new_solo_fac ? new_solo_fac->id : faction_id( "no_faction" ) );
1391             g->load_npcs();
1392         }
1393         break;
1394 
1395         case debug_menu_index::SPAWN_MON:
1396             debug_menu::wishmonster( cata::nullopt );
1397             break;
1398 
1399         case debug_menu_index::GAME_STATE: {
1400             std::string mfus;
1401             std::vector<std::pair<m_flag, int>> sorted;
1402             for( int f = 0; f < m_flag::MF_MAX; f++ ) {
1403                 sorted.push_back( {static_cast<m_flag>( f ), MonsterGenerator::generator().m_flag_usage_stats[f]} );
1404             }
1405             std::sort( sorted.begin(), sorted.end(), []( std::pair<m_flag, int> a, std::pair<m_flag, int> b ) {
1406                 return a.second != b.second ? a.second > b.second : a.first < b.first;
1407             } );
1408             popup( player_character.total_daily_calories_string() );
1409             for( auto &m_flag_stat : sorted ) {
1410                 mfus += string_format( "%s;%d\n", io::enum_to_string<m_flag>( m_flag_stat.first ),
1411                                        m_flag_stat.second );
1412             }
1413             DebugLog( D_INFO, DC_ALL ) << "Monster flag usage statistics:\nFLAG;COUNT\n" << mfus;
1414             std::fill( MonsterGenerator::generator().m_flag_usage_stats.begin(),
1415                        MonsterGenerator::generator().m_flag_usage_stats.end(), 0 );
1416             popup_top( "Monster flag usage statistics were dumped to debug.log and cleared." );
1417 
1418             std::string s = _( "Location %d:%d in %d:%d, %s\n" );
1419             s += _( "Current turn: %d.\n" );
1420             s += ngettext( "%d creature exists.\n", "%d creatures exist.\n", g->num_creatures() );
1421 
1422             std::unordered_map<std::string, int> creature_counts;
1423             for( Creature &critter : g->all_creatures() ) {
1424                 std::string this_name = critter.get_name();
1425                 creature_counts[this_name]++;
1426             }
1427 
1428             if( !creature_counts.empty() ) {
1429                 std::vector<std::pair<std::string, int>> creature_names_sorted;
1430                 for( const std::pair<const std::string, int> &it : creature_counts ) {
1431                     creature_names_sorted.emplace_back( it );
1432                 }
1433 
1434                 std::stable_sort( creature_names_sorted.begin(), creature_names_sorted.end(), []( auto a, auto b ) {
1435                     return a.second > b.second;
1436                 } );
1437 
1438                 s += _( "\nSpecific creature type list:\n" );
1439                 for( const std::pair<std::string, int> &crit_name : creature_names_sorted ) {
1440                     s += string_format( "%i %s\n", crit_name.second, crit_name.first );
1441                 }
1442             }
1443 
1444             popup_top(
1445                 s.c_str(),
1446                 player_character.posx(), player_character.posy(), abs_sub.x, abs_sub.y,
1447                 overmap_buffer.ter( player_character.global_omt_location() )->get_name(),
1448                 to_turns<int>( calendar::turn - calendar::turn_zero ),
1449                 g->num_creatures() );
1450             for( const npc &guy : g->all_npcs() ) {
1451                 tripoint t = guy.global_sm_location();
1452                 add_msg( m_info, _( "%s: map ( %d:%d ) pos ( %d:%d )" ), guy.name, t.x,
1453                          t.y, guy.posx(), guy.posy() );
1454             }
1455 
1456             add_msg( m_info, _( "(you: %d:%d)" ), player_character.posx(), player_character.posy() );
1457             std::string stom =
1458                 _( "Stomach Contents: %d ml / %d ml kCal: %d, Water: %d ml" );
1459             add_msg( m_info, stom.c_str(), units::to_milliliter( player_character.stomach.contains() ),
1460                      units::to_milliliter( player_character.stomach.capacity( player_character ) ),
1461                      player_character.stomach.get_calories(),
1462                      units::to_milliliter( player_character.stomach.get_water() ), player_character.get_hunger() );
1463             stom = _( "Guts Contents: %d ml / %d ml kCal: %d, Water: %d ml\nHunger: %d, Thirst: %d, kCal: %d / %d" );
1464             add_msg( m_info, stom.c_str(), units::to_milliliter( player_character.guts.contains() ),
1465                      units::to_milliliter( player_character.guts.capacity( player_character ) ),
1466                      player_character.guts.get_calories(), units::to_milliliter( player_character.guts.get_water() ),
1467                      player_character.get_hunger(), player_character.get_thirst(), player_character.get_stored_kcal(),
1468                      player_character.get_healthy_kcal() );
1469             add_msg( m_info, _( "Body Mass Index: %.0f\nBasal Metabolic Rate: %i" ), player_character.get_bmi(),
1470                      player_character.get_bmr() );
1471             add_msg( m_info, _( "Player activity level: %s" ), player_character.activity_level_str() );
1472             if( get_option<bool>( "STATS_THROUGH_KILLS" ) ) {
1473                 add_msg( m_info, _( "Kill xp: %d" ), player_character.kill_xp() );
1474             }
1475             g->invalidate_main_ui_adaptor();
1476             g->disp_NPCs();
1477             break;
1478         }
1479         case debug_menu_index::KILL_NPCS:
1480             for( npc &guy : g->all_npcs() ) {
1481                 add_msg( _( "%s's head implodes!" ), guy.name );
1482                 guy.set_part_hp_cur( bodypart_id( "head" ), 0 );
1483             }
1484             break;
1485 
1486         case debug_menu_index::MUTATE:
1487             debug_menu::wishmutate( &player_character );
1488             break;
1489 
1490         case debug_menu_index::SPAWN_VEHICLE:
1491             if( here.veh_at( player_character.pos() ) ) {
1492                 dbg( D_ERROR ) << "game:load: There's already vehicle here";
1493                 debugmsg( "There's already vehicle here" );
1494             } else {
1495                 // Vector of name, id so that we can sort by name
1496                 std::vector<std::pair<std::string, vproto_id>> veh_strings;
1497                 for( auto &elem : vehicle_prototype::get_all() ) {
1498                     if( elem == vproto_id( "custom" ) ) {
1499                         continue;
1500                     }
1501                     veh_strings.emplace_back( elem->name.translated(), elem );
1502                 }
1503                 std::sort( veh_strings.begin(), veh_strings.end(), localized_compare );
1504                 uilist veh_menu;
1505                 veh_menu.text = _( "Choose vehicle to spawn" );
1506                 int menu_ind = 0;
1507                 for( auto &elem : veh_strings ) {
1508                     //~ Menu entry in vehicle wish menu: 1st string: displayed name, 2nd string: internal name of vehicle
1509                     veh_menu.addentry( menu_ind, true, MENU_AUTOASSIGN, _( "%1$s (%2$s)" ),
1510                                        elem.first, elem.second.c_str() );
1511                     ++menu_ind;
1512                 }
1513                 veh_menu.query();
1514                 if( veh_menu.ret >= 0 && veh_menu.ret < static_cast<int>( veh_strings.size() ) ) {
1515                     // Didn't cancel
1516                     const vproto_id &selected_opt = veh_strings[veh_menu.ret].second;
1517                     tripoint dest = player_character.pos();
1518                     uilist veh_cond_menu;
1519                     veh_cond_menu.text = _( "Vehicle condition" );
1520                     veh_cond_menu.addentry( 0, true, MENU_AUTOASSIGN, _( "Light damage" ) );
1521                     veh_cond_menu.addentry( 1, true, MENU_AUTOASSIGN, _( "Undamaged" ) );
1522                     veh_cond_menu.addentry( 2, true, MENU_AUTOASSIGN, _( "Disabled (tires or engine)" ) );
1523                     veh_cond_menu.query();
1524 
1525                     if( veh_cond_menu.ret >= 0 && veh_cond_menu.ret < 3 ) {
1526                         // TODO: Allow picking this when add_vehicle has 3d argument
1527                         vehicle *veh = here.add_vehicle(
1528                                            selected_opt, dest, -90_degrees, 100, veh_cond_menu.ret - 1 );
1529                         if( veh != nullptr ) {
1530                             here.board_vehicle( dest, &player_character );
1531                         }
1532                     }
1533                 }
1534             }
1535             break;
1536 
1537         case debug_menu_index::CHANGE_SKILLS: {
1538             debug_menu::wishskill( &player_character );
1539         }
1540         break;
1541 
1542         case debug_menu_index::LEARN_MA:
1543             add_msg( m_info, _( "Martial arts debug." ) );
1544             add_msg( _( "Your eyes blink rapidly as knowledge floods your brain." ) );
1545             for( auto &style : all_martialart_types() ) {
1546                 if( style != matype_id( "style_none" ) ) {
1547                     player_character.martial_arts_data->add_martialart( style );
1548                 }
1549             }
1550             add_msg( m_good, _( "You now know a lot more than just 10 styles of kung fu." ) );
1551             break;
1552 
1553         case debug_menu_index::UNLOCK_RECIPES: {
1554             add_msg( m_info, _( "Recipe debug." ) );
1555             add_msg( _( "Your eyes blink rapidly as knowledge floods your brain." ) );
1556             for( const auto &e : recipe_dict ) {
1557                 player_character.learn_recipe( &e.second );
1558             }
1559             add_msg( m_good, _( "You know how to craft that now." ) );
1560         }
1561         break;
1562 
1563         case debug_menu_index::EDIT_PLAYER:
1564             debug_menu::character_edit_menu();
1565             break;
1566 
1567         case debug_menu_index::SPAWN_ARTIFACT:
1568             if( const cata::optional<tripoint> center = g->look_around() ) {
1569                 artifact_natural_property prop = static_cast<artifact_natural_property>( rng( ARTPROP_NULL + 1,
1570                                                  ARTPROP_MAX - 1 ) );
1571                 here.create_anomaly( *center, prop );
1572                 here.spawn_artifact( *center, relic_procgen_id( "alien_reality" ) );
1573             }
1574             break;
1575 
1576         case debug_menu_index::SPAWN_CLAIRVOYANCE:
1577             player_character.i_add( item( "architect_cube", calendar::turn ) );
1578             break;
1579 
1580         case debug_menu_index::MAP_EDITOR:
1581             g->look_debug();
1582             break;
1583 
1584         case debug_menu_index::CHANGE_WEATHER: {
1585             uilist weather_menu;
1586             weather_menu.text = _( "Select new weather pattern:" );
1587             weather_menu.addentry( 0, true, MENU_AUTOASSIGN, g->weather.weather_override == WEATHER_NULL ?
1588                                    _( "Keep normal weather patterns" ) : _( "Disable weather forcing" ) );
1589             for( size_t i = 0; i < weather_types::get_all().size(); i++ ) {
1590                 weather_menu.addentry( i, true, MENU_AUTOASSIGN,
1591                                        weather_types::get_all()[i].name.translated() );
1592             }
1593 
1594             weather_menu.query();
1595 
1596             if( weather_menu.ret >= 0 &&
1597                 static_cast<size_t>( weather_menu.ret ) < weather_types::get_all().size() ) {
1598                 const weather_type_id selected_weather = weather_types::get_all()[weather_menu.ret].id;
1599                 g->weather.weather_override = selected_weather;
1600                 g->weather.set_nextweather( calendar::turn );
1601             }
1602         }
1603         break;
1604 
1605         case debug_menu_index::WIND_DIRECTION: {
1606             uilist wind_direction_menu;
1607             wind_direction_menu.text = _( "Select new wind direction:" );
1608             wind_direction_menu.addentry( 0, true, MENU_AUTOASSIGN, g->weather.wind_direction_override ?
1609                                           _( "Disable direction forcing" ) : _( "Keep normal wind direction" ) );
1610             int count = 1;
1611             for( int angle = 0; angle <= 315; angle += 45 ) {
1612                 wind_direction_menu.addentry( count, true, MENU_AUTOASSIGN, get_wind_arrow( angle ) );
1613                 count += 1;
1614             }
1615             wind_direction_menu.query();
1616             if( wind_direction_menu.ret == 0 ) {
1617                 g->weather.wind_direction_override = cata::nullopt;
1618             } else if( wind_direction_menu.ret >= 0 && wind_direction_menu.ret < 9 ) {
1619                 g->weather.wind_direction_override = ( wind_direction_menu.ret - 1 ) * 45;
1620                 g->weather.set_nextweather( calendar::turn );
1621             }
1622         }
1623         break;
1624 
1625         case debug_menu_index::WIND_SPEED: {
1626             uilist wind_speed_menu;
1627             wind_speed_menu.text = _( "Select new wind speed:" );
1628             wind_speed_menu.addentry( 0, true, MENU_AUTOASSIGN, g->weather.wind_direction_override ?
1629                                       _( "Disable speed forcing" ) : _( "Keep normal wind speed" ) );
1630             int count = 1;
1631             for( int speed = 0; speed <= 100; speed += 10 ) {
1632                 std::string speedstring = std::to_string( speed ) + " " + velocity_units( VU_WIND );
1633                 wind_speed_menu.addentry( count, true, MENU_AUTOASSIGN, speedstring );
1634                 count += 1;
1635             }
1636             wind_speed_menu.query();
1637             if( wind_speed_menu.ret == 0 ) {
1638                 g->weather.windspeed_override = cata::nullopt;
1639             } else if( wind_speed_menu.ret >= 0 && wind_speed_menu.ret < 12 ) {
1640                 int selected_wind_speed = ( wind_speed_menu.ret - 1 ) * 10;
1641                 g->weather.windspeed_override = selected_wind_speed;
1642                 g->weather.set_nextweather( calendar::turn );
1643             }
1644         }
1645         break;
1646 
1647         case debug_menu_index::GEN_SOUND: {
1648             const cata::optional<tripoint> where = g->look_around();
1649             if( !where ) {
1650                 return;
1651             }
1652 
1653             int volume;
1654             if( !query_int( volume, _( "Volume of sound: " ) ) ) {
1655                 return;
1656             }
1657 
1658             if( volume < 0 ) {
1659                 return;
1660             }
1661 
1662             sounds::sound( *where, volume, sounds::sound_t::order, string_format( _( "DEBUG SOUND ( %d )" ),
1663                            volume ) );
1664         }
1665         break;
1666 
1667         case debug_menu_index::KILL_MONS: {
1668             for( monster &critter : g->all_monsters() ) {
1669                 // Use the normal death functions, useful for testing death
1670                 // and for getting a corpse.
1671                 if( critter.type->id != mon_generator ) {
1672                     critter.die( nullptr );
1673                 }
1674             }
1675             g->cleanup_dead();
1676         }
1677         break;
1678         case debug_menu_index::DISPLAY_HORDES:
1679             ui::omap::display_hordes();
1680             break;
1681         case debug_menu_index::TEST_IT_GROUP: {
1682             item_group::debug_spawn();
1683         }
1684         break;
1685 
1686         // Damage Self
1687         case debug_menu_index::DAMAGE_SELF: {
1688             const int torso_hp = player_character.get_part_hp_cur( bodypart_id( "torso" ) );
1689             const int head_hp = player_character.get_part_hp_cur( bodypart_id( "head" ) );
1690             const int arm_l_hp = player_character.get_part_hp_cur( bodypart_id( "arm_l" ) );
1691             const int arm_r_hp = player_character.get_part_hp_cur( bodypart_id( "arm_r" ) );
1692             const int leg_l_hp = player_character.get_part_hp_cur( bodypart_id( "leg_l" ) );
1693             const int leg_r_hp = player_character.get_part_hp_cur( bodypart_id( "leg_r" ) );
1694             uilist smenu;
1695             smenu.addentry( 0, true, 'q', "%s: %d", _( "Torso" ), torso_hp );
1696             smenu.addentry( 1, true, 'w', "%s: %d", _( "Head" ), head_hp );
1697             smenu.addentry( 2, true, 'a', "%s: %d", _( "Left arm" ), arm_l_hp );
1698             smenu.addentry( 3, true, 's', "%s: %d", _( "Right arm" ), arm_r_hp );
1699             smenu.addentry( 4, true, 'z', "%s: %d", _( "Left leg" ), leg_l_hp );
1700             smenu.addentry( 5, true, 'x', "%s: %d", _( "Right leg" ), leg_r_hp );
1701             smenu.query();
1702             bodypart_id part;
1703             int dbg_damage;
1704             switch( smenu.ret ) {
1705                 case 0:
1706                     part = bodypart_id( "torso" );
1707                     break;
1708                 case 1:
1709                     part = bodypart_id( "head" );
1710                     break;
1711                 case 2:
1712                     part = bodypart_id( "arm_l" );
1713                     break;
1714                 case 3:
1715                     part = bodypart_id( "arm_r" );
1716                     break;
1717                 case 4:
1718                     part = bodypart_id( "leg_l" );
1719                     break;
1720                 case 5:
1721                     part = bodypart_id( "leg_r" );
1722                     break;
1723                 default:
1724                     break;
1725             }
1726             if( query_int( dbg_damage, _( "Damage self for how much?  hp: %s" ), part.id().c_str() ) ) {
1727                 player_character.apply_damage( nullptr, part, dbg_damage );
1728                 player_character.die( nullptr );
1729             }
1730         }
1731         break;
1732 
1733         // Add bleeding
1734         case debug_menu_index::BLEED_SELF: {
1735             uilist smenu;
1736             smenu.addentry( 0, true, 'q', _( "Torso" ) );
1737             smenu.addentry( 1, true, 'w', _( "Head" ) );
1738             smenu.addentry( 2, true, 'a', _( "Left arm" ) );
1739             smenu.addentry( 3, true, 's', _( "Right arm" ) );
1740             smenu.addentry( 4, true, 'z', _( "Left leg" ) );
1741             smenu.addentry( 5, true, 'x', _( "Right leg" ) );
1742             smenu.query();
1743             bodypart_id part;
1744             int intensity = 0;
1745             switch( smenu.ret ) {
1746                 case 0:
1747                     part = bodypart_id( "torso" );
1748                     break;
1749                 case 1:
1750                     part = bodypart_id( "head" );
1751                     break;
1752                 case 2:
1753                     part = bodypart_id( "arm_l" );
1754                     break;
1755                 case 3:
1756                     part = bodypart_id( "arm_r" );
1757                     break;
1758                 case 4:
1759                     part = bodypart_id( "leg_l" );
1760                     break;
1761                 case 5:
1762                     part = bodypart_id( "leg_r" );
1763                     break;
1764                 default:
1765                     break;
1766             }
1767             if( query_int( intensity, _( "Add bleeding duration in minutes, equal to intensity:" ) ) ) {
1768                 player_character.make_bleed( effect_source::empty(), part, 1_minutes * intensity );
1769             }
1770         }
1771         break;
1772 
1773         case debug_menu_index::SHOW_SOUND: {
1774 #if defined(TILES)
1775             const auto &sounds_to_draw = sounds::get_monster_sounds();
1776 
1777             shared_ptr_fast<game::draw_callback_t> sound_cb = make_shared_fast<game::draw_callback_t>( [&]() {
1778                 const point offset {
1779                     player_character.view_offset.xy() + point( POSX - player_character.posx(), POSY - player_character.posy() )
1780                 };
1781                 for( const auto &sound : sounds_to_draw.first ) {
1782                     mvwputch( g->w_terrain, offset + sound.xy(), c_yellow, '?' );
1783                 }
1784                 for( const auto &sound : sounds_to_draw.second ) {
1785                     mvwputch( g->w_terrain, offset + sound.xy(), c_red, '?' );
1786                 }
1787             } );
1788             g->add_draw_callback( sound_cb );
1789 
1790             ui_manager::redraw();
1791             inp_mngr.wait_for_any_key();
1792 #else
1793             popup( _( "This binary was not compiled with tiles support." ) );
1794 #endif
1795         }
1796         break;
1797 
1798         case debug_menu_index::DISPLAY_WEATHER:
1799             ui::omap::display_weather();
1800             break;
1801         case debug_menu_index::DISPLAY_SCENTS:
1802             ui::omap::display_scents();
1803             break;
1804         case debug_menu_index::DISPLAY_SCENTS_LOCAL:
1805             g->display_toggle_overlay( ACTION_DISPLAY_SCENT );
1806             break;
1807         case debug_menu_index::DISPLAY_SCENTS_TYPE_LOCAL:
1808             g->display_toggle_overlay( ACTION_DISPLAY_SCENT_TYPE );
1809             break;
1810         case debug_menu_index::DISPLAY_TEMP:
1811             g->display_toggle_overlay( ACTION_DISPLAY_TEMPERATURE );
1812             break;
1813         case debug_menu_index::DISPLAY_VEHICLE_AI:
1814             g->display_toggle_overlay( ACTION_DISPLAY_VEHICLE_AI );
1815             break;
1816         case debug_menu_index::DISPLAY_VISIBILITY:
1817             g->display_toggle_overlay( ACTION_DISPLAY_VISIBILITY );
1818             break;
1819         case debug_menu_index::DISPLAY_LIGHTING:
1820             g->display_toggle_overlay( ACTION_DISPLAY_LIGHTING );
1821             break;
1822         case debug_menu_index::DISPLAY_RADIATION:
1823             g->display_toggle_overlay( ACTION_DISPLAY_RADIATION );
1824             break;
1825         case debug_menu_index::DISPLAY_TRANSPARENCY:
1826             g->display_toggle_overlay( ACTION_DISPLAY_TRANSPARENCY );
1827             break;
1828         case debug_menu_index::DISPLAY_REACHABILITY_ZONES:
1829             g->display_reachability_zones();
1830             break;
1831         case debug_menu_index::HOUR_TIMER:
1832             g->toggle_debug_hour_timer();
1833             break;
1834         case debug_menu_index::CHANGE_TIME: {
1835             auto set_turn = [&]( const int initial, const time_duration & factor, const char *const msg ) {
1836                 const auto text = string_input_popup()
1837                                   .title( msg )
1838                                   .width( 20 )
1839                                   .text( std::to_string( initial ) )
1840                                   .only_digits( true )
1841                                   .query_string();
1842                 if( text.empty() ) {
1843                     return;
1844                 }
1845                 const int new_value = std::atoi( text.c_str() );
1846                 const time_duration offset = ( new_value - initial ) * factor;
1847                 // Arbitrary maximal value.
1848                 const time_point max = calendar::turn_zero + time_duration::from_turns(
1849                                            std::numeric_limits<int>::max() / 2 );
1850                 calendar::turn = std::max( std::min( max, calendar::turn + offset ), calendar::turn_zero );
1851             };
1852 
1853             uilist smenu;
1854             static const auto years = []( const time_point & p ) {
1855                 return static_cast<int>( ( p - calendar::turn_zero ) / calendar::year_length() );
1856             };
1857             do {
1858                 const int iSel = smenu.ret;
1859                 smenu.reset();
1860                 smenu.addentry( 0, true, 'y', "%s: %d", _( "year" ), years( calendar::turn ) );
1861                 smenu.addentry( 1, !calendar::eternal_season(), 's', "%s: %d",
1862                                 _( "season" ), static_cast<int>( season_of_year( calendar::turn ) ) );
1863                 smenu.addentry( 2, true, 'd', "%s: %d", _( "day" ), day_of_season<int>( calendar::turn ) );
1864                 smenu.addentry( 3, true, 'h', "%s: %d", _( "hour" ), hour_of_day<int>( calendar::turn ) );
1865                 smenu.addentry( 4, true, 'm', "%s: %d", _( "minute" ), minute_of_hour<int>( calendar::turn ) );
1866                 smenu.addentry( 5, true, 't', "%s: %d", _( "turn" ),
1867                                 to_turns<int>( calendar::turn - calendar::turn_zero ) );
1868                 smenu.selected = iSel;
1869                 smenu.query();
1870 
1871                 switch( smenu.ret ) {
1872                     case 0:
1873                         set_turn( years( calendar::turn ), calendar::year_length(), _( "Set year to?" ) );
1874                         break;
1875                     case 1:
1876                         set_turn( static_cast<int>( season_of_year( calendar::turn ) ), calendar::season_length(),
1877                                   _( "Set season to?  (0 = spring)" ) );
1878                         break;
1879                     case 2:
1880                         set_turn( day_of_season<int>( calendar::turn ), 1_days, _( "Set days to?" ) );
1881                         break;
1882                     case 3:
1883                         set_turn( hour_of_day<int>( calendar::turn ), 1_hours, _( "Set hour to?" ) );
1884                         break;
1885                     case 4:
1886                         set_turn( minute_of_hour<int>( calendar::turn ), 1_minutes, _( "Set minute to?" ) );
1887                         break;
1888                     case 5:
1889                         set_turn( to_turns<int>( calendar::turn - calendar::turn_zero ), 1_turns,
1890                                   string_format( _( "Set turn to?  (One day is %i turns)" ), to_turns<int>( 1_days ) ).c_str() );
1891                         break;
1892                     default:
1893                         break;
1894                 }
1895             } while( smenu.ret != UILIST_CANCEL );
1896         }
1897         break;
1898         case debug_menu_index::SET_AUTOMOVE: {
1899             const cata::optional<tripoint> dest = g->look_around();
1900             if( !dest || *dest == player_character.pos() ) {
1901                 break;
1902             }
1903 
1904             auto rt = here.route( player_character.pos(), *dest, player_character.get_pathfinding_settings(),
1905                                   player_character.get_path_avoid() );
1906             if( !rt.empty() ) {
1907                 player_character.set_destination( rt );
1908             } else {
1909                 popup( "Couldn't find path" );
1910             }
1911         }
1912         break;
1913         case debug_menu_index::SHOW_MUT_CAT:
1914             for( const auto &elem : player_character.mutation_category_level ) {
1915                 add_msg( "%s: %d", elem.first.c_str(), elem.second );
1916             }
1917             break;
1918 
1919         case debug_menu_index::OM_EDITOR:
1920             ui::omap::display_editor();
1921             break;
1922 
1923         case debug_menu_index::BENCHMARK: {
1924             const int ms = string_input_popup()
1925                            .title( _( "Enter benchmark length (in milliseconds):" ) )
1926                            .width( 20 )
1927                            .text( "5000" )
1928                            .query_int();
1929             debug_menu::draw_benchmark( ms );
1930         }
1931         break;
1932 
1933         case debug_menu_index::OM_TELEPORT:
1934             debug_menu::teleport_overmap();
1935             break;
1936         case debug_menu_index::OM_TELEPORT_COORDINATES:
1937             debug_menu::teleport_overmap( true );
1938             break;
1939         case debug_menu_index::TRAIT_GROUP:
1940             trait_group::debug_spawn();
1941             break;
1942         case debug_menu_index::ENABLE_ACHIEVEMENTS:
1943             if( achievements.is_enabled() ) {
1944                 popup( _( "Achievements are already enabled" ) );
1945             } else {
1946                 achievements.set_enabled( true );
1947                 popup( _( "Achievements enabled" ) );
1948             }
1949             break;
1950         case debug_menu_index::SHOW_MSG:
1951             debugmsg( "Test debugmsg" );
1952             break;
1953         case debug_menu_index::CRASH_GAME:
1954             raise( SIGSEGV );
1955             break;
1956         case debug_menu_index::MAP_EXTRA: {
1957             const std::vector<std::string> &mx_str = MapExtras::get_all_function_names();
1958             uilist mx_menu;
1959             for( const std::string &extra : mx_str ) {
1960                 mx_menu.addentry( -1, true, -1, extra );
1961             }
1962             mx_menu.query();
1963             int mx_choice = mx_menu.ret;
1964             if( mx_choice >= 0 && mx_choice < static_cast<int>( mx_str.size() ) ) {
1965                 const tripoint_abs_omt where_omt( ui::omap::choose_point() );
1966                 if( where_omt != overmap::invalid_tripoint ) {
1967                     tripoint_abs_sm where_sm = project_to<coords::sm>( where_omt );
1968                     tinymap mx_map;
1969                     mx_map.load( where_sm, false );
1970                     MapExtras::apply_function( mx_str[mx_choice], mx_map, where_sm.raw() );
1971                     g->load_npcs();
1972                     here.invalidate_map_cache( here.get_abs_sub().z );
1973                 }
1974             }
1975             break;
1976         }
1977         case debug_menu_index::NESTED_MAPGEN:
1978             debug_menu::spawn_nested_mapgen();
1979             break;
1980         case debug_menu_index::DISPLAY_NPC_PATH:
1981             g->debug_pathfinding = !g->debug_pathfinding;
1982             break;
1983         case debug_menu_index::PRINT_FACTION_INFO: {
1984             int count = 0;
1985             for( const auto &elem : g->faction_manager_ptr->all() ) {
1986                 std::cout << std::to_string( count ) << " Faction_id key in factions map = " << elem.first.str() <<
1987                           std::endl;
1988                 std::cout << std::to_string( count ) << " Faction name associated with this id is " <<
1989                           elem.second.name << std::endl;
1990                 std::cout << std::to_string( count ) << " the id of that faction object is " << elem.second.id.str()
1991                           << std::endl;
1992                 count++;
1993             }
1994             std::cout << "Player faction is " << player_character.get_faction()->id.str() << std::endl;
1995             break;
1996         }
1997         case debug_menu_index::PRINT_NPC_MAGIC: {
1998             for( npc &guy : g->all_npcs() ) {
1999                 const std::vector<spell_id> spells = guy.magic->spells();
2000                 if( spells.empty() ) {
2001                     std::cout << guy.disp_name() << " does not know any spells." << std::endl;
2002                     continue;
2003                 }
2004                 std::cout << guy.disp_name() << "knows : ";
2005                 int counter = 1;
2006                 for( const spell_id &sp : spells ) {
2007                     std::cout << sp->name.translated() << " ";
2008                     if( counter < static_cast<int>( spells.size() ) ) {
2009                         std::cout << "and ";
2010                     } else {
2011                         std::cout << "." << std::endl;
2012                     }
2013                     counter++;
2014                 }
2015             }
2016             break;
2017         }
2018         case debug_menu_index::QUIT_NOSAVE:
2019             if( query_yn(
2020                     _( "Quit without saving?  This may cause issues such as duplicated or missing items and vehicles!" ) ) ) {
2021                 player_character.moves = 0;
2022                 g->uquit = QUIT_NOSAVED;
2023             }
2024             break;
2025         case debug_menu_index::TEST_WEATHER: {
2026             get_weather().get_cur_weather_gen().test_weather( g->get_seed(),
2027                     get_weather().next_instance_allowed );
2028         }
2029         break;
2030 
2031         case debug_menu_index::SAVE_SCREENSHOT: {
2032 #if defined(TILES)
2033             // check that the current '<world>/screenshots' directory exists
2034             std::stringstream map_directory;
2035             map_directory << PATH_INFO::world_base_save_path() << "/screenshots/";
2036             assure_dir_exist( map_directory.str() );
2037 
2038             // build file name: <map_dir>/screenshots/[<character_name>]_<date>.png
2039             // Date format is a somewhat ISO-8601 compliant GMT time date (except for some characters that wouldn't pass on most file systems like ':').
2040             std::time_t time = std::time( nullptr );
2041             std::stringstream date_buffer;
2042             date_buffer << std::put_time( std::gmtime( &time ), "%F_%H-%M-%S_%z" );
2043             const auto tmp_file_name = string_format( "[%s]_%s.png", player_character.get_name(),
2044                                        date_buffer.str() );
2045 
2046             std::string file_name = ensure_valid_file_name( tmp_file_name );
2047             auto current_file_path = map_directory.str() + file_name;
2048 
2049             // Take a screenshot of the viewport.
2050             if( g->take_screenshot( current_file_path ) ) {
2051                 popup( _( "Successfully saved your screenshot to: %s" ), map_directory.str() );
2052             } else {
2053                 popup( _( "An error occurred while trying to save the screenshot." ) );
2054             }
2055 #else
2056             popup( _( "This binary was not compiled with tiles support." ) );
2057 #endif
2058         }
2059         break;
2060 
2061         case debug_menu_index::GAME_REPORT: {
2062             // generate a game report, useful for bug reporting.
2063             std::string report = game_info::game_report();
2064             // write to log
2065             DebugLog( DL_ALL, DC_ALL ) << " GAME REPORT:\n" << report;
2066             std::string popup_msg = _( "Report written to debug.log" );
2067 #if defined(TILES)
2068             // copy to clipboard
2069             int clipboard_result = SDL_SetClipboardText( report.c_str() );
2070             printErrorIf( clipboard_result != 0, "Error while copying the game report to the clipboard." );
2071             if( clipboard_result == 0 ) {
2072                 popup_msg += _( " and to the clipboard." );
2073             }
2074 #endif
2075             popup( popup_msg );
2076         }
2077         break;
2078         case debug_menu_index::LEARN_SPELLS:
2079             if( spell_type::get_all().empty() ) {
2080                 add_msg( m_bad, _( "There are no spells to learn.  You must install a mod that adds some." ) );
2081             } else {
2082                 for( const spell_type &learn : spell_type::get_all() ) {
2083                     player_character.magic->learn_spell( &learn, player_character, true );
2084                 }
2085                 add_msg( m_good,
2086                          _( "You have become an Archwizardpriest!  What will you do with your newfound power?" ) );
2087             }
2088             break;
2089         case debug_menu_index::LEVEL_SPELLS: {
2090             std::vector<spell *> spells = player_character.magic->get_spells();
2091             if( spells.empty() ) {
2092                 add_msg( m_bad, _( "Try learning some spells first." ) );
2093                 return;
2094             }
2095             std::vector<uilist_entry> uiles;
2096             {
2097                 uilist_entry uile( _( "Spell" ) );
2098                 uile.ctxt = string_format( "%s %s",
2099                                            //~ translation should not exceed 4 console cells
2100                                            right_justify( _( "LVL" ), 4 ),
2101                                            //~ translation should not exceed 4 console cells
2102                                            right_justify( _( "MAX" ), 4 ) );
2103                 uile.enabled = false;
2104                 uiles.emplace_back( uile );
2105             }
2106             int retval = 0;
2107             for( spell *sp : spells ) {
2108                 uilist_entry uile( sp->name() );
2109                 uile.ctxt = string_format( "%4d %4d", sp->get_level(), sp->get_max_level() );
2110                 uile.retval = retval++;
2111                 uile.enabled = !sp->is_max_level();
2112                 uiles.emplace_back( uile );
2113             }
2114             int action = uilist( _( "Debug level spell:" ), uiles );
2115             if( action < 0 ) {
2116                 return;
2117             }
2118             int desired_level = 0;
2119             int cur_level = spells[action]->get_level();
2120             query_int( desired_level, _( "Desired Spell Level: (Current %d)" ), cur_level );
2121             desired_level = std::min( desired_level, spells[action]->get_max_level() );
2122             while( cur_level < desired_level ) {
2123                 spells[action]->gain_level();
2124                 cur_level = spells[action]->get_level();
2125             }
2126             add_msg( m_good, _( "%s is now level %d!" ), spells[action]->name(), spells[action]->get_level() );
2127             break;
2128         }
2129         case debug_menu_index::TEST_MAP_EXTRA_DISTRIBUTION:
2130             MapExtras::debug_spawn_test();
2131             break;
2132 
2133         case debug_menu_index::GENERATE_EFFECT_LIST:
2134             write_to_file( "effect_list.output", [&]( std::ostream & testfile ) {
2135                 testfile << "|;id;duration;intensity;perm;bp" << std::endl;
2136 
2137                 for( const effect &eff :  get_player_character().get_effects() ) {
2138                     testfile << "|;" << eff.get_id().str() << ";" << to_string( eff.get_duration() ) << ";" <<
2139                              eff.get_intensity() << ";" << ( eff.is_permanent() ? "true" : "false" ) << ";" <<
2140                              eff.get_bp().id().str()
2141                              << std::endl;
2142                 }
2143 
2144             }, "effect_list" );
2145 
2146             popup( _( "Effect list written to effect_list.output" ) );
2147             break;
2148 
2149         case debug_menu_index::VEHICLE_BATTERY_CHARGE: {
2150 
2151             optional_vpart_position v_part_pos = here.veh_at( player_character.pos() );
2152             if( !v_part_pos ) {
2153                 add_msg( m_bad, _( "There's no vehicle there." ) );
2154                 break;
2155             }
2156 
2157             int amount = 0;
2158             string_input_popup popup;
2159             popup
2160             .title( _( "By how much?  (in kJ, negative to discharge)" ) )
2161             .width( 30 )
2162             .edit( amount );
2163             if( !popup.canceled() ) {
2164                 vehicle &veh = v_part_pos->vehicle();
2165                 if( amount >= 0 ) {
2166                     veh.charge_battery( amount, false );
2167                 } else {
2168                     veh.discharge_battery( -amount, false );
2169                 }
2170             }
2171             break;
2172         }
2173 
2174         case debug_menu_index::last:
2175             return;
2176     }
2177     here.invalidate_map_cache( here.get_abs_sub().z );
2178 }
2179 
2180 } // namespace debug_menu
2181