Lephisto/src/lephisto.c

698 lines
19 KiB
C

/**
* @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 <SDL/SDL.h>
/* Global. */
#include <string.h>
#if defined(LINUX) && !defined(DEBUGGING)
#include <signal.h>
#include <execinfo.h>
#include <stdlib.h>
#include <unistd.h>
#endif /* defined(LINUX) && !defined(DEBUGGING) */
/* 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 "gui.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 */
/**
* @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);
}
/**
* @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);
}
/**
* @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();
}
/**
* @brief Frees the loading screen.
*/
static void loadscreen_unload(void) {
/* Free the textures. */
gl_freeTexture(loading);
loading = NULL;
}
/**
* @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. */
}
/**
* @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;
/**
* @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);
}
/**
* @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);
}
/**
* @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();
gui_renderBG(dt);
weapons_render(WEAPON_LAYER_BG, dt);
/* N. */
pilots_render();
weapons_render(WEAPON_LAYER_FG, dt);
spfx_render(SPFX_LAYER_BACK);
/* FG. */
player_render(dt);
spfx_render(SPFX_LAYER_FRONT);
space_renderOverlay(dt);
gui_render(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 / dt_mod;
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);
}
/**
* @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. */
/**
* @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;
}
/**
* @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(DEBUGGING)
/**
* @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(DEBUGGING)
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(DEBUGGING) */
}