// -----------------------------------------------
// KBall Game
// By Kronoman
// Copyright (c) 2003,2004,2005, Kronoman
// In loving memory of my father
// -----------------------------------------------
// gkernel.h
// -----------------------------------------------
// Game main loop and kernel
// -----------------------------------------------

#include "gkernel.h" // kernel header

#include "gerror.h" // to show any error that may arise
#include "misc_def.h" // some definitions
#include "filehelp.h"
#include "savescrs.h" // for screenshoots
#include "ui_misc.h"

// -----------------------------------------------
// Global variables
// -----------------------------------------------
// Counter for the main timer
static volatile int speed_counter = 0;

// this 2 counters are for count the fps
static volatile int frame_count, fps;

// ---- End of global variables declaration ----

// -----------------------------------------------
// Timing stuff
// -----------------------------------------------
// Timer callback for the speed counter
void increment_speed_counter()
{
	speed_counter++;
}

END_OF_FUNCTION(increment_speed_counter);

// Timer callback for measuring the frames per second
void fps_proc(void)
{
	fps = frame_count;
	frame_count = 0;
}

END_OF_FUNCTION(fps_proc);

// ==========================================================================
// KERNEL CLASS ITSELF BELOW THIS
// ==========================================================================

int CGameKernel::timer_installed_count = 0; // how many times we installed the timer; when this is 0, remove/install timers

// -------------------------------------
// Constructor
// -------------------------------------
CGameKernel::CGameKernel()
{
	mtracer.add("CGameKernel::CGameKernel()");

	game_over = false;

	dbuffer = NULL; // doble buffer

	backdropbmp = NULL; // background bitmap for current level

	current_level_file_name[0] = '\0';
	
	stats.reset(); // reset stats
}

// -------------------------------------
// Destructor
// -------------------------------------
CGameKernel::~CGameKernel()
{
	mtracer.add("CGameKernel::~CGameKernel()");

	this->shutdown();
}

