/*-
 * Copyright (c) 2000
 * Tatsuya Kudoh(CDR/TK),ROYALPANDA.    All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include<stdlib.h>
#include<sys/bios.h>

#include"soari.h"

#define FLOOR		1
#define CEIL		2
#define LEFT_WALL	4
#define RIGHT_WALL	8

static map_t *Map_ptr;

static ene_t Ene_list;
static ene_t Ene_list_free;

static int Ene_life_count;

static unsigned Score;


#define map(x,y)	 (Map_ptr->map[MAP_WIDTH*((y)/64) + (x)/64])

static void create_damage_table( hero_t *hp )
{
	long l,d;
	int *p;

	p = hp->damage_table;
	for( l = 1 ; l <= 256 ; l++ ){
		d = l*l*hp->attack / 65536;
		if( d < 1 )
			d = 1;
		*p++ = d;
	}
}
		

static void init_hero( hero_t *hp )
{
#if 1
	hp->x = 16*4;
	hp->y = (Map_ptr->height * 16 - 17)*4;
#else
	hp->x = 128*4;
	hp->y = 32*4;
#endif
	hp->vx = hp->vy = 0;
	hp->stat = 0;
	hp->dir = 1;
	hp->count = 15;
	hp->attack = 24;
	hp->attack_ready_speed = 4;
	hp->attack_speed = 16;
	hp->attack_count = 0;
	hp->jump = 0;
	hp->damage = 0;
	hp->damage_time = 45;
	hp->life_max = 100;
	hp->life = hp->life_max;

	create_damage_table(hp);

}
	
static int check_wall( int x, int y )
{
	int f;

	f = 0;

	if( map(x+32,y+1) || map(x+95,y+1) )
		f |= FLOOR;
	if( map(x+32,y-128) || map(x+95,y-128) )
		f |= CEIL;
	if( map(x,y-31) || map(x,y-95) )
		f |= LEFT_WALL;
	if( map(x+127,y-31) || map(x+127,y-95) )
		f |= RIGHT_WALL;

	if( (f & LEFT_WALL) && (f & RIGHT_WALL) )
		f &= ~(LEFT_WALL|RIGHT_WALL);

	return f;
}


static void move_hero( hero_t *hp, unsigned key )
{
	int wall;

	if( hp->damage )
		hp->damage--;

	if( hp->life == 0 )
		return;

	hp->x += hp->vx;
	if( hp->vy < -12 )
		hp->y -= 12;
	else
		hp->y += hp->vy;

	wall = check_wall(hp->x,hp->y);

	if( wall & FLOOR ){
		hp->y = ((hp->y+1) & ~63) - 1;
		if( hp->vy > 0 )
			hp->vy = 0;
			hp->jump = 0;
	}else if( wall & CEIL ){
		hp->y = (hp->y & ~63) + 63;
		if( hp->vy < 0 )
			hp->vy = 0;
			hp->jump = 0;
	}

	if( wall & LEFT_WALL ){
		hp->x = (hp->x & ~63) + 64;
		if( hp->vx < 0 )
			hp->vx = 0;
	}else if( wall & RIGHT_WALL ){
		hp->x &= ~63;
		if( hp->vx > 0 )
			hp->vx = 0;
	}

	if( hp->stat != 2 ){
		if( wall & FLOOR ){
			if( --hp->count <= 0 ){
				hp->count = 15;
				hp->stat ^= 1;
			}
		}
		if( (key & KEY_A) && hp->attack_count > 32 ){
			hp->stat = 2;
		}else{
			hp->attack_count += hp->attack_ready_speed;
			if( hp->attack_count > 256 )
				hp->attack_count = 256;
		}
	}else{
		hp->attack_count -= hp->attack_speed;
		if( hp->attack_count <= 0 ){
			hp->stat = 0;
			hp->attack_count = 0;
		}
	}


	hp->vy++;
	if( hp->vy > -12 )
		hp->jump = 0;
	else if( hp->vy > 12 )
		hp->vy = 12;

	if( key & KEY_UP1 ){
		if( (wall & FLOOR) && !(wall & CEIL) ){
			hp->jump = 1;
			hp->vy = -32;
		}
	}else if( hp->jump ){
		hp->jump = 0;
		hp->vy = -12;
	}

	if( hp->vx > 8 )
		hp->vx--;
	else if( hp->vx < -8 )
		hp->vx++;

	key &= KEY_RIGHT1 | KEY_LEFT1;
	if( !(wall & LEFT_WALL) && key == KEY_LEFT1 ){
		hp->dir = 0;
		if( hp->vx > 1 )
			hp->vx -= 2;
		else if( hp->vx > -8 )
			hp->vx--;
	}else if( !(wall & RIGHT_WALL) && key == KEY_RIGHT1 ){
		hp->dir = 1;
		if( hp->vx < -1 )
			hp->vx += 2;
		else if( hp->vx < 8 )
			hp->vx++;
	}else if( wall & FLOOR ){
		if( hp->vx < 0 )
			hp->vx++;
		else if( hp->vx > 0 )
			hp->vx--;
	}
}



#if 1

static int ene_common_move( ene_t *ep )
{
	int wall;

	ep->x += ep->vx;
	if( ep->vy < -12 )
		ep->y -= 12;
	else
		ep->y += ep->vy;

	wall = check_wall(ep->x,ep->y);

	if( wall & FLOOR ){
		ep->y = ((ep->y+1) & ~63) - 1;
		if( ep->vy > 0 )
			ep->vy = 0;
	}else if( wall & CEIL ){
		ep->y = (ep->y & ~63) + 63;
		if( ep->vy < 0 )
			ep->vy = 0;
	}

	if( wall & LEFT_WALL ){
		ep->x = (ep->x & ~63) + 64;
		if( ep->vx < 0 )
			ep->vx = 0;
	}else if( wall & RIGHT_WALL ){
		ep->x &= ~63;
		if( ep->vx > 0 )
			ep->vx = 0;
	}

	if( ep->damage ){
		ep->vy++;
		if( ep->vy > 12 )
			ep->vy = 12;
		ep->damage--;
		if( wall & FLOOR ){
			if( ep->vx > 0 )
				ep->vx--;
			else if( ep->vx < -0 )
				ep->vx++;
		}
	}

	return wall;
}

static void ene_move1( ene_t *ep, hero_t *hp )
{
	int r;
	int wall;
	int x;
	int jump;


	wall = ene_common_move(ep);

	if( ep->damage )
		return;

	if( ep->dir ){
		if( ep->vx < 6 )
			ep->vx++;
	}else{
		if( ep->vx > -6 )
			ep->vx--;
	}

	if( ep->vx > 6 )
		ep->vx--;
	else if( ep->vx < -6 )
		ep->vx++;

	if( --ep->count <= 0 ){
		ep->stat = !ep->stat;
		ep->count = 15;
	}

	if( !(wall & FLOOR) ){
		ep->vy++;
		if( ep->vy > 12 )
			ep->vy = 12;
		if( wall & CEIL ){
			if( wall & LEFT_WALL )
				ep->dir = 1;
			else if( wall & RIGHT_WALL )
				ep->dir = 0;
		}
			
		return;
	}

	jump = 0;

	x = ep->x + ep->dir * 255 - 64;
	if( !map(x,ep->y+1) ){	/* no floor ahead */
		r = rnd(100);
		if( r < 10 )
			jump = 1;
		else if( r < 20 )
			ep->dir = !ep->dir;
	}else if( map(x,ep->y-32)||map(x,ep->y-96) ){ /* wall ahead */
		if( !(wall & CEIL) && rnd(100) < 50 )
			jump = 1;
		else
			ep->dir = !ep->dir;
	}

	if( jump )
		ep->vy = -32;
	else if( rnd(100) < 2 )
		ep->vy = -16;
}

