In-Game Chat
This commit is contained in:
parent
d175f692e0
commit
58713976d4
@ -123,6 +123,8 @@ static SDLKey glfw_key_to_sdl_key(int key) {
|
|||||||
return SDLK_RETURN;
|
return SDLK_RETURN;
|
||||||
case GLFW_KEY_BACKSPACE:
|
case GLFW_KEY_BACKSPACE:
|
||||||
return SDLK_BACKSPACE;
|
return SDLK_BACKSPACE;
|
||||||
|
case GLFW_KEY_DELETE:
|
||||||
|
return SDLK_DELETE;
|
||||||
// Fullscreen
|
// Fullscreen
|
||||||
case GLFW_KEY_F11:
|
case GLFW_KEY_F11:
|
||||||
return SDLK_F11;
|
return SDLK_F11;
|
||||||
|
@ -28,6 +28,7 @@ typedef enum {
|
|||||||
SDLK_s = 115,
|
SDLK_s = 115,
|
||||||
SDLK_t = 116,
|
SDLK_t = 116,
|
||||||
SDLK_w = 119,
|
SDLK_w = 119,
|
||||||
|
SDLK_DELETE = 127,
|
||||||
SDLK_UP = 273,
|
SDLK_UP = 273,
|
||||||
SDLK_DOWN = 274,
|
SDLK_DOWN = 274,
|
||||||
SDLK_RIGHT = 275,
|
SDLK_RIGHT = 275,
|
||||||
|
@ -15,7 +15,7 @@ set(SRC
|
|||||||
src/version/version.cpp
|
src/version/version.cpp
|
||||||
# chat
|
# chat
|
||||||
src/chat/chat.cpp
|
src/chat/chat.cpp
|
||||||
src/chat/ui.c
|
src/chat/ui.cpp
|
||||||
# creative
|
# creative
|
||||||
src/creative/creative.cpp
|
src/creative/creative.cpp
|
||||||
# game-mode
|
# game-mode
|
||||||
@ -96,6 +96,9 @@ else()
|
|||||||
# textures
|
# textures
|
||||||
src/textures/textures.cpp
|
src/textures/textures.cpp
|
||||||
src/textures/lava.cpp
|
src/textures/lava.cpp
|
||||||
|
# text-input-box
|
||||||
|
src/text-input-box/TextInputBox.cpp
|
||||||
|
src/text-input-box/TextInputScreen.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ extern "C" {
|
|||||||
|
|
||||||
#ifndef MCPI_SERVER_MODE
|
#ifndef MCPI_SERVER_MODE
|
||||||
void chat_open();
|
void chat_open();
|
||||||
unsigned int chat_get_counter();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Override using the HOOK() macro to provide customized chat behavior.
|
// Override using the HOOK() macro to provide customized chat behavior.
|
||||||
|
@ -28,6 +28,8 @@ void Level_saveLevelData_injection(Level *level);
|
|||||||
// Use this instead of directly calling Gui::addMessage(), it has proper logging!
|
// Use this instead of directly calling Gui::addMessage(), it has proper logging!
|
||||||
void misc_add_message(Gui *gui, const char *text);
|
void misc_add_message(Gui *gui, const char *text);
|
||||||
|
|
||||||
|
extern bool is_in_chat;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
35
mods/include/mods/text-input-box/TextInputBox.h
Normal file
35
mods/include/mods/text-input-box/TextInputBox.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <symbols/minecraft.h>
|
||||||
|
|
||||||
|
struct TextInputBox {
|
||||||
|
GuiComponent super;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
void setSize(int x, int y, int width = 200, int height = 12);
|
||||||
|
void init(Font *pFont);
|
||||||
|
void setEnabled(bool bEnabled);
|
||||||
|
void keyPressed(int key);
|
||||||
|
void charPressed(int chr);
|
||||||
|
void render();
|
||||||
|
void tick();
|
||||||
|
void setFocused(bool b);
|
||||||
|
void onClick(int x, int y);
|
||||||
|
bool clicked(int x, int y);
|
||||||
|
|
||||||
|
static TextInputBox create(int id, const std::string &placeholder = "", const std::string &text = "");
|
||||||
|
};
|
12
mods/include/mods/text-input-box/TextInputScreen.h
Normal file
12
mods/include/mods/text-input-box/TextInputScreen.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <symbols/minecraft.h>
|
||||||
|
|
||||||
|
#include <mods/text-input-box/TextInputBox.h>
|
||||||
|
|
||||||
|
struct TextInputScreen {
|
||||||
|
Screen super;
|
||||||
|
std::vector<TextInputBox *> m_textInputs;
|
||||||
|
|
||||||
|
static void setup(Screen_vtable *vtable);
|
||||||
|
};
|
@ -6,9 +6,12 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
__attribute__((visibility("internal"))) extern int _chat_enabled;
|
|
||||||
#ifndef MCPI_SERVER_MODE
|
#ifndef MCPI_SERVER_MODE
|
||||||
__attribute__((visibility("internal"))) void _chat_queue_message(char *message);
|
__attribute__((visibility("internal"))) void _chat_queue_message(const char *message);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MCPI_HEADLESS_MODE
|
||||||
|
__attribute__((visibility("internal"))) void _init_chat_ui();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -5,9 +5,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#ifndef MCPI_HEADLESS_MODE
|
|
||||||
#include <pthread.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <symbols/minecraft.h>
|
#include <symbols/minecraft.h>
|
||||||
#ifndef MCPI_HEADLESS_MODE
|
#ifndef MCPI_HEADLESS_MODE
|
||||||
@ -22,9 +19,6 @@
|
|||||||
#include "chat-internal.h"
|
#include "chat-internal.h"
|
||||||
#include <mods/chat/chat.h>
|
#include <mods/chat/chat.h>
|
||||||
|
|
||||||
// Store If Chat is Enabled
|
|
||||||
int _chat_enabled = 0;
|
|
||||||
|
|
||||||
// Message Limitations
|
// Message Limitations
|
||||||
#define MAX_CHAT_MESSAGE_LENGTH 512
|
#define MAX_CHAT_MESSAGE_LENGTH 512
|
||||||
|
|
||||||
@ -95,45 +89,27 @@ static void ServerSideNetworkHandler_handle_ChatPacket_injection(ServerSideNetwo
|
|||||||
|
|
||||||
#ifndef MCPI_HEADLESS_MODE
|
#ifndef MCPI_HEADLESS_MODE
|
||||||
// Message Queue
|
// Message Queue
|
||||||
static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
static std::vector<std::string> queue;
|
static std::vector<std::string> queue;
|
||||||
// Add To Queue
|
// Add To Queue
|
||||||
void _chat_queue_message(char *message) {
|
void _chat_queue_message(const char *message) {
|
||||||
// Lock
|
|
||||||
pthread_mutex_lock(&queue_mutex);
|
|
||||||
// Add
|
// Add
|
||||||
std::string str;
|
std::string str = message;
|
||||||
str.append(message);
|
|
||||||
queue.push_back(str);
|
queue.push_back(str);
|
||||||
// Unlock
|
|
||||||
pthread_mutex_unlock(&queue_mutex);
|
|
||||||
}
|
}
|
||||||
// Empty Queue
|
// Empty Queue
|
||||||
unsigned int old_chat_counter = 0;
|
unsigned int old_chat_counter = 0;
|
||||||
static void send_queued_messages(Minecraft *minecraft) {
|
static void send_queued_messages(Minecraft *minecraft) {
|
||||||
// Lock
|
|
||||||
pthread_mutex_lock(&queue_mutex);
|
|
||||||
// If Message Was Submitted, No Other Chat Windows Are Open, And The Game Is Not Paused, Then Re-Lock Cursor
|
|
||||||
unsigned int new_chat_counter = chat_get_counter();
|
|
||||||
if (old_chat_counter > new_chat_counter && new_chat_counter == 0) {
|
|
||||||
// Unlock UI
|
|
||||||
media_set_interactable(1);
|
|
||||||
}
|
|
||||||
old_chat_counter = new_chat_counter;
|
|
||||||
// Loop
|
// Loop
|
||||||
for (unsigned int i = 0; i < queue.size(); i++) {
|
for (unsigned int i = 0; i < queue.size(); i++) {
|
||||||
send_api_chat_command(minecraft, (char *) queue[i].c_str());
|
send_api_chat_command(minecraft, (char *) queue[i].c_str());
|
||||||
}
|
}
|
||||||
queue.clear();
|
queue.clear();
|
||||||
// Unlock
|
|
||||||
pthread_mutex_unlock(&queue_mutex);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
void init_chat() {
|
void init_chat() {
|
||||||
_chat_enabled = feature_has("Implement Chat", server_enabled);
|
if (feature_has("Implement Chat", server_enabled)) {
|
||||||
if (_chat_enabled) {
|
|
||||||
// Disable Original ChatPacket Loopback
|
// Disable Original ChatPacket Loopback
|
||||||
unsigned char disable_chat_packet_loopback_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
|
unsigned char disable_chat_packet_loopback_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
|
||||||
patch((void *) 0x6b490, disable_chat_packet_loopback_patch);
|
patch((void *) 0x6b490, disable_chat_packet_loopback_patch);
|
||||||
@ -141,9 +117,11 @@ void init_chat() {
|
|||||||
overwrite_call((void *) 0x6b518, (void *) CommandServer_parse_CommandServer_dispatchPacket_injection);
|
overwrite_call((void *) 0x6b518, (void *) CommandServer_parse_CommandServer_dispatchPacket_injection);
|
||||||
// Re-Broadcast ChatPacket
|
// Re-Broadcast ChatPacket
|
||||||
patch_address(ServerSideNetworkHandler_handle_ChatPacket_vtable_addr, (void *) ServerSideNetworkHandler_handle_ChatPacket_injection);
|
patch_address(ServerSideNetworkHandler_handle_ChatPacket_vtable_addr, (void *) ServerSideNetworkHandler_handle_ChatPacket_injection);
|
||||||
// Send Messages On Input Tick
|
|
||||||
#ifndef MCPI_HEADLESS_MODE
|
#ifndef MCPI_HEADLESS_MODE
|
||||||
|
// Send Messages On Input Tick
|
||||||
input_run_on_tick(send_queued_messages);
|
input_run_on_tick(send_queued_messages);
|
||||||
|
// Init UI
|
||||||
|
_init_chat_ui();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
// Config Needs To Load First
|
|
||||||
#include <libreborn/libreborn.h>
|
|
||||||
|
|
||||||
// Chat UI Code Is Useless In Headless Mode
|
|
||||||
#ifndef MCPI_HEADLESS_MODE
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <media-layer/core.h>
|
|
||||||
|
|
||||||
#include "chat-internal.h"
|
|
||||||
#include <mods/chat/chat.h>
|
|
||||||
|
|
||||||
// Count Chat Windows
|
|
||||||
static pthread_mutex_t chat_counter_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
static volatile unsigned int chat_counter = 0;
|
|
||||||
unsigned int chat_get_counter() {
|
|
||||||
return chat_counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat Thread
|
|
||||||
#define DIALOG_TITLE "Chat"
|
|
||||||
static void *chat_thread(__attribute__((unused)) void *nop) {
|
|
||||||
// Open
|
|
||||||
int return_code;
|
|
||||||
const char *command[] = {
|
|
||||||
"zenity",
|
|
||||||
"--title", DIALOG_TITLE,
|
|
||||||
"--name", MCPI_APP_ID,
|
|
||||||
"--entry",
|
|
||||||
"--text", "Enter Chat Message:",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
char *output = run_command(command, &return_code, NULL);
|
|
||||||
// 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 (length > 0) {
|
|
||||||
// Submit
|
|
||||||
char *safe_output = to_cp437(output);
|
|
||||||
_chat_queue_message(safe_output);
|
|
||||||
free(safe_output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Free Output
|
|
||||||
free(output);
|
|
||||||
}
|
|
||||||
// Update Counter
|
|
||||||
pthread_mutex_lock(&chat_counter_lock);
|
|
||||||
chat_counter--;
|
|
||||||
pthread_mutex_unlock(&chat_counter_lock);
|
|
||||||
// Return
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Chat Thead
|
|
||||||
void chat_open() {
|
|
||||||
if (_chat_enabled) {
|
|
||||||
// Lock UI
|
|
||||||
media_set_interactable(0);
|
|
||||||
|
|
||||||
// Update Counter
|
|
||||||
pthread_mutex_lock(&chat_counter_lock);
|
|
||||||
chat_counter++;
|
|
||||||
pthread_mutex_unlock(&chat_counter_lock);
|
|
||||||
// Start Thread
|
|
||||||
pthread_t thread;
|
|
||||||
pthread_create(&thread, NULL, chat_thread, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
98
mods/src/chat/ui.cpp
Normal file
98
mods/src/chat/ui.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Config Needs To Load First
|
||||||
|
#include <libreborn/libreborn.h>
|
||||||
|
|
||||||
|
// Chat UI Code Is Useless In Headless Mode
|
||||||
|
#ifndef MCPI_HEADLESS_MODE
|
||||||
|
|
||||||
|
#include "chat-internal.h"
|
||||||
|
#include <mods/chat/chat.h>
|
||||||
|
#include <mods/text-input-box/TextInputScreen.h>
|
||||||
|
#include <mods/misc/misc.h>
|
||||||
|
|
||||||
|
// Structure
|
||||||
|
struct ChatScreen {
|
||||||
|
TextInputScreen super;
|
||||||
|
TextInputBox chat;
|
||||||
|
};
|
||||||
|
CUSTOM_VTABLE(chat_screen, Screen) {
|
||||||
|
TextInputScreen::setup(vtable);
|
||||||
|
// Init
|
||||||
|
vtable->init = [](Screen *super) {
|
||||||
|
Screen_init_non_virtual(super);
|
||||||
|
ChatScreen *self = (ChatScreen *) super;
|
||||||
|
self->super.m_textInputs.push_back(&self->chat);
|
||||||
|
self->chat.init(super->font);
|
||||||
|
self->chat.setFocused(true);
|
||||||
|
is_in_chat = true;
|
||||||
|
};
|
||||||
|
// Removal
|
||||||
|
vtable->removed = [](Screen *super) {
|
||||||
|
Screen_removed_non_virtual(super);
|
||||||
|
is_in_chat = false;
|
||||||
|
};
|
||||||
|
// 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);
|
||||||
|
// Render Chat
|
||||||
|
Gui_renderChatMessages(&super->minecraft->gui, super->height, 20, true, super->font);
|
||||||
|
// Call Original Method
|
||||||
|
original_render(super, x, y, param_1);
|
||||||
|
};
|
||||||
|
// Positioning
|
||||||
|
vtable->setupPositions = [](Screen *super) {
|
||||||
|
Screen_setupPositions_non_virtual(super);
|
||||||
|
ChatScreen *self = (ChatScreen *) super;
|
||||||
|
int height = 20;
|
||||||
|
int x = 0;
|
||||||
|
int y = super->height - height;
|
||||||
|
int width = super->width;
|
||||||
|
self->chat.setSize(x, y, width, height);
|
||||||
|
};
|
||||||
|
// Key Presses
|
||||||
|
static Screen_keyPressed_t original_keyPressed = vtable->keyPressed;
|
||||||
|
vtable->keyPressed = [](Screen *super, int key) {
|
||||||
|
// Handle Enter
|
||||||
|
ChatScreen *self = (ChatScreen *) super;
|
||||||
|
if (key == 0x0d) {
|
||||||
|
_chat_queue_message(self->chat.m_text.c_str());
|
||||||
|
Minecraft_setScreen(super->minecraft, NULL);
|
||||||
|
}
|
||||||
|
// Call Original Method
|
||||||
|
original_keyPressed(super, key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static Screen *create_chat_screen() {
|
||||||
|
// Construct
|
||||||
|
ChatScreen *screen = new ChatScreen;
|
||||||
|
ALLOC_CHECK(screen);
|
||||||
|
Screen_constructor(&screen->super.super);
|
||||||
|
|
||||||
|
// Set VTable
|
||||||
|
screen->super.super.vtable = get_chat_screen_vtable();
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
screen->chat = TextInputBox::create(0);
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return (Screen *) screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open Screen
|
||||||
|
static bool open_chat_screen = false;
|
||||||
|
void chat_open() {
|
||||||
|
open_chat_screen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init
|
||||||
|
void _init_chat_ui() {
|
||||||
|
misc_run_on_tick([](Minecraft *minecraft) {
|
||||||
|
if (open_chat_screen && Minecraft_isLevelGenerated(minecraft) && minecraft->screen == NULL) {
|
||||||
|
Minecraft_setScreen(minecraft, create_chat_screen());
|
||||||
|
}
|
||||||
|
open_chat_screen = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -70,12 +70,7 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
|
|||||||
input_third_person();
|
input_third_person();
|
||||||
handled = 1;
|
handled = 1;
|
||||||
} else if (event->key.keysym.sym == SDLK_t) {
|
} else if (event->key.keysym.sym == SDLK_t) {
|
||||||
// Only When In-Game With No Other Chat Windows Open
|
chat_open();
|
||||||
if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON && chat_get_counter() == 0) {
|
|
||||||
// Open Chat
|
|
||||||
chat_open();
|
|
||||||
}
|
|
||||||
// Mark Handled
|
|
||||||
handled = 1;
|
handled = 1;
|
||||||
} else if (event->key.keysym.sym == SDLK_ESCAPE) {
|
} else if (event->key.keysym.sym == SDLK_ESCAPE) {
|
||||||
// Treat Escape As Back Button Press (This Fixes Issues With Signs)
|
// Treat Escape As Back Button Press (This Fixes Issues With Signs)
|
||||||
|
@ -80,7 +80,8 @@ static void _handle_mouse_grab(Minecraft *minecraft) {
|
|||||||
|
|
||||||
// Block UI Interaction When Mouse Is Locked
|
// Block UI Interaction When Mouse Is Locked
|
||||||
static bool Gui_tickItemDrop_Minecraft_isCreativeMode_call_injection(Minecraft *minecraft) {
|
static bool Gui_tickItemDrop_Minecraft_isCreativeMode_call_injection(Minecraft *minecraft) {
|
||||||
if (!enable_misc || SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF) {
|
bool is_in_game = minecraft->screen == NULL || minecraft->screen->vtable == (Screen_vtable *) Touch_IngameBlockSelectionScreen_vtable_base;
|
||||||
|
if (!enable_misc || (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF && is_in_game)) {
|
||||||
// Call Original Method
|
// Call Original Method
|
||||||
return creative_is_restricted() && Minecraft_isCreativeMode(minecraft);
|
return creative_is_restricted() && Minecraft_isCreativeMode(minecraft);
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,6 +65,7 @@ static void Gui_renderBubbles_GuiComponent_blit_injection(Gui *component, int32_
|
|||||||
|
|
||||||
// Additional GUI Rendering
|
// Additional GUI Rendering
|
||||||
static int hide_chat_messages = 0;
|
static int hide_chat_messages = 0;
|
||||||
|
bool is_in_chat = 0;
|
||||||
static int render_selected_item_text = 0;
|
static int render_selected_item_text = 0;
|
||||||
static void Gui_renderChatMessages_injection(Gui *gui, int32_t y_offset, uint32_t max_messages, bool disable_fading, Font *font) {
|
static void Gui_renderChatMessages_injection(Gui *gui, int32_t y_offset, uint32_t max_messages, bool disable_fading, Font *font) {
|
||||||
// Handle Classic HUD
|
// Handle Classic HUD
|
||||||
@ -76,7 +77,7 @@ static void Gui_renderChatMessages_injection(Gui *gui, int32_t y_offset, uint32_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call Original Method
|
// Call Original Method
|
||||||
if (!hide_chat_messages) {
|
if (!hide_chat_messages && !is_in_chat) {
|
||||||
Gui_renderChatMessages(gui, y_offset, max_messages, disable_fading, font);
|
Gui_renderChatMessages(gui, y_offset, max_messages, disable_fading, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,13 @@
|
|||||||
// Handle Backspace
|
// Handle Backspace
|
||||||
static int32_t sdl_key_to_minecraft_key_injection(int32_t sdl_key) {
|
static int32_t sdl_key_to_minecraft_key_injection(int32_t sdl_key) {
|
||||||
if (sdl_key == SDLK_BACKSPACE) {
|
if (sdl_key == SDLK_BACKSPACE) {
|
||||||
return 8;
|
return 0x8;
|
||||||
|
} else if (sdl_key == SDLK_DELETE) {
|
||||||
|
return 0x2e;
|
||||||
|
} else if (sdl_key == SDLK_LEFT) {
|
||||||
|
return 0x25;
|
||||||
|
} else if (sdl_key == SDLK_RIGHT) {
|
||||||
|
return 0x27;
|
||||||
} else {
|
} else {
|
||||||
// Call Original Method
|
// Call Original Method
|
||||||
return Common_sdl_key_to_minecraft_key(sdl_key);
|
return Common_sdl_key_to_minecraft_key(sdl_key);
|
||||||
@ -31,37 +37,17 @@ static void LocalPlayer_openTextEdit_injection(LocalPlayer *local_player, TileEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store Text Input
|
// Store Text Input
|
||||||
std::vector<char> input;
|
|
||||||
void sign_key_press(char key) {
|
void sign_key_press(char key) {
|
||||||
input.push_back(key);
|
Keyboard__inputText.push_back(key);
|
||||||
}
|
|
||||||
static void clear_input(__attribute__((unused)) Minecraft *minecraft) {
|
|
||||||
input.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Text Input
|
|
||||||
static void TextEditScreen_updateEvents_injection(TextEditScreen *screen) {
|
|
||||||
// Call Original Method
|
|
||||||
TextEditScreen_updateEvents_non_virtual(screen);
|
|
||||||
|
|
||||||
if (!screen->passthrough_input) {
|
|
||||||
for (char key : input) {
|
|
||||||
// Handle Normal Key
|
|
||||||
screen->vtable->keyboardNewChar(screen, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clear_input(NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
void init_sign() {
|
void init_sign() {
|
||||||
if (feature_has("Fix Sign Placement", server_disabled)) {
|
if (feature_has("Fix Sign Placement", server_disabled)) {
|
||||||
// Handle Backspace
|
|
||||||
overwrite_calls((void *) Common_sdl_key_to_minecraft_key, (void *) sdl_key_to_minecraft_key_injection);
|
|
||||||
// Fix Signs
|
// Fix Signs
|
||||||
patch_address(LocalPlayer_openTextEdit_vtable_addr, (void *) LocalPlayer_openTextEdit_injection);
|
patch_address(LocalPlayer_openTextEdit_vtable_addr, (void *) LocalPlayer_openTextEdit_injection);
|
||||||
patch_address(TextEditScreen_updateEvents_vtable_addr, (void *) TextEditScreen_updateEvents_injection);
|
|
||||||
// Clear Input On Input Tick
|
|
||||||
input_run_on_tick(clear_input);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Backspace
|
||||||
|
overwrite_calls((void *) Common_sdl_key_to_minecraft_key, (void *) sdl_key_to_minecraft_key_injection);
|
||||||
}
|
}
|
||||||
|
2
mods/src/text-input-box/README.md
Normal file
2
mods/src/text-input-box/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# ``text-input-box`` Mod
|
||||||
|
This mod implements a GUI component for text input. This is ported from [ReMinecraftPE](https://github.com/ReMinecraftPE/mcpe/blob/d7a8b6baecf8b3b050538abdbc976f690312aa2d/source/client/gui/components/TextInputBox.cpp).
|
200
mods/src/text-input-box/TextInputBox.cpp
Normal file
200
mods/src/text-input-box/TextInputBox.cpp
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
#include <libreborn/libreborn.h>
|
||||||
|
|
||||||
|
#include <mods/text-input-box/TextInputBox.h>
|
||||||
|
|
||||||
|
TextInputBox TextInputBox::create(int id, const std::string &placeholder, const std::string &text) {
|
||||||
|
// Construct
|
||||||
|
TextInputBox self;
|
||||||
|
GuiComponent_constructor(&self.super);
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
self.m_ID = id;
|
||||||
|
self.m_xPos = 0;
|
||||||
|
self.m_yPos = 0;
|
||||||
|
self.m_width = 0;
|
||||||
|
self.m_height = 0;
|
||||||
|
self.m_placeholder = placeholder;
|
||||||
|
self.m_text = text;
|
||||||
|
self.m_bFocused = false;
|
||||||
|
self.m_bEnabled = true;
|
||||||
|
self.m_bCursorOn = true;
|
||||||
|
self.m_insertHead = 0;
|
||||||
|
self.m_lastFlashed = 0;
|
||||||
|
self.m_pFont = nullptr;
|
||||||
|
self.m_maxLength = -1;
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::setSize(int x, int y, int width, int height) {
|
||||||
|
m_xPos = x;
|
||||||
|
m_yPos = y;
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::init(Font *pFont) {
|
||||||
|
m_pFont = pFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::setEnabled(bool bEnabled) {
|
||||||
|
m_bEnabled = bEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::keyPressed(int key) {
|
||||||
|
if (!m_bFocused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 0x8: {
|
||||||
|
// Backspace
|
||||||
|
if (m_text.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_insertHead <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_insertHead > int(m_text.size())) {
|
||||||
|
m_insertHead = int(m_text.size());
|
||||||
|
}
|
||||||
|
m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead);
|
||||||
|
m_insertHead--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x2e: {
|
||||||
|
// Delete
|
||||||
|
if (m_text.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_insertHead < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_insertHead >= int(m_text.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_text.erase(m_text.begin() + m_insertHead, m_text.begin() + m_insertHead + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x25: {
|
||||||
|
// Left
|
||||||
|
m_insertHead--;
|
||||||
|
if (m_insertHead < 0) {
|
||||||
|
m_insertHead = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x27: {
|
||||||
|
// Right
|
||||||
|
m_insertHead++;
|
||||||
|
if (!m_text.empty()) {
|
||||||
|
if (m_insertHead > int(m_text.size())) {
|
||||||
|
m_insertHead = int(m_text.size());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_insertHead = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0d: {
|
||||||
|
// Enter
|
||||||
|
m_bFocused = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::tick() {
|
||||||
|
if (!m_lastFlashed) {
|
||||||
|
m_lastFlashed = Common_getTimeMs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_bFocused) {
|
||||||
|
if (Common_getTimeMs() > m_lastFlashed + 500) {
|
||||||
|
m_lastFlashed += 500;
|
||||||
|
m_bCursorOn ^= 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_bCursorOn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::setFocused(bool b) {
|
||||||
|
if (m_bFocused == b) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_bFocused = b;
|
||||||
|
if (b) {
|
||||||
|
m_lastFlashed = Common_getTimeMs();
|
||||||
|
m_bCursorOn = true;
|
||||||
|
m_insertHead = int(m_text.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextInputBox::onClick(int x, int y) {
|
||||||
|
setFocused(clicked(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int PADDING = 5;
|
||||||
|
void TextInputBox::charPressed(int k) {
|
||||||
|
if (!m_bFocused) {
|
||||||
|
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) {
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (m_text.empty()) {
|
||||||
|
GuiComponent_drawString(&super, m_pFont, &m_placeholder, m_xPos + PADDING, m_yPos + textYPos, 0x404040);
|
||||||
|
} else {
|
||||||
|
GuiComponent_drawString(&super, m_pFont, &m_text, m_xPos + PADDING, m_yPos + textYPos, 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_bCursorOn) {
|
||||||
|
int xPos = 5;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextInputBox::clicked(int xPos, int yPos) {
|
||||||
|
if (!m_bEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xPos < m_xPos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (yPos < m_yPos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (xPos >= m_xPos + m_width) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (yPos >= m_yPos + m_height) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
40
mods/src/text-input-box/TextInputScreen.cpp
Normal file
40
mods/src/text-input-box/TextInputScreen.cpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#include <libreborn/libreborn.h>
|
||||||
|
|
||||||
|
#include <mods/text-input-box/TextInputScreen.h>
|
||||||
|
|
||||||
|
// VTable
|
||||||
|
void TextInputScreen::setup(Screen_vtable *vtable) {
|
||||||
|
vtable->keyPressed = [](Screen *super2, int key) {
|
||||||
|
Screen_keyPressed_non_virtual(super2, key);
|
||||||
|
TextInputScreen *self = (TextInputScreen *) super2;
|
||||||
|
for (int i = 0; i < int(self->m_textInputs.size()); i++) {
|
||||||
|
TextInputBox *textInput = self->m_textInputs[i];
|
||||||
|
textInput->keyPressed(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vtable->keyboardNewChar = [](Screen *super2, char key) {
|
||||||
|
Screen_keyboardNewChar_non_virtual(super2, key);
|
||||||
|
TextInputScreen *self = (TextInputScreen *) super2;
|
||||||
|
for (int i = 0; i < int(self->m_textInputs.size()); i++) {
|
||||||
|
TextInputBox *textInput = self->m_textInputs[i];
|
||||||
|
textInput->charPressed(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vtable->mouseClicked = [](Screen *super2, int x, int y, int param_1) {
|
||||||
|
Screen_mouseClicked_non_virtual(super2, x, y, param_1);
|
||||||
|
TextInputScreen *self = (TextInputScreen *) super2;
|
||||||
|
for (int i = 0; i < int(self->m_textInputs.size()); i++) {
|
||||||
|
TextInputBox *textInput = self->m_textInputs[i];
|
||||||
|
textInput->onClick(x, y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vtable->render = [](Screen *super2, int x, int y, float param_1) {
|
||||||
|
Screen_render_non_virtual(super2, x, y, param_1);
|
||||||
|
TextInputScreen *self = (TextInputScreen *) super2;
|
||||||
|
for (int i = 0; i < int(self->m_textInputs.size()); i++) {
|
||||||
|
TextInputBox *textInput = self->m_textInputs[i];
|
||||||
|
textInput->tick();
|
||||||
|
textInput->render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -106,7 +106,7 @@ set(SRC
|
|||||||
src/gui/components/OptionsPane.def
|
src/gui/components/OptionsPane.def
|
||||||
src/gui/components/GuiComponent.def
|
src/gui/components/GuiComponent.def
|
||||||
src/gui/components/Button.def
|
src/gui/components/Button.def
|
||||||
src/gui/components/Gui.def
|
src/gui/Gui.def
|
||||||
src/gui/components/IntRectangle.def
|
src/gui/components/IntRectangle.def
|
||||||
src/gui/components/RectangleArea.def
|
src/gui/components/RectangleArea.def
|
||||||
src/gui/components/ScrollingPane.def
|
src/gui/components/ScrollingPane.def
|
||||||
@ -145,6 +145,7 @@ set(SRC
|
|||||||
src/input/IBuildInput.def
|
src/input/IBuildInput.def
|
||||||
src/input/MouseBuildInput.def
|
src/input/MouseBuildInput.def
|
||||||
src/input/Mouse.def
|
src/input/Mouse.def
|
||||||
|
src/input/Keyboard.def
|
||||||
src/recipes/FurnaceRecipes.def
|
src/recipes/FurnaceRecipes.def
|
||||||
src/recipes/Recipes.def
|
src/recipes/Recipes.def
|
||||||
src/recipes/Recipes_Type.def
|
src/recipes/Recipes_Type.def
|
||||||
|
@ -40,6 +40,7 @@ property HitResult hit_result = 0xc38;
|
|||||||
property int progress = 0xc60;
|
property int progress = 0xc60;
|
||||||
property PerfRenderer *perf_renderer = 0xcbc;
|
property PerfRenderer *perf_renderer = 0xcbc;
|
||||||
property CommandServer *command_server = 0xcc0;
|
property CommandServer *command_server = 0xcc0;
|
||||||
|
property Font *font = 0x16c;
|
||||||
|
|
||||||
// Smooth Lighting
|
// Smooth Lighting
|
||||||
static-property bool useAmbientOcclusion = 0x136b90;
|
static-property bool useAmbientOcclusion = 0x136b90;
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
method int width(std::string *string) = 0x24d4c;
|
@ -1,3 +1,8 @@
|
|||||||
|
size 0x8;
|
||||||
|
|
||||||
|
constructor () = 0x28204;
|
||||||
|
|
||||||
method void blit(int x_dest, int y_dest, int x_src, int y_src, int width_dest, int height_dest, int width_src, int height_src) = 0x282a4;
|
method void blit(int x_dest, int y_dest, int x_src, int y_src, int width_dest, int height_dest, int width_src, int height_src) = 0x282a4;
|
||||||
method void drawCenteredString(Font *font, std::string *text, int x, int y, int color) = 0x2821c;
|
method void drawCenteredString(Font *font, std::string *text, int x, int y, int color) = 0x2821c;
|
||||||
|
method void drawString(Font *font, std::string *text, int x, int y, int color) = 0x28284;
|
||||||
method void fill(int x1, int y1, int x2, int y2, uint color) = 0x285f0;
|
method void fill(int x1, int y1, int x2, int y2, uint color) = 0x285f0;
|
@ -1,15 +1,23 @@
|
|||||||
extends GuiComponent;
|
extends GuiComponent;
|
||||||
|
|
||||||
|
size 0x48;
|
||||||
|
constructor () = 0x29028;
|
||||||
|
|
||||||
|
vtable-size 0x74;
|
||||||
vtable 0x1039d8;
|
vtable 0x1039d8;
|
||||||
|
|
||||||
virtual-method void updateEvents() = 0x14;
|
virtual-method void updateEvents() = 0x14;
|
||||||
virtual-method void keyboardNewChar(char key) = 0x70;
|
virtual-method void keyboardNewChar(char key) = 0x70;
|
||||||
virtual-method void keyPressed(int key) = 0x6c;
|
virtual-method void keyPressed(int key) = 0x6c;
|
||||||
virtual-method void render(int param_1, int param_2, float param_3) = 0x8;
|
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 param_1) = 0x24;
|
||||||
virtual-method void tick() = 0x28;
|
virtual-method void tick() = 0x28;
|
||||||
virtual-method void buttonClicked(Button *button) = 0x60;
|
virtual-method void buttonClicked(Button *button) = 0x60;
|
||||||
virtual-method void init() = 0xc;
|
virtual-method void init() = 0xc;
|
||||||
|
virtual-method void mouseClicked(int x, int y, int param_1) = 0x64;
|
||||||
|
virtual-method void removed() = 0x2c;
|
||||||
|
virtual-method void renderBackground() = 0x30;
|
||||||
|
virtual-method void setupPositions() = 0x10;
|
||||||
|
|
||||||
property Minecraft *minecraft = 0x14;
|
property Minecraft *minecraft = 0x14;
|
||||||
property std::vector<Button *> rendered_buttons = 0x18;
|
property std::vector<Button *> rendered_buttons = 0x18;
|
||||||
@ -17,3 +25,4 @@ property std::vector<Button *> selectable_buttons = 0x30;
|
|||||||
property int width = 0x8;
|
property int width = 0x8;
|
||||||
property int height = 0xc;
|
property int height = 0xc;
|
||||||
property bool passthrough_input = 0x10;
|
property bool passthrough_input = 0x10;
|
||||||
|
property Font *font = 0x40;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
extends Screen;
|
extends Screen;
|
||||||
|
|
||||||
size 0x16c;
|
size 0x16c;
|
||||||
|
|
||||||
constructor () = 0x3afbc;
|
constructor () = 0x3afbc;
|
||||||
|
|
||||||
|
vtable 0x1053c0;
|
||||||
|
1
symbols/src/input/Keyboard.def
Normal file
1
symbols/src/input/Keyboard.def
Normal file
@ -0,0 +1 @@
|
|||||||
|
static-property std::vector<char> _inputText = 0x1364f0;
|
Loading…
Reference in New Issue
Block a user