From 1e99728d3bbc775396b35c9b95ad4d5868f34370 Mon Sep 17 00:00:00 2001 From: MackValentine Date: Tue, 28 May 2024 21:27:15 +0200 Subject: [PATCH 01/12] 9992, ValueOrvarible?, PictureID, Mode, ValueOrvarible? (*), variableID (*) Mode = 0 : Initialisation Mode = 1 : RightAfter, you need ShowChoices command. Everything is automatic Mode = 2 : Update You will need parameters after Mode Mode = 3 : Disable window Params with (*) is only for mode 2. You always need mode 0 before mode 1/2/3 Mode 2/3 are for "manual" system Mode 1 is for "automatic" system --- src/game_interpreter.cpp | 144 +++++++++++++++++++++++++++++++++++++++ src/game_interpreter.h | 2 + src/game_windows.cpp | 2 + 3 files changed, 148 insertions(+) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 84dc8748fd..4eeb7da82c 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -835,6 +835,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CommandManiacCallCommand(com); case static_cast(2053): //Cmd::EasyRpg_SetInterpreterFlag return CommandEasyRpgSetInterpreterFlag(com); + case 9992: + return CommandShowStringPicSelectable(com); default: return true; } @@ -5198,3 +5200,145 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { return value; } + +bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand const& com) { + + int picIndex = ValueOrVariable(com.parameters[0], com.parameters[1]); + + int type = com.parameters[2]; + + // Output::Debug("CommandShowStringPicSelectable pic Index : {}, {}", picIndex, type); + + if (!Main_Data::game_windows->GetWindow(picIndex).window) + Output::Info("ShowStringPic {} doesn't exist", picIndex); + else { + if (type == 0) { + Main_Data::game_windows->GetWindow(picIndex).window->SetIndex(0); + Main_Data::game_windows->GetWindow(picIndex).window->SetActive(true); + auto text = Main_Data::game_windows->GetWindow(picIndex).data.texts; + int maxItem = 0; + for (const auto& text : text) { + std::stringstream ss(ToString(text.text)); + std::string out; + while (Utils::ReadLine(ss, out)) { + maxItem++; + } + } + Main_Data::game_windows->GetWindow(picIndex).window->SetItemMax(maxItem); + } + else if (type == 1) { + if (Main_Data::game_windows->GetWindow(picIndex).window->GetActive()) { + Main_Data::game_windows->GetWindow(picIndex).window->Update(); + + if (Input::IsTriggered(Input::CANCEL)) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel)); + int choice_result = Main_Data::game_windows->GetWindow(picIndex).window->GetPageItemMax(); + //Output::Debug("CANCEL {}", choice_result); + + + int label_id = com.parameters[0]; + + auto& frame = GetFrame(); + const auto& list = frame.commands; + auto& index = frame.current_command; + auto& old_index = index; + int indent = list[index].indent; + + int i = 0; + + for (int idx = 0; (size_t)idx < list.size(); idx++) { + if (static_cast(list[idx].code) != Cmd::ShowChoiceOption) + continue; + //if (list[idx].parameters[0] != label_id) + if (list[idx].indent > indent) + continue; + if (list[idx].indent == indent) { + //Output::Debug("Indent : {} {} {}", i, list[idx].indent, com.indent); + if (i != choice_result) { + i++; + continue; + } + } + if (list[idx].indent < indent) { + //Output::Debug("Indent : {} {}", list[idx].indent, com.indent); + break; + } + index = idx + 1; + break; + } + + return true; + } + if (Input::IsTriggered(Input::DECISION)) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision)); + int choice_result = Main_Data::game_windows->GetWindow(picIndex).window->GetIndex(); + //Output::Debug("CONFIRM {}", choice_result); + + + int label_id = com.parameters[0]; + + auto& frame = GetFrame(); + const auto& list = frame.commands; + auto& index = frame.current_command; + auto& old_index = index; + int indent = list[index].indent; + + int i = 0; + + for (int idx = 0; (size_t)idx < list.size(); idx++) { + if (static_cast(list[idx].code) != Cmd::ShowChoiceOption) + continue; + //if (list[idx].parameters[0] != label_id) + if (list[idx].indent > indent) + continue; + if (list[idx].indent == indent) { + //Output::Debug("Indent : {} {} {}", i, list[idx].indent, com.indent); + if (i != choice_result) { + i++; + continue; + } + } + if (list[idx].indent < indent) { + //Output::Debug("Indent : {} {}", list[idx].indent, com.indent); + break; + } + index = idx + 1; + break; + } + + return true; + } + } + else { + //Output::Debug("!Active"); + Main_Data::game_windows->GetWindow(picIndex).window->SetIndex(0); + Main_Data::game_windows->GetWindow(picIndex).window->SetActive(true); + auto text = Main_Data::game_windows->GetWindow(picIndex).data.texts; + int maxItem = 0; + for (const auto& text : text) { + std::stringstream ss(ToString(text.text)); + std::string out; + while (Utils::ReadLine(ss, out)) { + maxItem++; + } + } + Main_Data::game_windows->GetWindow(picIndex).window->SetItemMax(maxItem); + return false; + } + + return false; + } + else if (type == 2) { + Main_Data::game_windows->GetWindow(picIndex).window->Update(); + + int variableID = ValueOrVariable(com.parameters[3], com.parameters[4]); + int value = Main_Data::game_windows->GetWindow(picIndex).window->GetIndex(); + Main_Data::game_variables->Set(variableID, value); + } + else if (type == 3) { + Main_Data::game_windows->GetWindow(picIndex).window->SetActive(false); + } + } + + return true; +} diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 731aadf5cb..8427e52d9f 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -298,6 +298,8 @@ class Game_Interpreter bool CommandManiacCallCommand(lcf::rpg::EventCommand const& com); bool CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com); + bool CommandShowStringPicSelectable(lcf::rpg::EventCommand const& com); + int DecodeInt(lcf::DBArray::const_iterator& it); const std::string DecodeString(lcf::DBArray::const_iterator& it); lcf::rpg::MoveCommand DecodeMove(lcf::DBArray::const_iterator& it); diff --git a/src/game_windows.cpp b/src/game_windows.cpp index 2a3850722a..dc755c8aaa 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -354,6 +354,8 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { window->CreateContents(); window->SetVisible(false); + window->SetActive(false); + BitmapRef system; // FIXME: Transparency setting is currently not applied to the system graphic // Disabling transparency breaks the rendering of the system graphic From 8ffeeaa438cc6fd4659249bdf2143ead02e49dd6 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:08:58 -0300 Subject: [PATCH 02/12] CommandShowStringPicSelectable - Refactor command became: ```js @raw 9992, "", Mode, // 0: Enable Menu || 1: Disable Menu TargetIsVar, Target, // String Picture's ID OutputIsVar, Output // Optional. A variable that will receive the current selected Menu; ``` --- src/game_interpreter.cpp | 223 +++++++++++++++++---------------------- src/game_interpreter.h | 3 + 2 files changed, 98 insertions(+), 128 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 4eeb7da82c..f0de142068 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -835,7 +835,7 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CommandManiacCallCommand(com); case static_cast(2053): //Cmd::EasyRpg_SetInterpreterFlag return CommandEasyRpgSetInterpreterFlag(com); - case 9992: + case static_cast(9992): return CommandShowStringPicSelectable(com); default: return true; @@ -5202,142 +5202,109 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { } bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand const& com) { + int mode = com.parameters[0]; + int strpic_index = ValueOrVariable(com.parameters[1], com.parameters[2]); + int output_variable = ValueOrVariable(com.parameters[3], com.parameters[4]); - int picIndex = ValueOrVariable(com.parameters[0], com.parameters[1]); - - int type = com.parameters[2]; - - // Output::Debug("CommandShowStringPicSelectable pic Index : {}, {}", picIndex, type); - - if (!Main_Data::game_windows->GetWindow(picIndex).window) - Output::Info("ShowStringPic {} doesn't exist", picIndex); - else { - if (type == 0) { - Main_Data::game_windows->GetWindow(picIndex).window->SetIndex(0); - Main_Data::game_windows->GetWindow(picIndex).window->SetActive(true); - auto text = Main_Data::game_windows->GetWindow(picIndex).data.texts; - int maxItem = 0; - for (const auto& text : text) { - std::stringstream ss(ToString(text.text)); - std::string out; - while (Utils::ReadLine(ss, out)) { - maxItem++; - } - } - Main_Data::game_windows->GetWindow(picIndex).window->SetItemMax(maxItem); - } - else if (type == 1) { - if (Main_Data::game_windows->GetWindow(picIndex).window->GetActive()) { - Main_Data::game_windows->GetWindow(picIndex).window->Update(); - - if (Input::IsTriggered(Input::CANCEL)) { - Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel)); - int choice_result = Main_Data::game_windows->GetWindow(picIndex).window->GetPageItemMax(); - //Output::Debug("CANCEL {}", choice_result); - - - int label_id = com.parameters[0]; - - auto& frame = GetFrame(); - const auto& list = frame.commands; - auto& index = frame.current_command; - auto& old_index = index; - int indent = list[index].indent; - - int i = 0; - - for (int idx = 0; (size_t)idx < list.size(); idx++) { - if (static_cast(list[idx].code) != Cmd::ShowChoiceOption) - continue; - //if (list[idx].parameters[0] != label_id) - if (list[idx].indent > indent) - continue; - if (list[idx].indent == indent) { - //Output::Debug("Indent : {} {} {}", i, list[idx].indent, com.indent); - if (i != choice_result) { - i++; - continue; - } - } - if (list[idx].indent < indent) { - //Output::Debug("Indent : {} {}", list[idx].indent, com.indent); - break; - } - index = idx + 1; - break; - } + if (!Main_Data::game_windows->GetWindow(strpic_index).window) { + Output::Warning("String Picture Menu - String Picture {} doesn't exist", strpic_index); + return true; + } - return true; - } - if (Input::IsTriggered(Input::DECISION)) { - Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision)); - int choice_result = Main_Data::game_windows->GetWindow(picIndex).window->GetIndex(); - //Output::Debug("CONFIRM {}", choice_result); - - - int label_id = com.parameters[0]; - - auto& frame = GetFrame(); - const auto& list = frame.commands; - auto& index = frame.current_command; - auto& old_index = index; - int indent = list[index].indent; - - int i = 0; - - for (int idx = 0; (size_t)idx < list.size(); idx++) { - if (static_cast(list[idx].code) != Cmd::ShowChoiceOption) - continue; - //if (list[idx].parameters[0] != label_id) - if (list[idx].indent > indent) - continue; - if (list[idx].indent == indent) { - //Output::Debug("Indent : {} {} {}", i, list[idx].indent, com.indent); - if (i != choice_result) { - i++; - continue; - } - } - if (list[idx].indent < indent) { - //Output::Debug("Indent : {} {}", list[idx].indent, com.indent); - break; - } - index = idx + 1; - break; - } + auto& window = Main_Data::game_windows->GetWindow(strpic_index).window; + bool is_menu_active = window->GetActive(); - return true; - } - } - else { - //Output::Debug("!Active"); - Main_Data::game_windows->GetWindow(picIndex).window->SetIndex(0); - Main_Data::game_windows->GetWindow(picIndex).window->SetActive(true); - auto text = Main_Data::game_windows->GetWindow(picIndex).data.texts; - int maxItem = 0; - for (const auto& text : text) { - std::stringstream ss(ToString(text.text)); - std::string out; - while (Utils::ReadLine(ss, out)) { - maxItem++; - } - } - Main_Data::game_windows->GetWindow(picIndex).window->SetItemMax(maxItem); - return false; - } + if (mode == 0) { // ENABLE MENU + if (!is_menu_active) { + if (window->GetIndex() == -1) + window->SetIndex(0); + window->SetActive(true); + InitializeMenu(strpic_index); return false; } - else if (type == 2) { - Main_Data::game_windows->GetWindow(picIndex).window->Update(); - int variableID = ValueOrVariable(com.parameters[3], com.parameters[4]); - int value = Main_Data::game_windows->GetWindow(picIndex).window->GetIndex(); - Main_Data::game_variables->Set(variableID, value); + window->Update(); + + if (Input::IsTriggered(Input::DECISION) || Input::IsTriggered(Input::CANCEL)) { + window->SetActive(false); + return HandleMenuSelection(strpic_index, output_variable); + } + + return false; + } + else if (mode == 1) { // DISABLE MENU + window->SetIndex(-1); + window->SetActive(false); + } + + return true; +} + +void Game_Interpreter::InitializeMenu(int strpic_index) { + auto text_data = Main_Data::game_windows->GetWindow(strpic_index).data.texts; + int max_item = 0; + for (const auto& text : text_data) { + std::stringstream ss(ToString(text.text)); + std::string out; + while (Utils::ReadLine(ss, out)) { + max_item++; + } + } + Main_Data::game_windows->GetWindow(strpic_index).window->SetItemMax(max_item); +} + +bool Game_Interpreter::HandleMenuSelection(int strpic_index, int output_variable) { + auto& frame = GetFrame(); + const auto& list = frame.commands; + auto& index = frame.current_command; + int indent = list[index].indent; + + int choice_result = 0; + auto choice_controller = list[index + 1]; + + if (Input::IsTriggered(Input::DECISION)) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision)); + choice_result = Main_Data::game_windows->GetWindow(strpic_index).window->GetIndex(); + } + + if (Input::IsTriggered(Input::CANCEL)) { + if (static_cast(choice_controller.code) == Cmd::ShowChoice) { + choice_result = choice_controller.parameters[0] - 1; + + if (choice_result == -1) { + Main_Data::game_windows->GetWindow(strpic_index).window->SetActive(true); + return false; + } + } + else if (choice_result == 0) { + choice_result = -1; + } + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel)); + } + + if (output_variable > 0) { + Main_Data::game_variables->Set(output_variable, choice_result); + Game_Map::SetNeedRefresh(true); + } + + int i = 0; + for (int idx = index; (size_t)idx < list.size(); idx++) { + if (static_cast(list[idx].code) != Cmd::ShowChoiceOption) + continue; + if (list[idx].indent > indent) + continue; + if (list[idx].indent == indent) { + if (i != choice_result) { + i++; + continue; + } } - else if (type == 3) { - Main_Data::game_windows->GetWindow(picIndex).window->SetActive(false); + if (list[idx].indent < indent) { + break; } + index = idx + 1; + break; } return true; diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 8427e52d9f..9fc0c0b4c7 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -299,6 +299,9 @@ class Game_Interpreter bool CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com); bool CommandShowStringPicSelectable(lcf::rpg::EventCommand const& com); + + void InitializeMenu(int strpic_index); + bool HandleMenuSelection(int strpic_index, int output_variable); int DecodeInt(lcf::DBArray::const_iterator& it); const std::string DecodeString(lcf::DBArray::const_iterator& it); From 6e38514a7ced3a3f2173f98a36e60902823bb77c Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:45:46 -0300 Subject: [PATCH 03/12] CommandShowStringPicSelectable - Menu Inherits StrPic System To do that, I flicker between currently used system and the system extracted from a strpic. Kinda hacky, but it does its job. --- src/game_interpreter.cpp | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index f0de142068..ddab4f8822 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5206,14 +5206,36 @@ bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand con int strpic_index = ValueOrVariable(com.parameters[1], com.parameters[2]); int output_variable = ValueOrVariable(com.parameters[3], com.parameters[4]); - if (!Main_Data::game_windows->GetWindow(strpic_index).window) { + auto& window_data = Main_Data::game_windows->GetWindow(strpic_index); + + if (!window_data.window) { Output::Warning("String Picture Menu - String Picture {} doesn't exist", strpic_index); return true; } - auto& window = Main_Data::game_windows->GetWindow(strpic_index).window; + auto& window = window_data.window; + auto& data = window_data.data; bool is_menu_active = window->GetActive(); + auto& game_system = Main_Data::game_system; + struct SystemProperties { + std::string name; + lcf::rpg::System::Stretch stretch; + lcf::rpg::System::Font font; + }; + + SystemProperties current_system = { + ToString(game_system->GetSystemName()), + static_cast(game_system->GetMessageStretch()), + static_cast(game_system->GetFontId()) + }; + + SystemProperties strpic_system = { + ToString(data.system_name), + static_cast(data.message_stretch), + current_system.font + }; + if (mode == 0) { // ENABLE MENU if (!is_menu_active) { if (window->GetIndex() == -1) @@ -5224,7 +5246,9 @@ bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand con return false; } + game_system->SetSystemGraphic(strpic_system.name, strpic_system.stretch, strpic_system.font); window->Update(); + game_system->SetSystemGraphic(current_system.name, current_system.stretch, current_system.font); if (Input::IsTriggered(Input::DECISION) || Input::IsTriggered(Input::CANCEL)) { window->SetActive(false); @@ -5242,10 +5266,10 @@ bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand con } void Game_Interpreter::InitializeMenu(int strpic_index) { - auto text_data = Main_Data::game_windows->GetWindow(strpic_index).data.texts; + auto data = Main_Data::game_windows->GetWindow(strpic_index).data; int max_item = 0; - for (const auto& text : text_data) { - std::stringstream ss(ToString(text.text)); + for (const auto& texts : data.texts) { + std::stringstream ss(ToString(texts.text)); std::string out; while (Utils::ReadLine(ss, out)) { max_item++; From 23a3f4c858753d4a6a66e686fc51d45c134dfe9e Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:39:29 -0300 Subject: [PATCH 04/12] CommandShowStringPicSelectable - Add support to Line spacing. There's a hardcoded 4 in window_selectable.cpp I wonder if that value is always like this. Also when the line spacer is smaller than 4, it creates a scrollable menu that is not necessary. --- src/game_interpreter.cpp | 1 + src/window_selectable.cpp | 5 +++++ src/window_selectable.h | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index ddab4f8822..f62e88b037 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5276,6 +5276,7 @@ void Game_Interpreter::InitializeMenu(int strpic_index) { } } Main_Data::game_windows->GetWindow(strpic_index).window->SetItemMax(max_item); + Main_Data::game_windows->GetWindow(strpic_index).window->SetMenuItemLineSpacing(data.texts[0].line_spacing); } bool Game_Interpreter::HandleMenuSelection(int strpic_index, int output_variable) { diff --git a/src/window_selectable.cpp b/src/window_selectable.cpp index a414a7e260..8ebd374c28 100644 --- a/src/window_selectable.cpp +++ b/src/window_selectable.cpp @@ -116,6 +116,7 @@ void Window_Selectable::UpdateCursorRect() { x = (index % column_max * (cursor_width + 8)) - 4; int y = index / column_max * menu_item_height - oy; + y += ( menu_item_line_spacing * index ) - (4 * index); // calculate spacing between lines SetCursorRect(Rect(x, y, cursor_width, menu_item_height)); } @@ -235,6 +236,10 @@ void Window_Selectable::SetMenuItemHeight(int height) { menu_item_height = height; } +void Window_Selectable::SetMenuItemLineSpacing(int spacing) { + menu_item_line_spacing = spacing; +} + void Window_Selectable::SetSingleColumnWrapping(bool wrap) { wrap_limit = wrap ? 1 : 2; } diff --git a/src/window_selectable.h b/src/window_selectable.h index b676f7140d..54e0bccdc2 100644 --- a/src/window_selectable.h +++ b/src/window_selectable.h @@ -92,6 +92,13 @@ class Window_Selectable: public Window_Base { */ void SetMenuItemHeight(int height); + /** + * Sets the menu item height. + * + * @param spacing between menu items. + */ + void SetMenuItemLineSpacing(int spacing); + /** * Allow left/right input to move cursor up/down when the selectable has only one column. * By default this behaviour is only enabled for two and more columns. @@ -112,6 +119,7 @@ class Window_Selectable: public Window_Base { bool endless_scrolling = true; int menu_item_height = 16; + int menu_item_line_spacing = 4; int scroll_dir = 0; int scroll_progress = 0; From fafaf78d3d357b54347eb9048c91e853d4133bef Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Wed, 19 Jun 2024 08:52:33 -0300 Subject: [PATCH 05/12] CommandShowStringPicSelectable - Fix cursor's x position when menu is borderless more hardcoded numbers to re-center the menu when the border is off Still need to figure out a problem when cursor index is 0 and when it's max amount of items. The problem is that half of those cursors are cut in half, because it goes beyond its image container. --- src/game_interpreter.cpp | 3 ++- src/window_selectable.cpp | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index f62e88b037..236f2395c9 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5238,11 +5238,12 @@ bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand con if (mode == 0) { // ENABLE MENU if (!is_menu_active) { + InitializeMenu(strpic_index); + if (window->GetIndex() == -1) window->SetIndex(0); window->SetActive(true); - InitializeMenu(strpic_index); return false; } diff --git a/src/window_selectable.cpp b/src/window_selectable.cpp index 8ebd374c28..d9f7dac770 100644 --- a/src/window_selectable.cpp +++ b/src/window_selectable.cpp @@ -117,7 +117,15 @@ void Window_Selectable::UpdateCursorRect() { int y = index / column_max * menu_item_height - oy; y += ( menu_item_line_spacing * index ) - (4 * index); // calculate spacing between lines - SetCursorRect(Rect(x, y, cursor_width, menu_item_height)); + + int item_height = menu_item_height; + if (border_x == 0) { // When margin doesn't exist + x += 4; + cursor_width += 8; + if (index == 0) item_height += 3; + } + + SetCursorRect(Rect(x, y, cursor_width, item_height)); } void Window_Selectable::UpdateArrows() { From 2812e815828533fd6cb00a9e585b9718fb1b4b54 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:17:59 -0300 Subject: [PATCH 06/12] Game_Windows - Always Calculate and use x_max and y_max Change that fix an issue that happens when calling `window->CreateContents();` - The issue is that It cuts text's image in half, not being possible to recover them when changing the window size. --- src/game_interpreter.cpp | 7 ++++--- src/game_windows.cpp | 14 ++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 236f2395c9..b9b08b282b 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5267,7 +5267,8 @@ bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand con } void Game_Interpreter::InitializeMenu(int strpic_index) { - auto data = Main_Data::game_windows->GetWindow(strpic_index).data; + auto& window_data = Main_Data::game_windows->GetWindow(strpic_index); + auto data = window_data.data; int max_item = 0; for (const auto& texts : data.texts) { std::stringstream ss(ToString(texts.text)); @@ -5276,8 +5277,8 @@ void Game_Interpreter::InitializeMenu(int strpic_index) { max_item++; } } - Main_Data::game_windows->GetWindow(strpic_index).window->SetItemMax(max_item); - Main_Data::game_windows->GetWindow(strpic_index).window->SetMenuItemLineSpacing(data.texts[0].line_spacing); + window_data.window->SetItemMax(max_item); + window_data.window->SetMenuItemLineSpacing(data.texts[0].line_spacing); } bool Game_Interpreter::HandleMenuSelection(int strpic_index, int output_variable) { diff --git a/src/game_windows.cpp b/src/game_windows.cpp index dc755c8aaa..d502a1eb55 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -238,11 +238,11 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { return font->ApplyStyle(style); }; - if (data.width == 0 || data.height == 0) { - // Automatic window size - int x_max = 0; - int y_max = 0; + // Automatic window size + int x_max = 0; + int y_max = 0; + if (true) {//(data.width == 0 || data.height == 0) { for (size_t i = 0; i < data.texts.size(); ++i) { // Lots of duplication with the rendering code below but cannot be easily reduced more auto& font = fonts[i]; @@ -351,7 +351,13 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { // FIXME: Figure out why 0 does not work here (bug in Window class) window->SetBorderY(-3); } + + if (data.width != 0) window->SetWidth(x_max); + if (data.height != 0) window->SetHeight(y_max); window->CreateContents(); + if (data.width != 0) window->SetWidth(data.width); + if (data.height != 0) window->SetHeight(data.height); + window->SetVisible(false); window->SetActive(false); From 2a5e12a2954b406506675262918ccc46a3a607a3 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:46:05 -0300 Subject: [PATCH 07/12] CommandStringPicMenu - Major Refactor - Lambda Functions - Menu can output Position and State of Cursor as variables - Show Choices properly mapped. --- src/game_interpreter.cpp | 198 +++++++++++++++++++++------------------ src/game_interpreter.h | 5 +- src/window.cpp | 2 + 3 files changed, 109 insertions(+), 96 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index b9b08b282b..8a8f850dc3 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -835,8 +835,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CommandManiacCallCommand(com); case static_cast(2053): //Cmd::EasyRpg_SetInterpreterFlag return CommandEasyRpgSetInterpreterFlag(com); - case static_cast(9992): - return CommandShowStringPicSelectable(com); + case static_cast(2057): //Cmd::EasyRpg_StringPicMenu + return CommandStringPicMenu(com); default: return true; } @@ -5201,10 +5201,11 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { return value; } -bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand const& com) { - int mode = com.parameters[0]; - int strpic_index = ValueOrVariable(com.parameters[1], com.parameters[2]); - int output_variable = ValueOrVariable(com.parameters[3], com.parameters[4]); +bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { + const int mode = com.parameters[0]; + const int strpic_index = ValueOrVariable(com.parameters[1], com.parameters[2]); + const int output_var_current_item = ValueOrVariable(com.parameters[3], com.parameters[4]); + const int output_var_input_state = ValueOrVariable(com.parameters[5], com.parameters[6]); auto& window_data = Main_Data::game_windows->GetWindow(strpic_index); @@ -5215,123 +5216,136 @@ bool Game_Interpreter::CommandShowStringPicSelectable(lcf::rpg::EventCommand con auto& window = window_data.window; auto& data = window_data.data; - bool is_menu_active = window->GetActive(); - auto& game_system = Main_Data::game_system; + struct SystemProperties { std::string name; lcf::rpg::System::Stretch stretch; lcf::rpg::System::Font font; }; - SystemProperties current_system = { + const SystemProperties current_system = { ToString(game_system->GetSystemName()), static_cast(game_system->GetMessageStretch()), static_cast(game_system->GetFontId()) }; - SystemProperties strpic_system = { + const SystemProperties strpic_system = { ToString(data.system_name), - static_cast(data.message_stretch), + static_cast(!bool(data.message_stretch)), current_system.font }; - if (mode == 0) { // ENABLE MENU - if (!is_menu_active) { - InitializeMenu(strpic_index); - - if (window->GetIndex() == -1) - window->SetIndex(0); + auto UpdateVariables = [&](int current_item, int input_state) { + if (output_var_current_item > 0) { + if (current_item == -2) current_item = -1; + Main_Data::game_variables->Set(output_var_current_item, current_item); + Game_Map::SetNeedRefresh(true); + } + if (output_var_input_state > 0) { + Main_Data::game_variables->Set(output_var_input_state, input_state); + Game_Map::SetNeedRefresh(true); + } + }; - window->SetActive(true); - return false; + auto GetRealChoiceId = [&](int ui_choice_index, int indent) { //Gets a choice ID from a "Show Choices" command bellow this command. + auto& frame = GetFrame(); + const auto& commands = frame.commands; + auto& current_index = frame.current_command; + + if (ui_choice_index == -2) { + for (size_t idx = current_index + 1; idx < commands.size(); idx++) { + const auto& command = commands[idx]; + if (static_cast(command.code) != Cmd::ShowChoiceOption) continue; + if (command.indent != indent) break; + current_index = idx + 1; + } + return commands[current_index - 1].parameters[0]; } - game_system->SetSystemGraphic(strpic_system.name, strpic_system.stretch, strpic_system.font); - window->Update(); - game_system->SetSystemGraphic(current_system.name, current_system.stretch, current_system.font); + int option_count = 0; + for (size_t idx = current_index + 1; idx < commands.size(); idx++) { + const auto& command = commands[idx]; + if (static_cast(command.code) != Cmd::ShowChoiceOption) continue; + if (command.indent != indent) break; - if (Input::IsTriggered(Input::DECISION) || Input::IsTriggered(Input::CANCEL)) { - window->SetActive(false); - return HandleMenuSelection(strpic_index, output_variable); + if (ui_choice_index != -1 && option_count == ui_choice_index) { + current_index = idx + 1; + return command.parameters[0]; + } + option_count++; } - return false; - } - else if (mode == 1) { // DISABLE MENU - window->SetIndex(-1); - window->SetActive(false); - } + current_index++; + return ui_choice_index; + }; - return true; -} + auto HandleMenuSelection = [&]() -> bool { + auto& frame = GetFrame(); + const auto& commands = frame.commands; + auto& current_index = frame.current_command; + int indent = commands[current_index].indent; + int ui_choice_index = -1; + const auto& choice_controller = commands[current_index + 1]; + bool has_choice_list = (static_cast(choice_controller.code) == Cmd::ShowChoice); + + UpdateVariables(window->GetIndex(), 0); + + auto ProcessSelection = [&](int input_state, int sound) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(sound)); + int real_choice_id = GetRealChoiceId(ui_choice_index, indent); + UpdateVariables(ui_choice_index == -1 ? window->GetIndex() : ui_choice_index, input_state); + //Output::Debug("Selected choice: UI index {} - real Choice ID {}", ui_choice_index, real_choice_id); + window->SetActive(false); + return true; + }; -void Game_Interpreter::InitializeMenu(int strpic_index) { - auto& window_data = Main_Data::game_windows->GetWindow(strpic_index); - auto data = window_data.data; - int max_item = 0; - for (const auto& texts : data.texts) { - std::stringstream ss(ToString(texts.text)); - std::string out; - while (Utils::ReadLine(ss, out)) { - max_item++; + if (Input::IsTriggered(Input::DECISION)) { + ui_choice_index = window->GetIndex(); + return ProcessSelection(1, Main_Data::game_system->SFX_Decision); } - } - window_data.window->SetItemMax(max_item); - window_data.window->SetMenuItemLineSpacing(data.texts[0].line_spacing); -} - -bool Game_Interpreter::HandleMenuSelection(int strpic_index, int output_variable) { - auto& frame = GetFrame(); - const auto& list = frame.commands; - auto& index = frame.current_command; - int indent = list[index].indent; - - int choice_result = 0; - auto choice_controller = list[index + 1]; + else if (Input::IsTriggered(Input::CANCEL)) { + if (has_choice_list) { + ui_choice_index = (choice_controller.parameters[0] == 5) ? -2 : choice_controller.parameters[0] - 1; // -2 means that cancel has a branch + if (ui_choice_index == -1) return false; + } + return ProcessSelection(-1, Main_Data::game_system->SFX_Cancel); + } + return false; + }; - if (Input::IsTriggered(Input::DECISION)) { - Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision)); - choice_result = Main_Data::game_windows->GetWindow(strpic_index).window->GetIndex(); - } + if (mode == 0) { // ENABLE MENU + if (!window->GetActive()) { + int max_item = 0; + for (const auto& text_data : data.texts) { + std::stringstream ss(ToString(text_data.text)); + std::string out; + while (Utils::ReadLine(ss, out)) { + max_item++; + } + } - if (Input::IsTriggered(Input::CANCEL)) { - if (static_cast(choice_controller.code) == Cmd::ShowChoice) { - choice_result = choice_controller.parameters[0] - 1; + window->SetItemMax(max_item); + //window->SetColumnMax(2); + window->SetMenuItemHeight(data.texts[0].font_size + 4); // TODO: item area with bigger font-sizes could look better... + window->SetMenuItemLineSpacing(data.texts[0].line_spacing); + if (window->GetIndex() == -1) window->SetIndex(0); - if (choice_result == -1) { - Main_Data::game_windows->GetWindow(strpic_index).window->SetActive(true); - return false; - } - } - else if (choice_result == 0) { - choice_result = -1; + window->SetActive(true); + UpdateVariables(window->GetIndex(), 0); + return false; } - Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel)); - } - if (output_variable > 0) { - Main_Data::game_variables->Set(output_variable, choice_result); - Game_Map::SetNeedRefresh(true); + //WORKAROUND: I have to flicker between 2 systems to set menu visuals as current stringpic menu. + game_system->SetSystemGraphic(strpic_system.name, strpic_system.stretch, strpic_system.font); + window->Update(); + game_system->SetSystemGraphic(current_system.name, current_system.stretch, current_system.font); + window->SetStretch(strpic_system.stretch); + return HandleMenuSelection(); } - - int i = 0; - for (int idx = index; (size_t)idx < list.size(); idx++) { - if (static_cast(list[idx].code) != Cmd::ShowChoiceOption) - continue; - if (list[idx].indent > indent) - continue; - if (list[idx].indent == indent) { - if (i != choice_result) { - i++; - continue; - } - } - if (list[idx].indent < indent) { - break; - } - index = idx + 1; - break; + else if (mode == 1) { // DISABLE MENU + window->SetIndex(-1); + window->SetActive(false); } return true; diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 9fc0c0b4c7..5e576af719 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -298,10 +298,7 @@ class Game_Interpreter bool CommandManiacCallCommand(lcf::rpg::EventCommand const& com); bool CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com); - bool CommandShowStringPicSelectable(lcf::rpg::EventCommand const& com); - - void InitializeMenu(int strpic_index); - bool HandleMenuSelection(int strpic_index, int output_variable); + bool CommandStringPicMenu(lcf::rpg::EventCommand const& com); int DecodeInt(lcf::DBArray::const_iterator& it); const std::string DecodeString(lcf::DBArray::const_iterator& it); diff --git a/src/window.cpp b/src/window.cpp index 6fc4dcba4b..bafaffe118 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -61,6 +61,8 @@ void Window::Draw(Bitmap& dst) { if (width <= 0 || height <= 0) return; if (x < -width || x > dst.GetWidth() || y < -height || y > dst.GetHeight()) return; + if (back_opacity == 0) dst.ClearRect(Rect(0, 0, width, height)); // WORKAROUND: This may be exclusive to stringPic without backgrounds. + if (windowskin) { if (width > 4 && height > 4 && (back_opacity * opacity / 255 > 0)) { if (background_needs_refresh) RefreshBackground(); From 616128d8dffd55cf5c7d11b1b80ec063fde9c572 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sun, 21 Jul 2024 23:54:49 -0300 Subject: [PATCH 08/12] CommandStringPicMenu - support font resize --- src/game_interpreter.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 8a8f850dc3..308a224bbf 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5327,8 +5327,17 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { window->SetItemMax(max_item); //window->SetColumnMax(2); - window->SetMenuItemHeight(data.texts[0].font_size + 4); // TODO: item area with bigger font-sizes could look better... - window->SetMenuItemLineSpacing(data.texts[0].line_spacing); + if (data.texts.empty()) { + Output::Warning("String Picture Menu - String Picture {} is not valid", strpic_index); + return true; + } + + int item_height = data.texts[0].font_size + 4; + int item_spacing = data.texts[0].line_spacing; + + window->SetMenuItemHeight(item_height); + window->SetMenuItemLineSpacing(item_spacing); + if (window->GetIndex() == -1) window->SetIndex(0); window->SetActive(true); @@ -5340,6 +5349,7 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { game_system->SetSystemGraphic(strpic_system.name, strpic_system.stretch, strpic_system.font); window->Update(); game_system->SetSystemGraphic(current_system.name, current_system.stretch, current_system.font); + window->SetStretch(strpic_system.stretch); return HandleMenuSelection(); } From d1a5164f26e3768219a1208797e1fea2e512ca77 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:02:58 -0300 Subject: [PATCH 09/12] CommandStringPicMenu - Support StringVars --- src/game_interpreter.cpp | 17 ++++++++++++----- src/game_windows.cpp | 17 +++++++++++------ src/game_windows.h | 2 ++ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 308a224bbf..798b8bef37 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5318,15 +5318,22 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { if (!window->GetActive()) { int max_item = 0; for (const auto& text_data : data.texts) { - std::stringstream ss(ToString(text_data.text)); - std::string out; - while (Utils::ReadLine(ss, out)) { - max_item++; + auto pending_message = Main_Data::game_windows->GeneratePendingMessage(ToString(text_data.text)); + const auto& lines = pending_message.GetLines(); + + for (const auto& line : lines) { + std::stringstream ss(line); + std::string sub_line; + + while (Utils::ReadLine(ss, sub_line)) { + max_item++; + // Output::Warning("{}", sub_line); + } } } window->SetItemMax(max_item); - //window->SetColumnMax(2); + //window->SetColumnMax(2); // TODO: is there an easy way to put text near cursor rects? if (data.texts.empty()) { Output::Warning("String Picture Menu - String Picture {} is not valid", strpic_index); return true; diff --git a/src/game_windows.cpp b/src/game_windows.cpp index d502a1eb55..2326846006 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -46,6 +46,16 @@ Game_Windows::Window_User::Window_User(lcf::rpg::SaveEasyRpgWindow save) } +PendingMessage Game_Windows::GeneratePendingMessage(const std::string& text) { + std::stringstream ss(text); + std::string out; + PendingMessage pm(CommandCodeInserter); + while (Utils::ReadLine(ss, out)) { + pm.PushLine(out); + } + return pm; +} + void Game_Windows::SetSaveData(std::vector save) { windows.clear(); @@ -218,12 +228,7 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { fonts.emplace_back(font); std::stringstream ss(ToString(text.text)); - std::string out; - PendingMessage pm(CommandCodeInserter); - while (Utils::ReadLine(ss, out)) { - pm.PushLine(out); - } - messages.emplace_back(pm); + messages.emplace_back(GeneratePendingMessage(ToString(text.text))); } auto apply_style = [](auto& font, const auto& text) { diff --git a/src/game_windows.h b/src/game_windows.h index 1d4e6f9039..88000a829c 100644 --- a/src/game_windows.h +++ b/src/game_windows.h @@ -41,6 +41,8 @@ class Game_Windows { public: Game_Windows() = default; + static PendingMessage GeneratePendingMessage(const std::string& text); + void SetSaveData(std::vector save); std::vector GetSaveData() const; From fe7f49da569c4e926fa43605d98c1d9efb52e541 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:50:09 -0300 Subject: [PATCH 10/12] String Pictures - Reduce Duplicated Code There was a big chunk of code that was written twice inside two for loops. I put them inside a single lambda function to ease its readability. --- src/game_windows.cpp | 240 ++++++++++++++++++------------------------- src/game_windows.h | 2 + 2 files changed, 102 insertions(+), 140 deletions(-) diff --git a/src/game_windows.cpp b/src/game_windows.cpp index 2326846006..18ca11cdec 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -243,94 +243,132 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { return font->ApplyStyle(style); }; + BitmapRef system; // Automatic window size int x_max = 0; int y_max = 0; - if (true) {//(data.width == 0 || data.height == 0) { - for (size_t i = 0; i < data.texts.size(); ++i) { - // Lots of duplication with the rendering code below but cannot be easily reduced more - auto& font = fonts[i]; - const auto& pm = messages[i]; - const auto& text = data.texts[i]; - auto style_guard = apply_style(font, text); - - int x = text.position_x; - int y = text.position_y; - for (const auto& line: pm.GetLines()) { - std::u32string line32; - auto* text_index = line.data(); - const auto* end = line.data() + line.size(); - - while (text_index != end) { - auto tret = Utils::TextNext(text_index, end, Player::escape_char); - text_index = tret.next; - - if (EP_UNLIKELY(!tret)) { - continue; - } + auto ProcessText = [&](ProcessTextMode mode) { + for (size_t i = 0; i < data.texts.size(); ++i) { + auto& font = fonts[i]; + const auto& pm = messages[i]; + const auto& text = data.texts[i]; + auto style_guard = apply_style(font, text); - const auto ch = tret.ch; + int x = text.position_x; + int y; + int text_color = 0; - if (ch == '\n') { - if (!line32.empty()) { - x += Text::GetSize(*font, Utils::EncodeUTF(line32)).width; - line32.clear(); - } + if (mode == ProcessTextMode::TextDrawing) { + y = text.position_y + 2; // +2 to match the offset RPG_RT uses + } + else { // SetMaxCoordinates + y = text.position_y; + } - x_max = std::max(x, x_max); - x = 0; - y += text.font_size + text.line_spacing; + for (const auto& line : pm.GetLines()) { + std::u32string line32; + auto* text_index = line.data(); + const auto* end = line.data() + line.size(); - continue; - } + while (text_index != end) { + auto tret = Utils::TextNext(text_index, end, Player::escape_char); + text_index = tret.next; - if (Utils::IsControlCharacter(ch)) { - // control characters not handled - continue; - } + if (EP_UNLIKELY(!tret)) { + continue; + } - if (tret.is_exfont) { - // exfont processed later - line32 += '$'; - } + const auto ch = tret.ch; + + if (ch == '\n') { + if (!line32.empty()) { + if (mode == ProcessTextMode::TextDrawing) { + Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)); + } + else { // SetMaxCoordinates + x += Text::GetSize(*font, Utils::EncodeUTF(line32)).width; + } + line32.clear(); + } - if (tret.is_escape && ch != Player::escape_char) { - if (!line32.empty()) { - x += Text::GetSize(*font, Utils::EncodeUTF(line32)).width; - line32.clear(); + if (mode == ProcessTextMode::SetMaxCoordinates) { + x_max = std::max(x, x_max); + } + x = 0; + y += text.font_size + text.line_spacing; + continue; + } + + if (Utils::IsControlCharacter(ch)) { + // control characters not handled + continue; } - // Special message codes - switch (ch) { + if (tret.is_exfont) { + // exfont processed later + line32 += '$'; + } + + if (tret.is_escape && ch != Player::escape_char) { + if (!line32.empty()) { + if (mode == ProcessTextMode::TextDrawing) { + x += Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)).x; + } + else { // SetMaxCoordinates + x += Text::GetSize(*font, Utils::EncodeUTF(line32)).width; + } + line32.clear(); + } + + // Special message codes + switch (ch) { case 'c': case 'C': { // Color - text_index = Game_Message::ParseColor(text_index, end, Player::escape_char, true).next; + auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true); + if (mode == ProcessTextMode::TextDrawing) { + auto value = pres.value; + text_color = value > 19 ? 0 : value; + } + text_index = pres.next; } break; + } + continue; } - continue; + + line32 += static_cast(ch); } - line32 += static_cast(ch); - } + if (!line32.empty()) { + if (mode == ProcessTextMode::TextDrawing) { + Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)); + } + else { // SetMaxCoordinates + x += Text::GetSize(*font, Utils::EncodeUTF(line32)).width; + } + } - if (!line32.empty()) { - x += Text::GetSize(*font, Utils::EncodeUTF(line32)).width; - } + if (mode == ProcessTextMode::SetMaxCoordinates) { + x_max = std::max(x, x_max); + } - x_max = std::max(x, x_max); + x = 0; + y += text.font_size + text.line_spacing; + } - x = 0; - y += text.font_size + text.line_spacing; + if (mode == ProcessTextMode::SetMaxCoordinates) { + y -= text.line_spacing; + y_max = std::max(y, y_max); + } } + }; - y -= text.line_spacing; - y_max = std::max(y, y_max); - } + if (true) {//(data.width == 0 || data.height == 0) { + ProcessText(ProcessTextMode::SetMaxCoordinates); // Border size if (data.flags.border_margin) { @@ -366,8 +404,7 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { window->SetVisible(false); window->SetActive(false); - - BitmapRef system; + // FIXME: Transparency setting is currently not applied to the system graphic // Disabling transparency breaks the rendering of the system graphic if (!data.system_name.empty()) { @@ -388,84 +425,7 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { } // Draw text - for (size_t i = 0; i < data.texts.size(); ++i) { - auto& font = fonts[i]; - const auto& pm = messages[i]; - const auto& text = data.texts[i]; - auto style_guard = apply_style(font, text); - - int x = text.position_x; - int y = text.position_y + 2; // +2 to match the offset RPG_RT uses - int text_color = 0; - for (const auto& line: pm.GetLines()) { - std::u32string line32; - auto* text_index = line.data(); - const auto* end = line.data() + line.size(); - - while (text_index != end) { - auto tret = Utils::TextNext(text_index, end, Player::escape_char); - text_index = tret.next; - - if (EP_UNLIKELY(!tret)) { - continue; - } - - const auto ch = tret.ch; - - if (ch == '\n') { - if (!line32.empty()) { - Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)); - line32.clear(); - } - - x = 0; - y += text.font_size + text.line_spacing; - continue; - } - - if (Utils::IsControlCharacter(ch)) { - // control characters not handled - continue; - } - - if (tret.is_exfont) { - // exfont processed later - line32 += '$'; - } - - if (tret.is_escape && ch != Player::escape_char) { - if (!line32.empty()) { - x += Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)).x; - line32.clear(); - } - - // Special message codes - switch (ch) { - case 'c': - case 'C': - { - // Color - auto pres = Game_Message::ParseColor(text_index, end, Player::escape_char, true); - auto value = pres.value; - text_index = pres.next; - text_color = value > 19 ? 0 : value; - } - break; - } - continue; - } - - line32 += static_cast(ch); - } - - if (!line32.empty()) { - Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)); - } - - x = 0; - y += text.font_size + text.line_spacing; - } - } + ProcessText(ProcessTextMode::TextDrawing); // Add to picture auto& pic = Main_Data::game_pictures->GetPicture(data.ID); diff --git a/src/game_windows.h b/src/game_windows.h index 88000a829c..bf645d0564 100644 --- a/src/game_windows.h +++ b/src/game_windows.h @@ -46,6 +46,8 @@ class Game_Windows { void SetSaveData(std::vector save); std::vector GetSaveData() const; + enum class ProcessTextMode { TextDrawing, SetMaxCoordinates }; + struct WindowText { std::string text; int position_x = 0; From c0d7560c1fe0c8337cf53fc9a1071d616cc2494b Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sat, 27 Jul 2024 02:58:25 -0300 Subject: [PATCH 11/12] CommandStringPicMenu - Inline buttons the substrings "\/" are the tag that points to a link CommandStringPicMenu - Fix glitch related to the command is at the end of a list of commands --- src/game_interpreter.cpp | 37 ++++++++- src/game_windows.cpp | 35 ++++++++ src/window_selectable.cpp | 167 ++++++++++++++++++++++++++++++++++++++ src/window_selectable.h | 8 ++ 4 files changed, 245 insertions(+), 2 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 798b8bef37..8e67231549 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5207,6 +5207,10 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { const int output_var_current_item = ValueOrVariable(com.parameters[3], com.parameters[4]); const int output_var_input_state = ValueOrVariable(com.parameters[5], com.parameters[6]); + if (strpic_index <= 0) { + Output::Warning("CommandStringPicMenu: Requested invalid picture id ({})", strpic_index); + return true; + } auto& window_data = Main_Data::game_windows->GetWindow(strpic_index); if (!window_data.window) { @@ -5286,7 +5290,11 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { auto& current_index = frame.current_command; int indent = commands[current_index].indent; int ui_choice_index = -1; - const auto& choice_controller = commands[current_index + 1]; + + const auto& choice_controller = (current_index + 1 < commands.size()) ? + commands[current_index + 1] : + commands[current_index]; + bool has_choice_list = (static_cast(choice_controller.code) == Cmd::ShowChoice); UpdateVariables(window->GetIndex(), 0); @@ -5321,12 +5329,37 @@ bool Game_Interpreter::CommandStringPicMenu(const lcf::rpg::EventCommand& com) { auto pending_message = Main_Data::game_windows->GeneratePendingMessage(ToString(text_data.text)); const auto& lines = pending_message.GetLines(); + bool use_substring_mode = false; + + // Detect mode by checking for "\/" in the entire text + for (const auto& line : lines) { + if (line.find("\\/") != std::string::npos) { + use_substring_mode = true; + break; + } + } + + // Now apply the appropriate counting method for (const auto& line : lines) { std::stringstream ss(line); std::string sub_line; while (Utils::ReadLine(ss, sub_line)) { - max_item++; + if (use_substring_mode) { + size_t pos = 0; + int count = 0; + while ((pos = sub_line.find("\\/", pos)) != std::string::npos) { + count++; + if (count % 2 != 0) { // Check if count is odd + max_item++; + } + pos += 2; // Move past the found substring + } + } + else { + max_item++; // Increment once per sub_line in original mode + } + // Output::Warning("{}", sub_line); } } diff --git a/src/game_windows.cpp b/src/game_windows.cpp index 18ca11cdec..cdda12c741 100644 --- a/src/game_windows.cpp +++ b/src/game_windows.cpp @@ -248,6 +248,10 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { int x_max = 0; int y_max = 0; + bool is_drawing_rect = false; + Rect current_rect; + std::vector inline_rects = {}; + auto ProcessText = [&](ProcessTextMode mode) { for (size_t i = 0; i < data.texts.size(); ++i) { auto& font = fonts[i]; @@ -335,6 +339,26 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { text_index = pres.next; } break; + case '/': + { + if (mode == ProcessTextMode::TextDrawing) continue; + // Toggle rect drawing mode + if (!is_drawing_rect) { + // Start drawing a new rect + current_rect.x = x; + current_rect.y = y; + is_drawing_rect = true; + } + else { + // Finish the current rect + current_rect.width = x + 1 - current_rect.x; + if (current_rect.width > x_max && x_max != 0 ) current_rect.width -=1; + current_rect.height = y - current_rect.y; + inline_rects.push_back(current_rect); + is_drawing_rect = false; + } + } + break; } continue; } @@ -342,6 +366,14 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { line32 += static_cast(ch); } + // After the loop, check if there's an unfinished rect + if (is_drawing_rect) { + current_rect.width = x_max -1; + current_rect.height = y; + inline_rects.push_back(current_rect); + is_drawing_rect = false; + } + if (!line32.empty()) { if (mode == ProcessTextMode::TextDrawing) { Text::Draw(*window->GetContents(), x, y, *font, *system, text_color, Utils::EncodeUTF(line32)); @@ -389,6 +421,9 @@ void Game_Windows::Window_User::Refresh(bool& async_wait) { } window = std::make_unique(0, 0, data.width, data.height); + + if (!inline_rects.empty()) window->inline_rects = inline_rects; + if (!data.flags.border_margin) { window->SetBorderX(0); // FIXME: Figure out why 0 does not work here (bug in Window class) diff --git a/src/window_selectable.cpp b/src/window_selectable.cpp index d9f7dac770..8ae4efbab6 100644 --- a/src/window_selectable.cpp +++ b/src/window_selectable.cpp @@ -21,6 +21,7 @@ #include "input.h" #include "util_macro.h" #include "bitmap.h" +#include "output.h" constexpr int arrow_animation_frames = 20; @@ -99,6 +100,13 @@ void Window_Selectable::UpdateHelp() { // Update Cursor Rect void Window_Selectable::UpdateCursorRect() { + + bool cursor_is_inline = !inline_rects.empty() && index < static_cast(inline_rects.size()); + if (cursor_is_inline) { + InlineUpdateCursorRect(); + return; + } + int cursor_width = 0; int x = 0; if (index < 0) { @@ -128,6 +136,67 @@ void Window_Selectable::UpdateCursorRect() { SetCursorRect(Rect(x, y, cursor_width, item_height)); } +void Window_Selectable::InlineUpdateCursorRect() { + if (index < 0 || index >= static_cast(inline_rects.size())) { + SetCursorRect(Rect()); + return; + } + + const auto& rect = inline_rects[index]; + int cursor_width = rect.width + 9; + int x = rect.x - 4; + int y = rect.y; + + int height = this->height - border_y * 2; + int max_y = std::max_element(inline_rects.begin(), inline_rects.end(), + [](const auto& a, const auto& b) { return a.y + a.height < b.y + b.height; })->y + menu_item_height; + int max_oy = std::max(0, max_y - height); + + int target_oy = oy; + if (y < oy) { + target_oy = y; + scroll_dir = scroll_dir == 0 ? -1 : scroll_dir; + } + else if (y + menu_item_height > oy + height) { + target_oy = std::min(y + menu_item_height - height, max_oy); + scroll_dir = scroll_dir == 0 ? 1 : scroll_dir; + } + else { + scroll_dir = 0; + } + + if (scroll_dir != 0) { + scroll_progress = std::min(scroll_progress + 1, 4); + int scroll_amount = (menu_item_height * scroll_progress / 4 - menu_item_height * (scroll_progress - 1) / 4) * scroll_dir; + oy = std::clamp(oy + scroll_amount, 0, max_oy); + if (scroll_progress == 4) { + scroll_dir = 0; + scroll_progress = 0; + oy = target_oy; + } + } + else { + oy = target_oy; + } + + y -= oy; + if (border_x == 0) { + x += border_x; + cursor_width -= border_x * 2; + } + + //workaround to deal with the cursor appearing over the borders while scrolling + int output_y = scroll_dir == -1 ? y / 2 : y; + int output_height = scroll_dir == 1 ? menu_item_height / 2 : menu_item_height; + + SetCursorRect(Rect(x, output_y, cursor_width, output_height)); + + UpdateArrows(); + bool latest_rect_is_oob = inline_rects.back().y + menu_item_height != oy + height; + bool arrow_visible = (arrow_frame < arrow_animation_frames); + SetDownArrow(latest_rect_is_oob && arrow_visible); +} + void Window_Selectable::UpdateArrows() { bool show_up_arrow = (GetTopRow() > 0); bool show_down_arrow = (GetTopRow() < (GetRowMax() - GetPageRowMax())); @@ -143,6 +212,13 @@ void Window_Selectable::UpdateArrows() { // Update void Window_Selectable::Update() { Window_Base::Update(); + + bool cursor_is_inline = !inline_rects.empty() && index < static_cast(inline_rects.size()); + if (cursor_is_inline) { + InlineUpdate(); + return; + } + if (active && item_max > 0 && index >= 0) { if (scroll_dir != 0) { scroll_progress++; @@ -234,6 +310,97 @@ void Window_Selectable::Update() { UpdateArrows(); } +void Window_Selectable::InlineUpdate() { + if (active && !inline_rects.empty()) { + int old_index = index; + if (Input::IsRepeated(Input::DOWN) || Input::IsTriggered(Input::SCROLL_DOWN)) { + MoveIndexVertical(1); + } + if (Input::IsRepeated(Input::UP) || Input::IsTriggered(Input::SCROLL_UP)) { + MoveIndexVertical(-1); + } + if (Input::IsRepeated(Input::RIGHT)) { + MoveIndexHorizontal(1); + } + if (Input::IsRepeated(Input::LEFT)) { + MoveIndexHorizontal(-1); + } + if (index != old_index) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor)); + if (active && help_window != NULL) { + UpdateHelp(); + } + } + } + InlineUpdateCursorRect(); + +} + +void Window_Selectable::MoveIndexHorizontal(int direction) { + int current_x = inline_rects[index].x; + int current_y = inline_rects[index].y; + int nearest_index = -1; + int min_distance = std::numeric_limits::max(); + + for (size_t i = 0; i < inline_rects.size(); ++i) { + if (i != index && inline_rects[i].y == current_y) { + if ((direction > 0 && inline_rects[i].x > current_x) || + (direction < 0 && inline_rects[i].x < current_x)) { + int distance = std::abs(inline_rects[i].x - current_x); + if (distance < min_distance) { + min_distance = distance; + nearest_index = i; + } + } + } + } + + if (nearest_index != -1) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor)); + index = nearest_index; + } +} + +void Window_Selectable::MoveIndexVertical(int direction) { + int current_x = inline_rects[index].x; + int current_y = inline_rects[index].y; + int nearest_index = -1; + int min_y_distance = std::numeric_limits::max(); + int min_x = std::numeric_limits::max(); + + // First pass: find the minimum y distance in the correct direction + for (size_t i = 0; i < inline_rects.size(); ++i) { + if (i != index) { + if ((direction > 0 && inline_rects[i].y > current_y) || + (direction < 0 && inline_rects[i].y < current_y)) { + int y_distance = std::abs(inline_rects[i].y - current_y); + if (y_distance < min_y_distance) { + min_y_distance = y_distance; + } + } + } + } + + // Second pass: among buttons with minimum y distance, find the one with smallest x value + for (size_t i = 0; i < inline_rects.size(); ++i) { + if (i != index) { + if ((direction > 0 && inline_rects[i].y > current_y) || + (direction < 0 && inline_rects[i].y < current_y)) { + int y_distance = std::abs(inline_rects[i].y - current_y); + if (y_distance == min_y_distance && inline_rects[i].x < min_x) { + min_x = inline_rects[i].x; + nearest_index = i; + } + } + } + } + + if (nearest_index != -1) { + Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor)); + index = nearest_index; + } +} + // Set endless scrolling state void Window_Selectable::SetEndlessScrolling(bool state) { endless_scrolling = state; diff --git a/src/window_selectable.h b/src/window_selectable.h index 54e0bccdc2..1ba84dc885 100644 --- a/src/window_selectable.h +++ b/src/window_selectable.h @@ -30,6 +30,8 @@ class Window_Selectable: public Window_Base { public: Window_Selectable(int ix, int iy, int iwidth, int iheight); + std::vector inline_rects = {}; + /** * Creates the contents based on how many items * are currently in the window. @@ -76,6 +78,12 @@ class Window_Selectable: public Window_Base { virtual void UpdateCursorRect(); void Update() override; + void InlineUpdateCursorRect(); + void InlineUpdate(); + + void MoveIndexVertical(int direction); + void MoveIndexHorizontal(int direction); + virtual void UpdateHelp(); /** From 0351e75529615dda8732033aca7b6a076e3d3c1d Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Thu, 1 Aug 2024 08:28:38 -0300 Subject: [PATCH 12/12] update command's code to 2058 As requested by @Ghabry --- src/game_interpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 8e67231549..6f8ec19bb1 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -835,7 +835,7 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CommandManiacCallCommand(com); case static_cast(2053): //Cmd::EasyRpg_SetInterpreterFlag return CommandEasyRpgSetInterpreterFlag(com); - case static_cast(2057): //Cmd::EasyRpg_StringPicMenu + case static_cast(2058): //Cmd::EasyRpg_StringPicMenu return CommandStringPicMenu(com); default: return true;