static void ene_move2( ene_t *ep, hero_t *hp )
{
	int jump;
	int wall;
	int x,tx;

	wall = ene_common_move(ep);

	if( ep->damage ){
		ep->value[1] = ep->vx * 8;
		return;
	}

	if( wall & RIGHT_WALL ){
		if( ep->dir == 1 ){
			ep->dir = 0;
			ep->value[1] = -ep->value[1];
		}
	}else if( wall & LEFT_WALL ){
		if( ep->dir == 0 ){
			ep->dir = 1;
			ep->value[1] = -ep->value[1];
		}
	}

	if( wall & FLOOR ){
		tx = ep->x + ep->dir * 255 - 64;
		jump = 0;

		if( --ep->count <= 0 ){
			ep->stat = !ep->stat;
			ep->count = 15;
		}

		if( ep->value[1] > 0 ){
			ep->value[1]--;
		}else if( ep->value[1] < 0 ){
			ep->value[1]++;
		}else if( ep->value[0] > 0 ){
			ep->value[0]--;
		}else{
			if( rnd(100) < 25 )
				ep->dir = !ep->dir;
			x = rnd(48) + 48;
			if( ep->dir )
				ep->value[1] = x;
			else
				ep->value[1] = -x;
			ep->value[0] = rnd(70)+10;

			if( map(tx,ep->y-32)||map(tx,ep->y-96) ){
				jump = 1;
			}else{
				if( rnd(100) < 30 )
					jump = 1;
			}
		}

		if( abs(ep->value[1]) > 32 && !map(tx,ep->y+1) )
			jump = 1;

		if( jump )
			ep->vy = -rnd(24)-16;

	}else{
		ep->vy++;
		if( ep->vy > 12 )
			ep->vy = 12;
	}
	ep->vx = ep->value[1] / 8;
}



