Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion src/game_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
#include "game_variables.h"
#include "game_party.h"
#include "game_actors.h"

#if defined(USE_SDL)
#include <SDL.h>
#endif

#include "game_strings.h"
#include "game_system.h"
#include "game_message.h"
Expand Down Expand Up @@ -441,7 +446,16 @@
_state.wait_time--;
break;
}

#if defined(USE_SDL)
if (_state.wait_typing_input) {

Check failure on line 450 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 450 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 450 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 450 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'
if (_is_typing_mode) {
// We are waiting for typing to finish. Break the loop to wait another frame.
break;
}
// Typing is no longer active, so clear the wait flag and continue execution.
_state.wait_typing_input = false;

Check failure on line 456 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 456 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 456 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 456 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'
}
#endif
if (_state.wait_key_enter) {
if (Game_Message::IsMessageActive()) {
break;
Expand Down Expand Up @@ -818,6 +832,8 @@
return CmdSetup<&Game_Interpreter::CommandEasyRpgCloneMapEvent, 10>(com);
case Cmd::EasyRpg_DestroyMapEvent:
return CmdSetup<&Game_Interpreter::CommandEasyRpgDestroyMapEvent, 2>(com);
case static_cast<Cmd>(2059):

Check warning on line 835 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

case value '2059' not in enumerated type 'Game_Interpreter::Cmd' {aka 'lcf::rpg::EventCommand::Code'} [-Wswitch]

Check warning on line 835 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

case value '2059' not in enumerated type 'Game_Interpreter::Cmd' {aka 'lcf::rpg::EventCommand::Code'} [-Wswitch]

Check warning on line 835 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

case value '2059' not in enumerated type 'Game_Interpreter::Cmd' {aka 'lcf::rpg::EventCommand::Code'} [-Wswitch]

Check warning on line 835 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

case value '2059' not in enumerated type 'Game_Interpreter::Cmd' {aka 'lcf::rpg::EventCommand::Code'} [-Wswitch]
return CommandEasyRpgTypeMode(com);
default:
return true;
}
Expand Down Expand Up @@ -5744,6 +5760,128 @@
return true;
}

