From ec308ab453ca427739176c8a0d7b5767d9d8a2fd Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 4 Jun 2025 21:28:39 +0200 Subject: [PATCH 01/11] Changed condition for timer sprite positioning to match behavior of RPG_RT (Fix #3412) --- src/sprite_timer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sprite_timer.cpp b/src/sprite_timer.cpp index 79f926ba77..1cbceb45b3 100644 --- a/src/sprite_timer.cpp +++ b/src/sprite_timer.cpp @@ -23,6 +23,7 @@ #include "game_party.h" #include "game_system.h" #include "game_battle.h" +#include "window_message.h" #include Sprite_Timer::Sprite_Timer(int which) : @@ -90,7 +91,7 @@ void Sprite_Timer::Draw(Bitmap& dst) { if (Game_Battle::IsBattleRunning()) { SetY((Player::screen_height / 3 * 2) - 20); } - else if (Game_Message::IsMessageActive() && Game_Message::GetRealPosition() == 0) { + else if (Game_Message::GetWindow()->GetY() < 20) { SetY(Player::screen_height - 20 - Player::menu_offset_y); } else { From 8cbb694c37d1a7d2b986c3403b5208972a36af73 Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 5 Jun 2025 15:06:36 +0200 Subject: [PATCH 02/11] Refactored window_message, so that page setup is handled after the foreground interpreter has been processed (Fix #2432 / #2932) --- src/window_message.cpp | 23 ++++++++++++++++------- src/window_message.h | 7 ++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/window_message.cpp b/src/window_message.cpp index dbde321640..01da394396 100644 --- a/src/window_message.cpp +++ b/src/window_message.cpp @@ -189,11 +189,8 @@ void Window_Message::StartMessageProcessing(PendingMessage pm) { DebugLog("{}: MSG TEXT \n{}", text); - auto open_frames = (!IsVisible() && !Game_Battle::IsBattleRunning()) ? message_animation_frames : 0; - SetOpenAnimation(open_frames); - DebugLog("{}: MSG START OPEN {}", open_frames); - - InsertNewPage(); + disallow_next_message = true; + msg_was_pushed_this_frame = true; } void Window_Message::OnFinishPage() { @@ -399,7 +396,7 @@ void Window_Message::Update() { if (IsClosing()) { DebugLog("{}: MSG CLOSING"); } close_started_this_frame = false; - close_finished_this_frame = false; + disallow_next_message = false; const bool was_closing = IsClosing(); @@ -408,10 +405,22 @@ void Window_Message::Update() { gold_window->Update(); if (was_closing && !IsClosing()) { - close_finished_this_frame = true; + disallow_next_message = true; } if (!IsVisible()) { + if (msg_was_pushed_this_frame) { + msg_was_pushed_this_frame = false; + disallow_next_message = true; + return; + } + if (!text.empty() && text_index == text.data()) { + auto open_frames = (!IsVisible() && !Game_Battle::IsBattleRunning()) ? message_animation_frames : 0; + SetOpenAnimation(open_frames); + DebugLog("{}: MSG START OPEN {}", open_frames); + + InsertNewPage(); + } return; } diff --git a/src/window_message.h b/src/window_message.h index 8d90546968..4c5b10325d 100644 --- a/src/window_message.h +++ b/src/window_message.h @@ -165,8 +165,9 @@ class Window_Message: public Window_Selectable { // FIXME: This hacky flags exist because RPG_RT likely animates the message window // after the game loop finishes. Our code isn't structured that way, so we must hack // around it. + bool msg_was_pushed_this_frame = false; bool close_started_this_frame = false; - bool close_finished_this_frame = false; + bool disallow_next_message = false; /** Frames to wait when a message wait command was used */ int wait_count = 0; @@ -214,8 +215,8 @@ inline AsyncOp Window_Message::GetAsyncOp() const { } inline bool Window_Message::GetAllowNextMessage(bool foreground) const { - bool is_active = (IsVisible() || close_finished_this_frame); - return foreground ? !is_active || close_started_this_frame : !is_active; + bool is_active = (IsVisible() || disallow_next_message); + return foreground ? !is_active || (close_started_this_frame && !disallow_next_message): !is_active; } inline int Window_Message::GetMaxLinesPerPage() const { From 34ce00d248424a5e0cafbca592c4a07374ff422f Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 5 Jun 2025 15:40:06 +0200 Subject: [PATCH 03/11] Updated panorama rendering code to clear offscreen areas that shouldn't be shown when screen shaking is applied (Fix #2607) --- src/plane.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/plane.cpp b/src/plane.cpp index a779b059f4..f1bd19fc48 100644 --- a/src/plane.cpp +++ b/src/plane.cpp @@ -50,11 +50,13 @@ void Plane::Draw(Bitmap& dst) { int src_x = -ox - GetRenderOx(); int src_y = -oy - GetRenderOy(); + int offset_x = 0; + // Apply screen shaking const int shake_x = Main_Data::game_screen->GetShakeOffsetX(); const int shake_y = Main_Data::game_screen->GetShakeOffsetY(); if (Game_Map::LoopHorizontal()) { - src_x += shake_x; + offset_x = shake_x; } else { // The panorama occupies the same rectangle as the whole map. // Using coordinates where the top-left of the screen is the origin... @@ -83,10 +85,17 @@ void Plane::Draw(Bitmap& dst) { dst_rect.width = bg_width; // Correct the offset if the top-left corner moved. - src_x += shake_x + bg_x; + offset_x = shake_x + bg_x; } src_y += shake_y; - dst.TiledBlit(src_x, src_y, source->GetRect(), *source, dst_rect, 255); + dst.TiledBlit(src_x + offset_x, src_y, source->GetRect(), *source, dst_rect, 255); + if (offset_x < 0) { + auto clear_rect = Rect(dst.GetRect().x, dst.GetRect().y, abs(offset_x), dst.GetRect().height); + dst.ClearRect(clear_rect); + } else if (offset_x > 0) { + auto clear_rect = Rect(dst.GetRect().width - offset_x, dst.GetRect().y, offset_x, dst.GetRect().height); + dst.ClearRect(clear_rect); + } } From 73fd715826c86a7c82b5faaae9a5fc9ef88e15fc Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 11 Jun 2025 07:12:47 +0200 Subject: [PATCH 04/11] Fix MSVC warning: Potential null pointer dereference in Window_ShopBuy::DrawItem --- src/window_shopbuy.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window_shopbuy.cpp b/src/window_shopbuy.cpp index 2b3ea45ae3..6541d5f6cb 100644 --- a/src/window_shopbuy.cpp +++ b/src/window_shopbuy.cpp @@ -67,6 +67,7 @@ void Window_ShopBuy::DrawItem(int index) { if (!item) { Output::Warning("Window ShopBuy: Invalid item ID {}", item_id); + return; } else { enabled = item->price <= Main_Data::game_party->GetGold() && Main_Data::game_party->GetItemCount(item_id) < Main_Data::game_party->GetMaxItemCount(item_id); price = item->price; From dea3eeacf65d83c12ea73f5d951a90e6051e9703 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 11 Jun 2025 07:14:28 +0200 Subject: [PATCH 05/11] Fix MSVC warning in Spriteset_Battle --- src/spriteset_battle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spriteset_battle.cpp b/src/spriteset_battle.cpp index c9dbcf632a..02fd57b3ed 100644 --- a/src/spriteset_battle.cpp +++ b/src/spriteset_battle.cpp @@ -31,7 +31,7 @@ #include "sprite_actor.h" #include "sprite_enemy.h" -Spriteset_Battle::Spriteset_Battle(const std::string bg_name, int terrain_id) +Spriteset_Battle::Spriteset_Battle(std::string bg_name, int terrain_id) { background_name = std::move(bg_name); From 6a55ced981d54209fbe8e84ee78793056ca3d985 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 11 Jun 2025 07:15:36 +0200 Subject: [PATCH 06/11] Fix MSVC warning in Game_Character::CalculateMoveRoute: List iteration might create unnecessarily expensive copy --- src/game_character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_character.cpp b/src/game_character.cpp index caf7ad8542..f6128442a9 100644 --- a/src/game_character.cpp +++ b/src/game_character.cpp @@ -1054,7 +1054,7 @@ bool Game_Character::CalculateMoveRoute(const CalculateMoveRouteArgs& args) { route.skippable = args.skip_when_failed; route.repeat = false; - for (SearchNode node2 : list_move) { + for (SearchNode const& node2 : list_move) { if (node2.direction >= 0) { lcf::rpg::MoveCommand cmd; cmd.command_id = node2.direction; From 8ad963c9902bb79ef62959b1dfe65c5ab598fe0c Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 11 Jun 2025 08:04:44 +0200 Subject: [PATCH 07/11] Minor fix in Audio_Generic: Possible usage of filestream.GetName() after the resource has been moved --- src/audio_generic.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/audio_generic.cpp b/src/audio_generic.cpp index 8eba6bdc8d..2ae15af2c8 100644 --- a/src/audio_generic.cpp +++ b/src/audio_generic.cpp @@ -221,8 +221,9 @@ bool GenericAudio::PlayOnChannel(BgmChannel& chan, Filesystem_Stream::InputStrea chan.paused = true; // Pause channel so the audio thread doesn't work on it chan.stopped = false; // Unstop channel so the audio thread doesn't delete it + std::string_view name = filestream.GetName(); if (!filestream) { - Output::Warning("BGM file not readable: {}", filestream.GetName()); + Output::Warning("BGM file not readable: {}", name); return false; } @@ -272,7 +273,7 @@ bool GenericAudio::PlayOnChannel(BgmChannel& chan, Filesystem_Stream::InputStrea return true; } else { - Output::Warning("Couldn't play BGM {}. Format not supported", filestream.GetName()); + Output::Warning("Couldn't play BGM {}. Format not supported", name); } return false; From d280a41caefbd486c1adcdebcde658c4f1b0ceb9 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 11 Jun 2025 13:45:52 +0200 Subject: [PATCH 08/11] ScreenShake fix: Outside areas should only be cleared when the map is not scrolling horizontally --- src/plane.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/plane.cpp b/src/plane.cpp index f1bd19fc48..28b1764578 100644 --- a/src/plane.cpp +++ b/src/plane.cpp @@ -90,12 +90,14 @@ void Plane::Draw(Bitmap& dst) { src_y += shake_y; dst.TiledBlit(src_x + offset_x, src_y, source->GetRect(), *source, dst_rect, 255); - if (offset_x < 0) { - auto clear_rect = Rect(dst.GetRect().x, dst.GetRect().y, abs(offset_x), dst.GetRect().height); - dst.ClearRect(clear_rect); - } else if (offset_x > 0) { - auto clear_rect = Rect(dst.GetRect().width - offset_x, dst.GetRect().y, offset_x, dst.GetRect().height); - dst.ClearRect(clear_rect); + if (!Game_Map::LoopHorizontal()) { + if (offset_x < 0) { + auto clear_rect = Rect(dst.GetRect().x, dst.GetRect().y, abs(offset_x), dst.GetRect().height); + dst.ClearRect(clear_rect); + } else if (offset_x > 0) { + auto clear_rect = Rect(dst.GetRect().width - offset_x, dst.GetRect().y, offset_x, dst.GetRect().height); + dst.ClearRect(clear_rect); + } } } From 8cd6fc94be2a6f155ff9207262606d63dae5e4c2 Mon Sep 17 00:00:00 2001 From: florianessl Date: Sat, 28 Jun 2025 11:54:40 +0200 Subject: [PATCH 09/11] Added note to explain the change in sprite_timer.cpp --- src/sprite_timer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sprite_timer.cpp b/src/sprite_timer.cpp index 1cbceb45b3..dc23bc52e3 100644 --- a/src/sprite_timer.cpp +++ b/src/sprite_timer.cpp @@ -91,6 +91,9 @@ void Sprite_Timer::Draw(Bitmap& dst) { if (Game_Battle::IsBattleRunning()) { SetY((Player::screen_height / 3 * 2) - 20); } + // RPG_RT doesn't check for the global setting for window positioning (Top/Middle/Bottom) + // here. It actually just checks for the Y position of the message window, regardless if + // it is active or visible. else if (Game_Message::GetWindow()->GetY() < 20) { SetY(Player::screen_height - 20 - Player::menu_offset_y); } From 8cf98f3081091c529125a9cbfa5eab41471540de Mon Sep 17 00:00:00 2001 From: Ghabry Date: Sun, 14 Dec 2025 16:18:14 +0100 Subject: [PATCH 10/11] ScreenShake: Fix the math for detection of outside areas --- src/plane.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plane.cpp b/src/plane.cpp index 28b1764578..d4c9806cd2 100644 --- a/src/plane.cpp +++ b/src/plane.cpp @@ -84,20 +84,26 @@ void Plane::Draw(Bitmap& dst) { dst_rect.x = bg_x; dst_rect.width = bg_width; + if (Game_Map::GetDisplayX() / 16 + Player::screen_width > Game_Map::GetTilesX() * TILE_SIZE) { + // Do not draw out of bounds to the right + dst_rect.width -= (Game_Map::GetDisplayX() / 16 + Player::screen_width) - (Game_Map::GetTilesX() * TILE_SIZE); + } + // Correct the offset if the top-left corner moved. offset_x = shake_x + bg_x; } src_y += shake_y; dst.TiledBlit(src_x + offset_x, src_y, source->GetRect(), *source, dst_rect, 255); + if (!Game_Map::LoopHorizontal()) { - if (offset_x < 0) { - auto clear_rect = Rect(dst.GetRect().x, dst.GetRect().y, abs(offset_x), dst.GetRect().height); + // Clear out of bounds map area visible during shake + if (offset_x < 0 && src_x + offset_x < 0) { + auto clear_rect = Rect(dst.GetRect().x, dst.GetRect().y, -offset_x, dst.GetRect().height); dst.ClearRect(clear_rect); - } else if (offset_x > 0) { - auto clear_rect = Rect(dst.GetRect().width - offset_x, dst.GetRect().y, offset_x, dst.GetRect().height); + } else if (dst_rect.width < Player::screen_width) { + auto clear_rect = Rect(dst_rect.width, dst.GetRect().y, Player::screen_width - dst_rect.width, dst.GetRect().height); dst.ClearRect(clear_rect); } } } - From 814489c443c407b3460d668e4e3c64249f05cd43 Mon Sep 17 00:00:00 2001 From: Carsten Teibes Date: Tue, 6 Jan 2026 19:51:10 +0100 Subject: [PATCH 11/11] Unify Item/Skill/ShopBuy window draw logic --- src/window_item.cpp | 27 ++++++++++++++------------- src/window_shopbuy.cpp | 19 ++++++++----------- src/window_skill.cpp | 18 ++++++++---------- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/window_item.cpp b/src/window_item.cpp index dedac43052..592452a2e7 100644 --- a/src/window_item.cpp +++ b/src/window_item.cpp @@ -108,26 +108,27 @@ void Window_Item::DrawItem(int index) { contents->ClearRect(rect); int item_id = data[index]; + if (item_id <= 0) + return; - if (item_id > 0) { - int number = Main_Data::game_party->GetItemCount(item_id); + int number = Main_Data::game_party->GetItemCount(item_id); - // Items are guaranteed to be valid - const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id); - if (actor) { - if (item->use_skill) { - number += actor->GetItemCount(item_id); - } + // Items are guaranteed to be valid + const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id); + if (actor) { + if (item->use_skill) { + number += actor->GetItemCount(item_id); } + } - bool enabled = CheckEnable(item_id); - DrawItemName(*item, rect.x, rect.y, enabled); + bool enabled = CheckEnable(item_id); + DrawItemName(*item, rect.x, rect.y, enabled); - Font::SystemColor color = enabled ? Font::ColorDefault : Font::ColorDisabled; - contents->TextDraw(rect.x + rect.width - 24, rect.y, color, fmt::format("{}{:3d}", lcf::rpg::Terms::TermOrDefault(lcf::Data::terms.easyrpg_item_number_separator, ":"), number)); - } + Font::SystemColor color = enabled ? Font::ColorDefault : Font::ColorDisabled; + contents->TextDraw(rect.x + rect.width - 24, rect.y, color, fmt::format("{}{:3d}", lcf::rpg::Terms::TermOrDefault(lcf::Data::terms.easyrpg_item_number_separator, ":"), number)); } + void Window_Item::UpdateHelp() { help_window->SetText(GetItem() == nullptr ? "" : ToString(GetItem()->description)); } diff --git a/src/window_shopbuy.cpp b/src/window_shopbuy.cpp index 6541d5f6cb..715a0bce83 100644 --- a/src/window_shopbuy.cpp +++ b/src/window_shopbuy.cpp @@ -57,28 +57,25 @@ void Window_ShopBuy::Refresh() { } void Window_ShopBuy::DrawItem(int index) { + Rect rect = GetItemRect(index); + contents->ClearRect(rect); + int item_id = data[index]; + if (item_id <= 0) + return; // (Shop) items are guaranteed to be valid const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id); - - int price = 0; - bool enabled = false; - if (!item) { Output::Warning("Window ShopBuy: Invalid item ID {}", item_id); return; - } else { - enabled = item->price <= Main_Data::game_party->GetGold() && Main_Data::game_party->GetItemCount(item_id) < Main_Data::game_party->GetMaxItemCount(item_id); - price = item->price; } - Rect rect = GetItemRect(index); - contents->ClearRect(rect); + bool enabled = CheckEnable(item_id); DrawItemName(*item, rect.x, rect.y, enabled); - std::string str = std::to_string(price); - contents->TextDraw(rect.width, rect.y, enabled ? Font::ColorDefault : Font::ColorDisabled, str, Text::AlignRight); + Font::SystemColor color = enabled ? Font::ColorDefault : Font::ColorDisabled; + contents->TextDraw(rect.width, rect.y, color, std::to_string(item->price), Text::AlignRight); } void Window_ShopBuy::UpdateHelp() { diff --git a/src/window_skill.cpp b/src/window_skill.cpp index b237f9c7e0..91b2dc740b 100644 --- a/src/window_skill.cpp +++ b/src/window_skill.cpp @@ -76,18 +76,16 @@ void Window_Skill::DrawItem(int index) { contents->ClearRect(rect); int skill_id = data[index]; + if (skill_id <= 0) + return; - if (skill_id > 0) { - int costs = actor->CalculateSkillCost(skill_id); + bool enabled = CheckEnable(skill_id); + Font::SystemColor color = enabled ? Font::ColorDefault : Font::ColorDisabled; + int costs = actor->CalculateSkillCost(skill_id); + contents->TextDraw(rect.x + rect.width - 24, rect.y, color, fmt::format("{}{:3d}", lcf::rpg::Terms::TermOrDefault(lcf::Data::terms.easyrpg_skill_cost_separator, "-"), costs)); - bool enabled = CheckEnable(skill_id); - int color = !enabled ? Font::ColorDisabled : Font::ColorDefault; - - contents->TextDraw(rect.x + rect.width - 24, rect.y, color, fmt::format("{}{:3d}", lcf::rpg::Terms::TermOrDefault(lcf::Data::terms.easyrpg_skill_cost_separator, "-"), costs)); - - // Skills are guaranteed to be valid - DrawSkillName(*lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id), rect.x, rect.y, enabled); - } + // Skills are guaranteed to be valid + DrawSkillName(*lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id), rect.x, rect.y, enabled); } void Window_Skill::UpdateHelp() {