From a8734d0984a3e53573cf069da3a533d6cb7a16ce Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:33:23 -0300 Subject: [PATCH 1/2] Maniacs Patch - ChangePictureId command Added support for the Maniac Patch ChangePictureId event command, enabling move, swap, and slide operations on picture IDs with error handling and sprite refresh logic. This replaces the previous stub implementation and ensures proper picture management for Maniac Patch compatibility. --- src/game_interpreter.cpp | 236 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 234 insertions(+), 2 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 3e4842ba67..62b3858956 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4967,12 +4967,244 @@ 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"); + int operation = com.parameters[0]; + int target1 = ValueOrVariableBitfield(com, 1, 0, 2); + int size = ValueOrVariableBitfield(com, 1, 1, 3); + int arg3 = 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& windows = *Main_Data::game_windows; + + auto isValidId = [](int id) { + return id > 0; + }; + + // Helper to move a single picture from src to dst + auto move_picture = [&](int src, int dst) { + // Ensure existence in vectors to avoid reference invalidation during assignments + int max_id = std::max(src, dst); + pictures.GetPicture(max_id); + windows.GetWindow(max_id); + + auto& src_pic = pictures.GetPicture(src); + auto& dst_pic = pictures.GetPicture(dst); + + // If source is empty, erase destination + if (!src_pic.Exists() && !src_pic.IsWindowAttached()) { + dst_pic.Erase(); + return; + } + + // 1. Handle Window Data (String Pictures) + if (src_pic.IsWindowAttached()) { + auto& src_win = windows.GetWindow(src); + auto& dst_win = windows.GetWindow(dst); + dst_win.data = src_win.data; + dst_win.data.ID = dst; + src_win.Erase(); + } + else { + // If overwriting a window picture with a normal one, clear the old window data + // (Safe to call even if dst wasn't a window before) + windows.GetWindow(dst).Erase(); + } + + // 2. Handle Picture Data + BitmapRef src_bmp = src_pic.sprite ? src_pic.sprite->GetBitmap() : nullptr; + auto request_id = src_pic.request_id; + src_pic.request_id = nullptr; // Prevent cancellation on Erase + + dst_pic.data = src_pic.data; + dst_pic.data.ID = dst; + dst_pic.request_id = request_id; + + src_pic.Erase(); + + // 3. Refresh Sprite + if (dst_pic.IsWindowAttached()) { + // Re-attach window to generate sprite + bool async; + windows.GetWindow(dst).Refresh(async); + } + else if (!dst_pic.data.name.empty()) { + if (!dst_pic.sprite) dst_pic.CreateSprite(); + if (src_bmp) { + dst_pic.sprite->SetBitmap(src_bmp); + dst_pic.sprite->OnPictureShow(); + dst_pic.sprite->SetVisible(true); + } + } + else { + dst_pic.sprite.reset(); + } + }; + + // Helper to swap two pictures + auto swap_picture = [&](int id1, int id2) { + // Ensure existence in vectors to avoid reference invalidation during assignments + int max_id = std::max(id1, id2); + pictures.GetPicture(max_id); + windows.GetWindow(max_id); + + auto& p1 = pictures.GetPicture(id1); + auto& p2 = pictures.GetPicture(id2); + + // Swap Window Data + auto& w1 = windows.GetWindow(id1); + auto& w2 = windows.GetWindow(id2); + std::swap(w1.data, w2.data); + w1.data.ID = id1; + w2.data.ID = id2; + + // Swap Picture Data + BitmapRef b1 = p1.sprite ? p1.sprite->GetBitmap() : nullptr; + BitmapRef b2 = p2.sprite ? p2.sprite->GetBitmap() : nullptr; + + using std::swap; + swap(p1.data, p2.data); + swap(p1.request_id, p2.request_id); + + p1.data.ID = id1; + p2.data.ID = id2; + + // Refresh Sprites Helper + auto refresh = [&](Game_Pictures::Picture& p, BitmapRef bmp) { + if (p.IsWindowAttached()) { + bool async; + windows.GetWindow(p.data.ID).Refresh(async); + } + else if (!p.data.name.empty()) { + if (!p.sprite) p.CreateSprite(); + if (bmp) { + p.sprite->SetBitmap(bmp); + p.sprite->OnPictureShow(); + p.sprite->SetVisible(true); + } + } + else { + p.sprite.reset(); + } + }; + + refresh(p1, b2); + refresh(p2, b1); + }; + + if (operation == 0 || operation == 2) { + // Move (0) or Slide (2) + int target2 = (operation == 0) ? arg3 : (target1 + arg3); + + int start = 0; + int end = size; + int step = 1; + + // Handle overlapping ranges + if (target2 > target1) { + start = size - 1; + end = -1; + step = -1; + } + + for (int i = start; i != end; i += step) { + int src_id = target1 + i; + int dst_id = target2 + i; + + if (!isValidId(src_id)) { + if (!ignore_error) { + Output::Warning("Maniac ChangePictureId {}: Invalid Picture ID {}", (operation == 0 ? "Move (Source)" : "Slide (Source)"), src_id); + return true; + } + continue; + } + + if (!isValidId(dst_id)) { + if (!ignore_error) { + Output::Warning("Maniac ChangePictureId {}: Invalid Picture ID {}", (operation == 0 ? "Move (Dest)" : "Slide (Dest)"), dst_id); + return true; + } + + // "If you use the "Ignore out-of-range errors" setting, moving from/to an out-of-range ID will be replaced by a simple delete operation." + // If destination is invalid, delete source. + auto& src_pic = pictures.GetPicture(src_id); + if (src_pic.Exists() || src_pic.IsWindowAttached()) { + pictures.Erase(src_id); + } + continue; + } + + if (src_id != dst_id) { + move_picture(src_id, dst_id); + } + } + } + else if (operation == 1) { + // Swap + int target2 = arg3; + + for (int i = 0; i < size; ++i) { + int id1 = target1 + i; + int id2 = target2 + i; + + bool valid1 = isValidId(id1); + bool valid2 = isValidId(id2); + + if (!valid1 && !ignore_error) { + Output::Warning("Maniac ChangePictureId Swap: Invalid Picture ID {}", id1); + return true; + } + if (!valid2 && !ignore_error) { + Output::Warning("Maniac ChangePictureId Swap: Invalid Picture ID {}", id2); + return true; + } + + if (valid1 && valid2) { + swap_picture(id1, id2); + } + else if (valid1 && !valid2) { + // Valid swap with invalid -> erase valid + pictures.Erase(id1); + } + else if (!valid1 && valid2) { + // Invalid swap with valid -> erase valid + pictures.Erase(id2); + } + } + } + else { + Output::Warning("Maniac ChangePictureId: Unknown operation {}", operation); + } + + Game_Map::SetNeedRefresh(true); + return true; } From f0cc536a59726e1626aaea98bf853a137e09e854 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:44:02 -0300 Subject: [PATCH 2/2] New Feature - Picture Inspector Introduces a new debug scene and windows for viewing and inspecting active pictures and string windows. Updates scene and debug menu logic to support the new 'DebugPicture' scene, adds menu option, and implements detailed property display for both image and string pictures. --- CMakeLists.txt | 4 + Makefile.am | 4 + src/scene.cpp | 7 +- src/scene.h | 3 +- src/scene_debug.cpp | 6 + src/scene_debug.h | 1 + src/scene_debug_picture.cpp | 68 ++++++++ src/scene_debug_picture.h | 36 ++++ src/window_debug_picture.cpp | 320 +++++++++++++++++++++++++++++++++++ src/window_debug_picture.h | 70 ++++++++ 10 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 src/scene_debug_picture.cpp create mode 100644 src/scene_debug_picture.h create mode 100644 src/window_debug_picture.cpp create mode 100644 src/window_debug_picture.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f4214968d..f9b59aec96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,6 +305,8 @@ add_library(${PROJECT_NAME} OBJECT src/scene.cpp src/scene_debug.cpp src/scene_debug.h + src/scene_debug_picture.cpp + src/scene_debug_picture.h src/scene_end.cpp src/scene_end.h src/scene_equip.cpp @@ -420,6 +422,8 @@ add_library(${PROJECT_NAME} OBJECT src/window_command.h src/window_command_horizontal.cpp src/window_command_horizontal.h + src/window_debug_picture.cpp + src/window_debug_picture.h src/window.cpp src/window_equip.cpp src/window_equip.h diff --git a/Makefile.am b/Makefile.am index 5d00e6cdcd..6067bf14f9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -280,6 +280,8 @@ libeasyrpg_player_a_SOURCES = \ src/scene_battle_rpg2k3.h \ src/scene_debug.cpp \ src/scene_debug.h \ + src/scene_debug_picture.cpp \ + src/scene_debug_picture.h \ src/scene_end.cpp \ src/scene_end.h \ src/scene_equip.cpp \ @@ -426,6 +428,8 @@ libeasyrpg_player_a_SOURCES = \ src/window_numberinput.h \ src/window_paramstatus.cpp \ src/window_paramstatus.h \ + src/window_debug_picture.cpp \ + src/window_debug_picture.h \ src/window_savefile.cpp \ src/window_savefile.h \ src/window_selectable.cpp \ diff --git a/src/scene.cpp b/src/scene.cpp index bc95a3d3f4..e5977e1ade 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -44,7 +44,7 @@ std::shared_ptr Scene::instance; std::vector > Scene::old_instances; std::vector > Scene::instances; -const char Scene::scene_names[SceneMax][12] = +const char Scene::scene_names[SceneMax][13] = { "Null", "Title", @@ -69,7 +69,8 @@ const char Scene::scene_names[SceneMax][12] = "GameBrowser", "Teleport", "Settings", - "Language" + "Language", + "DebugPicture" }; enum PushPopOperation { @@ -116,6 +117,8 @@ lcf::rpg::SaveSystem::Scene Scene::rpgRtSceneFromSceneType(SceneType t) { return lcf::rpg::SaveSystem::Scene_game_over; case Debug: return lcf::rpg::SaveSystem::Scene_debug; + case DebugPicture: + return lcf::rpg::SaveSystem::Scene_debug; } return lcf::rpg::SaveSystem::Scene(-1); } diff --git a/src/scene.h b/src/scene.h index 51d077b43c..d00af5e624 100644 --- a/src/scene.h +++ b/src/scene.h @@ -60,6 +60,7 @@ class Scene { Teleport, Settings, LanguageMenu, + DebugPicture, SceneMax }; @@ -203,7 +204,7 @@ class Scene { static std::vector > old_instances; /** Contains name of the Scenes. For debug purposes. */ - static const char scene_names[SceneMax][12]; + static const char scene_names[SceneMax][13]; /** * Called by the graphic system to request drawing of a background, usually a system color background diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 9206a73af0..d2c9f114f5 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 "scene_debug_picture.h" #include "scene_battle.h" #include "player.h" #include "window_command.h" @@ -621,6 +622,10 @@ void Scene_Debug::vUpdate() { PushUiRangeList(); } break; + case ePictureTool: + Scene::Push(std::make_shared()); + mode = eMain; + return; case eInterpreter: if (sz == 3) { auto action = interpreter_window->GetSelectedAction(); @@ -776,6 +781,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); } diff --git a/src/scene_debug.h b/src/scene_debug.h index a3efc68e6e..2c04ed97e5 100644 --- a/src/scene_debug.h +++ b/src/scene_debug.h @@ -72,6 +72,7 @@ class Scene_Debug : public Scene { eCallMapEvent, eCallBattleEvent, eString, + ePictureTool, eInterpreter, eOpenMenu, eLastMainMenuOption, diff --git a/src/scene_debug_picture.cpp b/src/scene_debug_picture.cpp new file mode 100644 index 0000000000..ca3b221d7a --- /dev/null +++ b/src/scene_debug_picture.cpp @@ -0,0 +1,68 @@ +/* + * 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 "scene_debug_picture.h" +#include "input.h" +#include "player.h" +#include "game_system.h" +#include "main_data.h" + +Scene_DebugPicture::Scene_DebugPicture() { + type = Scene::DebugPicture; +} + +void Scene_DebugPicture::Start() { + // Make list window narrow (just IDs) to maximize info space + int list_w = 64; + + list_window = std::make_unique( + Player::menu_offset_x, + Player::menu_offset_y, + list_w, + MENU_HEIGHT + ); + + info_window = std::make_unique( + Player::menu_offset_x + list_w, + Player::menu_offset_y, + MENU_WIDTH - list_w, + MENU_HEIGHT + ); + + list_window->SetActive(true); + list_window->SetIndex(0); + + // Initialize info window with first item + info_window->SetPictureId(list_window->GetPictureId()); +} + +void Scene_DebugPicture::vUpdate() { + list_window->Update(); + info_window->Update(); + + // Update info window based on selection + if (list_window->GetActive()) { + // Always refresh info window to see real-time coordinate updates + info_window->SetPictureId(list_window->GetPictureId()); + info_window->Refresh(); + } + + if (Input::IsTriggered(Input::CANCEL)) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel)); + Scene::Pop(); + } +} diff --git a/src/scene_debug_picture.h b/src/scene_debug_picture.h new file mode 100644 index 0000000000..dcc5d920dc --- /dev/null +++ b/src/scene_debug_picture.h @@ -0,0 +1,36 @@ +/* + * 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_SCENE_DEBUG_PICTURE_H +#define EP_SCENE_DEBUG_PICTURE_H + +#include "scene.h" +#include "window_debug_picture.h" +#include + +class Scene_DebugPicture : public Scene { +public: + Scene_DebugPicture(); + void Start() override; + void vUpdate() override; + +private: + std::unique_ptr list_window; + std::unique_ptr info_window; +}; + +#endif diff --git a/src/window_debug_picture.cpp b/src/window_debug_picture.cpp new file mode 100644 index 0000000000..09a057566c --- /dev/null +++ b/src/window_debug_picture.cpp @@ -0,0 +1,320 @@ +/* + * 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 +#include + +Window_DebugPictureList::Window_DebugPictureList(int x, int y, int w, int h) : + Window_Selectable(x, y, w, h) +{ + SetMenuItemHeight(16); + SetColumnMax(1); + SetContents(Bitmap::Create(width, height)); + Refresh(); +} + +void Window_DebugPictureList::Refresh() { + picture_ids.clear(); + + // Scan all allocated pictures + for (int i = 1; ; ++i) { + auto* pic = Main_Data::game_pictures->GetPicturePtr(i); + if (!pic) { + break; + } + + if (pic->Exists() || pic->IsWindowAttached()) { + picture_ids.push_back(i); + } + } + + item_max = picture_ids.size(); + + int required_height = std::max(height - 32, (int)(item_max * menu_item_height)); + if (contents->GetHeight() != required_height) { + SetContents(Bitmap::Create(contents->GetWidth(), required_height)); + } + + contents->Clear(); + + if (item_max == 0) { + contents->TextDraw(0, 0, Font::ColorDisabled, "No Pic"); + } + else { + for (int i = 0; i < item_max; ++i) { + Rect rect = GetItemRect(i); + int id = picture_ids[i]; + auto* pic = Main_Data::game_pictures->GetPicturePtr(id); + + std::string suffix = ""; + if (pic->IsWindowAttached()) suffix = " T"; // Text/String + + std::string text = fmt::format("{:04d}{}", id, suffix); + contents->TextDraw(rect.x, rect.y, Font::ColorDefault, text); + } + } +} + +int Window_DebugPictureList::GetPictureId() const { + if (index >= 0 && index < static_cast(picture_ids.size())) { + return picture_ids[index]; + } + return 0; +} + +// --------------------------------------------------------------------------- + +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; + int row_start_y = y; + + 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() && !is_str) { + contents->TextDraw(0, 0, Font::ColorDisabled, "Empty"); + return; + } + + const auto& d = pic->data; + int y = 0; + + // === COMMON PROPERTIES === + + // 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); + + // === SPECIFIC PROPERTIES === + + 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..54e10cf074 --- /dev/null +++ b/src/window_debug_picture.h @@ -0,0 +1,70 @@ +/* + * 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_selectable.h" +#include "window_base.h" +#include +#include + + /** + * Debug window showing the list of active pictures. + */ +class Window_DebugPictureList : public Window_Selectable { +public: + Window_DebugPictureList(int x, int y, int w, int h); + + void Refresh(); + int GetPictureId() const; + +private: + std::vector picture_ids; +}; + +/** + * 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