/*(GPL) ------------------------------------------------------------ Kobo Deluxe - An enhanced SDL port of XKobo ------------------------------------------------------------ * Copyright (C) 1995, 1996 Akira Higuchi * Copyright (C) 2001-2003, 2005-2007 David Olofson * Copyright (C) 2005 Erik Auerswald * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. */ #define DBG(x) x #undef DEBUG_OUT // Use this to benchmark and create a new percentage table! #undef TIME_PROGRESS #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include "audio.h" extern "C" { /*For the oscilloscope*/ #include "a_globals.h" /*For VU and voice info*/ #include "a_struct.h" } #endif #include "kobolog.h" #include "config.h" #include "kobo.h" #include "states.h" #include "screen.h" #include "manage.h" #include "score.h" #include "gamectl.h" #include "random.h" #include "version.h" #include "options.h" #include "myship.h" #include "enemies.h" #include "prof.h" #include "bench.h" #include "httpclient.h" #define MAX_FPS_RESULTS 64 /* Joystick support */ #define DEFAULT_JOY_LR 0 // Joystick axis left-right default #define DEFAULT_JOY_UD 1 // Joystick axis up-down default #define DEFAULT_JOY_FIRE 0 // Default fire button on joystick #define DEFAULT_JOY_START 1 /*---------------------------------------------------------- Singletons ----------------------------------------------------------*/ KOBO_sound sound; /*---------------------------------------------------------- Globals ----------------------------------------------------------*/ filemapper_t *fmap = NULL; prefs_t *prefs = NULL; kobo_gfxengine_t *gengine = NULL; screen_window_t *wscreen = NULL; dashboard_window_t *wdash = NULL; bargraph_t *whealth = NULL; bargraph_t *wtemp = NULL; bargraph_t *wttemp = NULL; radar_map_t *wmap = NULL; radar_window_t *wradar = NULL; window_t *wmain = NULL; display_t *dhigh = NULL; display_t *dscore = NULL; display_t *dstage = NULL; display_t *dships = NULL; RGN_region *logo_region = NULL; int mouse_x = 0; int mouse_y = 0; int mouse_left = 0; int mouse_middle = 0; int mouse_right = 0; int exit_game = 0; static int main_init() { prefs = new prefs_t; fmap = new filemapper_t; gengine = new kobo_gfxengine_t; return 0; } static void main_cleanup() { delete gengine; gengine = NULL; delete fmap; fmap = NULL; delete prefs; prefs = NULL; } /*---------------------------------------------------------- Various functions ----------------------------------------------------------*/ static void setup_dirs(char *xpath) { fmap->exepath(xpath); fmap->addpath("DATA", KOBO_DATA_DIR); /* * Graphics data */ /* Current dir; from within the build tree */ fmap->addpath("GFX", "./data/gfx"); /* Real data dir */ fmap->addpath("GFX", "DATA>>gfx"); /* Current dir */ fmap->addpath("GFX", "./gfx"); /* * Sound data */ /* Current dir; from within the build tree */ fmap->addpath("SFX", "./data/sfx"); /* Real data dir */ fmap->addpath("SFX", "DATA>>sfx"); /* Current dir */ fmap->addpath("SFX", "./sfx"); /* * Score files (user and global) */ fmap->addpath("SCORES", KOBO_SCORE_DIR); /* 'scores' in current dir (For importing scores, perhaps...) */ // (Disabled for now, since filemapper_t can't tell // when it hits the same dir more than once...) // fmap->addpath("SCORES", "./scores"); /* * Configuration files */ fmap->addpath("CONFIG", KOBO_CONFIG_DIR); /* System local */ fmap->addpath("CONFIG", SYSCONF_DIR); /* In current dir (last resort) */ fmap->addpath("CONFIG", "./"); } static void add_dirs(prefs_t *p) { char buf[300]; if(p->dir[0]) { char *upath = fmap->sys2unix(p->dir); snprintf(buf, 300, "%s/sfx", upath); fmap->addpath("SFX", buf, 1); snprintf(buf, 300, "%s/gfx", upath); fmap->addpath("GFX", buf, 1); snprintf(buf, 300, "%s/scores", upath); fmap->addpath("SCORES", buf, 1); snprintf(buf, 300, "%s", upath); fmap->addpath("CONFIG", buf, 0); } if(p->sfxdir[0]) fmap->addpath("SFX", fmap->sys2unix(p->sfxdir), 1); if(p->gfxdir[0]) fmap->addpath("GFX", fmap->sys2unix(p->gfxdir), 1); if(p->scoredir[0]) fmap->addpath("SCORES", fmap->sys2unix(p->scoredir), 1); } #ifdef DEBUG static void draw_osc(int mode) { int mx = oscframes; if(mx > wmain->width()) mx = wmain->width(); int yo = wmain->height() - 40; wmain->foreground(wmain->map_rgb(0x000099)); wmain->fillrect(0, yo, wmain->width(), 1); wmain->foreground(wmain->map_rgb(0x990000)); wmain->fillrect(0, yo - 32, wmain->width(), 1); wmain->fillrect(0, yo + 31, wmain->width(), 1); switch(mode) { case 0: wmain->foreground(wmain->map_rgb(0x009900)); for(int s = 0; s < mx; ++s) wmain->point(s, ((oscbufl[s]+oscbufr[s]) >> 11) + yo); break; case 1: wmain->foreground(wmain->map_rgb(0x009900)); for(int s = 0; s < mx; ++s) wmain->point(s, (oscbufl[s] >> 10) + yo); wmain->foreground(wmain->map_rgb(0xcc0000)); for(int s = 0; s < mx; ++s) wmain->point(s, (oscbufr[s] >> 10) + yo); break; case 2: wmain->foreground(wmain->map_rgb(0x009900)); for(int s = 0; s < mx; ++s) wmain->point(s>>1, (oscbufl[s] >> 10) + yo); wmain->foreground(wmain->map_rgb(0xcc0000)); for(int s = 0; s < mx; ++s) wmain->point((s>>1) + (mx>>1), (oscbufr[s] >> 10) + yo); wmain->fillrect(mx/2, yo-34, 1, 68); break; } wmain->foreground(wmain->map_rgb(0xcc0000)); wmain->fillrect(0, yo - 32, 6, limiter.attenuation >> 11); } static void draw_vu(void) { int xo = (wmain->width() - 5 * AUDIO_MAX_VOICES) / 2; int yo = wmain->height() - 50; wmain->foreground(wmain->map_rgb(0x000000)); for(int s = 4; s < 40; s += 4) wmain->fillrect(xo, yo+s, 5 * AUDIO_MAX_VOICES-1, 1); wmain->foreground(wmain->map_rgb(0x333333)); wmain->fillrect(xo-1, yo, 5 * AUDIO_MAX_VOICES+1, 1); wmain->fillrect(xo-1, yo+40, 5 * AUDIO_MAX_VOICES+1, 4); for(int s = 0; s <= AUDIO_MAX_VOICES; ++s) wmain->fillrect(xo + s*5 - 1, yo+1, 1, 39); for(int s = 0; s < AUDIO_MAX_VOICES; ++s) { int vu, vu2, vumin; if(VS_STOPPED != voicetab[s].state) { wmain->foreground(wmain->map_rgb(0x009900)); wmain->fillrect(xo + 1, yo + 41, 2, 2); vu = labs((voicetab[s].ic[VIC_LVOL].v>>1) + (voicetab[s].ic[VIC_RVOL].v>>1)) >> RAMP_BITS; #ifdef AUDIO_USE_VU vu2 = voicetab[s].vu; voicetab[s].vu = 0; #else vu2 = 0; #endif vu2 = (vu2>>4) * (vu>>4) >> 8; vu2 >>= 11; if(vu2 > 40) vu2 = 40; vu >>= 11; if(vu > 40) vu = 40; vumin = vu < vu2 ? vu : vu2; } else vu = vu2 = vumin = 0; wmain->foreground(wmain->map_rgb(0x006600)); wmain->fillrect(xo, yo + 40 - vu, 4, vu - vu2); wmain->foreground(wmain->map_rgb(0xffcc00)); wmain->fillrect(xo, yo + 40 - vu2, 4, vu2); xo += 5; } } #endif /*---------------------------------------------------------- The main object ----------------------------------------------------------*/ class KOBO_main { public: #ifdef DEBUG static int audio_vismode; #endif static SDL_Joystick *joystick; static int js_lr; static int js_ud; static int js_fire; static int js_start; static FILE *logfile; static Uint32 esc_tick; static int esc_count; static int exit_game_fast; // Frame rate counter static int fps_count; static int fps_starttime; static int fps_nextresult; static int fps_lastresult; static float *fps_results; static display_t *dfps; // Frame rate limiter static float max_fps_filter; static int max_fps_begin; static int xoffs; static int yoffs; // Backup in case we screw up we can't get back up static prefs_t safe_prefs; static int open(); static void close(); static int run(); static int open_logging(prefs_t *p); static void close_logging(); static void load_config(prefs_t *p); static void save_config(prefs_t *p); static void build_screen(); static int init_display(prefs_t *p); static void close_display(); static void show_progress(prefs_t *p); static void progress(); static void doing(const char *msg); static int load_graphics(prefs_t *p); static int load_sounds(prefs_t *p, int render_all = 0); static int init_js(prefs_t *p); static void close_js(); static int escape_hammering(); static int quit_requested(); static void brutal_quit(); static void pause_game(); static void print_fps_results(); }; #ifdef DEBUG int KOBO_main::audio_vismode = 0; #endif SDL_Joystick *KOBO_main::joystick = NULL; int KOBO_main::js_lr = DEFAULT_JOY_LR; int KOBO_main::js_ud = DEFAULT_JOY_UD; int KOBO_main::js_fire = DEFAULT_JOY_FIRE; int KOBO_main::js_start = DEFAULT_JOY_START; FILE *KOBO_main::logfile = NULL; Uint32 KOBO_main::esc_tick = 0; int KOBO_main::esc_count = 0; int KOBO_main::exit_game_fast = 0; int KOBO_main::fps_count = 0; int KOBO_main::fps_starttime = 0; int KOBO_main::fps_nextresult = 0; int KOBO_main::fps_lastresult = 0; float *KOBO_main::fps_results = NULL; display_t *KOBO_main::dfps = NULL; float KOBO_main::max_fps_filter = 0.0f; int KOBO_main::max_fps_begin = 0; int KOBO_main::xoffs = 0; int KOBO_main::yoffs = 0; prefs_t KOBO_main::safe_prefs; static KOBO_main km; void KOBO_main::print_fps_results() { int i, r = fps_nextresult; if(fps_lastresult != MAX_FPS_RESULTS-1) r = 0; for(i = 0; i < fps_lastresult; ++i) { log_printf(ULOG, "%.1f fps\n", fps_results[r++]); if(r >= MAX_FPS_RESULTS) r = 0; } free(fps_results); fps_nextresult = 0; fps_lastresult = 0; fps_results = NULL; } int KOBO_main::escape_hammering() { Uint32 nt = SDL_GetTicks(); if(nt - esc_tick > 300) esc_count = 1; else ++esc_count; esc_tick = nt; return esc_count >= 5; } int KOBO_main::quit_requested() { SDL_Event e; while(SDL_PollEvent(&e)) { switch(e.type) { case SDL_EVENT_QUIT: exit_game_fast = 1; break; case SDL_EVENT_WINDOW_EXPOSED: gengine->invalidate(); break; case SDL_EVENT_KEY_UP: switch(e.key.key) { case SDLK_ESCAPE: if(escape_hammering()) exit_game_fast = 1; break; default: break; } break; } } return exit_game_fast ? 1 : 0; } void KOBO_main::close_logging() { /* Flush logs to disk, close log files etc. */ log_close(); if(logfile) { fclose(logfile); logfile = NULL; } } int KOBO_main::open_logging(prefs_t *p) { close_logging(); if(log_open() < 0) return -1; if(p && p->logfile) switch (p->logformat) { case 2: logfile = fopen("log.html", "wb"); break; default: logfile = fopen("log.txt", "wb"); break; } if(logfile) { log_set_target_stream(0, logfile); log_set_target_stream(1, logfile); } else { log_set_target_stream(0, stdout); log_set_target_stream(1, stderr); } log_set_target_stream(2, NULL); if(p) switch(p->logformat) { default: log_set_target_flags(-1, LOG_TIMESTAMP); break; case 1: log_set_target_flags(-1, LOG_ANSI | LOG_TIMESTAMP); break; case 2: log_set_target_flags(-1, LOG_HTML | LOG_TIMESTAMP); break; } /* All levels output to stdout... */ log_set_level_target(-1, 0); /* ...except these, that output to stderr. */ log_set_level_target(ELOG, 1); log_set_level_target(CELOG, 1); /* Some fancy colors... */ log_set_level_attr(ULOG, LOG_YELLOW); log_set_level_attr(WLOG, LOG_YELLOW | LOG_BRIGHT); log_set_level_attr(ELOG, LOG_RED | LOG_BRIGHT); log_set_level_attr(CELOG, LOG_RED | LOG_BRIGHT | LOG_BLINK); log_set_level_attr(DLOG, LOG_CYAN); log_set_level_attr(D2LOG, LOG_BLUE | LOG_BRIGHT); log_set_level_attr(D3LOG, LOG_BLUE); /* Disable levels as desired */ if(p) switch(p->logverbosity) { case 0: log_set_level_target(ELOG, 2); case 1: log_set_level_target(WLOG, 2); case 2: log_set_level_target(DLOG, 2); case 3: log_set_level_target(D2LOG, 2); case 4: log_set_level_target(D3LOG, 2); case 5: break; } if(p && p->logfile && !logfile) log_printf(ELOG, "Couldn't open log file!\n"); return 0; } void KOBO_main::build_screen() { gengine->clear(0x000000); wdash->place(xoffs, yoffs, SCREEN_WIDTH, SCREEN_HEIGHT); whealth->place(xoffs + 4, yoffs + 92, 8, 128); whealth->bgimage(B_HEALTH_LID, 0); whealth->background(whealth->map_rgb(0x182838)); whealth->redmax(0); wmain->place(xoffs + 8 + MARGIN, yoffs + MARGIN, WSIZE, WSIZE); gengine->output(wmain); dhigh->place(xoffs + 252, yoffs + 4, 64, 18); dhigh->font(B_NORMAL_FONT); dhigh->bgimage(B_HIGH_BACK, 0); dhigh->caption("HIGHSCORE"); dhigh->text("000000000"); dscore->place(dhigh->x(), dhigh->y2() + 4, 64, 18); dscore->font(B_NORMAL_FONT); dscore->bgimage(B_SCORE_BACK, 0); dscore->caption("SCORE"); dscore->text("000000000"); wmap->place(0, 0, MAP_SIZEX, MAP_SIZEY); wmap->offscreen(); wradar->place(xoffs + 244, yoffs + (SCREEN_HEIGHT - MAP_SIZEY) / 2, MAP_SIZEX, MAP_SIZEY); wradar->bgimage(B_RADAR_BACK, 0); wtemp->place(xoffs + 244, yoffs + 188, 4, 32); wtemp->bgimage(B_TEMP_LID, 0); wtemp->background(wtemp->map_rgb(0x182838)); wtemp->redmax(1); wttemp->place(xoffs + 248, yoffs + 188, 4, 32); wttemp->bgimage(B_TTEMP_LID, 0); wttemp->background(wttemp->map_rgb(0x182838)); wttemp->redmax(1); dships->place(xoffs + 264, yoffs + 196, 38, 18); dships->font(B_NORMAL_FONT); dships->bgimage(B_SHIPS_BACK, 0); dships->caption("SHIPS"); dships->text("000"); dstage->place(dships->x(), dships->y2() + 4, 38, 18); dstage->font(B_NORMAL_FONT); dstage->bgimage(B_STAGE_BACK, 0); dstage->caption("STAGE"); dstage->text("000"); if(prefs->cmd_fps) { dfps = new display_t; dfps->init(gengine); dfps->place(0, -9, 48, 18); dfps->color(wdash->map_rgb(0, 0, 0)); dfps->font(B_NORMAL_FONT); dfps->caption("FPS"); } } int KOBO_main::init_display(prefs_t *p) { int dw, dh; // Display size int gw, gh; // Game "window" size gengine->title("Kobo Deluxe " VERSION, "kobodl"); dw = p->width; dh = p->height; if(p->fullscreen) { // This game assumes 1:1 pixel aspect ratio, or 4:3 // width:height ratio, so we need to adjust accordingly. // Note: // This code assumes 1:1 pixels for all resolutions! // This does not hold true for 1280x1024 or a CRT // for example, but that's an incorrect (although // all too common) setup anyway. (1280x1024 is for // 5:4 TFT displays only!) if(dw * 3 >= dh * 4) { // 4:3 or widescreen; Height defines size gw = dh * 4 / 3; gh = dh; } else { // "tallscreen" (probably 5:4 TFT or rotated display) // Width defines size gw = dw; gh = dw * 3 / 4; } } else { gw = dw; gh = dh; } // Scaling has 16ths granularity, so tiles scale properly! gengine->scale((int)((gw * 16 + 8) / SCREEN_WIDTH) / 16.f, (int)((gh * 16 + 8) / SCREEN_HEIGHT) / 16.f); // Read back and recalculate, in case the engine has some ideas... gw = (int)(SCREEN_WIDTH * gengine->xscale() + 0.5f); gh = (int)(SCREEN_HEIGHT * gengine->yscale() + 0.5f); if(!p->fullscreen) { //Add thin black border around the game "screen" in windowed mode. dw = gw + 8; dh = gh + 8; } xoffs = (int)((dw - gw) / 2 / gengine->xscale()); yoffs = (int)((dh - gh) / 2 / gengine->yscale()); gengine->size(dw, dh); gengine->mode(0, p->fullscreen); gengine->vsync(p->vsync); gengine->cursor(0); gengine->period(game.speed); sound.period(game.speed); gengine->timefilter(p->timefilter * 0.01f); gengine->interpolation(p->filter); gengine->scroll_ratio(LAYER_OVERLAY, 0.0, 0.0); gengine->scroll_ratio(LAYER_BULLETS, 1.0, 1.0); gengine->scroll_ratio(LAYER_FX, 1.0, 1.0); gengine->scroll_ratio(LAYER_PLAYER, 1.0, 1.0); gengine->scroll_ratio(LAYER_ENEMIES, 1.0, 1.0); gengine->scroll_ratio(LAYER_BASES, 1.0, 1.0); gengine->wrap(MAP_SIZEX * CHIP_SIZEX, MAP_SIZEY * CHIP_SIZEY); if(gengine->open(ENEMY_MAX) < 0) return -1; gengine->clear(); wscreen = new screen_window_t; wscreen->init(gengine); wscreen->place(0, 0, (int)(gengine->width() / gengine->xscale() + 0.5f), (int)(gengine->height() / gengine->yscale() + 0.5f)); wscreen->border((int)(yoffs * gengine->yscale() + 0.5f), (int)(xoffs * gengine->xscale() + 0.5f), dw - gw - (int)(xoffs * gengine->xscale() + 0.5f), dh - gh - (int)(yoffs * gengine->yscale() + 0.5f)); wdash = new dashboard_window_t; wdash->init(gengine); whealth = new bargraph_t; whealth->init(gengine); wmain = new window_t; wmain->init(gengine); dhigh = new display_t; dhigh->init(gengine); dscore = new display_t; dscore->init(gengine); wmap = new radar_map_t; wmap->init(gengine); wradar = new radar_window_t; wradar->init(gengine); wtemp = new bargraph_t; wtemp->init(gengine); wttemp = new bargraph_t; wttemp->init(gengine); dships = new display_t; dships->init(gengine); dstage = new display_t; dstage->init(gengine); build_screen(); wdash->mode(DASHBOARD_BLACK); return 0; } void KOBO_main::close_display() { delete dfps; dfps = NULL; delete dstage; dstage = NULL; delete dships; dships = NULL; delete wttemp; wttemp = NULL; delete wtemp; wtemp = NULL; delete wradar; wradar = NULL; delete wmap; wmap = NULL; delete dscore; dscore = NULL; delete dhigh; dhigh = NULL; delete wmain; wmain = NULL; delete whealth; whealth = NULL; delete wdash; wdash = NULL; delete wscreen; wscreen = NULL; } void KOBO_main::show_progress(prefs_t *p) { wdash->mode(DASHBOARD_LOADING); wdash->doing(""); } void KOBO_main::progress() { wdash->progress(); } void KOBO_main::doing(const char *msg) { wdash->doing(msg); } #ifndef TIME_PROGRESS static float progtab_graphics[] = { 0.000000, 1.167315, 4.085603, 5.836576, 7.490273, 9.046693, 10.992218, 12.937743, 13.813230, 15.564202, 17.217899, 18.774319, 20.428015, 22.081713, 23.540855, 25.291828, 26.848249, 28.404669, 29.961090, 32.003891, 33.560310, 35.311283, 36.770428, 38.521400, 40.369648, 41.828793, 43.190662, 44.649807, 46.595329, 48.151752, 49.610893, 51.361866, 53.501945, 58.852139, 60.116730, 61.089493, 63.521400, 66.050583, 68.287941, 69.844360, 72.665367, 74.124512, 76.653694, 77.334633, 79.182877, 80.642021, 82.295723, 83.852142, 85.505836, 87.159531, 88.813232, 90.369652, 92.023346, 93.677040, 95.233459, 96.984436, 98.540855, 100.0, 0 }; static float progtab_sounds[] = { 0.000000, 58.709679, 59.516129, 98.790321, 98.790321, 100.0, 0 }; static float progtab_all[] = { 0.000000, 1.556684, 2.639594, 3.282572, 4.060914, 4.737733, 5.380711, 8.697124, 8.764806, 10.016920, 10.761421, 11.472081, 12.216582, 12.893401, 13.604061, 14.416244, 15.093062, 15.262267, 16.074450, 17.428087, 18.680202, 19.932318, 20.879864, 21.759729, 23.011845, 24.365482, 24.534687, 24.839256, 25.651438, 26.429781, 26.700508, 27.241962, 29.678511, 38.477158, 40.203045, 40.372250, 40.913704, 42.571911, 45.211506, 45.922165, 46.565144, 46.903553, 48.866329, 48.967850, 49.306259, 49.847717, 50.389172, 50.964466, 51.641285, 52.115059, 52.690357, 53.231812, 53.773266, 54.382404, 54.991539, 55.499153, 56.006767, 57.495770, 82.673431, 82.978004, 99.763115, 99.796951, 100.0, 0 }; #endif typedef enum { KOBO_CLAMP = 0x0001, // Clamp to frame edge pixels KOBO_CLAMP_OPAQUE = 0x0002, // Clamp to black instead of transparent KOBO_NOALPHA = 0x0004, // Disable alpha channel KOBO_NODITHER = 0x0008, // Disable dithering KOBO_NEAREST = 0x0010, // Force NEAREST scale mode KOBO_CENTER = 0x0020, // Center hotspot in frames KOBO_NOBRIGHT = 0x0040, // Disable brightness/contrast filter KOBO_FONT = 0x0080, // Load as "SFont" rather than tiles! KOBO_MESSAGE = 0x1000 // Not a file! 'path' is a message. } KOBO_GfxDescFlags; typedef struct KOBO_GfxDesc { const char *path; // Path + name (for filemapper) int bank; // Sprite bank ID int w, h; // Frame size (original image pixels) float scale; // Scale of source data rel. 320x240 int flags; // KOBO_GfxDescFlags } KOBO_GfxDesc; static KOBO_GfxDesc gfxdesc[] = { // Loading screen { "Loading loading screen graphics", 0, 0,0, 0.0f, KOBO_MESSAGE }, { "GFX>>loading3.png", B_LOADING, 0, 0, 2.0f, KOBO_CLAMP | KOBO_NOALPHA }, { "GFX>>icefont2.png", B_NORMAL_FONT, 0, 0, 2.0f, KOBO_FONT }, // In-game { "Loading in-game graphics", 0, 0,0, 0.0f, KOBO_MESSAGE }, { "GFX>>tiles-green.png", B_TILES1, 32, 32, 2.0f, KOBO_CLAMP }, { "GFX>>tiles-metal.png", B_TILES2, 32, 32, 2.0f, KOBO_CLAMP }, { "GFX>>tiles-blood.png", B_TILES3, 32, 32, 2.0f, KOBO_CLAMP }, { "GFX>>tiles-double.png", B_TILES4, 32, 32, 2.0f, KOBO_CLAMP }, { "GFX>>tiles-chrome.png", B_TILES5, 32, 32, 2.0f, KOBO_CLAMP }, { "GFX>>flatstars1.png", B_OLDSTARS, 32, 32, 2.0f, KOBO_CLAMP }, { "GFX>>crosshair.png", B_CROSSHAIR, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>player.png", B_PLAYER, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>bmr-green.png", B_BMR_GREEN, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>bmr-purple.png", B_BMR_PURPLE, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>bmr-pink.png", B_BMR_PINK, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>fighter.png", B_FIGHTER, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>missile.png", B_MISSILE1, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>missile2.png", B_MISSILE2, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>missile3.png", B_MISSILE3, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>bolt.png", B_BOLT, 16, 16, 2.0f, KOBO_CENTER }, { "GFX>>boltexpl.png", B_BOLTEXPL, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>explo1e.png", B_EXPLO1, 48, 48, 2.0f, KOBO_CENTER }, { "GFX>>explo3e.png", B_EXPLO3, 64, 64, 2.0f, KOBO_CENTER }, { "GFX>>explo4e.png", B_EXPLO4, 64, 64, 2.0f, KOBO_CENTER }, { "GFX>>explo5e.png", B_EXPLO5, 64, 64, 2.0f, KOBO_CENTER }, { "GFX>>rock1c.png", B_ROCK1, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>rock2.png", B_ROCK2, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>shinyrock.png", B_ROCK3, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>rockexpl.png", B_ROCKEXPL, 64, 64, 2.0f, KOBO_CENTER }, { "GFX>>bullet5b.png", B_BULLETS, 16, 16, 2.0f, KOBO_CENTER }, { "GFX>>bulletexpl2.png", B_BULLETEXPL, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>ring.png", B_RING, 32, 32, 2.0f, KOBO_CENTER }, { "GFX>>ringexpl2b.png", B_RINGEXPL, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>bomb.png", B_BOMB, 24, 24, 2.0f, KOBO_CENTER }, { "GFX>>bombdeto.png", B_BOMBDETO, 40, 40, 2.0f, KOBO_CENTER }, { "GFX>>bigship.png", B_BIGSHIP, 72, 72, 2.0f, KOBO_CENTER }, // Framework { "Loading framework graphics", 0, 0,0, 0.0f, KOBO_MESSAGE }, { "GFX>>screen2.png", B_SCREEN, 0, 0, 2.0f, KOBO_CLAMP_OPAQUE }, // Logo { "Loading logo", 0, 0,0, 0.0f, KOBO_MESSAGE }, { "GFX>>logo-outline.png", B_LOGO, 0, 0, 2.0f, 0 }, { "GFX>>deluxe.png", B_LOGODELUXE, 0, 0, 2.0f, 0 }, { "GFX>>logomask3.png", B_LOGOMASK, 0, 0, 4.0f, KOBO_NEAREST | KOBO_NODITHER }, // Fonts { "Loading fonts", 0, 0,0, 0.0f, KOBO_MESSAGE }, { "GFX>>goldfont.png", B_MEDIUM_FONT, 0, 0, 2.0f, KOBO_FONT }, { "GFX>>bigfont3.png", B_BIG_FONT, 0, 0, 2.0f, KOBO_FONT }, { "GFX>>counterfont.png", B_COUNTER_FONT, 0, 0, 2.0f, KOBO_FONT }, // Special FX { "Loading special FX graphics", 0, 0,0, 0.0f, KOBO_MESSAGE }, { "GFX>>noise.png", B_NOISE, NOISE_SIZEX, 1, 1.0f, KOBO_CLAMP }, { "GFX>>hitnoise.png", B_HITNOISE, NOISE_SIZEX, 1, 1.0f, KOBO_CLAMP | KOBO_NEAREST }, { "GFX>>focusfx.png", B_FOCUSFX, 0, 0, 1.0f, KOBO_CLAMP }, { "GFX>>brushes.png", B_BRUSHES, 16, 16, 1.0f, KOBO_CLAMP | KOBO_NEAREST | KOBO_NOALPHA }, { NULL, 0, 0, 0, 0.0f, 0 } // Terminator }; int KOBO_main::load_graphics(prefs_t *p) { KOBO_GfxDesc *gd; gengine->reset_filters(); show_progress(p); for(gd = gfxdesc; gd->path; ++gd) { if(gd->flags & KOBO_MESSAGE) { doing(gd->path); continue; } // Set scale (filter) mode and clamping int clamping; gfx_scalemodes_t sm; if(gd->flags & KOBO_CLAMP) { gengine->clampcolor(0, 0, 0, 0); clamping = 1; } else if(gd->flags & KOBO_CLAMP_OPAQUE) { gengine->clampcolor(0, 0, 0, 255); clamping = 1; } else clamping = 0; if(gd->flags & KOBO_NEAREST) sm = GFX_SCALE_NEAREST; else sm = (gfx_scalemodes_t) p->scalemode; gengine->scalemode(sm, clamping); // Disable brightness filter? if(gd->flags & KOBO_NOBRIGHT) gengine->brightness(1.0f, 1.0f); else gengine->brightness(0.01f * p->brightness, 0.01f * p->contrast); // Dithering if(gd->flags & KOBO_NODITHER || !p->use_dither) gengine->dither(-1); else gengine->dither(p->dither_type, 0); // Alpha channels if(gd->flags & KOBO_NOALPHA || !p->alpha) gengine->noalpha(NOALPHA_THRESHOLD); else gengine->noalpha(0); // Source image scale gengine->source_scale(gd->scale, gd->scale); // Load! const char *fn = fmap->get(gd->path); if(!fn) { log_printf(ELOG, "Couldn't get path to \"%s\"!\n", gd->path); return -1; } int res; if(gd->flags & KOBO_FONT) res = gengine->loadfont(gd->bank, fn); else if(!gd->w || !gd->h) res = gengine->loadimage(gd->bank, fn); else res = gengine->loadtiles(gd->bank, gd->w, gd->h, fn); if(res < 0) { log_printf(ELOG, "Couldn't load \"%s\"!\n", fn); return -1; } // Hotspot if(gd->flags & KOBO_CENTER) gengine->set_hotspot(gd->bank, -1, (int)(gd->w / gd->scale / 2), (int)(gd->h / gd->scale / 2)); // Update progress bar progress(); } // Chop up the dashboard graphics as needed SDL_Rect r; r.w = r.h = 16; r.x = (8 + MARGIN); r.y = MARGIN; if(gengine->copyrect(B_FRAME_TL, B_SCREEN, 0, &r) < 0) return -6; progress(); r.x += WSIZE - 16; if(gengine->copyrect(B_FRAME_TR, B_SCREEN, 0, &r) < 0) return -7; progress(); r.x = (8 + MARGIN); r.y = MARGIN + WSIZE - 16; if(gengine->copyrect(B_FRAME_BL, B_SCREEN, 0, &r) < 0) return -8; progress(); r.x += WSIZE - 16; if(gengine->copyrect(B_FRAME_BR, B_SCREEN, 0, &r) < 0) return -9; progress(); r.x = 252; r.y = 4; r.w = 64; r.h = 18; if(gengine->copyrect(B_HIGH_BACK, B_SCREEN, 0, &r) < 0) return -26; progress(); r.y += 18 + 4; if(gengine->copyrect(B_SCORE_BACK, B_SCREEN, 0, &r) < 0) return -27; progress(); r.x = 244; r.y = (SCREEN_HEIGHT - MAP_SIZEY) / 2; r.w = MAP_SIZEX; r.h = MAP_SIZEY; if(gengine->copyrect(B_RADAR_BACK, B_SCREEN, 0, &r) < 0) return -28; progress(); r.x = 264; r.y = 196; r.w = 38; r.h = 18; if(gengine->copyrect(B_SHIPS_BACK, B_SCREEN, 0, &r) < 0) return -29; progress(); r.y += 18 + 4; if(gengine->copyrect(B_STAGE_BACK, B_SCREEN, 0, &r) < 0) return -30; progress(); r.x = 4; r.y = 92; r.w = 8; r.h = 128; if(gengine->copyrect(B_HEALTH_LID, B_SCREEN, 0, &r) < 0) return -31; progress(); r.x = 244; r.y = 188; r.w = 4; r.h = 32; if(gengine->copyrect(B_TEMP_LID, B_SCREEN, 0, &r) < 0) return -32; progress(); r.x += 4; if(gengine->copyrect(B_TTEMP_LID, B_SCREEN, 0, &r) < 0) return -33; progress(); // Prepare the logo region s_sprite_t *s = gengine->get_sprite(B_LOGOMASK, 0); if(!s || !s->surface) return -10; RGN_FreeRegion(logo_region); logo_region = RGN_ScanMask(s->surface, SDL_MapSurfaceRGB(s->surface, 255, 255, 255)); if(!logo_region) { log_printf(ELOG, "Could not create logo region!\n"); return -92; } progress(); return 0; } static int progress_cb(const char *msg) { if(msg) km.doing(msg); km.progress(); if(km.quit_requested()) return -999; return 0; } int KOBO_main::load_sounds(prefs_t *p, int render_all) { if(!p->use_sound) return 0; show_progress(p); return sound.load(progress_cb, render_all); } int KOBO_main::init_js(prefs_t *p) { // SDL 3 joystick API: SDL_GetJoysticks returns an array of instance // IDs that must be SDL_free()d. SDL_OpenJoystick takes one of those // IDs and returns the SDL_Joystick handle. if(!p->use_joystick) return 0; if(!SDL_InitSubSystem(SDL_INIT_JOYSTICK)) { log_printf(ELOG, "Error setting up joystick: %s\n", SDL_GetError()); return -1; } int count = 0; SDL_JoystickID *ids = SDL_GetJoysticks(&count); p->number_of_joysticks = count; if(count <= 0) { SDL_free(ids); log_printf(ELOG, "No joysticks found!\n"); joystick = NULL; return -3; } if(p->joystick_no < 0 || p->joystick_no >= count) p->joystick_no = 0; joystick = SDL_OpenJoystick(ids[p->joystick_no]); SDL_free(ids); if(!joystick) { log_printf(ELOG, "Failed to open joystick: %s\n", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); return -2; } return 0; } void KOBO_main::close_js() { if(!SDL_WasInit(SDL_INIT_JOYSTICK)) return; if(joystick) { SDL_CloseJoystick(joystick); joystick = NULL; } SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } void KOBO_main::load_config(prefs_t *p) { FILE *f = fmap->fopen(KOBO_CONFIG_DIR "/" KOBO_CONFIG_FILE, "r"); if(f) { p->read(f); fclose(f); } } void KOBO_main::save_config(prefs_t *p) { FILE *f; #if defined(HAVE_GETEGID) && defined(HAVE_SETGID) gid_t oldgid = getegid(); if(setgid(getgid()) != 0) { log_printf(ELOG, "Cannot save config! (%s)\n", strerror(errno)); return; } #endif f = fmap->fopen(KOBO_CONFIG_DIR "/" KOBO_CONFIG_FILE, "w"); if(f) { p->write(f); fclose(f); } #if defined(HAVE_GETEGID) && defined(HAVE_SETGID) if(setgid(oldgid) != 0) { log_printf(ELOG, "Cannot restore GID! (%s)\n", strerror(errno)); return; } #endif } void KOBO_main::brutal_quit() { if(exit_game_fast) { log_printf(ULOG, "Second try quitting; using brutal method!\n"); atexit(SDL_Quit); close_logging(); exit(1); } exit_game_fast = 1; if(gengine) gengine->stop(); } void KOBO_main::pause_game() { if(gsm.current() != &st_pause_game) gsm.press(BTN_PAUSE); } void kobo_render_highlight(ct_widget_t *wg) { screen.set_highlight(wg->y() + wg->height() / 2 - wmain->y(), wg->height()); } int KOBO_main::open() { if(init_display(prefs) < 0) return -1; #ifdef TIME_PROGRESS wdash->progress_init(NULL); #else wdash->progress_init(progtab_all); #endif if(load_graphics(prefs) < 0) return -2; #ifdef TIME_PROGRESS // Use these two lines to time graphics and sounds separately! wdash->progress_done(); wdash->progress_init(NULL); #endif sound.open(); if(load_sounds(prefs) < 0) return -3; wdash->progress_done(); wdash->nibble(); ct_engine.render_highlight = kobo_render_highlight; wdash->mode(DASHBOARD_GAME); wradar->mode(RM_NOISE); // Bench mode overrides flags that downstream init reads (always_fire is // captured by gamecontrol.init at call-time, so it has to be set before). if(kobo_bench_enabled) { // cmd_indicator is "collision testing mode" — collisions still // register sounds but neither side takes damage, so the ship // can't die and the bench workload doesn't drift across runs. // (cmd_cheat is *unlimited lives*, not invulnerability.) prefs->cmd_indicator = 1; prefs->cmd_cheat = 1; // skip score recording on exit prefs->always_fire = 1; // constant auto-fire prefs->countdown = 0; // auto-pop the "GET READY" wait } if(kobo_bench_enabled) pubrand.init(kobo_bench_seed); else pubrand.init(); init_js(prefs); gamecontrol.init(prefs->always_fire); manage.init(); if(kobo_bench_enabled) { // Hands-off bench: jump straight into stage 0, no menu navigation. scorefile.profile()->skill = SKILL_CLASSIC; manage.set_scene_num(0); gsm.push(&st_game); } else { gsm.push(&st_intro_title); } return 0; } void KOBO_main::close() { close_js(); RGN_FreeRegion(logo_region); logo_region = NULL; close_display(); if(gengine) { delete gengine; gengine = NULL; } sound.close(); SDL_Quit(); } int KOBO_main::run() { int retry_status = 0; int dont_retry = 0; while(1) { if(!retry_status) { safe_prefs = *prefs; gengine->run(); if(!manage.game_stopped()) manage.abort(); if(exit_game_fast) break; if(exit_game) { if(wdash) wdash->nibble(); break; } dont_retry = 0; } retry_status = 0; if(global_status & OS_RESTART_AUDIO) { if(prefs->use_sound) { log_printf(ULOG, "--- Restarting audio...\n"); sound.stop(); #ifdef TIME_PROGRESS wdash->progress_init(NULL); #else wdash->progress_init(progtab_sounds); #endif if(load_sounds(prefs) < 0) return 5; wdash->progress_done(); sound.open(); wdash->nibble(); log_printf(ULOG, "--- Audio restarted.\n"); wdash->mode(DASHBOARD_GAME); wradar->mode(screen.radar_mode); manage.set_bars(); } else { log_printf(ULOG, "--- Stopping audio...\n"); sound.close(); log_printf(ULOG, "--- Audio stopped.\n"); } } if((global_status & OS_RELOAD_AUDIO_CACHE) && prefs->cached_sounds) { log_printf(ULOG, "--- Rendering sounds to disk...\n"); #ifdef TIME_PROGRESS wdash->progress_init(NULL); #else wdash->progress_init(progtab_sounds); #endif if(load_sounds(prefs) < 0) { log_printf(ELOG, "--- Could not render sounds to disk!\n"); st_error.message("Could not render to disk!", "Please, check your installation."); gsm.push(&st_error); } wdash->progress_done(); wdash->mode(DASHBOARD_GAME); wradar->mode(screen.radar_mode); manage.set_bars(); } if(global_status & OS_RESTART_VIDEO) { log_printf(ULOG, "--- Restarting video...\n"); wdash->nibble(); gengine->hide(); close_display(); gengine->unload(); if(init_display(prefs) < 0) { log_printf(ELOG, "--- Video init failed!\n"); st_error.message("Video initialization failed!", "Try different settings."); gsm.push(&st_error); if(dont_retry) return 6; *prefs = safe_prefs; dont_retry = 1; retry_status |= OS_RESTART_VIDEO; } else { gamecontrol.init(prefs->always_fire); log_printf(ULOG, "--- Video restarted.\n"); } } if(global_status & (OS_RELOAD_GRAPHICS | OS_RESTART_VIDEO)) { if(!(global_status & OS_RESTART_VIDEO)) wdash->nibble(); gengine->unload(); RGN_FreeRegion(logo_region); logo_region = NULL; if(!retry_status) { log_printf(ULOG, "--- Reloading graphics...\n"); #ifdef TIME_PROGRESS wdash->progress_init(NULL); #else wdash->progress_init(progtab_graphics); #endif if(load_graphics(prefs) < 0) return 7; wdash->progress_done(); wdash->nibble(); wdash->mode(DASHBOARD_GAME); wradar->mode(screen.radar_mode); manage.set_bars(); log_printf(ULOG, "--- Graphics reloaded.\n"); } } if(global_status & OS_RESTART_ENGINE) log_printf(ELOG, "OS_RESTART_ENGINE not implemented!\n"); if(global_status & OS_RESTART_INPUT) { close_js(); init_js(prefs); gamecontrol.init(prefs->always_fire); } if(global_status & OS_RESTART_LOGGER) open_logging(prefs); global_status = retry_status; km.pause_game(); manage.reenter(); } return 0; } /*---------------------------------------------------------- Kobo Graphics Engine ----------------------------------------------------------*/ kobo_gfxengine_t::kobo_gfxengine_t() { } void kobo_gfxengine_t::frame() { sound.frame(); if(!gsm.current()) { log_printf(CELOG, "INTERNAL ERROR: No gamestate!\n"); exit_game = 1; stop(); return; } if(exit_game || manage.aborted()) { stop(); return; } /* * Process input */ SDL_Event ev; while(SDL_PollEvent(&ev)) { int k, ms; switch (ev.type) { case SDL_EVENT_KEY_DOWN: switch(ev.key.key) { #ifdef PROFILE_AUDIO case SDLK_F10: ++km.audio_vismode; break; case SDLK_F12: audio_print_info(); break; case SDLK_R: { audio_channel_stop(-1, -1); int startt = SDL_GetTicks(); audio_wave_load(0, "sfx.agw", 0); log_printf(VLOG, "(Loading + processing time: %d ms)\n", SDL_GetTicks() - startt); break; } #endif case SDLK_DELETE: if(prefs->cmd_debug) { manage.ships = 1; myship.hit(1000); } break; case SDLK_RETURN: ms = SDL_GetModState(); if(ms & (SDL_KMOD_CTRL | SDL_KMOD_SHIFT | SDL_KMOD_GUI)) break; if(!(ms & SDL_KMOD_ALT)) break; km.pause_game(); prefs->fullscreen = !prefs->fullscreen; prefs->changed = 1; global_status |= OS_RELOAD_GRAPHICS | OS_RESTART_VIDEO; stop(); return; case SDLK_PRINTSCREEN: // FIXME: Doesn't this trigger when entering names and stuff...? case SDLK_S: gengine->screenshot(); break; default: break; } k = gamecontrol.map(ev.key.key); gamecontrol.press(k); // SDL 3 dropped per-keydown unicode; text input is via // SDL_EVENT_TEXT_INPUT. Pass 0 for the unicode arg until // the high-score name-entry path is re-wired. gsm.press(k, 0); break; case SDL_EVENT_KEY_UP: if((ev.key.key == SDLK_ESCAPE) && km.escape_hammering()) { km.pause_game(); prefs->fullscreen = 0; prefs->width = 640; prefs->height = 480; prefs->aspect = 1000; prefs->scalemode = (int)GFX_SCALE_NEAREST; prefs->brightness = 100; prefs->contrast = 100; global_status |= OS_RELOAD_GRAPHICS | OS_RESTART_VIDEO; stop(); st_error.message("Safe video settings applied!", "Enter Options menu to save."); gsm.push(&st_error); return; } k = gamecontrol.map(ev.key.key); if(k == SDLK_PAUSE) { gamecontrol.press(BTN_PAUSE); gsm.press(BTN_PAUSE); } else { gamecontrol.release(k); gsm.release(k); } break; case SDL_EVENT_TEXT_INPUT: // SDL 3 delivers typed characters via separate text-input // events when SDL_StartTextInput is active. Forward each // byte of the UTF-8 string as a unicode value to the // current gamestate (used by the high-score name-entry). for(const char *c = ev.text.text; *c; ++c) gsm.press(-1, (unsigned char)*c); break; case SDL_EVENT_WINDOW_EXPOSED: gengine->invalidate(); break; case SDL_EVENT_WINDOW_FOCUS_LOST: km.pause_game(); break; case SDL_EVENT_QUIT: /*gsm.press(BTN_CLOSE);*/ km.brutal_quit(); break; case SDL_EVENT_JOYSTICK_BUTTON_DOWN: if(ev.jbutton.button == km.js_fire) { gamecontrol.press(BTN_FIRE); gsm.press(BTN_FIRE); } else if(ev.jbutton.button == km.js_start) { gamecontrol.press(BTN_START); gsm.press(BTN_START); } break; case SDL_EVENT_JOYSTICK_BUTTON_UP: if(ev.jbutton.button == km.js_fire) { gamecontrol.release(BTN_FIRE); gsm.release(BTN_FIRE); } break; case SDL_EVENT_JOYSTICK_AXIS_MOTION: // FIXME: We will want to allow these to be // redefined, but for now, this works ;-) if(ev.jaxis.axis == km.js_lr) { if(ev.jaxis.value < -3200) { gamecontrol.press(BTN_LEFT); gsm.press(BTN_LEFT); } else if(ev.jaxis.value > 3200) { gamecontrol.press(BTN_RIGHT); gsm.press(BTN_RIGHT); } else { gamecontrol.release(BTN_LEFT); gamecontrol.release(BTN_RIGHT); gsm.release(BTN_LEFT); gsm.release(BTN_RIGHT); } } else if(ev.jaxis.axis == km.js_ud) { if(ev.jaxis.value < -3200) { gamecontrol.press(BTN_UP); gsm.press(BTN_UP); } else if(ev.jaxis.value > 3200) { gamecontrol.press(BTN_DOWN); gsm.press(BTN_DOWN); } else { gamecontrol.release(BTN_UP); gamecontrol.release(BTN_DOWN); gsm.release(BTN_UP); gsm.release(BTN_DOWN); } } break; case SDL_EVENT_MOUSE_MOTION: mouse_x = (int)(ev.motion.x / gengine->xscale()) - km.xoffs; mouse_y = (int)(ev.motion.y / gengine->yscale()) - km.yoffs; if(prefs->use_mouse) gamecontrol.mouse_position( mouse_x - 8 - MARGIN - WSIZE/2, mouse_y - MARGIN - WSIZE/2); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: mouse_x = (int)(ev.button.x / gengine->xscale()) - km.xoffs; mouse_y = (int)(ev.button.y / gengine->yscale()) - km.yoffs; gsm.press(BTN_FIRE); if(prefs->use_mouse) { gamecontrol.mouse_position( mouse_x - 8 - MARGIN - WSIZE/2, mouse_y - MARGIN - WSIZE/2); switch(ev.button.button) { case SDL_BUTTON_LEFT: mouse_left = 1; break; case SDL_BUTTON_MIDDLE: mouse_middle = 1; break; case SDL_BUTTON_RIGHT: mouse_right = 1; break; } gamecontrol.press(BTN_FIRE); } break; case SDL_EVENT_MOUSE_BUTTON_UP: mouse_x = (int)(ev.button.x / gengine->xscale()) - km.xoffs; mouse_y = (int)(ev.button.y / gengine->yscale()) - km.yoffs; if(prefs->use_mouse) { gamecontrol.mouse_position( mouse_x - 8 - MARGIN - WSIZE/2, mouse_y - MARGIN - WSIZE/2); switch(ev.button.button) { case SDL_BUTTON_LEFT: mouse_left = 0; break; case SDL_BUTTON_MIDDLE: mouse_middle = 0; break; case SDL_BUTTON_RIGHT: mouse_right = 0; break; } } if(!mouse_left && !mouse_middle && !mouse_right) { if(prefs->use_mouse) gamecontrol.release(BTN_FIRE); gsm.release(BTN_FIRE); } break; } } gamecontrol.process(); /* * Run the current gamestate for one frame */ http_pump(); gsm.frame(); if(prefs->cmd_autoshot && !manage.game_stopped()) { static int c = 0; ++c; if(c >= 3) { gengine->screenshot(); c = 0; } } } void kobo_gfxengine_t::pre_render() { sound.run(); gsm.pre_render(); } void kobo_gfxengine_t::post_render() { gsm.post_render(); if(!prefs->cmd_noframe) { wmain->sprite(0, 0, B_FRAME_TL, 0, 0); wmain->sprite(WSIZE - 16, 0, B_FRAME_TR, 0, 0); wmain->sprite(0, WSIZE - 16, B_FRAME_BL, 0, 0); wmain->sprite(WSIZE - 16, WSIZE - 16, B_FRAME_BR, 0, 0); } #ifdef DEBUG if(prefs->cmd_debug) { char buf[20]; snprintf(buf, sizeof(buf), "Obj: %d", gengine->objects_in_use()); wmain->font(B_NORMAL_FONT); wmain->string(160, 5, buf); } switch (km.audio_vismode % 5) { case 0: break; case 1: draw_osc(0); break; case 2: draw_vu(); break; case 3: draw_osc(1); break; case 4: draw_osc(2); break; } #endif // Frame rate counter int nt = (int)SDL_GetTicks(); int tt = nt - km.fps_starttime; if((tt > 1000) && km.fps_count) { float f = km.fps_count * 1000.0 / tt; ::screen.fps(f); km.fps_count = 0; km.fps_starttime = nt; if(prefs->cmd_fps) { char buf[20]; snprintf(buf, sizeof(buf), "%.1f", f); km.dfps->text(buf); if(!km.fps_results) km.fps_results = (float *) calloc(MAX_FPS_RESULTS, sizeof(float)); if(km.fps_results) { km.fps_results[km.fps_nextresult++] = f; if(km.fps_nextresult >= MAX_FPS_RESULTS) km.fps_nextresult = 0; if(km.fps_nextresult > km.fps_lastresult) km.fps_lastresult = km.fps_nextresult; } } } ++km.fps_count; // Frame rate limiter if(prefs->max_fps) { if(prefs->max_fps_strict) { static double nextframe = -1000000.0f; while(1) { double t = (double)SDL_GetTicks(); if(fabs(nextframe - t) > 1000.0f) nextframe = t; double d = nextframe - t; if(d > 10.0f) SDL_Delay(10); else if(d > 1.0f) SDL_Delay(1); else break; } nextframe += 1000.0f / prefs->max_fps; } else { int rtime = nt - km.max_fps_begin; km.max_fps_begin = nt; km.max_fps_filter += (float)(rtime - km.max_fps_filter) * 0.3; int delay = (int)(1000.0 / prefs->max_fps - km.max_fps_filter + 0.5); if((delay > 0) && (delay < 1100)) SDL_Delay(delay); km.max_fps_begin = (int)SDL_GetTicks(); } } } /*---------------------------------------------------------- main() and related stuff ----------------------------------------------------------*/ static void put_usage() { printf("\nKobo Deluxe %s\n", KOBO_VERSION); printf("Usage: kobodl []\n"); printf("Recognized options:\n"); int s = -1; while(1) { const char *fs; s = prefs->find_next(s); if(s < 0) break; switch(prefs->type(s)) { case CFG_BOOL: fs = " -[no]%-16.16s[%s]\t%s%s\n"; break; case CFG_INT: case CFG_FLOAT: fs = " -%-20.20s[%s]\t%s%s\n"; break; case CFG_STRING: fs = " -%-20.20s[\"%s\"]\t%s%s\n"; break; default: continue; } printf(fs, prefs->name(s), prefs->get_default_s(s), prefs->do_save(s) ? "" : "(Not saved!) ", prefs->description(s)); } } static void put_options_man() { int s = -1; while(1) { const char *fs; s = prefs->find_next(s); if(s < 0) break; switch(prefs->type(s)) { case CFG_BOOL: fs = ".TP\n.B \\-[no]%s\n%s%s. Default: %s.\n"; break; case CFG_INT: case CFG_FLOAT: fs = ".TP\n.B \\-%s\n%s%s. Default: %s.\n"; break; case CFG_STRING: fs = ".TP\n.B \\-%s\n%s%s. Default: \"%s\"\n"; break; default: continue; } printf(fs, prefs->name(s), prefs->do_save(s) ? "" : "(Not saved!) ", prefs->description(s), prefs->get_default_s(s)); } } extern "C" void emergency_close(void) { km.close(); } extern "C" RETSIGTYPE breakhandler(int dummy) { /* For platforms that drop the handlers on the first signal... */ signal(SIGTERM, breakhandler); signal(SIGINT, breakhandler); km.brutal_quit(); #if (RETSIGTYPE != void) return 0; #endif } int main(int argc, char *argv[]) { int cmd_exit = 0; atexit(emergency_close); signal(SIGTERM, breakhandler); signal(SIGINT, breakhandler); kobo_bench_init_env(); prof_init(); SDL_Init(0); http_init(); if(main_init()) { fprintf(stderr, "INTERNAL ERROR\n"); return 1; }; km.open_logging(NULL); setup_dirs(argv[0]); --argc; ++argv; if((argc < 1) || (strcmp("-override", argv[0]) != 0)) km.load_config(prefs); if((prefs->parse(argc, argv) < 0) || prefs->cmd_help) { put_usage(); main_cleanup(); return 1; } // First-run client_id generation. Stable per-install identifier sent // with leaderboard submissions for rate-limiting/dedup (not used as // the public identity — that's name + board_name). if(prefs->client_id[0] == '\0') { // Not cryptographically random — this is a rate-limit dedup // token, not a secret. SDL_rand_bits is sufficient. Uint8 b[16]; for(int i = 0; i < 16; i += 4) { Uint32 r = SDL_rand_bits(); b[i] = (Uint8)(r >> 24); b[i+1] = (Uint8)(r >> 16); b[i+2] = (Uint8)(r >> 8); b[i+3] = (Uint8)r; } b[6] = (b[6] & 0x0F) | 0x40; // RFC 4122 v4 b[8] = (b[8] & 0x3F) | 0x80; snprintf(prefs->client_id, sizeof(prefs->client_id), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", b[0],b[1],b[2],b[3], b[4],b[5], b[6],b[7], b[8],b[9], b[10],b[11],b[12],b[13],b[14],b[15]); prefs->changed = 1; } if(prefs->cmd_options_man) { put_options_man(); main_cleanup(); return 1; } if(prefs->cmd_noparachute) { // SDL 3 has no parachute / signal handler by default. SDL_Quit(); SDL_Init(0); } km.open_logging(prefs); for(int a = 0; a < argc; ++a) log_printf(DLOG, "argv[%d] = \"%s\"\n", a, argv[a]); int k = -1; while((k = prefs->find_next(k)) >= 0) { log_printf(D3LOG, "key %d: \"%s\"\ttype=%d\t\"%s\"\n", k, prefs->name(k), prefs->type(k), prefs->description(k) ); } add_dirs(prefs); if(prefs->cmd_hiscores) { scorefile.gather_high_scores(); scorefile.print_high_scores(); cmd_exit = 1; } if(prefs->cmd_showcfg) { printf("Configuration:\n"); printf("----------------------------------------\n"); prefs->write(stdout); printf("\nPaths:\n"); printf("----------------------------------------\n"); fmap->print(stdout, "*"); printf("----------------------------------------\n"); cmd_exit = 1; } if(cmd_exit) { km.close_logging(); main_cleanup(); return 0; } if(km.open() < 0) { km.close_logging(); main_cleanup(); return 1; } km.run(); km.close(); /* * Seems like we got all the way here without crashing, * so let's save the current configuration! :-) */ if(prefs->changed) { km.save_config(prefs); prefs->changed = 0; } if(prefs->cmd_fps) km.print_fps_results(); km.close_logging(); http_shutdown(); main_cleanup(); return 0; }