This commit is contained in:
Bigjango13 2024-02-06 20:09:37 -05:00
commit a620445f63
50 changed files with 899 additions and 527 deletions

View File

@ -1,4 +1,4 @@
name: 'Build'
name: 'CI'
on:
push:
@ -34,12 +34,13 @@ jobs:
run: ./scripts/install-dependencies.sh ${{ matrix.arch }}
# Build
- name: Build
run: ./scripts/package.sh ${{ matrix.mode }} ${{ matrix.arch }}
run: ./scripts/build.mjs appimage ${{ matrix.mode }} ${{ matrix.arch }}
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.mode }}-${{ matrix.arch }}
name: ${{ matrix.mode }} (${{ matrix.arch }})
path: ./out/*.AppImage*
if-no-files-found: error
# Test Project
test:
strategy:
@ -59,12 +60,42 @@ jobs:
# Dependencies
- name: Install Dependencies
run: ./scripts/install-dependencies.sh
- name: Install ARM Toolchain
if: ${{ matrix.mode == 'Client' }}
run: apt-get install --no-install-recommends -y g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf
# Test
- name: Test
run: ./scripts/test.sh ${{ matrix.mode }}
# Example Mods
example-mods:
name: Build Example Mods
runs-on: ubuntu-latest
container: node:lts-bullseye
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
submodules: true
# Dependencies
- name: Install Dependencies
run: ./scripts/install-dependencies.sh
- name: Install ARM Toolchain
run: apt-get install --no-install-recommends -y g++-arm-linux-gnueabihf gcc-arm-linux-gnueabihf
# Build SDK
- name: Build SDK
run: |
./scripts/build.mjs none client host
export _MCPI_SKIP_ROOT_CHECK=1
export DISPLAY=
./out/client/host/usr/bin/minecraft-pi-reborn-client --copy-sdk
# Build Example Mods
- name: Build Example Mods
run: |
cd example-mods
./build.sh
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: Example Mods
path: ./example-mods/out/*
if-no-files-found: error
# Create Release
release:
if: startsWith(github.ref, 'refs/tags/')
@ -87,7 +118,7 @@ jobs:
- name: Create Release
uses: https://gitea.com/actions/release-action@main
with:
files: ./out
files: ./out/*/*.AppImage*
api_key: ${{ secrets.RELEASE_TOKEN }}
title: v${{ github.ref_name }}
body: "[View Changelog](https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/CHANGELOG.md)"

2
.gitignore vendored
View File

