Fancy Info Screen

This commit is contained in:
TheBrokenRail 2024-05-09 01:25:53 -04:00
parent 09bae1cf3e
commit d519142a8a
15 changed files with 391 additions and 60 deletions

View File

@ -61,3 +61,4 @@ TRUE Add Splashes
TRUE Display Date In Select World Screen TRUE Display Date In Select World Screen
TRUE Optimized Chunk Sorting TRUE Optimized Chunk Sorting
TRUE Disable Buggy Held Item Caching TRUE Disable Buggy Held Item Caching
TRUE Add Info Button To Options

View File

@ -32,12 +32,18 @@ set(SRC
# options # options
src/options/options.cpp src/options/options.cpp
src/options/ui.cpp src/options/ui.cpp
src/options/info.cpp
# bucket # bucket
src/bucket/bucket.cpp src/bucket/bucket.cpp
# cake # cake
src/cake/cake.cpp src/cake/cake.cpp
# home # home
src/home/home.cpp src/home/home.cpp
# touch
src/touch/touch.cpp
# text-input-box
src/text-input-box/TextInputBox.cpp
src/text-input-box/TextInputScreen.cpp
# test # test
src/test/test.cpp src/test/test.cpp
# init # init
@ -83,8 +89,6 @@ else()
src/input/crafting.cpp src/input/crafting.cpp
# sign # sign
src/sign/sign.cpp src/sign/sign.cpp
# touch
src/touch/touch.cpp
# atlas # atlas
src/atlas/atlas.cpp src/atlas/atlas.cpp
# title-screen # title-screen
@ -98,9 +102,6 @@ else()
# textures # textures
src/textures/textures.cpp src/textures/textures.cpp
src/textures/lava.cpp src/textures/lava.cpp
# text-input-box
src/text-input-box/TextInputBox.cpp
src/text-input-box/TextInputScreen.cpp
# fps # fps
src/fps/fps.cpp src/fps/fps.cpp
) )

View File

@ -17,12 +17,12 @@ void init_sound();
void init_input(); void init_input();
void init_sign(); void init_sign();
void init_camera(); void init_camera();
void init_touch();
void init_atlas(); void init_atlas();
void init_title_screen(); void init_title_screen();
void init_skin(); void init_skin();
void init_fps(); void init_fps();
#endif #endif
void init_touch();
void init_textures(); void init_textures();
void init_creative(); void init_creative();
void init_game_mode(); void init_game_mode();

View File

@ -2,6 +2,8 @@
#include <string> #include <string>
#include <symbols/minecraft.h>
// Message Limitations // Message Limitations
#define MAX_CHAT_MESSAGE_LENGTH 256 #define MAX_CHAT_MESSAGE_LENGTH 256
@ -9,11 +11,7 @@
__attribute__((visibility("internal"))) std::string _chat_get_prefix(char *username); __attribute__((visibility("internal"))) std::string _chat_get_prefix(char *username);
// Queue Message For Sending // Queue Message For Sending
#ifndef MCPI_SERVER_MODE __attribute__((visibility("internal"))) void _chat_send_message(Minecraft *minecraft, const char *message);
__attribute__((visibility("internal"))) void _chat_queue_message(const char *message);
#endif
// Init Chat UI // Init Chat UI
#ifndef MCPI_HEADLESS_MODE
__attribute__((visibility("internal"))) void _init_chat_ui(); __attribute__((visibility("internal"))) void _init_chat_ui();
#endif

View File

