#include #include #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 "pack.h" #include "weapon.h" #include "faction.h" #include "xml.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 "misn_lua.h" #include "lfile.h" #include "nebulae.h" #define XML_START_ID "Start" #define START_DATA "../dat/start.xml" #define CONF_FILE "conf" #define VERSION_FILE "VERSION" #define VERSION_LEN 10 #define MINIMUM_FPS 0.5 #define FONT_SIZE 12 #define FONT_SIZE_SMALL 10 #define LEPHISTO_INIT_DELAY 3000 /* Minimum amount of time to wait with loading screen. */ static int quit = 0; /* Primary loop. */ unsigned int gtime = 0; /* Calculate FPS and movement. */ static char version[VERSION_LEN]; /* Just some default crap. */ char* data = NULL; char dataname[DATA_NAME_LEN] = ""; int nosound = 0; int show_fps = 1; /* Default - True. */ int max_fps = 0; int indjoystick = -1; char* namjoystick = NULL; /* Prototypes. */ static void load_screen(void); static void load_all(void); static void unload_all(void); void main_loop(void); static void display_fps(const double dt); static void window_caption(void); static void data_name(void); /* Update. */ static void fps_control(void); static void update_all(void); static void update_routine(double dt); static void render_all(void); /** @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(" "APPNAME" v%s", version); /* Initialize SDL for possible warnings. */ SDL_Init(0); /* Create the home directory if needed. */ if(lfile_dirMakeExist(".")) WARN("Unable to create lephisto directory '%s'", lfile_basePath()); /* 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. */ /* Load the data basics. */ data_name(); LOG(" %s", dataname); DEBUG(); /* Random numbers. */ rng_init(); /* OpenGL */ if(gl_init()) { /* Init video output. */ ERR("Initializing video output failed, exiting..."); SDL_Quit(); exit(EXIT_FAILURE); } window_caption(); load_screen(); gtime = SDL_GetTicks(); /* OpenAL sound. */ if(nosound) LOG("Sound is disabled!"); else { if(sound_init()) WARN("Problem setting up sound!"); music_choose("load"); } /* 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(); } } /* Misc. */ if(ai_init()) WARN("Error initializing AI"); /* Misc openGL init stuff. */ nebu_init(); /* Init the nebulae. */ gl_fontInit(NULL, NULL, FONT_SIZE); /* Init default font size. */ gl_fontInit(&gl_smallFont, NULL, FONT_SIZE_SMALL); /* Small font. */ gui_init(); /* Init the GUI crap. */ toolkit_init(); /* Init the toolkit. */ /* Data loading. */ load_all(); /* Start menu. */ menu_main(); /* Force a minimum delay with loading screen. */ if((SDL_GetTicks() - gtime) < LEPHISTO_INIT_DELAY) SDL_Delay(LEPHISTO_INIT_DELAY - (SDL_GetTicks() - gtime)); gtime = 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. */ space_exit(); /* Clean up the universe!!! */ pilots_free(); /* Free the pilots, they where locked up D: */ space_exit(); /* Cleans up the universe itself. */ /* Unload data. */ unload_all(); /* Cleanup opengl fonts. */ gl_freeFont(NULL); gl_freeFont(&gl_smallFont); /* 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); } /** * @brief Display a loading screen. */ void load_screen(void) { int i; glTexture* tex; char file_path[PATH_MAX]; char** files; uint32_t nfiles; size_t len; int nload; /* Count the loading screens. */ files = pack_listfiles(data, &nfiles); len = strlen("../gfx/loading"); nload = 0; for(i = 0; i < (int)nfiles; i++) { if(strncmp(files[i], "../gfx/loading", len)==0) nload++; free(files[i]); } /* Must have loading screens. */ if(nload == 0) { WARN("No loading screens found!"); return; } /* Load the texture. */ snprintf(file_path, PATH_MAX, "../gfx/loading%03d.png", RNG(0, nload-1)); tex = gl_newImage(file_path); /* Draw once, won't be redrawn. */ glClear(GL_COLOR_BUFFER_BIT); gl_blitScale(tex, 0., 0., SCREEN_W, SCREEN_H, NULL); SDL_GL_SwapBuffers(); /* Free the textures. */ gl_freeTexture(tex); } /** * @brief Load all the data, makes main() simpler. */ void load_all(void) { /* Ordering of these is very important as they are interdependent. */ commodity_load(); factions_load(); /* Dep for fleet, space, missions. */ missions_load(); /* No dep. */ spfx_load(); outfit_load(); ships_load(); fleet_load(); space_load(); } /** * @brief Unloads all data, simplifies main(). */ void unload_all(void) { /* Data unloading - order should not matter, but inverse load_all is good. */ 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) { sound_update(); /* Do sound stuff. */ glClear(GL_COLOR_BUFFER_BIT); fps_control(); /* Everyone loves fps control.. */ 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.; static double cur_dt = 0.; /** * @brief Controls the FPS. */ static void fps_control(void) { unsigned int t; double delay; /* dt in ms/1000. */ t = SDL_GetTicks(); cur_dt = (double)(t-gtime)/1000.; gtime = 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; /** * @brief Updates the game itself (player flying around etc). */ static void update_all(void) { double tmpdt; if(cur_dt > 0.25) { /* 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); } /** * @brief Actually runs the update. */ static void update_routine(double dt) { space_update(dt); weapons_update(dt); spfx_update(dt); pilots_update(dt); } /** * @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. * X * N | NPC ships. * | Front layer weapons. * | Normal layer particles (above ships). * X * FG | Player. * | Foreground particles. * | Text and GUI. */ static void render_all(void) { /* Setup. */ spfx_start(cur_dt); /* BG. */ space_render(cur_dt); planets_render(); player_renderBG(); weapons_render(WEAPON_LAYER_BG); /* N. */ pilots_render(); weapons_render(WEAPON_LAYER_FG); spfx_render(SPFX_LAYER_BACK); /* FG. */ player_render(); spfx_render(SPFX_LAYER_FRONT); space_renderOverlay(cur_dt); player_renderGUI(); display_fps(cur_dt); } static double fps = 0.; static double fps_cur = 0.; /** * @brief Displays FPS on the screen. */ 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); } /* * @brief Set the data module's name. */ static void data_name(void) { uint32_t bufsize; char* buf; /* Check if data file is valid. */ if(pack_check(DATA)) { ERR("Data file '%s' not found", data); WARN("You should specify which data file to use with '-d'"); WARN("See -h or --help for more information"); SDL_Quit(); exit(EXIT_FAILURE); } /* Check the version. */ buf = pack_readfile(DATA, VERSION_FILE, &bufsize); if(strncmp(buf, version, bufsize) != 0) { WARN("Lephisto version and data module version differ!"); WARN("Lephisto is v%s, data is for v%s", version, buf); } free(buf); /* Load the datafiles name. */ buf = pack_readfile(DATA, START_DATA, &bufsize); xmlNodePtr node; xmlDocPtr doc = xmlParseMemory(buf, bufsize); node = doc->xmlChildrenNode; if(!xml_isNode(node, XML_START_ID)) { ERR("Maformed '"START_DATA"' file: missing root element '"XML_START_ID"'"); return; } node = node->xmlChildrenNode; /* First node. */ if(node == NULL) { ERR("Malformed '"START_DATA"' file: does not contain elements"); return; } do { if(xml_isNode(node, "name")) { strncpy(dataname, xml_get(node), DATA_NAME_LEN); dataname[DATA_NAME_LEN-1] = '\0'; } } while((node = node->next)); xmlFreeDoc(doc); free(buf); xmlCleanupParser(); } /* * @brief Set the window caption. */ static void window_caption(void) { char tmp[DATA_NAME_LEN+10]; snprintf(tmp, DATA_NAME_LEN+10, APPNAME" - %s", dataname); SDL_WM_SetCaption(tmp, NULL); } static char human_version[50]; /** * @brief Return the version in a human readable string. */ char* lephisto_version(void) { if(human_version[0] == '\0') snprintf(human_version, 50, " "APPNAME" v%s - %s", version, dataname); return human_version; }