diff --git a/data/dynos.c.h b/data/dynos.c.h index a632cbc1..a5df5a3f 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 ff1d9a0d..2a6c0a36 100644 --- a/data/dynos_c.cpp +++ b/data/dynos_c.cpp @@ -63,8 +63,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 dc13e0a1..67622e3a 100644 --- a/lang/Czech.ini +++ b/lang/Czech.ini @@ -357,3 +357,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 0184f5a6..c3b8e4c6 100644 --- a/lang/Dutch.ini +++ b/lang/Dutch.ini @@ -354,4 +354,7 @@ REFRESH = "Herlaad" REFRESHING = "herladen..." ENTER_PASSWORD = "Typ het wachtwoord van de privé lobby:" SEARCH = "Zoek" -NONE_FOUND = "Er zijn geen lobby's gevonden." \ No newline at end of file +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 44313959..84ff1d59 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -357,3 +357,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 5d8facf8..fdc7f485 100644 --- a/lang/French.ini +++ b/lang/French.ini @@ -358,3 +358,5 @@ 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 869f2e23..44ee264b 100644 --- a/lang/German.ini +++ b/lang/German.ini @@ -357,3 +357,6 @@ REFRESHING = "Wird Aktualisiert..." ENTER_PASSWORD = "Gib das Passwort für das Lobby ein:" SEARCH = "Suchen" NONE_FOUND = "Es wurden keine Lobbys gefunden." + +[LOADING_SCREEN] +LOADING = "Wird geladen" diff --git a/lang/Italian.ini b/lang/Italian.ini index 5209f6f5..533186e6 100644 --- a/lang/Italian.ini +++ b/lang/Italian.ini @@ -354,3 +354,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 27529bdb..af11ca34 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!" @@ -356,4 +356,7 @@ REFRESH = "Recarregar" REFRESHING = "Recarregando..." ENTER_PASSWORD = "Coloque a senha para a partida privada:" SEARCH = "Pesquisar" -NONE_FOUND = "Nenhuma partida foi encontrada." \ No newline at end of file +NONE_FOUND = "Nenhuma partida foi encontrada." + +[LOADING_SCREEN] +LOADING = "Carregando" diff --git a/lang/Russian.ini b/lang/Russian.ini index d3eae869..5829e9ee 100644 --- a/lang/Russian.ini +++ b/lang/Russian.ini @@ -355,4 +355,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 2c5acb63..91051ab0 100644 --- a/src/game/characters.c +++ b/src/game/characters.c @@ -323,7 +323,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, @@ -600,7 +600,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, @@ -880,7 +880,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, @@ -1157,7 +1157,7 @@ struct Character gCharacters[CT_MAX] = { .torsoRotMult = 3.0f / 5.0f, // anim .animOffsetEnabled = true, - + // character anims .animSlowLedgeGrab = MARIO_ANIM_SLOW_LEDGE_GRAB, .animFallOverBackwards = MARIO_ANIM_FALL_OVER_BACKWARDS, @@ -1520,7 +1520,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; @@ -1611,7 +1611,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 be09f754..7ba7b4c6 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -281,6 +281,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++) { @@ -288,7 +305,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 d4081db2..20b988ea 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -121,6 +121,7 @@ extern char configPassword[]; extern char configDestId[]; extern bool configFadeoutDistantSounds; +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 d5e5b59e..a2e4b8bd 100644 --- a/src/pc/crash_handler.c +++ b/src/pc/crash_handler.c @@ -275,8 +275,7 @@ static void crash_handler_produce_one_frame() { // 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() { // 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 10a09f39..39138a1b 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; @@ -104,7 +123,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 2499a8d7..08f71726 100644 --- a/src/pc/djui/djui.h +++ b/src/pc/djui/djui.h @@ -45,3 +45,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 0bf0f711..bc84fee3 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" @@ -1895,6 +1896,8 @@ void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, co gfx_rapi->init(); gfx_cc_precomp(); + + gGfxInited = true; } #ifdef EXTERNAL_DATA 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 3ff71793..f7d7c42c 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -7,6 +7,7 @@ #include "pc/crash_handler.h" #include "src/game/hud.h" #include "pc/debug_context.h" +#include "pc/pc_main.h" #if defined(DEVELOPMENT) #include "../mods/mods.h" @@ -90,6 +91,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 b751b0da..2833dd5c 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 b60c1a57..95773c01 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -17,6 +17,7 @@ #include "audio/audio_null.h" #include "pc_main.h" +#include "loading.h" #include "cliopts.h" #include "configfile.h" #include "controller/controller_api.h" @@ -234,7 +235,7 @@ void audio_shutdown(void) { } void game_deinit(void) { - configfile_save(configfile_name()); + if (gGameInited) configfile_save(configfile_name()); controller_shutdown(); audio_custom_shutdown(); audio_shutdown(); @@ -251,24 +252,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 @@ -286,7 +291,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); @@ -304,25 +308,6 @@ void main_func(void) { thread5_game_loop(NULL); - djui_init_late(); - - // init network - if (gCLIOpts.Network == NT_CLIENT) { - network_set_system(NS_SOCKET); - snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", gCLIOpts.JoinIp); - snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gCLIOpts.JoinIp); - configJoinPort = gCLIOpts.NetworkPort; - network_init(NT_CLIENT, false); - } else if (gCLIOpts.Network == NT_SERVER) { - network_set_system(NS_SOCKET); - configHostPort = gCLIOpts.NetworkPort; - network_init(NT_SERVER, false); - djui_panel_shutdown(); - djui_panel_modlist_create(NULL); - } else { - network_init(NT_NONE, false); - } - #ifdef EXTERNAL_DATA // precache data if needed if (configPrecacheRes) { @@ -333,7 +318,58 @@ void main_func(void) { #endif 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 + if (gCLIOpts.Network == NT_CLIENT) { + network_set_system(NS_SOCKET); + snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", gCLIOpts.JoinIp); + snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gCLIOpts.JoinIp); + configJoinPort = gCLIOpts.NetworkPort; + network_init(NT_CLIENT, false); + } 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); + } else { + network_init(NT_NONE, false); + } + + // Main loop while (true) { debug_context_reset(); CTX_BEGIN(CTX_FRAME); @@ -349,10 +385,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 f6d00894..baa72eb3 100644 --- a/src/pc/pc_main.h +++ b/src/pc/pc_main.h @@ -58,8 +58,6 @@ extern "C" { #define TITLE ({ char title[96] = ""; snprintf(title, 96, "sm64ex-coop: %s", get_version_local()); title; }) #endif -#define LOAD_STEPS 6 - #define AT_STARTUP __attribute__((constructor)) extern bool gGameInited;