// -------------------------------------------------------------------
// Initializes game (sets timers, loads data, etc)
// -------------------------------------------------------------------
void CGameKernel::init()
{
	mtracer.add("CGameKernel::init()");

	if (dbuffer == NULL)
		dbuffer = create_bitmap(SCREEN_W, SCREEN_H); // create doble buffer bitmap

	if (dbuffer == NULL)
		raise_error("CGameKernel::init()\nERROR: unable to create doble buffer bitmap!\n");

	clear_bitmap(screen);

	textout_centre_ex(screen, font, "[   Please wait... loading...   ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64));

	// Load general data for the game
	char tmp_file_buf[2048];

	if (game_map.load_tile_set_from_file(where_is_the_filename(tmp_file_buf, TILESET_FILE)) )
		raise_error("CGameKernel::init()\nERROR!\nCan't load  \"%s\" file!\n", TILESET_FILE); // load tileset

	if (main_data_file.load_datafile(where_is_the_filename(tmp_file_buf, SPRITES_FILE)) )
		raise_error("CGameKernel::init()\nERROR!\nUnable to load \"%s\" file!\n", SPRITES_FILE);

	mtracer.add("\tGeneral data loaded OK");

	// find the ball's sprite texture and mask
	player_ball.spr = (BITMAP *)main_data_file.get_resource_dat("BALL");

	if (player_ball.spr == NULL)
		raise_error("CGameKernel::init()\nERROR! Can't find BALL sprite!\n");

	player_ball.spr_shadow = (BITMAP *)main_data_file.get_resource_dat("BALL_MASK_SHADOW");

	if (player_ball.spr_shadow == NULL)
		raise_error("CGameKernel::init()\nERROR! Can't find BALL_MASK_SHADOW sprite!\n");

	mtracer.add("\tBall data loaded OK");

	// find GUI fonts
	game_time_font = (FONT *)main_data_file.get_resource_dat("GAME_TIME_FONT");

	game_score_font = (FONT *)main_data_file.get_resource_dat("GAME_SCORE_FONT");

	game_messages_font = (FONT *)main_data_file.get_resource_dat("GAME_MESSAGES_FONT");

	// set some stuff
	game_map.timer_tick_rate = GKERNEL_FPSQ; // set timer ratio for map

	// set the timers
	if (timer_installed_count == 0)
	{
		LOCK_VARIABLE(speed_counter);
		LOCK_FUNCTION((void *)increment_speed_counter);

		LOCK_VARIABLE(fps);
		LOCK_VARIABLE(frame_count);
		LOCK_FUNCTION((void *)fps_proc);

		// install the timer
		if (install_int_ex(increment_speed_counter, BPS_TO_TIMER(GKERNEL_FPSQ)))
			raise_error("CGameKernel::init() : Can't install timer at %d bps\n", GKERNEL_FPSQ);

		if (install_int(fps_proc, 1000))
			raise_error("CGameKernel::init() : Can't install timer to measure fps.");

		mtracer.add("\tTimer installed for first time");
	}

	timer_installed_count++; // just to make sure that nobody else will remove our precious timer until we are ready too

	mtracer.add("\ttimer_installed_count = %d", timer_installed_count);

	// here start all the variables and all stuff
	game_over = false;
	frame_count = fps = 0;

	current_level_file_name[0] = '\0';
}

// -------------------------------------------------------------------
// shuts down game (unsets timers, unloads data, etc)
// -------------------------------------------------------------------
void CGameKernel::shutdown()
{
	mtracer.add("CGameKernel::shutdown()");

	// remove timer(s), only if active
	if (timer_installed_count > 0)
	{
		timer_installed_count--;

		mtracer.add("\ttimer_installed_count = %d", timer_installed_count);

		if (timer_installed_count == 0)
		{
			remove_int(increment_speed_counter);
			remove_int(fps_proc);
			timer_installed_count = 0;

			mtracer.add("\tTimer totally removed");
		}
	}

	// FREE THE RAM USED
	game_map.free_memory();

	main_data_file.nuke_datafile();

	particle_manager.nuke_particle_list();

	if (dbuffer != NULL)
	{
		destroy_bitmap(dbuffer);
		dbuffer = NULL;
	}

	if (backdropbmp != NULL)
	{
		destroy_bitmap(backdropbmp);
		backdropbmp = NULL;
	}

	current_level_file_name[0] = '\0';
	
	music_loader.free_memory();
	
	background_loader.free_memory();
}

// -------------------------------------------------------------------
// This is the main game loop
//
// **NOTICE**
// YOU **MUST** CALL IN THIS ORDER:
//   gkernel.init();
//   gkernel.load_level_file("level.map");
//   gkernel.game_loop();
//   gkernel.shutdown();
//
// Will return CBALL_IS_DEAD, or CBALL_EXIT_LEVEL (dead, or won level)
//
// **NOTE** THE SPECIAL RETURN VALUE 'GKERNEL_USER_FINISHED_GAME' MEANS THAT THE PLAYER
// DON'T WANT TO PLAY (HE HIT ESC, SO ABORT THE GAME)
// -------------------------------------------------------------------
int CGameKernel::game_loop()
{
	int ret = 0;
	mtracer.add("CGameKernel::game_loop()");

	// Main game loop
	game_over = false; // we start just now :P

	speed_counter = 0;

	// play music
	soundw.music_start();
	
	while (game_over == false || ret == 0) // don't go out if this particular game is not over, and if ret don't has a valid value (!= 0)
	{
		while (speed_counter > 0)
		{
			ret = this->update_logic(); // update game logic
			speed_counter--;

			if (key[KEY_ESC])
			{
				game_over = true;
				ret = GKERNEL_USER_FINISHED_GAME;
			} // with ESC you end the game

			if (ret == CBALL_IS_DEAD || ret == CBALL_EXIT_LEVEL)
				game_over = true; // end of this game session
		}

		this->update_screen(); // update screen

		frame_count++; // count fps

		if (key[KEY_F12]) // with F12 you take a screenshoot
		{
			soundw.music_pause();
			
			// watermark
			textout_right_ex(screen, font, "(C) 2004, Kronoman", SCREEN_W, SCREEN_H - text_height(font), makecol(64, 64, 64),-1);

			save_screenshoot("kball", 0, "bmp"); // save

			// add confirmation message

			ui_misc_text_out_shadow(screen, font, "Screenshoot saved", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(255, 255, 255));
			clear_keybuf();
			rest(800);
			clear_keybuf();
			this->update_screen();
			speed_counter = 0;
			
			soundw.music_resume();
		}

		if (key[KEY_ESC] || ret == GKERNEL_USER_FINISHED_GAME ) // with ESC you end the program
		{
			soundw.music_pause();
			
			// add confirmation message
			ui_misc_dark_bmp(screen);
			ui_misc_text_out_shadow(screen, game_messages_font, "END GAME? Y/N", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255));
			clear_keybuf();
			rest(25);
			clear_keybuf();

			if ((readkey() >> 8) == KEY_Y)
			{
				game_over = true;
				ret = GKERNEL_USER_FINISHED_GAME;
			}
			else
			{
				game_over = false;
				ret = 0; // abort the exit  -- DEBUG this, may be the misterious 0 value returned... :P
			}

			this->update_screen();

			speed_counter = 0;
			
			soundw.music_resume();
		}


	} // end of main game loop

	
	soundw.music_stop();
	
	return ret;
}

