/** * @mainpage LEPHISTO * * Doxygen documentation for the Lephisto project. (c) SaraCraft. */ /** * @file lephisto.c * * @brief Control the overall game flow: data loading/unloading and game loop. */ #include /* Global. */ #include #if defined(LINUX) && !defined(NODEBUG) #include #include #include #include #endif /* defined(LINUX) && !defined(NODEBUG) */ /* Local. */ #include "lephisto.h" #include "conf.h" #include "log.h" #include "physics.h" #include "opengl.h" #include "font.h" #include "ship.h" #include "pilot.h" #include "player.h" #include "input.h" #include "joystick.h" #include "space.h" #include "rng.h" #include "ai.h" #include "outfit.h" #include "ldata.h" #include "weapon.h" #include "faction.h" #include "lxml.h" #include "pause.h" #include "toolkit.h" #include "pilot.h" #include "sound.h" #include "music.h" #include "spfx.h" #include "economy.h" #include "menu.h" #include "mission.h" #include "llua_misn.h" #include "lfile.h" #include "unidiff.h" #include "nebulae.h" #define CONF_FILE "conf" /**< Configuration file by default. */ #define VERSION_FILE "VERSION" /**< Version file by default. */ #define VERSION_LEN 10 /**< Max length of the version file. */ #define FONT_SIZE 12 /**< Normal font size. */ #define FONT_SIZE_SMALL 10 /**< Small font size. */ #define LEPHISTO_INIT_DELAY 3000 /**< Minimum amount of time to wait with loading screen. */ static int quit = 0; /**< For primary loop. */ static unsigned int time = 0; /**< Used to calculate FPS and movement, in pause.c */ static char version[VERSION_LEN]; /**< Contains version. */ static glTexture* loading; /**< Loading screen. */ /* Some defaults. */ int nosound = 0; /**< Disable sound when loaded. */ int show_fps = 1; /**< Shows fps - default true. */ int max_fps = 0; /**< Default FPS limit, 0 is no limit. */ int indjoystick = -1; /**< Index of joystick to use, -1 is none. */ char* namjoystick = NULL; /**< Name of joystick to use, NULL is none. */ /* Prototypes. */ /* Loading. */ static void print_SDLversion(void); static void loadscreen_load(void); void loadscreen_render(double done, const char* msg); /* nebulae.c */ static void loadscreen_unload(void); static void load_all(void); static void unload_all(void); static void display_fps(const double dt); static void window_caption(void); static void debug_sigInit(void); /* Update. */ static void fps_control(void); static void update_all(void); static void update_routine(double dt); static void render_all(void); /* Misc. */ void main_loop(void); /* dialogue.c */ /** * @fn int main(int argc, char** argv) * * @brief The entry point of Lephisto. * * @param[in] argc Number of arguments. * @param[in] argv Array of argc arguments. * @return EXIT_SUCCESS on success. */ #ifdef WIN32 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { int argc = 0; char *argv[] = { NULL }; #else int main(int argc, char** argv) { #endif char buf[PATH_MAX]; /* Print the version. */ snprintf(version, VERSION_LEN, "%d.%d.%d", VMAJOR, VMINOR, VREV); LOG("\n "APPNAME" - Dark Tides v%s", version); LOG(" ----------------------------\n"); /* Initialize SDL for possible warnings. */ SDL_Init(0); /* Set up debug signal handlers. */ debug_sigInit(); /* Create the home directory if needed. */ if(lfile_dirMakeExist("%s", lfile_basePath())) WARN("Unable to create lephisto directory '%s'", lfile_basePath()); /* Must be initialized befre input init is called. */ if(SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { WARN("Unable to initialize SDL video: %s", SDL_GetError()); return -1; } /* Input must be initialized for config to work. */ input_init(); /* Set the configuration. */ snprintf(buf, PATH_MAX, "%s"CONF_FILE, lfile_basePath()); conf_setDefaults(); /* Default config values. */ conf_loadConfig(buf); /* Have Lua parse config. */ conf_parseCLI(argc, argv); /* Parse CLI arguments. */ /* Open the data. */ if(ldata_open() != 0) ERR("Failed to open ldata."); /* Load the data basics. */ LOG(" %s", ldata_name()); DEBUG(); /* Display the SDL Version. */ print_SDLversion(); DEBUG(); /* Random numbers. */ rng_init(); /* OpenGL */ if(gl_init()) { /* Init video output. */ ERR("Initializing video output failed, exiting..."); SDL_Quit(); exit(EXIT_FAILURE); } gl_fontInit(NULL, NULL, FONT_SIZE); /* Initializes default font size. */ gl_fontInit(&gl_smallFont, NULL, FONT_SIZE_SMALL); /* Small font. */ window_caption(); /* Display the load screen. */ loadscreen_load(); loadscreen_render(0., "Initializing subsystems..."); time = SDL_GetTicks(); /* Input. */ if((indjoystick >= 0) || (namjoystick != NULL)) { if(joystick_init()) WARN("Error initializing joystick input"); if(namjoystick != NULL) { /* Use a joystick name to find joystick. */ if(joystick_use(joystick_get(namjoystick))) { WARN("Failure to open any joystick, falling back to default keybinds"); input_setDefault(); } free(namjoystick); } else if(indjoystick >= 0) /* Must be using an id instead. */ if(joystick_use(indjoystick)) { WARN("Failure to open any joystick, falling back to default keybinds"); input_setDefault(); } } /* OpenAL sound. */ if(nosound) { LOG("Sound is disabled!"); sound_disabled = 1; music_disabled = 1; } if(sound_init()) WARN("Problem setting up sound!"); music_choose("load"); /* Misc. */ if(ai_init()) WARN("Error initializing AI"); /* Misc graphics init stuff. */ if(nebu_init() != 0) { /* Init the nebulae. */ /* An error happened. */ ERR("Unable to init the Nebulae subsystem!"); /* Weirdness will accur. */ } gui_init(); /* Init the GUI crap. */ toolkit_init(); /* Init the toolkit. */ /* Data loading. */ load_all(); /* Unload load screen. */ loadscreen_unload(); /* Start menu. */ menu_main(); /* Force a minimum delay with loading screen. */ if((SDL_GetTicks() - time) < LEPHISTO_INIT_DELAY) SDL_Delay(LEPHISTO_INIT_DELAY - (SDL_GetTicks() - time)); time = SDL_GetTicks(); /* Init the time. */ /* Main loop. */ SDL_Event event; /* flushes the event loop, since I notices that when the joystick is loaded, it */ /* creates button events that results in the player starting out accelerating. */ while(SDL_PollEvent(&event)); while(!quit) { /* Event loop. */ while(SDL_PollEvent(&event)) { if(event.type == SDL_QUIT) quit = 1; /* Handle quit. */ input_handle(&event); /* handles all the events the player keybinds. */ } main_loop(); } /* Clean up some stuff. */ player_cleanup(); /* Cleans up the player stuff. */ gui_free(); /* Free up the gui. */ weapon_exit(); /* Destroy all active weapons. */ pilots_free(); /* Free the pilots, they where locked up D: */ /* Unload data. */ unload_all(); /* Cleanup opengl fonts. */ gl_freeFont(NULL); gl_freeFont(&gl_smallFont); /* Close data. */ ldata_close(); /* Exit subsystems. */ toolkit_exit(); /* Kill the toolkit. */ ai_exit(); /* Stop the Lua AI magicness. */ joystick_exit(); /* Release joystick. */ input_exit(); /* Clean up keybindings. */ nebu_exit(); /* Destroys the nebulae. */ gl_exit(); /* Kills video output. */ sound_exit(); /* Kills the sound. */ SDL_Quit(); /* Quits SDL. */ /* All is good. */ exit(EXIT_SUCCESS); } /** * @fn void loadscreen_load(void) * * @brief Display a loading screen. */ void loadscreen_load(void) { unsigned int i; char file_path[PATH_MAX]; char** loadscreens; uint32_t nload; /* Count the loading screens. */ loadscreens = ldata_list("../gfx/loading/", &nload); /* Must have loading screens. */ if(nload == 0) { WARN("No loading screens found!"); return; } /* Load the texture. */ snprintf(file_path, PATH_MAX, "../gfx/loading/%s", loadscreens[RNG_SANE(0, nload-1)]); loading = gl_newImage(file_path, 0); /* Clean up. */ for(i = 0; i < nload; i++) free(loadscreens[i]); free(loadscreens); } /** * @fn void loadscreen_render(double done, const char* msg) * * @brief Render the load screen with message. * @param done Amount done (1. == completed). * @param msg Loading screen message. */ void loadscreen_render(double done, const char* msg) { double x, y, w, h, rh; /* Clear background. */ glClear(GL_COLOR_BUFFER_BIT); /* Draw loading screen image. */ gl_blitScale(loading, 0., 0., SCREEN_W, SCREEN_H, NULL); /* Draw progress bar. */ w = gl_screen.w * 0.4; h = gl_screen.h * 0.02; rh = h + gl_defFont.h + 4.; x = -w/2.; y = -h/2.; /* BG. */ ACOLOUR(cBlack, 0.7); glBegin(GL_QUADS); glVertex2d(x-2., y-2. + 0.); glVertex2d(x-2., y+2. + rh); glVertex2d(x-2. + w+4., y+2. + rh); glVertex2d(x-2. + w+4., y-2. + 0.); glEnd(); /* FG. */ ACOLOUR(cConsole, 0.7); glBegin(GL_QUADS); glVertex2d(x, y + 0.); glVertex2d(x, y + h); glVertex2d(x + done*w, y + h); glVertex2d(x + done*w, y + 0.); glEnd(); /* Draw text. */ gl_print(&gl_defFont, x + gl_screen.w/2., y + gl_screen.h/2 + 2. + h, &cConsole, msg); /* Flip buffers. */ SDL_GL_SwapBuffers(); } /** * @fn static void loadscreen_unload(void) * * @brief Frees the loading screen. */ static void loadscreen_unload(void) { /* Free the textures. */ gl_freeTexture(loading); loading = NULL; } /** * @fn void load_all(void) * * @brief Load all the data, makes main() simpler. */ #define LOADING_STAGES 9. /**< Amount of loading stages. */ void load_all(void) { /* Ordering of these is very important as they are interdependent. */ loadscreen_render(1./LOADING_STAGES, "Loading the pilot's food surplus..."); commodity_load(); loadscreen_render(2./LOADING_STAGES, "Loading the powers that be..."); factions_load(); /* Dep for fleet, space, missions. */ loadscreen_render(3./LOADING_STAGES, "Loading things to do..."); missions_load(); /* No dep. */ loadscreen_render(4./LOADING_STAGES, "Loading firework displays..."); spfx_load(); loadscreen_render(5./LOADING_STAGES, "Loading laser beams..."); outfit_load(); loadscreen_render(6./LOADING_STAGES, "Loading floating homes..."); ships_load(); loadscreen_render(7./LOADING_STAGES, "Loading rich snobs..."); fleet_load(); loadscreen_render(8./LOADING_STAGES, "Loading the entire universe"); space_load(); loadscreen_render(1./LOADING_STAGES, "Load Complete!!"); xmlCleanupParser(); /* Only needed to be run after all the loading is done. */ } /** * @fn void unload_all(void) * * @brief Unloads all data, simplifies main(). */ void unload_all(void) { /* Data unloading - Inverse load_all is a good order. */ economy_destroy(); /* Must be called before space_exit. */ space_exit(); /* Cleans up the universe. */ fleet_free(); ships_free(); outfit_free(); spfx_free(); /* Remove the special effects. */ missions_free(); factions_free(); commodity_free(); var_cleanup(); /* Clean up mission variables. */ } /** * @brief Split main loop from main() for secondary loop hack in toolkit.c. */ void main_loop(void) { glClear(GL_COLOR_BUFFER_BIT); fps_control(); /* Everyone loves fps control.. */ sound_update(); /* Update sounds. */ if(toolkit) toolkit_update(); /* To simulate key repetition. */ if(!menu_isOpen(MENU_MAIN)) { if(!paused) update_all(); /* Update game. */ render_all(); } if(toolkit) toolkit_render(); gl_checkErr(); /* Check error every loop. */ SDL_GL_SwapBuffers(); } static double fps_dt = 1.; /**< Display fps accumulator. */ static double cur_dt = 0.; /**< Current deltatick. */ /** * @brief Controls the FPS. */ static void fps_control(void) { unsigned int t; double delay; /* dt in s. */ t = SDL_GetTicks(); cur_dt = (double)(t - time); /* Get the elapsed ms. */ cur_dt *= dt_mod; /* Apply the modifier. */ cur_dt /= 1000.; /* Convert to seconds. */ time = t; if(paused) SDL_Delay(10); /* Drop paused FPS to be nice to the CPU. */ /* If the fps is limited.. */ if((max_fps != 0) && (cur_dt < 1./max_fps)) { delay = 1./max_fps - cur_dt; SDL_Delay((unsigned int)(delay*1000)); fps_dt += delay; /* Make sure it displays the propper FPS. */ } } static const double fps_min = 1./50.0; /** * @fn static void update_all(void) * * @brief Updates the game itself (player flying around etc). */ static void update_all(void) { double tmpdt; if(cur_dt > 0.25/**dt_mod*/) { /* Slow timers down and rerun calculations */ pause_delay((unsigned int)cur_dt*1000.); return; } /* We'll force a minimum of 50 FPS. */ else if(cur_dt > fps_min) { tmpdt = cur_dt - fps_min; pause_delay((unsigned int)(tmpdt*1000)); update_routine(fps_min); /* Run as many cycles of dt=fps_min as needed. */ while(tmpdt > fps_min) { pause_delay((unsigned int)(-fps_min*1000)); /* Increment counters */ update_routine(fps_min); tmpdt -= fps_min; } /* Note we don't touch cur_dt so that fps_delay works well. */ update_routine(tmpdt); /* Leftovers. */ } else /* Standard, just update with the last dt. */ update_routine(cur_dt); } /** * @fn static void update_routine(double dt) * * @brief Actually runs the update. * @param[in] dt Current delta tick. */ static void update_routine(double dt) { space_update(dt); weapons_update(dt); spfx_update(dt); pilots_update(dt); missions_update(dt); } /** * @fn static void render_all(void) * * @brief Renders the game itself (player flying around etc). * * Blitting order. (layers) * * - BG * -- Stars and planets. * -- Background player stuff (planet targetting) * -- Background particles. * -- Back layer weapons. * - N * -- NPC ships. * -- Front layer weapons. * -- Normal layer particles (above ships). * - FG * -- Player. * -- Foreground particles. * -- Text and GUI. */ static void render_all(void) { double dt; dt = (paused) ? 0. : cur_dt; /* Setup. */ spfx_start(dt); /* BG. */ space_render(dt); planets_render(); player_renderBG(); weapons_render(WEAPON_LAYER_BG, dt); /* N. */ pilots_render(); weapons_render(WEAPON_LAYER_FG, dt); spfx_render(SPFX_LAYER_BACK); /* FG. */ player_render(); spfx_render(SPFX_LAYER_FRONT); space_renderOverlay(dt); player_renderGUI(dt); display_fps(dt); /* Exception. */ } static double fps = 0.; /**< FPS to finally display. */ static double fps_cur = 0.; /**< FPS accumulator to trigger change. */ /** * @brief Displays FPS on the screen. * * @param[in] dt Current delta tick. */ static void display_fps(const double dt) { double x, y; fps_dt += dt; fps_cur += 1.; if(fps_dt > 1.) { /* Recalculate every second. */ fps = fps_cur / fps_dt; fps_dt = fps_cur = 0.; } x = 10.; y = (double)(gl_screen.h - 20); if(show_fps) { gl_print(NULL, x, y, NULL, "%3.2f", fps); y -= gl_defFont.h + 5.; } if(dt_mod != 1.) gl_print(NULL, x, y, NULL, "%3.1fx", dt_mod); } /** * @fn static void window_caption(void) * * @brief Set the window caption. */ static void window_caption(void) { char buf[PATH_MAX]; snprintf(buf, PATH_MAX, APPNAME" - %s", ldata_name()); SDL_WM_SetCaption(buf, NULL); } static char human_version[50]; /**< Stores human readable version string. */ /** * @fn char* lephisto_version(void) * * @brief Return the version in a human readable string. * * @return The human readable version string. */ char* lephisto_version(void) { if(human_version[0] == '\0') snprintf(human_version, 50, " "APPNAME" v%s - %s", version, ldata_name()); return human_version; } /** * @fn static void print print_SDLversion. * * @bief Print the SDL version to console. */ static void print_SDLversion(void) { const SDL_version* linked; SDL_version compiled; SDL_VERSION(&compiled); linked = SDL_Linked_Version(); DEBUG("SDL: %d.%d.%d [compiled: %d.%d.%d]", linked->major, linked->minor, linked->patch, compiled.major, compiled.minor, compiled.patch); /* Check if major/minor version differ. */ if((linked->major*100 + linked->minor) > compiled.major*100 + compiled.minor) WARN("SDL is newer than compiled version."); if((linked->major*100 + linked->minor) < compiled.major*100 + compiled.minor) WARN("SDL is older than compiled version."); } #if defined(LINUX) && !defined(NODEBUG) /** * @brief Get the string related to the signal code. * @param sig Signal to which code belongs. * @param sig_code Signal code to get string of. * @return String of signal code. */ static const char* debug_sigCodeToStr(int sig, int sig_code) { if(sig == SIGFPE) switch(sig_code) { case SI_USER: return "SIGFPE (raised by program)"; case FPE_INTDIV: return "SIGFPE (integer divide by zero)"; case FPE_INTOVF: return "SIGFPE (integer overflow)"; case FPE_FLTDIV: return "SIGFPE (floating-point divide by zero)"; case FPE_FLTOVF: return "SIGFPE (floating-point overflow)"; case FPE_FLTUND: return "SIGFPE (floating-point underflow)"; case FPE_FLTRES: return "SIGFPE (floating-point inexact result)"; case FPE_FLTINV: return "SIGFPE (floating-point invalid operation)"; case FPE_FLTSUB: return "SIGFPE (subscript out of range)"; default: return "SIGFPE"; } else if(sig == SIGSEGV) switch(sig_code) { case SI_USER: return "SIGSEGV (raised by program)"; case SEGV_MAPERR: return "SIGEGV (address not mapped to object)"; case SEGV_ACCERR: return "SIGEGV (invalid permissions for mapped object)"; default: return "SIGSEGV"; } else if(sig == SIGABRT) switch(sig_code) { case SI_USER: return "SIGABRT (raised by program)"; default: return "SIGABRT"; } /* No suitable code found. */ return strsignal(sig); } /** * @brief Backtrace signal handler for linux. * @param sig Signal. * @param info Signal information. * @param unused Unused. */ static void debug_sigHandler(int sig, siginfo_t* info, void* unused) { (void) sig; (void) unused; int i, num; void* buf[64]; char** symbols; num = backtrace(buf, 64); symbols = backtrace_symbols(buf, num); DEBUG("LEPHISTO recieved %s!", debug_sigCodeToStr(info->si_signo, info->si_code)); for(i = 0; i < num; i++) DEBUG(" %s", symbols[i]); DEBUG("Report this to project maintainer with the backtrace."); exit(1); } #endif /* defined(LINUX) && !defined(DEBUG) */ /** * @brief Set up the SignalHandler for Linux. */ static void debug_sigInit(void) { #if defined(LINUX) && !defined(NODEBUG) struct sigaction sa, so; /* Set up handler. */ sa.sa_handler = NULL; sa.sa_sigaction = debug_sigHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; /* Attach signals. */ sigaction(SIGSEGV, &sa, &so); if(so.sa_handler == SIG_IGN) DEBUG("Unable to set up SIGSEGV signal handler."); sigaction(SIGFPE, &sa, &so); if(so.sa_handler == SIG_IGN) DEBUG("Unable to set up SIGFPE signal handler."); sigaction(SIGABRT, &sa, &so); if(so.sa_handler == SIG_IGN) DEBUG("Unable to get set up SIGABRT signal handler."); #endif /* #if defined(LINUX) && !defined(NODEBUG) */ }