/** 
 *  _   _        _  _                           _             
 * | |_| |  ___ | || |  ___   __ _  _ __  _ __ (_)  ___  _ __ 
 * |  _  | / _ \| || | / __| / _` || '__|| '__|| | / _ \| '__|
 * | | | ||  __/| || || (__ | (_| || |   | |   | ||  __/| |   
 * |_| |_| \___||_||_| \___| \__,_||_|   |_|   |_| \___||_|   
 * 
 *  (c) 2001 Free Lunch Design, Teknisk-IT, AerotechTelub
 *			  http://www.freelunchdesign.com
 * 
 * This source code is released as is under GPL (General Public
 * License). Please refer to license.txt for more information.
 * 
 */
 
#include <stdio.h>
#include <math.h>
#include <string.h>

#include "allegro.h"
#include "jgmod.h"
#include "menu.h"
#include "control.h"
#include "object.h"
#include "mission.h"
#include "scenery.h"
#include "building.h"
#include "soldier.h"
#include "player.h"
#include "smoke.h"
#include "pickup.h"
#include "blob.h"
#include "debris.h"

// datafiles headers
#include "../data/general.h"
#include "../data/music.h"

// init menu structs
Tmenu diff_menu[] = {
	{ "Easy",		IDM_D_EASY,		0,	0,	0 },
	{ "Normal",		IDM_D_NORMAL,		0,	0,	0 },
	{ "Hard",		IDM_D_HARD,		0,	0,	MF_LAST }
};

Tmenu main_menu[] = {
	{ "New Game",		IDM_NEWGAME,		0,	0,	0 },
	{ "Load Game",		IDM_LOADGAME,		0,	0,	0 },
	{ "Options",		IDM_OPTIONS,		0,	0,	0 },
	{ "Instructions",	IDM_INSTRUCTIONS,	0,	0,	0 },
//	{ "Hall of Fame",	IDM_HIGHSCORES,		0,	0,	0 },
	{ "Credits",		IDM_CREDITS,		0,	0,	0 },
	{ "Exit",			IDM_EXIT,			0,	0,	MF_LAST }
};

Tmenu opt_menu[] = {
	{ "Sound",			IDM_MUTE_SOUND,	IDM_DEC_SOUND,	IDM_INC_SOUND,	MF_DRAW_SOUND },
	{ "Music",			IDM_MUTE_MUSIC,	IDM_DEC_MUSIC,	IDM_INC_MUSIC,	MF_DRAW_MUSIC },
	{ "Video Options",	IDM_VIDEO_OPTIONS, 0,			0,				0 },
	{ "Controls",		IDM_CONTROLS,	   0,			0,				0 },
//	{ "Size",			0,				IDM_DEC_SIZE,	IDM_INC_SIZE,	MF_DRAW_SIZE },
	{ "Back",			IDM_BACK,			0,	0,	MF_LAST }
};

Tmenu game_menu[] = {
	{ "Resume Game",	IDM_RESUME,		0,	0,	0 },
	{ "Options",		IDM_OPTIONS,	0,	0,	0 },
	{ "Help",			IDM_HELP,		0,	0,	0 },
	{ "End Game",		IDM_END,		0,	0,	MF_LAST }
};

Tmenu video_menu[] = {
	{ "Ground Detail",	0,					IDM_DEC_DETAIL,IDM_INC_DETAIL,MF_DRAW_DETAIL },
	{ "Shadows",		IDM_TOGGLE_SHADOWS,IDM_TOGGLE_SHADOWS,IDM_TOGGLE_SHADOWS,MF_DRAW_SHADOWS },
	{ "Smoke",			IDM_TOGGLE_SMOKE,IDM_TOGGLE_SMOKE,IDM_TOGGLE_SMOKE,MF_DRAW_SMOKE },
	{ "FPS",			0,				IDM_INC_FRAMES,	IDM_DEC_FRAMES,	MF_DRAW_FRAMES },
	{ "Back",			IDM_BACK,			0,	0,	MF_LAST }
};

Tmenu ctrl_menu[] = {
	{ "Control",		IDM_TOGGLE_CONTROL,	IDM_TOGGLE_CONTROL,IDM_TOGGLE_CONTROL,MF_DRAW_CONTROL },
	{ "Joystick",		IDM_TOGGLE_ANALOG,	IDM_TOGGLE_ANALOG,IDM_TOGGLE_ANALOG,MF_DRAW_ANALOG },
//	{ "Define Keys",	IDM_SET_KEYS,		0,			0,				0 },
//	{ "Reset Keys",		IDM_RESET_KEYS,		0,			0,				0 },
	{ "Back",			IDM_BACK,			0,			0,				MF_LAST }
};


// some function headers
void main_menu_callback(void);
void game_menu_callback(void);
int handle_menu(Tmenu *menu, void (*callback)(void));
int break_text(BITMAP *bmp, char *txt, int ox, int oy, int w, int col, bool step);
void COP_draw_stat_bar(BITMAP *bmp, char *lbl, int len, int val, int x, int y);
void blit_to_screen(BITMAP *bmp);
void show_text(char *txt);
char *get_line(char *src, char *dest);
void show_credits();

control *ctrl;
Toptions options;
double difficulty = 1.0;

// timing variables
volatile int fps, frame_count, game_count, sec_count;

// bitmaps and the like
BITMAP *swap_screen;
BITMAP *targ_ptr;

// flags
bool got_sound = TRUE;

// datafiles
DATAFILE *gen = NULL;			// general stuff, fonts etc
DATAFILE *sfx;
DATAFILE *msc;

// color maps
RGB_MAP rgb_table;
COLOR_MAP trans_table_50;
COLOR_MAP trans_table_100;
COLOR_MAP fire_table;
COLOR_MAP explosion_table;
COLOR_MAP white_table;
COLOR_MAP black_table;

// cheats
int cheat_sequence[16];
int cs_pos;
int cht_guns[] = { KEY_M, KEY_A, KEY_R, KEY_K, KEY_S, KEY_M, KEY_A, KEY_N, NULL };
int cht_fire[] = { KEY_F, KEY_I, KEY_R, KEY_E, KEY_M, KEY_A, KEY_N, NULL };
int cht_home[] = { KEY_H, KEY_O, KEY_M, KEY_E, KEY_B, KEY_O, KEY_Y, NULL };
int cht_demo[] = { KEY_D, KEY_E, KEY_M, KEY_O, KEY_L, KEY_I, KEY_T, KEY_I, KEY_O, KEY_N, NULL };
int cht_shld[] = { KEY_P, KEY_R, KEY_O, KEY_T, KEY_E, KEY_C, KEY_T, KEY_M, KEY_E, NULL };
int cht_cat[]  = { KEY_F, KEY_E, KEY_L, KEY_I, KEY_X, KEY_T, KEY_H, KEY_E, KEY_C, KEY_A, KEY_T, NULL };
int cht_beam[]  = { KEY_B, KEY_E, KEY_A, KEY_M, KEY_M, KEY_E, KEY_U, KEY_P, NULL };
bool special_fx[7];
char woody_str[] = "aoodahuck";

// text messages
char text_message[64];
int text_time = 0;
int text_color = 1;

///////////////////////////////////////////////////////////////
// DEBUG STUFF
///////////////////////////////////////////////////////////////

// shows a text message
// char *func - name of calling function (null terminated)
// char *txt - message to be shown (null terminated)
void my_alert(char *func, char *txt) {
	PALETTE p;

	get_palette(p);
	set_palette(desktop_palette);
	alert("Heli Alert", func, txt, "Righty", "Amazing", 13, 27);
	set_palette(p);
}

void show_number(char *txt, double d) {
	//PALETTE p;

	//get_palette(p);
	//set_palette(desktop_palette);
	char str[256];
	sprintf(str, "%f", d);
	alert("Heli Alert", txt, str, "Righty", "Amazing", 13, 27);
	//set_palette(p);
}

char *get_pwd() {
	return woody_str;
}


///////////////////////////////////////////////////////////////
// INTRO STUFF
///////////////////////////////////////////////////////////////

int my_rest(int msec) {
	int time = msec / FRAME_LENGTH;
	game_count = 0;
	while(game_count < time) if (key[KEY_ESC]) return 1;
	return 0;
}

// show the introduction
void intro() {
	packfile_password(woody_str);
	DATAFILE *d = load_datafile("data/intro.dat");
	packfile_password(NULL);
	PALETTE pal;
	if (d==NULL) return;

	BITMAP *bmp = (BITMAP *) d[1].dat;
	BITMAP *big = (BITMAP *) d[0].dat;

	set_palette(black_palette);
	clear(screen);
	rectfill(screen, 0, 45, 640, 435, 77);

	for(int i=0;i<256;i++) {
		pal[i].r = ((RGB*)d[2].dat)[77].r;
		pal[i].g = ((RGB*)d[2].dat)[77].g;
		pal[i].b = ((RGB*)d[2].dat)[77].b;
	}
	set_palette(pal);
	
	vsync();
	if (my_rest(500)) return;
	draw_sprite(screen, big, SCREEN_W/2-big->w/2, SCREEN_H/2-big->h/2);
	fade_from(pal, (RGB*)d[2].dat, 2);
	if (my_rest(200)) return;
	for(i=100;i<120;i+=4) {
		game_count=0;
		clear_to_color(swap_screen, 77);
		stretch_sprite(swap_screen, big, SCREEN_W/2-i/100.0*big->w/2, SCREEN_H/2-i/100.0*big->h/2, i/100.0*big->w, i/100.0*big->h);
		rectfill(swap_screen, 0, 0, 640, 44, 1);
		rectfill(swap_screen, 0, 480, 640, 436, 1);
		blit_to_screen(swap_screen);
		if (key[KEY_ESC]) return;
		while(!game_count);
	}
	for(i=120;i>=0;i-=8) {
		game_count=0;
		clear_to_color(swap_screen, 77);
		stretch_sprite(swap_screen, big, SCREEN_W/2-i/100.0*big->w/2, SCREEN_H/2-i/100.0*big->h/2, i/100.0*big->w, i/100.0*big->h);
		rectfill(swap_screen, 0, 0, 640, 44, 1);
		rectfill(swap_screen, 0, 480, 640, 436, 1);
		blit_to_screen(swap_screen);
		if (key[KEY_ESC]) return;
		while(!game_count);
	}
	for(i=0;i<100;i++) {
		game_count=0;
		clear_to_color(swap_screen, 77);
		stretch_sprite(swap_screen, bmp, SCREEN_W/2-i/100.0*bmp->w/2, SCREEN_H/2-i/100.0*bmp->h/2, i/100.0*bmp->w, i/100.0*bmp->h);
		rectfill(swap_screen, 0, 0, 640, 44, 1);
		rectfill(swap_screen, 0, 480, 640, 436, 1);
		blit_to_screen(swap_screen);
		if (key[KEY_ESC]) return;
		while(!game_count);
	}
	clear(swap_screen);
	rectfill(swap_screen, 0, 45, 640, 435, 77);
	draw_sprite(swap_screen, bmp, SCREEN_W/2-bmp->w/2, SCREEN_H/2-bmp->h/2);
	blit_to_screen(swap_screen);

	if (my_rest(1000)) return;
	fade_out(4);
	clear(screen);
	set_palette((RGB*)d[7].dat);
	if (my_rest(300)) return;
	
	bmp = (BITMAP *) d[6].dat;
	for(int x=0;x<640;x++) {
		game_count = 0;
		frame_count ++;
		for(int y=0;y<bmp->h;y++) {
			hline(screen, x, SCREEN_H/2-bmp->h/2+y, x+rand()%(x+1), bmp->line[y][x]);
		}
		if (key[KEY_ESC]) return;
		if (!(frame_count % 3)) while(!game_count);
	}
	if (my_rest(1000)) return;
	fade_out(4);

	// show fld_logo
	blit_to_screen((BITMAP*)d[3].dat);
	fade_in((RGB*)d[5].dat, 4);
	if (my_rest(400)) return;
	blit_to_screen((BITMAP*)d[4].dat);
	if (my_rest(200)) return;
	blit_to_screen((BITMAP*)d[3].dat);
	if (my_rest(50)) return;
	blit_to_screen((BITMAP*)d[4].dat);
	if (my_rest(100)) return;
	blit_to_screen((BITMAP*)d[3].dat);
	if (my_rest(100)) return;
	blit_to_screen((BITMAP*)d[4].dat);
	if (my_rest(50)) return;
	blit_to_screen((BITMAP*)d[3].dat);
	if (my_rest(50)) return;
	blit_to_screen((BITMAP*)d[4].dat);
	if (my_rest(1100)) return;
	fade_out(4);

	unload_datafile(d);
}

void intro2() {
	char buf[128];
	char lines[64][128];
	char *cred;
	char *ptr;
	bool done = FALSE;
	int num_lines; 
	int i;

	clear(screen);
	
	// read credits from file
	num_lines = 0;
	cred = ptr = (char *)gen[INTRO_TXT].dat;
	while(!done) {
	    ptr = get_line(ptr, buf);  // read a line
		if (stricmp(buf, "#end#")) {
			strcpy(lines[num_lines], buf); 
			num_lines++;
		}
		else done = TRUE;
	}

	create_rgb_table(&rgb_table, (RGB *)gen[INTRO_PAL].dat, NULL);
	rgb_map = &rgb_table;
	create_light_table(&black_table, (RGB *)gen[INTRO_PAL].dat,  0,  0,  0, NULL);

	blit_to_screen((BITMAP *)gen[INTRO].dat);

	// show credits
	char *txt;
	int the_font;
	int justify;
	int y_pos = SCREEN_H;
	bool f = TRUE;

	color_map = &black_table;
	drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);

	while(key[KEY_ESC] || key[KEY_ENTER] || ctrl->is_fire1()) ctrl->poll_controls();

	done = FALSE;
	while(!done && y_pos > - 20) {
		game_count = 0;
		frame_count ++;

		clear(swap_screen);

		// draw text
		for(i=0;i<num_lines;i++) {
			if (y_pos + i*20 < SCREEN_H && y_pos + i*20 > SCREEN_H-120) {
				txt = lines[i] + 2;
				the_font = (lines[i][1] == '1' ? FONT_DIALOG_BOLD : FONT_DIALOG);
				justify = (lines[i][0] == '<' ? 0 : (lines[i][0] == '-' ? 1 : 2));
	
				if (justify == 0) textout(swap_screen, (FONT *)gen[the_font].dat, txt, 80, y_pos + i*20, 1);
				else if (justify == 1) textout_centre(swap_screen, (FONT *)gen[the_font].dat, txt, 320, y_pos + i*20, 1);
				else textout_right(swap_screen, (FONT *)gen[the_font].dat, txt, 560, y_pos + i*20, 1);
			}
		}

		blit((BITMAP *)gen[INTRO].dat, swap_screen, 0, 0, 0, 0, 640, 480);

		for(i=0;i<8;i++) {
			hline(swap_screen, 0, 390-i, 640, i*32);
			hline(swap_screen, 0, 390+i, 640, i*32);
		}

	
		blit_to_screen(swap_screen);
		if (f) {
			fade_in((RGB *)gen[INTRO_PAL].dat, 4);
			f = FALSE;
		}

		// move text
		if (frame_count%2) y_pos --;
		if (y_pos < -num_lines*20-10) y_pos = SCREEN_H;

		// skip it?
		ctrl->poll_controls();
		if (key[KEY_ESC] || key[KEY_ENTER] || ctrl->is_fire1()) done = TRUE;

		//if (!key[KEY_TAB]) 
		while(game_count<2);
	}

	solid_mode();
	

	fade_out(4);
}

///////////////////////////////////////////////////////////////
// PALETTE STUFF
///////////////////////////////////////////////////////////////

// x = bgcolor, y = new color
void return_explosion_color(AL_CONST PALETTE pal, int y, int x, RGB *rgb) {
/*
	int c;
	int n = 99;
	if (x < 208 || x > 239) c = x;
	else c = MID(208, x + y, 239);
	if (y == 208) c = 208;

	rgb->r = (pal[c].r * n + pal[(c!=208?c:x)].r)/(n+1);
	rgb->g = (pal[c].g * n + pal[(c!=208?c:x)].g)/(n+1);
	rgb->b = (pal[c].b * n + pal[(c!=208?c:x)].b)/(n+1);
*/
	///*  this code works with circlefill()
	int c;
	int n = 3;
	int min = 212;
	if (x < min || x > 239) c = min;
	else c = MIN(x+y, 239);

	rgb->r = (pal[c].r * n + pal[(c!=min?c:x)].r)/(n+1);
	rgb->g = (pal[c].g * n + pal[(c!=min?c:x)].g)/(n+1);
	rgb->b = (pal[c].b * n + pal[(c!=min?c:x)].b)/(n+1);
	//*/
}


