From 99b43ddb5adf9b08b6120c41d22592774f81b262 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Fri, 2 Feb 2024 04:20:34 -0500 Subject: [PATCH] Create World Screen + Scrolling Text Boxes! --- dependencies/symbol-processor/src | 2 +- media-layer/core/src/media.c | 2 +- .../mods/text-input-box/TextInputBox.h | 38 +- mods/include/mods/touch/touch.h | 5 + mods/src/chat/chat-internal.h | 16 +- mods/src/chat/chat.cpp | 11 +- mods/src/chat/ui.cpp | 29 +- mods/src/game-mode/ui.cpp | 438 ++++++++---------- mods/src/input/misc.c | 2 +- mods/src/text-input-box/TextInputBox.cpp | 132 +++++- mods/src/touch/touch.cpp | 21 +- symbols/CMakeLists.txt | 3 + symbols/src/game/Minecraft.def | 1 + symbols/src/gui/components/Button.def | 1 + symbols/src/gui/screens/Screen.def | 2 +- symbols/src/gui/screens/ScreenChooser.def | 1 + symbols/src/level/LevelStorageSource.def | 1 + symbols/src/level/LevelSummary.def | 7 + symbols/src/misc/Common.def | 1 + symbols/src/misc/PerfRenderer.def | 3 +- symbols/src/misc/Strings.def | 4 +- symbols/src/misc/Util.def | 3 + symbols/src/network/packet/ChatPacket.def | 2 + 23 files changed, 414 insertions(+), 311 deletions(-) create mode 100644 mods/include/mods/touch/touch.h create mode 100644 symbols/src/gui/screens/ScreenChooser.def create mode 100644 symbols/src/level/LevelSummary.def create mode 100644 symbols/src/misc/Util.def diff --git a/dependencies/symbol-processor/src b/dependencies/symbol-processor/src index 95e24d13f..fbdd1e279 160000 --- a/dependencies/symbol-processor/src +++ b/dependencies/symbol-processor/src @@ -1 +1 @@ -Subproject commit 95e24d13fa223ff524f8920314c4984686c73928 +Subproject commit fbdd1e27983eeb1329d51ee3264b2974c4901fbe diff --git a/media-layer/core/src/media.c b/media-layer/core/src/media.c index 759c01ef8..68c5fd6b5 100644 --- a/media-layer/core/src/media.c +++ b/media-layer/core/src/media.c @@ -219,7 +219,7 @@ static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int c memset(str, 0, str_size); codepoint_to_utf8((unsigned char *) str, codepoint); char *cp437_str = to_cp437(str); - // Send EventĀ· + // Send Event for (int i = 0; cp437_str[i] != '\0'; i++) { character_event(cp437_str[i]); } diff --git a/mods/include/mods/text-input-box/TextInputBox.h b/mods/include/mods/text-input-box/TextInputBox.h index 2f3410bcb..16f62bfee 100644 --- a/mods/include/mods/text-input-box/TextInputBox.h +++ b/mods/include/mods/text-input-box/TextInputBox.h @@ -3,22 +3,9 @@ #include struct TextInputBox { - GuiComponent super; + static TextInputBox *create(const std::string &placeholder = "", const std::string &text = ""); - int m_ID; - int m_xPos; - int m_yPos; - int m_width; - int m_height; - std::string m_placeholder; - std::string m_text; - bool m_bFocused; - bool m_bEnabled; - bool m_bCursorOn; - int m_insertHead; - int m_lastFlashed; - Font *m_pFont; - int m_maxLength; + GuiComponent super; void setSize(int x, int y, int width = 200, int height = 12); void init(Font *pFont); @@ -30,6 +17,25 @@ struct TextInputBox { void setFocused(bool b); void onClick(int x, int y); bool clicked(int x, int y); + std::string getText(); + bool isFocused(); + void setMaxLength(int max_length); - static TextInputBox *create(int id, const std::string &placeholder = "", const std::string &text = ""); +private: + void recalculateScroll(); + + std::string m_text; + bool m_bFocused; + int m_xPos; + int m_yPos; + int m_width; + int m_height; + std::string m_placeholder; + bool m_bEnabled; + bool m_bCursorOn; + int m_insertHead; + int m_lastFlashed; + Font *m_pFont; + int m_maxLength; + int m_scrollPos; }; diff --git a/mods/include/mods/touch/touch.h b/mods/include/mods/touch/touch.h new file mode 100644 index 000000000..e375b4c0f --- /dev/null +++ b/mods/include/mods/touch/touch.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +Button *touch_create_button(int id, std::string text); diff --git a/mods/src/chat/chat-internal.h b/mods/src/chat/chat-internal.h index 293c6a0f0..128e47803 100644 --- a/mods/src/chat/chat-internal.h +++ b/mods/src/chat/chat-internal.h @@ -1,19 +1,21 @@ #pragma once +#include + #include -#ifdef __cplusplus -extern "C" { -#endif +// Message Limitations +#define MAX_CHAT_MESSAGE_LENGTH 256 +// Message Prefix +__attribute__((visibility("internal"))) std::string _chat_get_prefix(char *username); + +// Queue Message For Sending #ifndef MCPI_SERVER_MODE __attribute__((visibility("internal"))) void _chat_queue_message(const char *message); #endif +// Init Chat UI #ifndef MCPI_HEADLESS_MODE __attribute__((visibility("internal"))) void _init_chat_ui(); #endif - -#ifdef __cplusplus -} -#endif diff --git a/mods/src/chat/chat.cpp b/mods/src/chat/chat.cpp index 1fef0d43d..563d670ad 100644 --- a/mods/src/chat/chat.cpp +++ b/mods/src/chat/chat.cpp @@ -19,9 +19,6 @@ #include "chat-internal.h" #include -// Message Limitations -#define MAX_CHAT_MESSAGE_LENGTH 512 - // Send API Command std::string chat_send_api_command(Minecraft *minecraft, std::string str) { struct ConnectedClient client; @@ -47,9 +44,12 @@ static void send_api_chat_command(Minecraft *minecraft, char *str) { #endif // Send Message To Players +std::string _chat_get_prefix(char *username) { + return std::string("<") + username + "> "; +} void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message) { char *full_message = NULL; - safe_asprintf(&full_message, "<%s> %s", username, message); + safe_asprintf(&full_message, "%s%s", _chat_get_prefix(username).c_str(), message); sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0); std::string cpp_string = full_message; free(full_message); @@ -123,5 +123,8 @@ void init_chat() { // Init UI _init_chat_ui(); #endif + // Disable Built-In Chat Message Limiting + unsigned char message_limit_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r4, r4" + patch((void *) 0x6b4c0, message_limit_patch); } } diff --git a/mods/src/chat/ui.cpp b/mods/src/chat/ui.cpp index c7d600010..274e2d364 100644 --- a/mods/src/chat/ui.cpp +++ b/mods/src/chat/ui.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // Structure struct ChatScreen { @@ -23,24 +24,16 @@ CUSTOM_VTABLE(chat_screen, Screen) { original_init(super); ChatScreen *self = (ChatScreen *) super; // Text Input - self->chat = TextInputBox::create(1); + self->chat = TextInputBox::create(); self->super.m_textInputs->push_back(self->chat); self->chat->init(super->font); self->chat->setFocused(true); + // Determien Max Length + std::string prefix = _chat_get_prefix(Strings_default_username); + int max_length = MAX_CHAT_MESSAGE_LENGTH - prefix.length(); + self->chat->setMaxLength(max_length); // Send Button - if (Minecraft_isTouchscreen(super->minecraft)) { - self->send = (Button *) new Touch_TButton; - } else { - self->send = new Button; - } - ALLOC_CHECK(self->send); - int send_id = 2; - std::string send_text = "Send"; - if (Minecraft_isTouchscreen(super->minecraft)) { - Touch_TButton_constructor((Touch_TButton *) self->send, send_id, &send_text); - } else { - Button_constructor(self->send, send_id, &send_text); - } + self->send = touch_create_button(1, "Send"); super->rendered_buttons.push_back(self->send); super->selectable_buttons.push_back(self->send); // Hide Chat Messages @@ -69,7 +62,7 @@ CUSTOM_VTABLE(chat_screen, Screen) { vtable->setupPositions = [](Screen *super) { Screen_setupPositions_non_virtual(super); ChatScreen *self = (ChatScreen *) super; - self->send->height = 20; + self->send->height = 24; self->send->width = 40; int x = 0; int y = super->height - self->send->height; @@ -83,9 +76,9 @@ CUSTOM_VTABLE(chat_screen, Screen) { vtable->keyPressed = [](Screen *super, int key) { // Handle Enter ChatScreen *self = (ChatScreen *) super; - if (key == 0x0d && self->chat->m_bFocused) { - if (self->chat->m_text.length() > 0) { - _chat_queue_message(self->chat->m_text.c_str()); + if (key == 0x0d && self->chat->isFocused()) { + if (self->chat->getText().length() > 0) { + _chat_queue_message(self->chat->getText().c_str()); } Minecraft_setScreen(super->minecraft, NULL); } diff --git a/mods/src/game-mode/ui.cpp b/mods/src/game-mode/ui.cpp index e1ceff80c..14b27d539 100644 --- a/mods/src/game-mode/ui.cpp +++ b/mods/src/game-mode/ui.cpp @@ -4,230 +4,223 @@ // Game Mode UI Code Is Useless In Headless Mode #ifndef MCPI_SERVER_MODE -#include -#include -#include #include -#include +#include #include -#include +#include +#include #include "game-mode-internal.h" -// Run Command -static char *run_command_proper(const char *command[], bool allow_empty) { - // Run - int return_code; - char *output = run_command(command, &return_code, NULL); +// Strings +#define GAME_MODE_STR(mode) ("Game Mode: " mode) +#define SURVIVAL_STR GAME_MODE_STR("Survival") +#define CREATIVE_STR GAME_MODE_STR("Creative") - // Handle Message - if (output != NULL) { - // Check Return Code - if (is_exit_status_success(return_code)) { - // Remove Ending Newline - int length = strlen(output); - if (output[length - 1] == '\n') { - output[length - 1] = '\0'; - } - length = strlen(output); - // Don't Allow Empty Strings - if (allow_empty || length > 0) { - // Return - return output; - } - } - // Free Output - free(output); - } - // Return - return !is_exit_status_success(return_code) ? NULL : run_command_proper(command, allow_empty); -} - -// Track Create World State -static pthread_mutex_t create_world_state_lock = PTHREAD_MUTEX_INITIALIZER; -typedef enum { - DIALOG_CLOSED, - DIALOG_OPEN, - DIALOG_SUCCESS -} create_world_state_dialog_t; -struct create_world_state_t { - volatile create_world_state_dialog_t dialog_state = DIALOG_CLOSED; - volatile char *name = NULL; - volatile int32_t game_mode = 0; - volatile int32_t seed = 0; +// Structure +struct CreateWorldScreen { + TextInputScreen super; + TextInputBox *name; + TextInputBox *seed; + Button *game_mode; + Button *create; + Button *back; }; -static create_world_state_t create_world_state; -// Destructor -__attribute__((destructor)) static void _free_create_world_state_name() { - free((void *) create_world_state.name); -} - -// Reset State (Assume Lock) -static void reset_create_world_state() { - create_world_state.dialog_state = DIALOG_CLOSED; - if (create_world_state.name != NULL) { - free((void *) create_world_state.name); - } - create_world_state.name = NULL; - create_world_state.game_mode = 0; - create_world_state.seed = 0; -} - -// Chat Thread -#define DEFAULT_WORLD_NAME "Unnamed world" -#define DIALOG_TITLE "Create World" -#define GAME_MODE_DIALOG_SIZE "200" -static void *create_world_thread(__attribute__((unused)) void *nop) { - // Run Dialogs - char *world_name = NULL; - { - // World Name - { - // Open - const char *command[] = { - "zenity", - "--title", DIALOG_TITLE, - "--name", MCPI_APP_ID, - "--entry", - "--text", "Enter World Name:", - "--entry-text", DEFAULT_WORLD_NAME, - NULL - }; - char *output = run_command_proper(command, false); - // Handle Message - if (output != NULL) { - // Store - world_name = strdup(output); - ALLOC_CHECK(world_name); - // Free - free(output); - } else { - // Fail - goto fail; - } - } - - // Game Mode - int game_mode = 0; - { - // Open - const char *command[] = { - "zenity", - "--title", DIALOG_TITLE, - "--name", MCPI_APP_ID, - "--list", - "--radiolist", - "--width", GAME_MODE_DIALOG_SIZE, - "--height", GAME_MODE_DIALOG_SIZE, - "--text", "Select Game Mode:", - "--column","Selected", - "--column", "Name", - "TRUE", "Creative", - "FALSE", "Survival", - NULL - }; - char *output = run_command_proper(command, false); - // Handle Message - if (output != NULL) { - // Store - game_mode = strcmp(output, "Creative") == 0; - // Free - free(output); - } else { - // Fail - goto fail; - } - } - +static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed); +CUSTOM_VTABLE(create_world_screen, Screen) { + TextInputScreen::setup(vtable); + // Constants + static int line_height = 8; + static int bottom_padding = 4; + static int inner_padding = 4; + static int description_padding = 4; + static int title_padding = 8; + // Init + static Screen_init_t original_init = vtable->init; + vtable->init = [](Screen *super) { + original_init(super); + CreateWorldScreen *self = (CreateWorldScreen *) super; + // Name + self->name = TextInputBox::create("World Name", "Unnamed world"); + self->super.m_textInputs->push_back(self->name); + self->name->init(super->font); + self->name->setFocused(true); // Seed - int32_t seed = 0; - get_seed: - { - // Open - const char *command[] = { - "zenity", - "--title", DIALOG_TITLE, - "--name", MCPI_APP_ID, - "--entry", - "--only-numerical", - "--text", "Enter Seed (Leave Blank For Random):", - NULL - }; - char *output = run_command_proper(command, true); - // Handle Message - if (output != NULL) { - // Store - bool valid = true; - try { - seed = strlen(output) == 0 ? time(NULL) : std::stoi(output); - } catch (std::invalid_argument &e) { - // Invalid Seed - WARN("Invalid Seed: %s", output); - valid = false; - } catch (std::out_of_range &e) { - // Out-Of-Range Seed - WARN("Seed Out-Of-Range: %s", output); - valid = false; - } - // Free - free(output); - // Retry If Invalid - if (!valid) { - goto get_seed; - } - } else { - // Fail - goto fail; - } + self->seed = TextInputBox::create("Seed"); + self->super.m_textInputs->push_back(self->seed); + self->seed->init(super->font); + self->seed->setFocused(false); + // Game Mode + self->game_mode = touch_create_button(1, CREATIVE_STR); + super->rendered_buttons.push_back(self->game_mode); + super->selectable_buttons.push_back(self->game_mode); + // Create + self->create = touch_create_button(2, "Create"); + super->rendered_buttons.push_back(self->create); + super->selectable_buttons.push_back(self->create); + // Back + self->back = touch_create_button(3, "Back"); + super->rendered_buttons.push_back(self->back); + super->selectable_buttons.push_back(self->back); + }; + // Removal + static Screen_removed_t original_removed = vtable->removed; + vtable->removed = [](Screen *super) { + original_removed(super); + CreateWorldScreen *self = (CreateWorldScreen *) super; + delete self->name; + delete self->seed; + self->game_mode->vtable->destructor_deleting(self->game_mode); + self->back->vtable->destructor_deleting(self->back); + self->create->vtable->destructor_deleting(self->create); + }; + // Rendering + static Screen_render_t original_render = vtable->render; + vtable->render = [](Screen *super, int x, int y, float param_1) { + // Background + super->vtable->renderBackground(super); + // Call Original Method + original_render(super, x, y, param_1); + // Title + std::string title = "Create world"; + Screen_drawCenteredString(super, super->font, &title, super->width / 2, title_padding, 0xffffffff); + // Game Mode Description + CreateWorldScreen *self = (CreateWorldScreen *) super; + bool is_creative = self->game_mode->text == CREATIVE_STR; + std::string description = is_creative ? Strings_creative_mode_description : Strings_survival_mode_description; + Screen_drawString(super, super->font, &description, self->game_mode->x, self->game_mode->y + self->game_mode->height + description_padding, 0xa0a0a0); + }; + // Positioning + vtable->setupPositions = [](Screen *super) { + Screen_setupPositions_non_virtual(super); + CreateWorldScreen *self = (CreateWorldScreen *) super; + // Height/Width + int width = 120; + int height = 24; + self->create->width = self->back->width = self->game_mode->width = width; + int seed_width = self->game_mode->width; + int name_width = width * 1.5f; + self->create->height = self->back->height = self->game_mode->height = height; + int text_box_height = self->game_mode->height; + // Find Center Y + int top = (title_padding * 2) + line_height; + int bottom = super->height - self->create->height - (bottom_padding * 2); + int center_y = ((bottom - top) / 2) + top; + center_y -= (description_padding + line_height) / 2; + // X/Y + self->create->y = self->back->y = super->height - bottom_padding - height; + self->create->x = self->game_mode->x = (super->width / 2) - inner_padding - width; + self->back->x = (super->width / 2) + inner_padding; + int seed_x = self->back->x; + int name_x = (super->width / 2) - (name_width / 2); + int name_y = center_y - inner_padding - height; + self->game_mode->y = center_y + inner_padding; + int seed_y = self->game_mode->y; + // Update Text Boxes + self->name->setSize(name_x, name_y, name_width, text_box_height); + self->seed->setSize(seed_x, seed_y, seed_width, text_box_height); + }; + // ESC + vtable->handleBackEvent = [](Screen *super, bool do_nothing) { + if (!do_nothing) { + ScreenChooser_setScreen(&super->minecraft->screen_chooser, 5); } + return true; + }; + // Button Click + vtable->buttonClicked = [](Screen *super, Button *button) { + CreateWorldScreen *self = (CreateWorldScreen *) super; + bool is_creative = self->game_mode->text == CREATIVE_STR; + if (button == self->game_mode) { + // Toggle Game Mode + self->game_mode->text = is_creative ? SURVIVAL_STR : CREATIVE_STR; + } else if (button == self->back) { + // Back + super->vtable->handleBackEvent(super, false); + } else if (button == self->create) { + // Create + create_world(super->minecraft, self->name->getText(), is_creative, self->seed->getText()); + } + }; +} +static Screen *create_create_world_screen() { + // Construct + CreateWorldScreen *screen = new CreateWorldScreen; + ALLOC_CHECK(screen); + Screen_constructor(&screen->super.super); - // Update State - pthread_mutex_lock(&create_world_state_lock); - reset_create_world_state(); - create_world_state.dialog_state = DIALOG_SUCCESS; - char *safe_name = to_cp437(world_name); - create_world_state.name = safe_name; - free(world_name); - create_world_state.game_mode = game_mode; - create_world_state.seed = seed; - pthread_mutex_unlock(&create_world_state_lock); - // Return - return NULL; - } + // Set VTable + screen->super.super.vtable = get_create_world_screen_vtable(); - fail: - // Update State - pthread_mutex_lock(&create_world_state_lock); - reset_create_world_state(); - pthread_mutex_unlock(&create_world_state_lock); - free(world_name); // Return - return NULL; + return (Screen *) screen; } -// Create Chat Thead -static void open_create_world() { - // Update State (Assume Lock) - create_world_state.dialog_state = DIALOG_OPEN; - // Start Thread - pthread_t thread; - pthread_create(&thread, NULL, create_world_thread, NULL); +// Unique Level Name (https://github.com/ReMinecraftPE/mcpe/blob/d7a8b6baecf8b3b050538abdbc976f690312aa2d/source/client/gui/screens/CreateWorldScreen.cpp#L65-L83) +static std::string getUniqueLevelName(LevelStorageSource *source, const std::string &in) { + std::set maps; + std::vector vls; + source->vtable->getLevelList(source, &vls); + for (int i = 0; i < int(vls.size()); i++) { + const LevelSummary &ls = vls[i]; + maps.insert(ls.folder); + } + std::string out = in; + while (maps.find(out) != maps.end()) { + out += "-"; + } + return out; } // Create World -static void create_world(Screen *host_screen, std::string folder_name) { - // Get Minecraft - Minecraft *minecraft = host_screen->minecraft; +static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) { + // Get Seed + int seed; + seed_str = Util_stringTrim(&seed_str); + if (!seed_str.empty()) { + int num; + if (sscanf(seed_str.c_str(), "%d", &num) > 0) { + seed = num; + } else { + seed = Util_hashCode(&seed_str); + } + } else { + seed = Common_getEpochTimeS(); + } + + // Get Folder Name + name = Util_stringTrim(&name); + std::string folder = ""; + for (char c : name) { + if ( + c >= ' ' && c <= '~' && + c != '/' && + c != '\\' && + c != '`' && + c != '?' && + c != '*' && + c != '<' && + c != '>' && + c != '|' && + c != '"' && + c != ':' + ) { + folder += c; + } + } + if (folder.empty()) { + folder = "World"; + } + folder = getUniqueLevelName(Minecraft_getLevelSource(minecraft), folder); // Settings LevelSettings settings; - settings.game_type = create_world_state.game_mode; - settings.seed = create_world_state.seed; + settings.game_type = is_creative; + settings.seed = seed; // Create World - std::string world_name = (char *) create_world_state.name; - minecraft->vtable->selectLevel(minecraft, &folder_name, &world_name, &settings); + minecraft->vtable->selectLevel(minecraft, &folder, &name, &settings); // Multiplayer Minecraft_hostMultiplayer(minecraft, 19132); @@ -237,55 +230,20 @@ static void create_world(Screen *host_screen, std::string folder_name) { ALLOC_CHECK(screen); screen = ProgressScreen_constructor(screen); Minecraft_setScreen(minecraft, (Screen *) screen); - - // Reset - reset_create_world_state(); } // Redirect Create World Button #define create_SelectWorldScreen_tick_injection(prefix) \ static void prefix##SelectWorldScreen_tick_injection(prefix##SelectWorldScreen *screen) { \ - /* Lock */ \ - pthread_mutex_lock(&create_world_state_lock); \ - \ - bool *should_create_world = &screen->should_create_world; \ - if (*should_create_world) { \ - /* Check State */ \ - if (create_world_state.dialog_state == DIALOG_CLOSED) { \ - /* Open Dialog */ \ - open_create_world(); \ - } \ - \ + if (screen->should_create_world) { \ + /* Open Screen */ \ + Minecraft_setScreen(screen->minecraft, create_create_world_screen()); \ /* Finish */ \ - *should_create_world = false; \ + screen->should_create_world = false; \ } else { \ /* Call Original Method */ \ prefix##SelectWorldScreen_tick_non_virtual(screen); \ } \ - \ - /* Create World If Dialog Succeeded */ \ - if (create_world_state.dialog_state == DIALOG_SUCCESS) { \ - /* Create World Dialog Finished */ \ - \ - /* Get New World Name */ \ - std::string name = (char *) create_world_state.name; \ - std::string new_name = prefix##SelectWorldScreen_getUniqueLevelName(screen, &name); \ - \ - /* Create World */ \ - create_world((Screen *) screen, new_name); \ - } \ - \ - /* Lock/Unlock UI */ \ - if (create_world_state.dialog_state != DIALOG_OPEN) { \ - /* Dialog Closed, Unlock UI */ \ - media_set_interactable(1); \ - } else { \ - /* Dialog Open, Lock UI */ \ - media_set_interactable(0); \ - } \ - \ - /* Unlock */ \ - pthread_mutex_unlock(&create_world_state_lock); \ } create_SelectWorldScreen_tick_injection() create_SelectWorldScreen_tick_injection(Touch_) diff --git a/mods/src/input/misc.c b/mods/src/input/misc.c index 1dc74f6c9..ac0a57a9a 100644 --- a/mods/src/input/misc.c +++ b/mods/src/input/misc.c @@ -35,7 +35,7 @@ static void _handle_back(Minecraft *minecraft) { } // Fix OptionsScreen Ignoring The Back Button -static int32_t OptionsScreen_handleBackEvent_injection(OptionsScreen *screen, bool do_nothing) { +static bool OptionsScreen_handleBackEvent_injection(OptionsScreen *screen, bool do_nothing) { if (!do_nothing) { Minecraft *minecraft = screen->minecraft; Minecraft_setScreen(minecraft, NULL); diff --git a/mods/src/text-input-box/TextInputBox.cpp b/mods/src/text-input-box/TextInputBox.cpp index e91ede1b3..22d0c8e83 100644 --- a/mods/src/text-input-box/TextInputBox.cpp +++ b/mods/src/text-input-box/TextInputBox.cpp @@ -2,13 +2,12 @@ #include -TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const std::string &text) { +TextInputBox *TextInputBox::create(const std::string &placeholder, const std::string &text) { // Construct TextInputBox *self = new TextInputBox; GuiComponent_constructor(&self->super); // Setup - self->m_ID = id; self->m_xPos = 0; self->m_yPos = 0; self->m_width = 0; @@ -22,6 +21,7 @@ TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const self->m_lastFlashed = 0; self->m_pFont = nullptr; self->m_maxLength = -1; + self->m_scrollPos = 0; // Return return self; @@ -32,6 +32,7 @@ void TextInputBox::setSize(int x, int y, int width, int height) { m_yPos = y; m_width = width; m_height = height; + recalculateScroll(); } void TextInputBox::init(Font *pFont) { @@ -61,6 +62,7 @@ void TextInputBox::keyPressed(int key) { } m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead); m_insertHead--; + recalculateScroll(); break; } case 0x2e: { @@ -83,6 +85,7 @@ void TextInputBox::keyPressed(int key) { if (m_insertHead < 0) { m_insertHead = 0; } + recalculateScroll(); break; } case 0x27: { @@ -95,6 +98,7 @@ void TextInputBox::keyPressed(int key) { } else { m_insertHead = 0; } + recalculateScroll(); break; } case 0x0d: { @@ -130,6 +134,7 @@ void TextInputBox::setFocused(bool b) { m_lastFlashed = Common_getTimeMs(); m_bCursorOn = true; m_insertHead = int(m_text.size()); + recalculateScroll(); } } @@ -143,38 +148,64 @@ void TextInputBox::charPressed(int k) { return; } - // note: the width will increase by the same amount no matter where K is appended - std::string test_str = m_text + char(k); - if (m_maxLength != -1 && int(test_str.length()) > m_maxLength) { + // Ignore Newlines + if (k == '\n') { return; } - int width = Font_width(m_pFont, &test_str); - if (width < (m_width - PADDING)) { - m_text.insert(m_text.begin() + m_insertHead, k); - m_insertHead++; + + // Check Max Length + if (m_maxLength != -1 && int(m_text.length()) >= m_maxLength) { + return; } + + // Insert + m_text.insert(m_text.begin() + m_insertHead, k); + m_insertHead++; + recalculateScroll(); } +static std::string get_rendered_text(Font *font, int width, int scroll_pos, std::string text) { + std::string rendered_text = text.substr(scroll_pos); + int max_width = width - (PADDING * 2); + while (Font_width(font, &rendered_text) > max_width) { + rendered_text.pop_back(); + } + return rendered_text; +} + +static char CURSOR_CHAR = '_'; + void TextInputBox::render() { GuiComponent_fill(&super, m_xPos, m_yPos, m_xPos + m_width, m_yPos + m_height, 0xFFAAAAAA); GuiComponent_fill(&super, m_xPos + 1, m_yPos + 1, m_xPos + m_width - 1, m_yPos + m_height - 1, 0xFF000000); - int textYPos = (m_height - 8) / 2; - + int text_color; + int scroll_pos; + std::string rendered_text; if (m_text.empty()) { - GuiComponent_drawString(&super, m_pFont, &m_placeholder, m_xPos + PADDING, m_yPos + textYPos, 0x404040); + rendered_text = m_placeholder; + text_color = 0x404040; + scroll_pos = 0; } else { - GuiComponent_drawString(&super, m_pFont, &m_text, m_xPos + PADDING, m_yPos + textYPos, 0xFFFFFF); + rendered_text = m_text; + text_color = 0xffffff; + scroll_pos = m_scrollPos; } + rendered_text = get_rendered_text(m_pFont, m_width, scroll_pos, rendered_text); + + int textYPos = (m_height - 8) / 2; + GuiComponent_drawString(&super, m_pFont, &rendered_text, m_xPos + PADDING, m_yPos + textYPos, text_color); if (m_bCursorOn) { - int xPos = 5; + int cursor_pos = m_insertHead - m_scrollPos; + if (cursor_pos >= 0 && cursor_pos <= int(rendered_text.length())) { + std::string substr = rendered_text.substr(0, cursor_pos); + int xPos = PADDING + Font_width(m_pFont, &substr); - std::string substr = m_text.substr(0, m_insertHead); - xPos += Font_width(m_pFont, &substr); - - std::string str = "_"; - GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xFFFFFF); + std::string str; + str += CURSOR_CHAR; + GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xffffff); + } } } @@ -198,3 +229,66 @@ bool TextInputBox::clicked(int xPos, int yPos) { return true; } + +void TextInputBox::recalculateScroll() { + // Skip If Size Unset + if (m_width == 0) { + return; + } + // Ensure Cursor Is Visible + bool is_cursor_at_end = m_insertHead == int(m_text.length()); + if (m_scrollPos >= m_insertHead && m_scrollPos > 0) { + // Cursor Is At Scroll Position + // Move Back Scroll As Far As Possible + while (true) { + int test_scroll_pos = m_scrollPos - 1; + std::string rendered_text = m_text; + if (is_cursor_at_end) { + rendered_text += CURSOR_CHAR; + } + rendered_text = get_rendered_text(m_pFont, m_width, test_scroll_pos, rendered_text); + int cursor_pos = m_insertHead - test_scroll_pos; + if (cursor_pos >= int(rendered_text.length())) { + break; + } else { + m_scrollPos = test_scroll_pos; + if (m_scrollPos == 0) { + break; + } + } + } + } else { + // Cursor After Scroll Area + // Increase Scroll So Cursor Is Visible + while (true) { + std::string rendered_text = m_text; + if (is_cursor_at_end) { + rendered_text += CURSOR_CHAR; + } + rendered_text = get_rendered_text(m_pFont, m_width, m_scrollPos, rendered_text); + int cursor_pos = m_insertHead - m_scrollPos; + if (cursor_pos < int(rendered_text.length())) { + break; + } else { + if (m_scrollPos == int(m_text.length())) { + WARN("Text Box Is Too Small"); + break; + } else { + m_scrollPos++; + } + } + } + } +} + +std::string TextInputBox::getText() { + return m_text; +} + +bool TextInputBox::isFocused() { + return m_bFocused; +} + +void TextInputBox::setMaxLength(int max_length) { + m_maxLength = max_length; +} diff --git a/mods/src/touch/touch.cpp b/mods/src/touch/touch.cpp index 67d20f921..dd4de2eaf 100644 --- a/mods/src/touch/touch.cpp +++ b/mods/src/touch/touch.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -40,9 +41,27 @@ static void LargeImageButton_render_GuiComponent_drawCenteredString_injection(Gu GuiComponent_drawCenteredString(component, font, text, x, y, color); } +// Create Button +static int touch_gui = 0; +Button *touch_create_button(int id, std::string text) { + Button *button = nullptr; + if (touch_gui) { + button = (Button *) new Touch_TButton; + } else { + button = new Button; + } + ALLOC_CHECK(button); + if (touch_gui) { + Touch_TButton_constructor((Touch_TButton *) button, id, &text); + } else { + Button_constructor(button, id, &text); + } + return button; +} + // Init void init_touch() { - int touch_gui = feature_has("Full Touch GUI", server_disabled); + touch_gui = feature_has("Full Touch GUI", server_disabled); int touch_buttons = touch_gui; if (touch_gui) { // Main UI diff --git a/symbols/CMakeLists.txt b/symbols/CMakeLists.txt index eb06b629d..7cd1b1306 100644 --- a/symbols/CMakeLists.txt +++ b/symbols/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRC src/level/ServerLevel.def src/level/Dimension.def src/level/MultiPlayerLevel.def + src/level/LevelSummary.def src/item/ItemRenderer.def src/item/ItemInHandRenderer.def src/item/AuxDataTileItem.def @@ -100,6 +101,7 @@ set(SRC src/gui/screens/StartMenuScreen.def src/gui/screens/ProgressScreen.def src/gui/screens/Touch_SelectWorldScreen.def + src/gui/screens/ScreenChooser.def src/gui/Font.def src/gui/components/ImageButton.def src/gui/components/OptionButton.def @@ -142,6 +144,7 @@ set(SRC src/misc/Config.def src/misc/Random.def src/misc/Mth.def + src/misc/Util.def src/input/IMoveInput.def src/input/IBuildInput.def src/input/MouseBuildInput.def diff --git a/symbols/src/game/Minecraft.def b/symbols/src/game/Minecraft.def index 637a92f71..04707c03e 100644 --- a/symbols/src/game/Minecraft.def +++ b/symbols/src/game/Minecraft.def @@ -41,6 +41,7 @@ property int progress = 0xc60; property PerfRenderer *perf_renderer = 0xcbc; property CommandServer *command_server = 0xcc0; property Font *font = 0x16c; +property ScreenChooser screen_chooser = 0x168; // Smooth Lighting static-property bool useAmbientOcclusion = 0x136b90; diff --git a/symbols/src/gui/components/Button.def b/symbols/src/gui/components/Button.def index cf2bb7ba6..1b3d078b6 100644 --- a/symbols/src/gui/components/Button.def +++ b/symbols/src/gui/components/Button.def @@ -9,3 +9,4 @@ property int width = 0x14; property int height = 0x18; property int x = 0xc; property int y = 0x10; +property std::string text = 0x1c; diff --git a/symbols/src/gui/screens/Screen.def b/symbols/src/gui/screens/Screen.def index 79c381871..fe904fca0 100644 --- a/symbols/src/gui/screens/Screen.def +++ b/symbols/src/gui/screens/Screen.def @@ -10,7 +10,7 @@ virtual-method void updateEvents() = 0x14; virtual-method void keyboardNewChar(char key) = 0x70; virtual-method void keyPressed(int key) = 0x6c; virtual-method void render(int x, int y, float param_1) = 0x8; -virtual-method bool handleBackEvent(bool param_1) = 0x24; +virtual-method bool handleBackEvent(bool do_nothing) = 0x24; virtual-method void tick() = 0x28; virtual-method void buttonClicked(Button *button) = 0x60; virtual-method void init() = 0xc; diff --git a/symbols/src/gui/screens/ScreenChooser.def b/symbols/src/gui/screens/ScreenChooser.def new file mode 100644 index 000000000..bb2984b21 --- /dev/null +++ b/symbols/src/gui/screens/ScreenChooser.def @@ -0,0 +1 @@ +method void setScreen(uint id) = 0x29490; diff --git a/symbols/src/level/LevelStorageSource.def b/symbols/src/level/LevelStorageSource.def index 58e62e69f..723f01bec 100644 --- a/symbols/src/level/LevelStorageSource.def +++ b/symbols/src/level/LevelStorageSource.def @@ -1 +1,2 @@ virtual-method void deleteLevel(std::string *level_name) = 0x20; +virtual-method void getLevelList(std::vector *level_list) = 0xc; diff --git a/symbols/src/level/LevelSummary.def b/symbols/src/level/LevelSummary.def new file mode 100644 index 000000000..fc8242692 --- /dev/null +++ b/symbols/src/level/LevelSummary.def @@ -0,0 +1,7 @@ +size 0x14; + +property std::string folder = 0x0; +property std::string name = 0x4; +property int seed = 0x8; +property int game_mode = 0xc; +property int param_5 = 0x10; diff --git a/symbols/src/misc/Common.def b/symbols/src/misc/Common.def index 1d75668f9..de81022c9 100644 --- a/symbols/src/misc/Common.def +++ b/symbols/src/misc/Common.def @@ -6,3 +6,4 @@ static-method void sleepMs(int x) = 0x13cf4; static-method int sdl_key_to_minecraft_key(int sdl_key) = 0x1243c; static-method void anGenBuffers(int count, uint *buffers) = 0x5f28c; static-method int getTimeMs() = 0x13cd4; +static-method int getEpochTimeS() = 0x13d00; diff --git a/symbols/src/misc/PerfRenderer.def b/symbols/src/misc/PerfRenderer.def index eea41e6aa..9af6b065e 100644 --- a/symbols/src/misc/PerfRenderer.def +++ b/symbols/src/misc/PerfRenderer.def @@ -1 +1,2 @@ -method void debugFpsMeterKeyPress(int key) = 0x79118; \ No newline at end of file +method void debugFpsMeterKeyPress(int key) = 0x79118; +method void renderFpsMeter(float param_1) = 0x79280; diff --git a/symbols/src/misc/Strings.def b/symbols/src/misc/Strings.def index f83046fcf..10898c00e 100644 --- a/symbols/src/misc/Strings.def +++ b/symbols/src/misc/Strings.def @@ -7,4 +7,6 @@ static-property char *options_txt_fopen_mode_when_loading = 0x19d24; // w static-property char **feedback_vibration_options_txt_name_1 = 0x198a0; // feedback_vibration static-property char **feedback_vibration_options_txt_name_2 = 0x194bc; // feedback_vibration static-property char **gfx_lowquality_options_txt_name = 0x194c4; // gfx_lowquality -static-property char *classic_create_button_text = 0x39bec; // Create \ No newline at end of file +static-property char *classic_create_button_text = 0x39bec; // Create +static-property-array char creative_mode_description = 0x104492; // Unlimited resources and flying +static-property-array char survival_mode_description = 0x104470; // Mobs, health and gather resources diff --git a/symbols/src/misc/Util.def b/symbols/src/misc/Util.def new file mode 100644 index 000000000..4e6c6d490 --- /dev/null +++ b/symbols/src/misc/Util.def @@ -0,0 +1,3 @@ +static-method std::string stringTrim(std::string *str) = 0x77c40; +static-method int hashCode(std::string *str) = 0x77a50; +static-method std::string *stringReplace(std::string *str, std::string *what, std::string *with, int param_1) = 0x779f0; diff --git a/symbols/src/network/packet/ChatPacket.def b/symbols/src/network/packet/ChatPacket.def index aed55cc76..35b8833dd 100644 --- a/symbols/src/network/packet/ChatPacket.def +++ b/symbols/src/network/packet/ChatPacket.def @@ -1,3 +1,5 @@ extends Packet; +vtable 0x108a98; + property char *message = 0xc;