@ -12,3 +12,5 @@
/*.AppImage
/core*
/qemu_*
/example-mods/out
/.testing-tmp

View File

@ -9,8 +9,8 @@ endif()
include(cmake/options/core-options.cmake)
# Build Mode
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE)
endif()
# Start Project

View File

@ -2,6 +2,9 @@
mcpi_option(OPEN_SOURCE_ONLY "Only Install Open-Source Code (Will Result In Broken Install)" BOOL FALSE)
mcpi_option(IS_APPIMAGE_BUILD "AppImage Build" BOOL FALSE)
mcpi_option(IS_FLATPAK_BUILD "Flatpak Build" BOOL FALSE)
if(MCPI_IS_APPIMAGE_BUILD AND MCPI_IS_FLATPAK_BUILD)
message(FATAL_ERROR "Invalid Build Configuration")
endif()
# Server/Headless Builds
mcpi_option(SERVER_MODE "Server Mode" BOOL FALSE)
@ -61,3 +64,13 @@ mcpi_option(APP_TITLE "App Title" STRING "${DEFAULT_APP_TITLE}")
# Skin Server
mcpi_option(SKIN_SERVER "Skin Server" STRING "https://raw.githubusercontent.com/MCPI-Revival/Skins/data")
# QEMU
if(BUILD_NATIVE_COMPONENTS)
include(CheckSymbolExists)
check_symbol_exists("__ARM_ARCH" "" MCPI_IS_ARM32_OR_ARM64_TARGETING)
set(MCPI_USE_QEMU TRUE)
if(MCPI_IS_ARM32_OR_ARM64_TARGETING)
set(MCPI_USE_QEMU FALSE)
endif()
endif()

View File

@ -9,6 +9,7 @@ macro(setup_toolchain target)
add_target_variant(unknown)
add_target_variant(none)
add_target_variant(pc)
# Find Compiler
macro(find_compiler output name)
set(possible_names "")
@ -26,13 +27,11 @@ macro(setup_toolchain target)
endmacro()
find_compiler(CMAKE_C_COMPILER "gcc")
find_compiler(CMAKE_CXX_COMPILER "g++")
# Extra
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Custom Search Paths
if(NOT DEFINED ENV{MCPI_TOOLCHAIN_USE_DEFAULT_SEARCH_PATHS})
# Find Root
set(CMAKE_FIND_ROOT_PATH "/usr/${target}" "/usr/lib/${target}" "/usr")
# pkg-config
set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/${target}/pkgconfig:/usr/${target}/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig")
endif()
endmacro()

View File

@ -13,11 +13,11 @@ if(BUILD_NATIVE_COMPONENTS AND NOT MCPI_SERVER_MODE)
add_subdirectory(zenity)
endif()
# LIEF
if(BUILD_NATIVE_COMPONENTS OR (BUILD_ARM_COMPONENTS AND NOT MCPI_SERVER_MODE AND NOT MCPI_USE_MEDIA_LAYER_PROXY))
if(BUILD_NATIVE_COMPONENTS OR (BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE))
add_subdirectory(LIEF)
endif()
# QEMU
if(BUILD_NATIVE_COMPONENTS AND NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "arm*" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64"))
if(BUILD_NATIVE_COMPONENTS AND MCPI_USE_QEMU)
add_subdirectory(qemu)
endif()
# GLFW

View File

@ -9,14 +9,5 @@
## Instructions
```sh
./scripts/build.sh <client|server> <armhf|arm64|i686|amd64>
./scripts/build.mjs <none|appimage|flatpak> <client|server> <armhf|arm64|amd64|host> [--clean] [--install] [-Dvar=value...]
```
### Custom CMake Arguments
```sh
./scripts/setup.sh <client|server> <armhf|arm64|i686|amd64> <Custom CMake Arguments>
./scripts/build.sh <client|server> <armhf|arm64|i686|amd64>
```
### Environment Variables
* `MCPI_TOOLCHAIN_USE_DEFAULT_SEARCH_PATHS`: Use Default CMake Search Paths Rather Than Guessing

25
example-mods/build.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
set -e
# Create Output Directory
ROOT="$(pwd)"
OUT="${ROOT}/out"
rm -rf "${OUT}"
mkdir -p "${OUT}"
# Build
build() {
cd "${ROOT}/$1"
# Build
rm -rf build
mkdir build
cd build
cmake -GNinja ..
cmake --build .
# Copy Result
cp lib*.so "${OUT}"
}
build chat-commands
build expanded-creative
build recipes

View File

@ -10,10 +10,6 @@
#define MCPI_BINARY "minecraft-pi"
#define QEMU_BINARY "qemu-arm"
#ifndef __ARM_ARCH
#define USE_QEMU
#endif
#define REQUIRED_PAGE_SIZE 4096
#define _STR(x) #x
#define STR(x) _STR(x)
@ -163,7 +159,7 @@ void pre_bootstrap(int argc, char *argv[]) {
sigaction(SIGTERM, &act_sigterm, NULL);
// Check Page Size (Not Needed When Using QEMU)
#ifndef USE_QEMU
#ifndef MCPI_USE_QEMU
long page_size = sysconf(_SC_PAGESIZE);
if (page_size != REQUIRED_PAGE_SIZE) {
ERR("Invalid page size! A page size of %ld bytes is required, but the system size is %ld bytes.", (long) REQUIRED_PAGE_SIZE, page_size);
@ -338,7 +334,7 @@ void bootstrap(int argc, char *argv[]) {
new_args[argv_start] = new_mcpi_exe_path;
// Non-ARM Systems Need QEMU
#ifdef USE_QEMU
#ifdef MCPI_USE_QEMU
argv_start--;
new_args[argv_start] = QEMU_BINARY;
// Use 4k Page Size
@ -349,7 +345,7 @@ void bootstrap(int argc, char *argv[]) {
setup_exec_environment(1);
// Pass LD_* Variables Through QEMU
#ifdef USE_QEMU
#ifdef MCPI_USE_QEMU
char *qemu_set_env = NULL;
#define pass_variable_through_qemu(name) string_append(&qemu_set_env, "%s%s=%s", qemu_set_env == NULL ? "" : ",", name, getenv(name));
for_each_special_environmental_variable(pass_variable_through_qemu);

View File

@ -54,3 +54,4 @@ TRUE Replace Block Highlight With Outline
TRUE Add Cake
TRUE Use Java Beta 1.3 Light Ramp
TRUE Send Full Level When Hosting Game
FALSE Food Overlay

View File

@ -12,3 +12,4 @@
#cmakedefine MCPI_VARIANT_NAME "@MCPI_VARIANT_NAME@"
#cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@"
#cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@"
#cmakedefine MCPI_USE_QEMU

View File

@ -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]);
}

View File

@ -13,10 +13,6 @@ std::string chat_send_api_command(Minecraft *minecraft, std::string str);
extern "C" {
#endif
#ifndef MCPI_SERVER_MODE
void chat_open();
#endif
// Override using the HOOK() macro to provide customized chat behavior.
void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message);
void chat_handle_packet_send(Minecraft *minecraft, ChatPacket *packet);

View File

@ -10,8 +10,6 @@ typedef void (*input_tick_function_t)(Minecraft *minecraft);
void input_run_on_tick(input_tick_function_t function);
void input_set_is_right_click(int val);
void input_hide_gui();
void input_third_person();
int input_back();
void input_drop(int drop_slot);
void input_open_crafting();

View File

@ -23,6 +23,8 @@ typedef void (*misc_update_function_void_t)(void *obj);
void misc_run_on_tiles_setup(misc_update_function_void_t function); // obj == NULL
void misc_run_on_items_setup(misc_update_function_void_t function); // obj == NULL
void misc_run_on_language_setup(misc_update_function_void_t function); // obj == NULL
typedef bool (*misc_update_function_key_press_t)(Minecraft *minecrtaft, int key);
void misc_run_on_game_key_press(misc_update_function_key_press_t function); // In-Game Key Presses Only
void Level_saveLevelData_injection(Level *level);

View File

@ -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;
};

View File

@ -0,0 +1,5 @@
#pragma once
#include <symbols/minecraft.h>
Button *touch_create_button(int id, std::string text);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
// Determine 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);
}
@ -118,19 +111,17 @@ static Screen *create_chat_screen() {
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) {
misc_run_on_game_key_press([](Minecraft *minecraft, int key) {
if (key == 0x54) {
if (Minecraft_isLevelGenerated(minecraft) && minecraft->screen == NULL) {
Minecraft_setScreen(minecraft, create_chat_screen());
}
open_chat_screen = false;
return true;
} else {
return false;
}
});
}

View File

@ -63,15 +63,6 @@ HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
} else if (event->key.keysym.sym == SDLK_F2) {
screenshot_take(home_get());
handled = 1;
} else if (event->key.keysym.sym == SDLK_F1) {
input_hide_gui();
handled = 1;
} else if (event->key.keysym.sym == SDLK_F5) {
input_third_person();
handled = 1;
} else if (event->key.keysym.sym == SDLK_t) {
chat_open();
handled = 1;
} else if (event->key.keysym.sym == SDLK_ESCAPE) {
// Treat Escape As Back Button Press (This Fixes Issues With Signs)
handled = input_back();

View File

@ -1,233 +1,226 @@
// Config Needs To Load First
#include <libreborn/libreborn.h>
// 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 <symbols/minecraft.h>
#include <media-layer/core.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);
// Game Mode UI Code Is Useless In Headless Mode
#ifndef MCPI_HEADLESS_MODE
// 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);
}
#include <string>
#include <set>
// 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;
#include <symbols/minecraft.h>
#include <mods/text-input-box/TextInputScreen.h>
#include <mods/touch/touch.h>
// Strings
#define GAME_MODE_STR(mode) ("Game Mode: " mode)
#define SURVIVAL_STR GAME_MODE_STR("Survival")
#define CREATIVE_STR GAME_MODE_STR("Creative")
// 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
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);
};
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;
// 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_)

View File

@ -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);

View File

@ -4,41 +4,30 @@
#include "input-internal.h"
#include <mods/input/input.h>
#include <mods/feature/feature.h>
// Enable Toggles
static int enable_toggles = 0;
// Store Function Input
static int hide_gui_toggle = 0;
void input_hide_gui() {
hide_gui_toggle++;
}
static int third_person_toggle = 0;
void input_third_person() {
third_person_toggle++;
}
#include <mods/misc/misc.h>
// Handle Toggle Options
static void _handle_toggle_options(Minecraft *minecraft) {
if (enable_toggles) {
// Handle Functions
static bool _handle_toggle_options(Minecraft *minecraft, int key) {
Options *options = &minecraft->options;
if (hide_gui_toggle % 2 != 0) {
if (key == 0x70) {
// Toggle Hide GUI
options->hide_gui = options->hide_gui ^ 1;
}
hide_gui_toggle = 0;
if (third_person_toggle % 2 != 0) {
return 1;
} else if (key == 0x74) {
// Toggle Third Person
options->third_person = (options->third_person + 1) % 3;
return 1;
} else {
return 0;
}
third_person_toggle = 0;
}
static void _fix_third_person(Minecraft *minecraft) {
// Fix Broken Value From Third-Person OptionsButton Toggle
// (Because Front-Facing Code Repurposes A Boolean As A Ternary)
Options *options = &minecraft->options;
if (options->third_person == 3) {
options->third_person = 0;
}
}
}
// Font-Facing View
@ -99,11 +88,11 @@ static void ParticleEngine_render_injection(ParticleEngine *particle_engine, Ent
// Init
void _init_toggle() {
enable_toggles = feature_has("Bind Common Toggleable Options To Function Keys", server_disabled);
input_run_on_tick(_handle_toggle_options);
if (feature_has("Bind Common Toggleable Options To Function Keys", server_disabled)) {
misc_run_on_game_key_press(_handle_toggle_options);
misc_run_on_update(_fix_third_person);
// Font-Facing View
if (enable_toggles) {
overwrite_calls((void *) GameRenderer_setupCamera, (void *) GameRenderer_setupCamera_injection);
overwrite_calls((void *) ParticleEngine_render, (void *) ParticleEngine_render_injection);
}

View File

@ -7,18 +7,20 @@
#include "misc-internal.h"
// Callbacks
#define SETUP_CALLBACK(name, type) \
static std::vector<misc_update_function_##type##_t> &get_misc_##name##_functions() { \
#define STORE_CALLBACK(name, type) \
static std::vector<misc_update_function_##type##_t> &get_misc_##name##_functions() { \
static std::vector<misc_update_function_##type##_t> functions; \
return functions; \
} \
void misc_run_on_##name(misc_update_function_##type##_t function) { \
get_misc_##name##_functions().push_back(function); \
}
#define SETUP_CALLBACK(name, type) \
STORE_CALLBACK(name, type) \
static void handle_misc_##name(type *obj) { \
for (misc_update_function_##type##_t function : get_misc_##name##_functions()) { \
function(obj); \
} \
} \
void misc_run_on_##name(misc_update_function_##type##_t function) { \
get_misc_##name##_functions().push_back(function); \
}
// Run Functions On Update
@ -115,6 +117,27 @@ static void I18n_loadLanguage_injection(AppPlatform *app, std::string language_n
handle_misc_language_setup(NULL);
}
// Run Functions On GUI Key Press
STORE_CALLBACK(game_key_press, key_press)
static bool handle_misc_game_key_press(Minecraft *minecraft, int key) {
for (misc_update_function_key_press_t function : get_misc_game_key_press_functions()) {
if (function(minecraft, key)) {
return true;
}
}
return false;
}
// Handle Key Presses
static void Gui_handleKeyPressed_injection(Gui *self, int key) {
// Run Functions
if (handle_misc_game_key_press(self->minecraft, key)) {
return;
}
// Call Original Method
Gui_handleKeyPressed(self, key);
}
// Init
void _init_misc_api() {
// Handle Custom Update Behavior
@ -131,4 +154,6 @@ void _init_misc_api() {
overwrite_calls((void *) Item_initItems, (void *) Item_initItems_injection);
// Handle Custom Language Entries
overwrite_calls((void *) I18n_loadLanguage, (void *) I18n_loadLanguage_injection);
// Handle Key Presses
overwrite_calls((void *) Gui_handleKeyPressed, (void *) Gui_handleKeyPressed_injection);
}

View File

@ -3,6 +3,7 @@
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <math.h>
#ifndef MCPI_HEADLESS_MODE
#include <GLES/gl.h>
@ -19,6 +20,60 @@
#include "misc-internal.h"
#include <mods/misc/misc.h>
// Heart food overlay
static int heal_amount = 0, heal_amount_drawing = 0;
void Gui_renderHearts_injection(Gui *gui) {
// Get heal_amount
heal_amount = heal_amount_drawing = 0;
Inventory *inventory = gui->minecraft->player->inventory;
ItemInstance *held_ii = Inventory_getSelected(inventory);
if (held_ii) {
Item *held = Item_items[held_ii->id];
if (held->vtable->isFood(held) && held_ii->id) {
int nutrition = ((FoodItem *) held)->nutrition;
int cur_health = gui->minecraft->player->health;
int heal_num = fmin(cur_health + nutrition, 20) - cur_health;
heal_amount = heal_amount_drawing = heal_num;
}
}
// Call original
Gui_renderHearts(gui);
}
#define PINK_HEART_FULL 70
#define PINK_HEART_HALF 79
Gui_blit_t Gui_blit_renderHearts_original = NULL;
void Gui_renderHearts_GuiComponent_blit_overlay_empty_injection(Gui *gui, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t w1, int32_t h1, int32_t w2, int32_t h2) {
// Call original
Gui_blit_renderHearts_original(gui, x1, y1, x2, y2, w1, h1, w2, h2);
// Render the overlay
if (heal_amount_drawing == 1) {
// Half heart
Gui_blit_renderHearts_original(gui, x1, y1, PINK_HEART_HALF, 0, w1, h1, w2, h2);
heal_amount_drawing = 0;
} else if (heal_amount_drawing > 0) {
// Full heart
Gui_blit_renderHearts_original(gui, x1, y1, PINK_HEART_FULL, 0, w1, h1, w2, h2);
heal_amount_drawing -= 2;
}
}
void Gui_renderHearts_GuiComponent_blit_overlay_hearts_injection(Gui *gui, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t w1, int32_t h1, int32_t w2, int32_t h2) {
// Offset the overlay
if (x2 == 52) {
heal_amount_drawing += 2;
} else if (x2 == 61 && heal_amount) {
// Half heart, flipped
Gui_blit_renderHearts_original(gui, x1, y1, PINK_HEART_FULL, 0, w1, h1, w2, h2);
heal_amount_drawing += 1;
};
// Call original
Gui_blit_renderHearts_original(gui, x1, y1, x2, y2, w1, h1, w2, h2);
heal_amount_drawing = fmin(heal_amount_drawing, heal_amount);
}
// Classic HUD
#define DEFAULT_HUD_PADDING 2
#define NEW_HUD_PADDING 1
@ -581,14 +636,24 @@ void init_misc() {
patch((void *) 0x63c98, invalid_item_background_patch);
}
// Classic HUD
Gui_blit_renderHearts_original = Gui_blit;
if (feature_has("Classic HUD", server_disabled)) {
use_classic_hud = 1;
overwrite_call((void *) 0x266f8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection);
overwrite_call((void *) 0x26758, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection);
overwrite_call((void *) 0x267c8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection);
overwrite_call((void *) 0x2656c, (void *) Gui_renderHearts_GuiComponent_blit_armor_injection);
overwrite_call((void *) 0x268c4, (void *) Gui_renderBubbles_GuiComponent_blit_injection);
overwrite_call((void *) 0x266f8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection);
overwrite_call((void *) 0x267c8, (void *) Gui_renderHearts_GuiComponent_blit_hearts_injection);
Gui_blit_renderHearts_original = Gui_renderHearts_GuiComponent_blit_hearts_injection;
}
// Food overlay
if (feature_has("Food Overlay", server_disabled)) {
overwrite_calls((void *) Gui_renderHearts, Gui_renderHearts_injection);
overwrite_call((void *) 0x266f8, (void *) Gui_renderHearts_GuiComponent_blit_overlay_empty_injection);
overwrite_call((void *) 0x267c8, (void *) Gui_renderHearts_GuiComponent_blit_overlay_hearts_injection);
}
// Render Selected Item Text + Hide Chat Messages

View File

@ -127,6 +127,12 @@ static void OptionsPane_unknown_toggle_creating_function_injection(OptionsPane *
std::string cpp_string = "3D Anaglyph";
OptionsPane_unknown_toggle_creating_function(options_pane, group_id, &cpp_string, &Options_Option_ANAGLYPH);
}
// Add Peaceful Mode
if (option == &Options_Option_SERVER_VISIBLE) {
std::string cpp_string = "Peaceful mode";
OptionsPane_unknown_toggle_creating_function(options_pane, group_id, &cpp_string, &Options_Option_DIFFICULTY);
}
}
// Add Missing Options To Options::getBooleanValue
@ -134,12 +140,28 @@ static bool Options_getBooleanValue_injection(Options *options, Options_Option *
// Check
if (option == &Options_Option_GRAPHICS) {
return options->fancy_graphics;
} else if (option == &Options_Option_DIFFICULTY) {
return options->game_difficulty == 0;
} else {
// Call Original Method
return Options_getBooleanValue(options, option);
}
}
// Fix Difficulty When Toggling
static void OptionButton_toggle_Options_save_injection(Options *self) {
// Fix Value
if (self->game_difficulty == 1) {
// Disable Peaceful
self->game_difficulty = 2;
} else if (self->game_difficulty == 3) {
// Switch To Peaceful
self->game_difficulty = 0;
}
// Call Original Method
Options_save(self);
}
// Init C++
void _init_options_cpp() {
// NOP
@ -164,6 +186,9 @@ void _init_options_cpp() {
// Add Missing Options To Options::getBooleanValue
overwrite_calls((void *) Options_getBooleanValue, (void *) Options_getBooleanValue_injection);
// Fix Difficulty When Toggling
overwrite_call((void *) 0x1cd00, (void *) OptionButton_toggle_Options_save_injection);
}
// Actually Save options.txt

View File

@ -19,6 +19,10 @@ static int32_t sdl_key_to_minecraft_key_injection(int32_t sdl_key) {
return 0x25;
} else if (sdl_key == SDLK_RIGHT) {
return 0x27;
} else if (sdl_key == SDLK_F1) {
return 0x70;
} else if (sdl_key == SDLK_F5) {
return 0x74;
} else {
// Call Original Method
return Common_sdl_key_to_minecraft_key(sdl_key);

View File

@ -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)) {
// 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;
}

View File

@ -2,6 +2,7 @@
#include <mods/feature/feature.h>
#include <mods/init/init.h>
#include <mods/touch/touch.h>
#include <symbols/minecraft.h>
@ -28,7 +29,7 @@ static int32_t Button_hovered_injection(__attribute__((unused)) Button *button,
int32_t button_y2 = button_y1 + button->height;
// Check
return x >= button_x1 && x <= button_x2 && y >= button_y1 && y <= button_y2;
return x >= button_x1 && x < button_x2 && y >= button_y1 && y < button_y2;
}
static void LargeImageButton_render_GuiComponent_drawCenteredString_injection(GuiComponent *component, Font *font, std::string *text, int32_t x, int32_t y, int32_t color) {
// Change Color On Hover
@ -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

208
scripts/build.mjs Executable file
View File

@ -0,0 +1,208 @@
#!/usr/bin/env node
import * as path from 'node:path';
import * as url from 'node:url';
import * as fs from 'node:fs';
import * as child_process from 'node:child_process';
// Logging
const EXIT_FAILURE = 1;
function fail(message) {
console.error(message);
process.exit(EXIT_FAILURE);
}
function err(message) {
fail('ERROR: ' + message);
}
function info(message) {
console.log('INFO: ' + message);
}
// Enums
function Enum(values) {
for (const value of values) {
this[value] = {name: value.toLowerCase()};
}
}
Enum.prototype.get = function (name) {
for (const value in this) {
if (value.toLowerCase() === name.toLowerCase()) {
return this[value];
}
}
return null;
};
function wrap(obj) {
return new Proxy(obj, {
get(target, property) {
if (property in target) {
return target[property];
} else {
err('Undefined Value: ' + property);
}
}
});
}
const PackageTypes = wrap(new Enum([
'None',
'AppImage',
'Flatpak'
]));
const Variants = wrap(new Enum([
'Client',
'Server'
]));
const Architectures = wrap(new Enum([
'AMD64',
'ARM64',
'ARMHF',
'Host'
]));
// Folders
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const root = path.join(__dirname, '..');
let build = path.join(root, 'build');
let out = path.join(root, 'out');
// Positional Arguments
let argIndex = 2; // Skip First Two Arguments
const POSITIONAL_ARGUMENT_COUNT = 3;
function readArg(from, type) {
// Check Argument Count
if (argIndex >= process.argv.length) {
err('Expecting ' + type);
}
// Read Argument
const arg = process.argv[argIndex++];
const value = from.get(arg);
if (value === null) {
err(`Invalid ${type}: ${arg}`);
}
// Return
return value;
}
// Type Of Packaging
const packageType = readArg(PackageTypes, 'Package Type');
// Build Variant
const variant = readArg(Variants, 'Variant');
// Build Architecture
const architecture = readArg(Architectures, 'Architecture');
// Flatpak Builds Work Best Without Custom Toolchains
if (packageType === PackageTypes.Flatpak && architecture !== Architectures.Host) {
err('Flatpak Builds Do Not Support Custom Toolchains');
}
// CMake Build Options
const options = new Map();
// Other Arguments
let clean = false;
let install = false;
for (; argIndex < process.argv.length; argIndex++) {
const arg = process.argv[argIndex];
if (arg.startsWith('-D')) {
// Pass Build Option To CMake
let parsedArg = arg.substring(2);
const split = parsedArg.indexOf('=');
if (split === -1) {
err('Unable To Parse Build Option: ' + arg);
}
const name = parsedArg.substring(0, split);
const value = parsedArg.substring(split + 1);
if (!/^[a-zA-Z_]+$/.test(name) || name.length === 0) {
err('Invalid Build Option Name: ' + name);
}
options.set(name, value);
} else if (arg === '--clean') {
// Remove Existing Build Directory
clean = true;
} else if (arg === '--install') {
// Install To System Instead Of Output Directory
if (packageType === PackageTypes.AppImage) {
err('AppImages Cannot Be Installed');
}
install = true;
} else {
err('Invalid Argument: ' + arg);
}
}
// Update Folders
function updateDir(dir) {
if (packageType !== PackageTypes.None) {
dir = path.join(dir, packageType.name);
}
return path.join(dir, variant.name, architecture.name);
}
build = updateDir(build);
let cleanOut = false;
// AppImages Are Placed Directly In ./out
if (packageType !== PackageTypes.AppImage) {
cleanOut = true;
out = updateDir(out);
}
// Configure Build Options
function toCmakeBool(val) {
return val ? 'ON' : 'OFF';
}
options.set('MCPI_SERVER_MODE', toCmakeBool(variant === Variants.Server));
options.set('MCPI_IS_APPIMAGE_BUILD', toCmakeBool(packageType === PackageTypes.AppImage));
options.set('MCPI_IS_FLATPAK_BUILD', toCmakeBool(packageType === PackageTypes.Flatpak));
if (architecture !== Architectures.Host) {
options.set('CMAKE_TOOLCHAIN_FILE', path.join(root, 'cmake', 'toolchain', architecture.name + '-toolchain.cmake'));
} else {
options.delete('CMAKE_TOOLCHAIN_FILE');
}
// Make Build Directory
function createDir(dir, clean) {
if (clean) {
fs.rmSync(dir, {recursive: true, force: true});
}
fs.mkdirSync(dir, {recursive: true});
}
createDir(build, clean);
if (!install) {
createDir(out, cleanOut);
}
// Run CMake
function run(command) {
try {
info('Running: ' + command.join(' '));
child_process.execFileSync(command[0], command.slice(1), {cwd: build, stdio: 'inherit'});
} catch (e) {
err(e);
}
}
const cmake = ['cmake', '-GNinja'];
options.forEach((value, key, map) => {
cmake.push(`-D${key}=${value}`);
});
cmake.push(root);
run(cmake);
// Build
run(['cmake', '--build', '.']);
// Package
if (packageType !== PackageTypes.AppImage) {
if (!install) {
process.env.DESTDIR = out;
}
run(['cmake', '--install', '.']);
} else {
run(['cmake', '--build', '.', '--target', 'package']);
// Copy Generated Files
const files = fs.readdirSync(build);
for (const file of files) {
if (file.includes('.AppImage')) {
info('Copying: ' + file);
const src = path.join(build, file);
const dst = path.join(out, file);
fs.copyFileSync(src, dst);
}
}
}

View File

@ -1,28 +0,0 @@
#!/bin/sh
set -e
# Variables
MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')"
# Run CMake If Needed
if [ ! -f "build/${MODE}-${ARCH}/build.ninja" ]; then
./scripts/setup.sh "${MODE}" "${ARCH}"
fi
# Use Build Dir
cd "build/${MODE}-${ARCH}"
# Create Prefix
if [ -z "${DESTDIR+x}" ]; then
export DESTDIR="$(cd ../../; pwd)/out/${MODE}-${ARCH}"
rm -rf "${DESTDIR}"
mkdir -p "${DESTDIR}"
fi
# Build
cmake --build .
cmake --install .
# Exit
cd ../../

View File

@ -1,20 +0,0 @@
#!/bin/sh
set -e
# Prepare
NAME='minecraft-pi-reborn'
MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')"
# Build
./scripts/setup.sh "${MODE}" "${ARCH}" -DMCPI_IS_APPIMAGE_BUILD=ON
./scripts/build.sh "${MODE}" "${ARCH}"
# Package
cd "build/${MODE}-${ARCH}"
rm -f *.AppImage*
cmake --build . --target package
# Copy Output
cp *.AppImage* ../../out

View File

@ -1,38 +0,0 @@
#!/bin/sh
set -e
# Variables
MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')"
shift 2
# Verify Mode
if [ "${MODE}" != "client" ] && [ "${MODE}" != "server" ]; then
echo "Invalid Mode: ${MODE}" > /dev/stderr
exit 1
fi
# Find Toolchain
toolchain_file="$(pwd)/cmake/toolchain/${ARCH}-toolchain.cmake"
if [ ! -f "${toolchain_file}" ]; then
echo "Invalid Architecture: ${ARCH}" > /dev/stderr
exit 1
fi
# Create Build Dir
rm -rf "build/${MODE}-${ARCH}"
mkdir -p "build/${MODE}-${ARCH}"
cd "build/${MODE}-${ARCH}"
# Server Build
server_mode='OFF'
if [ "${MODE}" = "server" ]; then
server_mode='ON'
fi
# Build Components
cmake -GNinja -DCMAKE_TOOLCHAIN_FILE="${toolchain_file}" -DMCPI_SERVER_MODE="${server_mode}" "$@" ../../
# Exit
cd ../../

View File

@ -4,39 +4,30 @@ set -e
# Variables
MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH)"
ARCH='host'
# Build
./scripts/setup.sh "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON
./scripts/build.sh "${MODE}" "${ARCH}"
./scripts/build.mjs none "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON
# Add To PATH
export PATH="$(pwd)/out/${MODE}-${ARCH}/usr/bin:${PATH}"
export PATH="$(pwd)/out/${MODE}/${ARCH}/usr/bin:${PATH}"
# Make Test Directory
rm -rf build/test
mkdir -p build/test
TEST_WORKING_DIR="$(pwd)/.testing-tmp"
rm -rf "${TEST_WORKING_DIR}"
mkdir -p "${TEST_WORKING_DIR}"
# Run
if [ "${MODE}" = "server" ]; then
# Server Test
cd build/test
cd "${TEST_WORKING_DIR}"
minecraft-pi-reborn-server --only-generate
cd ../../
else
# Client Test
export _MCPI_SKIP_ROOT_CHECK=1
export HOME="$(pwd)/build/test"
export HOME="${TEST_WORKING_DIR}"
minecraft-pi-reborn-client --default --no-cache --benchmark
# Build Example Mods
for project in example-mods/*/; do
cd "${project}"
rm -rf build
mkdir build
cd build
cmake -GNinja ..
cmake --build .
cd ../../../
done
fi
# Clean Up
rm -rf "${TEST_WORKING_DIR}"