///////////////////////////////////////////////////////////////
// TIMER STUFF
///////////////////////////////////////////////////////////////

//	keeps track of frames each second
void fps_counter(void) {
	fps = frame_count;
	frame_count = 0;
	sec_count ++;
}
END_OF_FUNCTION(fps_counter);

// keeps track of internal game speed
void game_counter(void) {
	game_count++;
}
END_OF_FUNCTION(game_counter);

// initiates the timers
void init_timers() {
	install_timer();
	LOCK_VARIABLE(game_count);
	LOCK_VARIABLE(frame_count);
	LOCK_VARIABLE(fps);
	LOCK_VARIABLE(frame_count);
	LOCK_VARIABLE(sec_count);
	LOCK_FUNCTION(fps_counter);
	install_int(fps_counter,1000);
	fps=0;
	frame_count=0;
	LOCK_FUNCTION(game_counter);
	install_int(game_counter, FRAME_LENGTH);	
}


///////////////////////////////////////////////////////////////
// INIT, QUIT, LOAD, SAVE, ETC STUFF
///////////////////////////////////////////////////////////////

// clear cheat
void clear_cheat() {
	for(int i=0;i<16;i++) cheat_sequence[i] = KEY_X;
	cs_pos = 0;
}
			
// reads and sets the options according to config file
// sets to default if config errors
void get_options() {
	// read options
	options.detail_level = get_config_int("OPTIONS","Detail", DETAIL_HIGH);
	options.music_mute = get_config_int("OPTIONS","MusicMute", 0);
	options.music_volume = get_config_int("OPTIONS","MusicVolume", 200);
	options.sound_mute = get_config_int("OPTIONS","SoundMute", 0);
	options.sound_volume = get_config_int("OPTIONS","SoundVolume", 200);
	options.size = get_config_int("OPTIONS", "Size", 10);
	options.frame_step = get_config_int("OPTIONS", "FrameStep", 1);
	options.use_joy = get_config_int("OPTIONS", "UseJoy", FALSE);
	options.analog_joy = get_config_int("OPTIONS", "Analog", 0);
	options.shadow = get_config_int("OPTIONS", "Shadow", TRUE);
	options.smoke = get_config_int("OPTIONS", "Smoke", TRUE);
	// read controls
	ctrl->key_up = get_config_int("CONTROLS", "Up", KEY_UP);
	ctrl->key_down = get_config_int("CONTROLS", "Down", KEY_DOWN);
	ctrl->key_left = get_config_int("CONTROLS", "Left", KEY_LEFT);
	ctrl->key_right = get_config_int("CONTROLS", "Right", KEY_RIGHT);
	ctrl->key_strafe = get_config_int("CONTROLS", "Strafe", KEY_ALT);
	ctrl->key_fire1 = get_config_int("CONTROLS", "Fire1", KEY_LCONTROL);
	ctrl->key_fire2 = get_config_int("CONTROLS", "Fire2", KEY_LSHIFT);
	ctrl->key_toggle = get_config_int("CONTROLS", "Toggle", KEY_Z);
	ctrl->key_land = get_config_int("CONTROLS", "Land", KEY_SPACE);
	ctrl->analog_joy = options.analog_joy;
	ctrl->use_joy = options.use_joy;
}


void report_init(char *txt) {
	static y = 10;
	textout(screen, font, txt, 10, y, 15);
	rectfill(screen, y*2, 420, y*2+12, 460, 5);
	rectfill(screen, y*2-20, 420, y*2-8, 460, 10);
	rectfill(screen, y*2-40, 420, y*2-28, 460, 15);
	y += 10;
}

//  inits the game
void init_game() {
	bool joy_available = FALSE;
	bool hold = FALSE;
	bool errors = FALSE;
	RGB col;
	int i;

	set_uformat(U_ASCII);

	allegro_init();
	text_mode(-1);

	set_color_depth(8);
	set_gfx_mode(GFX_AUTODETECT, 640, 480, 0, 0);
	//set_gfx_mode(GFX_DIRECTX_WIN, 640, 480, 0, 0);
	
	// set colors for feedback
	for(i=0;i<16;i++) {
		col.r = col.g = col.b = i*16;
		set_color(i, &col);
	}
		

	woody_str[0] = 'w';
	woody_str[4] = 'c';
	clear(screen);

	// show cool pic here?

	report_init("installing keyboard");
	install_keyboard();

	report_init("installing mouse");
	install_mouse();

	report_init("installing timers");
	init_timers();

	report_init("reading configuration file");
	set_config_file("data/config.txt");

	report_init("installing sound");
    reserve_voices(32, -1);     // JGMOD: call this before install_sound 
	got_sound = TRUE;
	if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, NULL)) {
		report_init("   failed");
		hold = TRUE;
		got_sound = FALSE;
	}
	else {
		report_init("installing mod player");
		if (install_mod(8) < 0) {			// JGMOD: call install_mod only after install_sound
			report_init("   failed");
			hold = TRUE;
			got_sound = FALSE;
		}
	}

	report_init("reserving memory");
	swap_screen = create_bitmap(GAME_W, GAME_H);
	targ_ptr = create_bitmap(18, 18);
	if (swap_screen == NULL || targ_ptr == NULL) {
		report_init("   failed reserve memory");
		errors = TRUE;
	}

	// load data
	packfile_password(woody_str);
	report_init("loading data");
	msc = load_datafile("data/music.dat");
	if (msc == NULL) {
		report_init("   failed to load music.dat");
		errors = TRUE;
	}
	gen = load_datafile("data/general.dat");
	if (gen == NULL) {
		report_init("   failed to load general.dat");
		errors = TRUE;
	}
	((RGB *)gen[0].dat)[0].r = ((RGB *)gen[0].dat)[0].g = ((RGB *)gen[0].dat)[0].b = 0;
	((RGB *)gen[TITLE_PAL].dat)[0].r = ((RGB *)gen[TITLE_PAL].dat)[0].g = ((RGB *)gen[TITLE_PAL].dat)[0].b = 0;

	sfx = load_datafile("data/sfx.dat");
	if (sfx == NULL) {
		report_init("   failed to load sfx.dat");
		errors = TRUE;
	}
	packfile_password(NULL);

	report_init("installing game controller");
	if (!install_joystick(JOY_TYPE_AUTODETECT)) {
		joy_available = TRUE;
	}
	else {
		report_init("   no controller detected");
		hold = TRUE;
	}

	// initiations and stuff
	report_init("creating menu objects and controls");
	ctrl = new control(joy_available);
	init_menu(gen, &options);

	// create fx for title
	report_init("creating menu fx");
	create_light_table(&fire_table, (RGB *)gen[TITLE_PAL].dat, 63, 63, 0, NULL);

	// get options from config file
	report_init("setting up options from file");
	get_options();
	set_mod_volume((options.music_mute?0:options.music_volume));

	if (errors) {
		report_init("");
		report_init("There were some errors.");
		report_init("Press any key to exit.");
		exit(1);
	}
	if (hold) {
		report_init("");
		report_init("Press any key to continue.");
		readkey();
	}

	text_mode(-1);
}


// shots allegro down, saves configs etc
void shut_down() {
	// todo: unload and destroy stuff

	// save config
	set_config_int("OPTIONS","Detail", options.detail_level);
	set_config_int("OPTIONS","MusicMute", options.music_mute);
	set_config_int("OPTIONS","MusicVolume", options.music_volume);
	set_config_int("OPTIONS","SoundMute", options.sound_mute);
	set_config_int("OPTIONS","SoundVolume", options.sound_volume);
	set_config_int("OPTIONS", "Size", options.size);
	set_config_int("OPTIONS", "FrameStep", options.frame_step);
	set_config_int("OPTIONS", "UseJoy", options.use_joy);
	set_config_int("OPTIONS", "Analog", options.analog_joy);
	set_config_int("OPTIONS", "Shadow", options.shadow);
	set_config_int("OPTIONS", "Smoke", options.smoke);
	// save controls
	set_config_int("CONTROLS", "Up",     ctrl->key_up);
	set_config_int("CONTROLS", "Down",   ctrl->key_down);
	set_config_int("CONTROLS", "Left",   ctrl->key_left);
	set_config_int("CONTROLS", "Right",  ctrl->key_right);
	set_config_int("CONTROLS", "Strafe", ctrl->key_strafe);
	set_config_int("CONTROLS", "Fire1",  ctrl->key_fire1);
	set_config_int("CONTROLS", "Fire2",  ctrl->key_fire2);
	set_config_int("CONTROLS", "Toggle", ctrl->key_toggle);
	set_config_int("CONTROLS", "Land",   ctrl->key_land);

	//	my_alert("Bye!","So long");

	allegro_exit();
}


///////////////////////////////////////////////////////////////
// HELPERS
///////////////////////////////////////////////////////////////

void key_2_str(int k, char *dest) {
    if (k == KEY_A) strcpy(dest, "A");
    else if (k == KEY_B) strcpy(dest, "B");
    else if (k == KEY_C) strcpy(dest, "C");              
    else if (k == KEY_D) strcpy(dest, "D");
    else if (k == KEY_E) strcpy(dest, "E");
    else if (k == KEY_F) strcpy(dest, "F");
    else if (k == KEY_G) strcpy(dest, "G");
    else if (k == KEY_H) strcpy(dest, "H");
    else if (k == KEY_I) strcpy(dest, "I");
    else if (k == KEY_J) strcpy(dest, "J");
    else if (k == KEY_K) strcpy(dest, "K");
    else if (k == KEY_L) strcpy(dest, "L");
    else if (k == KEY_M) strcpy(dest, "M");
    else if (k == KEY_N) strcpy(dest, "N");
    else if (k == KEY_O) strcpy(dest, "O");
    else if (k == KEY_P) strcpy(dest, "P");
    else if (k == KEY_Q) strcpy(dest, "Q");
    else if (k == KEY_R) strcpy(dest, "R");
    else if (k == KEY_S) strcpy(dest, "S");
    else if (k == KEY_T) strcpy(dest, "T");
    else if (k == KEY_U) strcpy(dest, "U");
    else if (k == KEY_V) strcpy(dest, "V");
    else if (k == KEY_W) strcpy(dest, "W");
    else if (k == KEY_X) strcpy(dest, "X");
    else if (k == KEY_Y) strcpy(dest, "Y");
    else if (k == KEY_Z) strcpy(dest, "Z");
    else if (k == KEY_0) strcpy(dest, "0");
    else if (k == KEY_1) strcpy(dest, "1");
    else if (k == KEY_2) strcpy(dest, "2");
    else if (k == KEY_3) strcpy(dest, "3");
    else if (k == KEY_4) strcpy(dest, "4");
    else if (k == KEY_5) strcpy(dest, "5");
    else if (k == KEY_6) strcpy(dest, "6");
    else if (k == KEY_7) strcpy(dest, "7");
    else if (k == KEY_8) strcpy(dest, "8");
    else if (k == KEY_9) strcpy(dest, "9");
    else if (k == KEY_0_PAD) strcpy(dest, "0 (Pad)");
    else if (k == KEY_1_PAD) strcpy(dest, "1 (Pad)");
    else if (k == KEY_2_PAD) strcpy(dest, "2 (Pad)");
    else if (k == KEY_3_PAD) strcpy(dest, "3 (Pad)");
    else if (k == KEY_4_PAD) strcpy(dest, "4 (Pad)");
    else if (k == KEY_5_PAD) strcpy(dest, "5 (Pad)");
    else if (k == KEY_6_PAD) strcpy(dest, "6 (Pad)");
    else if (k == KEY_7_PAD) strcpy(dest, "7 (Pad)");
    else if (k == KEY_8_PAD) strcpy(dest, "8 (Pad)");
    else if (k == KEY_9_PAD) strcpy(dest, "9 (Pad)");
    else if (k == KEY_F1) strcpy(dest, "F1");
    else if (k == KEY_F2) strcpy(dest, "F2");
    else if (k == KEY_F3) strcpy(dest, "F3");
    else if (k == KEY_F4) strcpy(dest, "F4");
    else if (k == KEY_F5) strcpy(dest, "F5");
    else if (k == KEY_F6) strcpy(dest, "F6");
    else if (k == KEY_F7) strcpy(dest, "F7");
    else if (k == KEY_F8) strcpy(dest, "F8");
    else if (k == KEY_F9) strcpy(dest, "F9");
    else if (k == KEY_F10) strcpy(dest, "F10");
    else if (k == KEY_F11) strcpy(dest, "F11");
    else if (k == KEY_F12) strcpy(dest, "F12");
    else if (k == KEY_ESC) strcpy(dest, "ESC");
    else if (k == KEY_TILDE) strcpy(dest, "~");
    else if (k == KEY_MINUS) strcpy(dest, "-");
    else if (k == KEY_EQUALS) strcpy(dest, "=");
    else if (k == KEY_BACKSPACE) strcpy(dest, "Backspace");
    else if (k == KEY_TAB) strcpy(dest, "Tab");
    else if (k == KEY_OPENBRACE) strcpy(dest, "{");
    else if (k == KEY_CLOSEBRACE) strcpy(dest, "}");
    else if (k == KEY_ENTER) strcpy(dest, "Enter");
    else if (k == KEY_COLON) strcpy(dest, ":");
    else if (k == KEY_QUOTE) strcpy(dest, "'");
    else if (k == KEY_BACKSLASH) strcpy(dest, "\\");
    else if (k == KEY_BACKSLASH2) strcpy(dest, "\\");
    else if (k == KEY_COMMA) strcpy(dest, ",");
    else if (k == KEY_STOP) strcpy(dest, ".");
    else if (k == KEY_SLASH) strcpy(dest, "/");
    else if (k == KEY_SPACE) strcpy(dest, "Space");
    else if (k == KEY_INSERT) strcpy(dest, "Insert");
    else if (k == KEY_DEL) strcpy(dest, "Delete");
    else if (k == KEY_HOME) strcpy(dest, "Home");
    else if (k == KEY_END) strcpy(dest, "End");
    else if (k == KEY_PGUP) strcpy(dest, "Page Up");
    else if (k == KEY_PGDN) strcpy(dest, "Page Down");
    else if (k == KEY_LEFT) strcpy(dest, "Left Arrow");
    else if (k == KEY_RIGHT) strcpy(dest, "Right Arrow");
    else if (k == KEY_UP) strcpy(dest, "Up Arrow");
    else if (k == KEY_DOWN) strcpy(dest, "Down Arrow");
    else if (k == KEY_SLASH_PAD) strcpy(dest, "/ (Pad)");
    else if (k == KEY_ASTERISK) strcpy(dest, "*");
    else if (k == KEY_MINUS_PAD) strcpy(dest, "- (Pad)");
    else if (k == KEY_PLUS_PAD) strcpy(dest, "+ (Pad)");
    else if (k == KEY_DEL_PAD) strcpy(dest, "Delete (Pad)");
    else if (k == KEY_ENTER_PAD) strcpy(dest, "Enter (Pad)");
    else if (k == KEY_PRTSCR) strcpy(dest, "Print Screen");
    else if (k == KEY_PAUSE) strcpy(dest, "Pause");
    else if (k == KEY_YEN) strcpy(dest, "Yen");
//  else if (k == KEY_YEN2) strcpy(dest, "Yen2");
    else if (k == KEY_KANA) strcpy(dest, "Kana");
//	else if (k == KEY_HENKAN) strcpy(dest, "Henkan");
//	else if (k == KEY_MUHENKAN) strcpy(dest, "Muhenkan");
    else if (k == KEY_LSHIFT) strcpy(dest, "Left Shift");
    else if (k == KEY_RSHIFT) strcpy(dest, "Right Shift");
    else if (k == KEY_LCONTROL) strcpy(dest, "Left Control");
    else if (k == KEY_RCONTROL) strcpy(dest, "Right Control");
    else if (k == KEY_ALT) strcpy(dest, "Alt");
    else if (k == KEY_ALTGR) strcpy(dest, "Alt Gr");
    else if (k == KEY_LWIN) strcpy(dest, "Left Win");
    else if (k == KEY_RWIN) strcpy(dest, "Right Win");
    else if (k == KEY_MENU) strcpy(dest, "Menu");
    else if (k == KEY_SCRLOCK) strcpy(dest, "Scroll Lock");
    else if (k == KEY_NUMLOCK) strcpy(dest, "Num Lock");
    else if (k == KEY_CAPSLOCK) strcpy(dest, "Caps Lock");
}


