sm64coopdx/src/pc/pc_main.c
Cooliokid956 3bfa75e32b
Some checks are pending
Build coop / build-ubuntu (push) Waiting to run
Build coop / build-windows (push) Waiting to run
Build coop / build-macos-arm (push) Waiting to run
Build coop / build-macos-intel (push) Waiting to run
Miscellaneous Additions: Addendum (#723)
* More autogen work

- made the `Pointer_` classes into aliases instead so that they're actually associated with their true type
- "Total constants" metric is now accurate

* High Quality Master Volume

* Audio work

it's 12:55am and there are some bright flashes outside (lightning)

- cracked the code (interpreted the "Acc" in `tempoAcc`)
- added several functions that will help greatly in the Streamed Music department (you can now match sequenced music fading (transitions, eepy, etc.))
- introducing `gMasterVolume`! a variable that is only updated when it needs to be, theoretically improving performance (by some amount). this variable is used in many places in place of recalculations of the same number
- made it so that muting the game skips some audio processing (not the main process since that would linger after unmuting (not good))
- fixed an oversight where lua volumes were not taken into account when `audio_stream_set_volume`
- it's its 😁
- removed additional `#include "audio/external.h"`

it is 1:06am
gn

* Add mouse status functions

you can now check if mouse buttons were held, clicked, or released

* Sorting was a bad idea

disabled sorting for constants so that they are represented more closely to their original defines

* Expose playerlist page index

also noticed that sorting still sucks

* Minor (very important) detail

lalette

* Addressing the PeachyPeachSM64 reviews

* Return of the Forced 4:3 Mode

shoutouts to DISPLAY.FORCE_4BY3 for sticking through the toughest of times, waiting for this day to come

* Added scroll support

- Scrolling added to chat box (hold ctrl to scroll fewer lines, shift to scroll faster)
- Scrolling functions added to smlua

* Addressing the Isaac0-dev review + fixes

- mouse scroll is now accumulated
- djui_gfx_get_dimensions
- forced 4:3 won't kick in if the window isn't wide enough
- game now recognizes horizontal resizing when in 4:3 mode

* Run autogen

* gfx_get_dimensions

works just as well
2025-04-05 13:52:14 -04:00

572 lines
17 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include "sm64.h"
#include "pc/lua/smlua.h"
#include "pc/lua/utils/smlua_text_utils.h"
#include "game/memory.h"
#include "audio/data.h"
#include "audio/external.h"
#include "network/network.h"
#include "lua/smlua.h"
#include "audio/audio_api.h"
#include "audio/audio_sdl.h"
#include "audio/audio_null.h"
#include "rom_assets.h"
#include "rom_checker.h"
#include "pc_main.h"
#include "loading.h"
#include "cliopts.h"
#include "configfile.h"
#include "thread.h"
#include "controller/controller_api.h"
#include "controller/controller_keyboard.h"
#include "controller/controller_mouse.h"
#include "fs/fs.h"
#include "game/display.h" // for gGlobalTimer
#include "game/game_init.h"
#include "game/main.h"
#include "game/rumble_init.h"
#include "pc/lua/utils/smlua_audio_utils.h"
#include "pc/network/version.h"
#include "pc/network/socket/socket.h"
#include "pc/network/network_player.h"
#include "pc/update_checker.h"
#include "pc/djui/djui.h"
#include "pc/djui/djui_unicode.h"
#include "pc/djui/djui_panel.h"
#include "pc/djui/djui_panel_modlist.h"
#include "pc/djui/djui_ctx_display.h"
#include "pc/djui/djui_fps_display.h"
#include "pc/djui/djui_lua_profiler.h"
#include "pc/debuglog.h"
#include "pc/utils/misc.h"
#include "pc/mods/mods.h"
#include "debug_context.h"
#include "menu/intro_geo.h"
#include "gfx_dimensions.h"
#include "game/segment2.h"
#ifdef DISCORD_SDK
#include "pc/discord/discord.h"
#endif
#include "pc/mumble/mumble.h"
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#endif
extern Vp D_8032CF00;
OSMesg D_80339BEC;
OSMesgQueue gSIEventMesgQueue;
s8 gResetTimer;
s8 D_8032C648;
s8 gDebugLevelSelect;
s8 gShowProfiler;
s8 gShowDebugText;
s32 gRumblePakPfs;
u32 gNumVblanks = 0;
u8 gRenderingInterpolated = 0;
f32 gRenderingDelta = 0;
#define FRAMERATE 30
static const f64 sFrameTime = (1.0 / ((double)FRAMERATE));
static f64 sFpsTimeLast = 0;
static f64 sFrameTimeStart = 0;
static u32 sDrawnFrames = 0;
bool gGameInited = false;
bool gGfxInited = false;
f32 gMasterVolume;
u8 gLuaVolumeMaster = 127;
u8 gLuaVolumeLevel = 127;
u8 gLuaVolumeSfx = 127;
u8 gLuaVolumeEnv = 127;
static struct AudioAPI *audio_api;
struct GfxWindowManagerAPI *wm_api = &WAPI;
extern void gfx_run(Gfx *commands);
extern void thread5_game_loop(void *arg);
extern void create_next_audio_buffer(s16 *samples, u32 num_samples);
void game_loop_one_iteration(void);
void dispatch_audio_sptask(UNUSED struct SPTask *spTask) {}
void set_vblank_handler(UNUSED s32 index, UNUSED struct VblankHandler *handler, UNUSED OSMesgQueue *queue, UNUSED OSMesg *msg) {}
void send_display_list(struct SPTask *spTask) {
if (!gGameInited) { return; }
gfx_run((Gfx *)spTask->task.t.data_ptr);
}
#ifdef VERSION_EU
#define SAMPLES_HIGH 560 // gAudioBufferParameters.maxAiBufferLength
#define SAMPLES_LOW 528 // gAudioBufferParameters.minAiBufferLength
#else
#define SAMPLES_HIGH 544
#define SAMPLES_LOW 528
#endif
extern void patch_mtx_before(void);
extern void patch_screen_transition_before(void);
extern void patch_title_screen_before(void);
extern void patch_dialog_before(void);
extern void patch_hud_before(void);
extern void patch_paintings_before(void);
extern void patch_bubble_particles_before(void);
extern void patch_snow_particles_before(void);
extern void patch_djui_before(void);
extern void patch_djui_hud_before(void);
extern void patch_scroll_targets_before(void);
extern void patch_mtx_interpolated(f32 delta);
extern void patch_screen_transition_interpolated(f32 delta);
extern void patch_title_screen_interpolated(f32 delta);
extern void patch_dialog_interpolated(f32 delta);
extern void patch_hud_interpolated(f32 delta);
extern void patch_paintings_interpolated(f32 delta);
extern void patch_bubble_particles_interpolated(f32 delta);
extern void patch_snow_particles_interpolated(f32 delta);
extern void patch_djui_interpolated(f32 delta);
extern void patch_djui_hud(f32 delta);
extern void patch_scroll_targets_interpolated(f32 delta);
static void patch_interpolations_before(void) {
patch_mtx_before();
patch_screen_transition_before();
patch_title_screen_before();
patch_dialog_before();
patch_hud_before();
patch_paintings_before();
patch_bubble_particles_before();
patch_snow_particles_before();
patch_djui_before();
patch_djui_hud_before();
patch_scroll_targets_before();
}
static inline void patch_interpolations(f32 delta) {
patch_mtx_interpolated(delta);
patch_screen_transition_interpolated(delta);
patch_title_screen_interpolated(delta);
patch_dialog_interpolated(delta);
patch_hud_interpolated(delta);
patch_paintings_interpolated(delta);
patch_bubble_particles_interpolated(delta);
patch_snow_particles_interpolated(delta);
patch_djui_interpolated(delta);
patch_djui_hud(delta);
patch_scroll_targets_interpolated(delta);
}
static void compute_fps(f64 curTime) {
u32 fps = round((f64) sDrawnFrames / MAX(0.001, curTime - sFpsTimeLast));
djui_fps_display_update(fps);
sFpsTimeLast = curTime;
sDrawnFrames = 0;
}
static s32 get_num_frames_to_draw(f64 t) {
if (configFrameLimit % FRAMERATE == 0) {
return configFrameLimit / FRAMERATE;
}
s64 numFramesCurr = (s64) (t * (f64) configFrameLimit);
s64 numFramesNext = (s64) ((t + sFrameTime) * (f64) configFrameLimit);
return (s32) MAX(1, numFramesNext - numFramesCurr);
}
void produce_interpolation_frames_and_delay(void) {
bool is30Fps = (!configUncappedFramerate && configFrameLimit == FRAMERATE);
gRenderingInterpolated = true;
f64 curTime = clock_elapsed_f64();
f64 targetTime = sFrameTimeStart + sFrameTime;
s32 numFramesToDraw = get_num_frames_to_draw(sFrameTimeStart);
f64 loopStartTime = curTime;
f64 expectedTime = 0;
// interpolate and render
// make sure to draw at least one frame to prevent the game from freezing completely
// (including inputs and window events) if the game update duration is greater than 33ms
do {
f32 delta = (
is30Fps ?
1.0f :
MIN(MAX((curTime - sFrameTimeStart) / sFrameTime, 0.f), 1.f)
);
gRenderingDelta = delta;
gfx_start_frame();
if (!gSkipInterpolationTitleScreen) { patch_interpolations(delta); }
send_display_list(gGfxSPTask);
gfx_end_frame();
sDrawnFrames++;
if (!is30Fps && configUncappedFramerate) { continue; }
// delay if our framerate is capped
f64 now = clock_elapsed_f64();
f64 elapsedTime = now - loopStartTime;
expectedTime += (targetTime - curTime) / (f64) numFramesToDraw;
f64 delay = (expectedTime - elapsedTime) * 1000.0;
if (delay > 0.0) {
WAPI.delay((u32)delay);
}
numFramesToDraw--;
} while ((curTime = clock_elapsed_f64()) < targetTime && numFramesToDraw > 0);
// compute and update the frame rate every second
if ((curTime = clock_elapsed_f64()) >= sFpsTimeLast + 1.0) {
compute_fps(curTime);
}
// advance frame start time
if (curTime > sFrameTimeStart + 2 * sFrameTime) {
sFrameTimeStart = curTime;
} else {
sFrameTimeStart += sFrameTime;
}
gRenderingInterpolated = false;
}
// It's just better to have this off the stack, Because the size isn't small.
// It also may help static analysis and bug catching.
static s16 sAudioBuffer[SAMPLES_HIGH * 2 * 2] = { 0 };
inline static void buffer_audio(void) {
bool shouldMute = (configMuteFocusLoss && !WAPI.has_focus()) || (gMasterVolume == 0);
if (!shouldMute) {
set_sequence_player_volume(SEQ_PLAYER_LEVEL, (f32)configMusicVolume / 127.0f * (f32)gLuaVolumeLevel / 127.0f);
set_sequence_player_volume(SEQ_PLAYER_SFX, (f32)configSfxVolume / 127.0f * (f32)gLuaVolumeSfx / 127.0f);
set_sequence_player_volume(SEQ_PLAYER_ENV, (f32)configEnvVolume / 127.0f * (f32)gLuaVolumeEnv / 127.0f);
}
int samplesLeft = audio_api->buffered();
u32 numAudioSamples = samplesLeft < audio_api->get_desired_buffered() ? SAMPLES_HIGH : SAMPLES_LOW;
for (s32 i = 0; i < 2; i++) {
create_next_audio_buffer(sAudioBuffer + i * (numAudioSamples * 2), numAudioSamples);
}
if (!shouldMute) {
for (u16 i=0; i < ARRAY_COUNT(sAudioBuffer); i++) {
sAudioBuffer[i] *= gMasterVolume;
}
audio_api->play((u8 *)sAudioBuffer, 2 * numAudioSamples * 4);
}
}
void *audio_thread(UNUSED void *arg) {
// As long as we have an audio api and that we're threaded, Loop.
while (audio_api) {
f64 curTime = clock_elapsed_f64();
// Buffer the audio.
lock_mutex(&gAudioThread);
buffer_audio();
unlock_mutex(&gAudioThread);
// Delay till the next frame for smooth audio at the correct speed.
// delay
f64 targetDelta = 1.0 / (f64)FRAMERATE;
f64 now = clock_elapsed_f64();
f64 actualDelta = now - curTime;
if (actualDelta < targetDelta) {
f64 delay = ((targetDelta - actualDelta) * 1000.0);
WAPI.delay((u32)delay);
}
}
// Exit the thread if our loop breaks.
exit_thread();
return NULL;
}
void produce_one_frame(void) {
CTX_EXTENT(CTX_NETWORK, network_update);
CTX_EXTENT(CTX_INTERP, patch_interpolations_before);
CTX_EXTENT(CTX_GAME_LOOP, game_loop_one_iteration);
CTX_EXTENT(CTX_SMLUA, smlua_update);
// If we aren't threaded
if (gAudioThread.state == INVALID) {
CTX_EXTENT(CTX_AUDIO, buffer_audio);
}
CTX_EXTENT(CTX_RENDER, produce_interpolation_frames_and_delay);
}
// used for rendering 2D scenes fullscreen like the loading or crash screens
void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB) {
// 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++, clearColorR, clearColorG, clearColorB, 0xFF);
gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box);
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
// call the callback
callback();
// 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();
}
void audio_shutdown(void) {
audio_custom_shutdown();
if (audio_api) {
if (audio_api->shutdown) audio_api->shutdown();
audio_api = NULL;
}
}
void game_deinit(void) {
if (gGameInited) { configfile_save(configfile_name()); }
controller_shutdown();
audio_custom_shutdown();
audio_shutdown();
network_shutdown(true, true, false, false);
smlua_text_utils_shutdown();
smlua_shutdown();
smlua_audio_custom_deinit();
mods_shutdown();
djui_shutdown();
gfx_shutdown();
gGameInited = false;
}
void game_exit(void) {
LOG_INFO("exiting cleanly");
game_deinit();
exit(0);
}
void* main_game_init(UNUSED void* dummy) {
// load language
if (!djui_language_init(configLanguage)) { snprintf(configLanguage, MAX_CONFIG_STRING, "%s", ""); }
LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading"));
dynos_gfx_init();
enable_queued_dynos_packs();
sync_objects_init_system();
if (gCLIOpts.network != NT_SERVER && !gCLIOpts.skipUpdateCheck) {
check_for_updates();
}
LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading ROM Assets"));
rom_assets_load();
smlua_text_utils_init();
mods_init();
enable_queued_mods();
LOADING_SCREEN_MUTEX(
gCurrLoadingSegment.percentage = 0;
loading_screen_set_segment_text("Starting Game");
);
audio_init();
sound_init();
network_player_init();
mumble_init();
gGameInited = true;
}
int main(int argc, char *argv[]) {
// handle terminal arguments
if (!parse_cli_opts(argc, argv)) { return 0; }
#if defined(RAPI_DUMMY) || defined(WAPI_DUMMY)
gCLIOpts.headless = true;
#endif
#ifdef _WIN32
// handle Windows console
if (gCLIOpts.console || gCLIOpts.headless) {
SetConsoleOutputCP(CP_UTF8);
} else {
FreeConsole();
freopen("NUL", "w", stdout);
}
#endif
#ifdef _WIN32
if (gCLIOpts.savePath[0]) {
char portable_path[SYS_MAX_PATH] = {};
sys_windows_short_path_from_mbs(portable_path, SYS_MAX_PATH, gCLIOpts.savePath);
fs_init(portable_path);
} else {
fs_init(sys_user_path());
}
#else
fs_init(gCLIOpts.savePath[0] ? gCLIOpts.savePath : sys_user_path());
#endif
#if !defined(RAPI_DUMMY) && !defined(WAPI_DUMMY)
if (gCLIOpts.headless) {
memcpy(&WAPI, &gfx_dummy_wm_api, sizeof(struct GfxWindowManagerAPI));
memcpy(&RAPI, &gfx_dummy_renderer_api, sizeof(struct GfxRenderingAPI));
}
#endif
configfile_load();
legacy_folder_handler();
// create the window almost 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, keyboard_on_text_editing);
WAPI.set_scroll_callback(mouse_on_scroll);
}
// render the rom setup screen
if (!main_rom_handler()) {
#ifdef LOADING_SCREEN_SUPPORTED
if (!gCLIOpts.hideLoadingScreen) {
render_rom_setup_screen(); // holds the game load until a valid rom is provided
} else
#endif
{
printf("ERROR: could not find valid vanilla us sm64 rom in game's user folder\n");
return 0;
}
}
// start the thread for setting up the game
#ifdef LOADING_SCREEN_SUPPORTED
bool threadSuccess = false;
if (!gCLIOpts.hideLoadingScreen && !gCLIOpts.headless) {
if (init_thread_handle(&gLoadingThread, main_game_init, NULL, NULL, 0) == 0) {
render_loading_screen(); // render the loading screen while the game is setup
threadSuccess = true;
destroy_mutex(&gLoadingThread);
}
}
if (!threadSuccess)
#endif
{
main_game_init(NULL); // failsafe incase threading doesn't work
}
// initialize sm64 data and controllers
thread5_game_loop(NULL);
// initialize sound outside threads
if (gCLIOpts.headless) audio_api = &audio_null;
#if defined(AAPI_SDL1) || defined(AAPI_SDL2)
if (!audio_api && audio_sdl.init()) audio_api = &audio_sdl;
#endif
if (!audio_api) audio_api = &audio_null;
// Initialize the audio thread if possible.
// init_thread_handle(&gAudioThread, audio_thread, NULL, NULL, 0);
#ifdef LOADING_SCREEN_SUPPORTED
loading_screen_reset();
#endif
// initialize djui
djui_init();
djui_unicode_init();
djui_init_late();
djui_console_message_dequeue();
show_update_popup();
// initialize 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 || gCLIOpts.coopnet) {
if (gCLIOpts.network == NT_SERVER) {
configNetworkSystem = NS_SOCKET;
configHostPort = gCLIOpts.networkPort;
} else {
configNetworkSystem = NS_COOPNET;
snprintf(configPassword, MAX_CONFIG_STRING, "%s", gCLIOpts.coopnetPassword);
}
// 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;
extern void djui_panel_do_host(bool reconnecting, bool playSound);
djui_panel_do_host(NULL, false);
} else {
network_init(NT_NONE, false);
}
// main loop
while (true) {
debug_context_reset();
CTX_BEGIN(CTX_TOTAL);
WAPI.main_loop(produce_one_frame);
#ifdef DISCORD_SDK
discord_update();
#endif
mumble_update();
#ifdef DEBUG
fflush(stdout);
fflush(stderr);
#endif
CTX_END(CTX_TOTAL);
#ifdef DEVELOPMENT
djui_ctx_display_update();
#endif
djui_lua_profiler_update();
}
return 0;
}