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