@ -1,21 +1,13 @@
// Config Needs To Load First
#include <libreborn/libreborn.h>
#include <string> #include <string>
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <vector> #include <vector>
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
#ifndef MCPI_HEADLESS_MODE
#include <media-layer/core.h>
#endif
#include <mods/init/init.h> #include <mods/init/init.h>
#include <mods/feature/feature.h> #include <mods/feature/feature.h>
#ifndef MCPI_HEADLESS_MODE
#include <mods/input/input.h>
#endif
#include "chat-internal.h" #include "chat-internal.h"
#include <mods/chat/chat.h> #include <mods/chat/chat.h>
@ -33,7 +25,6 @@ std::string chat_send_api_command(Minecraft *minecraft, std::string str) {
} }
} }
#ifndef MCPI_HEADLESS_MODE
// Send API Chat Command // Send API Chat Command
static void send_api_chat_command(Minecraft *minecraft, char *str) { static void send_api_chat_command(Minecraft *minecraft, char *str) {
char *command = nullptr; char *command = nullptr;
@ -41,7 +32,6 @@ static void send_api_chat_command(Minecraft *minecraft, char *str) {
chat_send_api_command(minecraft, command); chat_send_api_command(minecraft, command);
free(command); free(command);
} }
#endif
// Send Message To Players // Send Message To Players
std::string _chat_get_prefix(char *username) { std::string _chat_get_prefix(char *username) {
@ -87,25 +77,10 @@ static void ServerSideNetworkHandler_handle_ChatPacket_injection(ServerSideNetwo
} }
} }
#ifndef MCPI_HEADLESS_MODE // Send Message
// Message Queue void _chat_send_message(Minecraft *minecraft, const char *message) {
static std::vector<std::string> queue; send_api_chat_command(minecraft, (char *) message);
// Add To Queue
void _chat_queue_message(const char *message) {
// Add
std::string str = message;
queue.push_back(str);
} }
// Empty Queue
unsigned int old_chat_counter = 0;
static void send_queued_messages(Minecraft *minecraft) {
// Loop
for (unsigned int i = 0; i < queue.size(); i++) {
send_api_chat_command(minecraft, (char *) queue[i].c_str());
}
queue.clear();
}
#endif
// Init // Init
void init_chat() { void init_chat() {
@ -117,12 +92,8 @@ void init_chat() {
overwrite_call((void *) 0x6b518, (void *) CommandServer_parse_CommandServer_dispatchPacket_injection); overwrite_call((void *) 0x6b518, (void *) CommandServer_parse_CommandServer_dispatchPacket_injection);
// Re-Broadcast ChatPacket // Re-Broadcast ChatPacket
patch_vtable(ServerSideNetworkHandler_handle_ChatPacket, ServerSideNetworkHandler_handle_ChatPacket_injection); patch_vtable(ServerSideNetworkHandler_handle_ChatPacket, ServerSideNetworkHandler_handle_ChatPacket_injection);
#ifndef MCPI_HEADLESS_MODE
// Send Messages On Input Tick
input_run_on_tick(send_queued_messages);
// Init UI // Init UI
_init_chat_ui(); _init_chat_ui();
#endif
// Disable Built-In Chat Message Limiting // Disable Built-In Chat Message Limiting
unsigned char message_limit_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r4, r4" unsigned char message_limit_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r4, r4"
patch((void *) 0x6b4c0, message_limit_patch); patch((void *) 0x6b4c0, message_limit_patch);

View File

@ -1,10 +1,5 @@
// Config Needs To Load First
#include <libreborn/libreborn.h>
// Chat UI Code Is Useless In Headless Mode
#ifndef MCPI_HEADLESS_MODE
#include "chat-internal.h" #include "chat-internal.h"
#include <libreborn/libreborn.h>
#include <mods/chat/chat.h> #include <mods/chat/chat.h>
#include <mods/text-input-box/TextInputScreen.h> #include <mods/text-input-box/TextInputScreen.h>
#include <mods/misc/misc.h> #include <mods/misc/misc.h>
@ -94,7 +89,7 @@ CUSTOM_VTABLE(chat_screen, Screen) {
if (get_history().size() == 0 || text != get_history().back()) { if (get_history().size() == 0 || text != get_history().back()) {
get_history().push_back(text); get_history().push_back(text);
} }
_chat_queue_message(text.c_str()); _chat_send_message(super->minecraft, text.c_str());
} }
Minecraft_setScreen(super->minecraft, nullptr); Minecraft_setScreen(super->minecraft, nullptr);
} else if (key == 0x26) { } else if (key == 0x26) {
@ -158,5 +153,3 @@ void _init_chat_ui() {
} }
}); });
} }
#endif

