/*
 _____    _      _                         _
|_   _|  (_)    | |                       (_)
  | |_ __ _  ___| |__  _ __ ___  _ __ ___  _  ___
  | | '__| |/ __| '_ \| '__/ _ \| '_ ` _ \| |/ __|
  | | |  | | (__| | | | | | (_) | | | | | | | (__
  \_/_|  |_|\___|_| |_|_|  \___/|_| |_| |_|_|\___|
  BY KENT DEVILLAFRANCA

(changed mid-project from "Empire Allegro 2", which explains all the "empire" stuff)
*/

//TODO: Test the saving/loading some more

#include "empire.h"

volatile int counter;

_globalsettings global;
_character *currentcharacter[4];
vector<_character *> character;
//_character *loadedcharacter[4];  //remnants from when this had allegro.cc COs
//vector<ol::Bitmap *> avatars;  //left in just for the hell of it
_player player[4];
_map worldmap;
_minimap minimap;

extern void reset_mouse_counter();
extern void check_mouse_stuff();

//int first_mission_character;  //the first "mission-only" AI character
int NUM_UNITS;

bitmapdat land, river, sea, shoal, buildings, special;
bitmapdat mouse;
bitmapdat glb;
bitmapdat greystatbmp, statbmp, sidebarbmp;
bitmapdat explode1, nukeboom;
bitmapdat units[MAX_UNITS], bigunits;
int SIDEBAR_WIDTH, BOTTOMBAR_HEIGHT;

ol::Bitmap *transition;

ol::TextRenderer normal;
ol::TextRenderer med;
ol::TextRenderer big;

_armystats armystats[3];

void load_global_settings();
void load_army_settings();
void load_unit_settings();
void init_tile_animations();
int load_characters();

void text_box(int x, int y, int w, int h, char *ret, int leng, int spaces, string title);
void text_box(int x, int y, int w, int h, char *ret, int leng, int spaces);

int intro();
int logic();
int menu();
int mapedit();
int show_winner();
int helpscreen();
int credits();
int player_color(int army, int tintnum);
void save_debug_info();
void save_pathmap_info();
void save_unitmap_info();
void save_ownermap_info();
void empdebug(string text);
void empdebug(int i);
void empdebug(float f);
string init_everything();

string unit_name(int type);
int unit_type(string name);

sem_t semaphore;

void timer_handler()
{
	counter++;
	sem_post(&semaphore);
}
END_OF_FUNCTION(timer_handler);

void close_button_handler(void)
{
	global.gamestatus = QUIT;
}
END_OF_FUNCTION(close_button_handler)

_init_variables init;
gamestate *game;

int main()
{
	unsigned int i = 0;

	empdebug("-CLEAR-");
	empdebug(""); empdebug("Starting Trichromic...");
	empdebug("Setting up all the stuff...");

#ifdef ALLEGRO_LINUX
	XInitThreads();  //without this, moving the window will crash on Intel i810 systems, and probably other chipsets too
#endif

	sem_init(&semaphore, 0, 1);  //initialize the semaphore

	string s = init_everything();
	if (s != "-")
	{
		set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
		empdebug(s);
		return 1;
	}

	global.gamestatus = GAMEINTRO;

	drawqueue.reserve(500);

	counter = 0;
	game = gamestate::getinstance();
	game->reset();

	empdebug("Finished doing the initial setup and loading, going into the actual game...");

	while (global.gamestatus != QUIT)
	{
		if (counter <= 0)
			sem_wait(&semaphore);

		while ((counter > 0) && (global.gamestatus != QUIT))
		{
			int old_counter = counter;

			reset_mouse_counter();
			check_mouse_stuff();

			if (global.sound)
			{
				FMOD_System_Update(fmodmain);
				ensure_music_is_playing();
			}

			drawqueue.clear();
			if ((key[KEY_LCONTROL]) && (key[KEY_B]))
			{
				global.gamestatus = QUIT;
			}

			switch(global.gamestatus)
			{
				case GAMEINTRO:
					global.gamestatus = intro();
					break;
				case MENU:
					global.gamestatus = menu();
					break;
				case LOGIC:
					global.gamestatus = logic();
					break;
				case GAMEOVER:
					global.gamestatus = show_winner();
					break;
				case MAPEDIT:
					global.gamestatus = mapedit();
					break;
				case HELPSCREEN:
					global.gamestatus = helpscreen();
					break;
				case CREDITS:
					global.gamestatus = credits();
					break;
				case NETWORK:
					global.gamestatus = network();
					if (global.gamestatus != NETWORK)
					{  //if exiting from the networking menu, you may have played a netgame, and may have had the ini data changed
						load_army_settings();
						load_unit_settings();  //so make sure the ini data is set back to what it was originally
					}
					break;
			}

			counter--;

			if (old_counter <= counter)
			{  //if this frame took too long to do, make sure the game doesn't freeze up
				break;
			}
			if (counter > 20)
				counter = 0;
		}

		if (drawqueue.size() > 0)
			draw_all();

		if (key[KEY_LCONTROL] && key[KEY_D] && (global.gamestatus == LOGIC))
		{  //save a metric crapload of debug info
			save_debug_info();
			save_pathmap_info();
			save_unitmap_info();
			save_ownermap_info();
		}

		if (key[KEY_F12])
		{  //save a screenshot
			ol::Canvas::Save("screenshot.png");
			counter = 0;
		}
	}

	buffer_clear_to_color(BLACK);
	buffer_textout_ex(big, "Goodbye", 5, 5, WHITE);
	draw_all();

	empdebug("Game over, exiting...");
	empdebug("Saving settings...");

	set_config_int("empiresettings", "mousespeed", global.mspeed);
	set_config_int("empiresettings", "sfxvolume", global.volume);
	set_config_int("empiresettings", "musicvolume", global.music_volume);
	set_config_int("empiresettings", "fullscreen", global.fullscreen);
	set_config_int("empiresettings", "maporder", global.maporder);
	set_config_int("empiresettings", "screen_width", global.screen_width);
	set_config_int("empiresettings", "screen_height", global.screen_height);
	set_config_int("empiresettings", "scrolldivider", global.scroll_divider);
	set_config_int("empiresettings", "AIscrolldivider", global.AI_scroll_divider);
	set_config_int("empiresettings", "battleanimation", global.battle_animation);
	set_config_string("empiresettings", "netname", global.orignetname.c_str());
	set_config_string("empiresettings", "netlobby", global.netlobby.c_str());
	set_config_string("empiresettings", "lastmap", global.lastmap.c_str());

	empdebug("Unloading army bitmaps...");
	i = 0;
	while (i < character.size())
	{
		character[i]->destroy();
		delete(character[i]);
		i++;
	}

	shutdown_sound();
	unload_graphics();

	empdebug("Shutting down networking...");
	stop_networking();
	empdebug("Done.");

	empdebug("Finished unloading everything, exiting...");
	allegro_exit();

	empdebug("Done.");
	exit(0);
	return 0;
}
END_OF_MAIN()


