/*
------------------------------------------------------------
   Kobo Deluxe - An enhanced SDL port of XKobo
------------------------------------------------------------
 * Copyright (C) 1995, 1996, Akira Higuchi
 * Copyright (C) 2001-2003, 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 "kobo.h"
#include "screen.h"
#include "myship.h"
#include "enemies.h"
#include "gamectl.h"
#include "manage.h"
#include "random.h"
#include "sound.h"

#define	WING_GUN_OFFSET	4

// Unit-vector tables indexed by direction (1=N, 2=NE, ..., 8=NW). Index 0 unused.
// Odd directions are cardinals; even directions are diagonals.
static const int dir_dx[9] = { 0,  0, +1, +1, +1,  0, -1, -1, -1 };
static const int dir_dy[9] = { 0, -1, -1,  0, +1, +1, +1,  0, -1 };

_myship_state _myship::_state;
int _myship::di;
int _myship::virtx;
int _myship::virty;
int _myship::x;
int _myship::y;
int _myship::_health;
int _myship::health_time;
int _myship::explo_time;
int _myship::nose_reload_timer;
int _myship::nose_temperature;
int _myship::nose_alt = 0;
int _myship::tail_reload_timer;
int _myship::tail_temperature;
int _myship::tail_alt = WING_GUN_OFFSET;
int _myship::lapx;
int _myship::lapy;
int _myship::boltx[MAX_BOLTS];
int _myship::bolty[MAX_BOLTS];
int _myship::boltdx[MAX_BOLTS];
int _myship::boltdy[MAX_BOLTS];
int _myship::boltst[MAX_BOLTS];
cs_obj_t *_myship::object;
cs_obj_t *_myship::bolt_objects[MAX_BOLTS];
cs_obj_t *_myship::crosshair;


_myship::_myship()
{
	object = NULL;
	memset(bolt_objects, 0, sizeof(bolt_objects));
	crosshair = NULL;
}


void _myship::state(_myship_state s)
{
	switch (s)
	{
	  case dead:
		if(object)
			gengine->free_obj(object);
		object = NULL;
		if(crosshair)
			gengine->free_obj(crosshair);
		crosshair = NULL;
		break;
	  case normal:
		if(!object)
			object = gengine->get_obj(LAYER_PLAYER);
		if(object)
			cs_obj_show(object);
		break;
	}
	_state = s;
}


void _myship::free_bolt(int i)
{
	boltst[i] = 0;
	if(bolt_objects[i])
	{
		gengine->free_obj(bolt_objects[i]);
		bolt_objects[i] = NULL;
	}
}


void _myship::off()
{
	state(dead);
	for(int i = 0; i < MAX_BOLTS; i++)
		free_bolt(i);
}


int _myship::init()
{
	nose_reload_timer = 0;
	nose_temperature = 0;
	tail_reload_timer = 0;
	tail_temperature = 0;
	x = WORLD_SIZEX >> 1;
	y = (WORLD_SIZEY >> 2) * 3;
	virtx = x - (WSIZE >> 1);
	virty = y - (WSIZE >> 1);
	lapx = 0;
	lapy = 0;
	di = 1;
	state(normal);

	apply_position();

	for(int i = 0; i < MAX_BOLTS; i++)
	{
		boltx[i] = 0;
		bolty[i] = 0;
		boltdx[i] = 0;
		boltdy[i] = 0;
		free_bolt(i);
	}
	return 0;
}


void _myship::explode(int x, int y)
{
	if(explo_time == 56)
		manage.noise_out(1000);
	else if(explo_time > 64)
		return;

	int d = 4096 - (64 - explo_time)*(64 - explo_time);
	int i;
	for(i = 0; i < 2; ++i)
	{
		int dx = (int)(pubrand.get(6)) - 32;
		int dy = (int)(pubrand.get(6)) - 32;
		int vx = dx * (4096 - d) >> 8;
		int vy = dy * (4096 - d) >> 8;
		dx = dx * d >> 12;
		dy = dy * d >> 12;
		enemies.make(enemies.randexp(), x + dx, y + dy, vx, vy,
				explo_time >> 4);
	}
	++explo_time;
}


#define BEAMV1           12
#define BEAMV2           (BEAMV1*2/3)

int _myship::move()
{
	int i;
	di = gamecontrol.dir();

	if(++health_time >= game.health_fade)
	{
		health_time = 0;
		if(_health > game.health)
			--_health;
	}

	virtx = x - (WSIZE >> 1);
	virty = y - (WSIZE >> 1);
	if(_state == normal)
	{
		int vd, vo;
		if(!prefs->cmd_pushmove)
		{
			vd = 2;
			vo = 3;
		}
		else if(gamecontrol.dir_push())
			{
				vd = 1;
				vo = 1;
			}
			else
			{
				vd = 0;
				vo = 0;
			}
		int spd = (di & 1) ? vo : vd;
		virtx += dir_dx[di] * spd;
		virty += dir_dy[di] * spd;
		explo_time = 0;
	}
	else if(_state == dead)
		explode(x, y);

	lapx = 0;
	lapy = 0;
	if(virtx < 0)
	{
		virtx += WORLD_SIZEX;
		lapx = WORLD_SIZEX;
	}
	if(virtx >= WORLD_SIZEX)
	{
		virtx -= WORLD_SIZEX;
		lapx = -WORLD_SIZEX;
	}
	if(virty < 0)
	{
		virty += WORLD_SIZEY;
		lapy = WORLD_SIZEY;
	}
	if(virty >= WORLD_SIZEY)
	{
		virty -= WORLD_SIZEY;
		lapy = -WORLD_SIZEY;
	}
	x = virtx + (WSIZE >> 1);
	y = virty + (WSIZE >> 1);

	nose_temperature -= game.nosecooling;
	if(nose_temperature < 0)
		nose_temperature = 0;
	tail_temperature -= game.tailcooling;
	if(tail_temperature < 0)
		tail_temperature = 0;

	if((_state == normal) && gamecontrol.get_shot())
	{
		if(game.skill == SKILL_CLASSIC)
		{
			if(nose_reload_timer > 0)
				--nose_reload_timer;
			else
			{
				_myship::xkobo_shot();
				nose_reload_timer = game.noseloadtime - 1;
			}
		}
		else
		{
			int fired = 0;
			if(tail_reload_timer > 0)
				--tail_reload_timer;
			else if(!_myship::tail_fire())
				fired = 1;	// Ok!
			else
				fired = 2;	// Overheat!
			if(nose_reload_timer > 0)
				--nose_reload_timer;
			else if(!_myship::nose_fire())
				fired = 1;	// Ok!
			else
				fired = 2;	// Overheat!
			if(fired == 1)
				sound.g_player_fire();
			else if(fired == 2)
				sound.g_player_overheat();
		}
	}
	else
	{
		if(!gamecontrol.get_shot())
		{
			nose_alt = 1;
			sound.g_player_fire_off();
		}
		if(nose_reload_timer > 0)
			--nose_reload_timer;
		if(tail_reload_timer > 0)
			--tail_reload_timer;
	}

	const char animtab[8] = { 3, 2, 1, 0, 1, 2, 1, 2 };
	for(i = 0; i < MAX_BOLTS; i++)
	{
		if(!boltst[i])
			continue;
		++boltst[i];
		boltx[i] += lapx;
		bolty[i] += lapy;
		boltx[i] += boltdx[i];
		bolty[i] += boltdy[i];
		if((ABS(boltx[i] - x) >= (VIEWLIMIT >> 1) + 16) ||
				(ABS(bolty[i] - y) >= (VIEWLIMIT >> 1) + 16))
			free_bolt(i);
		else if(bolt_objects[i])
			cs_obj_image(bolt_objects[i], B_BOLT,
					(bolt_objects[i]->anim.frame & 0xfffffffc) +
					animtab[boltst[i] & 7]);
	}
	return 0;
}


void _myship::hit(int dmg)
{
	if(_state != normal)
		return;

	if(!dmg)
		dmg = 1000;

	if(_health && (dmg < _health))
		manage.noise_damage((float)dmg / _health);
	else
		manage.noise_damage(1.0f);

	_health -= dmg;
	if(_health > 0)
		sound.g_player_damage();
	else
	{
		_health = 0;
		manage.lost_myship();
		state(dead);
		sound.g_player_explo_start();
	}
}


void _myship::health_bonus(int h)
{
	if(game.skill == SKILL_CLASSIC)
		return;
	_health += game.health * h / 100;
	health_time = 0;
	if(_health > game.health * 2)
		_health = game.health * 2;
}


int _myship::hit_structure()
{
	int x1, y1;
	int i, ch;

	// Check bolts/objects
	for(i = 0; i < MAX_BOLTS; i++)
	{
		if(!boltst[i])
			continue;
		x1 = (boltx[i] & (WORLD_SIZEX - 1)) >> 4;
		y1 = (bolty[i] & (WORLD_SIZEY - 1)) >> 4;
		ch = screen.get_map(x1, y1);
		if(!IS_SPACE(ch) && (ch & HIT_MASK))
		{
			sound.g_bolt_hit(x1<<12, y1<<12);
			enemies.make(&boltexpl, boltx[i], bolty[i]);
			free_bolt(i);
		}
	}

	// Check player/bases
	x1 = (x & (WORLD_SIZEX - 1)) >> 4;
	y1 = (y & (WORLD_SIZEY - 1)) >> 4;
	ch = screen.get_map(x1, y1);
	if(!IS_SPACE(ch) && (ch & HIT_MASK))
	{
		if(prefs->cmd_indicator)
			sound.g_player_damage();
		else
			_myship::hit(1000);
	}
	return 0;
}


int _myship::hit_bolt(int ex, int ey, int hitsize, int health)
{
	int dmg = 0;
	int i;
	for(i = 0; i < MAX_BOLTS; i++)
	{
		if(boltst[i] == 0)
			continue;
		if(ABS(ex - boltx[i]) >= hitsize)
			continue;
		if(ABS(ey - bolty[i]) >= hitsize)
			continue;
		if(!prefs->cmd_cheat)
			free_bolt(i);
		enemies.make(&boltexpl, boltx[i], bolty[i]);
		dmg += game.bolt_damage;
		if(dmg >= health)
			break;
	}
	return dmg;
}


void _myship::put_crosshair()
{
	if(_state != normal)
		return;
	if(!crosshair)
	{
		crosshair = gengine->get_obj(LAYER_OVERLAY);
		if(crosshair)
			cs_obj_show(crosshair);
	}
	if(crosshair)
	{
		cs_obj_image(crosshair, B_CROSSHAIR, 0);
		crosshair->point.v.x = PIXEL2CS(mouse_x - 8 - MARGIN);
		crosshair->point.v.y = PIXEL2CS(mouse_y - MARGIN);
	}
}


int _myship::put()
{
	/* Player */
	apply_position();
	if(object)
		cs_obj_image(object, B_PLAYER, (di - 1) * 2);

	/* Bullets */
	int i;
	for(i = 0; i < MAX_BOLTS; i++)
	{
		if(!bolt_objects[i])
			continue;
		if(boltst[i])
		{
			bolt_objects[i]->point.v.x = PIXEL2CS(boltx[i]);
			bolt_objects[i]->point.v.y = PIXEL2CS(bolty[i]);
		}
	}
	return 0;
}