// -------------------------------------------------------------------
// this updates 1 logic update of game ; also has code to PAUSE the game (KEY P or PAUSE)
// will return CBALL_IS_DEAD, CBALL_IS_FINE, or CBALL_EXIT_LEVEL
// -------------------------------------------------------------------
int CGameKernel::update_logic()
{
	int ret = CBALL_IS_FINE;
	
	// statistics
	
	// para contar el tiempo
	static int last_t_s = 0;
	if (++last_t_s > GKERNEL_FPSQ)
	{
		stats.add_time(0,0,1);
		last_t_s = 0;	
	}
	
	stats.score = player_ball.score;
	// end statistics

	if (key[KEY_PAUSE] || key[KEY_P]) // - pause the game -
	{
		soundw.music_pause();
		
		ui_misc_dark_bmp(screen);

		ui_misc_text_out_shadow(screen, game_messages_font, "- PAUSE -", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255));

		ui_misc_wait_for_input();

		this->update_screen();
		speed_counter = 0;
		
		soundw.music_resume();
	}

	game_map.update_logic();
	particle_manager.update_logic();

	ret = player_ball.update_logic(); // return value

	if (game_map.time_m*60 + game_map.time_s <= 10) // we are running low in time!, put a warning count
	{
		static int old_time = 0;

		if (old_time != game_map.time_s) // only each second
		{
			old_time = game_map.time_s;

			char tmp_str[80];
			usprintf(tmp_str, "%d", game_map.time_s);

			particle_manager.add_particle(new CExplosiveTextParticle(player_ball.x + player_ball.spr->w / 2, player_ball.y, (float)(rand() % 100 - 50) / 100.0, (float)((rand() % 300) + 150) / -100.0, makecol(rand() % 55 + 200, 0, 0), rand() % 15 + 25, tmp_str, game_time_font));
		}
	}

	if (game_map.time_m*60 + game_map.time_s <= 0) // time out, we are so DEAD!
	{
		ret = CBALL_IS_DEAD;
	}

	if (ret == CBALL_EXIT_LEVEL) // end of level :^D
	{
		if (game_map.prize_map_indispensable > 0) // we can't exit if we have prizes left.
		{
			// the user can't exit now, so drop a visual text saying it
			if (rand() % 100 < 40)
				particle_manager.add_particle(new CTextParticle(player_ball.x + player_ball.spr->w / 2, player_ball.y + player_ball.spr->h / 2, (float)(rand() % 300 - 150) / 100.0, (float)(rand() % 300) / -100.0, makecol(rand() % 55 + 200, 0, 255), rand() % 25 + 15, string("CAN'T EXIT"), font));

			ret = CBALL_IS_FINE; // abort level exit
		}

	}
	
	soundw.music_poll();

	return ret;
}

