Create World Screen + Scrolling Text Boxes!
This commit is contained in:
parent
4f32cfab45
commit
99b43ddb5a
2
dependencies/symbol-processor/src
vendored
2
dependencies/symbol-processor/src
vendored
@ -1 +1 @@
|
||||
Subproject commit 95e24d13fa223ff524f8920314c4984686c73928
|
||||
Subproject commit fbdd1e27983eeb1329d51ee3264b2974c4901fbe
|
@ -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]);
|
||||
}
|
||||
|
@ -3,22 +3,9 @@
|
||||
#include <symbols/minecraft.h>
|
||||
|
||||
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;
|
||||
};
|
||||
|
5
mods/include/mods/touch/touch.h
Normal file
5
mods/include/mods/touch/touch.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <symbols/minecraft.h>
|
||||
|
||||
Button *touch_create_button(int id, std::string text);
|
@ -1,19 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <libreborn/libreborn.h>
|
||||
|
||||
#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
|
||||
|
@ -19,9 +19,6 @@
|
||||
#include "chat-internal.h"
|
||||
#include <mods/chat/chat.h>
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <mods/chat/chat.h>
|
||||
#include <mods/text-input-box/TextInputScreen.h>
|
||||
#include <mods/misc/misc.h>
|
||||
#include <mods/touch/touch.h>
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
@ -4,230 +4,223 @@
|
||||
// Game Mode UI Code Is Useless In Headless Mode
|
||||
#ifndef MCPI_SERVER_MODE
|
||||
|
||||
#include <pthread.h>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <set>
|
||||
|
||||
#include <symbols/minecraft.h>
|
||||
#include <media-layer/core.h>
|
||||
|
||||
#include <mods/text-input-box/TextInputScreen.h>
|
||||
#include <mods/touch/touch.h>
|
||||
#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<std::string> maps;
|
||||
std::vector<LevelSummary> 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_)
|
||||
|
@ -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);
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
#include <mods/text-input-box/TextInputBox.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <mods/feature/feature.h>
|
||||
#include <mods/init/init.h>
|
||||
#include <mods/touch/touch.h>
|
||||
|
||||
#include <symbols/minecraft.h>
|
||||
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
1
symbols/src/gui/screens/ScreenChooser.def
Normal file
1
symbols/src/gui/screens/ScreenChooser.def
Normal file
@ -0,0 +1 @@
|
||||
method void setScreen(uint id) = 0x29490;
|
@ -1 +1,2 @@
|
||||
virtual-method void deleteLevel(std::string *level_name) = 0x20;
|
||||
virtual-method void getLevelList(std::vector<LevelSummary> *level_list) = 0xc;
|
||||
|
7
symbols/src/level/LevelSummary.def
Normal file
7
symbols/src/level/LevelSummary.def
Normal file
@ -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;
|
@ -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;
|
||||
|
@ -1 +1,2 @@
|
||||
method void debugFpsMeterKeyPress(int key) = 0x79118;
|
||||
method void debugFpsMeterKeyPress(int key) = 0x79118;
|
||||
method void renderFpsMeter(float param_1) = 0x79280;
|
||||
|
@ -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
|
||||
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
|
||||
|
3
symbols/src/misc/Util.def
Normal file
3
symbols/src/misc/Util.def
Normal file
@ -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;
|
@ -1,3 +1,5 @@
|
||||
extends Packet;
|
||||
|
||||
vtable 0x108a98;
|
||||
|
||||
property char *message = 0xc;
|
||||
|
Loading…
Reference in New Issue
Block a user