kobodl/screen.cpp
Ville Lindholm dbc223eb84
Initial commit
Import existing source tree; original VCS history is no longer available.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 16:35:31 +03:00

1043 lines
25 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
*
* 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.
*/
#include <math.h>
#ifndef M_PI
# define M_PI 3.14159265358979323846 /* pi */
#endif
#include "kobo.h"
#include "screen.h"
#include "manage.h"
#include "options.h"
#include "enemies.h"
#include "myship.h"
#include "radar.h"
#include "scenes.h"
#include "config.h"
#include "random.h"
#include "version.h"
int _screen::scene_max;
int _screen::scene_num;
int _screen::level;
int _screen::generate_count;
_map _screen::map;
int _screen::show_title = 0;
int _screen::do_noise = 0;
float _screen::_fps = 40;
float _screen::scroller_speed = SCROLLER_SPEED;
float _screen::target_speed = SCROLLER_SPEED;
int _screen::noise_y = 0;
int _screen::noise_h = WSIZE;
int _screen::noise_source = B_NOISE;
float _screen::noise_fade = 0.0f;
float _screen::noise_bright = 0.0f;
float _screen::noise_depth = 0.0f;
int _screen::highlight_y = WSIZE / 2;
int _screen::highlight_h = 0;
int _screen::hi_sc[10];
int _screen::hi_st[10];
char _screen::hi_nm[10][20];
int _screen::nstars = 0;
KOBO_Star *_screen::stars = NULL;
Uint32 _screen::starcolors[STAR_COLORS];
int _screen::star_oxo = 0;
int _screen::star_oyo = 0;
radar_modes_t _screen::radar_mode = RM_OFF;
_screen::~_screen()
{
free(stars);
}
void _screen::init()
{
scene_max = 0;
while(scene[scene_max].ratio != -1)
scene_max++;
}
void _screen::render_title_plasma(int t, float fade, int y, int h)
{
s_sprite_t *s = gengine->get_sprite(B_FOCUSFX, 0);
if(!s || !s->surface)
return;
SDL_Surface *fx = s->surface;
SDL_Rect sr;
sr.x = 0;
sr.w = fx->w;
sr.h = 1;
for(int ty = 0; ty < h; ++ty)
{
float sy = (float)ty / (h - 1);
float shape = sin(M_PI * sy);
float plasma = 0.5f + 0.5f * sin(t * 0.004f +
sin(t * 0.00017f) * sy * 18.0f);
float plasma2 = 0.5f + 0.5f * sin(t * 0.001f +
sin(t * 0.00013f) * sy * 12.0f);
int i = (int)((fx->h - 1) * (0.3f * plasma + 0.7f * shape) * fade);
int xo = (int)(8192 * plasma2 * gengine->xscale() / 256);
xo -= (int)(xo / fx->w) * fx->w;
int xmax = wmain->phys_rect.x + wmain->phys_rect.w;
for(int x = -xo; x < xmax; x += fx->w)
{
sr.y = i;
RGN_Blit(fx, &sr, x, y + ty);
}
}
}
void _screen::render_title_noise(float fade, int y, int h, int bank, int frame)
{
for(int ty = 0; ty < h; )
{
int xo = (int)(pubrand.get(16) * gengine->xscale()) / 256;
s_sprite_t *s = gengine->get_sprite(bank, pubrand.get(4));
if(!s || !s->surface)
continue;
SDL_Surface *fx = s->surface;
xo -= (int)(xo / fx->w) * fx->w;
int xmax = wmain->phys_rect.x + wmain->phys_rect.w;
for(int x = -xo; x < xmax; x += fx->w)
RGN_Blit(fx, NULL, x, y + ty);
ty += fx->h;
}
}
static int flashin(int t)
{
if(t < 0)
return 1;
else if(t > 800)
return 0;
else
return (t % 200) > 100;
}
void _screen::title(int t, float fade, int mode)
{
// Enclosed effect
s_sprite_t *s = gengine->get_sprite(B_LOGO, 0);
if(!s || !s->surface)
return;
float mf = (1.0f - fade);
int h = s->surface->h;
int y0 = wmain->phys_rect.y + (int)(60 * gengine->yscale() + 0.5f);
int y = (int)(y0 - mf * mf * mf * (y0 + h) + 0.5f);
wmain->select();
RGN_Target(gengine->surface());
RGN_SetRegion(logo_region,
wmain->phys_rect.x +
(int)((wmain->width() - 206) / 2 * gengine->xscale()),
y);
switch(mode)
{
case 0:
render_title_plasma(t, fade, y, h);
break;
case 1:
render_title_noise(fade, y, h, (int)B_NOISE, 0);
break;
case 2:
render_title_noise(fade, y, h, (int)B_HITNOISE, 0);
break;
}
// Outline
wmain->sprite_fxp(PIXEL2CS((wmain->width() - 206) / 2),
(int)((y * 256 + 255) / gengine->yscale()) -
PIXEL2CS(wmain->y()),
B_LOGO, 0);
// "DELUXE"
wmain->sprite_fxp(PIXEL2CS((wmain->width() - 139) / 2),
PIXEL2CS(123) + (int)(PIXEL2CS(160) * mf * mf),
B_LOGODELUXE, 0);
// Version
if(fade > 0.9)
{
wmain->font(B_NORMAL_FONT);
wmain->center(132, KOBO_VERSION);
}
// Cheat mode warning
if((prefs->cmd_cheat || prefs->cmd_pushmove) && (t % 1000 < 500))
{
wmain->font(B_MEDIUM_FONT);
wmain->center(145, "CHEAT MODE");
}
#if 1
// WIP notice
if(!flashin(t - 2000))
{
wmain->font(B_MEDIUM_FONT);
wmain->center(170, "This is a Work in Progress!");
wmain->center(180, "Check http://olofson.net/kobodl");
}
#endif
}
void _screen::init_highscores()
{
for(unsigned int i = 0; i < 10; ++i)
if(i < scorefile.highs)
{
hi_sc[i] = scorefile.high_tbl[i].score;
hi_st[i] = scorefile.high_tbl[i].end_scene;
strncpy(hi_nm[i], scorefile.high_tbl[i].name, 19);
hi_nm[i][19] = 0;
}
else
{
hi_sc[i] = 0;
hi_st[i] = 0;
strcpy(hi_nm[i], "---");
}
}
void _screen::highscores(int t, float fade)
{
int i, y;
float mf = (1.0f - fade);
mf *= mf * mf * mf * mf;
screen.set_highlight(0, 0);
wmain->font(B_BIG_FONT);
y = (int)(PIXEL2CS(100) * mf) - 256;
if(y < 0)
y = 0;
wmain->center_fxp(PIXEL2CS(20) - y, "HALL OF FAME");
int xo = (int)(50 * 256 * mf);
wmain->sprite_fxp(PIXEL2CS(32) - xo, PIXEL2CS(30),
B_PLAYER, (t / 50) % 16);
wmain->sprite_fxp(PIXEL2CS(wmain->width() - 32) + xo, PIXEL2CS(30),
B_PLAYER, 15 - (t / 50) % 16);
wmain->font(B_NORMAL_FONT);
y = (int)(PIXEL2CS(75) * mf - 0.5f) - 256;
if(y < 0)
y = 0;
wmain->center_fxp(PIXEL2CS(40) - y, "(Score/Stage/Name)");
wmain->font(B_MEDIUM_FONT);
float yo = t * (11*18+100) * 256.0 / 12500.0 - PIXEL2CS(110);
for(i = 0, y = 65; i < 10; ++i, y += 18)
{
static char s[20];
float xo, cy;
int real_y = PIXEL2CS(y) - (int)yo;
if(real_y < PIXEL2CS(55) || real_y > PIXEL2CS(165))
continue;
cy = (PIXEL2CS(y) - yo) - PIXEL2CS(65 + 5*18/2);
xo = cy*cy*cy*cy*cy * 1e-16;
snprintf(s, 16, "%d", hi_sc[i]);
wmain->center_token_fxp(PIXEL2CS(70)+(int)xo, real_y, s);
snprintf(s, 16, "%d", hi_st[i]);
wmain->center_token_fxp(PIXEL2CS((70+125)/2)+(int)xo, real_y, s, -1);
wmain->string_fxp(PIXEL2CS(125)+(int)xo, real_y, hi_nm[i]);
}
#if 1
if(!flashin(t - 1000))
{
wmain->font(B_MEDIUM_FONT);
wmain->center(170, "NOTE: Highscores for \"Classic\" only!");
wmain->center(180, "The new skill levels are experimental.");
}
#endif
}
void _screen::credits(int t)
{
if((t % 4000 < 3600) && (t / 4000 <= 2))
screen.set_highlight(102, 70);
else
screen.set_highlight(0, 0);
int t2 = 10 * (t % 4000) / 3700;
if(t % 4000 < 3700)
switch(t / 4000)
{
case 0:
wmain->font(B_BIG_FONT);
if(t2 > 0)
wmain->center(80, "DAVID OLOFSON");
wmain->font(B_NORMAL_FONT);
if(t2 > 2)
wmain->center(105, "New SDL Audio & GFX Engines");
if(t2 > 3)
wmain->center(115, "New Graphics, Sound & Music");
break;
case 1:
wmain->font(B_BIG_FONT);
if(t2 > 0)
wmain->center(80, "AKIRA HIGUCHI");
wmain->font(B_NORMAL_FONT);
if(t2 > 2)
wmain->center(110, "XKobo - The Original Game");
break;
case 2:
wmain->font(B_BIG_FONT);
if(t2 > 0)
wmain->center(80, "MASANAO IZUMO");
wmain->font(B_NORMAL_FONT);
if(t2 > 2)
wmain->center(105, "Original Sounds");
if(t2 > 3)
wmain->center(115, "Original Sound Engine");
break;
}
if(!flashin(t - 11000))
{
wmain->font(B_MEDIUM_FONT);
wmain->center(170, "Additional Credits & Thanks");
wmain->center(180, "in the scroller below");
}
}
void _screen::help(int t)
{
wmain->font(B_BIG_FONT);
wmain->center(53, "HOW TO PLAY");
// 0..3000: Control
if(t < 200)
screen.set_highlight(112, 80);
else if(t < 2800)
{
if(t > 2700)
screen.set_highlight(0, 0);
wmain->font(B_MEDIUM_FONT);
wmain->sprite(wmain->width() / 2, 105, B_PLAYER, (t / 50) % 16);
wmain->center(125, "Use arrow keys or NumPad.");
wmain->center(135, "to control the ship.");
}
else if(t < 3000)
return;
// 3000..6000: Fire
else if(t < 3200)
screen.set_highlight(112, 80);
else if(t < 5800)
{
if(t > 5700)
screen.set_highlight(0, 0);
wmain->font(B_MEDIUM_FONT);
int i;
for(i = 0; i < 4; ++i)
{
int t2 = t + 153 * i;
wmain->sprite(wmain->width() / 2 - (t2 / 5) % 120, 105,
B_BOLT, 8 + (t2 / 50) % 4);
wmain->sprite(wmain->width() / 2 + (t2 / 5) % 120, 105,
B_BOLT, 8 + (t2 / 50) % 4);
}
wmain->sprite(wmain->width() / 2, 105, B_PLAYER, 4);
wmain->center(135, "Use any SHIFT or CONTROL keys to fire.");
}
else if(t < 6000)
return;
// 6000..9000: Bases
else if(t < 6200)
screen.set_highlight(112, 80);
else if(t < 8800)
{
if(t > 8700)
screen.set_highlight(0, 0);
wmain->font(B_MEDIUM_FONT);
// TODO: Short demo of how to destroy a base
int i;
for(i = B_TILES1; i <= B_TILES5; ++i)
{
int xo = 24 * i - (5 * 24 / 2) + 4;
wmain->sprite(wmain->width() / 2 + xo, 105 - 8, i, 7);
}
wmain->center(135, "Destroy bases by shooting their cores.");
}
else if(t < 9000)
return;
// 9000..14000: Shoot everything!
else if(t < 9200)
screen.set_highlight(112, 80);
else if(t < 13800)
{
if(t > 13700)
screen.set_highlight(0, 0);
wmain->font(B_MEDIUM_FONT);
// TODO: Short demo of intense battle
wmain->sprite(50, 110, B_RING, (t / 30) % 16);
wmain->sprite(65, 90, B_BOMB, (t / 40) % 16);
wmain->sprite(80, 110, B_BMR_GREEN, (t / 80) % 16);
wmain->sprite(95, 90, B_BMR_PURPLE, (t / 70) % 16);
wmain->sprite(110, 110, B_BMR_PINK, (t / 60) % 16);
wmain->sprite(125, 90, B_FIGHTER, (t / 50) % 16);
wmain->sprite(140, 110, B_MISSILE1, (t / 45) % 16);
wmain->sprite(155, 90, B_MISSILE2, (t / 40) % 16);
wmain->sprite(170, 110, B_MISSILE3, (t / 55) % 16);
wmain->center(125, "Shoot everything that moves,");
wmain->center(135, "but avoid getting hit!");
}
else if(t < 14000)
return;
// 14000..20000: Indestructible
else if(t < 14200)
screen.set_highlight(112, 80);
else if(t < 19800)
{
if(t > 19700)
screen.set_highlight(0, 0);
wmain->font(B_MEDIUM_FONT);
// TODO: Demo destroying a rock in Gamer mode?
int i;
for(i = B_TILES1; i <= B_TILES5; ++i)
{
int xo = 24 * i - (5 * 24 / 2) + 4;
wmain->sprite(wmain->width() / 2 + xo, 82 - 8, i, 0);
}
wmain->center(87, "Some objects are indestructible...");
wmain->sprite(65, 120, B_ROCK1, (t / 45) % 32);
wmain->sprite(90, 115, B_ROCK2, (t / 40) % 32);
wmain->sprite(115, 125, B_ROCK3, (t / 35) % 48);
wmain->sprite(155, 120, B_BIGSHIP, (t / 40) % 16);
wmain->center(135, "...or take many hits to destroy.");
}
}
void _screen::scroller()
{
if(do_noise)
return;
/*
* Adjust scroller speed according to
* frame rate, for readability.
*/
if(_fps < 30)
target_speed = SCROLLER_SPEED / 2;
else if(_fps > 40)
target_speed = SCROLLER_SPEED;
scroller_speed += (target_speed - scroller_speed) * 0.05;
static const char scrolltext[] =
"Welcome to KOBO DELUXE, an enhanced version of "
"Akira Higuchi's fabulous X-Window game XKOBO. "
" "
"KOBO DELUXE uses SDL, the Simple DirectMedia "
"Layer (www.libsdl.org) for graphics and input, "
"and Audiality (olofson.net/audiality) for sound "
"and music. "
" "
"KOBO DELUXE has been known to hinder productivity on: "
" "
" - "
"Windows 95/98/ME - "
"Windows 2000/XP - "
"Mac OS X (PPC) - "
"BeOS (x86, PPC) - "
"AmigaOS (68k, PPC) - "
"Solaris (x86, SPARC) - "
"QNX (x86) - "
"GNU/Linux (x86, x86_64, PPC, PPC64) - "
"OpenBSD (x86, PPC, SPARC, SPARC64) - "
"FreeBSD (x86) - "
"NetBSD (x86) - "
"IRIX - "
"OS/2 - "
"PlayStation 2/PS2Linux - "
"Xbox - "
"Syllable - "
"ITOS (Nokia internet tablets) - "
" "
"Any help in the Cause of Infiltrating Further "
"Platforms is Greatly Appreciated! "
" "
"Additional Credits & Thanks to: "
" Torsten Giebl (Slackware)"
" David Andersson (Some Good Ideas)"
" Samuel Hart (Joystick Support)"
" Max Horn (Mac OS X)"
" Jeremy Sheeley (Player Profiles)"
" Tsuyoshi Iguchi (FreeBSD, NetBSD)"
" G. Low (Solaris)"
" Gerry Jo \"Trick\" Jellestad (Testing & Ideas)"
" \"Riki\" (Intel Compiler)"
" Andreas Spaangberg (Sun Compiler)"
" \"SixK\" (Amiga Port)"
" Joey Hess (Debian)"
" Martijn van Oosterhout (FPS limiter)"
" Antonio Messina (Stage 1601+, Always Fire)"
" Hans de Goede (Audio bug)"
" Marianne Ibbotson (\"Autopause\")"
" Sam Palmer (Windows testing)"
" Michael Sterrett (glSDL issues)"
" Sam Lantinga & Others (SDL)"
" Members of the SDL Mailing List"
" "
"Additional Thanks from Akira Higuchi Go To: "
" Bruce Cheng"
" Christoph Lameter"
" Davide Rossi"
" Eduard Martinescu"
" Elan Feingold"
" Helmut Hoenig"
" Jeff Epler"
" Joe Ramey"
" Joey Hess"
" Michael Sterrett"
" Mihail Iotov"
" Shoichi Nakayama"
" Thomas Marsh"
" Torsten Wolnik"
" "
"And as we said in the old days... "
" "
" <WRAP> ";
// FIXME: Nasty static state variables...
static const char *stp = scrolltext;
static int pos = PIXEL2CS(SCREEN_WIDTH);
static int t = 0;
int nt = (int)SDL_GetTicks();
int dt = nt - t;
t = nt;
if(dt > 100)
dt = 100;
static int fdt = 0;
fdt += ((dt<<8) - fdt) >> 3;
pos -= (fdt * PIXEL2CS((int)scroller_speed) + 128000) / 256000;
wmain->font(B_BIG_FONT);
wmain->string_fxp(pos, PIXEL2CS(200), stp);
/*
* Chop away characters at the left edge
*/
char buf[2] = {*stp, 0};
int cw = wmain->textwidth_fxp(buf);
if(-pos > cw)
{
pos += cw;
++stp;
if(*stp == 0)
{
// Wrap!
pos = PIXEL2CS(SCREEN_WIDTH);
stp = scrolltext;
}
}
}
void _screen::init_scene(int sc)
{
map.init();
if(sc < 0)
{
/*
* Intro mode
*/
show_title = 1;
myship.off();
enemies.off();
if(sc == INTRO_SCENE)
{
/*
* Plain intro - no map
*/
radar_mode = RM_OFF; /* Clear radar */
scene_num = 19;
level = 0;
}
else
{
/*
* Map selection - show current map
*/
radar_mode = RM_SHOW;
scene_num = -(sc+1) % scene_max;
level = -(sc+1) / scene_max;
}
}
else
{
/*
* In-game mode
*/
show_title = 0;
scene_num = sc % scene_max;
level = sc / scene_max;
radar_mode = RM_RADAR;
}
gengine->period(game.speed);
sound.period(game.speed);
const _scene *s = &scene[scene_num];
int i;
for(i = 0; i < s->base_max; i++)
map.make_maze(s->base[i].x, s->base[i].y, s->base[i].h,
s->base[i].v);
map.convert(s->ratio);
generate_count = 0;
wradar->mode(radar_mode);
}
int _screen::prepare()
{
if(scene_num < 0)
return 0;
const _scene *s = &scene[scene_num];
int i, j;
int count_core = 0;
int c = 0;
int lc = level > 31 ? 31 : level;
int interval_1 = (s->ek1_interval) >> lc;
int interval_2 = (s->ek2_interval) >> lc;
if(interval_1 < 4)
interval_1 = 4;
if(interval_2 < 4)
interval_2 = 4;
enemies.set_ekind_to_generate(s->ek1, interval_1, s->ek2,
interval_2);
wmain->clear();
for(i = 0; i < MAP_SIZEX; i++)
for(j = 0; j < MAP_SIZEY; j++)
{
int m = MAP_BITS(map.pos(i, j));
if(IS_SPACE(m))
continue;
if((m == U_MASK) || (m == R_MASK) || (m == D_MASK)
|| (m == L_MASK))
{
enemies.make(&cannon, i * 16 + 8,
j * 16 + 8);
c++;
}
else if(m & CORE)
{
enemies.make(&core, i * 16 + 8,
j * 16 + 8);
count_core++;
c++;
}
}
myship.set_position(s->startx << 4, s->starty << 4);
return count_core;
}
void _screen::generate_fixed_enemies()
{
static int sint[16] =
{ 0, 12, 23, 30, 32, 30, 23, 12, 0, -12, -23, -30,
-32, -30, -23, -12 };
static int cost[16] =
{ 32, 30, 23, 12, 0, -12, -23, -30, -32, -30, -23,
-12, 0, 12, 23, 30 };
const _scene *s = &scene[scene_num];
if(generate_count < s->enemy_max)
{
int j;
for(j = 0; j < s->enemy[generate_count].num; j++)
{
int sp = s->enemy[generate_count].speed;
int x, y, h, v, t;
x = gamerand.get() % (WORLD_SIZEX - VIEWLIMIT * 2);
y = gamerand.get() % (WORLD_SIZEY - VIEWLIMIT * 2);
x -= (WORLD_SIZEX / 2 - VIEWLIMIT);
y -= (WORLD_SIZEY / 2 - VIEWLIMIT);
if(x < 0)
x -= VIEWLIMIT;
else
x += VIEWLIMIT;
if(y < 0)
y -= VIEWLIMIT;
else
y += VIEWLIMIT;
x += myship.get_x();
y += myship.get_y();
t = gamerand.get(4);
h = PIXEL2CS(sp * sint[t]) / 64;
v = PIXEL2CS(sp * cost[t]) / 64;
enemies.make(s->enemy[generate_count].kind, x, y,
h, v);
}
generate_count++;
}
if(generate_count >= s->enemy_max)
generate_count = 0;
}
void _screen::set_map(int x, int y, int n)
{
if(n == 0)
map.clearpos(x, y);
else
map.pos(x, y) = n;
wradar->update(x, y);
}
void _screen::render_noise(window_t *win)
{
if(!do_noise)
return;
if(noise_fade < 0.01f)
return;
int ymax = noise_y + noise_h;
int np = pubrand.get(8);
int dnp = pubrand.get(4) - 8;
float ifade = 1.0f - noise_fade;
float step = 1.0f + ifade*ifade*ifade * 5.0f;
float rstep = (ifade + ifade*ifade*ifade * 10.0f) / 256.0f;
if(step < 1.0f)
step = 1.0f;
if(rstep < 0.0f)
rstep = 0.0f;
for(float fy = noise_y + pubrand.get(8) * rstep; fy < ymax;
fy += step + pubrand.get(8) * rstep)
{
int xo = PIXEL2CS(pubrand.get(NOISE_SIZEX_LOG2));
int xmax = ((WSIZE + CS2PIXEL(xo)) >> NOISE_SIZEX_LOG2) + 1;
dnp += pubrand.get(3) - 4 + pubrand.get(1);
np += dnp;
if(np > 255)
np = 255, dnp = -dnp / 2;
else if(np < 0)
np = 0, dnp = -dnp / 2;
float level = np * noise_depth * (1.0f - noise_bright) / 255.0f +
noise_bright;
for(int x = 0; x < xmax; ++x)
win->sprite_fxp(PIXEL2CS(x<<NOISE_SIZEX_LOG2) - xo,
PIXEL2CS((int)fy),
noise_source, (int)(level * 15.0f +
(float)pubrand.get(8) / 256.0f));
}
}
void _screen::set_noise(int source, float fade, float bright, float depth)
{
noise_source = source;
noise_y = 0;
noise_h = WSIZE;
noise_fade = fade;
noise_bright = bright;
noise_depth = depth;
}
void _screen::render_highlight(window_t *win)
{
if(!highlight_h)
highlight_y = win->height() / 2;
static float ypos = -50;
static float hf = 0;
static int ot = 0;
int t = (int)SDL_GetTicks();
int dt = t - ot;
ot = t;
if(dt > 1000)
dt = 100;
//Spring + friction style velocity component
if(dt < 500)
ypos += ((float)highlight_y - ypos) * (float)dt * 0.005f;
else
ypos = (float)highlight_y;
//Constant velocity component
float v = (float)dt * 0.2f;
if((float)highlight_y - ypos > v)
ypos += v;
else if((float)highlight_y - ypos < -v)
ypos -= v;
else
ypos = (float)highlight_y;
// Scale to new height
hf += ((float)highlight_h - hf) * (float)dt * 0.01f;
// Render!
int y = (int)((ypos - hf / 2.0f) * 256.0f);
int h = (int)(hf * 256.0f);
if(h < 128)
return;
for(int ty = -256; ty <= h; ty += h + 256)
{
int xo = PIXEL2CS(pubrand.get(NOISE_SIZEX_LOG2));
int xmax = ((WSIZE + CS2PIXEL(xo)) >> NOISE_SIZEX_LOG2) + 1;
for(int x = 0; x < xmax; ++x)
wmain->sprite_fxp(PIXEL2CS(x << NOISE_SIZEX_LOG2) - xo,
ty + y,
B_NOISE, 6);
}
int x0 = win->phys_rect.x;
s_sprite_t *s = gengine->get_sprite(B_FOCUSFX, 0);
if(!s || !s->surface)
return;
SDL_Surface *fx = s->surface;
SDL_Surface *dst = gengine->surface();
y = (int)((y * gengine->yscale() + 128) / 256) + win->phys_rect.y;
h = (int)(hf * gengine->yscale());
win->select();
for(int ty = 0; ty < h; ++ty)
{
float sy = (float)ty / (h - 1);
float scy = sy * hf;
float shape = 1.0f - sin(M_PI * sy);
float edges = shape * shape * shape;
float plasma = 0.5f + 0.5f * sin(t * 0.004f +
sin(t * 0.00017f) * scy * 0.18f);
float plasma2 = 0.5f + 0.5f * sin(t * .003 +
sin(t * 0.0001f) * scy * 0.12f);
int i = (int)((fx->h - 1) * ((.5f * plasma + .5f * plasma * shape) *
(1.0f - edges) + edges));
int xo = (int)((t * 10 + 8192 * plasma2) * gengine->xscale() / 256);
xo -= (int)(xo / fx->w) * fx->w;
int xmax = (int)((WSIZE * gengine->xscale() + xo) / fx->w);
for(int x = 0; x <= xmax; ++x)
{
SDL_Rect sr, dr;
sr.x = 0;
sr.y = i;
sr.w = fx->w;
sr.h = 1;
dr.x = x0 + (int)(x * fx->w) - xo;
dr.y = y + ty;
SDL_BlitSurface(fx, &sr, dst, &dr);
}
}
}
void _screen::set_highlight(int y, int h)
{
highlight_y = y;
highlight_h = h;
}
// This starfield implementation may seem a bit backwards. Instead of moving
// around in a 3D point cloud, projecting by dividing x and y by z etc, we're
// doing a plain orthogonal projection. That is, the z coordinates have no
// direct impact on rendering!
// What actually creates the parallax effect is that we scroll the *stars*
// around at different speeds, depending on their distance from the screen,
// which effectively replaces the usual (x/z, y/z) operation when projecting.
// The interesting part is that stars wrap around the edges automatically as
// a side effect of the integer arithmetics. Thus we can have a nice, dense
// "3D" starfield covering the whole window - no nearby stars way off screen,
// and no distant stars wrapping around in a small square in the middle of the
// screen. No clipping is needed, and thus, no cycles are wasted animating off-
// screen stars.
// Of course, this design means we cannot move along the Z axis (well, not
// trivially, at least), but we don't really need that here anyway.
//
#define WSX (WORLD_SIZEX << 8)
#define WSY (WORLD_SIZEY << 8)
void _screen::render_starfield(window_t *win, int xo, int yo)
{
int i;
int w = win->width() * 256;
int h = win->height() * 256;
int xc = w / 2;
int yc = h / 2;
// Calculate delta from last position, dealing with map position wrap
int dx = (xo - star_oxo) & (WSX - 1);
star_oxo = xo;
if(dx & (WSX >> 1))
dx |= 1 - (WSX - 1);
int dy = (yo - star_oyo) & (WSY - 1);
star_oyo = yo;
if(dy & (WSY >> 1))
dy |= 1 - (WSY - 1);
// Scale the deltas to compensate for window/starfield size mismatch
// (Otherwise stars at zero distance won't sync up with the map as
// intended!)
dx = (dx << 16) / w;
dy = (dy << 16) / h;
/* (Re-)initialize starfield */
if(nstars != prefs->stars)
{
free(stars);
nstars = prefs->stars;
stars = (KOBO_Star *)malloc(nstars * sizeof(KOBO_Star));
if(!stars)
{
nstars = 0;
return; // Out of memory!!!
}
for(i = 0; i < nstars; ++i)
{
stars[i].x = pubrand.get();
stars[i].y = pubrand.get();
int zz = 255 * i / nstars;
stars[i].z = 65025 - zz * zz;
}
}
// Map colors (Should be done elsewhere, but must be re-done after
// restarting video!)
for(i = 0; i < STAR_COLORS; i += 2)
{
int c = 64 + i * (255 - 64) / STAR_COLORS;
starcolors[STAR_COLORS - i - 1] = win->map_rgb(
win->fadergb(0x6699cc, c));
starcolors[STAR_COLORS - i - 2] = win->map_rgb(
win->fadergb(0x999966, c));
}
win->select();
for(i = 0; i < nstars; ++i)
{
int z = (int)stars[i].z >> (16 - STAR_ZBITS);
// Move stars with deltas scaled by 'z'
stars[i].x -= (dx << 8) / (z + STAR_Z0);
stars[i].y -= (dy << 8) / (z + STAR_Z0);
// Scale and center
int x = (stars[i].x * (w >> 8) >> 8) + xc;
int y = (stars[i].y * (h >> 8) >> 8) + yc;
// Plot!
win->foreground(starcolors[z * STAR_COLORS >> STAR_ZBITS]);
int s = 256 - (z * 128 >> STAR_ZBITS);
win->fillrect_fxp(x, y, s, s);
}
}
void _screen::render_background(window_t *win)
{
if(!win)
return;
if(do_noise && (noise_fade >= 1.0f))
return;
int vx, vy, xo, yo, x, y, xmax, ymax;
int mx, my;
vx = gengine->xoffs(LAYER_BASES);
vy = gengine->yoffs(LAYER_BASES);
/*
* Start exactly at the top-left corner
* of the tile visible in the top-left
* corner of the display window.
*/
xo = vx & (PIXEL2CS(CHIP_SIZEX) - 1);
yo = vy & (PIXEL2CS(CHIP_SIZEY) - 1);
mx = CS2PIXEL(vx >> CHIP_SIZEX_LOG2);
my = CS2PIXEL(vy >> CHIP_SIZEY_LOG2);
ymax = ((WSIZE+CS2PIXEL(yo)) >> CHIP_SIZEY_LOG2) + 1;
xmax = ((WSIZE+CS2PIXEL(xo)) >> CHIP_SIZEX_LOG2) + 1;
/*
* NOTE:
* We need to clear regardless of starfield mode, as the new tiles
* use alpha/colorkey.
*/
win->clear();
/* Render parallax starfield, if selected */
if(prefs->starfield == STARFIELD_PARALLAX)
render_starfield(win, vx, vy);
int tileset = B_TILES1 + (scene_num / 10) % 5;
switch(prefs->starfield)
{
case STARFIELD_NONE:
case STARFIELD_PARALLAX:
/* Ignore star tiles */
for(y = 0; y < ymax; ++y)
for(x = 0; x < xmax; ++x)
{
int n = map.pos(mx + x, my + y);
if(IS_SPACE(n))
continue;
win->sprite_fxp(PIXEL2CS(x<<CHIP_SIZEX_LOG2) - xo,
PIXEL2CS(y<<CHIP_SIZEX_LOG2) - yo,
tileset, n >> 8);
}
break;
case STARFIELD_OLD:
/* Draw all tiles, XKobo style */
for(y = 0; y < ymax; ++y)
for(x = 0; x < xmax; ++x)
{
int n = map.pos(mx + x, my + y);
int b = IS_SPACE(n) ? B_OLDSTARS : tileset;
win->sprite_fxp(PIXEL2CS(x<<CHIP_SIZEX_LOG2) - xo,
PIXEL2CS(y<<CHIP_SIZEX_LOG2) - yo,
b, n >> 8);
}
break;
}
}
void _screen::render_fx(window_t *win)
{
if(!win)
return;
render_noise(win);
render_highlight(win);
}
void _screen::fps(float f)
{
_fps = f;
}
void _screen::noise(int on)
{
do_noise = on;
}