kobodl/kobo.cpp
Ville Lindholm 869056c5d2
Phase 0: profiling scaffolding for DOD refactor
Adds a tiny zone-based profiler (prof.{h,cpp}) enabled via KOBO_PROF=1.
Instruments run_game with zones for myship.move, enemies.move (split into
realize and move_pass), hit_structure, and hit_bolt. Adds KOBO_STRESS=N
to keep the enemy pool topped up with rocks for stable per-frame timing.

Why: the entity/render pipeline refactor in BENCH.md needs a comparable
baseline before any layout changes. Without a stress workload the natural
gameplay enemy count is too noisy to compare across commits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-29 13:31:31 +03:00

2170 lines
46 KiB
C++

/*(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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#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"
#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);
pubrand.init();
init_js(prefs);
gamecontrol.init(prefs->always_fire);
manage.init();
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
*/
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 [<options>]\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);
prof_init();
SDL_Init(0);
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;
}
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();
main_cleanup();
return 0;
}