void unload_graphics()
{
	empdebug("Unloading graphics...");
	empdebug("  minimap");
	minimap.destroy();

	empdebug("  misc");
	glb.destroy();

	empdebug("  land");
	land.destroy();

	empdebug("  buildings");
	buildings.destroy();

	empdebug("  river");
	river.destroy();

	empdebug("  sea");
	sea.destroy();

	empdebug("  shoal");
	shoal.destroy();

	empdebug("  special");
	special.destroy();

	empdebug("  stat");
	statbmp.destroy();

	empdebug("  mouse");
	mouse.destroy();

	empdebug("  explosions");
	explode1.destroy();

	empdebug("  sidebar");
	sidebarbmp.destroy();

	empdebug("  nuke");
	nukeboom.destroy();

	empdebug("  bigunits");
	bigunits.destroy();

	empdebug("Done.");
	empdebug("Unloading unit data...");

	for (int j = 0; j < NUM_UNITS; j++)
		units[j].destroy();
}


void load_graphics()
{
	empdebug("Loading graphics...");
	glb.load("data/graphics/misc");
	glb.name = "misc bitmaps";
	land.load("data/graphics/tiles/land");
	land.name = "land tiles";
	buildings.load("data/graphics/tiles/buildings");
	buildings.name = "building tiles";
	river.load("data/graphics/tiles/river");
	river.name = "river tiles";
	sea.load("data/graphics/tiles/sea");
	sea.name = "sea tiles";
	shoal.load("data/graphics/tiles/shoal");
	shoal.name = "shoal tiles";
	special.load("data/graphics/tiles/special");
	special.name = "special tiles";
	statbmp.load("data/graphics/stats");
	statbmp.name = "stat bitmaps";
	SIDEBAR_WIDTH = statbmp.bmp(1)->Width();
	BOTTOMBAR_HEIGHT = statbmp.bmp(2)->Height();

	init_tile_animations();
	mouse.load("data/graphics/mouse");
	mouse.name = "mouse sprites";
	explode1.load("data/graphics/explosions/explode1");
	explode1.name = "explosion sprites";
	nukeboom.load("data/graphics/explosions/nuke");
	nukeboom.name = "nuke animation";
	bigunits.load("data/graphics/units/big");
	bigunits.name = "big unit bitmaps";
	empdebug("    ...done");

	empdebug("Loading units...");
	char c[50];

	for (int i = 0; i < NUM_UNITS; i++)
	{
		sprintf(c, "data/graphics/units/unit%d", i);
		units[i].load(c);  //load all the unit bitmaps
		units[i].name = unitstats[i].name + " sprites";
	}
	empdebug("    ...done");

	empdebug("Loading armies...");
	if (load_characters() != 0)
	{  //load_characters() loads the army images
		empdebug("ERROR: Unable to load data/players.dat or data/missionplayers.dat");
	}
	empdebug("  ...done");

	minimap.create(true);
}


bool areasort(const pair<int, int> &i, const pair<int, int> &j)
{
	return ((i.first * i.second) < (j.first * j.second));
}