///////////////////////////////////////////////////////////////
// DRAWING STUFF
///////////////////////////////////////////////////////////////

// blits a bmp to the center of the screen
void blit_to_screen(BITMAP *bmp) {
	acquire_screen();
	if (SCREEN_W == 640)
		blit(bmp, screen, 0, 0, 0, 0, bmp->w, bmp->h);
	else
		blit(bmp, screen, 0, 0, SCREEN_W/2-bmp->w/2, SCREEN_H/2-bmp->h/2, bmp->w, bmp->h);
	release_screen();
}

// displays an fps counter at x,y
void draw_fps(BITMAP *bmp, int x, int y) {
	rectfill(bmp, x, y, x+24, y+8, 1);
	textprintf(bmp, font, x+1, y+1, 0, "%3d", fps);
}


///////////////////////////////////////////////////////////////
// HELP SCREEN STUFF
///////////////////////////////////////////////////////////////

// shows the help screen
void show_help_screen(int effect) {
	int x, y;
	char buf[16];
	char *txt[] = { "Move forward", "Move backwards", "Rotate left", "Rotate right", "Strafe Modifier", "Strafe Left", "Strafe Right", "Fire machine gun", "Fire missiles", "Cycle missile type", "Land/take off", "Release Co-pilot (when landed)", "Mission Screen On/Off", "Game Menu", "Help (this screen)", NULL };
	int keys[] = { ctrl->key_up, ctrl->key_down, ctrl->key_left, ctrl->key_right, ctrl->key_strafe, ctrl->key_strafe_l, ctrl->key_strafe_r, ctrl->key_fire1, ctrl->key_fire2, ctrl->key_toggle, ctrl->key_land, ctrl->key_fire1, KEY_ENTER, KEY_ESC, KEY_F1 };
	int seconds = sec_count;

	if (effect) {
		// pixelize
		for(x=0;x<640;x+=8)
			for(y=0;y<480;y+=8)
				rectfill(swap_screen, x, y, x+7, y+7, _getpixel(screen, x+3, y+3));
	
		// darken
		color_map = &black_table;
		drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
		rectfill(swap_screen, 0, 0, SCREEN_W, SCREEN_H, 120);
		solid_mode();
	}
	else {
		clear(swap_screen);
	}

	rotate_scaled_sprite(swap_screen, (BITMAP *)gen[TITLE].dat, 10, 10, 0, ftofix(0.6));

	// show help
	textout(swap_screen, (FONT *)gen[FONT_MENU].dat, "HELP SCREEN", 400, 20, -1);
	x=0;
	while(txt[x] != NULL) {
		textout_right(swap_screen, (FONT *)gen[FONT_DIALOG].dat, txt[x], 300, 120+x*16, 1);
		key_2_str(keys[x], buf);
		textout(swap_screen, (FONT *)gen[FONT_DIALOG].dat, buf, 340, 120+x*16, 1);
		x++;
	}

	textout_centre(swap_screen, (FONT *)gen[FONT_SMALL].dat, "Press ENTER to resume game.", 320, 466, 4);

	blit_to_screen(swap_screen);

	// wait for key
	while (ctrl->is_any()) ctrl->poll_controls();
	while(!key[KEY_ENTER] && !key[KEY_ESC] && !key[KEY_SPACE] && !ctrl->is_fire1()) ctrl->poll_controls();
	while(key[KEY_ENTER] || key[KEY_ESC] || key[KEY_SPACE] || ctrl->is_fire1()) ctrl->poll_controls();

	sec_count = seconds;
}

// draw map screen
void draw_map_screen(BITMAP *bmp, mission *m, player *p, int sub_index, BITMAP *map) {
	int y,i;

	// bg
	blit((BITMAP *)gen[MAP_SCREEN].dat, bmp, 0, 0, 0, 0, 640, 480);
	textout(bmp, (FONT *)gen[FONT_MENU].dat, m->name, 36, 0, -1);

	// missions
	y = 40;
	for(i=0;i<MAX_SUB_MISSIONS;i++) {
		if (m->sub[i] != NULL) {
			if (m->sub[i]->triggered) {
				if (sub_index == i)
					rectfill(bmp, 12, y, 170, y+17, 12);
				textout(bmp, (FONT *)gen[FONT_DIALOG].dat, m->sub[i]->name, 16, y, (m->sub[i]->failed ? 253 : (m->sub[i]->completed ? 240 : 247)));
				y += 20;
			}
		}
	}

	// current mission
	if (sub_index >= 0) 
		if (m->sub[sub_index] != NULL) {
			textout(bmp, (FONT *)gen[FONT_DIALOG_BOLD].dat, m->sub[sub_index]->name, 200, 40, 192);
			y = 30 + break_text(bmp, m->sub[sub_index]->text, 200, 70, 400, 195, FALSE);
	
			textout(bmp, (FONT *)gen[FONT_DIALOG_BOLD].dat, "STATUS:", 200, y, 192);
			textout(bmp, (FONT *)gen[FONT_DIALOG_BOLD].dat, (m->sub[sub_index]->failed ? "FAILED" : (m->sub[sub_index]->completed ? "SUCCESS" : "IN PROGRESS")), 270, y, (m->sub[sub_index]->failed ? 253 : (m->sub[sub_index]->completed ? 240 : 247)));

			if (m->sub[sub_index]->param[1] > 0 && !m->sub[sub_index]->completed) { // timed mission
				y += 30;
				i = MAX(0, m->sub[sub_index]->end_time - sec_count);
				textprintf(bmp, (FONT *)gen[FONT_DIALOG].dat, 200, y, 195, "Time left:  %02d:%02d:%02d", i/3600, i/60, i%60);
			}
	
			y = 30 + break_text(bmp, (m->sub[sub_index]->failed ? m->sub[sub_index]->fail_text : (m->sub[sub_index]->completed ? m->sub[sub_index]->success_text : "")), 200, y+30, 400, 195, FALSE);
		}

	// co-pilot
	textout(bmp, (FONT *)gen[FONT_DIALOG_BOLD].dat, "Co Pilot: ", 16, 390, 195);
	textout(bmp, (FONT *)gen[FONT_DIALOG].dat, p->cop_info.callsign, 90, 391, 195);
	stretch_sprite(swap_screen, (BITMAP *)gen[COP000+p->cop_info.id].dat, 16, 407, 33, 45);
	if (p->cop != NULL) {
		COP_draw_stat_bar(swap_screen, "", p->cop_info.fire_skill, p->cop_info.fire_skill, 53, 403);
		COP_draw_stat_bar(swap_screen, "", p->cop_info.fire_speed, p->cop_info.fire_speed, 53, 413);
		COP_draw_stat_bar(swap_screen, "", p->cop_info.repair_skill, p->cop_info.repair_skill, 53, 423);
		COP_draw_stat_bar(swap_screen, "", p->cop_info.repair_speed, p->cop_info.repair_speed, 53, 433);
	} 
	else {
		if (frame_count & 8) {
			textout(bmp, (FONT *)gen[FONT_DIALOG_BOLD].dat, "MISSING IN", 70, 410, 252);
			textout(bmp, (FONT *)gen[FONT_DIALOG_BOLD].dat, "ACTION", 70, 428, 252);
		}
	}

	// draw map
	if (map != NULL) blit(map, bmp, 0, 0, 185, 36, 444, 342);

	// draw buttons
	if (ctrl->is_up()) draw_sprite(swap_screen, (BITMAP *)gen[BUTT_UP2].dat, 20, 350);
	else draw_sprite(swap_screen, (BITMAP *)gen[BUTT_UP].dat, 20, 350);
	if (ctrl->is_down()) draw_sprite(swap_screen, (BITMAP *)gen[BUTT_DN2].dat, 60, 350);
	else draw_sprite(swap_screen, (BITMAP *)gen[BUTT_DN].dat, 60, 350);
	if (ctrl->is_fire1()) draw_sprite(swap_screen, (BITMAP *)gen[(map == NULL ? BUTT_MAP2 : BUTT_ORD2)].dat, 100, 350);
	else draw_sprite(swap_screen, (BITMAP *)gen[(map == NULL ? BUTT_MAP : BUTT_ORD)].dat, 100, 350);
}

// shows the map/mission screen
void show_map_screen(mission *m, player *p) {
	int y,done=0,show_sm=-1,tot_sm=0;
	int kp=0,i,show_map = 0;
	int subs[MAX_SUB_MISSIONS];
	BITMAP *map = create_bitmap(444, 342);
	BITMAP *old = create_bitmap(SCREEN_W, SCREEN_H);
	double sx = 444 / (double)(m->space->width * SECTOR_W);
	double sy = 342 / (double)(m->space->height * SECTOR_H);
	double scale = MIN(sx,sy);
	int seconds = sec_count;

	// add to knowledge
	if (p->knowledge == 0) p->knowledge++;

	// save old screen
	blit(screen, old, 0, 0, 0, 0, 640, 480);

	// make mini map
	clear_to_color(map, 254);
	for(i=0;i<MAX_OBJECTS;i++) {
		if (m->obj[i] != NULL) {
			if (m->obj[i]->altitude > GL_GROUND_FLAT_SCENERY && !m->obj[i]->inside && !m->obj[i]->is_unit() && m->obj[i]->type != OBJ_PICKUP)
				rotate_scaled_sprite(map, (BITMAP *)m->gfx[m->obj[i]->base_image].dat, (m->obj[i]->x - m->space->origin_x)*scale, (m->obj[i]->y - m->space->origin_y)*scale, m->obj[i]->angle, ftofix(scale)<<1);
		}
	}

	// make sub list
	for(y=0;y<MAX_SUB_MISSIONS;y++)
		if (m->sub[y] != NULL) {
			if (m->sub[y]->triggered) {
				subs[tot_sm++] = y;
				if (!m->sub[y]->completed && !m->sub[y]->completed && show_sm == -1) show_sm = tot_sm-1;
			}
		}

	if (show_sm == -1) show_sm = tot_sm - 1;

	draw_map_screen(swap_screen, m, p, (show_sm>=0?subs[show_sm]:-1), NULL);

	// scroll in
	for(y=SCREEN_H;y>=0;y-=10) {
		game_count = 0;
		blit(swap_screen, screen, 0, 0, 0, y, 640, 480);
		while(!game_count);
	}

	// do the logic
	int xp, yp, xo, yo;
	int direction = 0;
	double ang = fixtof(m->obj[0]->angle)*2*PI/256.0 - PI/2.0;
	xp = 2+185+(m->obj[0]->x - m->space->origin_x)*scale;
	yp = 2+36+(m->obj[0]->y - m->space->origin_y)*scale;
	// FIX COORDS
	if (xp < 195) { xp = 195; direction +=1; }
	if (xp > 639) { xp = 639; direction +=2; }
	if (yp < 46)  { yp =  46; direction +=4; }
	if (yp > 370) { yp = 370; direction +=8; }
	xo = xp+12*cos(ang);
	yo = yp+12*sin(ang);

	do {
		game_count = 0;
		frame_count ++;
		sec_count = seconds;
		draw_map_screen(swap_screen, m, p, (show_sm >= 0 ? subs[show_sm] : -1), (show_map ? map : NULL));
		if (show_map) {
			// draw heli
			line(swap_screen, xp, yp, xo, yo, 1);
			line(swap_screen, xo, yo, xo+5*cos(ang-2.5), yo+5*sin(ang-2.5), 1);
			line(swap_screen, xo, yo, xo+5*cos(ang+2.5), yo+5*sin(ang+2.5), 1);
			if ((frame_count+1) & 4) {
				if (!direction) circlefill(swap_screen, xp, yp, 3, 92);
				else {
					if (direction == 1)  triangle(swap_screen, xp, yp, xp+5, yp-5, xp+5, yp+5, 92);
					if (direction == 2)  triangle(swap_screen, xp, yp, xp-5, yp-5, xp-5, yp+5, 92);
					if (direction == 4)  triangle(swap_screen, xp, yp, xp-5, yp+5, xp+5, yp+5, 92);
					if (direction == 8)  triangle(swap_screen, xp, yp, xp-5, yp-5, xp+5, yp-5, 92);
					if (direction == 5)  triangle(swap_screen, xp, yp, xp+5, yp, xp, yp+5, 92);
					if (direction == 6)  triangle(swap_screen, xp, yp, xp-5, yp, xp, yp+5, 92);
					if (direction == 9)  triangle(swap_screen, xp, yp, xp+5, yp, xp, yp-5, 92);
					if (direction == 10) triangle(swap_screen, xp, yp, xp-5, yp, xp, yp-5, 92);
				}
			}

			// mark targets on map
			if (show_sm>=0) {
				for(i=0;i<MAX_TARGETS;i++) {
					if (m->sub[subs[show_sm]]->target[i] != -1) {
						if (frame_count & 4) {
							if (m->obj[m->sub[subs[show_sm]]->target[i]] != NULL) 
								circle(swap_screen, 2+185+(m->obj[m->sub[subs[show_sm]]->target[i]]->x - m->space->origin_x)*scale, 2+36+(m->obj[m->sub[subs[show_sm]]->target[i]]->y - m->space->origin_y)*scale, 5, 1);
							break;
						}
					}
				}
			}
		}

		textout_right(swap_screen, (FONT *)gen[FONT_SMALL].dat, "Press ENTER to return to game.", 630, 450, 196);
		blit_to_screen(swap_screen);

		ctrl->poll_controls();
		if (show_sm>=0) {
			if (ctrl->is_down() && !kp) { show_sm = MIN(tot_sm-1, show_sm + 1); play_sample((SAMPLE *)sfx[11].dat, 200, 128, 1000, 0); }
			if (ctrl->is_up() && !kp) { show_sm = MAX(0, show_sm - 1); play_sample((SAMPLE *)sfx[11].dat, 200, 128, 1000, 0); }
			if (ctrl->is_fire1() && !kp) { show_map = (show_map ? 0 : 1); play_sample((SAMPLE *)sfx[11].dat, 200, 128, 1000, 0); }
			if ((key[KEY_M] || key[KEY_O]) && !kp) { show_map = (show_map ? 0 : 1); play_sample((SAMPLE *)sfx[11].dat, 200, 128, 1000, 0); }
		}
		if (ctrl->is_any() || key[KEY_O] || key[KEY_M]) kp = 1;
		else kp = 0;
		if (key[KEY_ENTER]) done = 1;
		if (key[KEY_F1]) show_help_screen(1);
		if (key[KEY_F2]) {
			save_bmp("hc_map.bmp", swap_screen, NULL);
		}
		while(!game_count);
	} while(!done);

	p->new_info = FALSE;

	// scroll out
	for(y=0;y<SCREEN_H;y+=10) {
		game_count = 0;
		blit(swap_screen, swap_screen, 0, y, 0, y+10, 640, 480);
		blit(old, swap_screen, 0, 0, 0, 0, 640, y);
		blit_to_screen(swap_screen);
		while(!game_count);
	}
	sec_count = seconds;
}


///////////////////////////////////////////////////////////////
// GAME STUFF
///////////////////////////////////////////////////////////////

// plays a sample, according to object position
void play_obj_sample(object *o, int smp) {
	int vol;

	if (smp < 0 || smp > 20) {
		my_alert("c","d");
		return;
	}
	if (o->r_hyp > 1600 || options.sound_mute) return;

	vol = (MID(0, 400 - o->r_hyp/4, 255) * options.sound_volume) / 255;

	play_sample((SAMPLE *)sfx[smp].dat, vol, 128, 1000, 0);
}


void update_smoke(mission *m) {
	int i;
	// move/update smoke
	for(i=0;i<MAX_SMOKE;i++)
		if (m->smk[i] != NULL) {
			m->smk[i]->update();
			if (!m->smk[i]->life) {
				m->smk[i]->~smoke();
				m->smk[i] = NULL;
			}
		}
}