View File

@ -20,12 +20,12 @@ __attribute__((constructor)) static void init(int argc, char *argv[]) {
init_input(); init_input();
init_sign(); init_sign();
init_camera(); init_camera();
init_touch();
init_atlas(); init_atlas();
init_title_screen(); init_title_screen();
init_skin(); init_skin();
init_fps(); init_fps();
#endif #endif
init_touch();
init_textures(); init_textures();
init_creative(); init_creative();
init_game_mode(); init_game_mode();

278
mods/src/options/info.cpp Normal file
View File

@ -0,0 +1,278 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <GLES/gl.h>
#include <mods/touch/touch.h>
#include "options-internal.h"
// Button IDs
#define DISCORD_ID 0
#define BACK_ID 1
#define INFO_ID_START 2
// Constants
static int line_button_width = 80;
static int line_button_height = 24;
static int padding = 4;
static int line_height = 8;
static int bottom_padding = padding;
static int inner_padding = padding;
static int title_padding = 8;
static int info_text_y_offset = (line_button_height - line_height) / 2;
static int content_y_offset_top = (title_padding * 2) + line_height;
static int content_y_offset_bottom = (bottom_padding * 2) + line_button_height;
// Extra Version Info
static std::string extra_version_info =
#ifdef MCPI_IS_APPIMAGE_BUILD
"AppImage"
#elif defined(MCPI_IS_FLATPAK_BUILD)
"Flatpak"
#else
""
#endif
;
static std::string extra_version_info_full = !extra_version_info.empty() ? (" (" + extra_version_info + ")") : "";
// Profile Directory
static std::string profile_directory_suffix =
#ifdef MCPI_IS_FLATPAK_BUILD
"/.var/app/" MCPI_APP_ID "/.minecraft-pi"
#else
"/.minecraft-pi"
#endif
;
static std::string get_profile_directory_url() {
const char *home = getenv("HOME");
if (home == nullptr) {
IMPOSSIBLE();
}
return std::string("file://") + home + profile_directory_suffix;
}
// Info Data
#define MORE_INFO_TEXT "More Info"
struct info_line {
std::string (*get_text)();
std::string button_url;
std::string button_text;
};
std::string info_sound_data_state = "N/A";
static info_line info[] = {
{
.get_text = []() {
return std::string("Version: v") + reborn_get_version() + extra_version_info_full;
},
.button_url = "https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/CHANGELOG.md",
.button_text = MORE_INFO_TEXT
},
{
.get_text = []() {
return std::string("Profile Directory");
},
.button_url = get_profile_directory_url(),
.button_text = "Open"
},
{
.get_text = []() {
return std::string("Sound Data: ") + info_sound_data_state;
},
.button_url = "https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/SOUND.md",
.button_text = MORE_INFO_TEXT
},
};
#define info_size int(sizeof(info) / sizeof(info_line))
// Positioned Info
struct info_pos {
int x;
int y;
};
struct info_line_position {
info_pos text;
info_pos button;
};
static info_line_position positioned_info[info_size];
static int content_height = 0;
static void position_info(Font *font, int width, int height) {
// First Stage (Find Max Text Width)
int info_text_width = 0;
for (int i = 0; i < info_size; i++) {
std::string text = info[i].get_text();
int text_width = font->width(&text);
if (text_width > info_text_width) {
info_text_width = text_width;
}
}
// Second Stage (Initial Positioning)
int y = 0;
for (int i = 0; i < info_size; i++) {
// Padding
if (i != 0) {
y += padding;
}
// Y
positioned_info[i].button.y = y;
positioned_info[i].text.y = y + info_text_y_offset;
// X
positioned_info[i].button.x = info_text_width + padding;
positioned_info[i].text.x = 0;
// Advance
y += line_button_height;
}
// Third Stage (Centering)
int info_height = y;
int info_width = info_text_width + padding + line_button_width;
content_height = height - content_y_offset_top - content_y_offset_bottom;
int info_y_offset = ((content_height - info_height) / 2) + content_y_offset_top;
int info_x_offset = (width - info_width) / 2;
for (int i = 0; i < info_size; i++) {
positioned_info[i].button.x += info_x_offset;
positioned_info[i].button.y += info_y_offset;
positioned_info[i].text.x += info_x_offset;
positioned_info[i].text.y += info_y_offset;
}
}
// Open URL
static void open_url(const std::string &url) {
int return_code;
const char *command[] = {"xdg-open", url.c_str(), nullptr};
char *output = run_command(command, &return_code, nullptr);
if (output != nullptr) {
free(output);
}
if (!is_exit_status_success(return_code)) {
WARN("Unable To Open URL: %s", url.c_str());
}
}
// Render Fancy Background
static void render_background(Minecraft *minecraft, int x, int y, int width, int height) {
// https://github.com/ReMinecraftPE/mcpe/blob/f0d65eaecec1b3fe9c2f2b251e114a890c54ab77/source/client/gui/components/RolledSelectionList.cpp#L169-L179
std::string texture = "gui/background.png";
minecraft->textures->loadAndBindTexture(&texture);
Tesselator *t = &Tesselator_instance;
t->begin(7);
t->color(32, 32, 32, 255);
float x1 = x;
float x2 = x + width;
float y1 = y;
float y2 = y + height;
t->vertexUV(x1, y2, 0.0f, x1 / 32.0f, y2 / 32.0f);
t->vertexUV(x2, y2, 0.0f, x2 / 32.0f, y2 / 32.0f);
t->vertexUV(x2, y1, 0.0f, x2 / 32.0f, y1 / 32.0f);
t->vertexUV(x1, y1, 0.0f, x1 / 32.0f, y1 / 32.0f);
t->draw();
}
// Create VTable
CUSTOM_VTABLE(info_screen, Screen) {
// Buttons
static Button *discord;
static Button *back;
static Button *info_buttons[info_size];
// Init
vtable->init = [](Screen *self) {
// Info
for (int i = 0; i < info_size; i++) {
Button *button = touch_create_button(INFO_ID_START + i, info[i].button_text);
self->rendered_buttons.push_back(button);
self->selectable_buttons.push_back(button);
info_buttons[i] = button;
}
// Discord Button
discord = touch_create_button(DISCORD_ID, "Discord");
self->rendered_buttons.push_back(discord);
self->selectable_buttons.push_back(discord);
// Back Button
back = touch_create_button(BACK_ID, "Back");
self->rendered_buttons.push_back(back);
self->selectable_buttons.push_back(back);
};
// Handle Back
vtable->handleBackEvent = [](Screen *self, bool do_nothing) {
if (!do_nothing) {
OptionsScreen *screen = alloc_OptionsScreen();
ALLOC_CHECK(screen);
screen->constructor();
self->minecraft->setScreen((Screen *) screen);
}
return true;
};
// Rendering
static Screen_render_t original_render = vtable->render;
vtable->render = [](Screen *self, int x, int y, float param_1) {
// Background
self->vtable->renderBackground(self);
// Gradient
render_background(self->minecraft, 0, content_y_offset_top, self->width, content_height);
// Call Original Method
original_render(self, x, y, param_1);
// Title
std::string title = "Reborn Information";
self->drawCenteredString(self->font, &title, self->width / 2, title_padding, 0xffffffff);
// Info Text
for (int i = 0; i < info_size; i++) {
std::string text = info[i].get_text();
self->drawString(self->font, &text, positioned_info[i].text.x, positioned_info[i].text.y, 0xffffffff);
}
};
// Positioning
vtable->setupPositions = [](Screen *self) {
// Height/Width
int width = 120;
discord->width = back->width = width;
discord->height = back->height = line_button_height;
// X/Y
discord->y = back->y = self->height - bottom_padding - line_button_height;
discord->x = (self->width / 2) - inner_padding - width;
back->x = (self->width / 2) + inner_padding;
// Info
position_info(self->font, self->width, self->height);
for (int i = 0; i < info_size; i++) {
Button *button = info_buttons[i];
button->width = line_button_width;
button->height = line_button_height;
button->x = positioned_info[i].button.x;
button->y = positioned_info[i].button.y;
}
};
// Cleanup
vtable->removed = [](Screen *self) {
for (Button *button : self->rendered_buttons) {
button->destructor_deleting();
}
};
// Handle Button Click
vtable->buttonClicked = [](Screen *self, Button *button) {
if (button->id == BACK_ID) {
// Back
self->handleBackEvent(false);
} else if (button->id == DISCORD_ID) {
// Open Discord Invite
open_url("https://discord.gg/mcpi-revival-740287937727561779");
} else if (button->id >= INFO_ID_START) {
// Open Info URL
int i = button->id - INFO_ID_START;
open_url(info[i].button_url);
}
};
}
// Create Screen
Screen *_create_options_info_screen() {
// Allocate
Screen *screen = alloc_Screen();
ALLOC_CHECK(screen);
screen->constructor();
// Set VTable
screen->vtable = get_info_screen_vtable();
// Return
return screen;
}

View File

@ -1,4 +1,7 @@
#pragma once #pragma once
#include <symbols/minecraft.h>
__attribute__((visibility("internal"))) void _init_options_ui(); __attribute__((visibility("internal"))) void _init_options_ui();
__attribute__((visibility("internal"))) extern Options *stored_options; __attribute__((visibility("internal"))) extern Options *stored_options;
__attribute__((visibility("internal"))) Screen *_create_options_info_screen();

View File

@ -1,4 +1,5 @@
#include <string> #include <string>
#include <algorithm>
#include <libreborn/libreborn.h> #include <libreborn/libreborn.h>
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
@ -89,6 +90,72 @@ static void OptionButton_toggle_Options_save_injection(Options *self) {
Options_save(self); Options_save(self);
} }
// Add "Reborn" Info Button
#define INFO_BUTTON_ID 99
static void OptionsScreen_init_injection(OptionsScreen_init_t original, OptionsScreen *self) {
// Call Original Method
original(self);
// Add Button
Touch_TButton *button = alloc_Touch_TButton();
ALLOC_CHECK(button);
std::string name = "Reborn";
button->constructor(INFO_BUTTON_ID, &name);
self->rendered_buttons.push_back((Button *) button);
self->selectable_buttons.push_back((Button *) button);
}
static void OptionsScreen_setupPositions_injection(OptionsScreen_setupPositions_t original, OptionsScreen *self) {
// Call Original Method
original(self);
// Find Button
Button *prevButton = nullptr;
Button *button = nullptr;
for (Button *x : self->selectable_buttons) {
if (x->id == INFO_BUTTON_ID) {
button = x;
break;
}
prevButton = x;
}
if (button == nullptr || prevButton == nullptr) {
IMPOSSIBLE();
}
// Setup Button
button->width = prevButton->width;
button->height = prevButton->height;
button->x = prevButton->x;
button->y = prevButton->y + prevButton->height;
}
static void OptionsScreen_buttonClicked_injection(OptionsScreen_buttonClicked_t original, OptionsScreen *self, Button *button) {
// Check ID
if (button->id == INFO_BUTTON_ID) {
// Show Screen
self->minecraft->setScreen(_create_options_info_screen());
} else {
// Call Original Method
original(self, button);
}
}
static void OptionsScreen_removed_injection(OptionsScreen_removed_t original, OptionsScreen *self) {
// Delete Button
Button *button = nullptr;
for (Button *x : self->selectable_buttons) {
if (x->id == INFO_BUTTON_ID) {
button = x;
break;
}
}
if (button == nullptr) {
IMPOSSIBLE();
}
button->destructor_deleting();
// Call Original Method
original(self);
}
// Init // Init
void _init_options_ui() { void _init_options_ui() {
// Fix Options Screen // Fix Options Screen
@ -115,4 +182,16 @@ void _init_options_ui() {
// Fix Difficulty When Toggling // Fix Difficulty When Toggling
overwrite_call((void *) 0x1cd00, (void *) OptionButton_toggle_Options_save_injection); overwrite_call((void *) 0x1cd00, (void *) OptionButton_toggle_Options_save_injection);
} }
// Info Button
if (feature_has("Add Info Button To Options", server_disabled)) {
// Add Button
overwrite_virtual_calls(OptionsScreen_init, OptionsScreen_init_injection);
// Position Button
overwrite_virtual_calls(OptionsScreen_setupPositions, OptionsScreen_setupPositions_injection);
// Handle Click
overwrite_virtual_calls(OptionsScreen_buttonClicked, OptionsScreen_buttonClicked_injection);
// Cleanup
overwrite_virtual_calls(OptionsScreen_removed, OptionsScreen_removed_injection);
}
} }