string init_everything()
{
//	bool goodmode = false;
	int i;
	int desktop_w, desktop_h;

	srand(time(NULL));
	LOCK_VARIABLE(counter);
	LOCK_FUNCTION(timer_handler);
	LOCK_FUNCTION(close_button_handler);

	//Settings::StoreMemoryBitmaps(true);

	empdebug("  Setup::SetupProgram()...");
	if (!ol::Setup::SetupProgram())
	{
		return "Couldn't set up the program for some reason.";
	}
	empdebug("    ...done");

	set_close_button_callback(close_button_handler);
	set_window_title("Trichromic");

	set_config_file("data/empire.ini");
	empdebug("  loading settings...");
	load_global_settings();

	if (lua_state::get_instance() == 0)
		return "Error: Can't initialize Lua";

	if (!global.fullscreen == 0)
	{
		if (get_desktop_resolution(&desktop_w, &desktop_h) == 0)  // 0 means no error getting the resolution
		{
			if ((desktop_w < 800) || (desktop_h < 600))
			{
				allegro_message("Your desktop resolution is too low!  It needs to be at least 800x600 to play Trichromic in windowed mode.  If your monitor can do higher resolutions and you want to play the game in fullscreen, open up data/empire.ini and change fullscreen to 1.");
				return "Error: Desktop resolution too low, exiting.";
			}
		}
	}

	load_army_settings();
	load_unit_settings();
	empdebug("    ...done");


	empdebug("Setting up the screen...");
	{  //sort a list of the available graphics modes detected, plus a few standard ones
		GFX_MODE_LIST *modes = get_gfx_mode_list(1330072646);
		for (i = 0; i < modes->num_modes; i++)
		{
			if ((modes->mode[i].width >= 800) && (modes->mode[i].height >= 600))
				global.screenmodes.push_back(pair<int, int>(modes->mode[i].width, modes->mode[i].height));
		}
		destroy_gfx_mode_list(modes);

		global.screenmodes.push_back(pair<int, int>(800, 600));
		global.screenmodes.push_back(pair<int, int>(1024, 768));
		global.screenmodes.push_back(pair<int, int>(1280, 960));
		global.screenmodes.push_back(pair<int, int>(1280, 1024));

		sort(global.screenmodes.begin(), global.screenmodes.end(), areasort);
		vector<pair<int, int> >::iterator it = unique(global.screenmodes.begin(), global.screenmodes.end());
		global.screenmodes.resize(it - global.screenmodes.begin());
	}

	if (global.screen_width < 800)
		global.screen_width = 800;
	if (global.screen_height < 600)
		global.screen_height = 600;

	stringstream s;
	s << "Trying to set the screen to " << global.screen_width << " x " << global.screen_height << ", ";
	if (global.fullscreen)
		s << "fullscreen...";
	else
		s << "windowed...";
	empdebug(s.str());

	if (!ol::Setup::SetupScreen(global.screen_width, global.screen_height, global.fullscreen))
	{
		char c[150];

		sprintf(c, "Error setting graphics mode (%d x %d, ", global.screen_width, global.screen_height);
		if (global.fullscreen)
			strcat(c, "fullscreen");
		else
			strcat(c, "windowed");
		strcat(c, ").  Trying default (800 x 600, windowed)");

		empdebug(c);

		if (!ol::Setup::SetupScreen(800, 600, 0))
		{
			empdebug("Error setting default screen resolution, too!");
			return "Error setting up graphics";
		}
	}
	empdebug("   ...done");

	s.str("");
	global.screen_width = ol::Setup::GetWindowWidth();
	global.screen_height = ol::Setup::GetWindowHeight();
	s << "Resolution is " << global.screen_width << " x " << global.screen_height << ", ";
	if (global.fullscreen)
		s << "fullscreen";
	else
		s << "windowed";
	empdebug(s.str());

	XTILES = (global.screen_width / MAP_TILE_SIZE) - 5;  //5 tiles needed for the sidebar
	YTILES = (global.screen_height / MAP_TILE_SIZE);

	glPointSize(2);

	empdebug("Loading fonts...");
	normal.Load("data/rock.ttf", 12, 12, ol::Rgba(0, 0, 0, 255));
	med.Load("data/rock.ttf", 13, 14, ol::Rgba(0, 0, 0, 255));
	big.Load("data/rock.ttf", 20, 20, ol::Rgba(0, 0, 0, 255));
	empdebug("   ...done");

	ol::Canvas::Fill(ol::Rgba(BLACK, 255));
	big.SetColor(ol::Rgba(WHITE, 255));
	big.Print("Loading...", 20, 30);
	ol::Canvas::Refresh();

	install_int_ex(timer_handler, BPS_TO_TIMER(50));

	empdebug("Loading sounds...");
	global.sound = init_sound();
	if (!global.sound)
	{
		empdebug("  Couldn't set up FMOD, continuing without music or sound...");
	}
	global.networking = init_networking();
	if (!global.networking)
	{
		empdebug("  Couldn't set up networking, continuing without networked multiplayer...");
	}

	if (global.sound)
	{
		load_sounds();
	}
	empdebug("   ...done");

	load_graphics();

	worldmap = _map(15, 15);

	mouse_type = MOUSE_NORMAL;
	return "-";
}