static void ene_move3( ene_t *ep, hero_t *hp )
{
	int wall;
	int dx,dy;

	wall = ene_common_move(ep);

	if( ep->damage ){
		ep->value[0] = rnd(2)*2;
		ep->value[3] = 0;
		return;
	}

	if( --ep->count <= 0 ){
		ep->stat = !ep->stat;
		ep->count = 15;
	}

	if( ep->value[3] ){
		if( wall ){
			ep->value[3] = 0;
		}else{
			ep->value[3]--;
			return;
		}
	}

	ep->value[2] += ep->value[1];
	if( ep->value[2] > 0 )
		ep->value[1]--;
	else if( ep->value[2] < 0 )
		ep->value[1]++;

	switch( ep->value[0] ){
	case 0:	/* UP */
		if( wall & CEIL ){
			ep->value[0] = rnd(3)+1;
		}else{
			if( ep->vy > -3 )
				ep->vy--;
			ep->vx = ep->value[1] / 2;
		}
		break;

	case 1:	/* RIGHT */
		if( wall & RIGHT_WALL ){
			ep->value[0] = rnd(3);
			if( ep->value[0] > 0 )
				ep->value[0]++;

		}else{
			dx = ep->x - hp->x;
			dy = ep->y - hp->y;
			if( dx < -128 && dx > -512 && abs(dy) < 32 ){
				ep->vx = 12;
				ep->vy = 0;
				ep->value[3] = 128;
			}else{
				if( ep->vx < 3 )
					ep->vx++;
				else if( ep->vx > 3 )
					ep->vx--;
				ep->vy = ep->value[1] / 2;
			}
		}
		break;

	case 2:	/* DOWN */
		if( wall & FLOOR ){
			ep->value[0] = rnd(3);
			if( ep->value[0] > 1 )
				ep->value[0]++;
		}else{
			if( ep->vy < 3 )
				ep->vy++;
			ep->vx = ep->value[1] / 2;
		}
		break;

	case 3:	/* LEFT */
		if( wall & LEFT_WALL ){
			ep->value[0] = rnd(3);
		}else{
			dx = ep->x - hp->x;
			dy = ep->y - hp->y;
			if( dx > 128 && dx < 512 && abs(dy) < 32 ){
				ep->vx = -12;
				ep->vy = 0;
				ep->value[3] = 128;
			}else{
				if( ep->vx > -3 )
					ep->vx--;
				else if( ep->vx < -3 )
					ep->vx++;
				ep->vy = ep->value[1] / 2;
			}
		}
		break;
	}
	if( ep->vx < 0 )
		ep->dir = 0;
	else if( ep->vx > 0 )
		ep->dir = 1;
}