bool Game_Interpreter::CommandEasyRpgTypeMode(const lcf::rpg::EventCommand& com) {
#if defined(USE_SDL)

int output_var_id = 0;
if (com.parameters.size() > 1) {
output_var_id = ValueOrVariable(com.parameters[0], com.parameters[1]);
}

if (output_var_id <= 0 || output_var_id > Main_Data::game_strings->GetSizeWithLimit()) {
if (_is_typing_mode) {
FinalizeTyping(true);
}
if (output_var_id != 0) {
Output::Warning("TypeMode: Invalid or zero Output String Variable ID provided: {}", output_var_id);
}
return true;
}

if (_is_typing_mode) {
Output::Warning("TypeMode: Command called to start while already active. Ignoring.");
return true;
}

_is_typing_mode = true;
_typing_output_var_id = output_var_id;
_typing_original_text = std::string(Main_Data::game_strings->Get(_typing_output_var_id));

std::string default_text;
if (!com.string.empty()) {
// Case 1: com.string has a value, use it directly.
default_text = ToString(com.string);
}
else {
// Case 2: com.string is blank, check parameters.
int default_text_mode = com.parameters.size() > 2 ? com.parameters[2] : 0;
int default_text_value_id = com.parameters.size() > 3 ? com.parameters[3] : 0;

switch (default_text_mode) {
case 0: // Use existing text from the output variable itself.
default_text = _typing_original_text;
break;
case 1: // Use text from another String Variable (direct ID).
default_text = std::string(Main_Data::game_strings->Get(default_text_value_id));
break;
case 2: // Use text from another String Variable (indirect ID).
default_text = std::string(Main_Data::game_strings->Get(Main_Data::game_variables->Get(default_text_value_id)));
break;
default:
Output::Warning("TypeMode: Invalid default text mode {}", default_text_mode);
// Fallback to existing text in the output variable.
default_text = _typing_original_text;
break;
}
}

Main_Data::game_strings->Asg(_typing_output_var_id, default_text);

_typing_multiline = (com.parameters.size() > 4 && com.parameters[4] == 1);

_state.wait_typing_input = true;

Check failure on line 5822 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 5822 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:22.04 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 5822 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / debian:12 (arm64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'

Check failure on line 5822 in src/game_interpreter.cpp

View workflow job for this annotation

GitHub Actions / ubuntu:24.04 (x86_64)

'class lcf::rpg::SaveEventExecState' has no member named 'wait_typing_input'
SDL_StartTextInput();

return true;
#endif

Output::Warning("TypeMode: SDL not supported on this platform");
return true;
}

// --- public methods for UI layer ---
#if defined(USE_SDL)
void Game_Interpreter::AppendTypingText(const char* text) {
if (!_is_typing_mode) return;
std::string current_text = std::string(Main_Data::game_strings->Get(_typing_output_var_id));
current_text += text;
Main_Data::game_strings->Asg(_typing_output_var_id, current_text);
}

void Game_Interpreter::HandleTypingBackspace() {
if (!_is_typing_mode) return;
std::string current_text = std::string(Main_Data::game_strings->Get(_typing_output_var_id));
if (!current_text.empty()) {
std::u32string u32_text = Utils::DecodeUTF32(current_text);
if (!u32_text.empty()) {
u32_text.pop_back();
Main_Data::game_strings->Asg(_typing_output_var_id, Utils::EncodeUTF(u32_text));
}
}
}

void Game_Interpreter::HandleTypingEnter(bool is_ctrl_pressed) {
if (!_is_typing_mode) return;
if (_typing_multiline && is_ctrl_pressed) {
AppendTypingText("\n");
}
else {
FinalizeTyping(true);
}
}

void Game_Interpreter::HandleTypingEscape() {
if (!_is_typing_mode) return;
FinalizeTyping(false);
}

// --- Helper function ---

void Game_Interpreter::FinalizeTyping(bool accepted) {
if (!_is_typing_mode) return;

_is_typing_mode = false;
#if defined(USE_SDL)
SDL_StopTextInput();
#endif

if (!accepted) {
if (_typing_output_var_id > 0) {
Main_Data::game_strings->Asg(_typing_output_var_id, _typing_original_text);
}
}
}
#endif
Game_Interpreter& Game_Interpreter::GetForegroundInterpreter() {
return Game_Battle::IsBattleRunning()
? Game_Battle::GetInterpreter()
Expand Down
40 changes: 40 additions & 0 deletions src/game_interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,39 @@ class Game_Interpreter : public Game_BaseInterpreterContext
/** @return true if wait command (time or key) is active. Used by 2k3 battle system */
bool IsWaitingForWaitCommand() const;

/**
* @brief Checks if the interpreter is currently in typing input mode.
* @return True if in typing mode, false otherwise.
*/
bool IsInTypingMode() const { return _is_typing_mode; }

/**
* @brief Appends text to the current typing buffer. Called by the UI layer.
* @param text The UTF-8 text to append.
*/
void AppendTypingText(const char* text);

/**
* @brief Handles a backspace key press during typing mode.
*/
void HandleTypingBackspace();

/**
* @brief Handles an enter key press during typing mode.
* @param is_ctrl_pressed True if the Ctrl modifier was held.
*/
void HandleTypingEnter(bool is_ctrl_pressed);

/**
* @brief Handles an escape key press, canceling the typing mode.
*/
void HandleTypingEscape();
/**
* @brief Finalizes typing mode, optionally canceling it.
* @param cancel If true, cancels typing mode; otherwise, completes it.
*/
void FinalizeTyping(bool accepted);

protected:
static constexpr int loop_limit = 10000;
static constexpr int call_stack_limit = 1000;
Expand Down Expand Up @@ -372,6 +405,13 @@ class Game_Interpreter : public Game_BaseInterpreterContext
void PushInternal(Game_Event* ev, const lcf::rpg::EventPage* page, InterpreterExecutionType ex_type);
void PushInternal(Game_CommonEvent* ev, InterpreterExecutionType ex_type);

bool _is_typing_mode = false;
int _typing_output_var_id = 0;
std::string _typing_original_text;
bool _typing_multiline = false;

bool CommandEasyRpgTypeMode(const lcf::rpg::EventCommand& com);

friend class Game_Interpreter_Inspector;
};

Expand Down
44 changes: 44 additions & 0 deletions src/platform/sdl/sdl2_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
# include "sdl2_audio.h"
# endif

#include "game_interpreter_map.h"
#include "game_interpreter_battle.h"
#include "game_battle.h"
#include "main_data.h"
#include "game_map.h"
AudioInterface& Sdl2Ui::GetAudio() {
return *audio_;
}
Expand Down Expand Up @@ -551,6 +556,45 @@ bool Sdl2Ui::ProcessEvents() {

// Poll SDL events and process them
while (SDL_PollEvent(&evnt)) {

// Determine which interpreter is currently active.
Game_Interpreter* active_interpreter = nullptr;
if (Game_Battle::IsBattleRunning()) {
active_interpreter = &Game_Battle::GetInterpreter();
}
else if (Main_Data::game_player) { // Ensure game objects are initialized
active_interpreter = &Game_Map::GetInterpreter();
}

// If an interpreter is active and in typing mode, delegate input to it.
if (active_interpreter && active_interpreter->IsInTypingMode()) {
// Check the type of SDL event and call the appropriate handler
if (evnt.type == SDL_TEXTINPUT) {
active_interpreter->AppendTypingText(evnt.text.text);
}
else if (evnt.type == SDL_KEYDOWN) {
switch (evnt.key.keysym.sym) {
case SDLK_BACKSPACE:
active_interpreter->HandleTypingBackspace();
break;
case SDLK_RETURN:
case SDLK_KP_ENTER:
active_interpreter->HandleTypingEnter(evnt.key.keysym.mod & KMOD_CTRL);
break;
case SDLK_ESCAPE:
active_interpreter->HandleTypingEscape();
break;
default:
// Other key presses (like arrows, function keys) are ignored.
break;
}
}

// Skip the default event processing to prevent mapping typed keys
// to game actions (e.g., 'Z' to Decision button).
continue;
}

ProcessEvent(evnt);

if (Player::exit_flag)
Expand Down
Loading