void init_tile_animations()
{
  bitmapdat *tiledats[6] = {&land, &river, &sea, &shoal, &buildings, &special};
  bitmapdat *current;
  int i = 0;
  int j;
  animtiles temp;

  while (i < 6)
  {
    current = tiledats[i];
    j = 0;
    while (j < current->size)
    {
      if (current->bmp(j)->Width() >= MAP_TILE_SIZE * 2)
      {
        temp.set = i;
        if (i > 4) temp.set++;  //compensate for the units datafile (which is set 5, but doesn't show up here)
        temp.num = j;
        temp.framenum = current->bmp(j)->Width() / MAP_TILE_SIZE;
        temp.frameinc = 0.1;
        animated_tiles.push_back(temp);
      }
      j++;
    }
    i++;
  }
}




void load_global_settings()
{
	global.sound = true;
	global.mspeed = get_config_int("empiresettings", "mousespeed", 2);
	global.volume = get_config_int("empiresettings", "sfxvolume", 50);
	global.music_volume = get_config_int("empiresettings", "musicvolume", 50);
	global.fullscreen = get_config_int("empiresettings", "fullscreen", 0);
	global.maporder = get_config_int("empiresettings", "maporder", 0);
	global.screen_width = get_config_int("empiresettings", "screen_width", 800);
	global.screen_height = get_config_int("empiresettings", "screen_height", 600);
	global.scroll_divider = get_config_int("empiresettings", "scrolldivider", 4);
	global.AI_scroll_divider = get_config_int("empiresettings", "AIscrolldivider", 2);
	global.battle_animation = get_config_int("empiresettings", "battleanimation", 1);
	global.orignetname = get_config_string("empiresettings", "netname", "Default");
	global.netlobby = get_config_string("empiresettings", "netlobby", DEFAULT_SERVER);
	global.lastmap = get_config_string("empiresettings", "lastmap", "");

	if ((global.scroll_divider < 1) || (global.scroll_divider > 16))
	{  //if someone's been messing with the config file stupidly
		global.scroll_divider = 4;  //this will set scroll_divider back to the default
		set_config_int("empiresettings", "scroll_divider", 4);
	}  //(preventing weird scrolling issues and possibly a divide by zero error)
	if ((global.AI_scroll_divider < 1) || (global.AI_scroll_divider > 16))
	{  //if someone's been messing with the config file stupidly
		global.AI_scroll_divider = 2;  //this will set AI_scroll_divider back to the default
		set_config_int("empiresettings", "AI_scroll_divider", 2);
	}  //(preventing weird scrolling issues and possibly a divide by zero error)
}

void load_army_settings()
{
	char c[20];
	int i = 0;

	while (i < 3)
	{
		switch(i)
		{
			case 0:
				sprintf(c, "Crimson");
				break;
			case 1:
				sprintf(c, "Cobalt");
				break;
			case 2:
				sprintf(c, "Pine");
				break;
		}
		armystats[i].pricefraction = get_config_int(c, "pricefraction", 100);
		armystats[i].movemod = get_config_int(c, "movemod", 0);
		armystats[i].rangemaxmod = get_config_int(c, "rangemaxmod", 0);
		armystats[i].attackfraction[0] = get_config_int(c, "attackfraction_BULLET", 100);
		armystats[i].attackfraction[1] = get_config_int(c, "attackfraction_FIRE", 100);
		armystats[i].attackfraction[2] = get_config_int(c, "attackfraction_EXPLODE", 100);
		armystats[i].resistfraction[0] = get_config_int(c, "resistfraction_BULLET", 100);
		armystats[i].resistfraction[1] = get_config_int(c, "resistfraction_FIRE", 100);
		armystats[i].resistfraction[2] = get_config_int(c, "resistfraction_EXPLODE", 100);
		i++;
	}
}