View File

@ -11,6 +11,7 @@
// Resolve Source File Path // Resolve Source File Path
#define SOURCE_FILE_BASE "data/libminecraftpe.so" #define SOURCE_FILE_BASE "data/libminecraftpe.so"
extern std::string info_sound_data_state;
std::string _sound_get_source_file() { std::string _sound_get_source_file() {
static bool source_loaded = false; static bool source_loaded = false;
static std::string source; static std::string source;
@ -38,9 +39,11 @@ std::string _sound_get_source_file() {
// Fail // Fail
WARN("Audio Source File Doesn't Exist: " SOURCE_FILE_BASE " (See: https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/SOUND.md)"); WARN("Audio Source File Doesn't Exist: " SOURCE_FILE_BASE " (See: https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/SOUND.md)");
source.assign(""); source.assign("");
info_sound_data_state = "Missing";
} else { } else {
// Set // Set
source.assign(path); source.assign(path);
info_sound_data_state = "Loaded";
} }
// Free // Free

View File

@ -1,7 +1,7 @@
extends GuiComponent; extends GuiComponent;
size 0x28; size 0x28;
constructor (int param_1, std::string *text) = 0x1bc54; constructor (int id, std::string *text) = 0x1bc54;
method int hovered(Minecraft *minecraft, int click_x, int click_y) = 0x1be2c; method int hovered(Minecraft *minecraft, int click_x, int click_y) = 0x1be2c;
@ -10,3 +10,4 @@ property int height = 0x18;
property int x = 0xc; property int x = 0xc;
property int y = 0x10; property int y = 0x10;
property std::string text = 0x1c; property std::string text = 0x1c;
property int id = 0x20;

View File

@ -4,7 +4,7 @@ constructor () = 0x28204;
vtable 0x1039b0; vtable 0x1039b0;
method void blit(int x_dest, int y_dest, int x_src, int y_src, int width_dest, int height_dest, int width_src, int height_src) = 0x282a4; method void blit(int x_dest, int y_dest, int x_src, int y_src, int width_dest, int height_dest, int width_src, int height_src) = 0x282a4;
method void drawCenteredString(Font *font, std::string *text, int x, int y, int color) = 0x2821c; method void drawCenteredString(Font *font, std::string *text, int x, int y, uint color) = 0x2821c;
method void drawString(Font *font, std::string *text, int x, int y, int color) = 0x28284; method void drawString(Font *font, std::string *text, int x, int y, uint color) = 0x28284;
method void fill(int x1, int y1, int x2, int y2, uint color) = 0x285f0; method void fill(int x1, int y1, int x2, int y2, uint color) = 0x285f0;
method void fillGradient(int x1, int y1, int x2, int y2, int color1, int color2) = 0x287c0; method void fillGradient(int x1, int y1, int x2, int y2, int color1, int color2) = 0x287c0;

View File

@ -1,4 +1,4 @@
extends Button; extends Button;
size 0x28; size 0x28;
constructor (int param_1, std::string *text) = 0x1c168; constructor (int id, std::string *text) = 0x1c168;

View File

@ -1,3 +1,6 @@
extends Screen; extends Screen;
vtable 0x104978; vtable 0x104978;
size 0x70;
constructor () = 0x35488;