diff --git a/CMakeLists.txt b/CMakeLists.txt index 9707ab6d9e..c80302d5ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -403,6 +403,8 @@ add_library(${PROJECT_NAME} OBJECT src/version.h src/weather.cpp src/weather.h + src/window.cpp + src/window.h src/window_about.cpp src/window_about.h src/window_actorinfo.cpp @@ -423,7 +425,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_command.h src/window_command_horizontal.cpp src/window_command_horizontal.h - src/window.cpp + src/window_debug_picture.cpp + src/window_debug_picture.h src/window_equip.cpp src/window_equip.h src/window_equipitem.cpp @@ -436,7 +439,6 @@ add_library(${PROJECT_NAME} OBJECT src/window_gamelist.h src/window_gold.cpp src/window_gold.h - src/window.h src/window_help.cpp src/window_help.h src/window_import_progress.cpp diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index c2ac849c37..de3807f272 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4736,6 +4736,16 @@ bool Game_Interpreter::CommandManiacGetPictureInfo(lcf::rpg::EventCommand const& int pic_id = ValueOrVariable(com.parameters[0], com.parameters[3]); auto& pic = Main_Data::game_pictures->GetPicture(pic_id); + std::array args = {com.parameters[4], com.parameters[5], com.parameters[6], com.parameters[7]}; + + if (!pic.Exists()) { + for (auto arg: args) { + Main_Data::game_variables->Set(arg, 0); + Game_Map::SetNeedRefreshForVarChange(arg); + } + return true; + } + if (pic.IsRequestPending()) { // Cannot do anything useful here without the dimensions pic.MakeRequestImportant(); @@ -4785,12 +4795,14 @@ bool Game_Interpreter::CommandManiacGetPictureInfo(lcf::rpg::EventCommand const& } } - Main_Data::game_variables->Set(com.parameters[4], x); - Main_Data::game_variables->Set(com.parameters[5], y); - Main_Data::game_variables->Set(com.parameters[6], width); - Main_Data::game_variables->Set(com.parameters[7], height); + Main_Data::game_variables->Set(args[0], x); + Main_Data::game_variables->Set(args[1], y); + Main_Data::game_variables->Set(args[2], width); + Main_Data::game_variables->Set(args[3], height); - Game_Map::SetNeedRefresh(true); + for (auto arg: args) { + Game_Map::SetNeedRefreshForVarChange(arg); + } return true; } @@ -5025,12 +5037,106 @@ bool Game_Interpreter::CommandManiacControlGlobalSave(lcf::rpg::EventCommand con return true; } -bool Game_Interpreter::CommandManiacChangePictureId(lcf::rpg::EventCommand const&) { +bool Game_Interpreter::CommandManiacChangePictureId(lcf::rpg::EventCommand const& com) { + /* + TPC Structure Reference: + @pic[target1].setId .move(target2, size) .ignoreError + @pic[target1].setId .swap(target2, size) .ignoreError + @pic[target1].setId .slide(distance, size) .ignoreError + + Parameters: + [0] Operation: 0 = Move, 1 = Swap, 2 = Slide + [1] Packing: + Bits 0-3: Target 1 Mode (0: Const, 1: Var, 2: Indirect) + Bits 4-7: Size Mode (0: Const, 1: Var, 2: Indirect) + Bits 8-11: Object 2 / Distance Mode (0: Const, 1: Var, 2: Indirect) + [2] Target 1 Value + [3] Size Value + [4] Object 2 / Distance Value + [5] Ignore Error (1 = Ignore) + */ + if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command ChangePictureId not supported"); + enum class Op { + Move, + Swap, + Slide + }; + + int op = com.parameters[0]; + int from_id = ValueOrVariableBitfield(com, 1, 0, 2); + int size = ValueOrVariableBitfield(com, 1, 1, 3); + int arg = ValueOrVariableBitfield(com, 1, 2, 4); // Target 2 or Distance + + bool ignore_error = com.parameters.size() > 5 && com.parameters[5] != 0; + + if (size <= 0) { + return true; + } + + auto& pictures = *Main_Data::game_pictures; + + auto checkValidId = [&](int id, const char* msg) { + bool valid = id > 0; + + if (!valid) { + auto outmsg = fmt::format("Maniac ChangePictureId {}: Invalid Picture ID {}", msg, id); + if (ignore_error) { + Output::DebugStr(outmsg); + } else { + Output::WarningStr(outmsg); + } + } + + return valid; + }; + + if (op < 0 || op > 2) { + Output::Warning("Maniac ChangePictureId: Unknown operation {}", op); + return true; + } + + auto operation = static_cast(op); + + if (operation == Op::Slide) { + arg = from_id + arg; + } + + auto func = (operation == Op::Swap) + ? &Game_Pictures::SwapPicture + : &Game_Pictures::MovePicture; + + for (int i = 0; i < size; ++i) { + int idx = i; + + // Handle overlapping ranges + if (arg > from_id) { + idx = size - 1 - i; + } + + int src_id = from_id + idx; + int dst_id = arg + idx; + + if (!checkValidId(src_id, ( + operation == Op::Move ? "Move (Source)" : + operation == Op::Swap ? "Swap (Source)" : "Slide (Source)"))) { + if (operation == Op::Swap) { + // Based on tests swapping with an invalid src is a no-op + continue; + } + } + + if (!checkValidId(dst_id, ( + operation == Op::Move ? "Move (Dest)" : + operation == Op::Swap ? "Swap (Dest)" : "Slide (Dest)"))) { + } + + (pictures.*func)(src_id, dst_id); + } + return true; } diff --git a/src/game_pictures.cpp b/src/game_pictures.cpp index fd9df44ac5..2b16c99160 100644 --- a/src/game_pictures.cpp +++ b/src/game_pictures.cpp @@ -146,7 +146,12 @@ int Game_Pictures::GetDefaultNumberOfPictures() { return 0; } +int Game_Pictures::GetPictureCount() const { + return static_cast(pictures.size()); +} + Game_Pictures::Picture& Game_Pictures::GetPicture(int id) { + assert(id > 0); if (EP_UNLIKELY(id > static_cast(pictures.size()))) { pictures.reserve(id); while (static_cast(pictures.size()) < id) { @@ -157,7 +162,7 @@ Game_Pictures::Picture& Game_Pictures::GetPicture(int id) { } Game_Pictures::Picture* Game_Pictures::GetPicturePtr(int id) { - return id <= static_cast(pictures.size()) + return id > 0 && id <= static_cast(pictures.size()) ? &pictures[id - 1] : nullptr; } @@ -347,7 +352,7 @@ void Game_Pictures::EraseAll() { bool Game_Pictures::Picture::Exists() const { // Incompatible with the Yume2kki edge-case that uses empty filenames - return !data.name.empty(); + return !data.name.empty() || IsWindowAttached(); } void Game_Pictures::Picture::CreateSprite() { @@ -462,6 +467,9 @@ void Game_Pictures::Picture::ApplyOrigin(bool is_move) { } data.finish_x = x; data.finish_y = y; + + // Origin was applied, prevent applying again in later calls + origin = 0; } void Game_Pictures::Picture::OnMapScrolled(int dx16, int dy16) { @@ -654,3 +662,76 @@ void Game_Pictures::Picture::SetNonEffectParams(const Params& params, bool set_p int Game_Pictures::Picture::NumSpriteSheetFrames() const { return data.spritesheet_cols * data.spritesheet_rows; } + +void Game_Pictures::MovePicture(int src_id, int dst_id) { + if (src_id == dst_id) { + return; + } + + // Delete the destination, then swap + if (dst_id > 0) { + auto& dst_pic = GetPicture(dst_id); + dst_pic.Erase(); + } + + SwapPicture(src_id, dst_id); +} + +void Game_Pictures::SwapPicture(int id1, int id2) { + if (id1 == id2 || (id1 <= 0 && id2 <= 0)) { + return; + } + + auto max_id = std::max(id1, id2); + GetPicture(max_id); // Preallocate to ensure references are stable + + Picture bad_pic{0}; // Sentinel when one of the pictures is invalid + + auto* src_pic = &bad_pic; + auto* dst_pic = &bad_pic; + + if (id1 > 0) { + src_pic = &GetPicture(id1); + } else { + bad_pic = Picture(id1); + } + + if (id2 > 0) { + dst_pic = &GetPicture(id2); + } else { + bad_pic = Picture(id2); + } + + // Handle Window Data (String Pictures) + if (src_pic->IsWindowAttached() || dst_pic->IsWindowAttached()) { + Main_Data::game_windows->SwapWindow(id1, id2); + } + + std::swap(src_pic->data.ID, dst_pic->data.ID); + if (src_pic->sprite) { + if (id2 <= 0) { + src_pic->sprite.reset(); + } else { + src_pic->sprite->SetPictureId(id2); + } + } + if (dst_pic->sprite) { + if (id1 <= 0) { + dst_pic->sprite.reset(); + } else { + dst_pic->sprite->SetPictureId(id1); + } + } + + // Cancel pending requests and restart them + if (src_pic->IsRequestPending()) { + RequestPictureSprite(*src_pic); + } + + if (dst_pic->IsRequestPending()) { + RequestPictureSprite(*dst_pic); + } + + // Must be last (invalidates references) + std::swap(*src_pic, *dst_pic); +} diff --git a/src/game_pictures.h b/src/game_pictures.h index 30d0a0251d..f82fb31d2d 100644 --- a/src/game_pictures.h +++ b/src/game_pictures.h @@ -42,6 +42,7 @@ class Game_Pictures { void InitGraphics(); static int GetDefaultNumberOfPictures(); + int GetPictureCount() const; struct Params { int position_x = 0; @@ -130,9 +131,34 @@ class Game_Pictures { bool IsWindowAttached() const; }; + /** + * @param id Picture ID + * @return Reference to a picture (allocates when necessary). Passing an invalid ID will abort! + */ Picture& GetPicture(int id); + + /** + * @param id Picture ID + * @return Pointer to an existing picture or nullptr if its an unused picture slot + */ Picture* GetPicturePtr(int id); + /** + * Moves picture data to a different ID + * + * @param src_id Source ID to move from + * @param dst_id Destination ID to move to + */ + void MovePicture(int src_id, int dst_id); + + /** + * Swaps picture data between two IDs + * + * @param id1 First ID to swap with + * @param id2 Second ID to swap with + */ + void SwapPicture(int id1, int id2); + private: void RequestPictureSprite(Picture& pic); void OnPictureSpriteReady(FileRequestResult*, int id); diff --git a/src/game_windows.cpp b/src/game_windows.cpp index 849a2b7694..5161ed095a 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -103,6 +103,51 @@ Game_Windows::Window_User* Game_Windows::GetWindowPtr(int id) { ? &windows[id - 1] : nullptr; } +void Game_Windows::MoveWindow(int src_id, int dst_id) { + if (src_id == dst_id) { + return; + } + + // Delete the destination, then swap + if (dst_id > 0) { + auto& dst_win = GetWindow(dst_id); + dst_win.Erase(); + } + + SwapWindow(src_id, dst_id); +} + +void Game_Windows::SwapWindow(int id1, int id2) { + if (id1 == id2 || (id1 <= 0 && id2 <= 0)) { + return; + } + + auto max_id = std::max(id1, id2); + GetWindow(max_id); // Preallocate to ensure references are stable + + Window_User bad_win{0}; + + auto* src_win = &bad_win; + auto* dst_win = &bad_win; + + if (id1 > 0) { + src_win = &GetWindow(id1); + } else { + bad_win = Window_User(id1); + } + + if (id2 > 0) { + dst_win = &GetWindow(id2); + } else { + bad_win = Window_User(id2); + } + + std::swap(src_win->data.ID, dst_win->data.ID); + + // Must be last (invalidates references) + std::swap(*src_win, *dst_win); +} + bool Game_Windows::Window_User::Create(const WindowParams& params) { Erase(); diff --git a/src/game_windows.h b/src/game_windows.h index 1d4e6f9039..e29c93d19e 100644 --- a/src/game_windows.h +++ b/src/game_windows.h @@ -96,6 +96,24 @@ class Game_Windows { Window_User& GetWindow(int id); Window_User* GetWindowPtr(int id); + /** + * Moves window data to a different ID. + * Do not call this function. Always use Game_Pictures::MovePicture. + * + * @param src_id Source ID to move from + * @param dst_id Destination ID to move to + */ + void MoveWindow(int src_id, int dst_id); + + /** + * Swaps window data between two IDs. + * Do not call this function. Always use Game_Pictures::SwapPicture. + * + * @param id1 First ID to swap with + * @param id2 Second ID to swap with + */ + void SwapWindow(int id1, int id2); + private: std::vector windows; }; diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 33924ac77b..69d1f8d4b1 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -34,6 +34,7 @@ #include "scene_menu.h" #include "scene_save.h" #include "scene_map.h" +#include "window_debug_picture.h" #include "scene_battle.h" #include "player.h" #include "window_command.h" @@ -41,6 +42,7 @@ #include "window_numberinput.h" #include "bitmap.h" #include "game_party.h" +#include "game_pictures.h" #include "game_player.h" #include #include "output.h" @@ -74,6 +76,7 @@ void Scene_Debug::Start() { CreateChoicesWindow(); CreateStringViewWindow(); CreateInterpreterWindow(); + CreatePictureInfoWindow(); SetupUiRangeList(); @@ -139,6 +142,9 @@ void Scene_Debug::UpdateFrameValueFromUi() { frame.value = GetSelectedIndexFromRange() + interpreter_window->GetIndex(); state_interpreter.selected_frame = interpreter_window->GetSelectedStackFrameLine(); break; + case eUiPictureView: + // Window is not interactive + break; } } @@ -308,6 +314,31 @@ void Scene_Debug::PushUiStringView() { stringview_window->Refresh(); } +void Scene_Debug::PushUiPictureView() { + const auto pic_id = GetFrame().value; + + if (pic_id <= 0) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer)); + return; + } + + auto* pic = Main_Data::game_pictures->GetPicturePtr(pic_id); + if (!pic || (!pic->Exists())) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer)); + return; + } + + Push(eUiPictureView); + + var_window->SetActive(false); + picture_info_window->SetVisible(true); + + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision)); + + picture_info_window->SetPictureId(pic_id); + picture_info_window->Refresh(); +} + void Scene_Debug::PushUiInterpreterView() { const bool was_range_list = (GetFrame().uimode == eUiRangeList); @@ -343,6 +374,7 @@ void Scene_Debug::Pop() { stringview_window->SetActive(false); stringview_window->SetVisible(false); interpreter_window->SetActive(false); + picture_info_window->SetVisible(false); if (mode == eInterpreter) { interpreter_window->SetIndex(-1); @@ -401,6 +433,11 @@ void Scene_Debug::Pop() { var_window->SetVisible(false); interpreter_window->SetVisible(true); break; + case eUiPictureView: + picture_info_window->SetVisible(true); + picture_info_window->SetPictureId(frame.value); + picture_info_window->Refresh(); + break; } if (stack_index == 0) { @@ -621,6 +658,15 @@ void Scene_Debug::vUpdate() { PushUiRangeList(); } break; + case ePictureTool: + if (sz > 2) { + PushUiPictureView(); + } else if (sz > 1) { + PushUiVarList(); + } else { + PushUiRangeList(); + } + break; case eInterpreter: if (sz == 3) { auto action = interpreter_window->GetSelectedAction(); @@ -776,6 +822,7 @@ void Scene_Debug::UpdateRangeListWindow() { addItem("Call MapEvent", Scene::Find(Scene::Map) != nullptr); addItem("Call BtlEvent", is_battle); addItem("Strings", Player::IsPatchManiac()); + addItem("Pictures"); addItem("Interpreter"); addItem("Open Menu", !is_battle); } @@ -785,6 +832,7 @@ void Scene_Debug::UpdateRangeListWindow() { case eItem: case eBattle: case eString: + case ePictureTool: fillRange(GetWindowMode()); break; case eMap: @@ -947,6 +995,16 @@ void Scene_Debug::CreateInterpreterWindow() { interpreter_window->SetIndex(-1); } +void Scene_Debug::CreatePictureInfoWindow() { + picture_info_window = std::make_unique( + Player::menu_offset_x + 20, + Player::menu_offset_y + 16, + MENU_WIDTH - 40, + MENU_HEIGHT - 32 + ); + picture_info_window->SetVisible(false); +} + int Scene_Debug::GetNumMainMenuItems() const { return static_cast(eLastMainMenuOption) - 1; } @@ -986,6 +1044,9 @@ int Scene_Debug::GetLastPage() const { case eString: num_elements = Main_Data::game_strings->GetSizeWithLimit(); break; + case ePictureTool: + num_elements = Main_Data::game_pictures->GetPictureCount(); + break; case eInterpreter: num_elements = 1 + state_interpreter.background_states.Count(); return (static_cast(num_elements) - 1) / 10; diff --git a/src/scene_debug.h b/src/scene_debug.h index a3efc68e6e..ba23cd41f1 100644 --- a/src/scene_debug.h +++ b/src/scene_debug.h @@ -27,6 +27,8 @@ #include "window_varlist.h" #include "window_stringview.h" #include "window_interpreter.h" +#include "window_debug_picture.h" + /** * Scene Equip class. @@ -72,6 +74,7 @@ class Scene_Debug : public Scene { eCallMapEvent, eCallBattleEvent, eString, + ePictureTool, eInterpreter, eOpenMenu, eLastMainMenuOption, @@ -84,7 +87,8 @@ class Scene_Debug : public Scene { eUiNumberInput, eUiStringView, eUiChoices, - eUiInterpreterView + eUiInterpreterView, + eUiPictureView }; private: Mode mode = eMain; @@ -112,6 +116,9 @@ class Scene_Debug : public Scene { /** Creates interpreter window. */ void CreateInterpreterWindow(); + /** Creates picture info window. */ + void CreatePictureInfoWindow(); + /** Get the last page for the current mode */ int GetLastPage() const; @@ -152,6 +159,8 @@ class Scene_Debug : public Scene { std::unique_ptr stringview_window; /** Displays the currently running inteprreters. */ std::unique_ptr interpreter_window; + /** Displays picture debug info. */ + std::unique_ptr picture_info_window; struct StackFrame { UiMode uimode = eUiMain; @@ -175,6 +184,7 @@ class Scene_Debug : public Scene { void PushUiChoices(std::vector choices, std::vector choices_enabled); void PushUiStringView(); void PushUiInterpreterView(); + void PushUiPictureView(); Window_VarList::Mode GetWindowMode() const; static constexpr Window_VarList::Mode GetWindowMode(Mode mode); @@ -225,6 +235,8 @@ constexpr Window_VarList::Mode Scene_Debug::GetWindowMode(Mode mode) { return Window_VarList::eMapEvent; case eString: return Window_VarList::eString; + case ePictureTool: + return Window_VarList::ePicture; default: return Window_VarList::eNone; } diff --git a/src/sprite_picture.h b/src/sprite_picture.h index 06e044a347..386053335f 100644 --- a/src/sprite_picture.h +++ b/src/sprite_picture.h @@ -44,12 +44,25 @@ class Sprite_Picture : public Sprite { /** @return Height of a single spritesheet frame or the entire width if the picture has no spritesheet */ int GetFrameHeight() const; + int GetPictureId() const; + + void SetPictureId(int pic_id); + private: int last_spritesheet_frame = -1; - const int pic_id = 0; + int pic_id = 0; const bool feature_spritesheet = false; const bool feature_priority_layers = false; const bool feature_bottom_trans = false; }; +inline int Sprite_Picture::GetPictureId() const { + return pic_id; +} + +inline void Sprite_Picture::SetPictureId(int pic_id) { + this->pic_id = pic_id; + OnPictureShow(); +} + #endif diff --git a/src/window_debug_picture.cpp b/src/window_debug_picture.cpp new file mode 100644 index 0000000000..91330657f4 --- /dev/null +++ b/src/window_debug_picture.cpp @@ -0,0 +1,254 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "window_debug_picture.h" +#include "game_pictures.h" +#include "game_windows.h" +#include "main_data.h" +#include "bitmap.h" +#include "font.h" +#include "utils.h" +#include +#include +#include +#include + +Window_DebugPictureInfo::Window_DebugPictureInfo(int x, int y, int w, int h) : + Window_Base(x, y, w, h) +{ + SetContents(Bitmap::Create(width, height)); +} + +void Window_DebugPictureInfo::SetPictureId(int id) { + if (picture_id != id) { + picture_id = id; + Refresh(); + } +} + +int Window_DebugPictureInfo::DrawLine(int y, std::string_view label, std::string_view value) { + contents->TextDraw(0, y, Font::ColorDefault, label); + int val_x = 40; + contents->TextDraw(val_x, y, Font::ColorHeal, value); + return y + 16; +} + +int Window_DebugPictureInfo::DrawDualLine(int y, std::string_view l1, std::string_view v1, std::string_view l2, std::string_view v2) { + contents->TextDraw(0, y, Font::ColorDefault, l1); + contents->TextDraw(40, y, Font::ColorHeal, v1); + + contents->TextDraw(110, y, Font::ColorDefault, l2); + contents->TextDraw(150, y, Font::ColorHeal, v2); + return y + 16; +} + +int Window_DebugPictureInfo::DrawSeparator(int y) { + // Draw a dim line + Color col; + col.alpha = 128; + contents->FillRect(Rect(0, y + 7, contents->GetWidth(), 1), col); + return y + 16; +} + +int Window_DebugPictureInfo::DrawFlags(int y, const std::vector& flags) { + int x = 0; + + for (const auto& flag : flags) { + int w = Text::GetSize(*Font::Default(), flag.name).width + 4; + if (x + w > contents->GetWidth()) { + x = 0; + y += 16; + } + + // Draw bracketed flag like [FlipX] + std::string text = fmt::format("[{}]", flag.name); + contents->TextDraw(x, y, flag.active ? Font::ColorHeal : Font::ColorDisabled, text); + + x += w + 12; + } + + return y + 16; +} + +void Window_DebugPictureInfo::Refresh() { + contents->Clear(); + + if (picture_id <= 0) { + contents->TextDraw(0, 0, Font::ColorDisabled, "No Selection"); + return; + } + + auto* pic = Main_Data::game_pictures->GetPicturePtr(picture_id); + if (!pic) { + contents->TextDraw(0, 0, Font::ColorCritical, "Invalid ID"); + return; + } + + bool is_str = pic->IsWindowAttached(); + if (!pic->Exists()) { + contents->TextDraw(0, 0, Font::ColorDisabled, "Empty"); + return; + } + + const auto& d = pic->data; + int y = 0; + + // ID & Type + std::string type_str = is_str ? "String" : "Image"; + DrawDualLine(y, "ID", fmt::format("{}", picture_id), "Type", type_str); + y += 16; + + // Position & Movement + std::string pos_str = fmt::format("{:.0f},{:.0f}", d.current_x, d.current_y); + y = DrawLine(y, "Pos", pos_str); + + if (d.time_left > 0 || d.current_x != d.finish_x || d.current_y != d.finish_y) { + std::string goal_str = fmt::format("{:.0f},{:.0f} ({}f)", d.finish_x, d.finish_y, d.time_left); + y = DrawLine(y, "Goal", goal_str); + } + + // Scale + std::string scale_str = fmt::format("{:.0f}", d.current_magnify); + if (d.maniac_current_magnify_height != d.current_magnify) { + scale_str += fmt::format(" / {:.0f}", d.maniac_current_magnify_height); + } + + + // Transparency + std::string trans_str; + if (d.current_top_trans == d.current_bot_trans) { + trans_str = fmt::format("{:.0f}", d.current_top_trans); + } + else { + trans_str = fmt::format("{:.0f}/{:.0f}", d.current_top_trans, d.current_bot_trans); + } + + + y = DrawDualLine(y, "Scale", scale_str + "%", "Trans", trans_str + "%"); + + // Blend & Layer + std::string blend = "None"; + if (d.easyrpg_blend_mode == 1) blend = "Multiply"; + if (d.easyrpg_blend_mode == 2) blend = "Addition"; + if (d.easyrpg_blend_mode == 3) blend = "Overlay"; + + std::string layer = fmt::format("M:{} B:{}", d.map_layer, d.battle_layer); + y = DrawDualLine(y, "Blend", blend, "Layer", layer); + + // Tone (R,G,B,S) + std::string tone_str = fmt::format("{:.0f},{:.0f},{:.0f},{:.0f}", d.current_red, d.current_green, d.current_blue, d.current_sat); + y = DrawLine(y, "Tone", tone_str); + + // Effects + if (d.effect_mode != lcf::rpg::SavePicture::Effect_none) { + std::string effect; + switch (d.effect_mode) { + case lcf::rpg::SavePicture::Effect_rotation: effect = "Rot"; break; + case lcf::rpg::SavePicture::Effect_wave: effect = "Wave"; break; + case lcf::rpg::SavePicture::Effect_maniac_fixed_angle: effect = "Ang"; break; + } + effect += fmt::format(" {:.1f}", d.current_effect_power); + if (d.effect_mode == lcf::rpg::SavePicture::Effect_rotation || d.effect_mode == lcf::rpg::SavePicture::Effect_maniac_fixed_angle) { + effect += fmt::format(" ({:.1f})", d.current_rotation); + } + y = DrawLine(y, "FX", effect); + } + + y = DrawSeparator(y); + // Common Flags + std::vector flags = { + { "Fixed", d.fixed_to_map }, + { "Chroma", d.use_transparent_color }, + { "Tint", d.flags.affected_by_tint }, + { "Flash", d.flags.affected_by_flash }, + { "Shake", d.flags.affected_by_shake }, + { "EraseOnMapChange", d.flags.erase_on_map_change }, + { "EraseAfterBattle", d.flags.erase_on_battle_end }, + { "FlipX", (bool)(d.easyrpg_flip & lcf::rpg::SavePicture::EasyRpgFlip_x) }, + { "FlipY", (bool)(d.easyrpg_flip & lcf::rpg::SavePicture::EasyRpgFlip_y) } + }; + y = DrawFlags(y, flags); + + y = DrawSeparator(y); + + if (!is_str) { + // File Picture + std::string name_str = std::string(d.name); + if (name_str.length() > 18) name_str = "..." + name_str.substr(name_str.length() - 15); + y = DrawLine(y, "File", name_str); + + if (d.spritesheet_cols > 1 || d.spritesheet_rows > 1) { + std::string cell_str = fmt::format("#{} ({}x{})", d.spritesheet_frame, d.spritesheet_cols, d.spritesheet_rows); + y = DrawLine(y, "Cell", cell_str); + + if (d.spritesheet_speed > 0) { + y = DrawLine(y, "Anim", fmt::format("Spd: {} {}", d.spritesheet_speed, d.spritesheet_play_once ? "[Once]" : "[Loop]")); + } + } + } + else { + // String Picture + auto& win = Main_Data::game_windows->GetWindow(picture_id); + const auto& wd = win.data; + + std::string dims = fmt::format("{}x{}", wd.width, wd.height); + y = DrawLine(y, "Size", dims); + + std::string skin = std::string(wd.system_name); + if (skin.empty()) skin = "Default"; + y = DrawLine(y, "Skin", skin); + + if (!wd.texts.empty()) { + const auto& txt = wd.texts[0]; // Usually only one text chunk for string pics + + std::string font_info = std::string(txt.font_name); + if (font_info.empty()) font_info = "Sys"; + font_info += fmt::format(" {}pt", txt.font_size); + y = DrawLine(y, "Font", font_info); + + y = DrawDualLine(y, "LSpc", std::to_string(txt.letter_spacing), "HSpc", std::to_string(txt.line_spacing)); + + // String Flags + std::vector str_flags = { + { "Frame", wd.flags.draw_frame }, + { "Grad", txt.flags.draw_gradient }, + { "Shdw", txt.flags.draw_shadow }, + { "Bold", txt.flags.bold }, + { "Ital", txt.flags.italic }, + { "Marg", wd.flags.border_margin } + }; + + // Background type (Stretch/Tile/None) + std::string bg_type = "Stretch"; + if (wd.message_stretch == 0) bg_type = "Tile"; + if (wd.message_stretch == 2) bg_type = "None"; // easyrpg_none + + y = DrawLine(y, "BG", bg_type); + y = DrawFlags(y, str_flags); + + // Content preview + y = DrawSeparator(y); + std::string content = ToString(txt.text); + // Simple replace newlines for preview + content = Utils::ReplaceAll(content, "\n", "\\n"); + if (content.length() > 22) content = content.substr(0, 20) + "..."; + + contents->TextDraw(0, y, Font::ColorDefault, "Text:"); + contents->TextDraw(40, y, Font::ColorDefault, content); + } + } +} diff --git a/src/window_debug_picture.h b/src/window_debug_picture.h new file mode 100644 index 0000000000..58655a6f25 --- /dev/null +++ b/src/window_debug_picture.h @@ -0,0 +1,54 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_WINDOW_DEBUG_PICTURE_H +#define EP_WINDOW_DEBUG_PICTURE_H + +#include "window_base.h" +#include + +/** + * Debug window showing details of a specific picture. + */ +class Window_DebugPictureInfo : public Window_Base { +public: + Window_DebugPictureInfo(int x, int y, int w, int h); + + void SetPictureId(int id); + void Refresh(); + +private: + int picture_id = 0; + + // Draw label and value. Returns next Y. + int DrawLine(int y, std::string_view label, std::string_view value); + + // Draw two label/value pairs on one line. Returns next Y. + int DrawDualLine(int y, std::string_view l1, std::string_view v1, std::string_view l2, std::string_view v2); + + // Draw a separator line. + int DrawSeparator(int y); + + // Helper for drawing boolean flags compactly + struct FlagInfo { + const char* name; + bool active; + }; + int DrawFlags(int y, const std::vector& flags); +}; + +#endif diff --git a/src/window_varlist.cpp b/src/window_varlist.cpp index 93fce1efb2..d7e6b4d553 100644 --- a/src/window_varlist.cpp +++ b/src/window_varlist.cpp @@ -22,14 +22,17 @@ #include "game_switches.h" #include "game_variables.h" #include "game_strings.h" +#include "game_pictures.h" #include "bitmap.h" #include #include #include "input.h" +#include "main_data.h" #include "output.h" #include "game_party.h" #include "game_map.h" #include "game_system.h" +#include constexpr int LINE_COUNT = 10; @@ -120,6 +123,18 @@ void Window_VarList::DrawItemValue(int index){ case eString: DrawStringVarItem(index, y); break; + case ePicture: { + auto* pic = Main_Data::game_pictures->GetPicturePtr(first_var + index); + if (pic && (pic->Exists())) { + auto pos_str = fmt::format("{:.0f},{:.0f}", pic->data.current_x, pic->data.current_y); + contents->TextDraw(GetWidth() - 16, y, Font::ColorHeal, pos_str, Text::AlignRight); + } else { + const int space_reserved = (GetDigitCount() + 2); + int x = space_reserved * 6; + contents->TextDraw(x, y, Font::ColorDisabled, "undefined"); + } + break; + } case eNone: break; } @@ -197,6 +212,19 @@ void Window_VarList::UpdateList(int first_value){ ss << strvar_name; } break; + case ePicture: { + auto* pic = Main_Data::game_pictures->GetPicturePtr(first_value + i); + if (pic->IsWindowAttached()) { + ss << "[String]"; + } else { + std::string name = ToString(pic->data.name); + if (name.length() > 14) { + name = name.substr(0, 11) + "..."; + } + ss << name; + } + break; + } default: break; } @@ -239,6 +267,9 @@ bool Window_VarList::DataIsValid(int range_index) { return Game_Map::GetEvent(range_index) != nullptr; case eString: return range_index > 0 && range_index <= Main_Data::game_strings->GetSizeWithLimit(); + case ePicture: { + return range_index > 0 && range_index <= Main_Data::game_pictures->GetPictureCount(); + } default: break; } @@ -263,6 +294,8 @@ int Window_VarList::GetNumElements(Mode mode) { return Game_Map::GetHighestEventId(); case eString: return Main_Data::game_strings->GetSizeWithLimit(); + case ePicture: + return Main_Data::game_pictures->GetPictureCount(); default: return -1; } diff --git a/src/window_varlist.h b/src/window_varlist.h index 0113e65a5f..51e104350b 100644 --- a/src/window_varlist.h +++ b/src/window_varlist.h @@ -35,7 +35,8 @@ class Window_VarList : public Window_Selectable eLevel, eCommonEvent, eMapEvent, - eString + eString, + ePicture }; /** @@ -50,7 +51,7 @@ class Window_VarList : public Window_Selectable /** * UpdateList. - * + * * @param first_value starting value. */ void UpdateList(int first_value); @@ -132,6 +133,8 @@ constexpr std::string_view Window_VarList::GetPrefix(Mode mode) { return "Me"; case eString: return "St"; + case ePicture: + return "Pi"; default: assert(false); return {}; @@ -141,6 +144,7 @@ constexpr std::string_view Window_VarList::GetPrefix(Mode mode) { constexpr int Window_VarList::GetItemCount(Mode mode, bool show_detail) { switch (mode) { case eString: + if (show_detail) { return 5; }