void update_debris(mission *m) {
	int i;
	// move/update debris
	for(i=0;i<MAX_DEBRIS;i++)
		if (m->dbrs[i] != NULL) {
			m->dbrs[i]->update();
			if (!m->dbrs[i]->life) {
				m->dbrs[i]->~debris();
				m->dbrs[i] = NULL;
			}
		}
}


void draw_mission(mission *m, player *p) {
	int i, ap;
	// tmp
	double zoom = 0.01;

	// render mission to swap_screen
	color_map = &trans_table_50; 
	clear_to_color(swap_screen, 254);
	//draw_fps(swap_screen, 0, 0);	// TODO: remove
	m->render(swap_screen, p->heli->x, p->heli->y, p->heli->angle);


	// draw meters & icons
	
	// armour meter
	color_map = &trans_table_50; 
	draw_trans_sprite(swap_screen, (BITMAP *)gen[METER000].dat, 5, 410);
	if (p->armour_buffer) {
		ap = p->get_armour_percent(TRUE) * 15;
		color_map = &trans_table_100; 
		if (ap > 0 && ap <= 15)
			draw_trans_sprite(swap_screen, (BITMAP *)gen[METER014 - ap + 1].dat, 5, 410);
	}
	
	ap = p->get_armour_percent(FALSE) * 15;
	if (ap > 0 && ap <= 15)
		draw_sprite(swap_screen, (BITMAP *)gen[METER014 - ap + 1].dat, 5, 410);
	draw_sprite(swap_screen, (BITMAP *)gen[METER_C].dat, 21, 426);
	textprintf_centre(swap_screen, (FONT *)gen[FONT_DIALOG].dat, 39, 434, 1, "%d", p->heli->armour);
	// draw life tokens
	if (p->lives >= 3) draw_sprite(swap_screen, (BITMAP *)gen[LIFE_TOKEN].dat, 21+38, 426+21);
	if (p->lives >= 2) draw_sprite(swap_screen, (BITMAP *)gen[LIFE_TOKEN].dat, 21+32, 426+32);
	if (p->lives >= 1) draw_sprite(swap_screen, (BITMAP *)gen[LIFE_TOKEN].dat, 21+21, 426+38);
		
	// ammo meters
	if (p->ammo[0])	{ 
		draw_sprite(swap_screen, (BITMAP *)gen[AMMO1].dat, 5, 10);
		textprintf(swap_screen, (FONT *)gen[FONT_DIALOG].dat, 35, 15, text_color, "%d", p->ammo[0]);
	}
	else {
		if (frame_count & 8) draw_sprite(swap_screen, (BITMAP *)gen[AMMO1].dat, 5, 10);
		else draw_trans_sprite(swap_screen, (BITMAP *)gen[AMMO1].dat, 5, 10);
	}
	if (p->got_sec_weapon) {
		draw_sprite(swap_screen, (BITMAP *)gen[AMMO1+p->sec_weapon].dat, 5, 40);
		textprintf(swap_screen, (FONT *)gen[FONT_DIALOG].dat, 35, 45, text_color, "%d", p->ammo[p->sec_weapon]);
	}
		
	// radar
	rotate_sprite(swap_screen, (BITMAP *)gen[COMPASS].dat, 565, -1, -p->heli->angle);
	draw_sprite(swap_screen, (BITMAP *)gen[RADAR].dat, 570, 8);
		
	double radar_range = 28/zoom;
	int col;
	for(i=0;i<MAX_OBJECTS;i++)
		if (m->obj[i] != NULL) 
			if (!m->obj[i]->inside && m->obj[i]->r_hyp < radar_range) { // && m->obj[i]->type != OBJ_SOLDIER && m->obj[i]->type != OBJ_FOOT_SOLDIER && m->obj[i]->type != OBJ_BAZOOKA_SOLDIER) {
				m->obj[i]->r_angle = atan2(m->obj[i]->r_dy, m->obj[i]->r_dx) - fixtof(p->heli->angle)*2*PI/256;
				if (m->obj[i]->is_unit()) col = (m->obj[i]->team == 1 ? 87 : 251);
				else col = (m->obj[i]->type == OBJ_BUILDING ? 6 : (m->obj[i]->type == OBJ_SCENERY ? 12 : 1));
				_putpixel(swap_screen, 570+33+m->obj[i]->r_hyp*cos(m->obj[i]->r_angle)*zoom, 
					8+33+m->obj[i]->r_hyp*sin(m->obj[i]->r_angle)*zoom, col);
			}
	
	// draw waiting message
	if (p->new_info) {
		if (frame_count>10) draw_sprite(swap_screen, (BITMAP *)gen[MESSAGE_INDICATOR].dat, 600, 90);
		else {
			draw_trans_sprite(swap_screen, (BITMAP *)gen[MESSAGE_INDICATOR].dat, 600, 90);
			if (p->knowledge == 0) {
				textout(swap_screen, (FONT *)gen[FONT_DIALOG].dat, "PRESS", 595, 90, text_color);
				textout(swap_screen, (FONT *)gen[FONT_DIALOG].dat, "ENTER", 595, 105, text_color);
			}
		}

	}

	// ugly temporary code: todo: betterize
	// find a primary sub mission
	for(int s=0;s<MAX_SUB_MISSIONS;s++) {
		if (m->sub[s] != NULL) {
			if (m->sub[s]->primary && m->sub[s]->triggered && !m->sub[s]->completed) {
				// draw to targets
				for(i=0;i<MAX_TARGETS;i++) {
					if (frame_count&8 && m->sub[s]->target[i] != -1) {
						if (m->obj[m->sub[s]->target[i]] != NULL) {
							if (!p->heli->is_passenger(m->sub[s]->target[i])) {
								// get angle
								int dx = p->heli->x - m->obj[m->sub[s]->target[i]]->x;
								int dy = p->heli->y - m->obj[m->sub[s]->target[i]]->y;
								int hyp = MIN(sqrt(dx*dx + dy*dy), 310);
								if (hyp < 310) hyp -= m->obj[m->sub[s]->target[i]]->radius*1.5;
								double ang = atan2(dy, dx) - 2*PI*fixtof(p->heli->angle)/256.0 - PI;
								int x = hyp*cos(ang)+HELI_X;
								int y = hyp*sin(ang)+HELI_Y;					
								if (y >= SCREEN_H-10) {
									y = SCREEN_H-11;
									x = HELI_X - ((double)(y-HELI_Y))*tan(ang-PI/2);
								}
								clear(targ_ptr);
								rotate_sprite(targ_ptr, (BITMAP *)gen[TARGET_PTR].dat, 0, 0, ftofix(ang*128/PI + 64));
								color_map = &trans_table_50;
								draw_trans_sprite(swap_screen, targ_ptr, x-9, y-9);
							}
						}
					}
				}
			}
		}
	}

	// point where people want off
	for(i=0;i<p->heli->max_passengers;i++) {
		if (p->heli->passenger[i] != -1) {
			// get angle
			int dx = p->heli->x - m->zone2[((soldier *)m->obj[p->heli->passenger[i]])->wanna_ride].cx;
			int dy = p->heli->y - m->zone2[((soldier *)m->obj[p->heli->passenger[i]])->wanna_ride].cy;
			int hyp = MIN(sqrt(dx*dx + dy*dy), 310);
			if (hyp < 310) hyp -= 40;
			double ang = atan2(dy, dx) - 2*PI*fixtof(p->heli->angle)/256.0 - PI;
			int x = hyp*cos(ang)+HELI_X;
			int y = hyp*sin(ang)+HELI_Y;					
			if (y >= SCREEN_H-10) {
				y = SCREEN_H-11;
				x = HELI_X - ((double)(y-HELI_Y))*tan(ang-PI/2);
			}

			clear(targ_ptr);
			rotate_sprite(targ_ptr, (BITMAP *)gen[TARGET_PTR].dat, 0, 0, ftofix(ang*128/PI + 64));
			color_map = &trans_table_100;
			draw_trans_sprite(swap_screen, targ_ptr, x-9, y-9);
					
			//break;
		}
	}

	if (text_time&4 && text_time) {
		textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, text_message, 319, 200, 15);
		textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, text_message, 321, 200, 15);
		textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, text_message, 320, 201, 15);
		textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, text_message, 320, 199, 15);
		textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, text_message, 320, 200, -1);
	}

	// blit to screen
	blit_to_screen(swap_screen);
}

void update_projectiles(mission *m, player *p) {
	int i,j;
	int hit;
	int explode;
	int warning;

	for(i=0;i<MAX_PROJECTILES;i++) {
		if (m->proj[i] != NULL) {
			if (m->proj[i]->p_type == P_N_MISSILE || m->proj[i]->p_type == P_HS_MISSILE || m->proj[i]->p_type == P_PH_MISSILE)
				if (options.smoke) {
					smoke *s = new smoke(&white_table, m->proj[i]->x, m->proj[i]->y, 150, 10, 2, 1);
					if (s != NULL) if (!m->add_smoke(s)) s->~smoke();
				}

			if (m->proj[i]->target == -1 && m->proj[i]->p_type == P_HS_MISSILE) m->proj[i]->get_target(m);
			m->proj[i]->move(m);
			// collision check
			sector *s = m->space->get_sector(m->proj[i]->x, m->proj[i]->y);
			if (s != NULL) {
				object *o = s->get_objects();
				explode = 0;
				while(o != NULL) {
					warning = 0;
					if (o->team != m->proj[i]->team && !o->inside && o->wreck_image && o->type != OBJ_SCENERY && o->armour > 0) {
						if (o->type == OBJ_BUILDING) hit = o->collision(m->proj[i]->x, m->proj[i]->y, o->pw, 1);
						else {
							if (m->proj[i]->p_type == P_PH_MISSILE) hit = o->collision(m->proj[i]->x, m->proj[i]->y, 16);
							else hit = o->collision(m->proj[i]->x, m->proj[i]->y, m->proj[i]->radius);
						}
						if (hit) {					// HIT (!)
							if (m->proj[i]->p_type == P_PH_MISSILE || m->proj[i]->p_type == P_HS_MISSILE || m->proj[i]->p_type == P_N_MISSILE) {
								explode = m->proj[i]->p_type;
							}
							o->rotate(rand()%5-2);
							if (o == p->heli)
								o->armour -= MAX((m->proj[i]->damage + m->proj[i]->bonus)*difficulty, 1);
							else
								o->armour -= (m->proj[i]->damage + m->proj[i]->bonus);
							m->proj[i]->life = 0;
							if (m->proj[i]->p_type == P_BULLET) {
								if (rand()%4 == 0)  {
									if (options.smoke) {
										smoke *s = new smoke(&white_table, m->proj[i]->x+rand()%5-2, m->proj[i]->y+rand()%5-2, 50, 55, 0, 1);
										if (s != NULL) if (!m->add_smoke(s)) s->~smoke();
									}
									if (rand()%4 == 0) play_obj_sample(m->proj[i], 13+rand()%2);
								}
							}
							else {
								if (options.smoke) {
									smoke *s = new smoke(&white_table, m->proj[i]->x, m->proj[i]->y, 0, 20, 3, 2);
									if (s!=NULL) if (!m->add_smoke(s)) s->~smoke();
								}
								play_obj_sample(m->proj[i], 17);
							}
							if (o->armour<=0) {
								if (o->type != OBJ_SOLDIER && o->type != OBJ_FOOT_SOLDIER && o->type != OBJ_BAZOOKA_SOLDIER) {
									int expls = (o->radius < 50 ? 5: (o->radius < 75 ? 10 : 20));
									for(j=0;j<expls;j++) {
										//m->add_smoke(new smoke(&black_table, o->x+rand()%o->radius-o->radius/2, o->y+rand()%o->radius-o->radius/2, rand()%50, 10+rand()%10-5, 10+rand()%10-5, (rand()%10)/8.0));
										smoke *smk = new smoke(&explosion_table, 
											o->x + (rand()%o->radius) - (o->radius>>1), 
											o->y + (rand()%o->radius) - (o->radius>>1), 
											1, 0, rand()%4+expls, 0.8);
										if (smk != NULL) {
											smk->set_speed((rand()%10-5)/20.0, (rand()%10-5)/20.0);
											if (!m->add_smoke(smk)) smk->~smoke();
										}
									}
									for(j=0;j<expls*2;j++) {
										debris *db = new debris((BITMAP *)m->gfx[o->base_image].dat, 
											o->x + (rand()%o->radius) - (o->radius>>1), 
											o->y + (rand()%o->radius) - (o->radius>>1));
										if (db != NULL) {
											db->set_params(rand()%10-5, rand()%10-5, -ftofix(0.015), itofix(rand()%7-3));
											if (!m->add_debris(db)) db->~debris();
										}
									}
								}
								if (o->type == OBJ_BUILDING && options.smoke) {
									for(j=0;j<16;j++) {
										//smoke *s = new smoke(&explosion_table, o->x+((rand()%o->radius-o->radius/2)>>1), o->y+((rand()%o->radius-o->radius/2)>>1), 1, 1, 3, 0.4);
										smoke *s = new smoke(&white_table, o->x+((rand()%o->radius-o->radius/2)>>1), o->y+((rand()%o->radius-o->radius/2)>>1), 30+rand()%60, 4, 3, 0.4);
										double r = (double)(rand()%628)/100.0;
										double v = (double)(rand()%200)/100.0;
										if (s != NULL) {
											s->set_speed(v*cos(r), v*sin(r));
											if (!m->add_smoke(s)) s->~smoke();
										}
									}
								}
								if (o->type == OBJ_SOLDIER || o->type == OBJ_FOOT_SOLDIER || o->type == OBJ_BAZOOKA_SOLDIER) {
									soldier *s = (soldier *)o;
									play_obj_sample(o, s->die_sound+rand()%4);
									warning = 1;
								}
								else if (o->type == OBJ_BUILDING) {
									play_obj_sample(o, 6+rand()%4);
								}
								else play_obj_sample(o, 16);

								if (o != p->heli) {
									o->kill();
									if (o->type == OBJ_BUILDING) ((building*)o)->destroy(m);
									else if (o->type == OBJ_TOWER || o->type == OBJ_TRUCK) ((vehicle*)o)->destroy(m);
									else o->empty(m);
									o->~object();
								}
								else {
									p->dead = TRUE;
									p->heli->armour = 0;
									play_obj_sample(p->heli, 6+rand()%4);
								}
							}
						}
					}
					o = o->next;
				}
				if (explode) {  // missile has detonated
					if (explode == P_PH_MISSILE) { // PH
						for(j=0;j<360;j+=36) {
							double ang = j*2*PI/360.0;
							smoke *smk = new smoke(&explosion_table, m->proj[i]->x+(16+rand()%8)*cos(ang), m->proj[i]->y+(16+rand()%8)*sin(ang), 
								1, 0, 4, (10+rand()%10)/10.0);
							if (smk != NULL) if (!m->add_smoke(smk)) smk->~smoke();
						}
					}
					// set soldiers to fire in area
					o = s->get_objects();
					while(o != NULL) {
						if (!o->essential) {
							if ((o->type == OBJ_SOLDIER || o->type == OBJ_FOOT_SOLDIER || o->type == OBJ_BAZOOKA_SOLDIER) && !o->inside) {
								if (explode == P_PH_MISSILE) {
									if (o->collision(m->proj[i]->x, m->proj[i]->y, 56)) {
										soldier *sold = (soldier *)o;
										sold->start_fire(60);
									}
								}
								else {
									if (o->collision(m->proj[i]->x, m->proj[i]->y, 24)) {
										soldier *sold = (soldier *)o;
										sold->start_fire(60);
									}
								}
							}
						}
						o = o->next;
					}

				}
			}
			if (!m->proj[i]->life) {
				m->proj[i]->~projectile();
				m->proj[i] = NULL;
			}
		}
	}
}