void load_unit_settings()
{
	bool nextunit = true;
	char unitstr[15] = "-";
	char str[20];
	vector<string> unitname;
	string n, d;
	int i, j;

	NUM_UNITS = 0;
	while ((nextunit) && (NUM_UNITS < MAX_UNITS))
	{
		sprintf(unitstr, "unit%d", NUM_UNITS);
		n = get_config_string("unitnames", unitstr, "-");
		if (n == "-")
		{  //no entry for this unit number
			nextunit = false;
		}
		else
		{
			NUM_UNITS++;
			unitname.push_back(n);
		}
	}
	empdebug(NUM_UNITS);

	i = 0;
	while (i < NUM_UNITS)
	{  //most defaults are 0 so that any loading errors can be easily spotted;
		unitstats[i]._move = get_config_int(unitname[i].c_str(), "move", 0);
		unitstats[i].movetype = get_config_int(unitname[i].c_str(), "movetype", 0);
		unitstats[i].attacktype = get_config_int(unitname[i].c_str(), "attacktype", 0);
		unitstats[i].basetype = get_config_int(unitname[i].c_str(), "basetype", 0);
		unitstats[i]._rangemin = get_config_int(unitname[i].c_str(), "rangemin", 0);
		unitstats[i]._rangemax = get_config_int(unitname[i].c_str(), "rangemax", 0);
		unitstats[i]._price = get_config_int(unitname[i].c_str(), "price", 0);
		unitstats[i].description = get_config_string(unitname[i].c_str(), "description", "");
		unitstats[i].loadcapacity = get_config_int(unitname[i].c_str(), "loadcapacity", 0);
		if (unitstats[i].loadcapacity < 0) unitstats[i].loadcapacity = 0;
		if (unitstats[i].loadcapacity > 2) unitstats[i].loadcapacity = 2;
		unitstats[i].loadtype = get_config_int(unitname[i].c_str(), "loadtype", 0);
		if (unitstats[i].loadtype == 0) unitstats[i].loadcapacity = 0;
		unitstats[i].canhit = get_config_int(unitname[i].c_str(), "canhit", 0);
		unitstats[i].attackparticle = get_config_int(unitname[i].c_str(), "attackparticle", 0);
		unitstats[i].attacksound = get_config_int(unitname[i].c_str(), "attacksound", 0);
		unitstats[i].movesound = get_config_int(unitname[i].c_str(), "movesound", 5);
		unitstats[i].moveparticle = get_config_int(unitname[i].c_str(), "moveparticle", 0);
		unitstats[i].cancapture = (get_config_int(unitname[i].c_str(), "cancapture", 0) != 0);
		unitstats[i].isarmy[0] = (get_config_int(unitname[i].c_str(), "isarmy1", 1) != 0);
		unitstats[i].isarmy[1] = (get_config_int(unitname[i].c_str(), "isarmy2", 1) != 0);
		unitstats[i].isarmy[2] = (get_config_int(unitname[i].c_str(), "isarmy3", 1) != 0);
		unitstats[i].techlevel = get_config_int(unitname[i].c_str(), "techlevel", 2);
		unitstats[i].attacksburn = (get_config_int(unitname[i].c_str(), "attacksburn", 0) != 0);
		unitstats[i].healthdrawhack = (get_config_int(unitname[i].c_str(), "healthdepdraw", 0) != 0);
		unitstats[i].blastradius = get_config_int(unitname[i].c_str(), "blastradius", 0);
		if (unitstats[i].blastradius < 0) unitstats[i].blastradius = 0;
		unitstats[i].attackdelay = get_config_int(unitname[i].c_str(), "attackdelay", 0);
		if (unitstats[i].attackdelay < 0) unitstats[i].attackdelay = 0;
		j = 0;
		while (j < 3)
		{
			sprintf(str, "attack%d", j);
			unitstats[i]._attack[j] = get_config_int(unitname[i].c_str(), str, 0);
			sprintf(str, "resist%d", j);
			unitstats[i]._resist[j] = get_config_int(unitname[i].c_str(), str, 0);
			j++;
		}
		j = 0;
		while (unsigned(j) < unitname[i].length())
		{
			if (unitname[i][j] == '_')
			{
				unitname[i][j] = ' ';
			}
			j++;
		}
		unitstats[i].name = unitname[i];

		n = "loaded ";
		n += unitname[i];
		empdebug(n);
		i++;
	}
}


int intro()
{
	static bool fadein = true;
	static float alpha = 0.0, aspeed = 0.02;
	static int time = 0;

	if (fadein)
	{
		if (alpha < 1.0)
		{
			alpha += aspeed;
			if (alpha > 1.0) alpha = 1.0;
		}
		else
		{
			time++;
			if (time > 40) fadein = false;
		}
	}
	else
	{
		if (alpha <= 0.0) return MENU;
		else
		{
			alpha -= aspeed;
			if (alpha < 0.0) alpha = 0.0;
		}
	}

	glb.draw_fullscreen(44, alpha);
	return GAMEINTRO;
}

int load_characters()
{
	for (unsigned int i = 0; i < character.size(); i++)
	{
		character[i]->destroy();
		delete(character[i]);
	}

	character.clear();
	character.reserve(3);

	for (int i = 0; i < 3; i++)
	{
		char str[30];

		sprintf(str, "army%d_name", i + 1);
		character.push_back(new _character);
		character[i]->setname(get_config_string("armies", str, "army"));
		sprintf(str, "army%d_description", i + 1);
		character[i]->setdescription(get_config_string("armies", str, "-"));
		sprintf(str, "data/graphics/army%d.png", i + 1);
		character[i]->picture = new ol::Bitmap(str);
	}

	return 0;
}

