OpenTTD Source 20251213-master-g1091fa6071
console_cmds.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
10#include "stdafx.h"
12#include "console_internal.h"
13#include "debug.h"
14#include "engine_func.h"
15#include "landscape.h"
16#include "saveload/saveload.h"
17#include "network/core/network_game_info.h"
18#include "network/network.h"
23#include "command_func.h"
24#include "settings_func.h"
25#include "fios.h"
26#include "fileio_func.h"
27#include "fontcache.h"
28#include "screenshot.h"
29#include "genworld.h"
30#include "strings_func.h"
31#include "viewport_func.h"
32#include "window_func.h"
33#include "timer/timer.h"
34#include "company_func.h"
35#include "gamelog.h"
36#include "ai/ai.hpp"
37#include "ai/ai_config.hpp"
38#include "newgrf.h"
39#include "newgrf_profiling.h"
40#include "console_func.h"
41#include "engine_base.h"
42#include "road.h"
43#include "rail.h"
44#include "game/game.hpp"
45#include "3rdparty/fmt/chrono.h"
46#include "company_cmd.h"
47#include "misc_cmd.h"
48
49#if defined(WITH_ZLIB)
51#endif /* WITH_ZLIB */
52
53#include "table/strings.h"
54
55#include "safeguards.h"
56
57/* scriptfile handling */
59
60/* Scheduled execution handling. */
61static std::string _scheduled_monthly_script;
62
64static const IntervalTimer<TimerGameCalendar> _scheduled_monthly_timer = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) {
65 if (_scheduled_monthly_script.empty()) {
66 return;
67 }
68
69 /* Clear the schedule before rather than after the script to allow the script to itself call
70 * schedule without it getting immediately cleared. */
71 const std::string filename = _scheduled_monthly_script;
73
74 IConsolePrint(CC_DEFAULT, "Executing scheduled script file '{}'...", filename);
75 IConsoleCmdExec(fmt::format("exec {}", filename));
76}};
77
84template <typename T>
85static std::optional<T> ParseType(std::string_view arg)
86{
87 auto i = ParseInteger(arg);
88 if (i.has_value()) return static_cast<T>(*i);
89 return std::nullopt;
90}
91
93class ConsoleFileList : public FileList {
94public:
96 {
97 }
98
101 {
102 this->clear();
103 this->file_list_valid = false;
104 }
105
110 void ValidateFileList(bool force_reload = false)
111 {
112 if (force_reload || !this->file_list_valid) {
113 this->BuildFileList(this->abstract_filetype, SLO_LOAD, this->show_dirs);
114 this->file_list_valid = true;
115 }
116 }
117
120 bool file_list_valid = false;
121};
122
126
127/****************
128 * command hooks
129 ****************/
130
135static inline bool NetworkAvailable(bool echo)
136{
137 if (!_network_available) {
138 if (echo) IConsolePrint(CC_ERROR, "You cannot use this command because there is no network available.");
139 return false;
140 }
141 return true;
142}
143
149{
150 if (!NetworkAvailable(echo)) return CHR_DISALLOW;
151
152 if (!_network_server) {
153 if (echo) IConsolePrint(CC_ERROR, "This command is only available to a network server.");
154 return CHR_DISALLOW;
155 }
156 return CHR_ALLOW;
157}
158
164{
165 if (!NetworkAvailable(echo)) return CHR_DISALLOW;
166
167 if (_network_server) {
168 if (echo) IConsolePrint(CC_ERROR, "This command is not available to a network server.");
169 return CHR_DISALLOW;
170 }
171 return CHR_ALLOW;
172}
173
179{
180 if (!NetworkAvailable(echo)) return CHR_DISALLOW;
181
183 if (echo) IConsolePrint(CC_ERROR, "Not connected. This command is only available in multiplayer.");
184 return CHR_DISALLOW;
185 }
186 return CHR_ALLOW;
187}
188
194{
195 if (!NetworkAvailable(echo)) return CHR_DISALLOW;
196
197 if (_network_dedicated) {
198 if (echo) IConsolePrint(CC_ERROR, "This command is not available to a dedicated network server.");
199 return CHR_DISALLOW;
200 }
201 return CHR_ALLOW;
202}
203
209{
210 if (_networking) {
211 if (echo) IConsolePrint(CC_ERROR, "This command is forbidden in multiplayer.");
212 return CHR_DISALLOW;
213 }
214 return CHR_ALLOW;
215}
216
222{
224 if (echo) IConsolePrint(CC_ERROR, "This command is only available to a network server.");
225 return CHR_DISALLOW;
226 }
227 return CHR_ALLOW;
228}
229
230static ConsoleHookResult ConHookNewGRFDeveloperTool(bool echo)
231{
233 if (_game_mode == GM_MENU) {
234 if (echo) IConsolePrint(CC_ERROR, "This command is only available in-game and in the editor.");
235 return CHR_DISALLOW;
236 }
237 return ConHookNoNetwork(echo);
238 }
239 return CHR_HIDE;
240}
241
246static bool ConResetEngines(std::span<std::string_view> argv)
247{
248 if (argv.empty()) {
249 IConsolePrint(CC_HELP, "Reset status data of all engines. This might solve some issues with 'lost' engines. Usage: 'resetengines'.");
250 return true;
251 }
252
254 return true;
255}
256
262static bool ConResetEnginePool(std::span<std::string_view> argv)
263{
264 if (argv.empty()) {
265 IConsolePrint(CC_HELP, "Reset NewGRF allocations of engine slots. This will remove invalid engine definitions, and might make default engines available again.");
266 return true;
267 }
268
269 if (_game_mode == GM_MENU) {
270 IConsolePrint(CC_ERROR, "This command is only available in-game and in the editor.");
271 return true;
272 }
273
275 IConsolePrint(CC_ERROR, "This can only be done when there are no vehicles in the game.");
276 return true;
277 }
278
279 return true;
280}
281
282#ifdef _DEBUG
288static bool ConResetTile(std::span<std::string_view> argv)
289{
290 if (argv.empty()) {
291 IConsolePrint(CC_HELP, "Reset a tile to bare land. Usage: 'resettile <tile>'.");
292 IConsolePrint(CC_HELP, "Tile can be either decimal (34161) or hexadecimal (0x4a5B).");
293 return true;
294 }
295
296 if (argv.size() == 2) {
297 auto result = ParseInteger(argv[1], 0);
298 if (result.has_value() && IsValidTile(*result)) {
299 DoClearSquare(TileIndex{*result});
300 return true;
301 }
302 }
303
304 return false;
305}
306#endif /* _DEBUG */
307
313static bool ConZoomToLevel(std::span<std::string_view> argv)
314{
315 switch (argv.size()) {
316 case 0:
317 IConsolePrint(CC_HELP, "Set the current zoom level of the main viewport.");
318 IConsolePrint(CC_HELP, "Usage: 'zoomto <level>'.");
319
321 IConsolePrint(CC_HELP, "The lowest zoom-in level allowed by current client settings is {}.", std::max(ZoomLevel::Min, _settings_client.gui.zoom_min));
322 } else {
323 IConsolePrint(CC_HELP, "The lowest supported zoom-in level is {}.", std::max(ZoomLevel::Min, _settings_client.gui.zoom_min));
324 }
325
327 IConsolePrint(CC_HELP, "The highest zoom-out level allowed by current client settings is {}.", std::min(_settings_client.gui.zoom_max, ZoomLevel::Max));
328 } else {
329 IConsolePrint(CC_HELP, "The highest supported zoom-out level is {}.", std::min(_settings_client.gui.zoom_max, ZoomLevel::Max));
330 }
331 return true;
332
333 case 2: {
334 auto level = ParseInteger<std::underlying_type_t<ZoomLevel>>(argv[1]);
335 if (level.has_value()) {
336 auto zoom_lvl = static_cast<ZoomLevel>(*level);
337 if (!IsInsideMM(zoom_lvl, ZoomLevel::Begin, ZoomLevel::End)) {
338 IConsolePrint(CC_ERROR, "Invalid zoom level. Valid range is {} to {}.", ZoomLevel::Min, ZoomLevel::Max);
340 IConsolePrint(CC_ERROR, "Current client settings limit zoom levels to range {} to {}.", _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
341 } else {
342 Window *w = GetMainWindow();
343 Viewport &vp = *w->viewport;
344 while (vp.zoom > zoom_lvl) DoZoomInOutWindow(ZOOM_IN, w);
345 while (vp.zoom < zoom_lvl) DoZoomInOutWindow(ZOOM_OUT, w);
346 }
347 return true;
348 }
349 break;
350 }
351 }
352
353 return false;
354}
355
365static bool ConScrollToTile(std::span<std::string_view> argv)
366{
367 if (argv.empty()) {
368 IConsolePrint(CC_HELP, "Center the screen on a given tile.");
369 IConsolePrint(CC_HELP, "Usage: 'scrollto [instant] <tile>' or 'scrollto [instant] <x> <y>'.");
370 IConsolePrint(CC_HELP, "Numbers can be either decimal (34161) or hexadecimal (0x4a5B).");
371 IConsolePrint(CC_HELP, "'instant' will immediately move and redraw viewport without smooth scrolling.");
372 return true;
373 }
374 if (argv.size() < 2) return false;
375
376 uint32_t arg_index = 1;
377 bool instant = false;
378 if (argv[arg_index] == "instant") {
379 ++arg_index;
380 instant = true;
381 }
382
383 switch (argv.size() - arg_index) {
384 case 1: {
385 auto result = ParseInteger(argv[arg_index], 0);
386 if (result.has_value()) {
387 if (*result >= Map::Size()) {
388 IConsolePrint(CC_ERROR, "Tile does not exist.");
389 return true;
390 }
391 ScrollMainWindowToTile(TileIndex{*result}, instant);
392 return true;
393 }
394 break;
395 }
396
397 case 2: {
398 auto x = ParseInteger(argv[arg_index], 0);
399 auto y = ParseInteger(argv[arg_index + 1], 0);
400 if (x.has_value() && y.has_value()) {
401 if (*x >= Map::SizeX() || *y >= Map::SizeY()) {
402 IConsolePrint(CC_ERROR, "Tile does not exist.");
403 return true;
404 }
405 ScrollMainWindowToTile(TileXY(*x, *y), instant);
406 return true;
407 }
408 break;
409 }
410 }
411
412 return false;
413}
414
420static bool ConSave(std::span<std::string_view> argv)
421{
422 if (argv.empty()) {
423 IConsolePrint(CC_HELP, "Save the current game. Usage: 'save <filename>'.");
424 return true;
425 }
426
427 if (argv.size() == 2) {
428 std::string filename = fmt::format("{}.sav", argv[1]);
429 IConsolePrint(CC_DEFAULT, "Saving map...");
430
431 if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, SAVE_DIR) != SL_OK) {
432 IConsolePrint(CC_ERROR, "Saving map failed.");
433 } else {
434 IConsolePrint(CC_INFO, "Map successfully saved to '{}'.", filename);
435 }
436 return true;
437 }
438
439 return false;
440}
441
446static bool ConSaveConfig(std::span<std::string_view> argv)
447{
448 if (argv.empty()) {
449 IConsolePrint(CC_HELP, "Saves the configuration for new games to the configuration file, typically 'openttd.cfg'.");
450 IConsolePrint(CC_HELP, "It does not save the configuration of the current game to the configuration file.");
451 return true;
452 }
453
454 SaveToConfig();
455 IConsolePrint(CC_DEFAULT, "Saved config.");
456 return true;
457}
458
459static bool ConLoad(std::span<std::string_view> argv)
460{
461 if (argv.empty()) {
462 IConsolePrint(CC_HELP, "Load a game by name or index. Usage: 'load <file | number>'.");
463 return true;
464 }
465
466 if (argv.size() != 2) return false;
467
468 std::string_view file = argv[1];
471 if (item != nullptr) {
472 if (item->type.abstract == FT_SAVEGAME) {
474 _file_to_saveload.Set(*item);
475 } else {
476 IConsolePrint(CC_ERROR, "'{}' is not a savegame.", file);
477 }
478 } else {
479 IConsolePrint(CC_ERROR, "'{}' cannot be found.", file);
480 }
481
482 return true;
483}
484
485static bool ConLoadScenario(std::span<std::string_view> argv)
486{
487 if (argv.empty()) {
488 IConsolePrint(CC_HELP, "Load a scenario by name or index. Usage: 'load_scenario <file | number>'.");
489 return true;
490 }
491
492 if (argv.size() != 2) return false;
493
494 std::string_view file = argv[1];
497 if (item != nullptr) {
498 if (item->type.abstract == FT_SCENARIO) {
500 _file_to_saveload.Set(*item);
501 } else {
502 IConsolePrint(CC_ERROR, "'{}' is not a scenario.", file);
503 }
504 } else {
505 IConsolePrint(CC_ERROR, "'{}' cannot be found.", file);
506 }
507
508 return true;
509}
510
511static bool ConLoadHeightmap(std::span<std::string_view> argv)
512{
513 if (argv.empty()) {
514 IConsolePrint(CC_HELP, "Load a heightmap by name or index. Usage: 'load_heightmap <file | number>'.");
515 return true;
516 }
517
518 if (argv.size() != 2) return false;
519
520 std::string_view file = argv[1];
523 if (item != nullptr) {
524 if (item->type.abstract == FT_HEIGHTMAP) {
526 _file_to_saveload.Set(*item);
527 } else {
528 IConsolePrint(CC_ERROR, "'{}' is not a heightmap.", file);
529 }
530 } else {
531 IConsolePrint(CC_ERROR, "'{}' cannot be found.", file);
532 }
533
534 return true;
535}
536
537static bool ConRemove(std::span<std::string_view> argv)
538{
539 if (argv.empty()) {
540 IConsolePrint(CC_HELP, "Remove a savegame by name or index. Usage: 'rm <file | number>'.");
541 return true;
542 }
543
544 if (argv.size() != 2) return false;
545
546 std::string_view file = argv[1];
549 if (item != nullptr) {
550 if (item->type.abstract == FT_SAVEGAME) {
551 if (!FioRemove(item->name)) {
552 IConsolePrint(CC_ERROR, "Failed to delete '{}'.", item->name);
553 }
554 } else {
555 IConsolePrint(CC_ERROR, "'{}' is not a savegame.", file);
556 }
557 } else {
558 IConsolePrint(CC_ERROR, "'{}' could not be found.", file);
559 }
560
562 return true;
563}
564
565
566/* List all the files in the current dir via console */
567static bool ConListFiles(std::span<std::string_view> argv)
568{
569 if (argv.empty()) {
570 IConsolePrint(CC_HELP, "List all loadable savegames and directories in the current dir via console. Usage: 'ls | dir'.");
571 return true;
572 }
573
575 for (uint i = 0; i < _console_file_list_savegame.size(); i++) {
576 IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_savegame[i].title.GetDecodedString());
577 }
578
579 return true;
580}
581
582/* List all the scenarios */
583static bool ConListScenarios(std::span<std::string_view> argv)
584{
585 if (argv.empty()) {
586 IConsolePrint(CC_HELP, "List all loadable scenarios. Usage: 'list_scenarios'.");
587 return true;
588 }
589
591 for (uint i = 0; i < _console_file_list_scenario.size(); i++) {
592 IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_scenario[i].title.GetDecodedString());
593 }
594
595 return true;
596}
597
598/* List all the heightmaps */
599static bool ConListHeightmaps(std::span<std::string_view> argv)
600{
601 if (argv.empty()) {
602 IConsolePrint(CC_HELP, "List all loadable heightmaps. Usage: 'list_heightmaps'.");
603 return true;
604 }
605
607 for (uint i = 0; i < _console_file_list_heightmap.size(); i++) {
608 IConsolePrint(CC_DEFAULT, "{}) {}", i, _console_file_list_heightmap[i].title.GetDecodedString());
609 }
610
611 return true;
612}
613
614/* Change the dir via console */
615static bool ConChangeDirectory(std::span<std::string_view> argv)
616{
617 if (argv.empty()) {
618 IConsolePrint(CC_HELP, "Change the dir via console. Usage: 'cd <directory | number>'.");
619 return true;
620 }
621
622 if (argv.size() != 2) return false;
623
624 std::string_view file = argv[1];
627 if (item != nullptr) {
628 switch (item->type.detailed) {
629 case DFT_FIOS_DIR:
630 case DFT_FIOS_DRIVE:
631 case DFT_FIOS_PARENT:
632 FiosBrowseTo(item);
633 break;
634 default: IConsolePrint(CC_ERROR, "{}: Not a directory.", file);
635 }
636 } else {
637 IConsolePrint(CC_ERROR, "{}: No such file or directory.", file);
638 }
639
641 return true;
642}
643
644static bool ConPrintWorkingDirectory(std::span<std::string_view> argv)
645{
646 if (argv.empty()) {
647 IConsolePrint(CC_HELP, "Print out the current working directory. Usage: 'pwd'.");
648 return true;
649 }
650
651 /* XXX - Workaround for broken file handling */
654
656 return true;
657}
658
659static bool ConClearBuffer(std::span<std::string_view> argv)
660{
661 if (argv.empty()) {
662 IConsolePrint(CC_HELP, "Clear the console buffer. Usage: 'clear'.");
663 return true;
664 }
665
666 IConsoleClearBuffer();
668 return true;
669}
670
671
672/**********************************
673 * Network Core Console Commands
674 **********************************/
675
676static bool ConKickOrBan(std::string_view arg, bool ban, std::string_view reason)
677{
678 uint n;
679
680 if (arg.find_first_of(".:") == std::string::npos) { // banning with ID
681 auto client_id = ParseType<ClientID>(arg);
682 if (!client_id.has_value()) {
683 IConsolePrint(CC_ERROR, "The given client-id is not a valid number.");
684 return true;
685 }
686
687 /* Don't kill the server, or the client doing the rcon. The latter can't be kicked because
688 * kicking frees closes and subsequently free the connection related instances, which we
689 * would be reading from and writing to after returning. So we would read or write data
690 * from freed memory up till the segfault triggers. */
691 if (*client_id == CLIENT_ID_SERVER || *client_id == _redirect_console_to_client) {
692 IConsolePrint(CC_ERROR, "You can not {} yourself!", ban ? "ban" : "kick");
693 return true;
694 }
695
697 if (ci == nullptr) {
698 IConsolePrint(CC_ERROR, "Invalid client-id.");
699 return true;
700 }
701
702 if (!ban) {
703 /* Kick only this client, not all clients with that IP */
704 NetworkServerKickClient(*client_id, reason);
705 return true;
706 }
707
708 /* When banning, kick+ban all clients with that IP */
709 n = NetworkServerKickOrBanIP(*client_id, ban, reason);
710 } else {
711 n = NetworkServerKickOrBanIP(arg, ban, reason);
712 }
713
714 if (n == 0) {
715 IConsolePrint(CC_DEFAULT, ban ? "Client not online, address added to banlist." : "Client not found.");
716 } else {
717 IConsolePrint(CC_DEFAULT, "{}ed {} client(s).", ban ? "Bann" : "Kick", n);
718 }
719
720 return true;
721}
722
723static bool ConKick(std::span<std::string_view> argv)
724{
725 if (argv.empty()) {
726 IConsolePrint(CC_HELP, "Kick a client from a network game. Usage: 'kick <ip | client-id> [<kick-reason>]'.");
727 IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'.");
728 return true;
729 }
730
731 if (argv.size() != 2 && argv.size() != 3) return false;
732
733 /* No reason supplied for kicking */
734 if (argv.size() == 2) return ConKickOrBan(argv[1], false, {});
735
736 /* Reason for kicking supplied */
737 size_t kick_message_length = argv[2].size();
738 if (kick_message_length >= 255) {
739 IConsolePrint(CC_ERROR, "Maximum kick message length is 254 characters. You entered {} characters.", kick_message_length);
740 return false;
741 } else {
742 return ConKickOrBan(argv[1], false, argv[2]);
743 }
744}
745
746static bool ConBan(std::span<std::string_view> argv)
747{
748 if (argv.empty()) {
749 IConsolePrint(CC_HELP, "Ban a client from a network game. Usage: 'ban <ip | client-id> [<ban-reason>]'.");
750 IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'.");
751 IConsolePrint(CC_HELP, "If the client is no longer online, you can still ban their IP.");
752 return true;
753 }
754
755 if (argv.size() != 2 && argv.size() != 3) return false;
756
757 /* No reason supplied for kicking */
758 if (argv.size() == 2) return ConKickOrBan(argv[1], true, {});
759
760 /* Reason for kicking supplied */
761 size_t kick_message_length = argv[2].size();
762 if (kick_message_length >= 255) {
763 IConsolePrint(CC_ERROR, "Maximum kick message length is 254 characters. You entered {} characters.", kick_message_length);
764 return false;
765 } else {
766 return ConKickOrBan(argv[1], true, argv[2]);
767 }
768}
769
770static bool ConUnBan(std::span<std::string_view> argv)
771{
772 if (argv.empty()) {
773 IConsolePrint(CC_HELP, "Unban a client from a network game. Usage: 'unban <ip | banlist-index>'.");
774 IConsolePrint(CC_HELP, "For a list of banned IP's, see the command 'banlist'.");
775 return true;
776 }
777
778 if (argv.size() != 2) return false;
779
780 /* Try by IP. */
781 uint index;
782 for (index = 0; index < _network_ban_list.size(); index++) {
783 if (_network_ban_list[index] == argv[1]) break;
784 }
785
786 /* Try by index. */
787 if (index >= _network_ban_list.size()) {
788 index = ParseInteger(argv[1]).value_or(0) - 1U; // let it wrap
789 }
790
791 if (index < _network_ban_list.size()) {
792 IConsolePrint(CC_DEFAULT, "Unbanned {}.", _network_ban_list[index]);
793 _network_ban_list.erase(_network_ban_list.begin() + index);
794 } else {
795 IConsolePrint(CC_DEFAULT, "Invalid list index or IP not in ban-list.");
796 IConsolePrint(CC_DEFAULT, "For a list of banned IP's, see the command 'banlist'.");
797 }
798
799 return true;
800}
801
802static bool ConBanList(std::span<std::string_view> argv)
803{
804 if (argv.empty()) {
805 IConsolePrint(CC_HELP, "List the IP's of banned clients: Usage 'banlist'.");
806 return true;
807 }
808
809 IConsolePrint(CC_DEFAULT, "Banlist:");
810
811 uint i = 1;
812 for (const auto &entry : _network_ban_list) {
813 IConsolePrint(CC_DEFAULT, " {}) {}", i, entry);
814 i++;
815 }
816
817 return true;
818}
819
820static bool ConPauseGame(std::span<std::string_view> argv)
821{
822 if (argv.empty()) {
823 IConsolePrint(CC_HELP, "Pause a network game. Usage: 'pause'.");
824 return true;
825 }
826
827 if (_game_mode == GM_MENU) {
828 IConsolePrint(CC_ERROR, "This command is only available in-game and in the editor.");
829 return true;
830 }
831
834 if (!_networking) IConsolePrint(CC_DEFAULT, "Game paused.");
835 } else {
836 IConsolePrint(CC_DEFAULT, "Game is already paused.");
837 }
838
839 return true;
840}
841
842static bool ConUnpauseGame(std::span<std::string_view> argv)
843{
844 if (argv.empty()) {
845 IConsolePrint(CC_HELP, "Unpause a network game. Usage: 'unpause'.");
846 return true;
847 }
848
849 if (_game_mode == GM_MENU) {
850 IConsolePrint(CC_ERROR, "This command is only available in-game and in the editor.");
851 return true;
852 }
853
856 if (!_networking) IConsolePrint(CC_DEFAULT, "Game unpaused.");
857 } else if (_pause_mode.Test(PauseMode::Error)) {
858 IConsolePrint(CC_DEFAULT, "Game is in error state and cannot be unpaused via console.");
859 } else if (_pause_mode.Any()) {
860 IConsolePrint(CC_DEFAULT, "Game cannot be unpaused manually; disable pause_on_join/min_active_clients.");
861 } else {
862 IConsolePrint(CC_DEFAULT, "Game is already unpaused.");
863 }
864
865 return true;
866}
867
868static bool ConRcon(std::span<std::string_view> argv)
869{
870 if (argv.empty()) {
871 IConsolePrint(CC_HELP, "Remote control the server from another client. Usage: 'rcon <password> <command>'.");
872 IConsolePrint(CC_HELP, "Remember to enclose the command in quotes, otherwise only the first parameter is sent.");
873 IConsolePrint(CC_HELP, "When your client's public key is in the 'authorized keys' for 'rcon', the password is not checked and may be '*'.");
874 return true;
875 }
876
877 if (argv.size() < 3) return false;
878
879 if (_network_server) {
880 IConsoleCmdExec(argv[2]);
881 } else {
882 NetworkClientSendRcon(argv[1], argv[2]);
883 }
884 return true;
885}
886
887static bool ConStatus(std::span<std::string_view> argv)
888{
889 if (argv.empty()) {
890 IConsolePrint(CC_HELP, "List the status of all clients connected to the server. Usage 'status'.");
891 return true;
892 }
893
895 return true;
896}
897
898static bool ConServerInfo(std::span<std::string_view> argv)
899{
900 if (argv.empty()) {
901 IConsolePrint(CC_HELP, "List current and maximum client/company limits. Usage 'server_info'.");
902 IConsolePrint(CC_HELP, "You can change these values by modifying settings 'network.max_clients' and 'network.max_companies'.");
903 return true;
904 }
905
907 IConsolePrint(CC_DEFAULT, "Current/maximum clients: {:3d}/{:3d}", _network_game_info.clients_on, _settings_client.network.max_clients);
908 IConsolePrint(CC_DEFAULT, "Current/maximum companies: {:3d}/{:3d}", Company::GetNumItems(), _settings_client.network.max_companies);
909 IConsolePrint(CC_DEFAULT, "Current spectators: {:3d}", NetworkSpectatorCount());
910
911 return true;
912}
913
914static bool ConClientNickChange(std::span<std::string_view> argv)
915{
916 if (argv.size() != 3) {
917 IConsolePrint(CC_HELP, "Change the nickname of a connected client. Usage: 'client_name <client-id> <new-name>'.");
918 IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'.");
919 return true;
920 }
921
922 auto client_id = ParseType<ClientID>(argv[1]);
923 if (!client_id.has_value()) {
924 IConsolePrint(CC_ERROR, "The given client-id is not a valid number.");
925 return true;
926 }
927
928 if (*client_id == CLIENT_ID_SERVER) {
929 IConsolePrint(CC_ERROR, "Please use the command 'name' to change your own name!");
930 return true;
931 }
932
933 if (NetworkClientInfo::GetByClientID(*client_id) == nullptr) {
934 IConsolePrint(CC_ERROR, "Invalid client-id.");
935 return true;
936 }
937
938 std::string client_name{StrTrimView(argv[2], StringConsumer::WHITESPACE_NO_NEWLINE)};
939 if (!NetworkIsValidClientName(client_name)) {
940 IConsolePrint(CC_ERROR, "Cannot give a client an empty name.");
941 return true;
942 }
943
944 if (!NetworkServerChangeClientName(*client_id, client_name)) {
945 IConsolePrint(CC_ERROR, "Cannot give a client a duplicate name.");
946 }
947
948 return true;
949}
950
951static std::optional<CompanyID> ParseCompanyID(std::string_view arg)
952{
953 auto company_id = ParseType<CompanyID>(arg);
954 if (company_id.has_value() && *company_id <= MAX_COMPANIES) return static_cast<CompanyID>(*company_id - 1);
955 return company_id;
956}
957
958static bool ConJoinCompany(std::span<std::string_view> argv)
959{
960 if (argv.size() < 2) {
961 IConsolePrint(CC_HELP, "Request joining another company. Usage: 'join <company-id>'.");
962 IConsolePrint(CC_HELP, "For valid company-id see company list, use 255 for spectator.");
963 return true;
964 }
965
966 auto company_id = ParseCompanyID(argv[1]);
967 if (!company_id.has_value()) {
968 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
969 return true;
970 }
971
973 if (info == nullptr) {
974 IConsolePrint(CC_ERROR, "You have not joined the game yet!");
975 return true;
976 }
977
978 /* Check we have a valid company id! */
979 if (!Company::IsValidID(*company_id) && *company_id != COMPANY_SPECTATOR) {
980 IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES);
981 return true;
982 }
983
984 if (info->client_playas == *company_id) {
985 IConsolePrint(CC_ERROR, "You are already there!");
986 return true;
987 }
988
989 if (*company_id != COMPANY_SPECTATOR && !Company::IsHumanID(*company_id)) {
990 IConsolePrint(CC_ERROR, "Cannot join AI company.");
991 return true;
992 }
993
994 if (!info->CanJoinCompany(*company_id)) {
995 IConsolePrint(CC_ERROR, "You are not allowed to join this company.");
996 return true;
997 }
998
999 /* non-dedicated server may just do the move! */
1000 if (_network_server) {
1002 } else {
1003 NetworkClientRequestMove(*company_id);
1004 }
1005
1006 return true;
1007}
1008
1009static bool ConMoveClient(std::span<std::string_view> argv)
1010{
1011 if (argv.size() < 3) {
1012 IConsolePrint(CC_HELP, "Move a client to another company. Usage: 'move <client-id> <company-id>'.");
1013 IConsolePrint(CC_HELP, "For valid client-id see 'clients', for valid company-id see 'companies', use 255 for moving to spectators.");
1014 return true;
1015 }
1016
1017 auto client_id = ParseType<ClientID>(argv[1]);
1018 if (!client_id.has_value()) {
1019 IConsolePrint(CC_ERROR, "The given client-id is not a valid number.");
1020 return true;
1021 }
1023
1024 auto company_id = ParseCompanyID(argv[2]);
1025 if (!company_id.has_value()) {
1026 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
1027 return true;
1028 }
1029
1030 /* check the client exists */
1031 if (ci == nullptr) {
1032 IConsolePrint(CC_ERROR, "Invalid client-id, check the command 'clients' for valid client-id's.");
1033 return true;
1034 }
1035
1036 if (!Company::IsValidID(*company_id) && *company_id != COMPANY_SPECTATOR) {
1037 IConsolePrint(CC_ERROR, "Company does not exist. Company-id must be between 1 and {}.", MAX_COMPANIES);
1038 return true;
1039 }
1040
1041 if (*company_id != COMPANY_SPECTATOR && !Company::IsHumanID(*company_id)) {
1042 IConsolePrint(CC_ERROR, "You cannot move clients to AI companies.");
1043 return true;
1044 }
1045
1047 IConsolePrint(CC_ERROR, "You cannot move the server!");
1048 return true;
1049 }
1050
1051 if (ci->client_playas == *company_id) {
1052 IConsolePrint(CC_ERROR, "You cannot move someone to where they already are!");
1053 return true;
1054 }
1055
1056 /* we are the server, so force the update */
1057 NetworkServerDoMove(ci->client_id, *company_id);
1058
1059 return true;
1060}
1061
1062static bool ConResetCompany(std::span<std::string_view> argv)
1063{
1064 if (argv.empty()) {
1065 IConsolePrint(CC_HELP, "Remove an idle company from the game. Usage: 'reset_company <company-id>'.");
1066 IConsolePrint(CC_HELP, "For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc.");
1067 return true;
1068 }
1069
1070 if (argv.size() != 2) return false;
1071
1072 auto index = ParseCompanyID(argv[1]);
1073 if (!index.has_value()) {
1074 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
1075 return true;
1076 }
1077
1078 /* Check valid range */
1079 if (!Company::IsValidID(*index)) {
1080 IConsolePrint(CC_ERROR, "Company does not exist. company-id must be between 1 and {}.", MAX_COMPANIES);
1081 return true;
1082 }
1083
1084 if (!Company::IsHumanID(*index)) {
1085 IConsolePrint(CC_ERROR, "Company is owned by an AI.");
1086 return true;
1087 }
1088
1089 if (NetworkCompanyHasClients(*index)) {
1090 IConsolePrint(CC_ERROR, "Cannot remove company: a client is connected to that company.");
1091 return false;
1092 }
1094 assert(ci != nullptr);
1095 if (ci->client_playas == *index) {
1096 IConsolePrint(CC_ERROR, "Cannot remove company: the server is connected to that company.");
1097 return true;
1098 }
1099
1100 /* It is safe to remove this company */
1102 IConsolePrint(CC_DEFAULT, "Company deleted.");
1103
1104 return true;
1105}
1106
1107static bool ConNetworkClients(std::span<std::string_view> argv)
1108{
1109 if (argv.empty()) {
1110 IConsolePrint(CC_HELP, "Get a list of connected clients including their ID, name, company-id, and IP. Usage: 'clients'.");
1111 return true;
1112 }
1113
1115
1116 return true;
1117}
1118
1119static bool ConNetworkReconnect(std::span<std::string_view> argv)
1120{
1121 if (argv.empty()) {
1122 IConsolePrint(CC_HELP, "Reconnect to server to which you were connected last time. Usage: 'reconnect [<company-id>]'.");
1123 IConsolePrint(CC_HELP, "Company 255 is spectator (default, if not specified), 254 means creating new company.");
1124 IConsolePrint(CC_HELP, "All others are a certain company with Company 1 being #1.");
1125 return true;
1126 }
1127
1129 if (argv.size() >= 2) {
1130 auto company_id = ParseCompanyID(argv[1]);
1131 if (!company_id.has_value()) {
1132 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
1133 return true;
1134 }
1135 if (*company_id >= MAX_COMPANIES && *company_id != COMPANY_NEW_COMPANY && *company_id != COMPANY_SPECTATOR) return false;
1136 playas = *company_id;
1137 }
1138
1139 if (_settings_client.network.last_joined.empty()) {
1140 IConsolePrint(CC_DEFAULT, "No server for reconnecting.");
1141 return true;
1142 }
1143
1144 /* Don't resolve the address first, just print it directly as it comes from the config file. */
1145 IConsolePrint(CC_DEFAULT, "Reconnecting to {} ...", _settings_client.network.last_joined);
1146
1148}
1149
1150static bool ConNetworkConnect(std::span<std::string_view> argv)
1151{
1152 if (argv.empty()) {
1153 IConsolePrint(CC_HELP, "Connect to a remote OTTD server and join the game. Usage: 'connect <ip>'.");
1154 IConsolePrint(CC_HELP, "IP can contain port and company: 'IP[:Port][#Company]', eg: 'server.ottd.org:443#2'.");
1155 IConsolePrint(CC_HELP, "Company #255 is spectator all others are a certain company with Company 1 being #1.");
1156 return true;
1157 }
1158
1159 if (argv.size() < 2) return false;
1160
1162}
1163
1164/*********************************
1165 * script file console commands
1166 *********************************/
1167
1168static bool ConExec(std::span<std::string_view> argv)
1169{
1170 if (argv.empty()) {
1171 IConsolePrint(CC_HELP, "Execute a local script file. Usage: 'exec <script> [0]'.");
1172 IConsolePrint(CC_HELP, "By passing '0' after the script name, no warning about a missing script file will be shown.");
1173 return true;
1174 }
1175
1176 if (argv.size() < 2) return false;
1177
1178 auto script_file = FioFOpenFile(argv[1], "r", BASE_DIR);
1179
1180 if (!script_file.has_value()) {
1181 if (argv.size() == 2 || argv[2] != "0") IConsolePrint(CC_ERROR, "Script file '{}' not found.", argv[1]);
1182 return true;
1183 }
1184
1185 if (_script_current_depth == 11) {
1186 IConsolePrint(CC_ERROR, "Maximum 'exec' depth reached; script A is calling script B is calling script C ... more than 10 times.");
1187 return true;
1188 }
1189
1191 uint script_depth = _script_current_depth;
1192
1193 char buffer[ICON_CMDLN_SIZE];
1194 while (fgets(buffer, sizeof(buffer), *script_file) != nullptr) {
1195 /* Remove newline characters from the executing script */
1196 std::string_view cmdline{buffer};
1197 auto last_non_newline = cmdline.find_last_not_of("\r\n");
1198 if (last_non_newline != std::string_view::npos) cmdline = cmdline.substr(0, last_non_newline + 1);
1199
1200 IConsoleCmdExec(cmdline);
1201 /* Ensure that we are still on the same depth or that we returned via 'return'. */
1202 assert(_script_current_depth == script_depth || _script_current_depth == script_depth - 1);
1203
1204 /* The 'return' command was executed. */
1205 if (_script_current_depth == script_depth - 1) break;
1206 }
1207
1208 if (ferror(*script_file) != 0) {
1209 IConsolePrint(CC_ERROR, "Encountered error while trying to read from script file '{}'.", argv[1]);
1210 }
1211
1212 if (_script_current_depth == script_depth) _script_current_depth--;
1213 return true;
1214}
1215
1216static bool ConSchedule(std::span<std::string_view> argv)
1217{
1218 if (argv.size() < 3 || std::string_view(argv[1]) != "on-next-calendar-month") {
1219 IConsolePrint(CC_HELP, "Schedule a local script to execute later. Usage: 'schedule on-next-calendar-month <script>'.");
1220 return true;
1221 }
1222
1223 /* Check if the file exists. It might still go away later, but helpful to show an error now. */
1224 if (!FioCheckFileExists(argv[2], BASE_DIR)) {
1225 IConsolePrint(CC_ERROR, "Script file '{}' not found.", argv[2]);
1226 return true;
1227 }
1228
1229 /* We only support a single script scheduled, so we tell the user what's happening if there was already one. */
1230 std::string_view filename = std::string_view(argv[2]);
1231 if (!_scheduled_monthly_script.empty() && filename == _scheduled_monthly_script) {
1232 IConsolePrint(CC_INFO, "Script file '{}' was already scheduled to execute at the start of next calendar month.", filename);
1233 } else if (!_scheduled_monthly_script.empty() && filename != _scheduled_monthly_script) {
1234 IConsolePrint(CC_INFO, "Script file '{}' scheduled to execute at the start of next calendar month, replacing the previously scheduled script file '{}'.", filename, _scheduled_monthly_script);
1235 } else {
1236 IConsolePrint(CC_INFO, "Script file '{}' scheduled to execute at the start of next calendar month.", filename);
1237 }
1238
1239 /* Store the filename to be used by _schedule_timer on the start of next calendar month. */
1240 _scheduled_monthly_script = filename;
1241
1242 return true;
1243}
1244
1245static bool ConReturn(std::span<std::string_view> argv)
1246{
1247 if (argv.empty()) {
1248 IConsolePrint(CC_HELP, "Stop executing a running script. Usage: 'return'.");
1249 return true;
1250 }
1251
1253 return true;
1254}
1255
1256/*****************************
1257 * default console commands
1258 ******************************/
1259extern bool CloseConsoleLogIfActive();
1260extern std::span<const GRFFile> GetAllGRFFiles();
1261extern void ConPrintFramerate(); // framerate_gui.cpp
1262extern void ShowFramerateWindow();
1263
1264static bool ConScript(std::span<std::string_view> argv)
1265{
1266 extern std::optional<FileHandle> _iconsole_output_file;
1267
1268 if (argv.empty()) {
1269 IConsolePrint(CC_HELP, "Start or stop logging console output to a file. Usage: 'script <filename>'.");
1270 IConsolePrint(CC_HELP, "If filename is omitted, a running log is stopped if it is active.");
1271 return true;
1272 }
1273
1274 if (!CloseConsoleLogIfActive()) {
1275 if (argv.size() < 2) return false;
1276
1277 _iconsole_output_file = FileHandle::Open(argv[1], "ab");
1278 if (!_iconsole_output_file.has_value()) {
1279 IConsolePrint(CC_ERROR, "Could not open console log file '{}'.", argv[1]);
1280 } else {
1281 IConsolePrint(CC_INFO, "Console log output started to '{}'.", argv[1]);
1282 }
1283 }
1284
1285 return true;
1286}
1287
1288static bool ConEcho(std::span<std::string_view> argv)
1289{
1290 if (argv.empty()) {
1291 IConsolePrint(CC_HELP, "Print back the first argument to the console. Usage: 'echo <arg>'.");
1292 return true;
1293 }
1294
1295 if (argv.size() < 2) return false;
1296 IConsolePrint(CC_DEFAULT, "{}", argv[1]);
1297 return true;
1298}
1299
1300static bool ConEchoC(std::span<std::string_view> argv)
1301{
1302 if (argv.empty()) {
1303 IConsolePrint(CC_HELP, "Print back the first argument to the console in a given colour. Usage: 'echoc <colour> <arg2>'.");
1304 return true;
1305 }
1306
1307 if (argv.size() < 3) return false;
1308
1309 auto colour = ParseInteger(argv[1]);
1310 if (!colour.has_value() || !IsInsideMM(*colour, TC_BEGIN, TC_END)) {
1311 IConsolePrint(CC_ERROR, "The colour must be a number between {} and {}.", TC_BEGIN, TC_END - 1);
1312 return true;
1313 }
1314
1315 IConsolePrint(static_cast<TextColour>(*colour), "{}", argv[2]);
1316 return true;
1317}
1318
1319static bool ConNewGame(std::span<std::string_view> argv)
1320{
1321 if (argv.empty()) {
1322 IConsolePrint(CC_HELP, "Start a new game. Usage: 'newgame [seed]'.");
1323 IConsolePrint(CC_HELP, "The server can force a new game using 'newgame'; any client joined will rejoin after the server is done generating the new game.");
1324 return true;
1325 }
1326
1327 uint32_t seed = GENERATE_NEW_SEED;
1328 if (argv.size() >= 2) {
1329 auto param = ParseInteger(argv[1]);
1330 if (!param.has_value()) {
1331 IConsolePrint(CC_ERROR, "The given seed must be a valid number.");
1332 return true;
1333 }
1334 seed = *param;
1335 }
1336
1338 return true;
1339}
1340
1341static bool ConRestart(std::span<std::string_view> argv)
1342{
1343 if (argv.empty() || argv.size() > 2) {
1344 IConsolePrint(CC_HELP, "Restart game. Usage: 'restart [current|newgame]'.");
1345 IConsolePrint(CC_HELP, "Restarts a game, using either the current or newgame (default) settings.");
1346 IConsolePrint(CC_HELP, " * if you started from a new game, and your current/newgame settings haven't changed, the game will be identical to when you started it.");
1347 IConsolePrint(CC_HELP, " * if you started from a savegame / scenario / heightmap, the game might be different, because the current/newgame settings might differ.");
1348 return true;
1349 }
1350
1351 if (argv.size() == 1 || std::string_view(argv[1]) == "newgame") {
1353 } else {
1357 }
1358
1359 return true;
1360}
1361
1362static bool ConReload(std::span<std::string_view> argv)
1363{
1364 if (argv.empty()) {
1365 IConsolePrint(CC_HELP, "Reload game. Usage: 'reload'.");
1366 IConsolePrint(CC_HELP, "Reloads a game if loaded via savegame / scenario / heightmap.");
1367 return true;
1368 }
1369
1371 IConsolePrint(CC_ERROR, "No game loaded to reload.");
1372 return true;
1373 }
1374
1375 /* Use a switch-mode to prevent copying over newgame settings to active settings. */
1379 return true;
1380}
1381
1386static void PrintLineByLine(const std::string &full_string)
1387{
1388 std::istringstream in(full_string);
1389 std::string line;
1390 while (std::getline(in, line)) {
1392 }
1393}
1394
1395template <typename F, typename ... Args>
1396bool PrintList(F list_function, Args... args)
1397{
1398 std::string output_str;
1399 auto inserter = std::back_inserter(output_str);
1400 list_function(inserter, args...);
1401 PrintLineByLine(output_str);
1402
1403 return true;
1404}
1405
1406static bool ConListAILibs(std::span<std::string_view> argv)
1407{
1408 if (argv.empty()) {
1409 IConsolePrint(CC_HELP, "List installed AI libraries. Usage: 'list_ai_libs'.");
1410 return true;
1411 }
1412
1413 return PrintList(AI::GetConsoleLibraryList);
1414}
1415
1416static bool ConListAI(std::span<std::string_view> argv)
1417{
1418 if (argv.empty()) {
1419 IConsolePrint(CC_HELP, "List installed AIs. Usage: 'list_ai'.");
1420 return true;
1421 }
1422
1423 return PrintList(AI::GetConsoleList, false);
1424}
1425
1426static bool ConListGameLibs(std::span<std::string_view> argv)
1427{
1428 if (argv.empty()) {
1429 IConsolePrint(CC_HELP, "List installed Game Script libraries. Usage: 'list_game_libs'.");
1430 return true;
1431 }
1432
1433 return PrintList(Game::GetConsoleLibraryList);
1434}
1435
1436static bool ConListGame(std::span<std::string_view> argv)
1437{
1438 if (argv.empty()) {
1439 IConsolePrint(CC_HELP, "List installed Game Scripts. Usage: 'list_game'.");
1440 return true;
1441 }
1442
1443 return PrintList(Game::GetConsoleList, false);
1444}
1445
1446static bool ConStartAI(std::span<std::string_view> argv)
1447{
1448 if (argv.empty() || argv.size() > 3) {
1449 IConsolePrint(CC_HELP, "Start a new AI. Usage: 'start_ai [<AI>] [<settings>]'.");
1450 IConsolePrint(CC_HELP, "Start a new AI. If <AI> is given, it starts that specific AI (if found).");
1451 IConsolePrint(CC_HELP, "If <settings> is given, it is parsed and the AI settings are set to that.");
1452 return true;
1453 }
1454
1455 if (_game_mode != GM_NORMAL) {
1456 IConsolePrint(CC_ERROR, "AIs can only be managed in a game.");
1457 return true;
1458 }
1459
1461 IConsolePrint(CC_ERROR, "Can't start a new AI (no more free slots).");
1462 return true;
1463 }
1464 if (_networking && !_network_server) {
1465 IConsolePrint(CC_ERROR, "Only the server can start a new AI.");
1466 return true;
1467 }
1469 IConsolePrint(CC_ERROR, "AIs are not allowed in multiplayer by configuration.");
1470 IConsolePrint(CC_ERROR, "Switch AI -> AI in multiplayer to True.");
1471 return true;
1472 }
1473 if (!AI::CanStartNew()) {
1474 IConsolePrint(CC_ERROR, "Can't start a new AI.");
1475 return true;
1476 }
1477
1478 int n = 0;
1479 /* Find the next free slot */
1480 for (const Company *c : Company::Iterate()) {
1481 if (c->index != n) break;
1482 n++;
1483 }
1484
1486 if (argv.size() >= 2) {
1487 config->Change(argv[1], -1, false);
1488
1489 /* If the name is not found, and there is a dot in the name,
1490 * try again with the assumption everything right of the dot is
1491 * the version the user wants to load. */
1492 if (!config->HasScript()) {
1493 StringConsumer consumer{std::string_view{argv[1]}};
1494 auto name = consumer.ReadUntilChar('.', StringConsumer::SKIP_ONE_SEPARATOR);
1495 if (consumer.AnyBytesLeft()) {
1496 auto version = consumer.TryReadIntegerBase<uint32_t>(10);
1497 if (!version.has_value()) {
1498 IConsolePrint(CC_ERROR, "The version is not a valid number.");
1499 return true;
1500 }
1501 config->Change(name, *version, true);
1502 }
1503 }
1504
1505 if (!config->HasScript()) {
1506 IConsolePrint(CC_ERROR, "Failed to load the specified AI.");
1507 return true;
1508 }
1509 if (argv.size() == 3) {
1510 config->StringToSettings(argv[2]);
1511 }
1512 }
1513
1514 /* Start a new AI company */
1516
1517 return true;
1518}
1519
1520static bool ConReloadAI(std::span<std::string_view> argv)
1521{
1522 if (argv.size() != 2) {
1523 IConsolePrint(CC_HELP, "Reload an AI. Usage: 'reload_ai <company-id>'.");
1524 IConsolePrint(CC_HELP, "Reload the AI with the given company id. For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc.");
1525 return true;
1526 }
1527
1528 if (_game_mode != GM_NORMAL) {
1529 IConsolePrint(CC_ERROR, "AIs can only be managed in a game.");
1530 return true;
1531 }
1532
1533 if (_networking && !_network_server) {
1534 IConsolePrint(CC_ERROR, "Only the server can reload an AI.");
1535 return true;
1536 }
1537
1538 auto company_id = ParseCompanyID(argv[1]);
1539 if (!company_id.has_value()) {
1540 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
1541 return true;
1542 }
1543
1544 if (!Company::IsValidID(*company_id)) {
1545 IConsolePrint(CC_ERROR, "Unknown company. Company range is between 1 and {}.", MAX_COMPANIES);
1546 return true;
1547 }
1548
1549 /* In singleplayer mode the player can be in an AI company, after cheating or loading network save with an AI in first slot. */
1550 if (Company::IsHumanID(*company_id) || *company_id == _local_company) {
1551 IConsolePrint(CC_ERROR, "Company is not controlled by an AI.");
1552 return true;
1553 }
1554
1555 /* First kill the company of the AI, then start a new one. This should start the current AI again */
1558 IConsolePrint(CC_DEFAULT, "AI reloaded.");
1559
1560 return true;
1561}
1562
1563static bool ConStopAI(std::span<std::string_view> argv)
1564{
1565 if (argv.size() != 2) {
1566 IConsolePrint(CC_HELP, "Stop an AI. Usage: 'stop_ai <company-id>'.");
1567 IConsolePrint(CC_HELP, "Stop the AI with the given company id. For company-id's, see the list of companies from the dropdown menu. Company 1 is 1, etc.");
1568 return true;
1569 }
1570
1571 if (_game_mode != GM_NORMAL) {
1572 IConsolePrint(CC_ERROR, "AIs can only be managed in a game.");
1573 return true;
1574 }
1575
1576 if (_networking && !_network_server) {
1577 IConsolePrint(CC_ERROR, "Only the server can stop an AI.");
1578 return true;
1579 }
1580
1581 auto company_id = ParseCompanyID(argv[1]);
1582 if (!company_id.has_value()) {
1583 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
1584 return true;
1585 }
1586
1587 if (!Company::IsValidID(*company_id)) {
1588 IConsolePrint(CC_ERROR, "Unknown company. Company range is between 1 and {}.", MAX_COMPANIES);
1589 return true;
1590 }
1591
1592 /* In singleplayer mode the player can be in an AI company, after cheating or loading network save with an AI in first slot. */
1593 if (Company::IsHumanID(*company_id) || *company_id == _local_company) {
1594 IConsolePrint(CC_ERROR, "Company is not controlled by an AI.");
1595 return true;
1596 }
1597
1598 /* Now kill the company of the AI. */
1600 IConsolePrint(CC_DEFAULT, "AI stopped, company deleted.");
1601
1602 return true;
1603}
1604
1605static bool ConRescanAI(std::span<std::string_view> argv)
1606{
1607 if (argv.empty()) {
1608 IConsolePrint(CC_HELP, "Rescan the AI dir for scripts. Usage: 'rescan_ai'.");
1609 return true;
1610 }
1611
1612 if (_networking && !_network_server) {
1613 IConsolePrint(CC_ERROR, "Only the server can rescan the AI dir for scripts.");
1614 return true;
1615 }
1616
1617 AI::Rescan();
1618
1619 return true;
1620}
1621
1622static bool ConRescanGame(std::span<std::string_view> argv)
1623{
1624 if (argv.empty()) {
1625 IConsolePrint(CC_HELP, "Rescan the Game Script dir for scripts. Usage: 'rescan_game'.");
1626 return true;
1627 }
1628
1629 if (_networking && !_network_server) {
1630 IConsolePrint(CC_ERROR, "Only the server can rescan the Game Script dir for scripts.");
1631 return true;
1632 }
1633
1634 Game::Rescan();
1635
1636 return true;
1637}
1638
1639static bool ConRescanNewGRF(std::span<std::string_view> argv)
1640{
1641 if (argv.empty()) {
1642 IConsolePrint(CC_HELP, "Rescan the data dir for NewGRFs. Usage: 'rescan_newgrf'.");
1643 return true;
1644 }
1645
1646 if (!RequestNewGRFScan()) {
1647 IConsolePrint(CC_ERROR, "NewGRF scanning is already running. Please wait until completed to run again.");
1648 }
1649
1650 return true;
1651}
1652
1653static bool ConGetSeed(std::span<std::string_view> argv)
1654{
1655 if (argv.empty()) {
1656 IConsolePrint(CC_HELP, "Returns the seed used to create this game. Usage: 'getseed'.");
1657 IConsolePrint(CC_HELP, "The seed can be used to reproduce the exact same map as the game started with.");
1658 return true;
1659 }
1660
1662 return true;
1663}
1664
1665static bool ConGetDate(std::span<std::string_view> argv)
1666{
1667 if (argv.empty()) {
1668 IConsolePrint(CC_HELP, "Returns the current date (year-month-day) of the game. Usage: 'getdate'.");
1669 return true;
1670 }
1671
1672 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date);
1673 IConsolePrint(CC_DEFAULT, "Date: {:04d}-{:02d}-{:02d}", ymd.year, ymd.month + 1, ymd.day);
1674 return true;
1675}
1676
1677static bool ConGetSysDate(std::span<std::string_view> argv)
1678{
1679 if (argv.empty()) {
1680 IConsolePrint(CC_HELP, "Returns the current date (year-month-day) of your system. Usage: 'getsysdate'.");
1681 return true;
1682 }
1683
1684 IConsolePrint(CC_DEFAULT, "System Date: {:%Y-%m-%d %H:%M:%S}", fmt::localtime(time(nullptr)));
1685 return true;
1686}
1687
1688static bool ConAlias(std::span<std::string_view> argv)
1689{
1690 IConsoleAlias *alias;
1691
1692 if (argv.empty()) {
1693 IConsolePrint(CC_HELP, "Add a new alias, or redefine the behaviour of an existing alias . Usage: 'alias <name> <command>'.");
1694 return true;
1695 }
1696
1697 if (argv.size() < 3) return false;
1698
1699 alias = IConsole::AliasGet(std::string(argv[1]));
1700 if (alias == nullptr) {
1701 IConsole::AliasRegister(std::string(argv[1]), argv[2]);
1702 } else {
1703 alias->cmdline = argv[2];
1704 }
1705 return true;
1706}
1707
1708static bool ConScreenShot(std::span<std::string_view> argv)
1709{
1710 if (argv.empty()) {
1711 IConsolePrint(CC_HELP, "Create a screenshot of the game. Usage: 'screenshot [viewport | normal | big | giant | heightmap | minimap] [no_con] [size <width> <height>] [<filename>]'.");
1712 IConsolePrint(CC_HELP, " 'viewport' (default) makes a screenshot of the current viewport (including menus, windows).");
1713 IConsolePrint(CC_HELP, " 'normal' makes a screenshot of the visible area.");
1714 IConsolePrint(CC_HELP, " 'big' makes a zoomed-in screenshot of the visible area.");
1715 IConsolePrint(CC_HELP, " 'giant' makes a screenshot of the whole map.");
1716 IConsolePrint(CC_HELP, " 'heightmap' makes a heightmap screenshot of the map that can be loaded in as heightmap.");
1717 IConsolePrint(CC_HELP, " 'minimap' makes a top-viewed minimap screenshot of the whole world which represents one tile by one pixel.");
1718 IConsolePrint(CC_HELP, " 'no_con' hides the console to create the screenshot (only useful in combination with 'viewport').");
1719 IConsolePrint(CC_HELP, " 'size' sets the width and height of the viewport to make a screenshot of (only useful in combination with 'normal' or 'big').");
1720 IConsolePrint(CC_HELP, " A filename ending in # will prevent overwriting existing files and will number files counting upwards.");
1721 return true;
1722 }
1723
1724 if (argv.size() > 7) return false;
1725
1727 uint32_t width = 0;
1728 uint32_t height = 0;
1729 std::string name{};
1730 uint32_t arg_index = 1;
1731
1732 if (argv.size() > arg_index) {
1733 if (argv[arg_index] == "viewport") {
1734 type = SC_VIEWPORT;
1735 arg_index += 1;
1736 } else if (argv[arg_index] == "normal") {
1737 type = SC_DEFAULTZOOM;
1738 arg_index += 1;
1739 } else if (argv[arg_index] == "big") {
1740 type = SC_ZOOMEDIN;
1741 arg_index += 1;
1742 } else if (argv[arg_index] == "giant") {
1743 type = SC_WORLD;
1744 arg_index += 1;
1745 } else if (argv[arg_index] == "heightmap") {
1746 type = SC_HEIGHTMAP;
1747 arg_index += 1;
1748 } else if (argv[arg_index] == "minimap") {
1749 type = SC_MINIMAP;
1750 arg_index += 1;
1751 }
1752 }
1753
1754 if (argv.size() > arg_index && argv[arg_index] == "no_con") {
1755 if (type != SC_VIEWPORT) {
1756 IConsolePrint(CC_ERROR, "'no_con' can only be used in combination with 'viewport'.");
1757 return true;
1758 }
1759 IConsoleClose();
1760 arg_index += 1;
1761 }
1762
1763 if (argv.size() > arg_index + 2 && argv[arg_index] == "size") {
1764 /* size <width> <height> */
1765 if (type != SC_DEFAULTZOOM && type != SC_ZOOMEDIN) {
1766 IConsolePrint(CC_ERROR, "'size' can only be used in combination with 'normal' or 'big'.");
1767 return true;
1768 }
1769 auto t = ParseInteger(argv[arg_index + 1]);
1770 if (!t.has_value()) {
1771 IConsolePrint(CC_ERROR, "Invalid width '{}'", argv[arg_index + 1]);
1772 return true;
1773 }
1774 width = *t;
1775
1776 t = ParseInteger(argv[arg_index + 2]);
1777 if (!t.has_value()) {
1778 IConsolePrint(CC_ERROR, "Invalid height '{}'", argv[arg_index + 2]);
1779 return true;
1780 }
1781 height = *t;
1782 arg_index += 3;
1783 }
1784
1785 if (argv.size() > arg_index) {
1786 /* Last parameter that was not one of the keywords must be the filename. */
1787 name = argv[arg_index];
1788 arg_index += 1;
1789 }
1790
1791 if (argv.size() > arg_index) {
1792 /* We have parameters we did not process; means we misunderstood any of the above. */
1793 return false;
1794 }
1795
1796 MakeScreenshot(type, std::move(name), width, height);
1797 return true;
1798}
1799
1800static bool ConInfoCmd(std::span<std::string_view> argv)
1801{
1802 if (argv.empty()) {
1803 IConsolePrint(CC_HELP, "Print out debugging information about a command. Usage: 'info_cmd <cmd>'.");
1804 return true;
1805 }
1806
1807 if (argv.size() < 2) return false;
1808
1809 const IConsoleCmd *cmd = IConsole::CmdGet(std::string(argv[1]));
1810 if (cmd == nullptr) {
1811 IConsolePrint(CC_ERROR, "The given command was not found.");
1812 return true;
1813 }
1814
1815 IConsolePrint(CC_DEFAULT, "Command name: '{}'", cmd->name);
1816
1817 if (cmd->hook != nullptr) IConsolePrint(CC_DEFAULT, "Command is hooked.");
1818
1819 return true;
1820}
1821
1822static bool ConDebugLevel(std::span<std::string_view> argv)
1823{
1824 if (argv.empty()) {
1825 IConsolePrint(CC_HELP, "Get/set the default debugging level for the game. Usage: 'debug_level [<level>]'.");
1826 IConsolePrint(CC_HELP, "Level can be any combination of names, levels. Eg 'net=5 ms=4'. Remember to enclose it in \"'\"s.");
1827 return true;
1828 }
1829
1830 if (argv.size() > 2) return false;
1831
1832 if (argv.size() == 1) {
1833 IConsolePrint(CC_DEFAULT, "Current debug-level: '{}'", GetDebugString());
1834 } else {
1835 SetDebugString(argv[1], [](std::string_view err) { IConsolePrint(CC_ERROR, "{}", err); });
1836 }
1837
1838 return true;
1839}
1840
1841static bool ConExit(std::span<std::string_view> argv)
1842{
1843 if (argv.empty()) {
1844 IConsolePrint(CC_HELP, "Exit the game. Usage: 'exit'.");
1845 return true;
1846 }
1847
1848 if (_game_mode == GM_NORMAL && _settings_client.gui.autosave_on_exit) DoExitSave();
1849
1850 _exit_game = true;
1851 return true;
1852}
1853
1854static bool ConPart(std::span<std::string_view> argv)
1855{
1856 if (argv.empty()) {
1857 IConsolePrint(CC_HELP, "Leave the currently joined/running game (only ingame). Usage: 'part'.");
1858 return true;
1859 }
1860
1861 if (_game_mode != GM_NORMAL) return false;
1862
1863 if (_network_dedicated) {
1864 IConsolePrint(CC_ERROR, "A dedicated server can not leave the game.");
1865 return false;
1866 }
1867
1869 return true;
1870}
1871
1872static bool ConHelp(std::span<std::string_view> argv)
1873{
1874 if (argv.size() == 2) {
1875 const IConsoleCmd *cmd;
1876 const IConsoleAlias *alias;
1877
1878 cmd = IConsole::CmdGet(std::string(argv[1]));
1879 if (cmd != nullptr) {
1880 cmd->proc({});
1881 return true;
1882 }
1883
1884 alias = IConsole::AliasGet(std::string(argv[1]));
1885 if (alias != nullptr) {
1886 cmd = IConsole::CmdGet(alias->cmdline);
1887 if (cmd != nullptr) {
1888 cmd->proc({});
1889 return true;
1890 }
1891 IConsolePrint(CC_ERROR, "Alias is of special type, please see its execution-line: '{}'.", alias->cmdline);
1892 return true;
1893 }
1894
1895 IConsolePrint(CC_ERROR, "Command not found.");
1896 return true;
1897 }
1898
1899 IConsolePrint(TC_LIGHT_BLUE, " ---- OpenTTD Console Help ---- ");
1900 IConsolePrint(CC_DEFAULT, " - commands: the command to list all commands is 'list_cmds'.");
1901 IConsolePrint(CC_DEFAULT, " call commands with '<command> <arg2> <arg3>...'");
1902 IConsolePrint(CC_DEFAULT, " - to assign strings, or use them as arguments, enclose it within quotes.");
1903 IConsolePrint(CC_DEFAULT, " like this: '<command> \"string argument with spaces\"'.");
1904 IConsolePrint(CC_DEFAULT, " - use 'help <command>' to get specific information.");
1905 IConsolePrint(CC_DEFAULT, " - scroll console output with shift + (up | down | pageup | pagedown).");
1906 IConsolePrint(CC_DEFAULT, " - scroll console input history with the up or down arrows.");
1908 return true;
1909}
1910
1911static bool ConListCommands(std::span<std::string_view> argv)
1912{
1913 if (argv.empty()) {
1914 IConsolePrint(CC_HELP, "List all registered commands. Usage: 'list_cmds [<pre-filter>]'.");
1915 return true;
1916 }
1917
1918 for (auto &it : IConsole::Commands()) {
1919 const IConsoleCmd *cmd = &it.second;
1920 if (argv.size() <= 1|| cmd->name.find(argv[1]) != std::string::npos) {
1921 if (cmd->hook == nullptr || cmd->hook(false) != CHR_HIDE) IConsolePrint(CC_DEFAULT, cmd->name);
1922 }
1923 }
1924
1925 return true;
1926}
1927
1928static bool ConListAliases(std::span<std::string_view> argv)
1929{
1930 if (argv.empty()) {
1931 IConsolePrint(CC_HELP, "List all registered aliases. Usage: 'list_aliases [<pre-filter>]'.");
1932 return true;
1933 }
1934
1935 for (auto &it : IConsole::Aliases()) {
1936 const IConsoleAlias *alias = &it.second;
1937 if (argv.size() <= 1 || alias->name.find(argv[1]) != std::string::npos) {
1938 IConsolePrint(CC_DEFAULT, "{} => {}", alias->name, alias->cmdline);
1939 }
1940 }
1941
1942 return true;
1943}
1944
1945static bool ConCompanies(std::span<std::string_view> argv)
1946{
1947 if (argv.empty()) {
1948 IConsolePrint(CC_HELP, "List the details of all companies in the game. Usage 'companies'.");
1949 return true;
1950 }
1951
1952 for (const Company *c : Company::Iterate()) {
1953 /* Grab the company name */
1954 std::string company_name = GetString(STR_COMPANY_NAME, c->index);
1955
1956 std::string colour = GetString(STR_COLOUR_DARK_BLUE + _company_colours[c->index]);
1957 IConsolePrint(CC_INFO, "#:{}({}) Company Name: '{}' Year Founded: {} Money: {} Loan: {} Value: {} (T:{}, R:{}, P:{}, S:{}) {}",
1958 c->index + 1, colour, company_name,
1959 c->inaugurated_year, (int64_t)c->money, (int64_t)c->current_loan, (int64_t)CalculateCompanyValue(c),
1960 c->group_all[VEH_TRAIN].num_vehicle,
1961 c->group_all[VEH_ROAD].num_vehicle,
1962 c->group_all[VEH_AIRCRAFT].num_vehicle,
1963 c->group_all[VEH_SHIP].num_vehicle,
1964 c->is_ai ? "AI" : "");
1965 }
1966
1967 return true;
1968}
1969
1970static bool ConSay(std::span<std::string_view> argv)
1971{
1972 if (argv.empty()) {
1973 IConsolePrint(CC_HELP, "Chat to your fellow players in a multiplayer game. Usage: 'say \"<msg>\"'.");
1974 return true;
1975 }
1976
1977 if (argv.size() != 2) return false;
1978
1979 if (!_network_server) {
1980 NetworkClientSendChat(NETWORK_ACTION_CHAT, DESTTYPE_BROADCAST, 0 /* param does not matter */, argv[1]);
1981 } else {
1982 bool from_admin = (_redirect_console_to_admin < AdminID::Invalid());
1983 NetworkServerSendChat(NETWORK_ACTION_CHAT, DESTTYPE_BROADCAST, 0, argv[1], CLIENT_ID_SERVER, from_admin);
1984 }
1985
1986 return true;
1987}
1988
1989static bool ConSayCompany(std::span<std::string_view> argv)
1990{
1991 if (argv.empty()) {
1992 IConsolePrint(CC_HELP, "Chat to a certain company in a multiplayer game. Usage: 'say_company <company-no> \"<msg>\"'.");
1993 IConsolePrint(CC_HELP, "CompanyNo is the company that plays as company <companyno>, 1 through max_companies.");
1994 return true;
1995 }
1996
1997 if (argv.size() != 3) return false;
1998
1999 auto company_id = ParseCompanyID(argv[1]);
2000 if (!company_id.has_value()) {
2001 IConsolePrint(CC_ERROR, "The given company-id is not a valid number.");
2002 return true;
2003 }
2004
2005 if (!Company::IsValidID(*company_id)) {
2006 IConsolePrint(CC_DEFAULT, "Unknown company. Company range is between 1 and {}.", MAX_COMPANIES);
2007 return true;
2008 }
2009
2010 if (!_network_server) {
2011 NetworkClientSendChat(NETWORK_ACTION_CHAT_COMPANY, DESTTYPE_TEAM, company_id->base(), argv[2]);
2012 } else {
2013 bool from_admin = (_redirect_console_to_admin < AdminID::Invalid());
2014 NetworkServerSendChat(NETWORK_ACTION_CHAT_COMPANY, DESTTYPE_TEAM, company_id->base(), argv[2], CLIENT_ID_SERVER, from_admin);
2015 }
2016
2017 return true;
2018}
2019
2020static bool ConSayClient(std::span<std::string_view> argv)
2021{
2022 if (argv.empty()) {
2023 IConsolePrint(CC_HELP, "Chat to a certain client in a multiplayer game. Usage: 'say_client <client-id> \"<msg>\"'.");
2024 IConsolePrint(CC_HELP, "For client-id's, see the command 'clients'.");
2025 return true;
2026 }
2027
2028 if (argv.size() != 3) return false;
2029
2030 auto client_id = ParseType<ClientID>(argv[1]);
2031 if (!client_id.has_value()) {
2032 IConsolePrint(CC_ERROR, "The given client-id is not a valid number.");
2033 return true;
2034 }
2035
2036 if (!_network_server) {
2037 NetworkClientSendChat(NETWORK_ACTION_CHAT_CLIENT, DESTTYPE_CLIENT, *client_id, argv[2]);
2038 } else {
2039 bool from_admin = (_redirect_console_to_admin < AdminID::Invalid());
2040 NetworkServerSendChat(NETWORK_ACTION_CHAT_CLIENT, DESTTYPE_CLIENT, *client_id, argv[2], CLIENT_ID_SERVER, from_admin);
2041 }
2042
2043 return true;
2044}
2045
2047static const std::initializer_list<std::pair<std::string_view, NetworkAuthorizedKeys *>> _console_cmd_authorized_keys{
2051};
2052
2053enum ConNetworkAuthorizedKeyAction : uint8_t {
2054 CNAKA_LIST,
2055 CNAKA_ADD,
2056 CNAKA_REMOVE,
2057};
2058
2059static void PerformNetworkAuthorizedKeyAction(std::string_view name, NetworkAuthorizedKeys *authorized_keys, ConNetworkAuthorizedKeyAction action, const std::string &authorized_key, CompanyID company = CompanyID::Invalid())
2060{
2061 switch (action) {
2062 case CNAKA_LIST:
2063 IConsolePrint(CC_WHITE, "The authorized keys for {} are:", name);
2064 for (auto &ak : *authorized_keys) IConsolePrint(CC_INFO, " {}", ak);
2065 return;
2066
2067 case CNAKA_ADD:
2068 if (authorized_keys->Contains(authorized_key)) {
2069 IConsolePrint(CC_WARNING, "Not added {} to {} as it already exists.", authorized_key, name);
2070 return;
2071 }
2072
2073 if (company == CompanyID::Invalid()) {
2074 authorized_keys->Add(authorized_key);
2075 } else {
2076 AutoRestoreBackup backup(_current_company, company);
2078 }
2079 IConsolePrint(CC_INFO, "Added {} to {}.", authorized_key, name);
2080 return;
2081
2082 case CNAKA_REMOVE:
2083 if (!authorized_keys->Contains(authorized_key)) {
2084 IConsolePrint(CC_WARNING, "Not removed {} from {} as it does not exist.", authorized_key, name);
2085 return;
2086 }
2087
2088 if (company == CompanyID::Invalid()) {
2089 authorized_keys->Remove(authorized_key);
2090 } else {
2091 AutoRestoreBackup backup(_current_company, company);
2093 }
2094 IConsolePrint(CC_INFO, "Removed {} from {}.", authorized_key, name);
2095 return;
2096 }
2097}
2098
2099static bool ConNetworkAuthorizedKey(std::span<std::string_view> argv)
2100{
2101 if (argv.size() <= 2) {
2102 IConsolePrint(CC_HELP, "List and update authorized keys. Usage: 'authorized_key list [type]|add [type] [key]|remove [type] [key]'.");
2103 IConsolePrint(CC_HELP, " list: list all the authorized keys of the given type.");
2104 IConsolePrint(CC_HELP, " add: add the given key to the authorized keys of the given type.");
2105 IConsolePrint(CC_HELP, " remove: remove the given key from the authorized keys of the given type; use 'all' to remove all authorized keys.");
2106 IConsolePrint(CC_HELP, "Instead of a key, use 'client:<id>' to add/remove the key of that given client.");
2107
2108 std::string buffer;
2109 for (auto [name, _] : _console_cmd_authorized_keys) format_append(buffer, ", {}", name);
2110 IConsolePrint(CC_HELP, "The supported types are: all{} and company:<id>.", buffer);
2111 return true;
2112 }
2113
2114 ConNetworkAuthorizedKeyAction action;
2115 std::string_view action_string = argv[1];
2116 if (StrEqualsIgnoreCase(action_string, "list")) {
2117 action = CNAKA_LIST;
2118 } else if (StrEqualsIgnoreCase(action_string, "add")) {
2119 action = CNAKA_ADD;
2120 } else if (StrEqualsIgnoreCase(action_string, "remove") || StrEqualsIgnoreCase(action_string, "delete")) {
2121 action = CNAKA_REMOVE;
2122 } else {
2123 IConsolePrint(CC_WARNING, "No valid action was given.");
2124 return false;
2125 }
2126
2127 std::string authorized_key;
2128 if (action != CNAKA_LIST) {
2129 if (argv.size() <= 3) {
2130 IConsolePrint(CC_ERROR, "You must enter the key.");
2131 return false;
2132 }
2133
2134 authorized_key = argv[3];
2135 if (StrStartsWithIgnoreCase(authorized_key, "client:")) {
2136 auto value = ParseInteger<uint32_t>(authorized_key.substr(7));
2137 if (value.has_value()) authorized_key = NetworkGetPublicKeyOfClient(static_cast<ClientID>(*value));
2138 if (!value.has_value() || authorized_key.empty()) {
2139 IConsolePrint(CC_ERROR, "You must enter a valid client id; see 'clients'.");
2140 return false;
2141 }
2142 }
2143
2144 if (authorized_key.size() != NETWORK_PUBLIC_KEY_LENGTH - 1) {
2145 IConsolePrint(CC_ERROR, "You must enter a valid authorized key.");
2146 return false;
2147 }
2148 }
2149
2150 std::string_view type = argv[2];
2151 if (StrEqualsIgnoreCase(type, "all")) {
2152 for (auto [name, authorized_keys] : _console_cmd_authorized_keys) PerformNetworkAuthorizedKeyAction(name, authorized_keys, action, authorized_key);
2153 for (Company *c : Company::Iterate()) PerformNetworkAuthorizedKeyAction(fmt::format("company:{}", c->index + 1), &c->allow_list, action, authorized_key, c->index);
2154 return true;
2155 }
2156
2157 if (StrStartsWithIgnoreCase(type, "company:")) {
2158 auto value = ParseInteger<uint32_t>(type.substr(8));
2159 Company *c = value.has_value() ? Company::GetIfValid(*value - 1) : nullptr;
2160 if (c == nullptr) {
2161 IConsolePrint(CC_ERROR, "You must enter a valid company id; see 'companies'.");
2162 return false;
2163 }
2164
2165 PerformNetworkAuthorizedKeyAction(type, &c->allow_list, action, authorized_key, c->index);
2166 return true;
2167 }
2168
2169 for (auto [name, authorized_keys] : _console_cmd_authorized_keys) {
2170 if (!StrEqualsIgnoreCase(type, name)) continue;
2171
2172 PerformNetworkAuthorizedKeyAction(name, authorized_keys, action, authorized_key);
2173 return true;
2174 }
2175
2176 IConsolePrint(CC_WARNING, "No valid type was given.");
2177 return false;
2178}
2179
2180
2181/* Content downloading only is available with ZLIB */
2182#if defined(WITH_ZLIB)
2183
2185static ContentType StringToContentType(std::string_view str)
2186{
2187 static const std::initializer_list<std::pair<std::string_view, ContentType>> content_types = {
2189 {"newgrf", CONTENT_TYPE_NEWGRF},
2190 {"ai", CONTENT_TYPE_AI},
2191 {"ailib", CONTENT_TYPE_AI_LIBRARY},
2192 {"scenario", CONTENT_TYPE_SCENARIO},
2193 {"heightmap", CONTENT_TYPE_HEIGHTMAP},
2194 };
2195 for (const auto &ct : content_types) {
2196 if (StrEqualsIgnoreCase(str, ct.first)) return ct.second;
2197 }
2198 return CONTENT_TYPE_END;
2199}
2200
2203 void OnConnect(bool success) override
2204 {
2205 IConsolePrint(CC_DEFAULT, "Content server connection {}.", success ? "established" : "failed");
2206 }
2207
2208 void OnDisconnect() override
2209 {
2210 IConsolePrint(CC_DEFAULT, "Content server connection closed.");
2211 }
2212
2214 {
2215 IConsolePrint(CC_DEFAULT, "Completed download of {}.", cid);
2216 }
2217};
2218
2223static void OutputContentState(const ContentInfo &ci)
2224{
2225 static const std::string_view types[] = { "Base graphics", "NewGRF", "AI", "AI library", "Scenario", "Heightmap", "Base sound", "Base music", "Game script", "GS library" };
2226 static_assert(lengthof(types) == CONTENT_TYPE_END - CONTENT_TYPE_BEGIN);
2227 static const std::string_view states[] = { "Not selected", "Selected", "Dep Selected", "Installed", "Unknown" };
2228 static const TextColour state_to_colour[] = { CC_COMMAND, CC_INFO, CC_INFO, CC_WHITE, CC_ERROR };
2229
2230 IConsolePrint(state_to_colour[to_underlying(ci.state)], "{}, {}, {}, {}, {:08X}, {}", ci.id, types[ci.type - 1], states[to_underlying(ci.state)], ci.name, ci.unique_id, FormatArrayAsHex(ci.md5sum));
2231}
2232
2233static bool ConContent(std::span<std::string_view> argv)
2234{
2235 [[maybe_unused]] static ContentCallback *const cb = []() {
2236 auto res = new ConsoleContentCallback();
2238 return res;
2239 }();
2240
2241 if (argv.size() <= 1) {
2242 IConsolePrint(CC_HELP, "Query, select and download content. Usage: 'content update|upgrade|select [id]|unselect [all|id]|state [filter]|download'.");
2243 IConsolePrint(CC_HELP, " update: get a new list of downloadable content; must be run first.");
2244 IConsolePrint(CC_HELP, " upgrade: select all items that are upgrades.");
2245 IConsolePrint(CC_HELP, " select: select a specific item given by its id. If no parameter is given, all selected content will be listed.");
2246 IConsolePrint(CC_HELP, " unselect: unselect a specific item given by its id or 'all' to unselect all.");
2247 IConsolePrint(CC_HELP, " state: show the download/select state of all downloadable content. Optionally give a filter string.");
2248 IConsolePrint(CC_HELP, " download: download all content you've selected.");
2249 return true;
2250 }
2251
2252 if (StrEqualsIgnoreCase(argv[1], "update")) {
2254 return true;
2255 }
2256
2257 if (StrEqualsIgnoreCase(argv[1], "upgrade")) {
2259 return true;
2260 }
2261
2262 if (StrEqualsIgnoreCase(argv[1], "select")) {
2263 if (argv.size() <= 2) {
2264 /* List selected content */
2265 IConsolePrint(CC_WHITE, "id, type, state, name");
2266 for (const ContentInfo &ci : _network_content_client.Info()) {
2267 if (ci.state != ContentInfo::State::Selected && ci.state != ContentInfo::State::Autoselected) continue;
2269 }
2270 } else if (StrEqualsIgnoreCase(argv[2], "all")) {
2271 /* The intention of this function was that you could download
2272 * everything after a filter was applied; but this never really
2273 * took off. Instead, a select few people used this functionality
2274 * to download every available package on BaNaNaS. This is not in
2275 * the spirit of this service. Additionally, these few people were
2276 * good for 70% of the consumed bandwidth of BaNaNaS. */
2277 IConsolePrint(CC_ERROR, "'select all' is no longer supported since 1.11.");
2278 } else if (auto content_id = ParseType<ContentID>(argv[2]); content_id.has_value()) {
2279 _network_content_client.Select(*content_id);
2280 } else {
2281 IConsolePrint(CC_ERROR, "The given content-id is not a number or 'all'");
2282 }
2283 return true;
2284 }
2285
2286 if (StrEqualsIgnoreCase(argv[1], "unselect")) {
2287 if (argv.size() <= 2) {
2288 IConsolePrint(CC_ERROR, "You must enter the id.");
2289 return false;
2290 }
2291 if (StrEqualsIgnoreCase(argv[2], "all")) {
2293 } else if (auto content_id = ParseType<ContentID>(argv[2]); content_id.has_value()) {
2294 _network_content_client.Unselect(*content_id);
2295 } else {
2296 IConsolePrint(CC_ERROR, "The given content-id is not a number or 'all'");
2297 }
2298 return true;
2299 }
2300
2301 if (StrEqualsIgnoreCase(argv[1], "state")) {
2302 IConsolePrint(CC_WHITE, "id, type, state, name");
2303 for (const ContentInfo &ci : _network_content_client.Info()) {
2304 if (argv.size() > 2 && !StrContainsIgnoreCase(ci.name, argv[2])) continue;
2306 }
2307 return true;
2308 }
2309
2310 if (StrEqualsIgnoreCase(argv[1], "download")) {
2311 uint files;
2312 uint bytes;
2314 IConsolePrint(CC_DEFAULT, "Downloading {} file(s) ({} bytes).", files, bytes);
2315 return true;
2316 }
2317
2318 return false;
2319}
2320#endif /* defined(WITH_ZLIB) */
2321
2327static std::string_view FontLoadReasonToName(FontLoadReason load_reason)
2328{
2329 static const std::string_view LOAD_REASON_TO_NAME[] = { "default", "configured", "language", "missing" };
2330 static_assert(std::size(LOAD_REASON_TO_NAME) == to_underlying(FontLoadReason::End));
2331 return LOAD_REASON_TO_NAME[to_underlying(load_reason)];
2332}
2333
2334static bool ConFont(std::span<std::string_view> argv)
2335{
2336 if (argv.empty()) {
2337 IConsolePrint(CC_HELP, "Manage the fonts configuration.");
2338 IConsolePrint(CC_HELP, "Usage 'font'.");
2339 IConsolePrint(CC_HELP, " Print out the fonts configuration.");
2340 IConsolePrint(CC_HELP, " The \"Currently active\" configuration is the one actually in effect (after interface scaling and replacing unavailable fonts).");
2341 IConsolePrint(CC_HELP, " The \"Requested\" configuration is the one requested via console command or config file.");
2342 IConsolePrint(CC_HELP, "Usage 'font [medium|small|large|mono] [<font name>] [<size>]'.");
2343 IConsolePrint(CC_HELP, " Change the configuration for a font.");
2344 IConsolePrint(CC_HELP, " Omitting an argument will keep the current value.");
2345 IConsolePrint(CC_HELP, " Set <font name> to \"\" for the default font. Note that <size> has no effect if the default font is in use, and fixed defaults are used instead.");
2346 IConsolePrint(CC_HELP, " If the sprite font is enabled in Game Options, it is used instead of the default font.");
2347 IConsolePrint(CC_HELP, " The <size> is automatically multiplied by the current interface scaling.");
2348 return true;
2349 }
2350
2351 FontSize argfs;
2352 for (argfs = FS_BEGIN; argfs < FS_END; argfs++) {
2353 if (argv.size() > 1 && StrEqualsIgnoreCase(argv[1], FontSizeToName(argfs))) break;
2354 }
2355
2356 /* First argument must be a FontSize. */
2357 if (argv.size() > 1 && argfs == FS_END) return false;
2358
2359 if (argv.size() > 2) {
2361 std::string font = setting->font;
2362 uint size = setting->size;
2363 uint8_t arg_index = 2;
2364 /* For <name> we want a string. */
2365
2366 if (!ParseInteger(argv[arg_index]).has_value()) {
2367 font = argv[arg_index++];
2368 }
2369
2370 if (argv.size() > arg_index) {
2371 /* For <size> we want a number. */
2372 auto v = ParseInteger(argv[arg_index]);
2373 if (v.has_value()) {
2374 size = *v;
2375 arg_index++;
2376 }
2377 }
2378
2379 SetFont(argfs, font, size);
2380 }
2381
2382 IConsolePrint(CC_INFO, "Configured fonts:");
2383 for (uint i = 0; FontSize fs : FONTSIZES_ALL) {
2384 const FontCacheSubSetting *setting = GetFontCacheSubSetting(fs);
2385 IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\", size {}", i, FontSizeToName(fs), setting->font, setting->size);
2386 ++i;
2387 }
2388
2389 IConsolePrint(CC_INFO, "Currently active fonts:");
2390 for (uint i = 0; const auto &fc : FontCache::Get()) {
2391 if (fc == nullptr) continue;
2392 IConsolePrint(CC_DEFAULT, "{}) {} font: \"{}\" size {} [{}]", i, FontSizeToName(fc->GetSize()), fc->GetFontName(), fc->GetFontSize(), FontLoadReasonToName(fc->GetFontLoadReason()));
2393 ++i;
2394 }
2395
2396 return true;
2397}
2398
2399static bool ConSetting(std::span<std::string_view> argv)
2400{
2401 if (argv.empty()) {
2402 IConsolePrint(CC_HELP, "Change setting for all clients. Usage: 'setting <name> [<value>]'.");
2403 IConsolePrint(CC_HELP, "Omitting <value> will print out the current value of the setting.");
2404 return true;
2405 }
2406
2407 if (argv.size() == 1 || argv.size() > 3) return false;
2408
2409 if (argv.size() == 2) {
2410 IConsoleGetSetting(argv[1]);
2411 } else {
2412 IConsoleSetSetting(argv[1], argv[2]);
2413 }
2414
2415 return true;
2416}
2417
2418static bool ConSettingNewgame(std::span<std::string_view> argv)
2419{
2420 if (argv.empty()) {
2421 IConsolePrint(CC_HELP, "Change setting for the next game. Usage: 'setting_newgame <name> [<value>]'.");
2422 IConsolePrint(CC_HELP, "Omitting <value> will print out the current value of the setting.");
2423 return true;
2424 }
2425
2426 if (argv.size() == 1 || argv.size() > 3) return false;
2427
2428 if (argv.size() == 2) {
2429 IConsoleGetSetting(argv[1], true);
2430 } else {
2431 IConsoleSetSetting(argv[1], argv[2], true);
2432 }
2433
2434 return true;
2435}
2436
2437static bool ConListSettings(std::span<std::string_view> argv)
2438{
2439 if (argv.empty()) {
2440 IConsolePrint(CC_HELP, "List settings. Usage: 'list_settings [<pre-filter>]'.");
2441 return true;
2442 }
2443
2444 if (argv.size() > 2) return false;
2445
2446 IConsoleListSettings((argv.size() == 2) ? argv[1] : std::string_view{});
2447 return true;
2448}
2449
2450static bool ConGamelogPrint(std::span<std::string_view> argv)
2451{
2452 if (argv.empty()) {
2453 IConsolePrint(CC_HELP, "Print logged fundamental changes to the game since the start. Usage: 'gamelog'.");
2454 return true;
2455 }
2456
2458 return true;
2459}
2460
2461static bool ConNewGRFReload(std::span<std::string_view> argv)
2462{
2463 if (argv.empty()) {
2464 IConsolePrint(CC_HELP, "Reloads all active NewGRFs from disk. Equivalent to reapplying NewGRFs via the settings, but without asking for confirmation. This might crash OpenTTD!");
2465 return true;
2466 }
2467
2469 return true;
2470}
2471
2472static bool ConListDirs(std::span<std::string_view> argv)
2473{
2474 struct SubdirNameMap {
2475 Subdirectory subdir;
2476 std::string_view name;
2477 bool default_only;
2478 };
2479 static const SubdirNameMap subdir_name_map[] = {
2480 /* Game data directories */
2481 { BASESET_DIR, "baseset", false },
2482 { NEWGRF_DIR, "newgrf", false },
2483 { AI_DIR, "ai", false },
2484 { AI_LIBRARY_DIR, "ailib", false },
2485 { GAME_DIR, "gs", false },
2486 { GAME_LIBRARY_DIR, "gslib", false },
2487 { SCENARIO_DIR, "scenario", false },
2488 { HEIGHTMAP_DIR, "heightmap", false },
2489 /* Default save locations for user data */
2490 { SAVE_DIR, "save", true },
2491 { AUTOSAVE_DIR, "autosave", true },
2492 { SCREENSHOT_DIR, "screenshot", true },
2493 { SOCIAL_INTEGRATION_DIR, "social_integration", true },
2494 };
2495
2496 if (argv.size() != 2) {
2497 IConsolePrint(CC_HELP, "List all search paths or default directories for various categories.");
2498 IConsolePrint(CC_HELP, "Usage: list_dirs <category>");
2499 std::string cats{subdir_name_map[0].name};
2500 bool first = true;
2501 for (const SubdirNameMap &sdn : subdir_name_map) {
2502 if (!first) {
2503 cats += ", ";
2504 cats += sdn.name;
2505 }
2506 first = false;
2507 }
2508 IConsolePrint(CC_HELP, "Valid categories: {}", cats);
2509 return true;
2510 }
2511
2512 std::set<std::string> seen_dirs;
2513 for (const SubdirNameMap &sdn : subdir_name_map) {
2514 if (!StrEqualsIgnoreCase(argv[1], sdn.name)) continue;
2515 bool found = false;
2516 for (Searchpath sp : _valid_searchpaths) {
2517 /* Get the directory */
2518 std::string path = FioGetDirectory(sp, sdn.subdir);
2519 /* Check it hasn't already been listed */
2520 if (seen_dirs.find(path) != seen_dirs.end()) continue;
2521 seen_dirs.insert(path);
2522 /* Check if exists and mark found */
2523 bool exists = FileExists(path);
2524 found |= exists;
2525 /* Print */
2526 if (!sdn.default_only || exists) {
2527 IConsolePrint(exists ? CC_DEFAULT : CC_INFO, "{} {}", path, exists ? "[ok]" : "[not found]");
2528 if (sdn.default_only) break;
2529 }
2530 }
2531 if (!found) {
2532 IConsolePrint(CC_ERROR, "No directories exist for category {}", argv[1]);
2533 }
2534 return true;
2535 }
2536
2537 IConsolePrint(CC_ERROR, "Invalid category name: {}", argv[1]);
2538 return false;
2539}
2540
2541static bool ConNewGRFProfile(std::span<std::string_view> argv)
2542{
2543 if (argv.empty()) {
2544 IConsolePrint(CC_HELP, "Collect performance data about NewGRF sprite requests and callbacks. Sub-commands can be abbreviated.");
2545 IConsolePrint(CC_HELP, "Usage: 'newgrf_profile [list]':");
2546 IConsolePrint(CC_HELP, " List all NewGRFs that can be profiled, and their status.");
2547 IConsolePrint(CC_HELP, "Usage: 'newgrf_profile select <grf-num>...':");
2548 IConsolePrint(CC_HELP, " Select one or more GRFs for profiling.");
2549 IConsolePrint(CC_HELP, "Usage: 'newgrf_profile unselect <grf-num>...':");
2550 IConsolePrint(CC_HELP, " Unselect one or more GRFs from profiling. Use the keyword \"all\" instead of a GRF number to unselect all. Removing an active profiler aborts data collection.");
2551 IConsolePrint(CC_HELP, "Usage: 'newgrf_profile start [<num-ticks>]':");
2552 IConsolePrint(CC_HELP, " Begin profiling all selected GRFs. If a number of ticks is provided, profiling stops after that many game ticks. There are 74 ticks in a calendar day.");
2553 IConsolePrint(CC_HELP, "Usage: 'newgrf_profile stop':");
2554 IConsolePrint(CC_HELP, " End profiling and write the collected data to CSV files.");
2555 IConsolePrint(CC_HELP, "Usage: 'newgrf_profile abort':");
2556 IConsolePrint(CC_HELP, " End profiling and discard all collected data.");
2557 return true;
2558 }
2559
2560 std::span<const GRFFile> files = GetAllGRFFiles();
2561
2562 /* "list" sub-command */
2563 if (argv.size() == 1 || StrStartsWithIgnoreCase(argv[1], "lis")) {
2564 IConsolePrint(CC_INFO, "Loaded GRF files:");
2565 int i = 1;
2566 for (const auto &grf : files) {
2567 auto profiler = std::ranges::find(_newgrf_profilers, &grf, &NewGRFProfiler::grffile);
2568 bool selected = profiler != _newgrf_profilers.end();
2569 bool active = selected && profiler->active;
2570 TextColour tc = active ? TC_LIGHT_BLUE : selected ? TC_GREEN : CC_INFO;
2571 std::string_view statustext = active ? " (active)" : selected ? " (selected)" : "";
2572 IConsolePrint(tc, "{}: [{:08X}] {}{}", i, std::byteswap(grf.grfid), grf.filename, statustext);
2573 i++;
2574 }
2575 return true;
2576 }
2577
2578 /* "select" sub-command */
2579 if (StrStartsWithIgnoreCase(argv[1], "sel") && argv.size() >= 3) {
2580 for (size_t argnum = 2; argnum < argv.size(); ++argnum) {
2581 auto grfnum = ParseInteger(argv[argnum]);
2582 if (!grfnum.has_value() || *grfnum < 1 || static_cast<size_t>(*grfnum) > files.size()) {
2583 IConsolePrint(CC_WARNING, "GRF number {} out of range, not added.", *grfnum);
2584 continue;
2585 }
2586 const GRFFile *grf = &files[*grfnum - 1];
2587 if (std::any_of(_newgrf_profilers.begin(), _newgrf_profilers.end(), [&](NewGRFProfiler &pr) { return pr.grffile == grf; })) {
2588 IConsolePrint(CC_WARNING, "GRF number {} [{:08X}] is already selected for profiling.", *grfnum, std::byteswap(grf->grfid));
2589 continue;
2590 }
2591 _newgrf_profilers.emplace_back(grf);
2592 }
2593 return true;
2594 }
2595
2596 /* "unselect" sub-command */
2597 if (StrStartsWithIgnoreCase(argv[1], "uns") && argv.size() >= 3) {
2598 for (size_t argnum = 2; argnum < argv.size(); ++argnum) {
2599 if (StrEqualsIgnoreCase(argv[argnum], "all")) {
2600 _newgrf_profilers.clear();
2601 break;
2602 }
2603 auto grfnum = ParseInteger(argv[argnum]);
2604 if (!grfnum.has_value() || *grfnum < 1 || static_cast<size_t>(*grfnum) > files.size()) {
2605 IConsolePrint(CC_WARNING, "GRF number {} out of range, not removing.", *grfnum);
2606 continue;
2607 }
2608 const GRFFile *grf = &files[*grfnum - 1];
2609 _newgrf_profilers.erase(std::ranges::find(_newgrf_profilers, grf, &NewGRFProfiler::grffile));
2610 }
2611 return true;
2612 }
2613
2614 /* "start" sub-command */
2615 if (StrStartsWithIgnoreCase(argv[1], "sta")) {
2616 std::string grfids;
2617 size_t started = 0;
2618 for (NewGRFProfiler &pr : _newgrf_profilers) {
2619 if (!pr.active) {
2620 pr.Start();
2621 started++;
2622
2623 if (!grfids.empty()) grfids += ", ";
2624 format_append(grfids, "[{:08X}]", std::byteswap(pr.grffile->grfid));
2625 }
2626 }
2627 if (started > 0) {
2628 IConsolePrint(CC_DEBUG, "Started profiling for GRFID{} {}.", (started > 1) ? "s" : "", grfids);
2629
2630 if (argv.size() >= 3) {
2631 auto ticks = StringConsumer{argv[2]}.TryReadIntegerBase<uint64_t>(0);
2632 if (!ticks.has_value()) {
2633 IConsolePrint(CC_ERROR, "No valid amount of ticks was given, profiling will not stop automatically.");
2634 } else {
2636 IConsolePrint(CC_DEBUG, "Profiling will automatically stop after {} ticks.", *ticks);
2637 }
2638 }
2639 } else if (_newgrf_profilers.empty()) {
2640 IConsolePrint(CC_ERROR, "No GRFs selected for profiling, did not start.");
2641 } else {
2642 IConsolePrint(CC_ERROR, "Did not start profiling for any GRFs, all selected GRFs are already profiling.");
2643 }
2644 return true;
2645 }
2646
2647 /* "stop" sub-command */
2648 if (StrStartsWithIgnoreCase(argv[1], "sto")) {
2649 NewGRFProfiler::FinishAll();
2650 return true;
2651 }
2652
2653 /* "abort" sub-command */
2654 if (StrStartsWithIgnoreCase(argv[1], "abo")) {
2655 for (NewGRFProfiler &pr : _newgrf_profilers) {
2656 pr.Abort();
2657 }
2659 return true;
2660 }
2661
2662 return false;
2663}
2664
2665#ifdef _DEBUG
2666/******************
2667 * debug commands
2668 ******************/
2669
2670static void IConsoleDebugLibRegister()
2671{
2672 IConsole::CmdRegister("resettile", ConResetTile);
2673 IConsole::AliasRegister("dbg_echo", "echo %A; echo %B");
2674 IConsole::AliasRegister("dbg_echo2", "echo %!");
2675}
2676#endif
2677
2678static bool ConFramerate(std::span<std::string_view> argv)
2679{
2680 if (argv.empty()) {
2681 IConsolePrint(CC_HELP, "Show frame rate and game speed information.");
2682 return true;
2683 }
2684
2686 return true;
2687}
2688
2689static bool ConFramerateWindow(std::span<std::string_view> argv)
2690{
2691 if (argv.empty()) {
2692 IConsolePrint(CC_HELP, "Open the frame rate window.");
2693 return true;
2694 }
2695
2696 if (_network_dedicated) {
2697 IConsolePrint(CC_ERROR, "Can not open frame rate window on a dedicated server.");
2698 return false;
2699 }
2700
2702 return true;
2703}
2704
2712static std::string FormatLabel(uint32_t label)
2713{
2714 if (std::isgraph(GB(label, 24, 8)) && std::isgraph(GB(label, 16, 8)) && std::isgraph(GB(label, 8, 8)) && std::isgraph(GB(label, 0, 8))) {
2715 return fmt::format("{:c}{:c}{:c}{:c}", GB(label, 24, 8), GB(label, 16, 8), GB(label, 8, 8), GB(label, 0, 8));
2716 }
2717
2718 return fmt::format("{:08X}", label);
2719}
2720
2721static void ConDumpRoadTypes()
2722{
2723 IConsolePrint(CC_DEFAULT, " Flags:");
2724 IConsolePrint(CC_DEFAULT, " c = catenary");
2725 IConsolePrint(CC_DEFAULT, " l = no level crossings");
2726 IConsolePrint(CC_DEFAULT, " X = no houses");
2727 IConsolePrint(CC_DEFAULT, " h = hidden");
2728 IConsolePrint(CC_DEFAULT, " T = buildable by towns");
2729
2730 std::map<uint32_t, const GRFFile *> grfs;
2731 for (RoadType rt = ROADTYPE_BEGIN; rt < ROADTYPE_END; rt++) {
2732 const RoadTypeInfo *rti = GetRoadTypeInfo(rt);
2733 if (rti->label == 0) continue;
2734 uint32_t grfid = 0;
2735 const GRFFile *grf = rti->grffile[ROTSG_GROUND];
2736 if (grf != nullptr) {
2737 grfid = grf->grfid;
2738 grfs.emplace(grfid, grf);
2739 }
2740 IConsolePrint(CC_DEFAULT, " {:02d} {} {}, Flags: {}{}{}{}{}, GRF: {:08X}, {}",
2741 (uint)rt,
2742 RoadTypeIsTram(rt) ? "Tram" : "Road",
2743 FormatLabel(rti->label),
2744 rti->flags.Test(RoadTypeFlag::Catenary) ? 'c' : '-',
2745 rti->flags.Test(RoadTypeFlag::NoLevelCrossing) ? 'l' : '-',
2746 rti->flags.Test(RoadTypeFlag::NoHouses) ? 'X' : '-',
2747 rti->flags.Test(RoadTypeFlag::Hidden) ? 'h' : '-',
2748 rti->flags.Test(RoadTypeFlag::TownBuild) ? 'T' : '-',
2749 std::byteswap(grfid),
2750 GetStringPtr(rti->strings.name)
2751 );
2752 }
2753 for (const auto &grf : grfs) {
2754 IConsolePrint(CC_DEFAULT, " GRF: {:08X} = {}", std::byteswap(grf.first), grf.second->filename);
2755 }
2756}
2757
2758static void ConDumpRailTypes()
2759{
2760 IConsolePrint(CC_DEFAULT, " Flags:");
2761 IConsolePrint(CC_DEFAULT, " c = catenary");
2762 IConsolePrint(CC_DEFAULT, " l = no level crossings");
2763 IConsolePrint(CC_DEFAULT, " h = hidden");
2764 IConsolePrint(CC_DEFAULT, " s = no sprite combine");
2765 IConsolePrint(CC_DEFAULT, " a = always allow 90 degree turns");
2766 IConsolePrint(CC_DEFAULT, " d = always disallow 90 degree turns");
2767
2768 std::map<uint32_t, const GRFFile *> grfs;
2769 for (RailType rt = RAILTYPE_BEGIN; rt < RAILTYPE_END; rt++) {
2770 const RailTypeInfo *rti = GetRailTypeInfo(rt);
2771 if (rti->label == 0) continue;
2772 uint32_t grfid = 0;
2773 const GRFFile *grf = rti->grffile[RTSG_GROUND];
2774 if (grf != nullptr) {
2775 grfid = grf->grfid;
2776 grfs.emplace(grfid, grf);
2777 }
2778 IConsolePrint(CC_DEFAULT, " {:02d} {}, Flags: {}{}{}{}{}{}, GRF: {:08X}, {}",
2779 (uint)rt,
2780 FormatLabel(rti->label),
2781 rti->flags.Test(RailTypeFlag::Catenary) ? 'c' : '-',
2782 rti->flags.Test(RailTypeFlag::NoLevelCrossing) ? 'l' : '-',
2783 rti->flags.Test(RailTypeFlag::Hidden) ? 'h' : '-',
2784 rti->flags.Test(RailTypeFlag::NoSpriteCombine) ? 's' : '-',
2785 rti->flags.Test(RailTypeFlag::Allow90Deg) ? 'a' : '-',
2786 rti->flags.Test(RailTypeFlag::Disallow90Deg) ? 'd' : '-',
2787 std::byteswap(grfid),
2788 GetStringPtr(rti->strings.name)
2789 );
2790 }
2791 for (const auto &grf : grfs) {
2792 IConsolePrint(CC_DEFAULT, " GRF: {:08X} = {}", std::byteswap(grf.first), grf.second->filename);
2793 }
2794}
2795
2796static void ConDumpCargoTypes()
2797{
2798 IConsolePrint(CC_DEFAULT, " Cargo classes:");
2799 IConsolePrint(CC_DEFAULT, " p = passenger");
2800 IConsolePrint(CC_DEFAULT, " m = mail");
2801 IConsolePrint(CC_DEFAULT, " x = express");
2802 IConsolePrint(CC_DEFAULT, " a = armoured");
2803 IConsolePrint(CC_DEFAULT, " b = bulk");
2804 IConsolePrint(CC_DEFAULT, " g = piece goods");
2805 IConsolePrint(CC_DEFAULT, " l = liquid");
2806 IConsolePrint(CC_DEFAULT, " r = refrigerated");
2807 IConsolePrint(CC_DEFAULT, " h = hazardous");
2808 IConsolePrint(CC_DEFAULT, " c = covered/sheltered");
2809 IConsolePrint(CC_DEFAULT, " o = oversized");
2810 IConsolePrint(CC_DEFAULT, " d = powderized");
2811 IConsolePrint(CC_DEFAULT, " n = not pourable");
2812 IConsolePrint(CC_DEFAULT, " e = potable");
2813 IConsolePrint(CC_DEFAULT, " i = non-potable");
2814 IConsolePrint(CC_DEFAULT, " S = special");
2815
2816 std::map<uint32_t, const GRFFile *> grfs;
2817 for (const CargoSpec *spec : CargoSpec::Iterate()) {
2818 uint32_t grfid = 0;
2819 const GRFFile *grf = spec->grffile;
2820 if (grf != nullptr) {
2821 grfid = grf->grfid;
2822 grfs.emplace(grfid, grf);
2823 }
2824 IConsolePrint(CC_DEFAULT, " {:02d} Bit: {:2d}, Label: {}, Callback mask: 0x{:02X}, Cargo class: {}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}, GRF: {:08X}, {}",
2825 spec->Index(),
2826 spec->bitnum,
2827 FormatLabel(spec->label.base()),
2828 spec->callback_mask.base(),
2829 spec->classes.Test(CargoClass::Passengers) ? 'p' : '-',
2830 spec->classes.Test(CargoClass::Mail) ? 'm' : '-',
2831 spec->classes.Test(CargoClass::Express) ? 'x' : '-',
2832 spec->classes.Test(CargoClass::Armoured) ? 'a' : '-',
2833 spec->classes.Test(CargoClass::Bulk) ? 'b' : '-',
2834 spec->classes.Test(CargoClass::PieceGoods) ? 'g' : '-',
2835 spec->classes.Test(CargoClass::Liquid) ? 'l' : '-',
2836 spec->classes.Test(CargoClass::Refrigerated) ? 'r' : '-',
2837 spec->classes.Test(CargoClass::Hazardous) ? 'h' : '-',
2838 spec->classes.Test(CargoClass::Covered) ? 'c' : '-',
2839 spec->classes.Test(CargoClass::Oversized) ? 'o' : '-',
2840 spec->classes.Test(CargoClass::Powderized) ? 'd' : '-',
2841 spec->classes.Test(CargoClass::NotPourable) ? 'n' : '-',
2842 spec->classes.Test(CargoClass::Potable) ? 'e' : '-',
2843 spec->classes.Test(CargoClass::NonPotable) ? 'i' : '-',
2844 spec->classes.Test(CargoClass::Special) ? 'S' : '-',
2845 std::byteswap(grfid),
2846 GetStringPtr(spec->name)
2847 );
2848 }
2849 for (const auto &grf : grfs) {
2850 IConsolePrint(CC_DEFAULT, " GRF: {:08X} = {}", std::byteswap(grf.first), grf.second->filename);
2851 }
2852}
2853
2854static bool ConDumpInfo(std::span<std::string_view> argv)
2855{
2856 if (argv.size() != 2) {
2857 IConsolePrint(CC_HELP, "Dump debugging information.");
2858 IConsolePrint(CC_HELP, "Usage: 'dump_info roadtypes|railtypes|cargotypes'.");
2859 IConsolePrint(CC_HELP, " Show information about road/tram types, rail types or cargo types.");
2860 return true;
2861 }
2862
2863 if (StrEqualsIgnoreCase(argv[1], "roadtypes")) {
2864 ConDumpRoadTypes();
2865 return true;
2866 }
2867
2868 if (StrEqualsIgnoreCase(argv[1], "railtypes")) {
2869 ConDumpRailTypes();
2870 return true;
2871 }
2872
2873 if (StrEqualsIgnoreCase(argv[1], "cargotypes")) {
2874 ConDumpCargoTypes();
2875 return true;
2876 }
2877
2878 return false;
2879}
2880
2881/*******************************
2882 * console command registration
2883 *******************************/
2884
2885void IConsoleStdLibRegister()
2886{
2887 IConsole::CmdRegister("debug_level", ConDebugLevel);
2888 IConsole::CmdRegister("echo", ConEcho);
2889 IConsole::CmdRegister("echoc", ConEchoC);
2890 IConsole::CmdRegister("exec", ConExec);
2891 IConsole::CmdRegister("schedule", ConSchedule);
2892 IConsole::CmdRegister("exit", ConExit);
2893 IConsole::CmdRegister("part", ConPart);
2894 IConsole::CmdRegister("help", ConHelp);
2895 IConsole::CmdRegister("info_cmd", ConInfoCmd);
2896 IConsole::CmdRegister("list_cmds", ConListCommands);
2897 IConsole::CmdRegister("list_aliases", ConListAliases);
2898 IConsole::CmdRegister("newgame", ConNewGame);
2899 IConsole::CmdRegister("restart", ConRestart);
2900 IConsole::CmdRegister("reload", ConReload);
2901 IConsole::CmdRegister("getseed", ConGetSeed);
2902 IConsole::CmdRegister("getdate", ConGetDate);
2903 IConsole::CmdRegister("getsysdate", ConGetSysDate);
2904 IConsole::CmdRegister("quit", ConExit);
2907 IConsole::CmdRegister("return", ConReturn);
2908 IConsole::CmdRegister("screenshot", ConScreenShot);
2909 IConsole::CmdRegister("script", ConScript);
2912 IConsole::CmdRegister("alias", ConAlias);
2913 IConsole::CmdRegister("load", ConLoad);
2914 IConsole::CmdRegister("load_save", ConLoad);
2915 IConsole::CmdRegister("load_scenario", ConLoadScenario);
2916 IConsole::CmdRegister("load_heightmap", ConLoadHeightmap);
2917 IConsole::CmdRegister("rm", ConRemove);
2919 IConsole::CmdRegister("saveconfig", ConSaveConfig);
2920 IConsole::CmdRegister("ls", ConListFiles);
2921 IConsole::CmdRegister("list_saves", ConListFiles);
2922 IConsole::CmdRegister("list_scenarios", ConListScenarios);
2923 IConsole::CmdRegister("list_heightmaps", ConListHeightmaps);
2924 IConsole::CmdRegister("cd", ConChangeDirectory);
2925 IConsole::CmdRegister("pwd", ConPrintWorkingDirectory);
2926 IConsole::CmdRegister("clear", ConClearBuffer);
2927 IConsole::CmdRegister("font", ConFont);
2928 IConsole::CmdRegister("setting", ConSetting);
2929 IConsole::CmdRegister("setting_newgame", ConSettingNewgame);
2930 IConsole::CmdRegister("list_settings", ConListSettings);
2931 IConsole::CmdRegister("gamelog", ConGamelogPrint);
2932 IConsole::CmdRegister("rescan_newgrf", ConRescanNewGRF);
2933 IConsole::CmdRegister("list_dirs", ConListDirs);
2934
2935 IConsole::AliasRegister("dir", "ls");
2936 IConsole::AliasRegister("del", "rm %+");
2937 IConsole::AliasRegister("newmap", "newgame");
2938 IConsole::AliasRegister("patch", "setting %+");
2939 IConsole::AliasRegister("set", "setting %+");
2940 IConsole::AliasRegister("set_newgame", "setting_newgame %+");
2941 IConsole::AliasRegister("list_patches", "list_settings %+");
2942 IConsole::AliasRegister("developer", "setting developer %+");
2943
2944 IConsole::CmdRegister("list_ai_libs", ConListAILibs);
2945 IConsole::CmdRegister("list_ai", ConListAI);
2946 IConsole::CmdRegister("reload_ai", ConReloadAI);
2947 IConsole::CmdRegister("rescan_ai", ConRescanAI);
2948 IConsole::CmdRegister("start_ai", ConStartAI);
2949 IConsole::CmdRegister("stop_ai", ConStopAI);
2950
2951 IConsole::CmdRegister("list_game", ConListGame);
2952 IConsole::CmdRegister("list_game_libs", ConListGameLibs);
2953 IConsole::CmdRegister("rescan_game", ConRescanGame);
2954
2955 IConsole::CmdRegister("companies", ConCompanies);
2956 IConsole::AliasRegister("players", "companies");
2957
2958 /* networking functions */
2959
2960/* Content downloading is only available with ZLIB */
2961#if defined(WITH_ZLIB)
2962 IConsole::CmdRegister("content", ConContent);
2963#endif /* defined(WITH_ZLIB) */
2964
2965 /*** Networking commands ***/
2967 IConsole::CmdRegister("say_company", ConSayCompany, ConHookNeedNetwork);
2968 IConsole::AliasRegister("say_player", "say_company %+");
2969 IConsole::CmdRegister("say_client", ConSayClient, ConHookNeedNetwork);
2970
2971 IConsole::CmdRegister("connect", ConNetworkConnect, ConHookClientOnly);
2972 IConsole::CmdRegister("clients", ConNetworkClients, ConHookNeedNetwork);
2973 IConsole::CmdRegister("status", ConStatus, ConHookServerOnly);
2974 IConsole::CmdRegister("server_info", ConServerInfo, ConHookServerOnly);
2975 IConsole::AliasRegister("info", "server_info");
2976 IConsole::CmdRegister("reconnect", ConNetworkReconnect, ConHookClientOnly);
2977 IConsole::CmdRegister("rcon", ConRcon, ConHookNeedNetwork);
2978
2980 IConsole::AliasRegister("spectate", "join 255");
2981 IConsole::CmdRegister("move", ConMoveClient, ConHookServerOnly);
2982 IConsole::CmdRegister("reset_company", ConResetCompany, ConHookServerOnly);
2983 IConsole::AliasRegister("clean_company", "reset_company %A");
2984 IConsole::CmdRegister("client_name", ConClientNickChange, ConHookServerOnly);
2985 IConsole::CmdRegister("kick", ConKick, ConHookServerOnly);
2987 IConsole::CmdRegister("unban", ConUnBan, ConHookServerOnly);
2988 IConsole::CmdRegister("banlist", ConBanList, ConHookServerOnly);
2989
2990 IConsole::CmdRegister("pause", ConPauseGame, ConHookServerOrNoNetwork);
2991 IConsole::CmdRegister("unpause", ConUnpauseGame, ConHookServerOrNoNetwork);
2992
2993 IConsole::CmdRegister("authorized_key", ConNetworkAuthorizedKey, ConHookServerOnly);
2994 IConsole::AliasRegister("ak", "authorized_key %+");
2995
2996 IConsole::AliasRegister("net_frame_freq", "setting frame_freq %+");
2997 IConsole::AliasRegister("net_sync_freq", "setting sync_freq %+");
2998 IConsole::AliasRegister("server_pw", "setting server_password %+");
2999 IConsole::AliasRegister("server_password", "setting server_password %+");
3000 IConsole::AliasRegister("rcon_pw", "setting rcon_password %+");
3001 IConsole::AliasRegister("rcon_password", "setting rcon_password %+");
3002 IConsole::AliasRegister("name", "setting client_name %+");
3003 IConsole::AliasRegister("server_name", "setting server_name %+");
3004 IConsole::AliasRegister("server_port", "setting server_port %+");
3005 IConsole::AliasRegister("max_clients", "setting max_clients %+");
3006 IConsole::AliasRegister("max_companies", "setting max_companies %+");
3007 IConsole::AliasRegister("max_join_time", "setting max_join_time %+");
3008 IConsole::AliasRegister("pause_on_join", "setting pause_on_join %+");
3009 IConsole::AliasRegister("autoclean_companies", "setting autoclean_companies %+");
3010 IConsole::AliasRegister("autoclean_protected", "setting autoclean_protected %+");
3011 IConsole::AliasRegister("restart_game_year", "setting restart_game_year %+");
3012 IConsole::AliasRegister("min_players", "setting min_active_clients %+");
3013 IConsole::AliasRegister("reload_cfg", "setting reload_cfg %+");
3014
3015 /* debugging stuff */
3016#ifdef _DEBUG
3017 IConsoleDebugLibRegister();
3018#endif
3019 IConsole::CmdRegister("fps", ConFramerate);
3020 IConsole::CmdRegister("fps_wnd", ConFramerateWindow);
3021
3022 /* NewGRF development stuff */
3023 IConsole::CmdRegister("reload_newgrfs", ConNewGRFReload, ConHookNewGRFDeveloperTool);
3024 IConsole::CmdRegister("newgrf_profile", ConNewGRFProfile, ConHookNewGRFDeveloperTool);
3025
3026 IConsole::CmdRegister("dump_info", ConDumpInfo);
3027}
Base functions for all AIs.
AIConfig stores the configuration settings of every AI.
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr enable_if_t< is_integral_v< T >, T > byteswap(T x) noexcept
Custom implementation of std::byteswap; remove once we build with C++23.
CargoClass
Cargo classes.
Definition cargotype.h:49
@ NotPourable
Not Pourable (open wagon, but not hopper wagon)
@ Powderized
Powderized, moist protected (powder/silo wagon)
@ Hazardous
Hazardous cargo (Nuclear Fuel, Explosives, etc.)
@ NonPotable
Non-potable / non-food / dirty.
@ Bulk
Bulk cargo (Coal, Grain etc., Ores, Fruit)
@ Mail
Mail.
@ Liquid
Liquids (Oil, Water, Rubber)
@ Passengers
Passengers.
@ Potable
Potable / food / clean.
@ Oversized
Oversized (stake/flatbed wagon)
@ Armoured
Armoured cargo (Valuables, Gold, Diamonds)
@ Refrigerated
Refrigerated cargo (Food, Fruit)
@ Express
Express cargo (Goods, Food, Candy, but also possible for passengers)
@ Special
Special bit used for livery refit tricks instead of normal cargoes.
@ PieceGoods
Piece goods (Livestock, Wood, Steel, Paper)
@ Covered
Covered/Sheltered Freight (Transportation in Box Vans, Silo Wagons, etc.)
static AIConfig * GetConfig(CompanyID company, ScriptSettingSource source=SSS_DEFAULT)
Get the config of a company.
Definition ai_config.cpp:20
static void GetConsoleLibraryList(std::back_insert_iterator< std::string > &output_iterator)
Wrapper function for AIScanner::GetAIConsoleLibraryList.
Definition ai_core.cpp:293
static void GetConsoleList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only)
Wrapper function for AIScanner::GetAIConsoleList.
Definition ai_core.cpp:288
static bool CanStartNew()
Is it possible to start a new AI company?
Definition ai_core.cpp:30
static void Rescan()
Rescans all searchpaths for available AIs.
Definition ai_core.cpp:318
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
void SelectUpgrade()
Select everything that's an update for something we've got.
void DownloadSelectedContent(uint &files, uint &bytes, bool fallback=false)
Actually begin downloading the content we selected.
void Select(ContentID cid)
Select a specific content id.
void UnselectAll()
Unselect everything that we've not downloaded so far.
void RequestContentList(ContentType type)
Request the content list for the given type.
void AddCallback(ContentCallback *cb)
Add a callback to this class.
void Unselect(ContentID cid)
Unselect a specific content id.
static bool IsConnected()
Check whether the client is actually connected (and in the game).
File list storage for the console, for caching the last 'ls' command.
void ValidateFileList(bool force_reload=false)
(Re-)validate the file storage cache.
bool file_list_valid
If set, the file list is valid.
AbstractFileType abstract_filetype
The abstract file type to list.
void InvalidateFileList()
Declare the file storage cache as being invalid, also clears all stored files.
bool show_dirs
Whether to show directories in the file list.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1171
List of file information.
Definition fios.h:87
void BuildFileList(AbstractFileType abstract_filetype, SaveLoadOperation fop, bool show_dirs)
Construct a file list with the given kind of files, for the stated purpose.
Definition fios.cpp:67
const FiosItem * FindItem(std::string_view file)
Find file information of a file by its name from the file list.
Definition fios.cpp:103
static std::span< const std::unique_ptr< FontCache > > Get()
Get span of all FontCaches.
Definition fontcache.h:177
static void GetConsoleList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only)
Wrapper function for GameScanner::GetConsoleList.
static void GetConsoleLibraryList(std::back_insert_iterator< std::string > &output_iterator)
Wrapper function for GameScanner::GetConsoleLibraryList.
void PrintConsole()
Print the gamelog data to the console.
Definition gamelog.cpp:310
An interval timer will fire every interval, and will continue to fire until it is deleted.
Definition timer.h:76
Simple helper to (more easily) manage authorized keys.
bool Contains(std::string_view key) const
Check whether the given key is contains in these authorized keys.
Definition network.cpp:179
bool Add(std::string_view key)
Add the given key to the authorized keys, when it is not already contained.
Definition network.cpp:189
bool Remove(std::string_view key)
Remove the given key from the authorized keys, when it is exists.
Definition network.cpp:205
This struct contains all the info that is needed to draw and construct tracks.
Definition rail.h:115
RailTypeLabel label
Unique 32 bit rail type identifier.
Definition rail.h:225
RailTypeFlags flags
Bit mask of rail type flags.
Definition rail.h:200
const GRFFile * grffile[RTSG_END]
NewGRF providing the Action3 for the railtype.
Definition rail.h:265
RoadTypeLabel label
Unique 32 bit road type identifier.
Definition road.h:121
const GRFFile * grffile[ROTSG_END]
NewGRF providing the Action3 for the roadtype.
Definition road.h:161
bool HasScript() const
Is this config attached to an Script? In other words, is there a Script that is assigned to this slot...
void Change(std::optional< std::string_view > name, int version=-1, bool force_exact_match=false)
Set another Script to be loaded in this slot.
void StringToSettings(std::string_view value)
Convert a string which is stored in the config file or savegames to custom settings of this Script.
Parse data from a string / buffer.
std::optional< T > TryReadIntegerBase(int base, bool clamp=false)
Try to read and parse an integer in number 'base', and then advance the reader.
std::string_view ReadUntilChar(char c, SeparatorUsage sep)
Read data until the first occurrence of 8-bit char 'c', and advance reader.
@ SKIP_ONE_SEPARATOR
Read and discard one separator, do not include it in the result.
static const std::string_view WHITESPACE_NO_NEWLINE
ASCII whitespace characters, excluding new-line.
static YearMonthDay ConvertDateToYMD(Date date)
Converts a Date to a Year, Month & Day.
static Date date
Current date in days (day counter).
Functions related to commands.
Commands
List of commands.
Money CalculateCompanyValue(const Company *c, bool including_loan=true)
Calculate the value of the company.
Definition economy.cpp:150
TypedIndexContainer< std::array< Colours, MAX_COMPANIES >, CompanyID > _company_colours
NOSAVE: can be determined from company structs.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
Command definitions related to companies.
Functions related to companies.
@ CCA_NEW_AI
Create a new AI company.
@ CCA_DELETE
Delete a company.
static constexpr CompanyID COMPANY_SPECTATOR
The client is spectating.
@ CALCA_REMOVE
Remove a public key.
@ CALCA_ADD
Create a public key.
static constexpr CompanyID COMPANY_NEW_COMPANY
The client wants a new company.
@ CRR_NONE
Dummy reason for actions that don't need one.
@ CRR_MANUAL
The company is manually removed.
static const uint NETWORK_PUBLIC_KEY_LENGTH
The maximum length of the hexadecimal encoded public keys, in bytes including '\0'.
Definition config.h:101
void IConsoleCmdExec(std::string_view command_string, const uint recurse_count)
Execute a given command passed to us.
Definition console.cpp:269
void IConsolePrint(TextColour colour_code, const std::string &string)
Handle the printing of text entered into the console or redirected there by any other means.
Definition console.cpp:90
static void OutputContentState(const ContentInfo &ci)
Outputs content state information to console.
static const std::initializer_list< std::pair< std::string_view, NetworkAuthorizedKeys * > > _console_cmd_authorized_keys
All the known authorized keys with their name.
static std::string _scheduled_monthly_script
Script scheduled to execute by the 'schedule' console command (empty if no script is scheduled).
static bool ConScrollToTile(std::span< std::string_view > argv)
Scroll to a tile on the map.
static bool ConSaveConfig(std::span< std::string_view > argv)
Explicitly save the configuration.
void ShowFramerateWindow()
Open the general framerate window.
static bool ConResetEngines(std::span< std::string_view > argv)
Reset status of all engines.
static std::optional< T > ParseType(std::string_view arg)
Parse an integer using ParseInteger and convert it to the requested type.
static bool ConListDirs(std::span< std::string_view > argv)
static bool ConResetEnginePool(std::span< std::string_view > argv)
Reset status of the engine pool.
static std::string_view FontLoadReasonToName(FontLoadReason load_reason)
Get string representation of a font load reason.
static ConsoleHookResult ConHookServerOrNoNetwork(bool echo)
Check if are either in singleplayer or a server.
static ConsoleFileList _console_file_list_scenario
File storage cache for scenarios.
static ConsoleFileList _console_file_list_heightmap
File storage cache for heightmaps.
static ConsoleHookResult ConHookServerOnly(bool echo)
Check whether we are a server.
static const IntervalTimer< TimerGameCalendar > _scheduled_monthly_timer
Timer that runs every month of game time for the 'schedule' console command.
static std::string FormatLabel(uint32_t label)
Format a label as a string.
static bool ConSave(std::span< std::string_view > argv)
Save the map to a file.
void ConPrintFramerate()
Print performance statistics to game console.
static ConsoleHookResult ConHookNeedNetwork(bool echo)
Check whether we are in a multiplayer game.
static ConsoleHookResult ConHookClientOnly(bool echo)
Check whether we are a client in a network game.
static bool ConZoomToLevel(std::span< std::string_view > argv)
Zoom map to given level.
static ContentType StringToContentType(std::string_view str)
Resolve a string to a content type.
static uint _script_current_depth
Depth of scripts running (used to abort execution when ConReturn is encountered).
static bool NetworkAvailable(bool echo)
Check network availability and inform in console about failure of detection.
static ConsoleHookResult ConHookNeedNonDedicatedNetwork(bool echo)
Check whether we are in a multiplayer game and are playing, i.e.
static ConsoleFileList _console_file_list_savegame
File storage cache for savegames.
static ConsoleHookResult ConHookNoNetwork(bool echo)
Check whether we are in singleplayer mode.
static void PrintLineByLine(const std::string &full_string)
Print a text buffer line by line to the console.
Console functions used outside of the console code.
void IConsoleClose()
Close the in-game console.
Internally used functions for the console.
ConsoleHookResult
Return values of console hooks (IConsoleHook).
@ CHR_HIDE
Hide the existence of the command.
@ CHR_DISALLOW
Disallow command execution.
@ CHR_ALLOW
Allow command execution.
static const uint ICON_CMDLN_SIZE
maximum length of a typed in command
static const TextColour CC_HELP
Colour for help lines.
static const TextColour CC_WHITE
White console lines for various things such as the welcome.
static const TextColour CC_INFO
Colour for information lines.
static const TextColour CC_COMMAND
Colour for the console's commands.
static const TextColour CC_WARNING
Colour for warning lines.
static const TextColour CC_DEBUG
Colour for debug output.
static const TextColour CC_DEFAULT
Default colour of the console.
static const TextColour CC_ERROR
Colour for error lines.
void SetDebugString(std::string_view s, SetDebugStringErrorFunc error_func)
Set debugging levels by parsing the text in s.
Definition debug.cpp:144
std::string GetDebugString()
Print out the current debug-level.
Definition debug.cpp:201
Functions related to debugging.
void StartupEngines()
Start/initialise all our engines.
Definition engine.cpp:798
Base class for engines.
Functions related to engines.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23)
Definition enum_type.hpp:17
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:327
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:242
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:121
Functions for Standard In/Out file operations.
@ SLO_SAVE
File is being saved.
Definition fileio_type.h:55
@ SLO_LOAD
File is being loaded.
Definition fileio_type.h:54
@ DFT_FIOS_DRIVE
A drive (letter) entry.
Definition fileio_type.h:41
@ DFT_GAME_FILE
Save game or scenario file.
Definition fileio_type.h:31
@ DFT_FIOS_DIR
A directory entry.
Definition fileio_type.h:43
@ DFT_FIOS_PARENT
A parent directory entry.
Definition fileio_type.h:42
Searchpath
Types of searchpaths OpenTTD might use.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
@ SCREENSHOT_DIR
Subdirectory for all screenshots.
@ SOCIAL_INTEGRATION_DIR
Subdirectory for all social integration plugins.
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
@ AI_DIR
Subdirectory for all AI files.
Definition fileio_type.h:99
@ SCENARIO_DIR
Base directory for all scenarios.
Definition fileio_type.h:92
@ BASE_DIR
Base directory for all subdirectories.
Definition fileio_type.h:89
@ SAVE_DIR
Base directory for all savegames.
Definition fileio_type.h:90
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
Definition fileio_type.h:93
@ NEWGRF_DIR
Subdirectory for all NewGRFs.
Definition fileio_type.h:97
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
Definition fileio_type.h:91
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
Definition fileio_type.h:96
@ GAME_DIR
Subdirectory for all game scripts.
AbstractFileType
The different abstract types of files that the system knows about.
Definition fileio_type.h:17
@ FT_SCENARIO
old or new scenario
Definition fileio_type.h:20
@ FT_HEIGHTMAP
heightmap file
Definition fileio_type.h:21
@ FT_NONE
nothing to do
Definition fileio_type.h:18
@ FT_SAVEGAME
old or new savegame
Definition fileio_type.h:19
@ FT_INVALID
Invalid or unknown file type.
Definition fileio_type.h:24
std::string FiosGetCurrentPath()
Get the current path/working directory.
Definition fios.cpp:132
bool FiosBrowseTo(const FiosItem *item)
Browse to a new path based on the passed item, starting at _fios_path.
Definition fios.cpp:142
Declarations for savegames operations.
Functions to read fonts from files and cache them.
FontCacheSubSetting * GetFontCacheSubSetting(FontSize fs)
Get the settings of a given font size.
Definition fontcache.h:280
Base functions for all Games.
Gamelog _gamelog
Gamelog instance.
Definition gamelog.cpp:31
Functions to be called to log fundamental changes to the game.
Functions related to world/map generation.
static const uint32_t GENERATE_NEW_SEED
Create a new random seed.
Definition genworld.h:25
void StartNewGameWithoutGUI(uint32_t seed)
Start a normal game without the GUI.
PauseModes _pause_mode
The current pause mode.
Definition gfx.cpp:50
SwitchMode _switch_mode
The next mainloop command.
Definition gfx.cpp:49
FontSize
Available font sizes.
Definition gfx_type.h:248
@ FS_BEGIN
First font.
Definition gfx_type.h:255
constexpr FontSizes FONTSIZES_ALL
Mask of all possible font sizes.
Definition gfx_type.h:262
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:307
Functions related to OTTD's landscape.
bool DoZoomInOutWindow(ZoomStateChange how, Window *w)
Zooms a viewport in a window in or out.
Definition main_gui.cpp:93
static TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition map_func.h:385
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
Miscellaneous command definitions.
ClientID _redirect_console_to_client
If not invalid, redirect the console output to a client.
Definition network.cpp:72
bool _network_available
is network mode available?
Definition network.cpp:68
bool _networking
are we in networking mode?
Definition network.cpp:66
StringList _network_ban_list
The banned clients.
Definition network.cpp:76
bool _network_dedicated
are we a dedicated server?
Definition network.cpp:69
bool _network_server
network-server is active
Definition network.cpp:67
bool NetworkClientConnectGame(std::string_view connection_string, CompanyID default_company, const std::string &join_server_password)
Join a client to the server at with the given connection string.
Definition network.cpp:785
ClientID _network_own_client_id
Our client identifier.
Definition network.cpp:71
Basic functions/variables used all over the place.
AdminID _redirect_console_to_admin
Redirection of the (remote) console to the admin.
Server part of the admin network protocol.
Base core network types and some helper functions to access them.
void NetworkClientSendRcon(std::string_view password, std::string_view command)
Send a remote console command.
void NetworkClientSendChat(NetworkAction action, DestType type, int dest, std::string_view msg, int64_t data)
Send a chat message.
void NetworkClientRequestMove(CompanyID company_id)
Notify the server of this client wanting to be moved to another company.
bool NetworkIsValidClientName(std::string_view client_name)
Check whether the given client name is deemed valid for use in network games.
Client part of the network protocol.
ClientNetworkContentSocketHandler _network_content_client
The client we use to connect to the server.
Part of the network protocol handling content distribution.
std::string _network_server_invite_code
Our invite code as indicated by the Game Coordinator.
Network functions used by other parts of OpenTTD.
bool NetworkServerChangeClientName(ClientID client_id, const std::string &new_name)
Change the client name of the given client.
void NetworkPrintClients()
Print all the clients to the console.
void NetworkServerDoMove(ClientID client_id, CompanyID company_id)
Handle the tid-bits of moving a client from one company to another.
bool NetworkCompanyHasClients(CompanyID company)
Check whether a particular company has clients.
void NetworkServerKickClient(ClientID client_id, std::string_view reason)
Kick a single client.
std::string_view NetworkGetPublicKeyOfClient(ClientID client_id)
Get the public key of the client with the given id.
void NetworkServerSendChat(NetworkAction action, DestType type, int dest, std::string_view msg, ClientID from_id, int64_t data=0, bool from_admin=false)
Send an actual chat message.
void NetworkServerShowStatusToConsole()
Show the status message of all clients on the console.
uint NetworkServerKickOrBanIP(ClientID client_id, bool ban, std::string_view reason)
Ban, or kick, everyone joined from the given client's IP.
@ DESTTYPE_CLIENT
Send message/notice to only a certain client (Private)
@ DESTTYPE_TEAM
Send message/notice to everyone playing the same company (Team)
@ DESTTYPE_BROADCAST
Send message/notice to all clients (All)
ClientID
'Unique' identifier to be given to clients
@ INVALID_CLIENT_ID
Client is not part of anything.
@ CLIENT_ID_SERVER
Servers always have this ID.
Base for the NewGRF implementation.
void ReloadNewGRFData()
Reload all NewGRF files during a running game.
Profiling of NewGRF action 2 handling.
bool RequestNewGRFScan(NewGRFScanCallback *callback)
Request a new NewGRF scan.
Definition openttd.cpp:1327
@ Error
A game paused because a (critical) error.
@ Normal
A game normally paused.
@ SM_START_HEIGHTMAP
Load a heightmap and start a new game from it.
Definition openttd.h:38
@ SM_MENU
Switch to game intro menu.
Definition openttd.h:33
@ SM_RESTARTGAME
Restart --> 'Random game' with current settings.
Definition openttd.h:29
@ SM_RELOADGAME
Reload the savegame / scenario / heightmap you started the game with.
Definition openttd.h:30
@ SM_LOAD_GAME
Load game, Play Scenario.
Definition openttd.h:32
Rail specific functions.
const RailTypeInfo * GetRailTypeInfo(RailType railtype)
Returns a pointer to the Railtype information for a given railtype.
Definition rail.h:300
RailTypeFlag
Railtype flag bit numbers.
Definition rail.h:27
@ Catenary
Bit number for drawing a catenary.
@ Allow90Deg
Bit number for always allowed 90 degree turns, regardless of setting.
@ Disallow90Deg
Bit number for never allowed 90 degree turns, regardless of setting.
@ Hidden
Bit number for hiding from selection.
@ NoSpriteCombine
Bit number for using non-combined junctions.
@ NoLevelCrossing
Bit number for disallowing level crossings.
@ RTSG_GROUND
Main group of ground images.
Definition rail.h:43
RailType
Enumeration for all possible railtypes.
Definition rail_type.h:25
@ RAILTYPE_BEGIN
Used for iterations.
Definition rail_type.h:26
@ RAILTYPE_END
Used for iterations.
Definition rail_type.h:31
Road specific functions.
RoadTypeFlag
Roadtype flag bit numbers.
Definition road.h:24
@ NoHouses
Bit number for setting this roadtype as not house friendly.
@ TownBuild
Bit number for allowing towns to build this roadtype.
const RoadTypeInfo * GetRoadTypeInfo(RoadType roadtype)
Returns a pointer to the Roadtype information for a given roadtype.
Definition road.h:215
@ ROTSG_GROUND
Required: Main group of ground images.
Definition road.h:39
RoadType
The different roadtypes we support.
Definition road_type.h:23
@ ROADTYPE_END
Used for iterations.
Definition road_type.h:27
@ ROADTYPE_BEGIN
Used for iterations.
Definition road_type.h:24
A number of safeguards to prevent using unsafe methods.
SaveOrLoadResult SaveOrLoad(std::string_view filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
Main Save or Load function where the high-level saveload functions are handled.
FileToSaveLoad _file_to_saveload
File to save or load in the openttd loop.
Definition saveload.cpp:78
void DoExitSave()
Do a save when exiting the game (_settings_client.gui.autosave_on_exit)
Functions/types related to saving and loading games.
@ SL_OK
completed successfully
Definition saveload.h:422
bool MakeScreenshot(ScreenshotType t, const std::string &name, uint32_t width, uint32_t height)
Schedule making a screenshot.
Functions to make screenshots.
ScreenshotType
Type of requested screenshot.
Definition screenshot.h:16
@ SC_VIEWPORT
Screenshot of viewport.
Definition screenshot.h:17
@ SC_ZOOMEDIN
Fully zoomed in screenshot of the visible area.
Definition screenshot.h:19
@ SC_HEIGHTMAP
Heightmap of the world.
Definition screenshot.h:22
@ SC_WORLD
World screenshot.
Definition screenshot.h:21
@ SC_MINIMAP
Minimap screenshot.
Definition screenshot.h:23
@ SC_DEFAULTZOOM
Zoomed to default zoom level screenshot of the visible area.
Definition screenshot.h:20
void IConsoleGetSetting(std::string_view name, bool force_newgame)
Output value of a specific setting to the console.
void SaveToConfig()
Save the values to the configuration file.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
void IConsoleListSettings(std::string_view prefilter)
List all settings and their value to the console.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Functions related to setting/changing the settings.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
std::string FormatArrayAsHex(std::span< const uint8_t > data)
Format a byte array into a continuous hex string.
Definition string.cpp:77
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:323
bool StrStartsWithIgnoreCase(std::string_view str, std::string_view prefix)
Check whether the given string starts with the given prefix, ignoring case.
Definition string.cpp:257
bool StrContainsIgnoreCase(std::string_view str, std::string_view value)
Checks if a string is contained in another string, while ignoring the case of the characters.
Definition string.cpp:336
Parse strings.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Definition strings.cpp:424
Functions related to OTTD's strings.
bool ai_in_multiplayer
so we allow AIs in multiplayer
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Specification of a cargo type.
Definition cargotype.h:74
NetworkSettings network
settings related to the network
GUISettings gui
settings related to the GUI
NetworkAuthorizedKeys allow_list
Public keys of clients that are allowed to join this company.
static bool IsHumanID(auto index)
Is this company a company not controlled by a NoAI program?
Asynchronous callback.
void OnConnect(bool success) override
Callback for when the connection has finished.
void OnDownloadComplete(ContentID cid) override
We have finished downloading a file.
void OnDisconnect() override
Callback for when the connection got disconnected.
Callbacks for notifying others about incoming data.
Container for all important information about a piece of content.
uint32_t unique_id
Unique ID; either GRF ID or shortname.
MD5Hash md5sum
The MD5 checksum.
State state
Whether the content info is selected (for download)
std::string name
Name of the content.
ContentID id
Unique (server side) ID for the content.
ContentType type
Type of content.
@ Selected
The content has been manually selected.
@ Autoselected
The content has been selected as dependency.
static bool ResetToCurrentNewGRFConfig()
Tries to reset the engine mapping to match the current NewGRF configuration.
Definition engine.cpp:584
FiosType ftype
File type.
Definition saveload.h:430
void Set(const FiosItem &item)
Set the title of the file.
Deals with finding savegames.
Definition fios.h:78
DetailedFileType detailed
Detailed file type.
Definition fileio_type.h:65
AbstractFileType abstract
Abstract file type.
Definition fileio_type.h:64
Settings for a single font.
Definition fontcache.h:249
std::string font
The name of the font, or path to the font.
Definition fontcache.h:250
uint size
The (requested) size of the font.
Definition fontcache.h:251
Dynamic data of a loaded NewGRF.
Definition newgrf.h:114
bool autosave_on_exit
save an autosave when you quit the game, but do not ask "Do you really want to quit?...
ZoomLevel zoom_min
minimum zoom out level
bool newgrf_developer_tools
activate NewGRF developer tools and allow modifying NewGRFs in an existing game
ZoomLevel zoom_max
maximum zoom out level
uint8_t map_x
X size of map.
uint8_t map_y
Y size of map.
uint32_t generation_seed
noise seed for world generation
AISettings ai
what may the AI do?
GameCreationSettings game_creation
settings used during the creation of a game (map)
–Aliases– Aliases are like shortcuts for complex functions, variable assignments, etc.
std::string cmdline
command(s) that is/are being aliased
std::string name
name of the alias
IConsoleCmdProc * proc
process executed when command is typed
IConsoleHook * hook
any special trigger action that needs executing
std::string name
name of command
static void AliasRegister(const std::string &name, std::string_view cmd)
Register a an alias for an already existing command in the console.
Definition console.cpp:159
static IConsoleAlias * AliasGet(const std::string &name)
Find the alias pointed to by its string.
Definition console.cpp:170
static void CmdRegister(const std::string &name, IConsoleCmdProc *proc, IConsoleHook *hook=nullptr)
Register a new command to be used in the console.
Definition console.cpp:137
static IConsoleCmd * CmdGet(const std::string &name)
Find the command pointed to by its string.
Definition console.cpp:147
static uint SizeX()
Get the size of the map along the X.
Definition map_func.h:272
static uint SizeY()
Get the size of the map along the Y.
Definition map_func.h:281
static uint LogX()
Logarithm of the map size along the X side.
Definition map_func.h:253
static uint LogY()
Logarithm of the map size along the y side.
Definition map_func.h:263
static uint Size()
Get the size of the map.
Definition map_func.h:290
Container for all information known about a client.
static NetworkClientInfo * GetByClientID(ClientID client_id)
Return the CI given it's client-identifier.
Definition network.cpp:117
bool CanJoinCompany(CompanyID company_id) const
Returns whether the given company can be joined by this client.
Definition network.cpp:131
CompanyID client_playas
As which company is this client playing (CompanyID)
ClientID client_id
Client identifier (same as ClientState->client_id)
uint8_t clients_on
Current count of clients on server.
uint8_t max_clients
maximum amount of clients
uint8_t max_companies
maximum amount of companies
NetworkAuthorizedKeys admin_authorized_keys
Public keys of clients that are authorized to use the admin network.
NetworkAuthorizedKeys rcon_authorized_keys
Public keys of clients that are authorized to use the rconsole (server side).
std::string last_joined
Last joined server.
NetworkAuthorizedKeys server_authorized_keys
Public keys of clients that are authorized to connect to the game.
Callback profiler for NewGRF development.
static void StartTimer(uint64_t ticks)
Start the timeout timer that will finish all profiling sessions.
bool active
Is this profiler collecting data.
const GRFFile * grffile
Which GRF is being profiled.
static void AbortTimer()
Abort the timeout timer, so the timer callback is never called.
static size_t GetNumItems()
Returns number of valid items in the pool.
Tindex index
Index of this pool item.
static bool IsValidID(auto index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
static Titem * GetIfValid(auto index)
Returns Titem with given index.
static constexpr size_t MAX_SIZE
Make template parameter accessible from outside.
Data structure for viewport, display of a part of the world.
ZoomLevel zoom
The zoom level of the viewport.
Data structure for an opened window.
Definition window_gui.h:274
std::unique_ptr< ViewportData > viewport
Pointer to viewport data, if present.
Definition window_gui.h:319
uint32_t ContentID
Unique identifier for the content.
ContentType
The values in the enum are important; they are used as database 'keys'.
@ CONTENT_TYPE_AI_LIBRARY
The content consists of an AI library.
@ CONTENT_TYPE_BASE_GRAPHICS
The content consists of base graphics.
@ CONTENT_TYPE_AI
The content consists of an AI.
@ CONTENT_TYPE_SCENARIO
The content consists of a scenario.
@ CONTENT_TYPE_NEWGRF
The content consists of a NewGRF.
@ CONTENT_TYPE_BEGIN
Helper to mark the begin of the types.
@ CONTENT_TYPE_END
Helper to mark the end of the types.
@ CONTENT_TYPE_HEIGHTMAP
The content consists of a heightmap.
bool IsValidTile(Tile tile)
Checks if a tile is valid.
Definition tile_map.h:161
Definition of Interval and OneShot timers.
@ VEH_ROAD
Road vehicle type.
@ VEH_AIRCRAFT
Aircraft vehicle type.
@ VEH_SHIP
Ship vehicle type.
@ VEH_TRAIN
Train vehicle type.
bool ScrollMainWindowToTile(TileIndex tile, bool instant)
Scrolls the viewport of the main window to a given location.
Functions related to (drawing on) viewports.
@ ZOOM_IN
Zoom in (get more detailed view).
@ ZOOM_OUT
Zoom out (get helicopter view).
Window * GetMainWindow()
Get the main window, i.e.
Definition window.cpp:1180
void SetWindowDirty(WindowClass cls, WindowNumber number)
Mark window as dirty (in need of repainting)
Definition window.cpp:3176
Window functions not directly related to making/drawing windows.
@ WC_CONSOLE
Console; Window numbers:
ZoomLevel
All zoom levels we know.
Definition zoom_type.h:20
@ Begin
Begin for iteration.
@ Max
Maximum zoom level.
@ Min
Minimum zoom level.
@ End
End for iteration.