Merge branch 'master' of https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn
This commit is contained in:
commit
a620445f63
|
@ -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)"
|
||||
|
|
|
@ -12,3 +12,5 @@
|
|||
/*.AppImage
|
||||
/core*
|
||||
/qemu_*
|
||||
/example-mods/out
|
||||
/.testing-tmp
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <symbols/minecraft.h>
|
||||
|
||||
Button *touch_create_button(int id, std::string text);
|
|
@ -1,19 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <libreborn/libreborn.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// Message Limitations
|
||||
#define MAX_CHAT_MESSAGE_LENGTH 256
|
||||
|
||||
// Message Prefix
|
||||
__attribute__((visibility("internal"))) std::string _chat_get_prefix(char *username);
|
||||
|
||||
// Queue Message For Sending
|
||||
#ifndef MCPI_SERVER_MODE
|
||||
__attribute__((visibility("internal"))) void _chat_queue_message(const char *message);
|
||||
#endif
|
||||
|
||||
// Init Chat UI
|
||||
#ifndef MCPI_HEADLESS_MODE
|
||||
__attribute__((visibility("internal"))) void _init_chat_ui();
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -19,9 +19,6 @@
|
|||
#include "chat-internal.h"
|
||||
#include <mods/chat/chat.h>
|
||||
|
||||
// Message Limitations
|
||||
#define MAX_CHAT_MESSAGE_LENGTH 512
|
||||
|
||||
// Send API Command
|
||||
std::string chat_send_api_command(Minecraft *minecraft, std::string str) {
|
||||
struct ConnectedClient client;
|
||||
|
@ -47,9 +44,12 @@ static void send_api_chat_command(Minecraft *minecraft, char *str) {
|
|||
#endif
|
||||
|
||||
// Send Message To Players
|
||||
std::string _chat_get_prefix(char *username) {
|
||||
return std::string("<") + username + "> ";
|
||||
}
|
||||
void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message) {
|
||||
char *full_message = NULL;
|
||||
safe_asprintf(&full_message, "<%s> %s", username, message);
|
||||
safe_asprintf(&full_message, "%s%s", _chat_get_prefix(username).c_str(), message);
|
||||
sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0);
|
||||
std::string cpp_string = full_message;
|
||||
free(full_message);
|
||||
|
@ -123,5 +123,8 @@ void init_chat() {
|
|||
// Init UI
|
||||
_init_chat_ui();
|
||||
#endif
|
||||
// Disable Built-In Chat Message Limiting
|
||||
unsigned char message_limit_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r4, r4"
|
||||
patch((void *) 0x6b4c0, message_limit_patch);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <mods/chat/chat.h>
|
||||
#include <mods/text-input-box/TextInputScreen.h>
|
||||
#include <mods/misc/misc.h>
|
||||
#include <mods/touch/touch.h>
|
||||
|
||||
// Structure
|
||||
struct ChatScreen {
|
||||
|
@ -23,24 +24,16 @@ CUSTOM_VTABLE(chat_screen, Screen) {
|
|||
original_init(super);
|
||||
ChatScreen *self = (ChatScreen *) super;
|
||||
// Text Input
|
||||
self->chat = TextInputBox::create(1);
|
||||
self->chat = TextInputBox::create();
|
||||
self->super.m_textInputs->push_back(self->chat);
|
||||
self->chat->init(super->font);
|
||||
self->chat->setFocused(true);
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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_)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
#include <mods/text-input-box/TextInputBox.h>
|
||||
|
||||
TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const std::string &text) {
|
||||
TextInputBox *TextInputBox::create(const std::string &placeholder, const std::string &text) {
|
||||
// Construct
|
||||
TextInputBox *self = new TextInputBox;
|
||||
GuiComponent_constructor(&self->super);
|
||||
|
||||
// Setup
|
||||
self->m_ID = id;
|
||||
self->m_xPos = 0;
|
||||
self->m_yPos = 0;
|
||||
self->m_width = 0;
|
||||
|
@ -22,6 +21,7 @@ TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const
|
|||
self->m_lastFlashed = 0;
|
||||
self->m_pFont = nullptr;
|
||||
self->m_maxLength = -1;
|
||||
self->m_scrollPos = 0;
|
||||
|
||||
// Return
|
||||
return self;
|
||||
|
@ -32,6 +32,7 @@ void TextInputBox::setSize(int x, int y, int width, int height) {
|
|||
m_yPos = y;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
recalculateScroll();
|
||||
}
|
||||
|
||||
void TextInputBox::init(Font *pFont) {
|
||||
|
@ -61,6 +62,7 @@ void TextInputBox::keyPressed(int key) {
|
|||
}
|
||||
m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead);
|
||||
m_insertHead--;
|
||||
recalculateScroll();
|
||||
break;
|
||||
}
|
||||
case 0x2e: {
|
||||
|
@ -83,6 +85,7 @@ void TextInputBox::keyPressed(int key) {
|
|||
if (m_insertHead < 0) {
|
||||
m_insertHead = 0;
|
||||
}
|
||||
recalculateScroll();
|
||||
break;
|
||||
}
|
||||
case 0x27: {
|
||||
|
@ -95,6 +98,7 @@ void TextInputBox::keyPressed(int key) {
|
|||
} else {
|
||||
m_insertHead = 0;
|
||||
}
|
||||
recalculateScroll();
|
||||
break;
|
||||
}
|
||||
case 0x0d: {
|
||||
|
@ -130,6 +134,7 @@ void TextInputBox::setFocused(bool b) {
|
|||
m_lastFlashed = Common_getTimeMs();
|
||||
m_bCursorOn = true;
|
||||
m_insertHead = int(m_text.size());
|
||||
recalculateScroll();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,38 +148,64 @@ void TextInputBox::charPressed(int k) {
|
|||
return;
|
||||
}
|
||||
|
||||
// note: the width will increase by the same amount no matter where K is appended
|
||||
std::string test_str = m_text + char(k);
|
||||
if (m_maxLength != -1 && int(test_str.length()) > m_maxLength) {
|
||||
// Ignore Newlines
|
||||
if (k == '\n') {
|
||||
return;
|
||||
}
|
||||
int width = Font_width(m_pFont, &test_str);
|
||||
if (width < (m_width - PADDING)) {
|
||||
m_text.insert(m_text.begin() + m_insertHead, k);
|
||||
m_insertHead++;
|
||||
|
||||
// Check Max Length
|
||||
if (m_maxLength != -1 && int(m_text.length()) >= m_maxLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert
|
||||
m_text.insert(m_text.begin() + m_insertHead, k);
|
||||
m_insertHead++;
|
||||
recalculateScroll();
|
||||
}
|
||||
|
||||
static std::string get_rendered_text(Font *font, int width, int scroll_pos, std::string text) {
|
||||
std::string rendered_text = text.substr(scroll_pos);
|
||||
int max_width = width - (PADDING * 2);
|
||||
while (Font_width(font, &rendered_text) > max_width) {
|
||||
rendered_text.pop_back();
|
||||
}
|
||||
return rendered_text;
|
||||
}
|
||||
|
||||
static char CURSOR_CHAR = '_';
|
||||
|
||||
void TextInputBox::render() {
|
||||
GuiComponent_fill(&super, m_xPos, m_yPos, m_xPos + m_width, m_yPos + m_height, 0xFFAAAAAA);
|
||||
GuiComponent_fill(&super, m_xPos + 1, m_yPos + 1, m_xPos + m_width - 1, m_yPos + m_height - 1, 0xFF000000);
|
||||
|
||||
int textYPos = (m_height - 8) / 2;
|
||||
|
||||
int text_color;
|
||||
int scroll_pos;
|
||||
std::string rendered_text;
|
||||
if (m_text.empty()) {
|
||||
GuiComponent_drawString(&super, m_pFont, &m_placeholder, m_xPos + PADDING, m_yPos + textYPos, 0x404040);
|
||||
rendered_text = m_placeholder;
|
||||
text_color = 0x404040;
|
||||
scroll_pos = 0;
|
||||
} else {
|
||||
GuiComponent_drawString(&super, m_pFont, &m_text, m_xPos + PADDING, m_yPos + textYPos, 0xFFFFFF);
|
||||
rendered_text = m_text;
|
||||
text_color = 0xffffff;
|
||||
scroll_pos = m_scrollPos;
|
||||
}
|
||||
rendered_text = get_rendered_text(m_pFont, m_width, scroll_pos, rendered_text);
|
||||
|
||||
int textYPos = (m_height - 8) / 2;
|
||||
GuiComponent_drawString(&super, m_pFont, &rendered_text, m_xPos + PADDING, m_yPos + textYPos, text_color);
|
||||
|
||||
if (m_bCursorOn) {
|
||||
int xPos = 5;
|
||||
int cursor_pos = m_insertHead - m_scrollPos;
|
||||
if (cursor_pos >= 0 && cursor_pos <= int(rendered_text.length())) {
|
||||
std::string substr = rendered_text.substr(0, cursor_pos);
|
||||
int xPos = PADDING + Font_width(m_pFont, &substr);
|
||||
|
||||
std::string substr = m_text.substr(0, m_insertHead);
|
||||
xPos += Font_width(m_pFont, &substr);
|
||||
|
||||
std::string str = "_";
|
||||
GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xFFFFFF);
|
||||
std::string str;
|
||||
str += CURSOR_CHAR;
|
||||
GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xffffff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,3 +229,66 @@ bool TextInputBox::clicked(int xPos, int yPos) {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextInputBox::recalculateScroll() {
|
||||
// Skip If Size Unset
|
||||
if (m_width == 0) {
|
||||
return;
|
||||
}
|
||||
// Ensure Cursor Is Visible
|
||||
bool is_cursor_at_end = m_insertHead == int(m_text.length());
|
||||
if (m_scrollPos >= m_insertHead && m_scrollPos > 0) {
|
||||
// Cursor Is At Scroll Position
|
||||
// Move Back Scroll As Far As Possible
|
||||
while (true) {
|
||||
int test_scroll_pos = m_scrollPos - 1;
|
||||
std::string rendered_text = m_text;
|
||||
if (is_cursor_at_end) {
|
||||
rendered_text += CURSOR_CHAR;
|
||||
}
|
||||
rendered_text = get_rendered_text(m_pFont, m_width, test_scroll_pos, rendered_text);
|
||||
int cursor_pos = m_insertHead - test_scroll_pos;
|
||||
if (cursor_pos >= int(rendered_text.length())) {
|
||||
break;
|
||||
} else {
|
||||
m_scrollPos = test_scroll_pos;
|
||||
if (m_scrollPos == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Cursor After Scroll Area
|
||||
// Increase Scroll So Cursor Is Visible
|
||||
while (true) {
|
||||
std::string rendered_text = m_text;
|
||||
if (is_cursor_at_end) {
|
||||
rendered_text += CURSOR_CHAR;
|
||||
}
|
||||
rendered_text = get_rendered_text(m_pFont, m_width, m_scrollPos, rendered_text);
|
||||
int cursor_pos = m_insertHead - m_scrollPos;
|
||||
if (cursor_pos < int(rendered_text.length())) {
|
||||
break;
|
||||
} else {
|
||||
if (m_scrollPos == int(m_text.length())) {
|
||||
WARN("Text Box Is Too Small");
|
||||
break;
|
||||
} else {
|
||||
m_scrollPos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string TextInputBox::getText() {
|
||||
return m_text;
|
||||
}
|
||||
|
||||
bool TextInputBox::isFocused() {
|
||||
return m_bFocused;
|
||||
}
|
||||
|
||||
void TextInputBox::setMaxLength(int max_length) {
|
||||
m_maxLength = max_length;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <mods/feature/feature.h>
|
||||
#include <mods/init/init.h>
|
||||
#include <mods/touch/touch.h>
|
||||
|
||||
#include <symbols/minecraft.h>
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ../../
|
|
@ -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
|
|
@ -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 ../../
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -9,3 +9,4 @@ property int width = 0x14;
|
|||
property int height = 0x18;
|
||||
property int x = 0xc;
|
||||
property int y = 0x10;
|
||||
property std::string text = 0x1c;
|
||||
|
|
|
@ -10,7 +10,7 @@ virtual-method void 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;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
method void setScreen(uint id) = 0x29490;
|
|
@ -1 +1,2 @@
|
|||
virtual-method void deleteLevel(std::string *level_name) = 0x20;
|
||||
virtual-method void getLevelList(std::vector<LevelSummary> *level_list) = 0xc;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
size 0x14;
|
||||
|
||||
property std::string folder = 0x0;
|
||||
property std::string name = 0x4;
|
||||
property int seed = 0x8;
|
||||
property int game_mode = 0xc;
|
||||
property int param_5 = 0x10;
|
|
@ -6,3 +6,4 @@ static-method void sleepMs(int x) = 0x13cf4;
|
|||
static-method int sdl_key_to_minecraft_key(int sdl_key) = 0x1243c;
|
||||
static-method void anGenBuffers(int count, uint *buffers) = 0x5f28c;
|
||||
static-method int getTimeMs() = 0x13cd4;
|
||||
static-method int getEpochTimeS() = 0x13d00;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
method void debugFpsMeterKeyPress(int key) = 0x79118;
|
||||
method void debugFpsMeterKeyPress(int key) = 0x79118;
|
||||
method void renderFpsMeter(float param_1) = 0x79280;
|
||||
|
|
|
@ -7,4 +7,6 @@ static-property char *options_txt_fopen_mode_when_loading = 0x19d24; // w
|
|||
static-property char **feedback_vibration_options_txt_name_1 = 0x198a0; // feedback_vibration
|
||||
static-property char **feedback_vibration_options_txt_name_2 = 0x194bc; // feedback_vibration
|
||||
static-property char **gfx_lowquality_options_txt_name = 0x194c4; // gfx_lowquality
|
||||
static-property char *classic_create_button_text = 0x39bec; // Create
|
||||
static-property char *classic_create_button_text = 0x39bec; // Create
|
||||
static-property-array char creative_mode_description = 0x104492; // Unlimited resources and flying
|
||||
static-property-array char survival_mode_description = 0x104470; // Mobs, health and gather resources
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
static-method std::string stringTrim(std::string *str) = 0x77c40;
|
||||
static-method int hashCode(std::string *str) = 0x77a50;
|
||||
static-method std::string *stringReplace(std::string *str, std::string *what, std::string *with, int param_1) = 0x779f0;
|
|
@ -1,3 +1,5 @@
|
|||
extends Packet;
|
||||
|
||||
vtable 0x108a98;
|
||||
|
||||
property char *message = 0xc;
|
||||
|
|
Loading…
Reference in New Issue