// -------------------------------------------------------------------
// This updates the screen
// -------------------------------------------------------------------
void CGameKernel::update_screen()
{
	int xc, yc; // x y of camera
	static int blink_counter = 0; // counter for blink text timer... :P
	static bool show_fps = false; // show fps ? F11 turns it
	
	if (key[KEY_F11]) // show fps
		show_fps = !show_fps;
	
	blink_counter++;

	if (blink_counter > GKERNEL_FPSQ)
		blink_counter = 0;

	// camera position
	xc = (int)player_ball.x - SCREEN_W / 2;

	yc = (int)player_ball.y - SCREEN_H / 2;

	if (xc < 0)
		xc = 0;

	if (yc < 0)
		yc = 0;

	if (xc > TMAP_SIZE*TMAPS_W - SCREEN_W)
		xc = TMAP_SIZE * TMAPS_W - SCREEN_W;

	if (yc > TMAP_SIZE*TMAPS_H - SCREEN_H)
		yc = TMAP_SIZE * TMAPS_H - SCREEN_H;


	blit(backdropbmp, dbuffer, 0, 0, 0, 0, backdropbmp->w, backdropbmp->h); // blit background

	//text_mode( -1); // transparent background for text

	
	if (show_fps)
	{
		textprintf_ex(dbuffer, font, 0, SCREEN_H - text_height(font), makecol(255, 255, 255), -1, "%4d fps", fps);
		textout_ex(dbuffer, font, "Copyright (c) 2003, 2004, 2005, Kronoman", 0, SCREEN_H - text_height(font)*3, makecol(128, 128, 255),-1);
	}

	// ---- 3D scene (this items are draw using the Allegro's 3D software polygon routines)

	clear_scene(dbuffer); // clear 3D scene zbuffer

	player_ball.draw(dbuffer, xc, yc); // draw ball

	game_map.draw_map(dbuffer, xc, yc, SCREEN_W, SCREEN_H, 0, false); // draw map

	game_map.draw_map(dbuffer, xc, yc, SCREEN_W, SCREEN_H, 1, false); // draw top layer (the one with prizes =D)

	render_scene(); // render the 3D scene to the bitmap


	// ---- end of 3D scene

	// draw particles, they are 2D
	particle_manager.render(dbuffer, xc, yc);

	// time left message
	textprintf_centre_ex(dbuffer, game_time_font, dbuffer->w / 2 + 3, 3, (game_map.time_m*60 + game_map.time_s > 10) ? makecol(100, 0, 128) : makecol(128, 0, 0),-1,"%02d:%02d", game_map.time_m, game_map.time_s);

	textprintf_centre_ex(dbuffer, game_time_font, dbuffer->w / 2, 0, (game_map.time_m*60 + game_map.time_s > 10) ? makecol(200, 0, 255) : makecol(255, 0, 0),-1, "%02d:%02d", game_map.time_m, game_map.time_s);

	// score message
	textprintf_ex(dbuffer, game_score_font, 3, 3, makecol(128, 100, 0), -1,"%010ld", player_ball.score);

	textprintf_ex(dbuffer, game_score_font, 0, 0, makecol(255, 200, 0), -1,"%010ld", player_ball.score);

	// prizes left message
	if (game_map.prize_map_indispensable > 0)
	{
		textprintf_right_ex(dbuffer, game_score_font, dbuffer->w + 3, 3, makecol(0, 64, 128),-1, "%04d", game_map.prize_map_indispensable);
		textprintf_right_ex(dbuffer, game_score_font, dbuffer->w, 0, makecol(0, 128, 255),-1, "%04d", game_map.prize_map_indispensable);
	}
	else
	{
		int cc1, cc2; // text color for blink text
		// the EXIT blinks... :D
		if (blink_counter < GKERNEL_FPSQ / 2)
		{
			cc1 = makecol(0, 100, 128);
			cc2 = makecol(0, 200, 255);
		}
		else
		{
			cc1 = makecol(128, 100, 0);
			cc2 = makecol(255, 200, 0);

		}

		textprintf_right_ex(dbuffer, game_score_font, dbuffer->w + 3, 3, cc1,-1, "EXIT");
		textprintf_right_ex(dbuffer, game_score_font, dbuffer->w, 0, cc2,-1, "EXIT");
	}

	// lives left message
	textprintf_ex(dbuffer, game_score_font, 0 + 3, (int)(text_height(game_score_font)*1.05) + 3, makecol(128, 110, 0),-1, "Balls:%2d", player_ball.lives);

	textprintf_ex(dbuffer, game_score_font, 0, (int)(text_height(game_score_font)*1.05), makecol(255, 220, 0),-1, "Balls:%2d", player_ball.lives);

	// debug, remove this!
	//textprintf(dbuffer, font,0,0,makecol(255,255,0), "x%3f,y%3f", player_ball.anglex,player_ball.angley);

	
	//stats.print(dbuffer, 50, makecol(255,255,255), -1, font); // DEBUG -- REMOVE THIS OR MAKE IT TOGGEABLE WITH A KEY
	
	blit(dbuffer, screen, 0, 0, 0, 0, dbuffer->w, dbuffer->h); // dump the render to screen

	// yield_timeslice(); // play nice with multitask
	//rest(0); // the way of playing nice with multitask since Allegro 4.1.15 WIP
}

