From b12b479d704063d85c2e21dc1fd4d3d920f1e5a6 Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Sun, 5 Nov 2023 09:55:34 +1000 Subject: [PATCH] loading screen (#495) * loading screen * fix compile error * Fix animation comparisons after character anim commit * Cleaned up character sound/anim lookup code * hopefully fix problems with queued mods * use dj's changes * fix compile errors due to upstream merge --- data/dynos.c.h | 1 + data/dynos_c.cpp | 5 +- data/dynos_gfx_init.cpp | 15 ++- lang/Czech.ini | 3 + lang/Dutch.ini | 3 + lang/English.ini | 3 + lang/French.ini | 3 + lang/German.ini | 3 + lang/Italian.ini | 3 + lang/Portuguese.ini | 5 +- lang/Russian.ini | 5 +- src/game/characters.c | 12 +-- src/pc/cliopts.c | 7 +- src/pc/cliopts.h | 2 +- src/pc/configfile.c | 28 +++++- src/pc/configfile.h | 1 + src/pc/crash_handler.c | 28 +++--- src/pc/debug_context.h | 2 +- src/pc/djui/djui.c | 21 ++++- src/pc/djui/djui.h | 2 + src/pc/gfx/gfx_pc.c | 3 + src/pc/loading.c | 193 +++++++++++++++++++++++++++++++++++++++ src/pc/loading.h | 25 +++++ src/pc/lua/smlua_hooks.c | 7 +- src/pc/mods/mod.c | 6 +- src/pc/mods/mod_import.c | 1 + src/pc/mods/mods.c | 29 ++++-- src/pc/pc_main.c | 75 ++++++++++----- src/pc/pc_main.h | 2 - 29 files changed, 427 insertions(+), 66 deletions(-) create mode 100644 src/pc/loading.c create mode 100644 src/pc/loading.h diff --git a/data/dynos.c.h b/data/dynos.c.h index ee4ec13a..c56c12a2 100644 --- a/data/dynos.c.h +++ b/data/dynos.c.h @@ -25,6 +25,7 @@ bool dynos_warp_exit_level(s32 aDelay); bool dynos_warp_to_castle(s32 aLevel); // -- dynos packs -- // +void dynos_gfx_init(void); void dynos_packs_init(void); int dynos_pack_get_count(void); const char* dynos_pack_get_name(s32 index); diff --git a/data/dynos_c.cpp b/data/dynos_c.cpp index be6dffc0..12261ff3 100644 --- a/data/dynos_c.cpp +++ b/data/dynos_c.cpp @@ -62,8 +62,11 @@ bool dynos_warp_to_castle(s32 aLevel) { // -- dynos packs -- // -void dynos_packs_init(void) { +void dynos_gfx_init(void) { DynOS_Gfx_Init(); +} + +void dynos_packs_init(void) { DynOS_Pack_Init(); } diff --git a/data/dynos_gfx_init.cpp b/data/dynos_gfx_init.cpp index 197db2a3..327d14c4 100644 --- a/data/dynos_gfx_init.cpp +++ b/data/dynos_gfx_init.cpp @@ -1,11 +1,17 @@ #include "dynos.cpp.h" +#include "src/pc/loading.h" void DynOS_Gfx_GeneratePacks(const char* directory) { DIR *modsDir = opendir(directory); if (!modsDir) { return; } struct dirent *dir = NULL; - while ((dir = readdir(modsDir)) != NULL) { + DIR* d = opendir(directory); + u32 pathCount = 0; + while ((dir = readdir(d)) != NULL) pathCount++; + closedir(d); + + for (u32 i = 0; (dir = readdir(modsDir)) != NULL; ++i) { // Skip . and .. if (SysPath(dir->d_name) == ".") continue; if (SysPath(dir->d_name) == "..") continue; @@ -15,21 +21,24 @@ void DynOS_Gfx_GeneratePacks(const char* directory) { if (fs_sys_dir_exists(_LevelPackFolder.c_str())) { DynOS_Lvl_GeneratePack(_LevelPackFolder); } + SysPath _ActorPackFolder = fstring("%s/%s/actors", directory, dir->d_name); if (fs_sys_dir_exists(_ActorPackFolder.c_str())) { DynOS_Actor_GeneratePack(_ActorPackFolder); } - + SysPath _BehaviorPackFolder = fstring("%s/%s/data", directory, dir->d_name); if (fs_sys_dir_exists(_BehaviorPackFolder.c_str())) { DynOS_Bhv_GeneratePack(_BehaviorPackFolder); } - + SysPath _TexturePackFolder = fstring("%s/%s", directory, dir->d_name); SysPath _TexturePackOutputFolder = fstring("%s/%s/textures", directory, dir->d_name); if (fs_sys_dir_exists(_TexturePackFolder.c_str())) { DynOS_Tex_GeneratePack(_TexturePackFolder, _TexturePackOutputFolder, true); } + + if (gIsThreaded) REFRESH_MUTEX(gCurrLoadingSegment.percentage = (f32) i / (f32) pathCount); } closedir(modsDir); diff --git a/lang/Czech.ini b/lang/Czech.ini index b6e7f067..c0357aa3 100644 --- a/lang/Czech.ini +++ b/lang/Czech.ini @@ -374,3 +374,6 @@ REFRESHING = "Obnovování..." ENTER_PASSWORD = "Zadejte heslo soukromé hry:" SEARCH = "Hledat" NONE_FOUND = "Nebyly nalezeny žádné hry." + +[LOADING_SCREEN] +LOADING = "Načítání" diff --git a/lang/Dutch.ini b/lang/Dutch.ini index db8698c2..09ff2156 100644 --- a/lang/Dutch.ini +++ b/lang/Dutch.ini @@ -374,3 +374,6 @@ REFRESHING = "herladen..." ENTER_PASSWORD = "Typ het wachtwoord van de privé lobby:" SEARCH = "Zoek" NONE_FOUND = "Er zijn geen lobby's gevonden." + +[LOADING_SCREEN] +LOADING = "Bezig met laden" diff --git a/lang/English.ini b/lang/English.ini index d6b77b8f..aa06e7b4 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -374,3 +374,6 @@ REFRESHING = "Refreshing..." ENTER_PASSWORD = "Enter the private lobby's password:" SEARCH = "Search" NONE_FOUND = "No lobbies were found." + +[LOADING_SCREEN] +LOADING = "Loading" diff --git a/lang/French.ini b/lang/French.ini index ced43a85..4f4395df 100644 --- a/lang/French.ini +++ b/lang/French.ini @@ -374,3 +374,6 @@ REFRESHING = "Actualisation..." ENTER_PASSWORD = "Entrez le mot de passe de la partie:" SEARCH = "Rechercher" NONE_FOUND = "Aucune partie n'a été trouvée." + +[LOADING_SCREEN] +LOADING = "Chargement" diff --git a/lang/German.ini b/lang/German.ini index f3072c1c..0b84589e 100644 --- a/lang/German.ini +++ b/lang/German.ini @@ -374,3 +374,6 @@ REFRESHING = "Aktualisiere..." ENTER_PASSWORD = "Gib das Passwort für die Lobby ein:" SEARCH = "Suchen" NONE_FOUND = "Keine Lobbys gefunden." + +[LOADING_SCREEN] +LOADING = "Wird geladen" diff --git a/lang/Italian.ini b/lang/Italian.ini index 1d93cedc..c4d44d1b 100644 --- a/lang/Italian.ini +++ b/lang/Italian.ini @@ -371,3 +371,6 @@ REFRESHING = "Refreshing..." ENTER_PASSWORD = "Enter the private lobby's password:" SEARCH = "Search" NONE_FOUND = "No lobbies were found." + +[LOADING_SCREEN] +LOADING = "Caricamento" diff --git a/lang/Portuguese.ini b/lang/Portuguese.ini index 20960bf9..8cce0e2b 100644 --- a/lang/Portuguese.ini +++ b/lang/Portuguese.ini @@ -31,7 +31,7 @@ UNKNOWN = "desconhecido" LOBBY_HOST = "o host da partida" [CHAT] -KICKING = "Expulso '@'!" +KICKING = "Expulso '@'!" BANNING = "Banindo '@'!" SERVER_ONLY = "Apenas o servidor pode usar este comando." PERM_BANNING = "'@' permanentemente banido!" @@ -374,3 +374,6 @@ REFRESHING = "Recarregando..." ENTER_PASSWORD = "Coloque a senha para a partida privada:" SEARCH = "Pesquisar" NONE_FOUND = "Nenhuma partida foi encontrada." + +[LOADING_SCREEN] +LOADING = "Carregando" diff --git a/lang/Russian.ini b/lang/Russian.ini index 7f9d7596..3919ee34 100644 --- a/lang/Russian.ini +++ b/lang/Russian.ini @@ -372,4 +372,7 @@ REFRESH = "Обновить" REFRESHING = "Обновление..." ENTER_PASSWORD = "Введите пароль закрытой группы:" SEARCH = "Поиск" -NONE_FOUND = "Группы не найдены." \ No newline at end of file +NONE_FOUND = "Группы не найдены." + +[LOADING_SCREEN] +LOADING = "Загрузка" diff --git a/src/game/characters.c b/src/game/characters.c index c7e760dd..75f0762d 100644 --- a/src/game/characters.c +++ b/src/game/characters.c @@ -324,7 +324,7 @@ struct Character gCharacters[CT_MAX] = { .torsoRotMult = 1.0f, // anim .animOffsetEnabled = false, - + // character anims .animSlowLedgeGrab = MARIO_ANIM_SLOW_LEDGE_GRAB, .animFallOverBackwards = MARIO_ANIM_FALL_OVER_BACKWARDS, @@ -601,7 +601,7 @@ struct Character gCharacters[CT_MAX] = { .torsoRotMult = 1.0f, // anim .animOffsetEnabled = false, - + // character anims .animSlowLedgeGrab = MARIO_ANIM_SLOW_LEDGE_GRAB, .animFallOverBackwards = MARIO_ANIM_FALL_OVER_BACKWARDS, @@ -881,7 +881,7 @@ struct Character gCharacters[CT_MAX] = { .animOffsetLowYPoint = 11, .animOffsetFeet = 25, .animOffsetHand = -10, - + // character anims .animSlowLedgeGrab = MARIO_ANIM_SLOW_LEDGE_GRAB, .animFallOverBackwards = MARIO_ANIM_FALL_OVER_BACKWARDS, @@ -1158,7 +1158,7 @@ struct Character gCharacters[CT_MAX] = { .torsoRotMult = 1.0f, // anim .animOffsetEnabled = true, - + // character anims .animSlowLedgeGrab = MARIO_ANIM_SLOW_LEDGE_GRAB, .animFallOverBackwards = MARIO_ANIM_FALL_OVER_BACKWARDS, @@ -1521,7 +1521,7 @@ struct Character* get_character(struct MarioState* m) { static s32 get_character_sound(struct MarioState* m, enum CharacterSound characterSound) { if (m == NULL || m->marioObj == NULL) { return 0; } - + s32 override = 0; if (smlua_call_event_hooks_mario_character_sound_param_ret_int(HOOK_CHARACTER_SOUND, m, characterSound, &override)) { return override; @@ -1612,7 +1612,7 @@ void update_character_anim_offset(struct MarioState* m) { s32 get_character_anim(struct MarioState* m, enum CharacterAnimID characterAnim) { if (m == NULL || m->marioObj == NULL) { return 0; } - + struct Character* character = ((m == NULL || m->character == NULL) ? &gCharacters[CT_MARIO] : m->character); if (!character || characterAnim < 0 || characterAnim >= CHAR_ANIM_MAX) { return 0; } return character->anims[characterAnim]; diff --git a/src/pc/cliopts.c b/src/pc/cliopts.c index 04cf0cd7..14e5cdac 100644 --- a/src/pc/cliopts.c +++ b/src/pc/cliopts.c @@ -43,7 +43,8 @@ static inline int arg_uint(UNUSED const char *name, const char *value, unsigned return 1; } -inline void parse_cli_opts(int argc, char* argv[]) { +bool parse_cli_opts(int argc, char* argv[]) { + // Initialize options with false values. memset(&gCLIOpts, 0, sizeof(gCLIOpts)); @@ -88,7 +89,9 @@ inline void parse_cli_opts(int argc, char* argv[]) { // Print help else if (strcmp(argv[i], "--help") == 0) { print_help(); - game_exit(); + return false; } } + + return true; } diff --git a/src/pc/cliopts.h b/src/pc/cliopts.h index 916e19b1..a034eaa3 100644 --- a/src/pc/cliopts.h +++ b/src/pc/cliopts.h @@ -26,6 +26,6 @@ struct PCCLIOptions { extern struct PCCLIOptions gCLIOpts; -void parse_cli_opts(int argc, char* argv[]); +bool parse_cli_opts(int argc, char* argv[]); #endif // _CLIOPTS_H diff --git a/src/pc/configfile.c b/src/pc/configfile.c index 855ec667..889c6b43 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -279,6 +279,23 @@ static const struct ConfigOption options[] = { // FunctionConfigOption functions +struct QueuedMods { + char* path; + struct QueuedMods *next; +}; + +static struct QueuedMods *sQueuedEnableModsHead = NULL; + +void enable_queued_mods() { + while (sQueuedEnableModsHead) { + struct QueuedMods *next = sQueuedEnableModsHead->next; + mods_enable(sQueuedEnableModsHead->path); + free(sQueuedEnableModsHead->path); + free(sQueuedEnableModsHead); + sQueuedEnableModsHead = next; + } +} + static void enable_mod_read(char** tokens, UNUSED int numTokens) { char combined[256] = { 0 }; for (int i = 1; i < numTokens; i++) { @@ -286,7 +303,16 @@ static void enable_mod_read(char** tokens, UNUSED int numTokens) { strncat(combined, tokens[i], 255); } - mods_enable(combined); + struct QueuedMods* queued = malloc(sizeof(struct QueuedMods)); + queued->path = strdup(combined); + queued->next = NULL; + if (!sQueuedEnableModsHead) { + sQueuedEnableModsHead = queued; + } else { + struct QueuedMods* tail = sQueuedEnableModsHead; + while (tail->next) { tail = tail->next; } + tail->next = queued; + } } static void enable_mod_write(FILE* file) { diff --git a/src/pc/configfile.h b/src/pc/configfile.h index ce808ba4..1b16cbdf 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -120,6 +120,7 @@ extern bool configFadeoutDistantSounds; extern unsigned int configDjuiTheme; extern bool configCoopCompatibility; +void enable_queued_mods(); void configfile_load(void); void configfile_save(const char *filename); const char *configfile_name(void); diff --git a/src/pc/crash_handler.c b/src/pc/crash_handler.c index 010cae19..2c8bc9bf 100644 --- a/src/pc/crash_handler.c +++ b/src/pc/crash_handler.c @@ -275,8 +275,7 @@ static void crash_handler_produce_one_frame(void) { // Render frame end_master_display_list(); alloc_display_list(0); - extern void send_display_list(struct SPTask *spTask); - send_display_list(&gGfxPool->spTask); + gfx_run((Gfx*) gGfxSPTask->task.t.data_ptr); // send_display_list display_and_vsync(); gfx_end_frame(); } @@ -302,7 +301,7 @@ static CRASH_HANDLER_TYPE crash_handler(EXCEPTION_POINTERS *ExceptionInfo) { #elif __linux__ static void crash_handler(const int signalNum, siginfo_t *info, ucontext_t *context) { #endif - LOG_INFO("game crashed! preparing crash screen..."); + printf("game crashed! preparing crash screen...\n"); memset(sCrashHandlerText, 0, sizeof(sCrashHandlerText)); CrashHandlerText *pText = &sCrashHandlerText[0]; gDjuiDisabled = true; @@ -658,6 +657,11 @@ static void crash_handler(const int signalNum, siginfo_t *info, ucontext_t *cont } #endif + // Incase it crashed before the game window opened + if (!gGfxInited) gfx_init(&WAPI, &RAPI, TITLE); + djui_init(); + djui_unicode_init(); + // Main loop while (true) { WAPI.main_loop(crash_handler_produce_one_frame); @@ -670,17 +674,17 @@ AT_STARTUP static void init_crash_handler(void) { // Windows SetUnhandledExceptionFilter(crash_handler); #elif __linux__ + // Linux - struct sigaction linux_crash_handler; + struct sigaction linuxCrashHandler; + linuxCrashHandler.sa_handler = (void*) &crash_handler; + sigemptyset(&linuxCrashHandler.sa_mask); + linuxCrashHandler.sa_flags = SA_SIGINFO; // Get extra info about the crash - linux_crash_handler.sa_handler = (void *)crash_handler; - sigemptyset(&linux_crash_handler.sa_mask); - linux_crash_handler.sa_flags = SA_SIGINFO; // Get extra info about the crash - - sigaction(SIGBUS, &linux_crash_handler, NULL); - sigaction(SIGFPE, &linux_crash_handler, NULL); - sigaction(SIGILL, &linux_crash_handler, NULL); - sigaction(SIGSEGV, &linux_crash_handler, NULL); + sigaction(SIGBUS, &linuxCrashHandler, NULL); + sigaction(SIGFPE, &linuxCrashHandler, NULL); + sigaction(SIGILL, &linuxCrashHandler, NULL); + sigaction(SIGSEGV, &linuxCrashHandler, NULL); #endif } diff --git a/src/pc/debug_context.h b/src/pc/debug_context.h index 18990e6c..e7dbe9c7 100644 --- a/src/pc/debug_context.h +++ b/src/pc/debug_context.h @@ -5,7 +5,7 @@ #define CTX_BEGIN(_ctx) debug_context_begin(_ctx) #define CTX_END(_ctx) debug_context_end(_ctx) #define CTX_WITHIN(_ctx) debug_context_within(_ctx) -#define CTX_EXTENT(__ctx, __func) CTX_BEGIN(__ctx); __func(); CTX_END(__ctx); +#define CTX_EXTENT(_ctx, _f) { CTX_BEGIN(_ctx); _f(); CTX_END(_ctx); } enum DebugContext { CTX_NONE, diff --git a/src/pc/djui/djui.c b/src/pc/djui/djui.c index 359d9350..3206a774 100644 --- a/src/pc/djui/djui.c +++ b/src/pc/djui/djui.c @@ -24,9 +24,26 @@ static u32 sDjuiLuaErrorTimeout = 0; bool gDjuiInMainMenu = true; bool gDjuiDisabled = false; bool gDjuiRenderBehindHud = false; +static bool sDjuiInited = false; bool sDjuiRendered60fps = false; +void reset_djui_text(void); + +void reset_djui(void) { + sSavedDisplayListHead = NULL; + sDjuiPauseOptions = NULL; + sDjuiLuaError = NULL; + sDjuiLuaErrorTimeout = 0; + if (gDjuiRoot) djui_base_destroy(&gDjuiRoot->base); + + if (gDjuiConsole) djui_base_destroy(&gDjuiConsole->panel->base); + extern u32 sDjuiConsoleMessages; + sDjuiConsoleMessages = 0; + + sDjuiInited = false; +} + void patch_djui_before(void) { sDjuiRendered60fps = false; } @@ -68,6 +85,7 @@ void djui_init(void) { djui_panel_playerlist_create(NULL); djui_console_create(); + sDjuiInited = true; } void djui_init_late(void) { @@ -91,6 +109,7 @@ void djui_connect_menu_open(void) { } void djui_lua_error(char* text) { + if (!sDjuiLuaError) { return; } djui_text_set_text(sDjuiLuaError, text); djui_base_set_visible(&sDjuiLuaError->base, true); sDjuiLuaErrorTimeout = 30 * 5; @@ -105,7 +124,7 @@ void djui_reset_hud_params(void) { } void djui_render(void) { - if (gDjuiDisabled) { return; } + if (!sDjuiInited || gDjuiDisabled) { return; } djui_reset_hud_params(); sSavedDisplayListHead = gDisplayListHead; diff --git a/src/pc/djui/djui.h b/src/pc/djui/djui.h index dd0b49a8..9654f3aa 100644 --- a/src/pc/djui/djui.h +++ b/src/pc/djui/djui.h @@ -46,3 +46,5 @@ void djui_connect_menu_open(void); void djui_lua_error(char* text); void djui_render(void); void djui_reset_hud_params(void); + +void reset_djui(void); diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 4f150b5b..431f379d 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -25,6 +25,7 @@ #include "../platform.h" #include "../configfile.h" #include "../fs/fs.h" +#include "../pc_main.h" #include "macros.h" @@ -1779,6 +1780,8 @@ void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, co gfx_rapi->init(); gfx_cc_precomp(); + + gGfxInited = true; } struct GfxRenderingAPI *gfx_get_current_rendering_api(void) { diff --git a/src/pc/loading.c b/src/pc/loading.c new file mode 100644 index 00000000..59918269 --- /dev/null +++ b/src/pc/loading.c @@ -0,0 +1,193 @@ +#include "gfx_dimensions.h" +#include "game/segment2.h" + +#include "djui/djui.h" +#include "pc/djui/djui_unicode.h" + +#include "controller/controller_keyboard.h" + +#include "pc_main.h" +#include "loading.h" +#include "pc/utils/misc.h" + +struct LoadingSegment gCurrLoadingSegment = { "", 0 }; + +struct LoadingScreen { + struct DjuiBase base; + struct DjuiText* splashText; + struct DjuiText* loadingText; + struct DjuiText* loadingDesc; + struct DjuiProgressBar *loadingBar; +}; + +struct LoadingScreen* sLoading = NULL; +pthread_t gLoadingThreadId; +pthread_mutex_t gLoadingThreadMutex = PTHREAD_MUTEX_INITIALIZER; + +bool gIsThreaded = false; + +extern Vp D_8032CF00; +extern u8 gRenderingInterpolated; + +static void loading_screen_produce_one_frame() { + + // Start frame + gfx_start_frame(); + config_gfx_pool(); + init_render_image(); + create_dl_ortho_matrix(); + djui_gfx_displaylist_begin(); + + // Fix scaling issues + gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&D_8032CF00)); + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - BORDER_HEIGHT); + + // Clear screen + create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), 240.f, 0.f); + create_dl_scale_matrix(MENU_MTX_NOPUSH, (GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT) / 130.f, 3.f, 1.f); + gDPSetEnvColor(gDisplayListHead++, 0x00, 0x00, 0x00, 0xFF); + gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + + // Render loading screen elements + if (sLoading) { djui_base_render(&sLoading->base); } + + // Render frame + djui_gfx_displaylist_end(); + end_master_display_list(); + alloc_display_list(0); + gfx_run((Gfx*) gGfxSPTask->task.t.data_ptr); // send_display_list + display_and_vsync(); + gfx_end_frame(); +} + +static bool loading_screen_on_render(struct DjuiBase* base) { + pthread_mutex_lock(&gLoadingThreadMutex); + + u32 windowWidth, windowHeight; + WAPI.get_dimensions(&windowWidth, &windowHeight); + f32 scale = djui_gfx_get_scale(); + windowWidth /= scale; + windowHeight /= scale; + + // Fill the screen + djui_base_set_size(base, windowWidth, windowHeight); + + // Splash text + djui_base_set_location(&sLoading->splashText->base, (windowWidth / 2) - 416, 0); + + { + // Loading... text + char* loadingStr = DLANG(LOADING_SCREEN, LOADING); + char tmp[20] = ""; + switch ((u8) floor(clock_elapsed()) % 3) { + case 0: snprintf(tmp, 20, "%s...", loadingStr); break; + case 1: snprintf(tmp, 20, "%s.", loadingStr); break; + default: snprintf(tmp, 20, "%s..", loadingStr); break; + } + djui_text_set_text(sLoading->loadingText, tmp); + djui_base_set_visible(&sLoading->loadingText->base, sLoading->loadingText->base.y.value + 50 < sLoading->loadingDesc->base.y.value); + } + + { + // Loading text description + char buffer[256] = ""; + if (strlen(gCurrLoadingSegment.str) > 0) { + if (gCurrLoadingSegment.percentage > 0) { + snprintf(buffer, 256, "%s... %d%%", gCurrLoadingSegment.str, (u8) floor(gCurrLoadingSegment.percentage * 100)); + } else { + snprintf(buffer, 256, "%s...", gCurrLoadingSegment.str); + } + } + djui_text_set_text(sLoading->loadingDesc, buffer); + djui_base_set_location(&sLoading->loadingDesc->base, 0, windowHeight - 250); + } + + // Loading bar + djui_base_set_location(&sLoading->loadingBar->base, windowWidth / 4, windowHeight - 100); + djui_base_set_visible(&sLoading->loadingBar->base, gCurrLoadingSegment.percentage > 0 && strlen(gCurrLoadingSegment.str) > 0); + + djui_base_compute(base); + + pthread_mutex_unlock(&gLoadingThreadMutex); + + return true; +} + +static void loading_screen_destroy(struct DjuiBase* base) { + struct LoadingScreen* load = (struct LoadingScreen*)base; + free(load); + sLoading = NULL; +} + +void render_loading_screen() { + struct LoadingScreen* load = malloc(sizeof(struct LoadingScreen)); + struct DjuiBase* base = &load->base; + + djui_base_init(NULL, base, loading_screen_on_render, loading_screen_destroy); + + { + // Splash text + struct DjuiText* splashDjuiText = djui_text_create(base, "\\#ff0800\\SM\\#1be700\\64\\#00b3ff\\EX\\#ffef00\\COOP"); + djui_text_set_font(splashDjuiText, gDjuiFonts[1]); + djui_text_set_font_scale(splashDjuiText, gDjuiFonts[1]->defaultFontScale * 4); + djui_text_set_alignment(splashDjuiText, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP); + djui_base_set_size(&splashDjuiText->base, 800, 800); + + load->splashText = splashDjuiText; + } + + { + // "Loading..." text + struct DjuiText *text = djui_text_create(base, ""); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&text->base, 1.0f, 32 * 4); + djui_base_set_color(&text->base, 200, 200, 200, 255); + djui_base_set_location(&text->base, 0, 400); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + djui_text_set_font(text, gDjuiFonts[0]); + djui_text_set_font_scale(text, gDjuiFonts[0]->defaultFontScale * 2); + + load->loadingText = text; + } + + { + // Current loading stage text + struct DjuiText *text = djui_text_create(base, ""); + djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); + djui_base_set_size(&text->base, 1.0f, 32 * 4); + djui_base_set_color(&text->base, 200, 200, 200, 255); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP); + djui_text_set_font(text, gDjuiFonts[0]); + djui_text_set_font_scale(text, gDjuiFonts[0]->defaultFontScale * 2); + + load->loadingDesc = text; + } + + { + // Loading bar + struct DjuiProgressBar *progressBar = djui_progress_bar_create(base, &gCurrLoadingSegment.percentage, 0.0f, 1.0f, false); + djui_base_set_visible(&progressBar->base, false); + progressBar->base.width.value = 0.5; + + load->loadingBar = progressBar; + } + + sLoading = load; + + // Loading screen loop + while (!gGameInited) { + WAPI.main_loop(loading_screen_produce_one_frame); + } + + pthread_join(gLoadingThreadId, NULL); + + // Reset some things after rendering the loading screen + reset_djui(); + alloc_display_list_reset(); + gDisplayListHead = NULL; + djui_init(); + djui_unicode_init(); + rendering_init(); + configWindow.settings_changed = true; +} diff --git a/src/pc/loading.h b/src/pc/loading.h new file mode 100644 index 00000000..cf9383a6 --- /dev/null +++ b/src/pc/loading.h @@ -0,0 +1,25 @@ +#ifndef LOADING_HEADER +#define LOADING_HEADER + +#include + +struct LoadingSegment { + char str[256]; + f32 percentage; +}; + +extern struct LoadingSegment gCurrLoadingSegment; + +#define REFRESH_MUTEX(...) \ + pthread_mutex_lock(&gLoadingThreadMutex); \ + __VA_ARGS__; \ + pthread_mutex_unlock(&gLoadingThreadMutex); \ + +extern pthread_t gLoadingThreadId; +extern pthread_mutex_t gLoadingThreadMutex; + +extern bool gIsThreaded; + +void render_loading_screen(); + +#endif diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index c4c86f46..5b0cb0c5 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -10,10 +10,7 @@ #include "pc/crash_handler.h" #include "src/game/hud.h" #include "pc/debug_context.h" -#include "pc/network/network.h" -#include "pc/network/network_player.h" -#include "pc/network/socket/socket.h" -#include "pc/chat_commands.h" +#include "pc/pc_main.h" #if defined(DEVELOPMENT) #include "../mods/mods.h" @@ -92,6 +89,8 @@ struct LuaHookedEvent { static struct LuaHookedEvent sHookedEvents[HOOK_MAX] = { 0 }; int smlua_call_hook(lua_State* L, int nargs, int nresults, int errfunc, struct Mod* activeMod) { + if (!gGameInited) { return 0; } // Don't call hooks while the game is booting + struct Mod* prev = gLuaActiveMod; gLuaActiveMod = activeMod; gLuaLastHookMod = activeMod; diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index fe018170..2fe1f63e 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -322,7 +322,7 @@ static bool mod_load_files(struct Mod* mod, char* modName, char* fullPath) { const char* fileTypes[] = { ".bin", ".col", NULL }; if (!mod_load_files_dir(mod, fullPath, "actors", fileTypes)) { return false; } } - + // deal with behaviors directory { const char* fileTypes[] = { ".bhv", NULL }; @@ -525,11 +525,11 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) { } // print - LOG_INFO(" %s", mod->name); + // LOG_INFO(" %s", mod->name); for (int i = 0; i < mod->fileCount; i++) { struct ModFile* file = &mod->files[i]; mod_cache_add(mod, file, true); - LOG_INFO(" - %s", file->relativePath); + // LOG_INFO(" - %s", file->relativePath); } return true; diff --git a/src/pc/mods/mod_import.c b/src/pc/mods/mod_import.c index 99d63d32..2eb183de 100644 --- a/src/pc/mods/mod_import.c +++ b/src/pc/mods/mod_import.c @@ -230,6 +230,7 @@ bool mod_import_file(char* path) { djui_language_replace(DLANG(NOTIF, IMPORT_MOD_SUCCESS), msg, SYS_MAX_PATH, '@', basename); djui_popup_create(msg, 2); } else if (isDynos) { + dynos_gfx_init(); dynos_packs_init(); djui_language_replace(DLANG(NOTIF, IMPORT_DYNOS_SUCCESS), msg, SYS_MAX_PATH, '@', basename); djui_popup_create(msg, 2); diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c index b03fe146..c9653a29 100644 --- a/src/pc/mods/mods.c +++ b/src/pc/mods/mods.c @@ -4,7 +4,7 @@ #include "mod_cache.h" #include "data/dynos.c.h" #include "pc/debuglog.h" -#include "pc/pc_main.h" +#include "pc/loading.h" #define MAX_SESSION_CHARS 7 @@ -147,7 +147,18 @@ static void mods_sort(struct Mods* mods) { } } -static void mods_load(struct Mods* mods, char* modsBasePath) { +static u32 mods_count_directory(char* modsBasePath) { + struct dirent* dir = NULL; + DIR* d = opendir(modsBasePath); + u32 pathCount = 0; + while ((dir = readdir(d)) != NULL) pathCount++; + closedir(d); + return pathCount; +} + +static void mods_load(struct Mods* mods, char* modsBasePath, bool isUserModPath) { + if (gIsThreaded) { REFRESH_MUTEX(snprintf(gCurrLoadingSegment.str, 256, "Generating DynOS Packs in %s mod path (%s)", isUserModPath ? "user" : "local", modsBasePath)); } + // generate bins dynos_generate_packs(modsBasePath); @@ -174,10 +185,13 @@ static void mods_load(struct Mods* mods, char* modsBasePath) { LOG_ERROR("Could not open directory '%s'", modsBasePath); return; } + f32 count = (f32) mods_count_directory(modsBasePath); + if (gIsThreaded) { REFRESH_MUTEX(snprintf(gCurrLoadingSegment.str, 256, "Loading Mods in %s mod path (%s)", isUserModPath ? "user" : "local", modsBasePath)); } // iterate char path[SYS_MAX_PATH] = { 0 }; - while ((dir = readdir(d)) != NULL) { + for (u32 i = 0; (dir = readdir(d)) != NULL; ++i) { + // sanity check / fill path[] if (!directory_sanity_check(dir, modsBasePath, path)) { continue; } @@ -185,10 +199,11 @@ static void mods_load(struct Mods* mods, char* modsBasePath) { if (!mod_load(mods, modsBasePath, dir->d_name)) { break; } + if (gIsThreaded) { REFRESH_MUTEX(gCurrLoadingSegment.percentage = (f32) i / count); } } closedir(d); - + if (gIsThreaded) { REFRESH_MUTEX(gCurrLoadingSegment.percentage = 1); } } void mods_refresh_local(void) { @@ -208,13 +223,13 @@ void mods_refresh_local(void) { mods_clear(&gLocalMods); // load mods - if (hasUserPath) { mods_load(&gLocalMods, userModPath); } + if (hasUserPath) { mods_load(&gLocalMods, userModPath, true); } const char* exePath = path_to_executable(); char defaultModsPath[SYS_MAX_PATH] = { 0 }; path_get_folder((char*)exePath, defaultModsPath); strncat(defaultModsPath, MOD_DIRECTORY, SYS_MAX_PATH-1); - mods_load(&gLocalMods, defaultModsPath); + mods_load(&gLocalMods, defaultModsPath, false); // sort mods_sort(&gLocalMods); @@ -242,6 +257,8 @@ void mods_enable(char* relativePath) { } void mods_init(void) { + if (gIsThreaded) { REFRESH_MUTEX(snprintf(gCurrLoadingSegment.str, 256, "Caching mods")); } + // load mod cache mod_cache_load(); mods_refresh_local(); diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index 1e275f6a..2fdf9e9d 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -19,6 +19,7 @@ #include "audio/audio_null.h" #include "pc_main.h" +#include "loading.h" #include "cliopts.h" #include "configfile.h" #include "controller/controller_api.h" @@ -238,8 +239,10 @@ void audio_shutdown(void) { } void game_deinit(void) { - smlua_call_event_hooks(HOOK_ON_EXIT); - configfile_save(configfile_name()); + if (gGameInited) { + smlua_call_event_hooks(HOOK_ON_EXIT); + configfile_save(configfile_name()); + } controller_shutdown(); audio_custom_shutdown(); audio_shutdown(); @@ -256,24 +259,28 @@ void game_exit(void) { exit(0); } -void main_func(void) { +void *main_game_init(void*) { const char *gamedir = gCLIOpts.GameDir[0] ? gCLIOpts.GameDir : FS_BASEDIR; const char *userpath = gCLIOpts.SavePath[0] ? gCLIOpts.SavePath : sys_user_path(); fs_init(sys_ropaths, gamedir, userpath); - sync_objects_init_system(); - djui_unicode_init(); - djui_init(); - dynos_packs_init(); - mods_init(); + dynos_gfx_init(); // load config configfile_load(); - if (!djui_language_init(configLanguage)) { - snprintf(configLanguage, MAX_CONFIG_STRING, "%s", ""); - } + configWindow.settings_changed = true; + if (!djui_language_init(configLanguage)) { snprintf(configLanguage, MAX_CONFIG_STRING, "%s", ""); } + dynos_packs_init(); + sync_objects_init_system(); - dynos_pack_init(); + mods_init(); + enable_queued_mods(); + if (gIsThreaded) { + REFRESH_MUTEX( + gCurrLoadingSegment.percentage = 0; + snprintf(gCurrLoadingSegment.str, 256, "Starting game"); + ); + } // If coop_custom_palette_* values are not found in sm64config.txt, the custom palette config will use the default values (Mario's palette) // But if no preset is found, that means the current palette is a custom palette @@ -292,7 +299,6 @@ void main_func(void) { if (gCLIOpts.FullScreen == 1) { configWindow.fullscreen = true; } else if (gCLIOpts.FullScreen == 2) { configWindow.fullscreen = false; } - // incase the loading screen failed, or is disabled if (!gGfxInited) { gfx_init(&WAPI, &RAPI, TITLE); WAPI.set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up, keyboard_on_text_input); @@ -310,9 +316,36 @@ void main_func(void) { thread5_game_loop(NULL); + gGameInited = true; +} + +int main(int argc, char *argv[]) { + + // Handle terminal arguments + if (!parse_cli_opts(argc, argv)) { return 0; } + + // Create the window straight away + if (!gGfxInited) { + gfx_init(&WAPI, &RAPI, TITLE); + WAPI.set_keyboard_callbacks(keyboard_on_key_down, keyboard_on_key_up, keyboard_on_all_keys_up, keyboard_on_text_input); + } + + // Start the thread for setting up the game + if (pthread_mutex_init(&gLoadingThreadMutex, NULL) == 0 && pthread_create(&gLoadingThreadId, NULL, main_game_init, (void*) 1) == 0) { + gIsThreaded = true; + render_loading_screen(); // Render the loading screen while the game is setup + gIsThreaded = false; + } else { + main_game_init(NULL); // Failsafe incase threading doesn't work + } + pthread_mutex_destroy(&gLoadingThreadMutex); + + // Initialize djui + djui_init(); + djui_unicode_init(); djui_init_late(); - // init network + // Init network if (gCLIOpts.Network == NT_CLIENT) { network_set_system(NS_SOCKET); snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", gCLIOpts.JoinIp); @@ -322,6 +355,12 @@ void main_func(void) { } else if (gCLIOpts.Network == NT_SERVER) { network_set_system(NS_SOCKET); configHostPort = gCLIOpts.NetworkPort; + + // Horrible, hacky fix for mods that access marioObj straight away + // best fix: host with the standard main menu method + static struct Object sHackyObject = { 0 }; + gMarioStates[0].marioObj = &sHackyObject; + network_init(NT_SERVER, false); djui_panel_shutdown(); djui_panel_modlist_create(NULL); @@ -329,8 +368,7 @@ void main_func(void) { network_init(NT_NONE, false); } - gGameInited = true; - + // Main loop while (true) { debug_context_reset(); CTX_BEGIN(CTX_FRAME); @@ -346,10 +384,5 @@ void main_func(void) { } bassh_deinit(); -} - -int main(int argc, char *argv[]) { - parse_cli_opts(argc, argv); - main_func(); return 0; } diff --git a/src/pc/pc_main.h b/src/pc/pc_main.h index 3390f008..02bee1bc 100644 --- a/src/pc/pc_main.h +++ b/src/pc/pc_main.h @@ -60,8 +60,6 @@ extern bool gCoopCompatibility; #define TITLE "sm64coopdx" #endif -#define LOAD_STEPS 6 - #define AT_STARTUP __attribute__((constructor)) extern bool gGameInited;