diff --git a/src/async_op.h b/src/async_op.h index 9230fb8eae..2d87f32b32 100644 --- a/src/async_op.h +++ b/src/async_op.h @@ -39,6 +39,8 @@ class AsyncOp { eToTitle, eExitGame, eTerminateBattle, + eSave, + eLoad }; AsyncOp() = default; @@ -64,6 +66,12 @@ class AsyncOp { /** @return a TerminateBattle async operation */ static AsyncOp MakeTerminateBattle(int result); + /** @return a Save async operation */ + static AsyncOp MakeSave(int save_slot, int save_result_var); + + /** @return a Load async operation */ + static AsyncOp MakeLoad(int save_slot); + /** @return the type of async operation */ Type GetType() const; @@ -100,6 +108,18 @@ class AsyncOp { **/ int GetBattleResult() const; + /** + * @return the desired slot to save or load + * @pre If GetType() is not eSave or eLoad, the return value is undefined. + **/ + int GetSaveSlot() const; + + /** + * @return the variable to set to 1 when the save was a success. + * @pre If GetType() is not eSave, the return value is undefined. + **/ + int GetSaveResultVar() const; + private: Type _type = eNone; int _args[3] = {}; @@ -142,6 +162,17 @@ inline int AsyncOp::GetBattleResult() const { return _args[0]; } +inline int AsyncOp::GetSaveSlot() const { + assert(GetType() == eSave || GetType() == eLoad); + return _args[0]; +} + +inline int AsyncOp::GetSaveResultVar() const { + assert(GetType() == eSave); + return _args[1]; +} + + template inline AsyncOp::AsyncOp(Type type, Args&&... args) : _type(type), _args{std::forward(args)...} @@ -175,5 +206,13 @@ inline AsyncOp AsyncOp::MakeTerminateBattle(int transition_type) { return AsyncOp(eTerminateBattle, transition_type); } +inline AsyncOp AsyncOp::MakeSave(int save_slot, int save_result_var) { + return AsyncOp(eSave, save_slot, save_result_var); +} + +inline AsyncOp AsyncOp::MakeLoad(int save_slot) { + return AsyncOp(eLoad, save_slot); +} + #endif diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 38405cbb36..71052122a5 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "game_interpreter.h" #include "audio.h" #include "dynrpg.h" @@ -43,6 +44,7 @@ #include "sprite_character.h" #include "scene_gameover.h" #include "scene_map.h" +#include "scene_save.h" #include "scene.h" #include "game_clock.h" #include "input.h" @@ -51,6 +53,7 @@ #include "player.h" #include "util_macro.h" #include +#include #include "game_battle.h" #include "utils.h" #include "transition.h" @@ -121,6 +124,20 @@ void Game_Interpreter::Push( void Game_Interpreter::KeyInputState::fromSave(const lcf::rpg::SaveEventExecState& save) { *this = {}; + // Maniac Patch aware check functions for parameters that handle + // keyboard and mouse through a bitmask + bool is_maniac = Player::IsPatchManiac(); + auto check_key = [&](auto& var) { + if (is_maniac) { + return (var & 1) != 0; + } else { + return var != 0; + } + }; + auto check_mouse = [&](auto& var) { + return (var & 2) != 0; + }; + wait = save.keyinput_wait; // FIXME: There is an RPG_RT bug where keyinput_variable is uint8_t // which we currently have to emulate. So the value from the save could be wrong. @@ -133,27 +150,35 @@ void Game_Interpreter::KeyInputState::fromSave(const lcf::rpg::SaveEventExecStat keys[Keys::eUp] = true; } else { if (Player::IsRPG2k3()) { - keys[Keys::eDown] = save.keyinput_2k3down; - keys[Keys::eLeft] = save.keyinput_2k3left; - keys[Keys::eRight] = save.keyinput_2k3right; - keys[Keys::eUp] = save.keyinput_2k3up; + keys[Keys::eDown] = check_key(save.keyinput_2k3down); + keys[Keys::eLeft] = check_key(save.keyinput_2k3left); + keys[Keys::eRight] = check_key(save.keyinput_2k3right); + keys[Keys::eUp] = check_key(save.keyinput_2k3up); } else { - keys[Keys::eDown] = save.keyinput_2kdown_2k3operators ; - keys[Keys::eLeft] = save.keyinput_2kleft_2k3shift; - keys[Keys::eRight] = save.keyinput_2kright; - keys[Keys::eUp] = save.keyinput_2kup; + keys[Keys::eDown] = check_key(save.keyinput_2kdown_2k3operators); + keys[Keys::eLeft] = check_key(save.keyinput_2kleft_2k3shift); + keys[Keys::eRight] = check_key(save.keyinput_2kright); + keys[Keys::eUp] = check_key(save.keyinput_2kup); } } - keys[Keys::eDecision] = save.keyinput_decision; - keys[Keys::eCancel] = save.keyinput_cancel; + keys[Keys::eDecision] = check_key(save.keyinput_decision); + keys[Keys::eCancel] = check_key(save.keyinput_cancel); if (Player::IsRPG2k3()) { - keys[Keys::eShift] = save.keyinput_2kleft_2k3shift; - keys[Keys::eNumbers] = save.keyinput_2kshift_2k3numbers; - keys[Keys::eOperators] = save.keyinput_2kdown_2k3operators; + keys[Keys::eShift] = check_key(save.keyinput_2kleft_2k3shift); + keys[Keys::eNumbers] = check_key(save.keyinput_2kshift_2k3numbers); + keys[Keys::eOperators] = check_key(save.keyinput_2kdown_2k3operators); + + if (is_maniac) { + keys[Keys::eMouseLeft] = check_mouse(save.keyinput_decision); + keys[Keys::eMouseRight] = check_mouse(save.keyinput_cancel); + keys[Keys::eMouseMiddle] = check_mouse(save.keyinput_2kleft_2k3shift); + keys[Keys::eMouseScrollUp] = check_mouse(save.keyinput_2k3up); + keys[Keys::eMouseScrollDown] = check_mouse(save.keyinput_2k3down); + } } else { - keys[Keys::eShift] = save.keyinput_2kshift_2k3numbers; + keys[Keys::eShift] = check_key(save.keyinput_2kshift_2k3numbers); } time_variable = save.keyinput_time_variable; @@ -212,6 +237,24 @@ void Game_Interpreter::KeyInputState::toSave(lcf::rpg::SaveEventExecState& save) save.keyinput_2kleft_2k3shift = keys[Keys::eShift]; save.keyinput_2kshift_2k3numbers = keys[Keys::eNumbers]; save.keyinput_2kdown_2k3operators = keys[Keys::eOperators]; + + if (Player::IsPatchManiac()) { + if (keys[Keys::eMouseLeft]) { + save.keyinput_decision |= 2; + } + if (keys[Keys::eMouseRight]) { + save.keyinput_cancel |= 2; + } + if (keys[Keys::eMouseMiddle]) { + save.keyinput_2kleft_2k3shift |= 2; + } + if (keys[Keys::eMouseScrollUp]) { + save.keyinput_2k3up |= 2; + } + if (keys[Keys::eMouseScrollDown]) { + save.keyinput_2k3down |= 2; + } + } } else { save.keyinput_2kshift_2k3numbers = keys[Keys::eShift]; } @@ -1154,6 +1197,7 @@ bool Game_Interpreter::CommandControlVariables(lcf::rpg::EventCommand const& com value = Main_Data::game_party->GetGold(); break; case 1: + // Timer 1 remaining time value = Main_Data::game_party->GetTimerSeconds(Main_Data::game_party->Timer1); break; case 2: @@ -1185,8 +1229,38 @@ bool Game_Interpreter::CommandControlVariables(lcf::rpg::EventCommand const& com value = Main_Data::game_ineluki->GetMidiTicks(); break; case 9: + // Timer 2 remaining time value = Main_Data::game_party->GetTimerSeconds(Main_Data::game_party->Timer2); break; + case 10: + // Current date (YYMMDD) + if (Player::IsPatchManiac()) { + std::time_t t = std::time(nullptr); + std::tm* tm = std::localtime(&t); + value = atoi(Utils::FormatDate(tm, Utils::DateFormat_YYMMDD).c_str()); + } + break; + case 11: + // Current time (HHMMSS) + if (Player::IsPatchManiac()) { + std::time_t t = std::time(nullptr); + std::tm* tm = std::localtime(&t); + value = atoi(Utils::FormatDate(tm, Utils::DateFormat_HHMMSS).c_str()); + } + break; + case 12: + // Frames + if (Player::IsPatchManiac()) { + value = Main_Data::game_system->GetFrameCounter(); + } + break; + case 13: + // Patch version + if (Player::IsPatchManiac()) { + // Latest version before the engine rewrite + value = 200128; + } + break; } break; case 8: @@ -1608,14 +1682,25 @@ bool Game_Interpreter::CommandChangeLevel(lcf::rpg::EventCommand const& com) { / } int Game_Interpreter::ValueOrVariable(int mode, int val) { - switch (mode) { - case 0: - return val; - case 1: - return Main_Data::game_variables->Get(val); - default: - return -1; - } + if (mode == 0) { + return val; + } else if (mode == 1) { + return Main_Data::game_variables->Get(val); + } else if (Player::IsPatchManiac()) { + // Maniac Patch does not implement all modes for all commands + // For simplicity it is enabled for all here + if (mode == 2) { + // Variable indirect + return Main_Data::game_variables->Get(Main_Data::game_variables->Get(val)); + } else if (mode == 3) { + // Switch (F = 0, T = 1) + return Main_Data::game_switches->Get(val) ? 1 : 0; + } else if (mode == 4) { + // Switch through Variable indirect + return Main_Data::game_switches->Get(Main_Data::game_variables->Get(val)) ? 1 : 0; + } + } + return -1; } bool Game_Interpreter::CommandChangeParameters(lcf::rpg::EventCommand const& com) { // Code 10430 @@ -2782,10 +2867,47 @@ bool Game_Interpreter::CommandPlayMemorizedBGM(lcf::rpg::EventCommand const& /* return true; } - int Game_Interpreter::KeyInputState::CheckInput() const { auto check = wait ? Input::IsTriggered : Input::IsPressed; +#if defined(USE_MOUSE) && defined(SUPPORT_MOUSE) + // FIXME: Refactor input system to make mouse buttons individual keys + auto check_raw = wait ? Input::IsRawKeyTriggered : Input::IsRawKeyPressed; + + // Mouse buttons checked first (Maniac checks them last) to prevent conflict + // with DECISION that is mapped to MOUSE_LEFT + // The order of checking matches the Maniac behaviour + if (keys[Keys::eMouseScrollDown]) { + if (check_raw(Input::Keys::MOUSE_SCROLLDOWN)) { + return 1001; + } + } + + if (keys[Keys::eMouseScrollUp]) { + if (check_raw(Input::Keys::MOUSE_SCROLLUP)) { + return 1004; + } + } + + if (keys[Keys::eMouseMiddle]) { + if (check_raw(Input::Keys::MOUSE_MIDDLE)) { + return 1007; + } + } + + if (keys[Keys::eMouseRight]) { + if (check_raw(Input::Keys::MOUSE_RIGHT)) { + return 1006; + } + } + + if (keys[Keys::eMouseLeft]) { + if (check_raw(Input::Keys::MOUSE_LEFT)) { + return 1005; + } + } +#endif + // RPG processes keys from highest variable value to lowest. if (keys[Keys::eOperators]) { for (int i = 5; i > 0;) { @@ -2847,11 +2969,25 @@ bool Game_Interpreter::CommandKeyInputProc(lcf::rpg::EventCommand const& com) { _keyinput.wait = wait; _keyinput.variable = var_id; - _keyinput.keys[Keys::eDecision] = com.parameters[3] != 0; - _keyinput.keys[Keys::eCancel] = com.parameters[4] != 0; - const size_t param_size = com.parameters.size(); + // Maniac Patch aware check functions for parameters that handle + // keyboard and mouse through a bitmask + bool is_maniac = Player::IsPatchManiac(); + auto check_key = [&](auto idx, bool handle_maniac = false) { + if (param_size <= idx) { + return false; + } + if (handle_maniac && is_maniac) { + return (com.parameters[idx] & 1) != 0; + } else { + return com.parameters[idx] != 0; + } + }; + + _keyinput.keys[Keys::eDecision] = check_key(3, true); + _keyinput.keys[Keys::eCancel] = check_key(4, true); + // All engines support older versions of the command depending on the // length of the parameter list if (Player::IsRPG2k()) { @@ -2866,10 +3002,10 @@ bool Game_Interpreter::CommandKeyInputProc(lcf::rpg::EventCommand const& com) { } else { // For Rpg2k >=1.50 _keyinput.keys[Keys::eShift] = com.parameters[5] != 0; - _keyinput.keys[Keys::eDown] = param_size > 6 ? com.parameters[6] != 0 : false; - _keyinput.keys[Keys::eLeft] = param_size > 7 ? com.parameters[7] != 0 : false; - _keyinput.keys[Keys::eRight] = param_size > 8 ? com.parameters[8] != 0 : false; - _keyinput.keys[Keys::eUp] = param_size > 9 ? com.parameters[9] != 0 : false; + _keyinput.keys[Keys::eDown] = check_key(6); + _keyinput.keys[Keys::eLeft] = check_key(7); + _keyinput.keys[Keys::eRight] = check_key(8); + _keyinput.keys[Keys::eUp] = check_key(9); } } else { if (param_size != 10 || Player::IsRPG2k3Legacy()) { @@ -2880,17 +3016,19 @@ bool Game_Interpreter::CommandKeyInputProc(lcf::rpg::EventCommand const& com) { _keyinput.keys[Keys::eRight] = true; _keyinput.keys[Keys::eUp] = true; } - _keyinput.keys[Keys::eNumbers] = param_size > 5 ? com.parameters[5] != 0 : false; - _keyinput.keys[Keys::eOperators] = param_size > 6 ? com.parameters[6] != 0 : false; - _keyinput.time_variable = param_size > 7 ? com.parameters[7] : false; - _keyinput.timed = param_size > 8 ? com.parameters[8] != 0 : false; + _keyinput.keys[Keys::eNumbers] = check_key(5); + _keyinput.keys[Keys::eOperators] = check_key(6); + _keyinput.time_variable = param_size > 7 ? com.parameters[7] : 0; // Attention: int, not bool + _keyinput.timed = check_key(8); if (param_size > 10 && Player::IsMajorUpdatedVersion()) { // For Rpg2k3 >=1.05 - _keyinput.keys[Keys::eShift] = com.parameters[9] != 0; - _keyinput.keys[Keys::eDown] = com.parameters[10] != 0; - _keyinput.keys[Keys::eLeft] = param_size > 11 ? com.parameters[11] != 0 : false; - _keyinput.keys[Keys::eRight] = param_size > 12 ? com.parameters[12] != 0 : false; - _keyinput.keys[Keys::eUp] = param_size > 13 ? com.parameters[13] != 0 : false; + // ManiacPatch Middle & Wheel only handled for 2k3 Major Updated, + // the only version that has this patch + _keyinput.keys[Keys::eShift] = check_key(9, true); + _keyinput.keys[Keys::eDown] = check_key(10, true); + _keyinput.keys[Keys::eLeft] = check_key(11); + _keyinput.keys[Keys::eRight] = check_key(12); + _keyinput.keys[Keys::eUp] = check_key(13, true); } } else { // Since RPG2k3 1.05 @@ -2903,6 +3041,29 @@ bool Game_Interpreter::CommandKeyInputProc(lcf::rpg::EventCommand const& com) { } } + if (is_maniac) { + auto check_mouse = [&](auto idx) { + if (param_size <= idx) { + return false; + } + + bool result = (com.parameters[idx] & 2) != 0; +#if !defined(USE_MOUSE) || !defined(SUPPORT_MOUSE) + if (result) { + Output::Warning("ManiacPatch: Mouse input is not supported on this platform"); + return false; + } +#else + return result; +#endif + }; + _keyinput.keys[Keys::eMouseLeft] = check_mouse(3); + _keyinput.keys[Keys::eMouseRight] = check_mouse(4); + _keyinput.keys[Keys::eMouseMiddle] = check_mouse(9); + _keyinput.keys[Keys::eMouseScrollDown] = check_mouse(10); + _keyinput.keys[Keys::eMouseScrollUp] = check_mouse(13); + } + if (_keyinput.wait) { // RPG_RT will reset all trigger key states when a waiting key input proc command is executed, // which means we always wait at least 1 frame to continue. Keys which are held down are not reset. @@ -3456,39 +3617,130 @@ bool Game_Interpreter::CommandToggleFullscreen(lcf::rpg::EventCommand const& /* return true; } -bool Game_Interpreter::CommandManiacGetSaveInfo(lcf::rpg::EventCommand const&) { +bool Game_Interpreter::CommandManiacGetSaveInfo(lcf::rpg::EventCommand const& com) { if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command GetSaveInfo not supported"); - return true; -} + int save_number = ValueOrVariable(com.parameters[0], com.parameters[1]); -bool Game_Interpreter::CommandManiacSave(lcf::rpg::EventCommand const&) { - if (!Player::IsPatchManiac()) { + // Error case, set to YYMMDD later on success + Main_Data::game_variables->Set(com.parameters[2], 0); + + if (save_number <= 0) { + Output::Debug("ManiacGetSaveInfo: Invalid save number {}", save_number); return true; } - Output::Warning("Maniac Patch: Command Save not supported"); + auto savefs = FileFinder::Save(); + std::string save_name = Scene_Save::GetSaveFilename(savefs, save_number); + auto save = lcf::LSD_Reader::Load(save_name, Player::encoding); + + if (!save) { + Output::Debug("ManiacGetSaveInfo: Save not found {}", save_number); + return true; + } + + std::time_t t = lcf::LSD_Reader::ToUnixTimestamp(save->title.timestamp); + std::tm* tm = std::gmtime(&t); + + Main_Data::game_variables->Set(com.parameters[2], atoi(Utils::FormatDate(tm, Utils::DateFormat_YYMMDD).c_str())); + Main_Data::game_variables->Set(com.parameters[3], atoi(Utils::FormatDate(tm, Utils::DateFormat_HHMMSS).c_str())); + Main_Data::game_variables->Set(com.parameters[4], save->title.hero_level); + Main_Data::game_variables->Set(com.parameters[5], save->title.hero_hp); + Game_Map::SetNeedRefresh(true); + + auto face_ids = Utils::MakeArray(save->title.face1_id, save->title.face2_id, save->title.face3_id, save->title.face4_id); + auto face_names = Utils::MakeArray(save->title.face1_name, save->title.face2_name, save->title.face3_name, save->title.face4_name); + + for (int i = 0; i <= 3; ++i) { + const int param = 8 + i; + + int pic_id = ValueOrVariable(com.parameters[7], com.parameters[param]); + if (pic_id <= 0) { + continue; + } + + if (face_names[i].empty()) { + Main_Data::game_pictures->Erase(pic_id); + continue; + } + + // When the picture exists: Data is reused and effects finish immediately + // When not: Default data is used + // New features (spritesheets etc.) are always set due to how the patch works + // We are incompatible here and only set name and spritesheet and reuse stuff like the layer + Game_Pictures::ShowParams params; + auto& pic = Main_Data::game_pictures->GetPicture(pic_id); + if (pic.Exists()) { + params = pic.GetShowParams(); + } else { + params.map_layer = 7; + params.battle_layer = 7; + } + params.name = FileFinder::MakePath("..\\FaceSet", face_names[i]); + params.spritesheet_cols = 4; + params.spritesheet_rows = 4; + params.spritesheet_frame = face_ids[i]; + params.spritesheet_speed = 0; + Main_Data::game_pictures->Show(pic_id, params); + } + return true; } -bool Game_Interpreter::CommandManiacLoad(lcf::rpg::EventCommand const&) { +bool Game_Interpreter::CommandManiacSave(lcf::rpg::EventCommand const& com) { if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command Load not supported"); + int slot = ValueOrVariable(com.parameters[0], com.parameters[1]); + if (slot <= 0) { + Output::Debug("ManiacSave: Invalid save slot {}", slot); + return true; + } + + int out_var = com.parameters[2] != 0 ? com.parameters[3] : -1; + + // Maniac Patch saves directly and game data could be in an undefined state + // We yield first to the Update loop and then do a save. + _async_op = AsyncOp::MakeSave(slot, out_var); + return true; } -bool Game_Interpreter::CommandManiacEndLoadProcess(lcf::rpg::EventCommand const&) { +bool Game_Interpreter::CommandManiacLoad(lcf::rpg::EventCommand const& com) { if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command EndLoadProcess not supported"); + int slot = ValueOrVariable(com.parameters[0], com.parameters[1]); + if (slot <= 0) { + Output::Debug("ManiacLoad: Invalid save slot {}", slot); + return true; + } + + // Not implemented (kinda useless feature): + // When com.parameters[2] is 1 the check whether the file exists is skipped + // When skipped and missing RPG_RT will crash + auto savefs = FileFinder::Save(); + std::string save_name = Scene_Save::GetSaveFilename(savefs, slot); + auto save = lcf::LSD_Reader::Load(save_name, Player::encoding); + + if (!save) { + Output::Debug("ManiacLoad: Save not found {}", slot); + return true; + } + + // FIXME: In Maniac the load causes a blackscreen followed by a fade-in that can be cancelled by a transition event + // This is not implemented yet, the loading is instant without fading + _async_op = AsyncOp::MakeLoad(slot); + + return true; +} + +bool Game_Interpreter::CommandManiacEndLoadProcess(lcf::rpg::EventCommand const&) { + // no-op return true; } diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 897a1e1b9f..63aeec8ae8 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -294,7 +294,13 @@ class Game_Interpreter eCancel, eShift, eNumbers, - eOperators + eOperators, + // ManiacPatch + eMouseLeft, + eMouseRight, + eMouseMiddle, + eMouseScrollDown, + eMouseScrollUp }; struct KeyInputState { diff --git a/src/game_pictures.cpp b/src/game_pictures.cpp index 136cb8477c..44820ff96b 100644 --- a/src/game_pictures.cpp +++ b/src/game_pictures.cpp @@ -299,6 +299,11 @@ void Game_Pictures::Erase(int id) { } } +bool Game_Pictures::Picture::Exists() const { + // Incompatible with the Yume2kki edge-case that uses empty filenames + return !data.name.empty(); +} + void Game_Pictures::RequestPictureSprite(Picture& pic) { const auto& name = pic.data.name; if (name.empty()) { @@ -450,6 +455,35 @@ void Game_Pictures::Update(bool is_battle) { } } +Game_Pictures::ShowParams Game_Pictures::Picture::GetShowParams() const { + Game_Pictures::ShowParams params; + params.position_x = static_cast(data.finish_x); + params.position_y = static_cast(data.finish_y); + params.magnify = data.finish_magnify; + params.top_trans = data.finish_top_trans; + params.bottom_trans = data.finish_bot_trans; + params.red = data.finish_red; + params.green = data.finish_green; + params.blue = data.finish_blue; + params.saturation = data.finish_sat; + params.effect_mode = data.effect_mode; + params.effect_power = data.finish_effect_power; + params.name = data.name; + params.spritesheet_cols = data.spritesheet_cols; + params.spritesheet_rows = data.spritesheet_rows; + params.spritesheet_frame = data.spritesheet_frame; + params.spritesheet_speed = data.spritesheet_speed; + params.map_layer = data.map_layer; + params.battle_layer = data.battle_layer; + for (size_t i = 0; i < data.flags.flags.size(); ++i) { + params.flags |= data.flags.flags[i] << i; + } + params.spritesheet_play_once = data.spritesheet_play_once; + params.use_transparent_color = data.use_transparent_color; + params.fixed_to_map = data.fixed_to_map; + return params; +} + void Game_Pictures::Picture::SetNonEffectParams(const Params& params, bool set_positions) { if (set_positions) { data.finish_x = params.position_x; diff --git a/src/game_pictures.h b/src/game_pictures.h index 0889a5f2d0..5dd4f09a02 100644 --- a/src/game_pictures.h +++ b/src/game_pictures.h @@ -43,17 +43,17 @@ class Game_Pictures { static int GetDefaultNumberOfPictures(); struct Params { - int position_x; - int position_y; - int magnify; - int top_trans; - int bottom_trans; - int red; - int green; - int blue; - int saturation; - int effect_mode; - int effect_power; + int position_x = 0; + int position_y = 0; + int magnify = 100; + int top_trans = 0; + int bottom_trans = 0; + int red = 100; + int green = 100; + int blue = 100; + int saturation = 100; + int effect_mode = 0; + int effect_power = 0; }; struct ShowParams : Params { std::string name; @@ -72,7 +72,7 @@ class Game_Pictures { }; struct MoveParams : Params { - int duration; + int duration = 0; }; void Show(int id, const ShowParams& params); @@ -100,11 +100,13 @@ class Game_Pictures { bool IsOnBattle() const; int NumSpriteSheetFrames() const; + ShowParams GetShowParams() const; void SetNonEffectParams(const Params& params, bool set_positions); bool Show(const ShowParams& params); void Move(const MoveParams& params); void Erase(); + bool Exists() const; void OnPictureSpriteReady(); void OnMapScrolled(int dx, int dy); diff --git a/src/output.cpp b/src/output.cpp index 3db39b6365..694d0c1d5d 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -86,9 +86,7 @@ namespace { init = true; } std::time_t t = std::time(nullptr); - char timestr[100]; - strftime(timestr, 100, "[%Y-%m-%d %H:%M:%S] ", std::localtime(&t)); - return LOG_FILE << timestr; + return LOG_FILE << Utils::FormatDate(std::localtime(&t), "[%Y-%m-%d %H:%M:%S] "); } bool ignore_pause = false; diff --git a/src/player.cpp b/src/player.cpp index 342af8f2d8..01deb20d2d 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1037,12 +1037,16 @@ static void OnMapSaveFileReady(FileRequestResult*, lcf::rpg::Save save) { void Player::LoadSavegame(const std::string& save_name, int save_id) { Output::Debug("Loading Save {}", save_name); - Main_Data::game_system->BgmFade(800); - // We erase the screen now before loading the saved game. This prevents an issue where - // if the save game has a different system graphic, the load screen would change before - // transitioning out. - Transition::instance().InitErase(Transition::TransitionFadeOut, Scene::instance.get(), 6); + bool load_on_map = Scene::instance->type == Scene::Map; + + if (!load_on_map) { + Main_Data::game_system->BgmFade(800); + // We erase the screen now before loading the saved game. This prevents an issue where + // if the save game has a different system graphic, the load screen would change before + // transitioning out. + Transition::instance().InitErase(Transition::TransitionFadeOut, Scene::instance.get(), 6); + } auto title_scene = Scene::Find(Scene::Title); if (title_scene) { @@ -1094,7 +1098,9 @@ void Player::LoadSavegame(const std::string& save_name, int save_id) { save->airship_location.animation_type = Game_Character::AnimType::AnimType_non_continuous; } - Scene::PopUntil(Scene::Title); + if (!load_on_map) { + Scene::PopUntil(Scene::Title); + } Game_Map::Dispose(); Main_Data::game_switches->SetData(std::move(save->system.switches)); @@ -1116,7 +1122,9 @@ void Player::LoadSavegame(const std::string& save_name, int save_id) { Main_Data::game_system->ReloadSystemGraphic(); map->Start(); - Scene::Push(std::make_shared(save_id)); + if (!load_on_map) { + Scene::Push(std::make_shared(save_id)); + } } static void OnMapFileReady(FileRequestResult*) { diff --git a/src/scene_map.cpp b/src/scene_map.cpp index 6218cfb8bf..aba1fadb24 100644 --- a/src/scene_map.cpp +++ b/src/scene_map.cpp @@ -29,7 +29,9 @@ #include "game_system.h" #include "game_screen.h" #include "game_pictures.h" +#include "game_variables.h" #include +#include #include "player.h" #include "transition.h" #include "audio.h" @@ -391,6 +393,21 @@ void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) { return; } + if (aop.GetType() == AsyncOp::eSave) { + auto savefs = FileFinder::Save(); + bool success = Scene_Save::Save(savefs, aop.GetSaveSlot()); + if (aop.GetSaveResultVar() > 0) { + Main_Data::game_variables->Set(aop.GetSaveResultVar(), success ? 1 : 0); + Game_Map::SetNeedRefresh(true); + } + } + + if (aop.GetType() == AsyncOp::eLoad) { + auto savefs = FileFinder::Save(); + std::string save_name = Scene_Save::GetSaveFilename(savefs, aop.GetSaveSlot()); + Player::LoadSavegame(save_name, aop.GetSaveSlot()); + } + auto& transition = Transition::instance(); if (aop.GetType() == AsyncOp::eEraseScreen) { diff --git a/src/scene_save.cpp b/src/scene_save.cpp index e5eb54d0b3..ca470de009 100644 --- a/src/scene_save.cpp +++ b/src/scene_save.cpp @@ -65,8 +65,6 @@ void Scene_Save::Action(int index) { std::string Scene_Save::GetSaveFilename(const FilesystemView& fs, int slot_id) { const auto save_file = fmt::format("Save{:02d}.lsd", slot_id); - Output::Debug("Saving to {}", save_file); - std::string filename = fs.FindFile(save_file); if (filename.empty()) { @@ -75,20 +73,21 @@ std::string Scene_Save::GetSaveFilename(const FilesystemView& fs, int slot_id) { return filename; } -void Scene_Save::Save(const FilesystemView& fs, int slot_id, bool prepare_save) { +bool Scene_Save::Save(const FilesystemView& fs, int slot_id, bool prepare_save) { const auto filename = GetSaveFilename(fs, slot_id); - + Output::Debug("Saving to {}", filename); + auto save_stream = FileFinder::Save().OpenOutputStream(filename); + if (!save_stream) { Output::Warning("Failed saving to {}", filename); - return; + return false; } - Save(save_stream, slot_id, prepare_save); + return Save(save_stream, slot_id, prepare_save); } -void Scene_Save::Save(std::ostream& os, int slot_id, bool prepare_save) { - +bool Scene_Save::Save(std::ostream& os, int slot_id, bool prepare_save) { lcf::rpg::Save save; auto& title = save.title; // TODO: Maybe find a better place to setup the save file? @@ -148,7 +147,7 @@ void Scene_Save::Save(std::ostream& os, int slot_id, bool prepare_save) { } } auto lcf_engine = Player::IsRPG2k3() ? lcf::EngineVersion::e2k3 : lcf::EngineVersion::e2k; - lcf::LSD_Reader::Save(os, save, lcf_engine, Player::encoding); + bool res = lcf::LSD_Reader::Save(os, save, lcf_engine, Player::encoding); DynRpg::Save(slot_id); @@ -160,6 +159,7 @@ void Scene_Save::Save(std::ostream& os, int slot_id, bool prepare_save) { }); #endif + return res; } bool Scene_Save::IsSlotValid(int) { diff --git a/src/scene_save.h b/src/scene_save.h index 0e151f3ed0..9fa5b2adc7 100644 --- a/src/scene_save.h +++ b/src/scene_save.h @@ -40,8 +40,8 @@ class Scene_Save : public Scene_File { bool IsSlotValid(int index) override; static std::string GetSaveFilename(const FilesystemView& tree, int slot_id); - static void Save(const FilesystemView& tree, int slot_id, bool prepare_save = true); - static void Save(std::ostream& os, int slot_id, bool prepare_save = true); + static bool Save(const FilesystemView& tree, int slot_id, bool prepare_save = true); + static bool Save(std::ostream& os, int slot_id, bool prepare_save = true); }; #endif diff --git a/src/utils.cpp b/src/utils.cpp index 662b82c4a6..a014091c79 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -623,3 +623,12 @@ std::string Utils::ReplacePlaceholders(StringView text_template, Span #include #include #include @@ -28,6 +29,9 @@ #include "span.h" namespace Utils { + constexpr StringView DateFormat_YYMMDD = "%y%m%d"; + constexpr StringView DateFormat_HHMMSS = "%H%M%S"; + /** * Converts a string to lower case (ASCII only) * @@ -327,6 +331,15 @@ namespace Utils { */ bool StringIsAscii(std::string& s); + /** + * Formats a date. + * + * @param tm Time structure + * @param format Format string (See strftime) + * @return formatted date + */ + std::string FormatDate(const std::tm* tm, StringView format); + /** * RPG_RT / Delphi compatible rounding of floating point. *