View File

@ -77,6 +77,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
@ -103,6 +104,7 @@ set(SRC
src/gui/screens/ProgressScreen.def
src/gui/screens/Touch_SelectWorldScreen.def
src/gui/screens/PaneCraftingScreen.def
src/gui/screens/ScreenChooser.def
src/gui/Font.def
src/gui/components/ImageButton.def
src/gui/components/OptionButton.def
@ -147,6 +149,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

View File

@ -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;

View File

@ -3,6 +3,7 @@ size 0x110;
method void initDefaultValue() = 0x18a54;
method bool getBooleanValue(Options_Option *option) = 0x1cd74;
method void addOptionToSaveOutput(std::vector<std::string> *data, std::string option, int value) = 0x195e4;
method void save() = 0x1966c;
property OptionsFile options_file = 0x10c;
property bool fancy_graphics = 0x17;

View File

@ -1,3 +1,5 @@
static-property Options_Option GRAPHICS = 0x136c2c;
static-property Options_Option AMBIENT_OCCLUSION = 0x136c38;
static-property Options_Option ANAGLYPH = 0x136c08;
static-property Options_Option DIFFICULTY = 0x136c20;
static-property Options_Option SERVER_VISIBLE = 0x136c68;

View File

@ -12,6 +12,7 @@ method void addMessage(std::string *text) = 0x27820;
method void getSlotPos(int slot, int *x, int *y) = 0x25548;
method void renderSlot(int slot, int x, int y, float alpha) = 0x25cc0;
method void renderSlotText(ItemInstance *item, float x, float y, bool finite, bool shadow) = 0x25df8;
method void handleKeyPressed(int key) = 0x25a08;
method void renderHearts() = 0x2641c;
property Minecraft *minecraft = 0x9f4;

View File

@ -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;

View File

@ -10,7 +10,7 @@ virtual-method void init() = 0xc;
virtual-method void render(int x, int y, float param_1) = 0x8;
virtual-method void setupPositions() = 0x10;
virtual-method void updateEvents() = 0x14;
virtual-method bool handleBackEvent(bool param_1) = 0x24;
virtual-method bool handleBackEvent(bool do_nothing) = 0x24;
virtual-method void tick() = 0x28;
virtual-method void removed() = 0x2c;
virtual-method void renderBackground() = 0x30;

View File

@ -0,0 +1 @@
method void setScreen(uint id) = 0x29490;

View File

@ -1 +1,2 @@
virtual-method void deleteLevel(std::string *level_name) = 0x20;
virtual-method void getLevelList(std::vector<LevelSummary> *level_list) = 0xc;

View 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;

View File

@ -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;

View File

@ -1 +1,2 @@
method void debugFpsMeterKeyPress(int key) = 0x79118;
method void renderFpsMeter(float param_1) = 0x79280;

View File

@ -8,3 +8,5 @@ static-property char **feedback_vibration_options_txt_name_1 = 0x198a0; // feedb
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-array char creative_mode_description = 0x104492; // Unlimited resources and flying
static-property-array char survival_mode_description = 0x104470; // Mobs, health and gather resources

View 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;

View File

@ -1,3 +1,5 @@
extends Packet;
vtable 0x108a98;
property char *message = 0xc;