static void ene_move4( ene_t *ep, hero_t *hp )
{
	int wall;
	int x,y;
	int xx,yy;
	int d;

	wall = ene_common_move(ep);

	if( ep->damage ){
		ep->value[1] = 0;
		return;
	}

	if( wall ){
		if( wall & (RIGHT_WALL|LEFT_WALL) )
			ep->value[0] ^= 1;
		else
			ep->value[0] = rnd(4);
		if( ep->value[1] ){
			ep->value[1] = 0;
			ep->value[2] = 60;
		}
	}

	if( --ep->count <= 0 ){
		ep->count = 15;
		ep->stat ^= 1;
	}

	if( !ep->value[1] ){
		switch( ep->value[0] ){
		case 0:	/* UpLeft*/
			if( ep->vx > -5 )
				ep->vx--;
			if( ep->vy > -2 )
				ep->vy--;
			break;

		case 1:	/* UpRight */
			if( ep->vx < 5 )
				ep->vx++;
			if( ep->vy > -2 )
				ep->vy--;
			break;

		case 2:	/* DownLeft */
			if( ep->vx > -5 )
				ep->vx--;
			if( ep->vy < 2 )
				ep->vy++;
			break;

		case 3:	/* DownRight */
			if( ep->vx < 5 )
				ep->vx++;
			if( ep->vy < 2 )
				ep->vy++;
			break;
		}

		if( ep->vx < 0 )
			ep->dir = 0;
		else if( ep->vx > 0 )
			ep->dir = 1;

		if( ep->value[2] > 0 ){
			ep->value[2]--;
		}else{
			x = hp->x - ep->x;
			y = hp->y - ep->y;
			xx = abs(x);
			yy = abs(y);
			d = xx-yy;
			d = abs(d);

			if( xx > 128 && yy > 128 && yy < 512 && d < 64 ){
				if( x > 0 ){
					ep->dir = 1;
					ep->vx = 8;
				}else {
					ep->dir = 0;
					ep->vx = -8;
				}

				if( y > 0 )
					ep->vy = 8;
				else 
					ep->vy = -8;

				ep->value[1] = 1;
			}
		}
	}
}


static void create_ene( ene_t *ep, int level, int boss )
{

	if( level > 64 )
		level = 64;

	ep->vx = 0;
	ep->vy = 0;
	ep->dir = rnd(2);
	ep->count = 15;
	ep->stat = rnd(2);

	if( level < 8 )
		ep->life_max = 5 + level * 10;
	else
		ep->life_max = 85 + (level-8) * 5;

	if( level < 16 )
		ep->attack = 4 + level / 4;
	else
		ep->attack = 8 + (level-16)/8;


	ep->score = level+2;
	if( boss ){
		ep->life_max *= 4;
		ep->score *= 5;
	}

	ep->damage = 0;
	ep->life = ep->life_max;

	switch( rnd(4) ){
	case 0:
		ep->func = ene_move1;
		ep->graphic = 0;
		break;

	case 1:
		ep->func = ene_move2;
		ep->value[0] = rnd(60)+10;
		ep->value[1] = 0;
		ep->graphic = 1;
		break;

	case 2:
		ep->func = ene_move3;
		ep->value[0] = rnd(4);
		ep->value[1] = 16;
		ep->value[2] = 0;
		ep->value[3] = 0;
		ep->graphic = 2;
		break;
	case 3:
		ep->func = ene_move4;
		ep->value[0] = rnd(4);
		ep->value[1] = 0;
		ep->value[2] = 60;
		ep->graphic = 3;
		break;
	}

}

static void init_ene( ene_t *ep, int num_ene, int level )
{
	if( num_ene < 1 )
		return;

	num_ene--;
	while( num_ene-- ){
		ep->next = ep+1;
		create_ene(ep,level,0);
		ep++;
	}
	ep->next = NULL;
	create_ene(ep,level,1);
}


#endif

static void check_hit( hero_t *hp, ene_t *ene_list )
{
	ene_t *ep;
	int dx,dy;
	int sx,sy;
	int vs;
	int damage;
	int damage_time;

	damage = hp->damage_table[hp->attack_count-1];
	damage_time = hp->attack_count / 6;

	ep = ene_list->next;
	sx = hp->x + hp->dir * 223 - 64;
	sy = hp->y - 64;
	vs = hp->dir * 48 - 24;
	while( ep ){
		if( ep->life > 0 && ep->damage == 0 ){
			dx = sx - ep->x;
			dy = sy - ep->y;
			if( dx >= -24 && dx < 120 && dy < -8 && dy > -120 ){
				ep->life -= damage;
				if( ep->life <= 0 ){
					ep->life = 0;
					ep->damage = 150;
				}else{
					ep->damage = damage_time;
				}
				draw_life_ene(ep);
				Ene_life_count = 400;
				ep->vx = vs;
				ep->vy = 16;
			}
		}
		ep = ep->next;
	}
}