void save_debug_info()
{
  int i = 0;
  int j, k, l;
  _unit* u;
  ofstream dump("empiredump.txt");
  while (i < 4)
  {
    if (player[i].playing == 1)
    {
      dump << "PLAYER " << i << "\n";
      j = 0;
      while (j < 50)
      {
        u = &player[i].unit[j];
        dump << "  UNIT " << j << "\n";
        dump << "    exists:         " << u->exists << "\n";
        dump << "    type:           " << unit_name(u->type) << "\n";
        dump << "    tilex:          " << u->tilex << "\n";
        dump << "    tiley:          " << u->tiley << "\n";
        dump << "    ready:          " << u->ready << "\n";
        dump << "    owner:          " << u->owner << "\n";
        dump << "    number:         " << u->number << "\n";
        dump << "    health:         " << u->health << "\n";
        k = 0;
        while (k < unitstats[u->type].loadcapacity)
        {
          dump << "      UNIT LOAD " << k << "\n";
          dump << "        loaded:  " << u->load[k].loaded << "\n";
          dump << "        type:    " << unit_name(u->load[k].type) << "\n";
          dump << "        health:  " << u->load[k].health << "\n";
          l = 0;
          while (l < 2)
          {
          	if (u->load[k].subload[l].loaded == 1)
          	{
	            dump << "          UNIT LOAD " << k << " SUBLOAD\n";
            	dump << "            loaded:  " << u->load[k].subload[l].loaded << "\n";
            	dump << "            type:    " << unit_name(u->load[k].subload[l].type) << "\n";
            	dump << "            health:  " << u->load[k].subload[l].health << "\n";
            }
            l++;
          }
          k++;
        }
        dump << "\n";
        j++;
      }
      dump << "\n";
    }
    i++;
  }
}

void save_pathmap_info()
{
	ofstream path("pathmap.txt");

	for (int j = 0; j < worldmap.get_height(); j++)
	{
		for (int i = 0; i < worldmap.get_length(); i++)
		{
			int k = worldmap.get_tile(i, j)->get_step();

			if (k == UNREACHABLE)
				path << "X ";
			else
			{
				if (k < 10) path << " ";
				path << k << " ";
			}
		}
		path << "\n";
	}
}

void save_unitmap_info()
{
	ofstream unit("unitmap.txt");

	for (int j = 0; j < worldmap.get_height(); j++)
	{
		for (int i = 0; i < worldmap.get_length(); i++)
		{
			int k = worldmap.get_tile(i, j)->unit_here();
			if ((k < 100) && (k != -1)) unit << " ";
			if (k < 10) unit << " ";
			unit << k << " ";
		}
		unit << "\n";
	}
}

void save_ownermap_info()
{
	ofstream unit("ownermap.txt");

	for (int j = 0; j < worldmap.get_height(); j++)
	{
		for (int i = 0; i < worldmap.get_length(); i++)
		{
			int k = worldmap.get_tile(i, j)->get_owner();
			if ((k < 100) && (k != -1)) unit << " ";
			if (k < 10) unit << " ";
			unit << k << " ";
		}
		unit << "\n";
	}
}

string unit_name(int type)
{
	if ((type > -1) && (type < NUM_UNITS))
	{
		return unitstats[type].name;
	}
	return "-";
}

int unit_type(string name)
{
	int i = 0;
	while (i < NUM_UNITS)
	{
		if (name == unitstats[i].name)
		{
			return i;
		}
		i++;
	}
	return -1;
}

int player_color(int army, int tintnum)
{
	switch (army)
	{
		case 0:  //crimson
			if (tintnum == 0) return RED;
			if (tintnum == 1) return makecol(255, 120, 0);
			if (tintnum == 2) return makecol(255, 200, 30);
			return makecol(255, 0, 100);
			break;
		case 1:  //cobalt
			if (tintnum == 0) return BLUE;
			if (tintnum == 1) return makecol(0, 255, 255);
			if (tintnum == 2) return makecol(0, 100, 255);
			return makecol(115, 0, 255);
			break;
		case 2: //pine
			if (tintnum == 0) return MGREEN;
			if (tintnum == 1) return makecol(180, 200, 0);
			if (tintnum == 2) return makecol(0, 200, 90);
			return makecol(140, 230, 65);
			break;
	}
	return BLACK;
}

void empdebug(string text)
{
	if (text == "-CLEAR-")
	{
		ofstream d("empdebug.txt");
		d.close();
	}
	else
	{
		ofstream d("empdebug.txt", ios::app);
		if (!d.bad())
		{
			d << text.c_str() << "\n";
		}
		d.close();

		cerr << text << "\n";
	}
}

void empdebug(int i)
{
	char c[30];
	sprintf(c, "%d", i);
	empdebug(c);
}

void empdebug(float f)
{
	char c[30];
	sprintf(c, "%f", f);
	empdebug(c);
}

int helpscreen()
{
	static bool first = true;
	static short n = 0;
	static _button next;

	if (first)
	{
		const float stretchx = float(global.screen_width) / 800.0;
		const float stretchy = float(global.screen_height) / 600.0;

		n = 0;
		next.init(int(689 * stretchx), int(551 * stretchy), int(101 * stretchx), int(39 * stretchy), "", 6);
		first = false;
	}

	glb.draw_fullscreen(45 + n);
	next.check();
	if (next.clicked())
	{
		n++;
		if (n > 3)
		{
			first = true;
			return MENU;
		}
	}

	draw_mouse();
	return HELPSCREEN;
}

