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()
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")
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

@ -53,4 +53,5 @@ TRUE 3D Chest Model
TRUE Replace Block Highlight With Outline
TRUE Add Cake
TRUE Use Java Beta 1.3 Light Ramp
TRUE Send Full Level When Hosting Game
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) {
Minecraft_setScreen(minecraft, create_chat_screen());
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());
}
return true;
} else {
return false;
}
open_chat_screen = 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
};
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_)

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,40 +4,29 @@
#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
Options *options = &minecraft->options;
if (hide_gui_toggle % 2 != 0) {
// Toggle Hide GUI
options->hide_gui = options->hide_gui ^ 1;
}
hide_gui_toggle = 0;
if (third_person_toggle % 2 != 0) {
// Toggle Third Person
options->third_person = (options->third_person + 1) % 3;
}
third_person_toggle = 0;
// Fix Broken Value From Third-Person OptionsButton Toggle
// (Because Front-Facing Code Repurposes A Boolean As A Ternary)
if (options->third_person == 3) {
options->third_person = 0;
}
static bool _handle_toggle_options(Minecraft *minecraft, int key) {
Options *options = &minecraft->options;
if (key == 0x70) {
// Toggle Hide GUI
options->hide_gui = options->hide_gui ^ 1;
return 1;
} else if (key == 0x74) {
// Toggle Third Person
options->third_person = (options->third_person + 1) % 3;
return 1;
} else {
return 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;
}
}
@ -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) {
// Font-Facing View
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)) {
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;
}

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 debugFpsMeterKeyPress(int key) = 0x79118;
method void renderFpsMeter(float param_1) = 0x79280;

View File

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

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;