void _myship::shot_single(int i, int dir, int offset)
{
	int doffset = (offset * 7071 + (offset > 0 ? 5000 : -5000)) / 10000;
	boltst[i] = 1;
	boltx[i] = x;
	bolty[i] = y;

	// Wing-gun position offset is perpendicular to the firing direction.
	int pd = (dir + 1) % 8 + 1;
	int poff = (pd & 1) ? offset : doffset;
	boltx[i] += dir_dx[pd] * poff;
	bolty[i] += dir_dy[pd] * poff;

	int v = (dir & 1) ? BEAMV1 : BEAMV2;
	boltdx[i] = dir_dx[dir] * v;
	boltdy[i] = dir_dy[dir] * v;
	if(!bolt_objects[i])
		bolt_objects[i] = gengine->get_obj(LAYER_PLAYER);
	if(bolt_objects[i])
	{
		bolt_objects[i]->point.v.x = PIXEL2CS(boltx[i]);
		bolt_objects[i]->point.v.y = PIXEL2CS(bolty[i]);
		cs_obj_image(bolt_objects[i], B_BOLT, (((dir - 1) & 0x3) << 2) + 3);
		cs_obj_show(bolt_objects[i]);
	}
}


int _myship::xkobo_shot()
{
	int i, j;
	for(i = 0; i < game.bolts && boltst[i]; i++)
		;
	for(j = i + 1; j < game.bolts && boltst[j]; j++)
		;
	if(j >= game.bolts)
	{
		sound.g_player_overheat(1);
		return 1;
	}
	shot_single(i, di, 0);
	shot_single(j, (di > 4) ? (di - 4) : (di + 4), 0);
	sound.g_player_fire();
	return 0;
}