static void check_damage( hero_t *hp, ene_t *ene_list )
{
	ene_t *ep,*epp;
	int dx,dy;
	int damage;

	damage = 0;
	ep = ene_list->next;
	while( ep ){
		if( ep->life > 0 && ep->damage == 0 ){
			dx = ep->x - hp->x + ep->dir * 96;
			dy = ep->y - hp->y;
			if( dx > -16 && dx < 112 && abs(dy) < 64 ){
				if( ep->attack > damage ){
					epp = ep;
					damage = ep->attack;
					hp->vx = ep->dir * 32 - 16;
				}
			}
		}
		ep = ep->next;
	}
	if( damage ){
		hp->life -= damage;
		if( hp->life <= 0 ){
			hp->life = 0;
			hp->damage = 225;
		}else{
			hp->damage = hp->damage_time;
		}
		draw_life_hero(hp);
		draw_life_ene(epp);
		Ene_life_count = 400;
	}
}

static void move_ene( ene_t *ene_list, hero_t *hp )
{
	ene_t *ep;
	ene_t *prev;

	ep = ene_list->next;
	prev = ene_list;

	while( ep ){
		if( ep->life == 0 ){
			ep->damage--;
			draw_ene(ep);
			if( ep->damage == 0 ){
				Score += ep->score;
				if( Score > 60000 )
					Score = 60000;
				show_score(Score);
				prev->next = ep->next;
				ep = ep->next;
			}else{
				prev = ep;
				ep = ep->next;
			}
		}else{
			if( ep->func )
				(ep->func)(ep,hp);
			draw_ene(ep);
			prev = ep;
			ep = ep->next;
		}
	}
}


static int exit_game( void )
{
	unsigned key;
	int i;

	draw_pause(1);
	while( key_press_check() & (KEY_START|KEY_Y3) )
		wait_v();
	wait_v();
	while( !((key = key_press_check()) & (KEY_START|KEY_Y3)) )
		wait_v();
	draw_pause(0);
	for( i = 0 ; i < 30 ; i++ )
		wait_v();
	if( key & KEY_START )
		return 0;
	else
		return 1;
}

	


unsigned game( int *lv, map_t *mp, hero_t *hp, ene_t *ene, int num_ene )
{
	unsigned k;
	static ene_t ene_list;
	ene_t *ep;
	int level;
	int over;

	level = *lv;

	Map_ptr = mp;

	set_fontdata_game();
	clear_sprite();

	Score = 0;

	create_map(mp,NUM_ROOM_X,NUM_ROOM_Y,15);
	init_hero(hp);
	init_ene(ene,num_ene,level);
	ene_list.next = ene;
	layout_ene(NUM_ROOM_X,NUM_ROOM_Y,&ene_list,0,NUM_ROOM_Y-1);
	init_scroll(mp,hp->x,hp->y);

	draw_hero(hp);
	ep = ene_list.next;
	while( ep ){
		draw_ene(ep);
		ep = ep->next;
	}
	refresh_screen();
	fade_in();
	draw_label();

	draw_life_hero(hp);
	show_level(level);
	show_score(Score);

	over = 0;
	for(;;){
		k = key_press_check();


		if( k & KEY_START )
			if( exit_game() )
				break;

		move_hero(hp,k);
		scroll(mp,hp->x,hp->y);

		draw_hero(hp);

		if( hp->life == 0 && hp->damage == 0 ){
			over = 1;
			break;
		}

		move_ene(&ene_list,hp);

		if( hp->stat == 2 )
			check_hit(hp,&ene_list);

		if( hp->damage == 0  )
			check_damage(hp,&ene_list);

		if( Ene_life_count ){
			Ene_life_count--;
			if( Ene_life_count == 0 )
				erase_life_ene();
		}
		refresh_screen();

		draw_attack(hp);

		if( ene_list.next == NULL ){
			Score = clear_level(hp,Score,level,level*50+50);
			fade_out();
			level++;
			create_map(mp,NUM_ROOM_X,NUM_ROOM_Y,15);
			init_hero(hp);
			init_ene(ene,num_ene,level);
			ene_list.next = ene;
			layout_ene(NUM_ROOM_X,NUM_ROOM_Y,
					&ene_list,0,NUM_ROOM_Y-1);
			init_scroll(mp,hp->x,hp->y);
			draw_hero(hp);
			ep = ene_list.next;
			while( ep ){
				draw_ene(ep);
				ep = ep->next;
			}
			refresh_screen();
			fade_in();
			draw_label();
			draw_life_hero(hp);
			show_level(level);
			show_score(Score);
		}
	}
	if( over ){
		draw_gameover();
		while( key_press_check() & (KEY_START|KEY_A) )
			wait_v();
		while( !(key_press_check() & (KEY_START|KEY_A)) )
			wait_v();
	}
	fade_out();
	clear_sprite();
	*lv = level;
	return Score;
}