// -------------------------------------------------------------------
// Helper function, takes a bitmap, and returns a new allocated bitmap
// with the 1st bitmap tiled in the size of the screen
// this serves for the background imagen
// -------------------------------------------------------------------
BITMAP *CGameKernel::tile_bmp_to_screen_size(BITMAP *bmp)
{
	BITMAP *b = NULL;

	if (bmp == NULL)
		raise_error("CGameKernel::tile_bmp_to_screen_size()\nERROR: bitmap null; can't tile it!\n");

	b = create_bitmap(SCREEN_W, SCREEN_H);

	if (b == NULL)
		raise_error("CGameKernel::tile_bmp_to_screen_size()\nERROR: unable to create bitmap!\n");

	for (int y = 0; y < SCREEN_H + bmp->h; y += bmp->h)
		for (int x = 0; x < SCREEN_W + bmp->w; x += bmp->w)
			blit(bmp, b, 0, 0, x, y, bmp->w, bmp->h);

	return b;
}

// -----------------------------------------------------------------------
// Loads a level from a file, and sets the game ready to play on that level
//
// *MUST* BE CALLED BEFORE STARTING THE GAME LOOP!
// WILL LOAD THE MAP, BACKGROUND, etc
// -----------------------------------------------------------------------
void CGameKernel::load_level_file(const char *file)
{
	mtracer.add("CGameKernel::load_level_file(\"%s\")", file);

	if (game_map.load_map_from_file(file))
		raise_error("CGameKernel::load_level_file()\nERROR!\nCan't load level '%s'!\n", file);

	usprintf(current_level_file_name, "%s", file); // save current file name

	// set the player position and properties
	player_ball.x = (game_map.sxp * TMAPS_W) + (TMAPS_W - BALL_RADIUS * 2) / 2;

	player_ball.y = (game_map.syp * TMAPS_H) + (TMAPS_H - BALL_RADIUS * 2) / 2;

	player_ball.z = 1.0;

	player_ball.dx = player_ball.dy = player_ball.dz = 0.0;

	player_ball.ctmap = &game_map;

	player_ball.particle_manager = &particle_manager;

	particle_manager.nuke_particle_list(); // CLEAN THE PARTICLES FOR THE NEW LEVEL :P

	// DEBUG -- BACKGROUND! - DECIDIR QUE CORNO HAGO CON ESTO
	// Load the background from info stored on the level map
	if (backdropbmp != NULL)
	{
		destroy_bitmap(backdropbmp);
		backdropbmp = NULL;
	}

	backdropbmp = tile_bmp_to_screen_size( background_loader.get_background("backgr.dat", game_map.background_index) );

	mtracer.add("\tLoaded background (%d) of level OK", game_map.background_index);
	
	// load music
	soundw.music_load(music_loader.get_music("music_l.dat", game_map.music_index));
	mtracer.add("\tLoaded music (%d) of level OK", game_map.music_index);
}


