1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Headers
19
20 #include <algorithm>
21 #include <cstring>
22 #include <cstdlib>
23 #include <iostream>
24 #include <iomanip>
25 #include <fstream>
26 #include <memory>
27 #include <thread>
28
29 #ifdef _WIN32
30 # include "platform/windows/utils.h"
31 # include <windows.h>
32 # include <shellapi.h>
33 #elif defined(GEKKO)
34 # include <fat.h>
35 #elif defined(EMSCRIPTEN)
36 # include <emscripten.h>
37 #elif defined(PSP2)
38 # include <psp2/kernel/processmgr.h>
39 #elif defined(_3DS)
40 # include <3ds.h>
41 #elif defined(__SWITCH__)
42 # include <switch.h>
43 #endif
44
45 #include "async_handler.h"
46 #include "audio.h"
47 #include "cache.h"
48 #include "rand.h"
49 #include "cmdline_parser.h"
50 #include "dynrpg.h"
51 #include "filefinder.h"
52 #include "filefinder_rtp.h"
53 #include "fileext_guesser.h"
54 #include "game_actors.h"
55 #include "game_battle.h"
56 #include "game_map.h"
57 #include "game_message.h"
58 #include "game_enemyparty.h"
59 #include "game_ineluki.h"
60 #include "game_party.h"
61 #include "game_player.h"
62 #include "game_switches.h"
63 #include "game_screen.h"
64 #include "game_pictures.h"
65 #include "game_system.h"
66 #include "game_variables.h"
67 #include "game_targets.h"
68 #include "graphics.h"
69 #include <lcf/inireader.h>
70 #include "input.h"
71 #include <lcf/ldb/reader.h>
72 #include <lcf/lmt/reader.h>
73 #include <lcf/lsd/reader.h>
74 #include "main_data.h"
75 #include "output.h"
76 #include "player.h"
77 #include <lcf/reader_lcf.h>
78 #include <lcf/reader_util.h>
79 #include "scene_battle.h"
80 #include "scene_logo.h"
81 #include "scene_map.h"
82 #include "utils.h"
83 #include "version.h"
84 #include "game_quit.h"
85 #include "scene_title.h"
86 #include "instrumentation.h"
87 #include "transition.h"
88 #include <lcf/scope_guard.h>
89 #include "baseui.h"
90 #include "game_clock.h"
91
92 #ifndef EMSCRIPTEN
93 // This is not used on Emscripten.
94 #include "exe_reader.h"
95 #endif
96
97 namespace Player {
98 bool exit_flag;
99 bool reset_flag;
100 bool debug_flag;
101 bool hide_title_flag;
102 bool new_game_flag;
103 int load_game_id;
104 int party_x_position;
105 int party_y_position;
106 std::vector<int> party_members;
107 int start_map_id;
108 bool no_rtp_flag;
109 std::string rtp_path;
110 bool no_audio_flag;
111 bool is_easyrpg_project;
112 bool mouse_flag;
113 bool touch_flag;
114 std::string encoding;
115 std::string escape_symbol;
116 uint32_t escape_char;
117 int engine;
118 std::string game_title;
119 int patch;
120 std::shared_ptr<Meta> meta;
121 FileExtGuesser::RPG2KFileExtRemap fileext_map;
122 Translation translation;
123 int frames;
124 std::string replay_input_path;
125 std::string record_input_path;
126 std::string command_line;
127 int speed_modifier = 3;
128 int speed_modifier_plus = 10;
129 Game_ConfigPlayer player_config;
130 #ifdef EMSCRIPTEN
131 std::string emscripten_game_name;
132 #endif
133 #ifdef _3DS
134 bool is_3dsx;
135 #endif
136 }
137
138 namespace {
139 // Overwritten by --encoding
140 std::string forced_encoding;
141
142 FileRequestBinding system_request_id;
143 FileRequestBinding save_request_id;
144 FileRequestBinding map_request_id;
145 }
146
Init(int argc,char * argv[])147 void Player::Init(int argc, char *argv[]) {
148 frames = 0;
149
150 // Must be called before the first call to Output
151 Graphics::Init();
152
153 // FIXME: actual command line parsing is too late for setting this,
154 // it should be refactored after release to not break things now
155 for (int i = 1; i < argc; ++i) {
156 if (strcmp(argv[i], "--no-log-color") == 0) {
157 Output::SetTermColor(false);
158 break;
159 }
160 }
161
162 // Display a nice version string
163 std::stringstream header;
164 std::string addtl_ver(PLAYER_ADDTL);
165 header << "EasyRPG Player " << PLAYER_VERSION;
166 if (!addtl_ver.empty())
167 header << " " << addtl_ver;
168 header << " started";
169 Output::Debug("{}", header.str());
170
171 unsigned int header_width = header.str().length();
172 header.str("");
173 header << std::setfill('=') << std::setw(header_width) << "=";
174 Output::Debug("{}", header.str());
175
176 #ifdef GEKKO
177 // Init libfat (Mount SD/USB)
178 if (!fatInitDefault()) {
179 Output::Error("Couldn't mount any storage medium!");
180 }
181 #elif defined(_3DS)
182 romfsInit();
183 #endif
184
185 #if defined(_WIN32)
186 WindowsUtils::InitMiniDumpWriter();
187 #endif
188
189 Game_Clock::logClockInfo();
190 Rand::SeedRandomNumberGenerator(time(NULL));
191
192 #ifdef EMSCRIPTEN
193 Output::IgnorePause(true);
194
195 // Retrieve save directory from persistent storage
196 EM_ASM(({
197 FS.mkdir("Save");
198 FS.mount(Module.EASYRPG_FS, {}, 'Save');
199 FS.syncfs(true, function(err) {
200 });
201 }));
202 #endif
203
204 auto cfg = ParseCommandLine(argc, argv);
205
206 Main_Data::Init();
207
208 DisplayUi.reset();
209
210 if(! DisplayUi) {
211 DisplayUi = BaseUi::CreateUi(SCREEN_TARGET_WIDTH, SCREEN_TARGET_HEIGHT, cfg.video);
212 }
213
214 auto buttons = Input::GetDefaultButtonMappings();
215 auto directions = Input::GetDefaultDirectionMappings();
216
217 Input::Init(std::move(buttons), std::move(directions), replay_input_path, record_input_path);
218 Input::AddRecordingData(Input::RecordingData::CommandLine, command_line);
219
220 player_config = std::move(cfg.player);
221 }
222
Run()223 void Player::Run() {
224 Instrumentation::Init("EasyRPG-Player");
225 Scene::Push(std::make_shared<Scene_Logo>());
226 Graphics::UpdateSceneCallback();
227
228 reset_flag = false;
229
230 Game_Clock::ResetFrame(Game_Clock::now());
231
232 // Main loop
233 // libretro invokes the MainLoop through a retro_run-callback
234 #ifndef USE_LIBRETRO
235 while (Transition::instance().IsActive() || (Scene::instance && Scene::instance->type != Scene::Null)) {
236 # if defined(_3DS)
237 if (!aptMainLoop())
238 Exit();
239 # elif defined(__SWITCH__)
240 if(!appletMainLoop())
241 Exit();
242 # endif
243 MainLoop();
244 }
245 #endif
246 }
247
MainLoop()248 void Player::MainLoop() {
249 Instrumentation::FrameScope iframe;
250
251 const auto frame_time = Game_Clock::now();
252 Game_Clock::OnNextFrame(frame_time);
253
254 Player::UpdateInput();
255
256 int num_updates = 0;
257 while (Game_Clock::NextGameTimeStep()) {
258 if (num_updates > 0) {
259 Player::UpdateInput();
260 }
261
262 Scene::old_instances.clear();
263 Scene::instance->MainFunction();
264
265 ++num_updates;
266 }
267 if (num_updates == 0) {
268 // If no logical frames ran, we need to update the system keys only.
269 Input::UpdateSystem();
270 }
271
272 Player::Draw();
273
274 Scene::old_instances.clear();
275
276 if (!Transition::instance().IsActive() && Scene::instance->type == Scene::Null) {
277 Exit();
278 return;
279 }
280
281 auto frame_limit = DisplayUi->GetFrameLimit();
282 if (frame_limit == Game_Clock::duration()) {
283 #ifdef EMSCRIPTEN
284 emscripten_sleep(0);
285 #endif
286 return;
287 }
288
289 // Still time after graphic update? Yield until it's time for next one.
290 auto now = Game_Clock::now();
291 auto next = frame_time + frame_limit;
292 if (Game_Clock::now() < next) {
293 iframe.End();
294 Game_Clock::SleepFor(next - now);
295 } else {
296 #ifdef EMSCRIPTEN
297 // Yield back to browser once per frame
298 emscripten_sleep(0);
299 #endif
300 }
301 }
302
Pause()303 void Player::Pause() {
304 Audio().BGM_Pause();
305 }
306
Resume()307 void Player::Resume() {
308 Input::ResetKeys();
309 Audio().BGM_Resume();
310 Game_Clock::ResetFrame(Game_Clock::now());
311 }
312
UpdateInput()313 void Player::UpdateInput() {
314 // Input Logic:
315 if (Input::IsSystemTriggered(Input::TOGGLE_FPS)) {
316 DisplayUi->ToggleShowFps();
317 }
318 if (Input::IsSystemTriggered(Input::TAKE_SCREENSHOT)) {
319 Output::TakeScreenshot();
320 }
321 if (Input::IsSystemTriggered(Input::SHOW_LOG)) {
322 Output::ToggleLog();
323 }
324 if (Input::IsSystemTriggered(Input::TOGGLE_ZOOM)) {
325 DisplayUi->ToggleZoom();
326 }
327 float speed = 1.0;
328 if (Input::IsSystemPressed(Input::FAST_FORWARD)) {
329 speed = speed_modifier;
330 }
331 if (Input::IsSystemPressed(Input::FAST_FORWARD_PLUS)) {
332 speed = speed_modifier_plus;
333 }
334 Game_Clock::SetGameSpeedFactor(speed);
335
336 if (Main_Data::game_quit) {
337 reset_flag |= Main_Data::game_quit->ShouldQuit();
338 }
339
340 // Update Logic:
341 DisplayUi->ProcessEvents();
342 }
343
Update(bool update_scene)344 void Player::Update(bool update_scene) {
345 std::shared_ptr<Scene> old_instance = Scene::instance;
346
347 if (exit_flag) {
348 Scene::PopUntil(Scene::Null);
349 } else if (reset_flag && !Scene::IsAsyncPending()) {
350 reset_flag = false;
351 if (Scene::ReturnToTitleScene()) {
352 // Fade out music and stop sound effects before returning
353 Main_Data::game_system->BgmFade(800);
354 Audio().SE_Stop();
355 // Do not update this scene until it's properly set up in the next main loop
356 update_scene = false;
357 }
358 }
359
360 if (update_scene) {
361 IncFrame();
362 }
363
364 Audio().Update();
365 Input::Update();
366
367 // Game events can query full screen status and change their behavior, so this needs to
368 // be a game key and not a system key.
369 if (Input::IsTriggered(Input::TOGGLE_FULLSCREEN)) {
370 DisplayUi->ToggleFullscreen();
371 }
372
373 if (Main_Data::game_quit) {
374 Main_Data::game_quit->Update();
375 }
376
377 auto& transition = Transition::instance();
378
379 if (transition.IsActive()) {
380 transition.Update();
381 } else {
382 // If we aren't waiting on a transition, but we are waiting for scene delay.
383 Scene::instance->UpdateDelayFrames();
384 }
385
386 if (update_scene) {
387 if (Main_Data::game_ineluki) {
388 Main_Data::game_ineluki->Update();
389 }
390
391 Scene::instance->Update();
392 }
393 }
394
Draw()395 void Player::Draw() {
396 Graphics::Update();
397 Graphics::Draw(*DisplayUi->GetDisplaySurface());
398 DisplayUi->UpdateDisplay();
399 }
400
IncFrame()401 void Player::IncFrame() {
402 ++frames;
403 if (Main_Data::game_system) {
404 Main_Data::game_system->IncFrameCounter();
405 }
406 }
407
GetFrames()408 int Player::GetFrames() {
409 return frames;
410 }
411
Exit()412 void Player::Exit() {
413 Graphics::UpdateSceneCallback();
414 #ifdef EMSCRIPTEN
415 BitmapRef surface = DisplayUi->GetDisplaySurface();
416 std::string message = "It's now safe to turn off\n your browser.";
417
418 Text::Draw(*surface, 84, DisplayUi->GetHeight() / 2 - 30, *Font::Default(), Color(221, 123, 64, 255), message);
419 DisplayUi->UpdateDisplay();
420 #endif
421
422 Player::ResetGameObjects();
423 Font::Dispose();
424 DynRpg::Reset();
425 Graphics::Quit();
426 Output::Quit();
427 FileFinder::Quit();
428 DisplayUi.reset();
429
430 #ifdef PSP2
431 sceKernelExitProcess(0);
432 #elif defined(_3DS)
433 romfsExit();
434 #endif
435 }
436
ParseCommandLine(int argc,char * argv[])437 Game_Config Player::ParseCommandLine(int argc, char *argv[]) {
438 #ifdef _3DS
439 is_3dsx = argc > 0;
440 #endif
441
442 #if defined(_WIN32) && !defined(__WINRT__)
443 int argc_w;
444 LPWSTR *argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
445 #endif
446
447 engine = EngineNone;
448 patch = PatchNone;
449 debug_flag = false;
450 hide_title_flag = false;
451 exit_flag = false;
452 reset_flag = false;
453 new_game_flag = false;
454 load_game_id = -1;
455 party_x_position = -1;
456 party_y_position = -1;
457 start_map_id = -1;
458 no_rtp_flag = false;
459 no_audio_flag = false;
460 is_easyrpg_project = false;
461 mouse_flag = false;
462 touch_flag = false;
463 Game_Battle::battle_test.enabled = false;
464
465 std::stringstream ss;
466 for (int i = 1; i < argc; ++i) {
467 ss << argv[i] << " ";
468 }
469 Output::Debug("CLI: {}", ss.str());
470 command_line = ss.str();
471
472 #if defined(_WIN32) && !defined(__WINRT__)
473 CmdlineParser cp(argc, argv_w);
474 #else
475 CmdlineParser cp(argc, argv);
476 #endif
477
478 auto cfg = Game_Config::Create(cp);
479
480 cp.Rewind();
481 while (!cp.Done()) {
482 CmdlineArg arg;
483 long li_value = 0;
484 if (cp.ParseNext(arg, 0, "window")) {
485 // Legacy RPG_RT argument - window
486 cfg.video.fullscreen.Set(false);
487 continue;
488 }
489 if (cp.ParseNext(arg, 0, "--enable-mouse")) {
490 mouse_flag = true;
491 continue;
492 }
493 if (cp.ParseNext(arg, 0, "--enable-touch")) {
494 touch_flag = true;
495 continue;
496 }
497 if (cp.ParseNext(arg, 0, {"testplay", "--test-play"})) {
498 // Legacy RPG_RT argument - testplay
499 debug_flag = true;
500 continue;
501 }
502 if (cp.ParseNext(arg, 0, {"hidetitle", "--hide-title"})) {
503 // Legacy RPG_RT argument - hidetitle
504 hide_title_flag = true;
505 continue;
506 }
507 if(cp.ParseNext(arg, 4, {"battletest", "--battle-test"})) {
508 // Legacy RPG_RT argument - battletest
509 Game_Battle::battle_test.enabled = true;
510 Game_Battle::battle_test.troop_id = 0;
511
512 if (arg.NumValues() > 0) {
513 if (arg.ParseValue(0, li_value)) {
514 Game_Battle::battle_test.troop_id = li_value;
515 }
516 }
517
518 if (arg.NumValues() >= 4) {
519 // 2k3 passes formation, condition and terrain_id as args 5-7
520 if (arg.ParseValue(1, li_value)) {
521 Game_Battle::battle_test.formation = static_cast<lcf::rpg::System::BattleFormation>(li_value);
522 }
523 if (arg.ParseValue(2, li_value)) {
524 Game_Battle::battle_test.condition = static_cast<lcf::rpg::System::BattleCondition>(li_value);
525 }
526 if (arg.ParseValue(3, li_value)) {
527 Game_Battle::battle_test.terrain_id = static_cast<lcf::rpg::System::BattleFormation>(li_value);
528 }
529 }
530 continue;
531 }
532 if (cp.ParseNext(arg, 1, "--project-path") && arg.NumValues() > 0) {
533 if (arg.NumValues() > 0) {
534 auto gamefs = FileFinder::Root().Create(FileFinder::MakeCanonical(arg.Value(0), 0));
535 if (!gamefs) {
536 Output::Error("Invalid --project-path {}", arg.Value(0));
537 }
538 FileFinder::SetGameFilesystem(gamefs);
539 }
540 continue;
541 }
542 if (cp.ParseNext(arg, 1, "--save-path")) {
543 if (arg.NumValues() > 0) {
544 auto savefs = FileFinder::Root().Create(FileFinder::MakeCanonical(arg.Value(0), 0));
545 if (!savefs) {
546 Output::Error("Invalid --save-path {}", arg.Value(0));
547 }
548 FileFinder::SetSaveFilesystem(savefs);
549 }
550 continue;
551 }
552 if (cp.ParseNext(arg, 0, "--new-game")) {
553 new_game_flag = true;
554 continue;
555 }
556 if (cp.ParseNext(arg, 1, "--load-game-id")) {
557 if (arg.ParseValue(0, li_value)) {
558 load_game_id = li_value;
559 }
560 continue;
561 }
562 /*else if (*it == "--load-game") {
563 // load game by filename
564 }
565 else if (*it == "--database") {
566 // overwrite database file
567 }
568 else if (*it == "--map-tree") {
569 // overwrite map tree file
570 }
571 else if (*it == "--start-map") {
572 // overwrite start map by filename
573 }*/
574 if (cp.ParseNext(arg, 1, "--seed")) {
575 if (arg.ParseValue(0, li_value)) {
576 Rand::SeedRandomNumberGenerator(li_value);
577 }
578 continue;
579 }
580 if (cp.ParseNext(arg, 1, "--start-map-id")) {
581 if (arg.ParseValue(0, li_value)) {
582 start_map_id = li_value;
583 }
584 continue;
585 }
586 if (cp.ParseNext(arg, 2, "--start-position")) {
587 if (arg.ParseValue(0, li_value)) {
588 party_x_position = li_value;
589 }
590 if (arg.ParseValue(1, li_value)) {
591 party_y_position = li_value;
592 }
593 continue;
594 }
595 if (cp.ParseNext(arg, 4, "--start-party")) {
596 for (int i = 0; i < arg.NumValues(); ++i) {
597 if (arg.ParseValue(i, li_value)) {
598 party_members.push_back(li_value);
599 }
600 }
601 continue;
602 }
603 if (cp.ParseNext(arg, 1, "--engine")) {
604 if (arg.NumValues() > 0) {
605 const auto& v = arg.Value(0);
606 if (v == "rpg2k" || v == "2000") {
607 engine = EngineRpg2k;
608 }
609 else if (v == "rpg2kv150" || v == "2000v150") {
610 engine = EngineRpg2k | EngineMajorUpdated;
611 }
612 else if (v == "rpg2ke" || v == "2000e") {
613 engine = EngineRpg2k | EngineMajorUpdated | EngineEnglish;
614 }
615 else if (v == "rpg2k3" || v == "2003") {
616 engine = EngineRpg2k3;
617 }
618 else if (v == "rpg2k3v105" || v == "2003v105") {
619 engine = EngineRpg2k3 | EngineMajorUpdated;
620 }
621 else if (v == "rpg2k3e") {
622 engine = EngineRpg2k3 | EngineMajorUpdated | EngineEnglish;
623 }
624 }
625 continue;
626 }
627 if (cp.ParseNext(arg, 1, "--record-input")) {
628 if (arg.NumValues() > 0) {
629 record_input_path = arg.Value(0);
630 }
631 continue;
632 }
633 if (cp.ParseNext(arg, 1, "--replay-input")) {
634 if (arg.NumValues() > 0) {
635 replay_input_path = arg.Value(0);
636 }
637 continue;
638 }
639 if (cp.ParseNext(arg, 1, "--encoding")) {
640 if (arg.NumValues() > 0) {
641 forced_encoding = arg.Value(0);
642 }
643 continue;
644 }
645 if (cp.ParseNext(arg, 0, "--disable-audio")) {
646 no_audio_flag = true;
647 continue;
648 }
649 if (cp.ParseNext(arg, 0, "--disable-rtp")) {
650 no_rtp_flag = true;
651 continue;
652 }
653 if (cp.ParseNext(arg, 1, "--rtp-path")) {
654 if (arg.NumValues() > 0) {
655 rtp_path = arg.Value(0);
656 }
657 continue;
658 }
659 if (cp.ParseNext(arg, 2, "--patch")) {
660 patch |= PatchOverride;
661 for (int i = 0; i < arg.NumValues(); ++i) {
662 const auto& v = arg.Value(i);
663 if (v == "dynrpg") {
664 patch |= PatchDynRpg;
665 } else if (v == "maniac") {
666 patch |= PatchManiac;
667 }
668 }
669 continue;
670 }
671 if (cp.ParseNext(arg, 0, "--no-log-color")) {
672 Output::SetTermColor(false);
673 continue;
674 }
675 if (cp.ParseNext(arg, 0, "--version", 'v')) {
676 PrintVersion();
677 exit(0);
678 break;
679 }
680 if (cp.ParseNext(arg, 0, {"--help", "/?"}, 'h')) {
681 PrintUsage();
682 exit(0);
683 break;
684 }
685 #ifdef EMSCRIPTEN
686 if (cp.ParseNext(arg, 1, "--game")) {
687 if (arg.NumValues() > 0) {
688 emscripten_game_name = arg.Value(0);
689 }
690 continue;
691 }
692 #endif
693 cp.SkipNext();
694 }
695
696 #if defined(_WIN32) && !defined(__WINRT__)
697 LocalFree(argv_w);
698 #endif
699
700 return cfg;
701 }
702
CreateGameObjects()703 void Player::CreateGameObjects() {
704 // Load the meta information file.
705 // Note: This should eventually be split across multiple folders as described in Issue #1210
706 std::string meta_file = FileFinder::Game().FindFile(META_NAME);
707 meta.reset(new Meta(meta_file));
708
709 // Guess non-standard extensions (for the DB) before loading the encoding
710 GuessNonStandardExtensions();
711
712 GetEncoding();
713 escape_symbol = lcf::ReaderUtil::Recode("\\", encoding);
714 if (escape_symbol.empty()) {
715 Output::Error("Invalid encoding: {}.", encoding);
716 }
717 escape_char = Utils::DecodeUTF32(Player::escape_symbol).front();
718
719 // Check for translation-related directories and load language names.
720 translation.InitTranslations();
721
722 std::string game_path = FileFinder::GetFullFilesystemPath(FileFinder::Game());
723 std::string save_path = FileFinder::GetFullFilesystemPath(FileFinder::Save());
724 if (game_path == save_path) {
725 Output::DebugStr("Game and Save Directory:");
726 FileFinder::DumpFilesystem(FileFinder::Game());
727 } else {
728 Output::Debug("Game Directory:");
729 FileFinder::DumpFilesystem(FileFinder::Game());
730 Output::Debug("SaveDirectory:", save_path);
731 FileFinder::DumpFilesystem(FileFinder::Save());
732 }
733
734 LoadDatabase();
735
736 bool no_rtp_warning_flag = false;
737 { // Scope lifetime of variables for ini parsing
738 std::string ini_file = FileFinder::Game().FindFile(INI_NAME);
739
740 auto ini_stream = FileFinder::Game().OpenInputStream(ini_file, std::ios_base::in);
741 if (ini_stream) {
742 lcf::INIReader ini(ini_stream);
743 if (ini.ParseError() != -1) {
744 std::string title = ini.Get("RPG_RT", "GameTitle", GAME_TITLE);
745 game_title = lcf::ReaderUtil::Recode(title, encoding);
746 no_rtp_warning_flag = ini.Get("RPG_RT", "FullPackageFlag", "0") == "1" ? true : no_rtp_flag;
747 }
748 }
749 }
750
751 std::stringstream title;
752 if (!game_title.empty()) {
753 Output::Debug("Loading game {}", game_title);
754 title << game_title << " - ";
755 Input::AddRecordingData(Input::RecordingData::GameTitle, game_title);
756 } else {
757 Output::Debug("Could not read game title.");
758 }
759 title << GAME_TITLE;
760 DisplayUi->SetTitle(title.str());
761
762 if (no_rtp_warning_flag) {
763 Output::Debug("Game does not need RTP (FullPackageFlag=1)");
764 }
765
766 if (engine == EngineNone) {
767 if (lcf::Data::system.ldb_id == 2003) {
768 engine = EngineRpg2k3;
769
770 if (FileFinder::Game().FindFile("ultimate_rt_eb.dll").empty()) {
771 // Heuristic: Detect if game was converted from 2000 to 2003 and
772 // no typical 2003 feature was used at all (breaks .flow e.g.)
773 if (lcf::Data::classes.size() == 1 &&
774 lcf::Data::classes[0].name.empty() &&
775 lcf::Data::system.menu_commands.empty() &&
776 lcf::Data::system.system2_name.empty() &&
777 lcf::Data::battleranimations.size() == 1 &&
778 lcf::Data::battleranimations[0].name.empty()) {
779 engine = EngineRpg2k;
780 Output::Debug("Using RPG2k Interpreter (heuristic)");
781 } else {
782 Output::Debug("Using RPG2k3 Interpreter");
783 }
784 } else {
785 engine |= EngineEnglish | EngineMajorUpdated;
786 Output::Debug("Using RPG2k3 (English release, v1.11) Interpreter");
787 }
788 } else {
789 engine = EngineRpg2k;
790 Output::Debug("Using RPG2k Interpreter");
791 if (lcf::Data::data.version >= 1) {
792 engine |= EngineEnglish | EngineMajorUpdated;
793 Output::Debug("RM2k >= v.1.61 (English release) detected");
794 }
795 }
796 if (!(engine & EngineMajorUpdated)) {
797 if (FileFinder::IsMajorUpdatedTree()) {
798 engine |= EngineMajorUpdated;
799 Output::Debug("RPG2k >= v1.50 / RPG2k3 >= v1.05 detected");
800 } else {
801 Output::Debug("RPG2k < v1.50 / RPG2k3 < v1.05 detected");
802 }
803 }
804 }
805 Output::Debug("Engine configured as: 2k={} 2k3={} MajorUpdated={} Eng={}", Player::IsRPG2k(), Player::IsRPG2k3(), Player::IsMajorUpdatedVersion(), Player::IsEnglish());
806
807 Main_Data::filefinder_rtp = std::make_unique<FileFinder_RTP>(no_rtp_flag, no_rtp_warning_flag, rtp_path);
808
809 if ((patch & PatchOverride) == 0) {
810 if (!FileFinder::Game().FindFile("dynloader.dll").empty()) {
811 patch |= PatchDynRpg;
812 Output::Warning("This game uses DynRPG and will not run properly.");
813 }
814
815 if (!FileFinder::Game().FindFile("accord.dll").empty()) {
816 patch |= PatchManiac;
817 Output::Warning("This game uses the Maniac Patch and will not run properly.");
818 }
819 }
820
821 Output::Debug("Patch configuration: dynrpg={} maniac={}", Player::IsPatchDynRpg(), Player::IsPatchManiac());
822
823 // ExFont parsing
824 Cache::exfont_custom.clear();
825 // Check for bundled ExFont
826 auto exfont_stream = FileFinder::OpenImage(".", "ExFont");
827 #ifndef EMSCRIPTEN
828 if (!exfont_stream) {
829 // Attempt reading ExFont from RPG_RT.exe (not supported on Emscripten,
830 // a ExFont can be manually bundled there)
831 std::string exep = FileFinder::Game().FindFile(EXE_NAME);
832 if (!exep.empty()) {
833 auto exesp = FileFinder::Game().OpenInputStream(exep);
834 if (exesp) {
835 Output::Debug("Loading ExFont from {}", exep);
836 EXEReader exe_reader = EXEReader(exesp);
837 Cache::exfont_custom = exe_reader.GetExFont();
838 } else {
839 Output::Debug("ExFont loading failed: {} not readable", exep);
840 }
841 } else {
842 Output::Debug("ExFont loading failed: {} not found", EXE_NAME);
843 }
844 }
845 #endif
846 if (exfont_stream) {
847 Output::Debug("Using custom ExFont: {}", exfont_stream.GetName());
848 Cache::exfont_custom = Utils::ReadStream(exfont_stream);
849 }
850
851 ResetGameObjects();
852
853 Main_Data::game_ineluki->ExecuteScriptList(FileFinder::Game().FindFile("autorun.script"));
854 }
855
ResetGameObjects()856 void Player::ResetGameObjects() {
857 // The init order is important
858 Main_Data::Cleanup();
859
860 Main_Data::game_switches = std::make_unique<Game_Switches>();
861
862 auto min_var = lcf::Data::system.easyrpg_variable_min_value;
863 if (min_var == 0) {
864 min_var = Player::IsRPG2k3() ? Game_Variables::min_2k3 : Game_Variables::min_2k;
865 }
866 auto max_var = lcf::Data::system.easyrpg_variable_max_value;
867 if (max_var == 0) {
868 max_var = Player::IsRPG2k3() ? Game_Variables::max_2k3 : Game_Variables::max_2k;
869 }
870 Main_Data::game_variables = std::make_unique<Game_Variables>(min_var, max_var);
871
872 // Prevent a crash when Game_Map wants to reset the screen content
873 // because Setup() modified pictures array
874 Main_Data::game_screen = std::make_unique<Game_Screen>();
875 Main_Data::game_pictures = std::make_unique<Game_Pictures>();
876
877 Main_Data::game_actors = std::make_unique<Game_Actors>();
878
879 Game_Map::Init();
880
881 Main_Data::game_system = std::make_unique<Game_System>();
882 Main_Data::game_targets = std::make_unique<Game_Targets>();
883 Main_Data::game_enemyparty = std::make_unique<Game_EnemyParty>();
884 Main_Data::game_party = std::make_unique<Game_Party>();
885 Main_Data::game_player = std::make_unique<Game_Player>();
886 Main_Data::game_quit = std::make_unique<Game_Quit>();
887 Main_Data::game_ineluki = std::make_unique<Game_Ineluki>();
888
889 DynRpg::Reset();
890
891 Game_Clock::ResetFrame(Game_Clock::now());
892
893 Main_Data::game_system->ReloadSystemGraphic();
894
895 Input::ResetMask();
896 }
897
DefaultLmuStartFileExists(const FilesystemView & fs)898 static bool DefaultLmuStartFileExists(const FilesystemView& fs) {
899 // Compute map_id based on command line.
900 int map_id = Player::start_map_id == -1 ? lcf::Data::treemap.start.party_map_id : Player::start_map_id;
901 std::string mapName = Game_Map::ConstructMapName(map_id, false);
902
903 // Now see if the file exists.
904 return !fs.FindFile(mapName).empty();
905 }
906
GuessNonStandardExtensions()907 void Player::GuessNonStandardExtensions() {
908 // Check all conditions, but check the remap last (since it is potentially slower).
909 FileExtGuesser::RPG2KNonStandardFilenameGuesser rpg2kRemap;
910 if (!FileFinder::IsRPG2kProject(FileFinder::Game()) &&
911 !FileFinder::IsEasyRpgProject(FileFinder::Game())) {
912
913 rpg2kRemap = FileExtGuesser::GetRPG2kProjectWithRenames(FileFinder::Game());
914 if (rpg2kRemap.Empty()) {
915 // Unlikely to happen because of the game browser only launches valid games
916 Output::Error("{}\n\n{}\n\n{}\n\n{}","No valid game was found.",
917 "EasyRPG must be run from a game folder containing\nRPG_RT.ldb and RPG_RT.lmt.",
918 "This engine only supports RPG Maker 2000 and 2003\ngames.",
919 "RPG Maker XP, VX, VX Ace and MV are NOT supported.");
920 }
921 }
922
923 // At this point we haven't yet determined if this is an easyrpg project or not.
924 // There are several ways to handle this, but we just put 'is_easyrpg_project' in the header
925 // and calculate it here.
926 // Try loading EasyRPG project files first, then fallback to normal RPG Maker
927 std::string edb = FileFinder::Game().FindFile(DATABASE_NAME_EASYRPG);
928 std::string emt = FileFinder::Game().FindFile(TREEMAP_NAME_EASYRPG);
929 is_easyrpg_project = !edb.empty() && !emt.empty();
930
931 // Non-standard extensions only apply to non-EasyRPG projects
932 if (!is_easyrpg_project && !rpg2kRemap.Empty()) {
933 fileext_map = rpg2kRemap.guessExtensions(*meta);
934 } else {
935 fileext_map = FileExtGuesser::RPG2KFileExtRemap();
936 }
937 }
938
LoadDatabase()939 void Player::LoadDatabase() {
940 // Load lcf::Database
941 lcf::Data::Clear();
942
943 if (is_easyrpg_project) {
944 std::string edb = FileFinder::Game().FindFile(DATABASE_NAME_EASYRPG);
945 auto edb_stream = FileFinder::Game().OpenInputStream(edb, std::ios_base::in);
946 if (!edb_stream) {
947 Output::Error("Error loading {}", DATABASE_NAME_EASYRPG);
948 return;
949 }
950
951 auto db = lcf::LDB_Reader::LoadXml(edb_stream);
952 if (!db) {
953 Output::ErrorStr(lcf::LcfReader::GetError());
954 return;
955 } else {
956 lcf::Data::data = std::move(*db);
957 }
958
959 std::string emt = FileFinder::Game().FindFile(TREEMAP_NAME_EASYRPG);
960 auto emt_stream = FileFinder::Game().OpenInputStream(emt, std::ios_base::in);
961 if (!emt_stream) {
962 Output::Error("Error loading {}", TREEMAP_NAME_EASYRPG);
963 return;
964 }
965
966 auto treemap = lcf::LMT_Reader::LoadXml(emt_stream);
967 if (!treemap) {
968 Output::ErrorStr(lcf::LcfReader::GetError());
969 } else {
970 lcf::Data::treemap = std::move(*treemap);
971 }
972 } else {
973 // Retrieve the appropriately-renamed files.
974 std::string ldb_name = fileext_map.MakeFilename(RPG_RT_PREFIX, SUFFIX_LDB);
975 std::string ldb = FileFinder::Game().FindFile(ldb_name);
976 std::string lmt_name = fileext_map.MakeFilename(RPG_RT_PREFIX, SUFFIX_LMT);
977 std::string lmt = FileFinder::Game().FindFile(lmt_name);
978
979 auto ldb_stream = FileFinder::Game().OpenInputStream(ldb);
980 if (!ldb_stream) {
981 Output::Error("Error loading {}", ldb_name);
982 return;
983 }
984
985 auto db = lcf::LDB_Reader::Load(ldb_stream, encoding);
986 if (!db) {
987 Output::ErrorStr(lcf::LcfReader::GetError());
988 return;
989 } else {
990 lcf::Data::data = std::move(*db);
991 }
992
993 auto lmt_stream = FileFinder::Game().OpenInputStream(lmt);
994 if (!lmt_stream) {
995 Output::Error("Error loading {}", lmt_name);
996 return;
997 }
998
999 auto treemap = lcf::LMT_Reader::Load(lmt_stream, encoding);
1000 if (!treemap) {
1001 Output::ErrorStr(lcf::LcfReader::GetError());
1002 return;
1003 } else {
1004 lcf::Data::treemap = std::move(*treemap);
1005 }
1006
1007 if (Input::IsRecording()) {
1008 ldb_stream.clear();
1009 ldb_stream.seekg(0, std::ios::beg);
1010 lmt_stream.clear();
1011 lmt_stream.seekg(0, std::ios::beg);
1012 Input::AddRecordingData(Input::RecordingData::Hash,
1013 fmt::format("ldb {:#08x}", Utils::CRC32(ldb_stream)));
1014 Input::AddRecordingData(Input::RecordingData::Hash,
1015 fmt::format("lmt {:#08x}", Utils::CRC32(lmt_stream)));
1016 }
1017
1018 // Override map extension, if needed.
1019 if (!DefaultLmuStartFileExists(FileFinder::Game())) {
1020 FileExtGuesser::GuessAndAddLmuExtension(FileFinder::Game(), *meta, fileext_map);
1021 }
1022 }
1023 }
1024
OnMapSaveFileReady(FileRequestResult *,lcf::rpg::Save save)1025 static void OnMapSaveFileReady(FileRequestResult*, lcf::rpg::Save save) {
1026 auto map = Game_Map::loadMapFile(Main_Data::game_player->GetMapId());
1027 Game_Map::SetupFromSave(
1028 std::move(map),
1029 std::move(save.map_info),
1030 std::move(save.boat_location),
1031 std::move(save.ship_location),
1032 std::move(save.airship_location),
1033 std::move(save.foreground_event_execstate),
1034 std::move(save.panorama),
1035 std::move(save.common_events));
1036 }
1037
LoadSavegame(const std::string & save_name,int save_id)1038 void Player::LoadSavegame(const std::string& save_name, int save_id) {
1039 Output::Debug("Loading Save {}", save_name);
1040
1041 bool load_on_map = Scene::instance->type == Scene::Map;
1042
1043 if (!load_on_map) {
1044 Main_Data::game_system->BgmFade(800);
1045 // We erase the screen now before loading the saved game. This prevents an issue where
1046 // if the save game has a different system graphic, the load screen would change before
1047 // transitioning out.
1048 Transition::instance().InitErase(Transition::TransitionFadeOut, Scene::instance.get(), 6);
1049 }
1050
1051 auto title_scene = Scene::Find(Scene::Title);
1052 if (title_scene) {
1053 static_cast<Scene_Title*>(title_scene.get())->OnGameStart();
1054 }
1055
1056 auto save_stream = FileFinder::Save().OpenInputStream(save_name);
1057 if (!save_stream) {
1058 Output::Error("Error loading {}", save_name);
1059 return;
1060 }
1061
1062 std::unique_ptr<lcf::rpg::Save> save = lcf::LSD_Reader::Load(save_stream, encoding);
1063
1064 if (!save.get()) {
1065 Output::ErrorStr(lcf::LcfReader::GetError());
1066 return;
1067 }
1068
1069 std::stringstream verstr;
1070 int ver = save->easyrpg_data.version;
1071 if (ver == 0) {
1072 verstr << "RPG_RT or EasyRPG Player Pre-0.6.0";
1073 } else if (ver >= 10000) {
1074 verstr << "Unknown Engine";
1075 } else {
1076 verstr << "EasyRPG Player ";
1077 char verbuf[64];
1078 sprintf(verbuf, "%d.%d.%d", ver / 1000 % 10, ver / 100 % 10, ver / 10 % 10);
1079 verstr << verbuf;
1080 if (ver % 10 > 0) {
1081 verstr << "." << ver % 10;
1082 }
1083 }
1084
1085 Output::Debug("Savegame version {} ({})", ver, verstr.str());
1086
1087 if (ver > PLAYER_SAVEGAME_VERSION) {
1088 Output::Warning("This savegame was created with {} which is newer than the current version of EasyRPG Player ({})",
1089 verstr.str(), PLAYER_VERSION);
1090 }
1091
1092 // Compatibility hacks for old EasyRPG Player saves.
1093 if (save->easyrpg_data.version == 0) {
1094 // Old savegames accidentally wrote animation_type as continuous for all events.
1095 save->party_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1096 save->boat_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1097 save->ship_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1098 save->airship_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1099 }
1100
1101 if (!load_on_map) {
1102 Scene::PopUntil(Scene::Title);
1103 }
1104 Game_Map::Dispose();
1105
1106 Main_Data::game_switches->SetData(std::move(save->system.switches));
1107 Main_Data::game_variables->SetData(std::move(save->system.variables));
1108 Main_Data::game_system->SetupFromSave(std::move(save->system));
1109 Main_Data::game_actors->SetSaveData(std::move(save->actors));
1110 Main_Data::game_party->SetupFromSave(std::move(save->inventory));
1111 Main_Data::game_screen->SetSaveData(std::move(save->screen));
1112 Main_Data::game_pictures->SetSaveData(std::move(save->pictures));
1113 Main_Data::game_targets->SetSaveData(std::move(save->targets));
1114 Main_Data::game_player->SetSaveData(save->party_location);
1115
1116 int map_id = Main_Data::game_player->GetMapId();
1117
1118 FileRequestAsync* map = Game_Map::RequestMap(map_id);
1119 save_request_id = map->Bind([save=std::move(*save)](auto* request) { OnMapSaveFileReady(request, std::move(save)); });
1120 map->SetImportantFile(true);
1121
1122 Main_Data::game_system->ReloadSystemGraphic();
1123
1124 map->Start();
1125 if (!load_on_map) {
1126 Scene::Push(std::make_shared<Scene_Map>(save_id));
1127 }
1128 }
1129
OnMapFileReady(FileRequestResult *)1130 static void OnMapFileReady(FileRequestResult*) {
1131 int map_id = Player::start_map_id == -1 ?
1132 lcf::Data::treemap.start.party_map_id : Player::start_map_id;
1133 int x_pos = Player::party_x_position == -1 ?
1134 lcf::Data::treemap.start.party_x : Player::party_x_position;
1135 int y_pos = Player::party_y_position == -1 ?
1136 lcf::Data::treemap.start.party_y : Player::party_y_position;
1137 if (Player::party_members.size() > 0) {
1138 Main_Data::game_party->Clear();
1139 for (auto& member: Player::party_members) {
1140 Main_Data::game_party->AddActor(member);
1141 }
1142 }
1143
1144 Main_Data::game_player->MoveTo(map_id, x_pos, y_pos);
1145 }
1146
SetupNewGame()1147 void Player::SetupNewGame() {
1148 Main_Data::game_system->BgmFade(800, true);
1149 Main_Data::game_system->ResetFrameCounter();
1150 auto title = Scene::Find(Scene::Title);
1151 if (title) {
1152 static_cast<Scene_Title*>(title.get())->OnGameStart();
1153 }
1154
1155 Main_Data::game_system->SetAtbMode(static_cast<Game_System::AtbMode>(lcf::Data::battlecommands.easyrpg_default_atb_mode));
1156
1157 Main_Data::game_party->SetupNewGame();
1158 SetupPlayerSpawn();
1159 Scene::Push(std::make_shared<Scene_Map>(0));
1160 }
1161
SetupPlayerSpawn()1162 void Player::SetupPlayerSpawn() {
1163 int map_id = Player::start_map_id == -1 ?
1164 lcf::Data::treemap.start.party_map_id : Player::start_map_id;
1165
1166 FileRequestAsync* request = Game_Map::RequestMap(map_id);
1167 map_request_id = request->Bind(&OnMapFileReady);
1168 request->SetImportantFile(true);
1169 request->Start();
1170 }
1171
SetupBattleTest()1172 void Player::SetupBattleTest() {
1173 BattleArgs args;
1174 args.troop_id = Game_Battle::battle_test.troop_id;
1175 args.first_strike = false;
1176 args.allow_escape = true;
1177 args.background = ToString(lcf::Data::system.battletest_background);
1178 args.terrain_id = 1; //Not used in 2k, for 2k3 only used to determine grid layout if formation == terrain.
1179
1180 if (Player::IsRPG2k3()) {
1181 args.formation = Game_Battle::battle_test.formation;
1182 args.condition = Game_Battle::battle_test.condition;
1183
1184 if (args.formation == lcf::rpg::System::BattleFormation_terrain) {
1185 args.terrain_id = Game_Battle::battle_test.terrain_id;
1186 }
1187
1188 Output::Debug("BattleTest Mode 2k3 troop=({}) background=({}) formation=({}) condition=({}) terrain=({})",
1189 args.troop_id, args.background.c_str(), args.formation, args.condition, args.terrain_id);
1190 } else {
1191 Output::Debug("BattleTest Mode 2k troop=({}) background=({})", args.troop_id, args.background);
1192 }
1193
1194 auto* troop = lcf::ReaderUtil::GetElement(lcf::Data::troops, args.troop_id);
1195 if (troop == nullptr) {
1196 Output::Error("BattleTest: Invalid Monster Party ID {}", args.troop_id);
1197 }
1198
1199 if (Game_Battle::battle_test.enabled) {
1200 Main_Data::game_party->SetupBattleTest();
1201 }
1202
1203 Scene::Push(Scene_Battle::Create(std::move(args)), true);
1204 }
1205
GetEncoding()1206 std::string Player::GetEncoding() {
1207 encoding = forced_encoding;
1208
1209 // command line > ini > detection > current locale
1210 if (encoding.empty()) {
1211 std::string ini = FileFinder::Game().FindFile(INI_NAME);
1212 auto ini_stream = FileFinder::Game().OpenInputStream(ini);
1213 if (ini_stream) {
1214 encoding = lcf::ReaderUtil::GetEncoding(ini_stream);
1215 }
1216 }
1217
1218 if (encoding.empty() || encoding == "auto") {
1219 encoding = "";
1220
1221 std::string ldb = FileFinder::Game().FindFile(fileext_map.MakeFilename(RPG_RT_PREFIX, SUFFIX_LDB));
1222 auto ldb_stream = FileFinder::Game().OpenInputStream(ldb);
1223 if (ldb_stream) {
1224 auto db = lcf::LDB_Reader::Load(ldb_stream);
1225 if (db) {
1226 std::vector<std::string> encodings = lcf::ReaderUtil::DetectEncodings(*db);
1227
1228 #ifndef EMSCRIPTEN
1229 for (std::string &enc : encodings) {
1230 // Heuristic: Check title graphic, system graphic, cursor SE, title BGM
1231 // Pure ASCII is skipped as it provides no added value
1232 escape_symbol = lcf::ReaderUtil::Recode("\\", enc);
1233 if (escape_symbol.empty()) {
1234 // Bad encoding
1235 Output::Debug("Bad encoding: {}. Trying next.", enc);
1236 continue;
1237 }
1238 escape_char = Utils::DecodeUTF32(Player::escape_symbol).front();
1239
1240 const auto& title_name = db->system.title_name;
1241 const auto& system_name = db->system.system_name;
1242 const auto& cursor_se = db->system.cursor_se.name;
1243 const auto& title_music = db->system.title_music.name;
1244 int check_max = 0;
1245 int check_okay = 0;
1246
1247 if (db->system.show_title && !Utils::StringIsAscii(title_name)) {
1248 ++check_max;
1249 check_okay += FileFinder::FindImage("Title", lcf::ReaderUtil::Recode(title_name, enc)).empty() ? 0 : 1;
1250 }
1251
1252 if (!Utils::StringIsAscii(system_name)) {
1253 ++check_max;
1254 check_okay += FileFinder::FindImage("System", lcf::ReaderUtil::Recode(system_name, enc)).empty() ? 0 : 1;
1255 }
1256
1257 if (!Utils::StringIsAscii(cursor_se)) {
1258 ++check_max;
1259 check_okay += FileFinder::FindSound(lcf::ReaderUtil::Recode(cursor_se, enc)).empty() ? 0 : 1;
1260 }
1261
1262 if (db->system.show_title && !Utils::StringIsAscii(title_music)) {
1263 ++check_max;
1264 check_okay += FileFinder::FindMusic(lcf::ReaderUtil::Recode(title_music, enc)).empty() ? 0 : 1;
1265 }
1266
1267 if (check_max == check_okay) {
1268 // Looks like a good encoding
1269 encoding = enc;
1270 break;
1271 } else {
1272 Output::Debug("Detected encoding: {}. Files not found ({}/{}). Trying next.", enc, check_okay, check_max);
1273 }
1274 }
1275 #endif
1276 if (!encodings.empty() && encoding.empty()) {
1277 // No encoding found that matches the files, maybe RTP missing.
1278 // Use the first one instead
1279 encoding = encodings.front();
1280 }
1281 }
1282 }
1283
1284 escape_symbol = "";
1285 escape_char = 0;
1286
1287 if (!encoding.empty()) {
1288 Output::Debug("Detected encoding: {}", encoding);
1289 } else {
1290 Output::Debug("Encoding not detected");
1291 encoding = lcf::ReaderUtil::GetLocaleEncoding();
1292 }
1293 }
1294
1295 return encoding;
1296 }
1297
PrintVersion()1298 void Player::PrintVersion() {
1299 std::string additional(PLAYER_ADDTL);
1300 std::stringstream version;
1301
1302 version << PLAYER_VERSION;
1303
1304 if (!additional.empty())
1305 version << " " << additional;
1306
1307 std::cout << "EasyRPG Player " << version.str() << std::endl;
1308 }
1309
PrintUsage()1310 void Player::PrintUsage() {
1311 std::cout <<
1312 R"(EasyRPG Player - An open source interpreter for RPG Maker 2000/2003 games.
1313 Options:
1314 --battle-test N Start a battle test with monster party N.
1315 --disable-audio Disable audio (in case you prefer your own music).
1316 --disable-rtp Disable support for the Runtime Package (RTP).
1317 --encoding N Instead of auto detecting the encoding or using
1318 the one in RPG_RT.ini, the encoding N is used.
1319 Use "auto" for automatic detection.
1320 --engine ENGINE Disable auto detection of the simulated engine.
1321 Possible options:
1322 rpg2k - RPG Maker 2000 engine (v1.00 - v1.10)
1323 rpg2kv150 - RPG Maker 2000 engine (v1.50 - v1.51)
1324 rpg2ke - RPG Maker 2000 (English release) engine (v1.61)
1325 rpg2k3 - RPG Maker 2003 engine (v1.00 - v1.04)
1326 rpg2k3v105 - RPG Maker 2003 engine (v1.05 - v1.09a)
1327 rpg2k3e - RPG Maker 2003 (English release) engine
1328 --fullscreen Start in fullscreen mode.
1329 --show-fps Enable frames per second counter.
1330 --fps-render-window Render the frames per second counter in windowed mode.
1331 --fps-limit Set a custom frames per second limit. The default is 60 FPS.
1332 Set to 0 to run with unlimited frames per second.
1333 This option is not supported on all platforms.
1334 --no-vsync Disable vertical sync and use fps-limit. Even without
1335 this option, vsync may not be supported on all platforms.
1336 --enable-mouse Use mouse click for decision and scroll wheel for lists
1337 --enable-touch Use one/two finger tap for decision/cancel
1338 --hide-title Hide the title background image and center the
1339 command menu.
1340 --load-game-id N Skip the title scene and load SaveN.lsd
1341 (N is padded to two digits).
1342 --new-game Skip the title scene and start a new game directly.
1343 --project-path PATH Instead of using the working directory the game in
1344 PATH is used.
1345 --record-input PATH Record all button input to a log file at PATH.
1346 --replay-input PATH Replays button presses from an input log generated by
1347 --record-input.
1348 --save-path PATH Instead of storing save files in the game directory
1349 they are stored in PATH. The directory must exist.
1350 When using the game browser all games will share
1351 the same save directory!
1352 --seed N Seeds the random number generator with N.
1353 --start-map-id N Overwrite the map used for new games and use.
1354 MapN.lmu instead (N is padded to four digits).
1355 Incompatible with --load-game-id.
1356 --autobattle-algo A Which AutoBattle algorithm to use.
1357 Possible options:
1358 RPG_RT - The default RPG_RT compatible algo, including RPG_RT bugs
1359 RPG_RT+ - The default RPG_RT compatible algo, with bug fixes
1360 ATTACK - RPG_RT+ but only physical attacks, no skills
1361 --enemyai-algo A Which EnemyAI algorithm to use.
1362 Possible options:
1363 RPG_RT - The default RPG_RT compatible algo, including RPG_RT bugs
1364 RPG_RT+ - The default RPG_RT compatible algo, with bug fixes
1365 --patch PATCH... Force emulation of engine patches, disabling auto-detection.
1366 Possible options:
1367 none - Disable all patches
1368 dynrpg - DynRPG patch by Cherry
1369 maniac - Maniac Patch by BingShan
1370 --start-position X Y Overwrite the party start position and move the
1371 party to position (X, Y).
1372 Incompatible with --load-game-id.
1373 --start-party A B... Overwrite the starting party members with the actors
1374 with IDs A, B, C...
1375 Incompatible with --load-game-id.
1376 --test-play Enable TestPlay mode.
1377 --window Start in window mode.
1378 -v, --version Display program version and exit.
1379 -h, --help Display this help and exit.
1380
1381 For compatibility with the legacy RPG Maker runtime the following arguments
1382 are supported:
1383 BattleTest N Same as --battle-test. When N is not a valid number
1384 the 4th argument is used as the party id.
1385 HideTitle Same as --hide-title.
1386 TestPlay Same as --test-play.
1387 Window Same as --window.
1388
1389 Game related parameters (e.g. new-game and load-game-id) don't work correctly when the
1390 startup directory does not contain a valid game (and the game browser loads)
1391
1392 Alex, EV0001 and the EasyRPG authors wish you a lot of fun!)" << std::endl;
1393 }
1394
IsCP932()1395 bool Player::IsCP932() {
1396 return (encoding == "ibm-943_P15A-2003" || encoding == "932");
1397 }
1398
IsCP949()1399 bool Player::IsCP949() {
1400 return (encoding == "windows-949-2000" ||
1401 encoding == "949");
1402 }
1403
IsBig5()1404 bool Player::IsBig5() {
1405 return (encoding == "Big5" || encoding == "950");
1406 }
1407
IsCP936()1408 bool Player::IsCP936() {
1409 return (encoding == "windows-936-2000" ||
1410 encoding == "936");
1411 }
1412
IsCJK()1413 bool Player::IsCJK() {
1414 return (IsCP932() || IsCP949() || IsBig5() || IsCP936());
1415 }
1416
IsCP1251()1417 bool Player::IsCP1251() {
1418 return (encoding == "ibm-5347_P100-1998" ||
1419 encoding == "windows-1251" || encoding == "1251");
1420 }
1421
EngineVersion()1422 int Player::EngineVersion() {
1423 if (IsRPG2k3()) return 2003;
1424 if (IsRPG2k()) return 2000;
1425 return 0;
1426 }
1427
GetEngineVersion()1428 std::string Player::GetEngineVersion() {
1429 if (EngineVersion() > 0) return std::to_string(EngineVersion());
1430 return std::string();
1431 }
1432
1433