void create_sidebars()
{
	ol::Rgba tint;
	sidebarbmp.destroy();

	for (int i = 0; i < 4; i++)
	{
		const ol::Bitmap *totint;
		tint = ol::Rgba(player[i].tintcolor, 80);


		totint = statbmp.bmp(0);

		sidebarbmp.b.push_back(new ol::Bitmap(totint->Width(), totint->Height(), ol::Rgba(BLACK, 0)));
		ol::Canvas::SetTo(*sidebarbmp.b[sidebarbmp.b.size() - 1]);
		ol::Canvas::SetPixelWriteMode(ol::COLOR_AND_ALPHA);
		totint->Blit(0, 0, ol::Tinted(tint));  // just doing this leaves a black horizontal line on the bottom for some reason
		totint->Blit(0, 1, ol::Tinted(tint));  //TODO: Get rid of this workaround once OpenLayer is replaced with something else
		ol::Canvas::Refresh();


		totint = statbmp.bmp(1);

		sidebarbmp.b.push_back(new ol::Bitmap(totint->Width(), totint->Height(), ol::Rgba(BLACK, 0)));
		ol::Canvas::SetTo(*sidebarbmp.b[sidebarbmp.b.size() - 1]);
		ol::Canvas::SetPixelWriteMode(ol::COLOR_AND_ALPHA);
		totint->Blit(0, 0, ol::Tinted(tint));
		totint->Blit(0, 1, ol::Tinted(tint));
		ol::Canvas::Refresh();


		if (global.netgame)
			totint = statbmp.bmp(2);  //bottom bar with "chat" button
		else
			totint = statbmp.bmp(13);  //standard bottom bar

		sidebarbmp.b.push_back(new ol::Bitmap(totint->Width(), totint->Height(), ol::Rgba(BLACK, 0)));
		ol::Canvas::SetTo(*sidebarbmp.b[sidebarbmp.b.size() - 1]);
		ol::Canvas::SetPixelWriteMode(ol::COLOR_AND_ALPHA);
		totint->Blit(0, 0, ol::Tinted(tint));
		totint->Blit(0, 1, ol::Tinted(tint));
		ol::Canvas::Refresh();

		totint = statbmp.bmp(14);

		sidebarbmp.b.push_back(new ol::Bitmap(totint->Width(), totint->Height(), ol::Rgba(BLACK, 0)));
		ol::Canvas::SetTo(*sidebarbmp.b[sidebarbmp.b.size() - 1]);
		ol::Canvas::SetPixelWriteMode(ol::COLOR_AND_ALPHA);
		totint->Blit(0, 0, ol::Tinted(tint));
		totint->Blit(0, 1, ol::Tinted(tint));
		ol::Canvas::Refresh();
	}

	sidebarbmp.size = sidebarbmp.b.size();

	ol::Canvas::SetTo(ol::SCREEN_BACKBUF);
}

