/*(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;
}