void update_units(mission *m) {
	int i, x, y;

	m->space->add_object(m->obj[0]);	// player heli

	if (m->obj[0]->armour < 25 && frame_count&1) {
		int r = m->obj[0]->radius >> 1;
		smoke *s = new smoke(&black_table, m->obj[0]->x + rand()%r - (r>>1), m->obj[0]->y + rand()%r - (r>>1), 30+rand()%60, 4, 3, 0.4);
		if (s != NULL) {
			s->set_speed(0.5, 1);
			if (!m->add_smoke(s)) s->~smoke();
		}
	}
	else if (m->obj[0]->armour < 50 && (frame_count%3)==0) {
		int r = m->obj[0]->radius >> 1;
		smoke *s = new smoke(&black_table, m->obj[0]->x + rand()%r - (r>>1), m->obj[0]->y + rand()%r - (r>>1), 20+rand()%40, 3, 2, 0.2);
		if (s != NULL) {
			s->set_speed(0.5, 1);
			if (!m->add_smoke(s)) s->~smoke();		
		}
	}

	for(i=1;i<MAX_OBJECTS;i++) {
		if (m->obj[i] != NULL) {

			if (m->obj[i]->anim_frames) {			// animate!
				if (++m->obj[i]->anim_count >= m->obj[i]->anim_rate) {
					m->obj[i]->anim_count = 0;
					if (++m->obj[i]->anim_pos >= m->obj[i]->anim_frames) m->obj[i]->anim_pos = 0;
				}
			}

			// check cop pup search
			if (m->obj[i]->type == OBJ_FOOT_SOLDIER) {
				soldier *s = (soldier *)m->obj[i];
				if (s->pup_collector) {
					if (!s->carrying_pup) {
						if (s->pup_search_time < 500) s->pup_search_time ++;
						else s->carrying_pup = 1000;
					}
				}
			}


			// check waypoints
			if (m->obj[i]->is_unit()) {
				unit *u = (unit *)m->obj[i];
				if (u->wp[u->wp_pos].active && m->event[u->event] && i!=173) {
					// go for it
					u->target_thrust = 1;
					u->angle_counter --;
					// if close enough, change to next wp
					double dx = u->x - u->wp[u->wp_pos].x;
					double dy = u->y - u->wp[u->wp_pos].y;
					double hyp = sqrt(dx*dx + dy*dy);
					if (hyp < u->radius*2) {  // reached waypoint
						u->angle_counter = 5;
						switch(u->wp[u->wp_pos].type) {
							case WP_UNLOAD:
								u->target_thrust = 0;
								u->empty(m);
								u->wp_pos += u->wp_dir;
								if (u->wp_pos < 0) {
									u->wp_pos = 0;
									u->wp_dir = -u->wp_dir;
									u->wp_pos += u->wp_dir;
								}
								break;
							case WP_GOAL:
								u->wp_dir = 0;
								u->target_thrust = 0;
								break;
							case WP_REVERSE:
								u->wp_dir = -u->wp_dir;
								u->wp_pos += u->wp_dir;
								if (u->wp_pos < 0) u->wp_pos = 0;
								break;
							case WP_RESTART:
								u->wp_pos = 0;
								break;
							default:
								u->wp_pos += u->wp_dir;
								if (u->wp_pos < 0) {
									u->wp_pos = 0;
									u->wp_dir = -u->wp_dir;
									u->wp_pos += u->wp_dir;
								}
								break;
						}
					}
					if (!u->wp[u->wp_pos].active) u->target_thrust = 0;
					if (u->angle_counter ==0) { // update target angles
						double a = atan2(dy, dx) - PI/2;
						u->target_angle = ftofix((a * 256)/(2 * PI));
						if (ABS(u->target_angle - u->angle) > itofix(128))  {
							if (u->target_angle > u->angle) u->target_angle -= itofix(256);
							else u->target_angle += itofix(256);
						}
						u->angle_counter = 50;
					}
				}
				m->obj[i]->calc_movement();
				m->space->add_object(m->obj[i]);
			}
			else {
				if (m->obj[i]->type == OBJ_PICKUP) m->space->add_object(m->obj[i]);
				else if (m->obj[i]->type == OBJ_SCENERY) {
					if (m->obj[i]->smoking)	{
						m->obj[i]->smoking --;
						int r = (int)(((double)m->obj[i]->radius) * 0.7);
						int h = (r < 50 ? 3: (r < 75 ? 2 : 1));
						if (m->obj[i]->smoking % (h*2) == 1) {
							smoke *s = new smoke(&black_table, m->obj[i]->x + rand()%r - (r>>1), m->obj[i]->y + rand()%r - (r>>1), 30+rand()%60, 4, 3, 0.4);
							if (s != NULL) {
								s->set_speed(1, 2);
								if (!m->add_smoke(s)) s->~smoke();
							}
						}
					}
				}
			}

			if (!m->obj[i]->inside) {
				if (m->obj[i]->is_unit()) {
					unit *u = (unit *) m->obj[i];
					// check surroundings
					u->may_fire = FALSE;
					u->current_target = NULL;
					u->jumping = FALSE;
					if (!u->wp[u->wp_pos].active) u->target_thrust = 0;
					
					x = MAX(u->sect->x-1,0);
					while(x < MIN(u->sect->x+2,m->space->width)) {
						y = MAX(u->sect->y-1,0);
						while(y < MIN(u->sect->y+2,m->space->height)) {
							object *o = m->space->s[x + y * m->space->width]->get_objects();
							object *onext;
							while(o != NULL) {
								onext = o->next;
								if (u != o) 
									if (!o->inside)
										u->act_on_object(o);
								o = onext;
							}
							y++;
							if (u->sect == NULL) break;
						}
						x++;
						if (u->sect == NULL) break;
					}
					
					u->update_movement();
					if (u->frame_count) u->frame_count --;
					if (u->may_fire && u->frame_count == 0) {
						projectile *p = u->fire(0);
						if (p != NULL) {
							if (p->p_type == P_BULLET)
								if (text_color != 1) p->bull_image = BULLET2;

							if (m->add_projectile(p)) {
								u->ammo_count ++;
								u->frame_count += u->fire_rate;
								play_obj_sample(u, u->fire_sound);
								if (u->ammo_count == u->mag_size) {
									// reload
									u->frame_count += u->reload_rate;
									u->ammo_count = 0;
									if (u->reload_sound != -1) play_obj_sample(u, u->reload_sound);
								}
							}
							else p->~projectile();
						}
					}
				}
			}
		}
	}

}


void check_for_fires(mission *m) {
	int i;

	for(i=1;i<MAX_OBJECTS;i++) {
		if (m->obj[i] != NULL) {
			//check soldiers for fires
			if (m->obj[i]->type == OBJ_SOLDIER || m->obj[i]->type == OBJ_FOOT_SOLDIER || m->obj[i]->type == OBJ_BAZOOKA_SOLDIER) {
				if (((soldier*)m->obj[i])->burning) {
					m->obj[i]->target_thrust = 6;
					((soldier *)m->obj[i])->update_movement();
					if (rand()%12 == 1) {
						m->obj[i]->armour --;
						m->obj[i]->target_angle = itofix(rand()%256);
						if (m->obj[i]->armour <= 0) {
							soldier *s = (soldier *)m->obj[i];
							play_obj_sample(m->obj[i], s->die_sound+rand()%4);
							object *o = m->obj[i];
							m->obj[i]->kill();
							o->~object();
						}
					}
				}
			}
		}
	}
}


void get_player_movement(player *p) {
	static int ptoggle = 0;
	if (ctrl->analog_joy && ctrl->use_joy) {
		if (ctrl->analog_joy == 3) {
			p->heli->strafe(ctrl->axis[0]/128.0);
			p->heli->thrust(ctrl->axis[1]/128.0);
			p->heli->rotate(ctrl->axis[2]/128.0);
		}
		else {  // 2 
			p->heli->rotate(ctrl->axis[0]/128.0);
			p->heli->thrust(ctrl->axis[1]/128.0);
			p->heli->strafe(ctrl->axis[2]/128.0);
		}
	}
	else { // digital
		if (ctrl->is_left()) (ctrl->is_strafe() ? p->heli->strafe(-1) : p->heli->rotate(-1));
		if (ctrl->is_right()) (ctrl->is_strafe() ? p->heli->strafe(1) : p->heli->rotate(1));
		if (ctrl->is_strafe_l()) p->heli->strafe(-1);
		if (ctrl->is_strafe_r()) p->heli->strafe(1);
		if (ctrl->is_up()) p->heli->thrust(-1);
		if (ctrl->is_down()) p->heli->thrust(1);
	}

	if (ctrl->is_toggle() && !ptoggle) { 
		if (p->next_sec_weapon()) play_obj_sample(p->heli, 20);
		ptoggle = 1; 
	}
	if (!ctrl->is_toggle()) ptoggle = 0;
}

void handle_player_input(mission *m, player* p) {
	static int pfire = 0, pfsound=0;
	static int mis_shift = 18;
	int x,y,i;

	ctrl->poll_controls();

	// if heli is in air
	if (p->heli->heli_altitude == 100)  {

		get_player_movement(p);

		if (ctrl->is_fire1() && !pfire) {
			if (p->ammo[0]) {
				projectile *bull = p->heli->fire(P_BULLET) ;
				if (bull != NULL) {
					if (p->cop != NULL) {
						bull->radius = 1+p->cop_info.fire_skill/10.0;
						bull->bonus = p->cop_info.fire_skill/25.0;
					} 
					else {
						bull->radius = 3;
						bull->bonus = 0;
					}
					if (text_color != 1) bull->bull_image = BULLET2;
				}
				if (m->add_projectile(bull)) {
					if (!special_fx[0]) p->ammo[0]--;
					if (!pfsound++) play_obj_sample(p->heli, 18);
					if (pfsound==2) pfsound = 0;
				}
				else bull->~projectile();
			}
			else ; // todo: play plunk sound
		}
		if (p->cop != NULL)	{
			if (++pfire >= 11-p->cop_info.fire_speed/10.0) pfire = 0;
		}
		else {
			if (++pfire >= 10) pfire = 0;
		}
		if (!ctrl->is_fire1()) { pfire = 0; pfsound = 0; }
		
		if (ctrl->is_fire2() && !p->fired_missile && p->got_sec_weapon && p->ammo[p->sec_weapon]) {
			projectile *mis = p->heli->fire((p->sec_weapon == 1 ? P_N_MISSILE : (p->sec_weapon == 2 ? P_HS_MISSILE : P_PH_MISSILE)));
			if (m->add_projectile(mis) ) {;
				// adjust and calc new position
				mis_shift = -mis_shift;
				mis->x += mis_shift * cos(fixtof(p->heli->angle)*2*PI/256); 
				mis->y += mis_shift * sin(fixtof(p->heli->angle)*2*PI/256); 
				if (mis_shift > 0) mis->angle -= ftofix(1.5);
				else mis->angle += ftofix(1.5);

				if (!special_fx[p->sec_weapon]) p->ammo[p->sec_weapon]--;
				if (!p->ammo[p->sec_weapon]) p->next_sec_weapon();
				p->fired_missile = TRUE;
				play_obj_sample(p->heli, 12);
			} 
			else mis->~projectile();
		}
		if (!ctrl->is_fire2()) p->fired_missile = FALSE;
	}

	// if heli is going up or down (z)
	if (p->landing) {
		if (p->landing > 0) {
			get_player_movement(p);
		}
		p->heli->heli_altitude = MIN(MAX(p->heli->heli_altitude + p->landing, 0), 100);
		if (p->heli->heli_altitude == 0 || p->heli->heli_altitude == 100) p->landing = 0;
		adjust_sample((SAMPLE *)sfx[1].dat, options.sound_volume*0.7, 128, 800 + p->heli->heli_altitude * 4, 1);
	}
	
	if (p->heli->heli_altitude == 0 && p->cop != NULL && ctrl->is_fire1()) {
		// check for pickups
		for(x=MAX(p->heli->sect->x-1,0);x<MIN(p->heli->sect->x+2,m->space->width);x++)
			for(y=MAX(p->heli->sect->y-1,0);y<MIN(p->heli->sect->y+2,m->space->height);y++) {
				object *o = m->space->s[x + y * m->space->width]->get_objects();
				int pup_count = 0;
		
				while(o != NULL) {
					if (o->type == OBJ_PICKUP && !o->inside)
						if (o->rendered && o->r_hyp < 150) pup_count++;
					o = o->next;
				}

				if (pup_count > 0 && p->cop != NULL) {	// release co_pilot
					p->cop->set_position(p->heli->x, p->heli->y);
					m->add_object(p->cop, TRUE);
					p->cop->wanna_ride = TRUE;
					p->cop->pup_collector = TRUE;
					p->cop->pup_search_time = 0;
					p->cop->carrying_pup = 0;
					p->cop->x_force = p->cop->y_force = 0;
					p->cop->rot_str = p->cop->str_str = p->cop->thr_str = 0;
					p->cop = NULL;
				}
				else {
					/* todo: play_sample: no sir*/
				}
			}
	}
		
	if (ctrl->is_land() && !p->landing) {
		if (p->heli->heli_altitude == 100) { // land
			// check speeds
			if (ABS(p->heli->x_force) < 2 && ABS(p->heli->y_force) < 2) {
				// check obstacles 
				bool can_land = TRUE;
				for(x=MAX(p->heli->sect->x-1,0);x<MIN(p->heli->sect->x+2,m->space->width);x++)
					for(y=MAX(p->heli->sect->y-1,0);y<MIN(p->heli->sect->y+2,m->space->height);y++) {
						object *o = m->space->s[x + y * m->space->width]->get_objects();
						while(o != NULL) {
							if (p->heli != o) 
								if (!o->inside) {
									if (o->type == OBJ_PICKUP || o->type == OBJ_SOLDIER || o->type == OBJ_FOOT_SOLDIER || o->type == OBJ_BAZOOKA_SOLDIER) {
										if (p->heli->collision(o, COLL_SEMI_SPHERE)) can_land = FALSE;
									}
									else {
										if (p->heli->collision(o, COLL_SPHERE)) can_land = FALSE;
									}
								}
								o = o->next;
						}
					}
				if (can_land) p->landing = -1;
			}
		}
		else 
			p->landing = 1; // take off
	}

	p->heli->calc_movement();
	p->heli->turret_angle += ftofix(7 + 4 * (double) p->heli->heli_altitude / 100);
	
	// if landed and got people in it, let them leave if they want to
	if (p->heli->heli_altitude == 0 && p->heli->num_passengers) {
		int z = m->get_zone(p->heli->x, p->heli->y, FALSE);
		for(i=0;i<p->heli->max_passengers;i++) {
			if (p->heli->passenger[i] != -1) {
				soldier *s = (soldier *)m->obj[p->heli->passenger[i]];
				if (s->wanna_ride == z)	{
					p->heli->empty(m, i);
					s->wanna_ride = 0;
				}
			}
		}
	}
}