int _myship::nose_fire()
{
	int i, j;
	// Logic: If firing would cause overheat, wait!
	if(255 - nose_temperature < game.noseheatup)
		return 1;
	for(i = 0; i < game.bolts && boltst[i]; i++)
		;
	if(!game.altfire)
	{
		if(i >= game.bolts)
			return 1;
		shot_single(i, di, 0);
		nose_temperature += game.noseheatup;
		nose_reload_timer = game.noseloadtime;
		return 0;
	}
	for(j = i + 1; j < game.bolts && boltst[j]; j++)
		;
	if(j >= game.bolts)
		return 1;
	if(++nose_alt > 2)
		nose_alt = 0;
	shot_single(i, di, (nose_alt - 1) * WING_GUN_OFFSET);
	if(++nose_alt > 2)
		nose_alt = 0;
	shot_single(j, di, (nose_alt - 1) * WING_GUN_OFFSET);
	nose_temperature += game.noseheatup;
	nose_reload_timer = game.noseloadtime;
	return 0;
}


int _myship::tail_fire()
{
	int i;
	if(255 - tail_temperature < game.tailheatup)
		return 1;
	for(i = 0; i < game.bolts && boltst[i]; i++)
		;
	if(i >= game.bolts)
		return 1;
	if(game.altfire)
	{
		tail_alt = -tail_alt;
		shot_single(i, (di > 4) ? (di - 4) : (di + 4), tail_alt);
	}
	else
		shot_single(i, (di > 4) ? (di - 4) : (di + 4), 0);
	tail_temperature += game.tailheatup;
	tail_reload_timer = game.tailloadtime;
	return 0;
}


void _myship::set_position(int px, int py)
{
	x = px;
	y = py;
	virtx = x - (WSIZE >> 1);
	virty = y - (WSIZE >> 1);

	if(object)
	{
		apply_position();
		cs_point_force(&object->point);
	}
}


void _myship::apply_position()
{
	if(object)
	{
		object->point.v.x = PIXEL2CS(x);
		object->point.v.y = PIXEL2CS(y);
	}
	sound.g_position(x, y);
}