// -----------------------------------------------------------------------
// This will start a single level game
// Will return CBALL_IS_DEAD(totally DEAD, full game over, 0 lives)
// CBALL_EXIT_LEVEL (only useful for campaign), or GKERNEL_USER_FINISHED_GAME if player cancelled game
// -----------------------------------------------------------------------
int CGameKernel::play_a_single_level(char *level_filename)
{
	int ret;
	bool do_the_loop = true; // loop, for loading level many times, etc
	int old_score = 0; // this is for restoring the score when the player loses a ball

	mtracer.add("\nCGameKernel::play_a_single_level('%s');\n", level_filename);

	this->init(); // init game kernel

	old_score = player_ball.score;

	while (do_the_loop)
	{
		mtracer.add("\tLoading and playing level.");

		this->load_level_file(level_filename);

		ret = this->game_loop();

		mtracer.add("\t--> this->game_loop = %4d\n", ret);

		// according to ret value, show the player, if we won, we lose, or if we have lives to keep playing, etc
		// the { } in the case <x>: are there to force the scope, so don't remove them (I know what I'm doing...)
		switch (ret)
		{

			case CBALL_EXIT_LEVEL:
			{
				// the player won the level, OK, so we exit
				// this value will be used by campaign function, if available

				this->update_screen();

				ui_misc_dark_bmp(screen);
				ui_misc_text_out_shadow(screen, game_messages_font, "YOU WON THE LEVEL!", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255));

				// GIVE BONUS
				int bonus = 0;
				//text_mode(makecol(0, 0, 64));

				clear_keybuf();
				rest(550);
				clear_keybuf();

				rectfill(screen, 0, SCREEN_H / 2 + text_height(game_messages_font), SCREEN_W, SCREEN_H, makecol(0, 0, 64));


				textout_centre_ex(screen, game_messages_font, "TIME BONUS", SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font), makecol(0, 255, 255),makecol(0,0,64));

				for (int i = 0; i < game_map.time_m*60 + game_map.time_s; i++)
				{
					textprintf_centre_ex(screen, game_messages_font, SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font)*2, makecol(0, 255, 255),makecol(0,0,64), "   %04d x 100 = %07d   ", i, bonus);
					bonus += 100; // 100 bonus points for each second

					// DEBUG -- falta SONIDO!

					if (keypressed() || mouse_b || joy[0].button[0].b)
						break; // let the user cancel the count

					poll_joystick();

					if (keyboard_needs_poll())
						poll_keyboard();

					if (mouse_needs_poll())
						poll_mouse();

					rest(10);
				}

				// give real bonus
				bonus = (game_map.time_m * 60 + game_map.time_s) * 100;

				player_ball.score += bonus;
				stats.score = player_ball.score;

				textprintf_centre_ex(screen, game_messages_font, SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font)*2, makecol(0, 255, 255), makecol(0,0,64),"   %04d x 100 = %07d   ", game_map.time_m*60 + game_map.time_s, bonus);

				textprintf_centre_ex(screen, game_messages_font, SCREEN_W / 2, SCREEN_H / 2 + text_height(game_messages_font)*3, makecol(0, 255, 255), makecol(0,0,64),"Score = %010ld", player_ball.score);

				//text_mode( -1);

				textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(255, 255, 255),-1);

				ui_misc_wait_for_input();

				do_the_loop = false;
			}

			break; // CBALL_EXIT_LEVEL


			case CBALL_IS_DEAD:
			{
				this->update_screen();

				ui_misc_dark_bmp(screen);

				if (game_map.time_m*60 + game_map.time_s > 0)
					ui_misc_text_out_shadow(screen, game_messages_font, "YOU LOST THE BALL!", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255));
				else
					ui_misc_text_out_shadow(screen, game_messages_font, "TIME UP!", SCREEN_W / 2, SCREEN_H / 2, makecol(0, 0, 0), makecol(0, 255, 255));

				textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(255, 255, 255),-1);

				ui_misc_wait_for_input();

				// the player died, rest one live
				player_ball.lives--;
				stats.blost++; // statistics
				stats.score = player_ball.score;

				player_ball.anglex = player_ball.angley = 0.0; // reset ball rotations

				if (player_ball.lives < 0) // the player is dead. 0 lives. GAME OVER. :(
				{

					BITMAP *bmp_game_over = (BITMAP *)main_data_file.get_resource_dat("GAME_OVER_BMP");

					blit(bmp_game_over, screen, 0, 0, 0, 0, bmp_game_over->w, bmp_game_over->h);
					
					stats.score = player_ball.score;
					stats.print(screen, 150, makecol(255,255,0), -1, game_score_font); // print stats

					// sound
					SAMPLE *game_over_smp = (SAMPLE *)main_data_file.get_resource_dat("GAME_OVER_WAV");
					play_sample(game_over_smp,255,128,1000,0);

					textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(128, 0, 0),-1);

					ui_misc_wait_for_input();

					do_the_loop = false;
					
					stats.reset(); // reset stats
				}
				else
				{
					// perdio una vida, pero le queda, jugar de nuevo el nivel
					do_the_loop = true; // load the next level, damn
					player_ball.score = old_score; // restore score
				}

			}

			break; // end of CBALL_IS_DEAD

			case GKERNEL_USER_FINISHED_GAME:
			do_the_loop = false; // the player hit <ESC>, end of game :(
			break; // GKERNEL_USER_FINISHED_GAME

			default:  // it should NEVER come here
			raise_error("ERROR at CGameKernel::play_a_single_level\nGuru meditation:\ngame_loop returned a invalid value (ret = %d)\n", ret);
			break;
		}

	}

	this->shutdown(); // shutdown this beauty...

	return ret;
}