// update sub missions in a mission
void update_sub_missions(mission *m, player *p) {
	int i,j;
	object *o;
	int targets_left;

	for(i=0;i<MAX_SUB_MISSIONS;i++) {
		if (m->sub[i] != NULL) {
			if (!m->sub[i]->completed && m->sub[i]->triggered) { 
				// decrease time if available
				if (m->sub[i]->param[1] > 0) {
					j = MAX (0, m->sub[i]->end_time - sec_count);
					//textprintf_centre(screen, font, 320, 0, 1, "Time left:  %02d:%02d:%02d", j/3600, j/60, j%60);
				}
				// check targets
				targets_left = 0;
				for(j=0;j<MAX_TARGETS;j++) {
					if (m->sub[i]->target[j] != -1) {
						o = m->obj[m->sub[i]->target[j]];
						if (o != NULL) {
							targets_left ++;
							switch (m->sub[i]->type) {
								case SM_DESTROY:
									if (!o->target) {
										m->sub[i]->target[j] = -1;
										m->sub[i]->param[2] --;	// one down
										m->sub[i]->num_targets --;
									}
									break;
								case SM_DEFEND:
									if (!o->target) {	// destroyed ?
										m->sub[i]->target[j] = -1;
									}
									break;
								case SM_TRANSPORT:
									if (!o->target) {	// dead?
										m->sub[i]->target[j] = -1;	// remove from target checking
									}
									if (m->get_zone(o->x, o->y, FALSE) == m->sub[i]->param[0])	{
										o->target = FALSE;
										m->sub[i]->target[j] = -1;
										m->sub[i]->param[2] --;			// one there
										m->sub[i]->num_targets --;
									}
									break;
								case SM_STRAFE:
									if (!o->target) {	// destroyed?
										m->sub[i]->target[j] = -1;	// remove from target checking
										m->sub[i]->num_targets --;
										m->sub[i]->param[2] --;		
									}
									if (m->get_zone(o->x, o->y, FALSE) == m->sub[i]->param[0])	{ // reached end zone
										o->target = FALSE;
										m->sub[i]->target[j] = -1;	// remove from target checking
									}
									break;
								case SM_ESCORT:  // same as SM_TRANSPORT
									if (!o->target) {	// dead?
										m->sub[i]->target[j] = -1;	// remove from target checking
									}
									if (m->get_zone(o->x, o->y, FALSE) == m->sub[i]->param[0])	{
										o->target = FALSE;
										m->sub[i]->target[j] = -1;
										m->sub[i]->param[2] --;			// one there
										m->sub[i]->num_targets --;
									}
									break;
								case SM_GOTO:
									// do nothing
									break;
								default: 
									break;
							}
						}
					}
				}

				//textprintf(screen, font, 200, 200+i*10, 1, "%d", targets_left);

				int status = 0;
				switch (m->sub[i]->type) {
					case SM_DESTROY:
						if (m->sub[i]->param[2] <= 0) status = MISSION_SUCCESS;
						else if (m->sub[i]->param[1] > 0 && m->sub[i]->end_time < sec_count) status = MISSION_FAIL;
						break;
					case SM_DEFEND:
						if (targets_left < m->sub[i]->param[2]) status = MISSION_FAIL;
						else if (m->sub[i]->param[1] > 0 && m->sub[i]->end_time < sec_count) status = MISSION_SUCCESS;
						break;
					case SM_TRANSPORT:
						if (m->sub[i]->param[2] <= 0) status = MISSION_SUCCESS;
						else if (targets_left < m->sub[i]->param[2]) status = MISSION_FAIL;
						break;
					case SM_ESCORT:	// NOT same as SM_TRANSPORT
						if (m->sub[i]->param[2] <= 0) status = MISSION_SUCCESS;
						else if (targets_left < m->sub[i]->param[2]) status = MISSION_FAIL;
						break;
					case SM_STRAFE:
						if (m->sub[i]->param[2] <= 0) status = MISSION_SUCCESS;
						else if (!targets_left && m->sub[i]->param[2]) status = MISSION_FAIL;
						break;
					case SM_GOTO:
						if (p->heli->heli_altitude ==0) {
							if (m->get_zone(p->heli->x, p->heli->y, FALSE) == m->sub[i]->param[0])	{
								status = MISSION_SUCCESS;
							}
						}
						break;
				}
				if (status == MISSION_SUCCESS) {
					m->sub[i]->completed = TRUE;
					m->event[m->sub[i]->success_event] = TRUE;
					p->new_info = p->not_played = TRUE;
				}
				else if (status == MISSION_FAIL) {
					m->sub[i]->completed = TRUE;
					m->sub[i]->failed = TRUE;
					m->event[m->sub[i]->fail_event] = TRUE;
					p->new_info = p->not_played = TRUE;
				}
			}
		}
	}
}
			
// checks for new sub missions in a mission
void check_sub_missions(mission *m, player *p) {
	int i,j;

	for(i=0;i<MAX_SUB_MISSIONS;i++) {
		if (m->sub[i] != NULL) {
			if (!m->sub[i]->completed && !m->sub[i]->triggered) { // not an old mission (or current)
				if (m->event[m->sub[i]->event_id]) {				// trigg it?
					p->new_info = p->not_played = TRUE;
					m->sub[i]->triggered = TRUE;
					m->sub[i]->num_targets = 0; // new'
					for(j=0;j<MAX_TARGETS;j++) {
						if (m->sub[i]->target[j] != -1) {
							if (m->obj[m->sub[i]->target[j]] != NULL) {
								if (m->obj[m->sub[i]->target[j]]->essential) {
									m->obj[m->sub[i]->target[j]]->target = TRUE;
									m->sub[i]->num_targets ++; // new
								}
							}
						}
					}
					m->sub[i]->start_time = sec_count;
					m->sub[i]->end_time = m->sub[i]->start_time + m->sub[i]->param[1];
				}
			}
		}
	}
}

// shows a message on screen
void show_text(char *txt) {
	text_time = 2*1000/FRAME_LENGTH;
	strcpy(text_message, txt);
}

// check if cht is inside cheat_sequence
bool check_cheat_sequence(int *cht) {
	int c,s;

	for(int i=0;i<16;i++) {
		c = 0;
		s = i;
		while(cht[c] == cheat_sequence[s]) {
			c++; s++;
			if (s == 16) s = 0;
			if (cht[c] == NULL) return TRUE;	// reached end of cheat
		}
	}
	return FALSE;
}

// check for and activate cheat
void check_for_cheats(player *p) {
	if (keypressed()) {
		cheat_sequence[cs_pos] = readkey() >> 8;
		if (++cs_pos == 16) cs_pos = 0;
		if (check_cheat_sequence(cht_guns)) {
			// unlimited ammo
			special_fx[0] = TRUE;
			show_text("UNLIMITED MACHINE GUN AMMO");
			clear_cheat();
		} else if (check_cheat_sequence(cht_fire)) {
			// unlimited phosphor missiles
			special_fx[3] = TRUE;
			show_text("UNLIMITED PHOSPHOR MISSILES");
			clear_cheat();
		} else if (check_cheat_sequence(cht_demo)) {
			// unlimited normal missiles
			special_fx[1] = TRUE;
			show_text("UNLIMITED MISSILES");
			clear_cheat();
		} else if (check_cheat_sequence(cht_home)) {
			// unlimited heat seeking missiles
			special_fx[2] = TRUE;
			show_text("UNLIMITED HEAT SEEKING MISSILES");
			clear_cheat();
		} else if (check_cheat_sequence(cht_cat)) {
			// unlimited lives
			special_fx[5] = TRUE;
			show_text("UNLIMITED LIVES");
			p->lives = 3;
			clear_cheat();
		} else if (check_cheat_sequence(cht_shld)) {
			// unlimited armour
			special_fx[4] = TRUE;
			show_text("UNLIMITED ARMOUR");
			clear_cheat();
		} else if (check_cheat_sequence(cht_beam)) {
			// unlimited armour
			special_fx[6] = TRUE;
			show_text("BEAM ME UP SCOTTY!");
			clear_cheat();
		} 
	}
}

void show_m_desc(mission *m) {
	int x=18, y=100;
	clear(screen);
	set_palette(black_palette);
	draw_sprite(screen, (BITMAP *)gen[DIALOG].dat, x, y);
	textout(screen, (FONT *)gen[FONT_MENU].dat, m->name, x+32, y, -1);
	textout(screen, (FONT *)gen[FONT_DIALOG_BOLD].dat, "Mission briefing", x+150, y+40, 97);
	draw_sprite(screen, (BITMAP *)gen[BUTT_OK].dat, x+482, y+220);
	draw_sprite(screen, (BITMAP *)gen[MICON000+m->micon].dat, x+32, y+50);
	rect(screen, x+30,  y+48, x+133, y+185, 100);
	
	fade_in((RGB *)gen[0].dat, 16);
	break_text(screen, m->text, x+150, y+60, 430, 100, TRUE);

	while(key[KEY_ENTER]);

	do {
		ctrl->poll_controls();
		if (ctrl->is_fire1() || key[KEY_ENTER]) draw_sprite(screen, (BITMAP *)gen[BUTT_OK2].dat, x+482, y+220);
		else draw_sprite(screen, (BITMAP *)gen[BUTT_OK].dat, x+482, y+220);
	} while(!ctrl->is_fire1() && !key[KEY_ENTER]);

	fade_out(16);
}

// plays a mission
int play_mission(mission *m, player *p) {
	bool playing = TRUE;
	int mission_trigger = 100;
	int sx, sy;
	bool fade_in_now = TRUE;
	int status = 0;
	
	// tmp
	m->random_noise(82+59, 90+59);
	p->heli->team = 1;
	p->heli->target_thrust = 1;
	sx = p->heli->x;
	sy = p->heli->y;
	p->heli->downed = FALSE;
	play_sample((SAMPLE *)sfx[1].dat, 0.7*options.sound_volume, 128, 800, 1);
	
	// show mission description
	show_m_desc(m);

	// set first event
	m->event[0] = TRUE;

	while(playing) { // play the game here
		frame_count ++;
		if (text_time) text_time--;
		game_count = 0;

		// get player input
		if (!p->dead) handle_player_input(m, p);

		p->update();
		if (p->new_info && p->not_played) {
			play_obj_sample(m->obj[0], 19);
			p->not_played = FALSE;
		}

		// update smokes and debris
		update_smoke(m);
		update_debris(m);

		// move unit + AI evaluation
		update_units(m);
	
		// move projectiles, check hits
		update_projectiles(m, p);

		// draw it all
		if (!(frame_count%options.frame_step) || fade_in_now) draw_mission(m, p);
		if (fade_in_now) {
			fade_in((RGB *)m->gfx[0].dat, 8);
			fade_in_now = FALSE;
		}


		// take out burning soldiers
		check_for_fires(m);

		// check player stats (is player dead?)
		if (p->dead) {
			if (p->heli->heli_altitude) {
				p->heli->heli_altitude--;
				adjust_sample((SAMPLE *)sfx[1].dat, options.sound_volume*0.7, 128, 800 + p->heli->heli_altitude * 4, 1);
			}
			else {
				if (!p->heli->downed) {
					play_obj_sample(p->heli, 6+rand()%4);
					stop_sample((SAMPLE *)sfx[1].dat);
					scenery *s = new scenery();
					s->base_image = p->heli->wreck_image;
					s->wreck_image = 0;
					s->shadow_image = 0;
					s->angle = p->heli->angle;
					s->set_position(p->heli->x, p->heli->y);
					m->add_object(s, TRUE);
					p->heli->empty(m);
					p->heli->downed = TRUE;
					if (!p->lives) if (got_sound) play_mod((JGMOD *)msc[FAILURE_XM].dat, FALSE);
				}
			}
			
			if (key[KEY_ENTER]) {   // restart or end game
				if (!p->lives) {
					draw_sprite(screen, (BITMAP *)gen[GAMEOVER].dat, 64, 80);
					playing = FALSE;		
					while(key[KEY_ENTER]);
					while(!key[KEY_ENTER]);
				}
				else {
					if (!special_fx[5]) p->lives --;
					fade_out(8);
					p->ammo[0] = MAX(500, p->ammo[0]);
					p->ammo[1] = MAX(4, p->ammo[1]);
					p->heli->armour = p->heli->armour = 150;
					p->heli->x = sx;
					p->heli->y = sy;
					p->heli->rot_str = p->heli->str_str = p->heli->thr_str = 
					p->heli->x_force = p->heli->y_force = 0;
					p->dead = FALSE;
					p->heli->angle = 0;
					fade_in_now = TRUE;
					play_sample((SAMPLE *)sfx[1].dat, options.sound_volume*0.7, 128, 800, 1);
					p->heli->downed = FALSE;
				}
			}
		}
		
		// check / update sub_missions
		if (++mission_trigger > 200) {
			mission_trigger = 0;
			check_sub_missions(m, p);
		}
		update_sub_missions(m, p);

		if (m->event[E_RESTART]) {
			status = 2; playing = FALSE;
		}
		if (m->event[E_ADVANCE]) {
			status = 1; playing = FALSE;
		}

		// check function keys
		if (key[KEY_ESC]) {
			int seconds = sec_count;
			clear(screen);
			set_palette((RGB *)gen[TITLE_PAL].dat);
			drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
			color_map = &fire_table;
			int ret = handle_menu(game_menu, game_menu_callback); 
			while (ctrl->is_any()) ctrl->poll_controls();
			if (ret == IDM_END) playing = FALSE;
			sec_count = seconds;		
			set_palette((RGB *)m->gfx[0].dat);
		}
		if (key[KEY_F1]) show_help_screen(1);
		if (key[KEY_ENTER] && !p->dead && !fade_in_now) show_map_screen(m, p);
	
		if (key[KEY_F2]) {
			//rotate_scaled_sprite(swap_screen, (BITMAP *)gen[TITLE].dat, 10, 10, 0, ftofix(0.4));
			save_bmp("hcarrier.bmp", swap_screen, NULL);
		}
		
		
		// detect cheat codes
		check_for_cheats(p);
		if (special_fx[4]) p->heli->armour = 150;
		if (special_fx[6]) { special_fx[6] = FALSE; status = 1; playing = FALSE; }
		/*
		if (special_fx[4]) {
			if (key[KEY_1]) m->event[1] = TRUE;
			if (key[KEY_2]) m->event[2] = TRUE;
			if (key[KEY_3]) m->event[3] = TRUE;
			if (key[KEY_4]) m->event[4] = TRUE;
			if (key[KEY_5]) m->event[5] = TRUE;
		}
		*/

		//if (!key[KEY_TAB]) 
		while(!game_count);
	}

	if (status == 2) {  // snafu
		if (!special_fx[5]) p->lives --;
		if (p->lives<0) {
			draw_sprite(screen, (BITMAP *)gen[GAMEOVER].dat, 64, 80);
			while(key[KEY_ENTER]);
			while(!key[KEY_ENTER]);
			status = 0;
		}
	}


	stop_sample((SAMPLE *)sfx[1].dat);
	stop_mod();

	if (status == 1) if (got_sound) play_mod((JGMOD *)msc[TRIUMPH_XM].dat, FALSE);

	fade_out(8);
	return status;
}

void COP_draw_stat_bar(BITMAP *bmp, char *lbl, int len, int val, int x, int y) {
	for(int i=0;i<101;i+=10) {
		line(bmp, x+i, y+6, x+i, y+16, 253-(13*i)/100);
		if (i) rect(bmp, x+i, y+7, x-10+i, y+15, 253-(13*i)/100);
	}
	textout(bmp, (FONT *)gen[FONT_SMALL].dat, lbl, x, y, 101);
	rectfill(bmp, x, y+7, x+len, y+15, 253-(13*len)/100);
	textprintf(bmp, (FONT *)gen[FONT_SMALL].dat, x+2, y+9, 15, "%d %%", val);
}

int break_text(BITMAP *bmp, char *txt, int ox, int oy, int w, int col, bool step) {
	char buf[2] = { 0, 0 };
	char row[512];
	int len = strlen(txt);
	int x, y, i, l, r;

	// calc rowbreaks
	i=0; x=0; y=0; l=0; r=1;
	do {
		buf[0]=txt[x];
		l+=text_length((FONT *)gen[FONT_DIALOG].dat,buf);
		row[y]=buf[0]; row[y+1]='\0';
		if (buf[0]==' ') i=x;
		if (buf[0]=='@') txt[x]='\0';
		if (buf[0]=='#') { row[0]=' '; row[1]='\0'; y=-1; l=0; r++; }
		if (l>w) {
			txt[i]='#';
			x=i;
			row[0]=' ';
			row[1]='\0';
			l=0;
			y=-1;
			r++;
		}
		x++; y++;
	} while (x<len);

	// print text
	i=0;
	x = ox; y = oy;
	do {
		buf[0]=txt[i];
		if (buf[0]!='#') {
			textout(bmp, (FONT *)gen[FONT_DIALOG].dat, buf, x, y, col);
			if (step) {
				game_count=0;
				if (!key[KEY_ENTER]) while(!game_count);
			}
			x+=text_length((FONT *)gen[FONT_DIALOG].dat, buf);
		} 
		else {
			x = ox;
			y += 16;
		}
		i++;
	} while(i<len);

	return y;
}