int credits()
{
	static bool first = true;
	static _button done;
	const int xadjust = (global.screen_width - 800) / 2;  //make sure the menu is always centered, no matter the resolution
	const int yadjust = (global.screen_height - 600) / 2;

	if (first)
	{
		done.init(10, global.screen_height - 30, 100, 20, "Back");
		first = false;
	}

	glb.draw_fullscreen(32);
	buffer_textout_centre_ex(big, "Credits", 400 + xadjust, 40 + yadjust, WHITE);

	buffer_textout_right_ex(big, "Project Leader, Programming:", 490 + xadjust, 100 + yadjust, WHITE);
	buffer_textout_ex(big, "Kent deVillafranca", 510 + xadjust, 100 + yadjust, WHITE);
	buffer_textout_right_ex(big, "Unit Type & Balancing, Game Design, Graphics:", 490 + xadjust, 130 + yadjust, WHITE);
	buffer_textout_ex(big, "Chris DeLeon", 510 + xadjust, 130 + yadjust, WHITE);
	buffer_textout_right_ex(big, "User Interface:", 490 + xadjust, 160 + yadjust, WHITE);
	buffer_textout_ex(big, "Greg Peng", 510 + xadjust, 160 + yadjust, WHITE);
	buffer_textout_right_ex(big, "Title Music:", 490 + xadjust, 190 + yadjust, WHITE);
	buffer_textout_ex(big, "Sam Chien", 510 + xadjust, 190 + yadjust, WHITE);

	buffer_textout_ex(normal, "Other music (except Holst) licensed on behalf of Chris DeLeon by Stockmusic.net (Digital Sound Media), a division of REM Publishing Ltd.", 5 + xadjust, 250 + yadjust, WHITE);

	buffer_textout_ex(normal, "Battle Setup:", 30 + xadjust, 270 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Name: Revelation of One", 50 + xadjust, 285 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Length: 2:00", 50 + xadjust, 300 + yadjust, WHITE);
	buffer_textout_ex(normal, "Composer (and Organisation): Joseph Bennie (APRA)", 50 + xadjust, 315 + yadjust, WHITE);
	buffer_textout_ex(normal, "Publisher (and Organisation): Joseph Bennie (APRA)", 50 + xadjust, 330 + yadjust, WHITE);

	buffer_textout_ex(normal, "Game Over:", 430 + xadjust, 270 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Name: Not a Happy Ending", 450 + xadjust, 285 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Length: 1:24", 450 + xadjust, 300 + yadjust, WHITE);
	buffer_textout_ex(normal, "Composer (and Organisation): Ron Sorbo (BMI)", 450 + xadjust, 315 + yadjust, WHITE);
	buffer_textout_ex(normal, "Publisher (and Organisation): Ames Rush (BMI)", 450 + xadjust, 330 + yadjust, WHITE);

	buffer_textout_ex(normal, "Crimson Theme", 30 + xadjust, 350 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Name: Mars, the Bringer of War", 50 + xadjust, 365 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Length: 4:40", 50 + xadjust, 380 + yadjust, WHITE);
	buffer_textout_ex(normal, "Composer (and Organisation): Gustav Holst", 50 + xadjust, 395 + yadjust, WHITE);
	buffer_textout_ex(normal, "Publisher (and Organisation): USAF Band", 50 + xadjust, 410 + yadjust, WHITE);

	buffer_textout_ex(normal, "Cobalt Theme", 430 + xadjust, 350 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Name: Prodigious Urban Spawn", 450 + xadjust, 365 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Length: 1:21", 450 + xadjust, 380 + yadjust, WHITE);
	buffer_textout_ex(normal, "Composer (and Organisation): Dan Schmidt (SOCAN)", 450 + xadjust, 395 + yadjust, WHITE);
	buffer_textout_ex(normal, "Publisher (and Organisation): Dan Schmidt (SOCAN)", 450 + xadjust, 410 + yadjust, WHITE);

	buffer_textout_ex(normal, "Pine Theme", 30 + xadjust, 430 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Name: Cold Wars", 50 + xadjust, 445 + yadjust, WHITE);
	buffer_textout_ex(normal, "Track Length: 0:50", 50 + xadjust, 460 + yadjust, WHITE);
	buffer_textout_ex(normal, "Composer (and Organisation): Patrick Smith (ASCAP)", 50 + xadjust, 475 + yadjust, WHITE);
	buffer_textout_ex(normal, "Publisher (and Organisation): Patrick Smith (ASCAP)", 50 + xadjust, 490 + yadjust, WHITE);

	done.check();
	if (done.clicked())
	{
		first = true;
		return MENU;
	}

	draw_mouse();
	return CREDITS;
}

void text_box(int x, int y, int w, int h, char *ret, int leng, int spaces)
{
  text_box(x, y, w, h, ret, leng, spaces, "");
}

void text_box(int x, int y, int w, int h, char *ret, int leng, int spaces, string title)
{
	bool exit = false;
	char orig[leng];
	int i = 0;
	int k;
	int textx = x;
	int texty = y;
	sprintf(orig, "%s", ret);
	k = 0;
	while (orig[k] != '\0')
	{
		i++;
		k++;
	}
	int q = 0;
	int y_offset = 0;
	if (title != "")
	{
		y_offset = 15;
	}

	ol::Rect(x, y, w, h, 2.0, 3.0).Draw(ol::Rgba(BLACK, 255));
	ol::Rect(x, y, w, h, 2.0, 3.0).DrawOutline(ol::Rgba(DGREY, 255));
	normal.SetColor(ol::Rgba(WHITE, 255));

	if (title != "")
	{
		normal.Print(title, textx + 3, texty + 3 + normal.Height(title));
	}

	normal.Print(orig, textx + 3, texty + y_offset + 4 + normal.Height(orig));
	ol::Canvas::Refresh();
	clear_keybuf();

	while (!exit)
	{
		k = readkey();
		if(((k >> 8) == KEY_ENTER)) q = 1;
		else if(((k >> 8) == KEY_BACKSPACE) && i > 0) { i--; ret[i] = '\0'; }
		else if(((k >> 8) == KEY_BACKSPACE)) { ret[i] = '\0'; }
		else if(((k >> 8) == KEY_ESC))
		{
			q = 1;
			sprintf(ret, "_CANCEL_");
		}
		else if (((k >> 8) == KEY_SPACE) && (spaces == 0))
		{  //do nothing
		}
		else
		{
			if (i < leng)
			{
				ret[i] = k & 0xFF;
				ret[i+1] = '\0';
				i++;
			}
		}
		if (q == 1)
		{
			if (ret[0] != '\0')
			{
				exit = true;
			}
			q = 0;
		}

		ol::Rect(x, y, w, h, 2.0, 3.0).Draw(ol::Rgba(BLACK, 255));
		ol::Rect(x, y, w, h, 2.0, 3.0).DrawOutline(ol::Rgba(DGREY, 255));

		if (title != "")
		{
			normal.Print(title, textx + 3, texty + 3 + normal.Height(title));
		}

		normal.Print(ret, textx + 3, texty + y_offset + 4 + normal.Height(ret));

		if (strcmp(ret, "_CANCEL_") != 0)
		{  //prevent the box from displaying "_CANCEL_" in its last frame
			ol::Canvas::Refresh();
		}

		rest(1);
	}
	counter = 0;
}