// -----------------------------------------------------------------------
// This will start a full campaign game
// -----------------------------------------------------------------------
void CGameKernel::play_a_full_campaign(char *level_filename)
{
	char tmp_str[4096]; // buffer to handle level filenames
	int current_level = 1; // current level number
	bool finished = false;
	int ret = 0;

	mtracer.add("\nCGameKernel::play_a_full_campaign('%s')\n", level_filename);

	while (!finished)
	{
		// load and play level
		usprintf(tmp_str, "%s#%d_MAP", level_filename, current_level);
		fix_filename_slashes(tmp_str);

		// check if the level exits before trying to play it -- DEBUG -- slow code here! do some better
		PACKFILE *fp_test = pack_fopen(tmp_str, F_READ);

		if (fp_test == NULL)
		{
			// EL NIVEL NO EXISTE, LISTO, GANO

			// the player won the campaign
			
			// NOTA: dado que cuando llega aca, ya hizo el this->shutdown, los .DAT estan DESCARGADOS
			// por eso, recargo el bitmap y font en RAM y el sonido...
			
			clear_bitmap(screen);
			textout_centre_ex(screen, font, "[   Please wait... loading...   ]", SCREEN_W / 2, SCREEN_H / 2, makecol(255, 255, 255), makecol(0, 0, 64));
			
			DATAFILE *dattmp = load_datafile_object(SPRITES_FILE, "WON_BMP");
			if (!dattmp)
				raise_error("CGameKernel::play_a_full_campaign\nCan't load %s -> WON_BMP\n", SPRITES_FILE);
			BITMAP *bmp_won = (BITMAP *)dattmp->dat;

			DATAFILE *dattmp2 = load_datafile_object(SPRITES_FILE, "GAME_SCORE_FONT");
			if (!dattmp2)
				raise_error("CGameKernel::play_a_full_campaign\nCan't load %s -> GAME_SCORE_FONT\n", SPRITES_FILE);
			FONT *fs = (FONT *)dattmp2->dat;
			
			
			blit(bmp_won, screen, 0, 0, 0, 0, bmp_won->w, bmp_won->h);
					
			stats.print(screen, 150, makecol(0,0,255), -1, fs);

			// sound
			DATAFILE *dattmp3 = load_datafile_object(SPRITES_FILE, "WON_WAV");
			if (dattmp3)
			{
				play_sample((SAMPLE *)dattmp3->dat,255,128,1000,0);
			}

			textout_centre_ex(screen, font, "-- Hit any key to continue --", SCREEN_W / 2, SCREEN_H - text_height(font), makecol(255, 255, 0),-1);

			ui_misc_wait_for_input();
			
			unload_datafile_object(dattmp); // bye bye bitmap... :)
			unload_datafile_object(dattmp2); // bye bye font... :)
			if (dattmp3)
				unload_datafile_object(dattmp3); // bye bye sample
			
			dattmp = NULL;
			
			stats.reset();

			return ; // chau... finalizo!
		}

		pack_fclose(fp_test);

		ret = this->play_a_single_level(tmp_str);

		mtracer.add("\t--> this->play_a_single_level('%s') = %4d\n", tmp_str, ret);

		if (ret == CBALL_EXIT_LEVEL) // exit level!
		{
			current_level++;
			finished = false;
		}
		else
		{
			finished = true; // any other value means GAME OVER, sorry d00d, insert coin :P
		}

	}

}

