diff --git a/cmake/options/extra-options.cmake b/cmake/options/extra-options.cmake index 08e9015d81..18c237e176 100644 --- a/cmake/options/extra-options.cmake +++ b/cmake/options/extra-options.cmake @@ -78,5 +78,8 @@ 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") -# Discord Invite -mcpi_option(DISCORD_INVITE "Discord Invite URL" STRING "https://discord.gg/mcpi-revival-740287937727561779") \ No newline at end of file +# Discord Invite URL +mcpi_option(DISCORD_INVITE "Discord Invite URL" STRING "https://discord.gg/mcpi-revival-740287937727561779") + +# Documentation URL +mcpi_option(DOCUMENTATION "Documentation URL" STRING "https://gitea.thebrokenrail.com/minecraft-pi-reborn/minecraft-pi-reborn/src/branch/master/docs/") \ No newline at end of file diff --git a/launcher/src/client/available-feature-flags b/launcher/src/client/available-feature-flags index f2ec82a2aa..381d58692a 100644 --- a/launcher/src/client/available-feature-flags +++ b/launcher/src/client/available-feature-flags @@ -66,4 +66,5 @@ TRUE Display Date In Select World Screen TRUE Optimized Chunk Sorting TRUE Fix Held Item Caching TRUE Add Reborn Info To Options -FALSE Track FPS \ No newline at end of file +FALSE Track FPS +TRUE Add Welcome Screen \ No newline at end of file diff --git a/libreborn/include/libreborn/config.h.in b/libreborn/include/libreborn/config.h.in index bd83fc44dc..c1c58c250f 100644 --- a/libreborn/include/libreborn/config.h.in +++ b/libreborn/include/libreborn/config.h.in @@ -15,3 +15,4 @@ #cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@" #cmakedefine MCPI_USE_QEMU #cmakedefine MCPI_DISCORD_INVITE "@MCPI_DISCORD_INVITE@" +#cmakedefine MCPI_DOCUMENTATION "@MCPI_DOCUMENTATION@" diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index a9aa3faee8..e252e0bf32 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -94,6 +94,7 @@ else() # title-screen src/title-screen/title-screen.cpp src/title-screen/splashes.txt # Show In IDE + src/title-screen/welcome.cpp # skin src/skin/skin.cpp src/skin/loader.cpp diff --git a/mods/include/mods/options/info.h b/mods/include/mods/options/info.h new file mode 100644 index 0000000000..d92a6d8c90 --- /dev/null +++ b/mods/include/mods/options/info.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#define CHANGELOG_FILE "CHANGELOG.md" + +extern "C" { +void open_url(const std::string &url); +} \ No newline at end of file diff --git a/mods/src/options/info.cpp b/mods/src/options/info.cpp index ef801ed064..53209ca061 100644 --- a/mods/src/options/info.cpp +++ b/mods/src/options/info.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "options-internal.h" @@ -63,7 +64,7 @@ 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_url = MCPI_DOCUMENTATION CHANGELOG_FILE, .button_text = "Changelog" }, { @@ -77,7 +78,7 @@ static info_line info[] = { .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_url = MCPI_DOCUMENTATION "SOUND.md", .button_text = "More Info" }, }; @@ -148,7 +149,7 @@ static void position_info(Font *font, int width, int height) { } // Open URL -static void open_url(const std::string &url) { +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); diff --git a/mods/src/title-screen/title-screen-internal.h b/mods/src/title-screen/title-screen-internal.h new file mode 100644 index 0000000000..ab3477d3cb --- /dev/null +++ b/mods/src/title-screen/title-screen-internal.h @@ -0,0 +1,3 @@ +#pragma once + +__attribute__((visibility("internal"))) void _init_welcome(); diff --git a/mods/src/title-screen/title-screen.cpp b/mods/src/title-screen/title-screen.cpp index 18cb76b0e9..b87ef57f21 100644 --- a/mods/src/title-screen/title-screen.cpp +++ b/mods/src/title-screen/title-screen.cpp @@ -11,6 +11,8 @@ #include #include +#include "title-screen-internal.h" + // Improved Title Screen Background static void StartMenuScreen_render_Screen_renderBackground_injection(Screen *screen) { // Draw @@ -165,4 +167,9 @@ void init_title_screen() { // Init Random srand(time(nullptr)); } + + // Init Welcome Screen + if (feature_has("Add Welcome Screen", server_disabled)) { + _init_welcome(); + } } diff --git a/mods/src/title-screen/welcome.cpp b/mods/src/title-screen/welcome.cpp new file mode 100644 index 0000000000..3e72a73d84 --- /dev/null +++ b/mods/src/title-screen/welcome.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include + +#include +#include +#include + +#include "title-screen-internal.h" + +// Constants +static std::string line1 = "Welcome to " MCPI_APP_BASE_TITLE " v" MCPI_VERSION "!"; +static int line_height = 8; +static int button_width = 120; +static int button_height = 24; +static int line_padding = 28; +static int button_padding = 4; + +// Track Whether To Show Screen +static std::string get_tracker_file() { + return std::string(home_get()) + "/.welcome-tracker"; +} +static bool should_show_welcome() { + // Open File + std::ifstream stream(get_tracker_file()); + if (!stream) { + return true; + } + // Read Line + std::string line; + std::getline(stream, line); + bool invalid = line != MCPI_VERSION; + // Close File + stream.close(); + // Return + return invalid; +} +static void mark_welcome_as_shown() { + // Open File + std::ofstream stream(get_tracker_file()); + if (!stream) { + return; + } + // Write + stream << MCPI_VERSION << std::endl; + // Close File + stream.close(); +} + +// Position GUI +static Button *getting_started; +static Button *changelog; +static Button *proceed; +static int text_y; +static void position_screen(int width, int height) { + // Width/Height + getting_started->width = changelog->width = proceed->width = button_width; + getting_started->height = changelog->height = proceed->height = button_height; + // X + proceed->x = (width / 2) - (button_width / 2); + getting_started->x = (width / 2) - button_padding - button_width; + changelog->x = (width / 2) + button_padding; + // Y + text_y = 0; + getting_started->y = changelog->y = line_height + line_padding; + proceed->y = getting_started->y + button_height + (button_padding * 2); + // Center + int content_height = proceed->y + proceed->height; + int y_offset = (height - content_height) / 2; + text_y += y_offset; + getting_started->y += y_offset; + changelog->y += y_offset; + proceed->y += y_offset; +} + +// Welcome Screen +CUSTOM_VTABLE(welcome_screen, Screen) { + // Init + vtable->init = [](__attribute__((unused)) Screen *self) { + // Buttons + getting_started = touch_create_button(0, "Getting Started"); + changelog = touch_create_button(1, "Changelog"); + proceed = touch_create_button(2, "Proceed"); + for (Button *button : {getting_started, changelog, proceed}) { + self->rendered_buttons.push_back(button); + self->selectable_buttons.push_back(button); + } + }; + // Rendering + static Screen_render_t original_render = vtable->render; + vtable->render = [](Screen *self, int x, int y, float param_1) { + // Background + self->renderBackground(); + // Call Original Method + original_render(self, x, y, param_1); + // Text + self->drawCenteredString(self->font, &line1, self->width / 2, text_y, 0xFFFFFFFF); + }; + // Positioning + vtable->setupPositions = [](Screen *self) { + position_screen(self->width, self->height); + }; + // 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 == getting_started) { + open_url(MCPI_DOCUMENTATION "GETTING_STARTED.md"); + } else if (button == changelog) { + open_url(MCPI_DOCUMENTATION CHANGELOG_FILE); + } else if (button == proceed) { + mark_welcome_as_shown(); + self->minecraft->screen_chooser.setScreen(1); + } + }; +} +static Screen *create_welcome_screen() { + // Allocate + Screen *screen = new Screen; + ALLOC_CHECK(screen); + screen->constructor(); + + // Set VTable + screen->vtable = get_welcome_screen_vtable(); + + // Return + return screen; +} + +// Show Welcome Screen +static void NinecraftApp_init_ScreenChooser_setScreen_injection(ScreenChooser *self, int id) { + if (should_show_welcome()) { + // Show Welcome Screen + self->minecraft->setScreen(create_welcome_screen()); + } else { + // Show Start Screen + self->setScreen(id); + } +} + +// Init +void _init_welcome() { + // Hijack Start Screen + overwrite_call((void *) 0x14a34, (void *) NinecraftApp_init_ScreenChooser_setScreen_injection); +} \ No newline at end of file diff --git a/symbols/src/gui/screens/ScreenChooser.def b/symbols/src/gui/screens/ScreenChooser.def index bb2984b214..ae58d82528 100644 --- a/symbols/src/gui/screens/ScreenChooser.def +++ b/symbols/src/gui/screens/ScreenChooser.def @@ -1 +1,3 @@ method void setScreen(uint id) = 0x29490; + +property Minecraft *minecraft = 0x0; \ No newline at end of file