void select_co_pilot(Tco_pilot *cp) {
	Tco_pilot cop[50];
	Tco_pilot t_cop = { 0, 0, 0, 0, "", "" };
	bool done = FALSE;
	int i=0;
	char buf[256], *ptr;
	int cop_id = 1;
	int num_cops;
	Tm_line xl[16];
	Tm_line yl[16];
	int y = 640, x = 18;
	int cheat = 0;
	int cheat_sequence[10] = { KEY_O, KEY_U, KEY_T, KEY_T, KEY_O, KEY_L, KEY_U, KEY_N, KEY_C, KEY_H }; 
	int cheating = FALSE;
	int ok = 0;

	solid_mode();
	
	// init lines
	for(i=0;i<16;i++) { xl[i].delta = 0; while(!xl[i].delta) xl[i].delta = rand()%3-1; xl[i].pos = rand()%60 + 525-18+x; xl[i].value = rand()%10 + 99; }
	for(i=0;i<16;i++) { yl[i].delta = 0; yl[i].pos = 234-50+y; yl[i].value = rand()%10 + 99; }

	// read cops from disc, todo: put file in datafile
	i=0;
	ptr = (char *)gen[COPFILE].dat;
	while(!done) {
		ptr = get_line(ptr, buf);     // read a line
		if (stricmp(buf, "#end#")) {
			strcpy(cop[i].callsign, buf);
		    
			ptr = get_line(ptr, buf);     // read a line
			cop[i].fire_skill = atoi(buf);
		    
			ptr = get_line(ptr, buf);     // read a line
			cop[i].fire_speed = atoi(buf);
		    
			ptr = get_line(ptr, buf);     // read a line
			cop[i].repair_skill = atoi(buf);
		    
			ptr = get_line(ptr, buf);     // read a line
			cop[i].repair_speed = atoi(buf);
		    
			ptr = get_line(ptr, buf);     // read a line
			strcpy(cop[i].desc, buf);
			cop[i].id = i;
		
			ptr = get_line(ptr, buf);     // read a line
			i++;
		}
		else done = TRUE;
	}
	num_cops = i;  

	// use only 5 of 45
	if (!key[KEY_J] || !key[KEY_P]) {
		for(i=1;i<6;i++)
			cop[i] = cop[(i-1)*9+rand()%9+1];
		num_cops = 6;  
	}

	set_palette((RGB *)gen[0].dat);
	done = FALSE;
	while(!done) {
		game_count = 0;
		clear_to_color(swap_screen, 15);
	
		draw_sprite(swap_screen, (BITMAP *)gen[DIALOG].dat, x, y);
		textout(swap_screen, (FONT *)gen[FONT_MENU].dat, "Select co-pilot", x+32, y, -1);
	
		rect(swap_screen, x+30,  y+48, x+133, y+185, 100);
		rect(swap_screen, x+382, y+48, x+137, y+185, 100);
		rect(swap_screen, x+386, y+48, x+502, y+185, 100);
		rect(swap_screen, x+572, y+48, x+506, y+70,  100);
		rect(swap_screen, x+572, y+74, x+506, y+185, 100);
	
		if (ctrl->is_left()) draw_sprite(swap_screen, (BITMAP *)gen[BUTT_PREV2].dat, x+52, y+220);
		else draw_sprite(swap_screen, (BITMAP *)gen[BUTT_PREV].dat, x+52, y+220);
		if (ctrl->is_right()) draw_sprite(swap_screen, (BITMAP *)gen[BUTT_NEXT2].dat, x+132, y+220);
		else draw_sprite(swap_screen, (BITMAP *)gen[BUTT_NEXT].dat, x+132, y+220);
		if (ctrl->is_fire1()) draw_sprite(swap_screen, (BITMAP *)gen[BUTT_OK2].dat, x+482, y+220);
		else draw_sprite(swap_screen, (BITMAP *)gen[BUTT_OK].dat, x+482, y+220);
	
		textout(swap_screen, (FONT *)gen[FONT_DIALOG].dat, "STATISTICS", x+392, y+52, 96);
		textprintf(swap_screen, (FONT *)gen[FONT_DIALOG].dat, x+350, y+52, 96, "%d/%d", cop_id, num_cops-1);
	
		COP_draw_stat_bar(swap_screen, "AIMING", t_cop.fire_skill, cop[cop_id].fire_skill, x+392, y+70);
		COP_draw_stat_bar(swap_screen, "FIRE SPEED", t_cop.fire_speed, cop[cop_id].fire_speed, x+392, y+90);
		COP_draw_stat_bar(swap_screen, "REPAIR SKILL", t_cop.repair_skill, cop[cop_id].repair_skill, x+392, y+110);
		COP_draw_stat_bar(swap_screen, "REPAIR SPEED", t_cop.repair_speed, cop[cop_id].repair_speed, x+392, y+130);
	
		draw_sprite(swap_screen, (BITMAP *)gen[COP000+cop[cop_id].id].dat, x+32, y+50);
		textout(swap_screen, (FONT *)gen[FONT_MENU].dat, cop[cop_id].callsign, x+152, y+50, -1);
		break_text(swap_screen, cop[cop_id].desc, x+152, y+90, 200, 101, FALSE);

		// draw lines
		for(i=0;i<16;i++) { 
			line(swap_screen, xl[i].pos, y+49, xl[i].pos, y+69, xl[i].value);
			line(swap_screen, x+507, yl[i].pos, x+571, yl[i].pos, yl[i].value);
		}

		blit_to_screen(swap_screen);

		if ((ctrl->is_left() || ctrl->is_up())&& ok) { cop_id--; ok = 0; play_sample((SAMPLE *)sfx[11].dat, 200, 128, 1000, 0); }
		if ((ctrl->is_right() || ctrl->is_down())&& ok) { cop_id++; ok = 0; play_sample((SAMPLE *)sfx[11].dat, 200, 128, 1000, 0); }
		if ((ctrl->is_fire1() || ctrl->is_menu_yes()) && ok) { done = TRUE; play_sample((SAMPLE *)sfx[19].dat, 200, 128, 1000, 0); }
		if (!ctrl->is_any()) ok = 1;
		if (cop_id == -1 || (cop_id == 0 && !cheating)) cop_id = num_cops-1;
		if (cop_id == num_cops) cop_id = (cheating ? 0 : 1);

		if (t_cop.fire_skill < cop[cop_id].fire_skill) t_cop.fire_skill++;
		if (t_cop.fire_speed < cop[cop_id].fire_speed) t_cop.fire_speed++;
		if (t_cop.repair_speed < cop[cop_id].repair_speed) t_cop.repair_speed++;
		if (t_cop.repair_skill < cop[cop_id].repair_skill) t_cop.repair_skill++;
		if (t_cop.fire_skill > cop[cop_id].fire_skill) t_cop.fire_skill--;
		if (t_cop.fire_speed > cop[cop_id].fire_speed) t_cop.fire_speed--;
		if (t_cop.repair_speed > cop[cop_id].repair_speed) t_cop.repair_speed--;
		if (t_cop.repair_skill > cop[cop_id].repair_skill) t_cop.repair_skill--;

		if (key[KEY_F2]) save_bmp("hc_cop.bmp", swap_screen, NULL);

		// move lines
		for(i=0;i<16;i++) { 
			// xlines
			xl[i].pos += xl[i].delta;
			if (xl[i].pos < x+508 || xl[i].pos > x+570) xl[i].delta = -xl[i].delta;
			else if (rand()%100<5) xl[i].delta = -xl[i].delta;
			// ylines
			if (yl[i].pos == y+184) yl[i].delta = -(rand()%(i+1));
			yl[i].pos += yl[i].delta;
			if (yl[i].pos < y+75) yl[i].pos = y+75;
			if (yl[i].pos > y+184) { yl[i].pos = y+184; yl[i].delta = 0; }
			if (yl[i].pos != y+184) yl[i].delta++;
		}

		// check cheat 
		if (cheat < 10) {
			if (key[cheat_sequence[cheat]]) cheat++;
			if (cheat == 10) cheating = TRUE; 
		}

		if (y>100) y-=16;

		ctrl->poll_controls();
		while(!game_count);
	}

	strcpy(cp->callsign, cop[cop_id].callsign);
	strcpy(cp->desc, cop[cop_id].desc);
	cp->fire_skill = cop[cop_id].fire_skill;
	cp->fire_speed = cop[cop_id].fire_speed;
	cp->repair_skill = cop[cop_id].repair_skill;
	cp->repair_speed = cop[cop_id].repair_speed;
	cp->id = cop[cop_id].id;

	fade_out(8);
	clear_to_color(screen, 15);
}

// shows the end :)
void show_end(int original) {
	int y, i;
	RGB rgb = { 0, 0, 0 };
	PALETTE this_pal;
	char *text1[] = { "Your mission is complete,", "The reign of terror", "has come to an end.", "", "Good work pilot!", "Peace has been restored." };
	char *text2[] = { "Well done pilot!", "", "You have completed your missions", "and saved the day!" };

	clear(screen);

	set_palette((RGB *)gen[TITLE_PAL].dat);

	// dark pic palette (0 - 95)
	for(y=0;y<16;y++) {
		set_color(y, &rgb);
	}
	for(y=16;y<96;y++) {
		rgb.r = ((RGB *)gen[TITLE_PAL].dat)[y].r >> 2;
		rgb.g = ((RGB *)gen[TITLE_PAL].dat)[y].g >> 2;
		rgb.b = ((RGB *)gen[TITLE_PAL].dat)[y].b >> 2;
		set_color(y, &rgb);
	}
	get_palette(this_pal);

	// scroll pic from top and down
	for(y=-300;y<=0;y++) {
		game_count = 0;
		blit((BITMAP *)gen[FINISH].dat, screen, 0, 0, 0, y, 640, 300);
		while(!game_count);
	}

	// add text
	textout_centre(screen, (FONT *)gen[FONT_MENU].dat, "CONGRATULATIONS!", 320, 300, -1);

	// fade in complete pal
	fade_from(this_pal, (RGB *)gen[TITLE_PAL].dat, 1);

	if (original) {
		clear(swap_screen);
		for(i=0;i<6;i++) textout_centre(swap_screen, (FONT *)gen[FONT_MENU].dat, text1[i], 320, 150+i*30, -1);

		for(y=0;y<300;y++) {
			game_count = 0;
			blit(swap_screen, screen, 0, y, 0, 330, 640, 300);
			while(game_count<2);
		}

		// fade to blue
		fade_from((RGB *)gen[TITLE_PAL].dat, (RGB *)gen[FINISH_PAL].dat, 4);
		rest(1000);
		fade_out(1);
		
		// show more text
		clear(screen);
		textout_centre(screen, (FONT *)gen[FONT_MENU].dat, "Fight! For everlasting peace!", 320, 200, -1);
		fade_in((RGB *)gen[FINISH_PAL].dat, 4);
		rest(3000);
		fade_out(1);
	}
	else {
		// show more text
		clear(swap_screen);
		for(i=0;i<4;i++) textout_centre(swap_screen, (FONT *)gen[FONT_MENU].dat, text2[i], 320, 150+i*30, -1);

		for(y=0;y<300;y++) {
			game_count = 0;
			blit(swap_screen, screen, 0, y, 0, 330, 640, 300);
			while(game_count<2);
		}
		fade_out(1);
	}

	clear(screen);
	rest(1000);
	show_credits();
}

// intiates a new game
void new_game(char *campfile) {
	int i,j,k;
	int m_num = 0;
	int done = FALSE;

	Tco_pilot cop;
	select_co_pilot(&cop);

	player *p = new player();
	p->ammo[0] = 500;
	p->ammo[1] = 4;
	p->ammo[2] = 0;
	p->ammo[3] = 0;
	p->next_sec_weapon();
	p->set_co_pilot(&cop);

	for(i=0;i<7;i++) special_fx[i] = FALSE;

	// user is done, load and play missions one by one

	// for each mission in campaign
	m_num=0;
	while(!done) {
		char mission_file[512];

		clear(screen);
		draw_sprite(screen, (BITMAP *)gen[LOADING].dat, 260, 200);
		fade_in((RGB *)gen[TITLE_PAL].dat, 16);
	

		if (campfile == NULL) { // read the datafile
			char *cptr = (char*)gen[CAMP1].dat;
			j = 0;
			for(i=0;i<=m_num;i++) {
				k = 0;
				while(cptr[j] != '\n' && cptr[j] != '\0') {
					mission_file[k] = cptr[j];
					j++; k++;
				}
				mission_file[k-1] = '\0';
				j++;
			}
		}
		else {  // get filename from file
			FILE *fp;
			fp = fopen(campfile, "rt");
			if (fp == NULL) {
				clear(screen);
				textout_centre(screen, (FONT *)gen[FONT_MENU].dat, "* FILE NOT FOUND *", 320, 100, -1);
				textout_centre(screen, (FONT *)gen[FONT_DIALOG].dat, "The campaign file", 320, 210, 1);
				textprintf_centre(screen, (FONT *)gen[FONT_DIALOG].dat, 320, 230, 1, "< %s >.", campfile);
				textout_centre(screen, (FONT *)gen[FONT_DIALOG].dat, "was not found.", 320, 250, 1);
				textout_centre(screen, (FONT *)gen[FONT_DIALOG].dat, "Press SPACE to abort.", 320, 440, 5);
				while(key[KEY_SPACE]); while(!key[KEY_SPACE]);
				fade_out(8);
				return ;
			}
			else {
				for(i=0;i<=m_num;i++) {
					fgets(mission_file, 512, fp);
					mission_file[strlen(mission_file)-1] = '\0';
				}
				fclose(fp);
			}
		}

		if (!exists(mission_file)) { // mission file not found
			if (!stricmp("#end#", mission_file)) {
				fade_out(8);
				show_end((campfile == NULL ? TRUE : FALSE));
				return;
			}
			else {
				clear(screen);
				textout_centre(screen, (FONT *)gen[FONT_MENU].dat, "* FILE NOT FOUND *", 320, 100, -1);
				textprintf_centre(screen, (FONT *)gen[FONT_DIALOG].dat, 320, 210, 1, "The mission < %s > was not found.", mission_file);
				if (campfile != NULL) {
					textprintf_centre(screen, (FONT *)gen[FONT_DIALOG].dat, 320, 240, 1, "%s", "Check the paths and filenames in the campaign file");
					textprintf_centre(screen, (FONT *)gen[FONT_DIALOG].dat, 320, 260, 1, "< %s >.", campfile);
				}
				else textout_centre(screen, (FONT *)gen[FONT_DIALOG].dat, "Please reinstall the game.", 320, 240, 1);
				textout_centre(screen, (FONT *)gen[FONT_DIALOG].dat, "Press SPACE to abort.", 320, 440, 5);
				while(key[KEY_SPACE]); while(!key[KEY_SPACE]);
				fade_out(8);
				return;
			}
		}


		mission *m = new mission(gen);
		m->load(mission_file);
		set_config_file("data/config.txt");
		m->o = &options;
		p->set_vehicle(m->obj[0]);
		p->new_info = FALSE;
	

		((RGB *)m->gfx[0].dat)[0].r = ((RGB *)m->gfx[0].dat)[0].g = ((RGB *)m->gfx[0].dat)[0].b = 0;
		if (((RGB *)m->gfx[0].dat)[254].r > 50 || ((RGB *)m->gfx[0].dat)[254].g > 50 || ((RGB *)m->gfx[0].dat)[254].b > 50)
			text_color = 15;
		else 
			text_color = 1;
	
		create_rgb_table(&rgb_table, (RGB *)m->gfx[0].dat, NULL);
		rgb_map = &rgb_table;
		create_trans_table(&trans_table_50, (RGB *)m->gfx[0].dat, 50, 50, 50, NULL);
		create_trans_table(&trans_table_100, (RGB *)m->gfx[0].dat, 100, 100, 100, NULL);
		create_light_table(&white_table, (RGB *)m->gfx[0].dat, 52, 52, 52, NULL);
		create_light_table(&black_table, (RGB *)m->gfx[0].dat,  0,  0,  0, NULL);
		create_color_table(&explosion_table, (RGB *)m->gfx[0].dat, return_explosion_color, NULL);
		solid_mode();
	
		p->heli->w = ((BITMAP *)m->gfx[p->heli->base_image].dat)->w;
		p->heli->h = ((BITMAP *)m->gfx[p->heli->base_image].dat)->h;
	
		sec_count = 0;
		clear_keybuf();
		clear_cheat();
		show_text("");
	
		fade_out(16);

	
		int end = play_mission(m, p);
		m->~mission();	// waste old mission 

		// depending on end, do different stuff
		//  end can be stuff like: quit, success, fail, more...
		if (end == 0) done = TRUE; // quit
		if (end == 2) m_num--; // redo mission
		if (end == 1) { // next mission
			clear(screen);
			set_palette((RGB *)gen[TITLE_PAL].dat);
			for(i=1;i<101;i+=2) {
				game_count = 0;
				clear(swap_screen);
				textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, "Mission Complete!", 320, 280+120*(i/100.0), 15);
				textout_centre(swap_screen, (FONT*)gen[FONT_MENU].dat, "Mission Complete!", 318, 278+120*(i/100.0), -1);
				stretch_sprite(swap_screen, (BITMAP *)gen[MCOMPLETE].dat, 320-160*(i/100.0), 240-120*(i/100.0), 320*(i/100.0), 240*(i/100.0));
				blit_to_screen(swap_screen);
				while(!game_count);
			}
			while(!ctrl->is_fire1() && !key[KEY_ENTER]) ctrl->poll_controls();
			fade_out(8);
		}; 

		m_num++;
	}

	if (got_sound) play_mod((JGMOD *)msc[MENU_XM].dat, TRUE);
	goto_mod_track(1);
}


///////////////////////////////////////////////////////////////
// CREDITS STUFF
///////////////////////////////////////////////////////////////
char *get_line(char *src, char *dest) {
	int c = 0;
	while(src[c] != '\n') { 
		dest[c]  = src[c];
		c++;
	}
	dest[c-1] = '\0';
	return &src[c+1];
}

void show_credits() {
	blob *b[64];
	int numb = 32;
	int i, num_lines;
	RGB rgb = { 0, 0, 0 };
	int frame_count = 0;
	int x,y;
	BITMAP *logo_org = (BITMAP *)gen[TITLE].dat;
	BITMAP *logo = create_bitmap(((BITMAP *)gen[TITLE].dat)->w, ((BITMAP *)gen[TITLE].dat)->h);
	bool done = FALSE;
	PALETTE pal;
	int col_type = rand()%6;
	char buf[128];
	char lines[64][128];
	char *cred;
	char *ptr;

	fade_out(8);
	clear(screen);
	set_color(0, &rgb);

	// setup palette
	for(i=0;i<128;i++) {
		rgb.r = (i*63)/128;
		rgb.g = (i>=64 ? (i-64) : 0);
		rgb.b = (i>=96 ? (i-96)*2 : 0);
		set_color(i, &rgb);
	}

	get_palette(pal);
	set_palette(black_palette);

	// create blobs
	for(i=0;i<numb;i++) {
		b[i] = new blob(rand()%50+50, 0, 127);
		b[i]->x = rand()%(639-b[i]->bmp->w);
		b[i]->y = rand()%(479-b[i]->bmp->h);
	}

	// convert logo to new palette
	for(i=0;i<16;i++) {
		rgb.r = rgb.g = rgb.b = 63-i*4;
		set_color(128+i, &rgb);
	}

	clear(logo);
	for(y=0;y<logo->h;y++)
		for(x=0;x<logo->w;x++)
			if (logo_org->line[y][x]) logo->line[y][x] = 63 - logo_org->line[y][x] * 4;
			else logo->line[y][x] = 1;

	// read credits from file
	num_lines = 0;
	cred = ptr = (char *)gen[CREDITS].dat;
	while(!done) {
	    ptr = get_line(ptr, buf);  // read a line
		if (stricmp(buf, "#end#")) {
			strcpy(lines[num_lines], buf); 
			num_lines++;
		}
		else done = TRUE;
	}

	//fade_in(pal, 8);
	set_palette(pal);

	// show credits
	char *txt;
	int the_font;
	int justify;
	int y_pos = SCREEN_H;
	while(!key[KEY_ESC]) {
		game_count = 0;
		frame_count ++;

		// fade in...
		/*
		if (frame_count < 63) {
			for(i=0;i<128;i++) {
				get_color(i, &rgb);
				if (rgb.r < pal[i].r) rgb.r++;
				if (rgb.g < pal[i].g) rgb.g++;
				if (rgb.b < pal[i].b) rgb.b++;
				set_color(i, &rgb);
			}
		}
			*/

		if (!(frame_count%options.frame_step)) {
			clear(swap_screen);
	
			textout_right(swap_screen, (FONT *)gen[FONT_SMALL].dat, "Press ESC to return to menu.", 630, 466, 96);
	
			// draw logo
			draw_sprite(swap_screen, logo, 320-logo->w/2, 130);
	
			// draw text
			for(i=0;i<num_lines;i++) {
				if (y_pos + i*34 < SCREEN_H && y_pos + i*34 > -40) {
					txt = lines[i] + 2;
					the_font = (lines[i][1] == '1' ? FONT_CRED_BIG : FONT_CRED_SML);
					justify = (lines[i][0] == '<' ? 0 : (lines[i][0] == '-' ? 1 : 2));
		
					if (justify == 0) textout(swap_screen, (FONT *)gen[the_font].dat, txt, 80, y_pos + i*34, -1);
					else if (justify == 1) textout_centre(swap_screen, (FONT *)gen[the_font].dat, txt, 320, y_pos + i*34, -1);
					else textout_right(swap_screen, (FONT *)gen[the_font].dat, txt, 560, y_pos + i*34, -1);
				}
			}
	
			// draw blobs
			for(i=0;i<numb;i++) b[i]->draw(swap_screen);
			
			blit_to_screen(swap_screen);
		}


		// move blobs
		for(i=0;i<numb;i++) {
			b[i]->x = SCREEN_W / 2  - b[i]->bmp->w / 2 + SCREEN_W / (i/8+4) * cos((i%3 ? -1 : 1) * frame_count*(i+15)/2000.0 + i);
			b[i]->y = SCREEN_H / 2  - b[i]->bmp->h / 2 + SCREEN_H / (i/8+4) * sin((i%3 ? -1 : 1) * frame_count*(i+15)/2000.0 + i);
		}

		// move text
		if (frame_count%2) y_pos --;
		if (y_pos < -num_lines*34-10) y_pos = SCREEN_H;

		if (!key[KEY_TAB]) while(!game_count);
	}

	fade_out(8);
	clear(screen);

	for(i=0;i<numb;i++) b[i]->~blob();

}


///////////////////////////////////////////////////////////////
// MENU STUFF
///////////////////////////////////////////////////////////////


// gets a campaign file
int get_campaign_file(char *buf) {
	int ok = 0;
	FONT *fnt;

	solid_mode();
	set_palette((RGB *)gen[TITLE_PAL].dat);
	show_mouse(screen);
	strcpy(buf, "./missions/");

	gui_bg_color = 1;
	gui_fg_color = 15;

	fnt = font;
	font = (FONT *)gen[FONT_DIALOG].dat;
	ok = file_select_ex("Select campaign to play:", buf, "TXT", 1024, 400, 400);
	font = fnt;

	show_mouse(NULL);

	drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
	return ok;
}

// callback for the game menu
void game_menu_callback(void) {
	frame_count ++;

	clear(swap_screen);

	BITMAP *bg = (BITMAP *)gen[TITLE_BG].dat;
	BITMAP *title = (BITMAP *)gen[TITLE].dat;

	draw_sprite(swap_screen, bg, 320-bg->w/2, 240-bg->h/2);
	draw_sprite(swap_screen, title, 320-title->w/2, 30);

	//draw_fps(swap_screen, 0, 0);
}

// callback for the main menu
void main_menu_callback(void) {
	frame_count ++;

	clear(swap_screen);

	BITMAP *bg = (BITMAP *)gen[TITLE_BG].dat;
	BITMAP *title = (BITMAP *)gen[TITLE].dat;

	stretch_sprite(swap_screen, bg, 0, 0, 640, 480);
	//draw_sprite(swap_screen, bg, 320-bg->w/2, 240-bg->h/2);
	draw_sprite(swap_screen, title, 320-title->w/2, 30);

	//textout_centre(swap_screen, (FONT*)gen[FONT_DIALOG_BOLD].dat, "<NON PUBLIC BETA - DO NOT SPREAD>", 320, 1, 0);
	//textout_centre(swap_screen, (FONT*)gen[FONT_DIALOG_BOLD].dat, "<NON PUBLIC BETA - DO NOT SPREAD>", 319, 0, 1);

	//textprintf_centre(swap_screen, (FONT*)gen[FONT_DIALOG_BOLD].dat, 319+10*cos((double)frame_count*2.0*PI/50.0), 30, 1, "c:%d o:%d (%0x)", ctrl->use_joy, options.use_joy, ctrl->get_flags());

	draw_sprite(swap_screen, (BITMAP *)gen[BANNER_AT].dat, 0, 440);
	draw_sprite(swap_screen, (BITMAP *)gen[BANNER_TIT].dat, 640-184, 440);
}

void set_difficulty(void (*callback)(void)) {
	switch (handle_menu(diff_menu, callback)) {
		case (IDM_D_EASY) : {
			difficulty = 0.5;
		break;
	
		}				
		case (IDM_D_HARD) : {
			difficulty = 1.5;
			break;
		}
		default : {
			difficulty = 1.0;
			break;
			}
		}
}

// handles a Tmenu
// callback is called every frame BEFORE the menu is drawn
int handle_menu(Tmenu *menu, void (*callback)(void)) {
	bool done = FALSE;
	int menu_return = 0;
	bool handle_keys = FALSE;
	int hold_counter = 30;
	int fadein = 0;

	reset_menu(menu, 0);

	while (!done) {
		game_count = 0;

		if (callback != NULL) callback();
		menu_return = update_menu(swap_screen, menu, (handle_keys ? ctrl : NULL), 200, 180, sfx);
		blit_to_screen(swap_screen);
		if (fadein) { fade_in((RGB *)gen[TITLE_PAL].dat, 8); fadein = 0; }
		if (key[KEY_F2]) save_bmp("hc_menu.bmp", swap_screen, NULL);

		if (!ctrl->is_any()) handle_keys = TRUE;
		else handle_keys = FALSE;
		ctrl->poll_controls();


		switch(menu_return) {
			// menu menu stuff
			case (IDM_NEWGAME) : {
				set_difficulty(callback);
				fade_out(8);
				intro2();
				stop_mod();
				clear(screen);
				new_game(NULL);
				fadein = TRUE;
				drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
				color_map = &fire_table;
				break;
			}
			case (IDM_LOADGAME) : {
				char buf[1024];

				if (get_campaign_file(buf)) {
					set_difficulty(callback);
					fade_out(8);
					clear(screen);
					stop_mod();
					new_game(buf);
					fadein = TRUE;
					drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
					color_map = &fire_table;
				}

				break;
			}
			case (IDM_OPTIONS) : {
				handle_menu(opt_menu, callback);
				break;
			}
			case (IDM_CONTROLS) : {
				handle_menu(ctrl_menu, callback);
				break;
			}
			case (IDM_VIDEO_OPTIONS) : {
				handle_menu(video_menu, callback);
				break;
			}
			case (IDM_INSTRUCTIONS) : {
				system("start docs\\manual\\index.html");
				break;
			}
			case (IDM_HIGHSCORES) : {
				my_alert("handle_menu", "Hall of Fame");
				break;
			}
			case (IDM_CREDITS) : {
				show_credits();
				fadein = 1;
				break;
			}
			case (IDM_EXIT) : {   // user wants to quit, TODO: confirm
				done = TRUE;
				break;
			}

			// options menu stuff
			case (IDM_INC_DETAIL) : {
				if (options.detail_level == DETAIL_MED) options.detail_level = DETAIL_HIGH;
				if (options.detail_level == DETAIL_LOW) options.detail_level = DETAIL_MED;
				break;
			}
			case (IDM_DEC_DETAIL) : {
				if (options.detail_level == DETAIL_MED) options.detail_level = DETAIL_LOW;
				if (options.detail_level == DETAIL_HIGH) options.detail_level = DETAIL_MED;
				break;
			}
			case (IDM_TOGGLE_SHADOWS) : {
				options.shadow = (options.shadow ? FALSE : TRUE);
				break;
			}
			case (IDM_TOGGLE_SMOKE) : {
				options.smoke = (options.smoke ? FALSE : TRUE);
				break;
			}
			case (IDM_TOGGLE_CONTROL) : {
				options.use_joy = (options.use_joy ? FALSE : TRUE);
				ctrl->use_joy = options.use_joy;
				break;
			}
			case (IDM_TOGGLE_ANALOG) : {
				if (options.use_joy) {
					options.analog_joy++;
					if (options.analog_joy == 1) options.analog_joy = 2;
					if (options.analog_joy == 4) options.analog_joy = 0;
					ctrl->analog_joy = options.analog_joy;
				}
				break;
			}
			case (IDM_RESET_KEYS) : {
				options.use_joy = FALSE;
				ctrl->set_controls(KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ALT,
						KEY_LCONTROL, KEY_LSHIFT, KEY_Z, KEY_SPACE, KEY_COMMA, KEY_STOP);
				// todo: show/sound confirmation
				break;
			}
			case (IDM_INC_FRAMES) : {
				options.frame_step = MID(1, options.frame_step + 1, 10);
				break;
			}
			case (IDM_DEC_FRAMES) : {
				options.frame_step = MID(1, options.frame_step - 1, 10);
				break;
			}
			case (IDM_INC_SOUND) : {
				if (!options.sound_mute) {
					options.sound_volume = MIN(options.sound_volume+25, 250);
					adjust_sample((SAMPLE *)sfx[1].dat, options.sound_volume*0.7, 128, -1, 1);
				}
				break;
			}
			case (IDM_DEC_SOUND) : {
				if (!options.sound_mute) {
					options.sound_volume = MAX(options.sound_volume-25, 0);
					adjust_sample((SAMPLE *)sfx[1].dat, options.sound_volume*0.7, 128, -1, 1);
				}
				break;
			}
			case (IDM_MUTE_SOUND) : {
				options.sound_mute = (options.sound_mute ? FALSE : TRUE);
				if (options.sound_mute) 
					adjust_sample((SAMPLE *)sfx[1].dat, 0, 128, -1, 1);
				else
					adjust_sample((SAMPLE *)sfx[1].dat, options.sound_volume*0.7, 128, -1, 1);
				break;
			}
			case (IDM_INC_MUSIC) : {
				if (!options.music_mute) options.music_volume = MIN(options.music_volume+25, 250);
			    set_mod_volume((options.music_mute?0:options.music_volume));
				break;
			}
			case (IDM_DEC_MUSIC) : {
				if (!options.music_mute) options.music_volume = MAX(options.music_volume-25, 0);
			    set_mod_volume((options.music_mute?0:options.music_volume));
				break;
			}
			case (IDM_MUTE_MUSIC) : {
				options.music_mute = (options.music_mute ? FALSE : TRUE);
			    set_mod_volume((options.music_mute?0:options.music_volume));
				break;
			}
			case (IDM_BACK) : {
				done = TRUE;
				break;
			}

			// game menu stuff
			case (IDM_RESUME) : {
				done = TRUE;
				break;
			}
			case (IDM_HELP) : {
				fade_out(16);
				clear(screen);
				set_palette((RGB *)gen[TITLE_PAL].dat);
				show_help_screen(0);
				drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
				color_map = &fire_table;
				break;
			}
			case (IDM_END) : {
				done = TRUE;
				break;
			}
			
			case (IDM_D_EASY) : {
				done = TRUE;
				break;
			}
			case (IDM_D_NORMAL) : {
				done = TRUE;
				break;
			}
			case (IDM_D_HARD) : {
				done = TRUE;
				break;
			}
			
			case (0) : break; // nothing has happened
			default : {   // unknown return value
				char buf[128];
				sprintf(buf,"unknown return value: %d", menu_return);
				my_alert("handle_menu", buf);
				break;
			}
		}
		while(!game_count);
	}
	return menu_return;
}



///////////////////////////////////////////////////////////////
// MAIN STUFF
///////////////////////////////////////////////////////////////

void enter_main_menu() {
	int y;

	BITMAP *title = (BITMAP *)gen[TITLE].dat;

	clear(screen);

	drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
	color_map = &fire_table;
	set_palette(black_palette);
	vsync();
	
	// fade in logo
	fade_in_range((RGB *)gen[TITLE_PAL].dat, 64, 0, 15);
	// scroll logo
	for(y=-300;y<30;y+=16) {
		game_count = 0;
		clear(swap_screen);
		draw_sprite(swap_screen, title, 320-title->w/2, y);
		blit_to_screen(swap_screen);
		while(!game_count);
	}
	main_menu_callback();  // draw gfx
	blit_to_screen(swap_screen);
	// fade in image
	PALETTE p;
	get_palette(p);
	fade_from(p, (RGB *)gen[TITLE_PAL].dat, 4);
	for(y=640;y>180;y-=16) {
		game_count = 0;
		main_menu_callback();  // draw gfx
		update_menu(swap_screen, main_menu, NULL, 200, y, sfx);
		blit_to_screen(swap_screen);
		while(!game_count);
	}
}

// main
int main(int argc, char *argv[]) {
	init_game();

	if (got_sound) play_mod((JGMOD *)msc[MENU_XM].dat, TRUE);
	intro();
	enter_main_menu();

	handle_menu(main_menu, main_menu_callback);

	shut_down();
	return 0;
} END_OF_MAIN();


