#include "empire.h"
#include "netlobby.h"

extern void get_teleports();
extern void get_connected_beaches();
extern void draw_minimap(int x, int y, string file);
extern void load_army_settings();
extern void load_unit_settings();

void send_lobbydata(string mapname);
void send_inidata(int thread);

void broadcast_unreadyall();

void send_playerdata(int thread, int placement);
bool load_client_playerdata(int p, DyBytes *data);

DyBytes menudata();
void send_menudata(int thread);
void broadcast_menudata();

void disconnect(int thread);
void do_netai_options(_button *b, int *colors, _button *ccycle,  _button *tcycle, const string &mapname);
void remove_player(int client_index, int *colors, _button *ccyc, _button *tcyc, const string &mapname);
void net_assign_tintcolor(int p, int *colors);

void cycle_startcash(int amount, _button &startingcash);
void cycle_funding(int amount, _button &fundsperbuilding);
void cycle_techlevel(int amount, _button &techlevel);
void cycle_cashturns(int amount, _button &cashturns);
void toggle_showdamage(_button &showdamage);
void update_player(int pnum, int playerinc, int teaminc, int colorinc, int *colors, _button &plyrcycle, _button &teamcycle, _button &colorcycle);

extern string connectmsg;

typedef struct hostclient
{
	bool maploaded;
	bool isai;
	bool ready;
	short baddata;
	int thread;

	hostclient() :
		maploaded (false),
		isai (false),
		ready (false),
		baddata (0),
		thread (-1)
	{}
} hostclient;

vector<hostclient> clients;

void got_bad_data(hostclient &client);

int net_hostgame()
{
	const float xratio = float(global.screen_width) / 800.0;
	const float yratio = float(global.screen_height) / 600.0;
	const float minratio=  (xratio < yratio) ? xratio : yratio;

	const int players_x = 38;
	const int players_y = 36;
	const int players_size = 90;
	const int orig_players_spacing = 116;
	const int players_spacing = int(orig_players_spacing * yratio);

	const int help_x = int(339 * xratio);
	const int help_y = int(29 * yratio);
	const int minimap_x = int(619 * xratio);  //where the minimap background image begins
	const int minimap_y = int(412 * yratio);
	const int minimap_realx = minimap_x + int(150.0 * (xratio - minratio) / 2.0);  //where the minimap is actually drawn
	const int minimap_realy = minimap_y + int(150.0 * (yratio - minratio) / 2.0);  //(to center the minimap)


	static bool first = true, changemap = false, returning_to_lobby = false;
	static int oldsel, prevmap, colors[4], last_lobby_update;
	static _button team, plyrcycle[4], teamcycle[4], fundsperbuilding, showdamage, startingcash, cashturns,
									back, mapchange, mapok, mapcancel, start, techlevel, colorcycle[4], boot[3], ai[3];
	static _listbox chatbox, mapbox;
	static _textline chattext;


	bool allready = false;
	bool pcycleclicked[4], pcyclerclicked[4], tcycleclicked[4], tcyclerclicked[4], ccycleclicked[4], ccyclerclicked[4];
	char c[70];
	string str, s;
	DyBytes data;

	if (first)
	{
		const int startw = 100;
		const int starth = 25;
		const int startx = global.screen_width - startw - 10;
		const int starty = global.screen_height - starth - 10;

		const int button_width = int(170 * (xratio + 1.0) / 2.0);  //as the screen width increases, make the buttons' widths increase by a smaller factor

		const int options_x = minimap_x - button_width - 20;
		const int options_y = int(390 * yratio);

		play_song(MENU_THEME);
		load_army_settings();  //this machine may have been a network client previously
		load_unit_settings();  //so make sure the unit and army settings are the local ones

		last_lobby_update = 5;
		returning_to_lobby = false;

		gameoptions.funding = 4000;
		gameoptions.show_damage = true;
		gameoptions.time = -999;
		oldsel = -1;
		changemap = false;

		clients.clear();

		for (int i = 0; i < 4; i++)
		{
			const float player_y_pos = players_y + (orig_players_spacing * i);
			const int centeredx = int(players_x * xratio + (players_size * (xratio - minratio) / 2.0));
			const int centeredy = int(player_y_pos * yratio + (players_size * (yratio - minratio) / 2.0));

			currentcharacter[i] = character[0];
			player[i].number = 0;
			player[i].team = i + 1;
			player[i].tintcolor = player_color(0, i);
			colors[i] = i;

			plyrcycle[i].init(centeredx, centeredy, int(players_size * minratio), int(players_size * minratio), "", 6, WHITE);
			teamcycle[i].init(int(150 * xratio), int((70 + (orig_players_spacing * i)) * yratio), 40, 40, "", glb.bmp(13 + player[i].team));
			colorcycle[i].init(int(275 * xratio), int((32 + (orig_players_spacing * i)) * yratio), 40, 40, "", NULL, 2, player[i].tintcolor);
		}

		mapchange.init(options_x, int(555 * yratio), button_width, int(40 * yratio), "Change map");
		mapbox.init(options_x, int(410 * yratio), button_width, int(160 * yratio));
		mapbox.setsel(0);
		prevmap = 0;

		mapok.init(options_x, int(575 * yratio), int(button_width / 2 - 5), int(21 * yratio), "OK");
		mapcancel.init(options_x + (button_width / 2) + 5, int(575 * yratio), button_width / 2 - 5, int(21 * yratio), "Cancel");
		changemap = false;

		for (int i = 0; i < signed(maplist.size()); i++)
		{
			mapbox.additem(maplist[i].c_str(), false);

			if (maplist[i] == global.lastmap)
			{
				mapbox.setsel(i);
				prevmap = i;
			}
		}

		if (mapbox.size() > 0)
		{
			string s = mapbox.selected();
			s.erase(0, 5); //get rid of the (2P) / (3P) / (4P)
			load_ai(s);
		}

		back.init(10, global.screen_height - 30, 100, 20, "Back");
		start.init(startx, starty, startw, starth, "Start!", 1, BLACK);

		startingcash.init(options_x, options_y, button_width, 20, "Starting Cash: 0");
		fundsperbuilding.init(options_x, options_y + 22, button_width, 20, "Funds per Building: 4000");
		showdamage.init(options_x, options_y + 44, button_width, 20, "Show Attacking Damage: Yes");
		techlevel.init(options_x, options_y + 66, button_width, 20, "Tech Level: 3");
		game->techlevel = 3;
		cashturns.init(options_x, options_y + 88, button_width, 20, "Derricks Last: 10 Turns");
		game->cashturns = 10;

		player[0].name = global.netname;
		player[1].name = "";
		player[2].name = "";
		player[3].name = "";

		chatbox.init(help_x, help_y, int(glb.bmp(52)->Width() * xratio), int(glb.bmp(52)->Height() * yratio - 17));
		chattext.init(help_x, int(help_y + glb.bmp(52)->Height() * yratio - 15), int(glb.bmp(52)->Width() * xratio), 15, true);

		for (int i = 0; i <= 2; i++)
		{
			boot[i].init(int(275 * xratio), int((200 + (orig_players_spacing * i)) * yratio), 65, 20, "Kick!");
			ai[i].init(int(275 * xratio), int((200 + (orig_players_spacing * i)) * yratio), 65, 20, "Toggle AI");
		}

		send_lobbydata("");  //doesn't actually send data here, just resets send_lobbydata's static string

		first = false;
	}

	if (returning_to_lobby)
	{  //we've told any connected clients to disconnect, give them some time to do so
		static int delay = 0;
		bool none_connected = true;

		glb.draw(54, 0, 0);
		buffer_textout_centre_ex(big, "Ending hosted game...", global.screen_width / 2, global.screen_height / 3, WHITE);

		for (unsigned int i = 0; i < net.engine->getMaxConnections(); i++)
		{
			if (net.engine->isConnected(i) || net.chatengine->isConnected(i))
				none_connected = false;
		}

		delay++;
		if ((delay > 50) || none_connected)
		{
			if (!none_connected)
			{  //we've given them plenty of time, now kick them out
				for (unsigned int i = 0; i < net.engine->getMaxConnections(); i++)
					disconnect(i);
			}

			delay = 0;
			first = true;
			return NET_FIRST;
		}

		return NET_HOST;
	}

	for (int i = 0; i < 4; i++)
	{
		pcycleclicked[i]  = plyrcycle[i].clicked();
		pcyclerclicked[i] = plyrcycle[i].rclicked();
		tcycleclicked[i]  = teamcycle[i].clicked();
		tcyclerclicked[i] = teamcycle[i].rclicked();
		ccycleclicked[i]  = colorcycle[i].clicked();
		ccyclerclicked[i] = colorcycle[i].rclicked();
	}

	glb.draw_fullscreen(36);
	back.check();
	chatbox.check(false);
	str = chattext.check();

	if (oldsel != mapbox.getsel())
	{
		s = mapbox.selected();
		s.erase(0, 5); //get rid of the (2P) / (3P) / (4P)
		sprintf(c, "maps/%s.map", s.c_str());
		load_map(mapbox.selected(), c, false);
		minimap.create();
		minimap.a = 1.0;
		oldsel = mapbox.getsel();
	}

	buffer_textout_ex(normal, worldmap.get_name(), minimap_x, minimap_y - 17, BLACK);

	if ((xratio > 1.0) || (yratio > 1.0))
	{
		buffer_rectfill(minimap_x, minimap_y, int(minimap_x + (150 * xratio)), int(minimap_y + (150 * yratio)), BLACK);
		minimap.draw_scaled(minimap_realx, minimap_realy, minratio);
	}
	else
		minimap.draw(false, minimap_x, minimap_y, false);

	buffer_rect(start.x - 6, start.y - 3, start.x + start.w + 2, start.y + start.h + 3, BLACK);
	buffer_rect(start.x - 3, start.y, start.x + start.w - 1, start.y + start.h, BLACK);

	if (changemap)
	{
		mapbox.check();
		mapok.check();
		mapcancel.check();
		if (mapok.clicked())
		{
			string s = mapbox.selected();
			s.erase(0, 5); //get rid of the (2P) / (3P) / (4P)
			load_ai(s);

			for (vector<hostclient>::iterator it = clients.begin(); it != clients.end(); it++)
			{
				it->maploaded = false;
				it->ready = false;
			}

			prevmap = mapbox.getsel();
			changemap = false;
			broadcast_unreadyall();
			broadcast_mapdata();
		}
		if (mapcancel.clicked())
		{
			mapbox.setsel(prevmap);
			changemap = false;
		}
	}
	else
	{
		int totalplayers = clients.size() + 1;
		bool playersok = true;

		if (totalplayers < worldmap.get_num_players())
		{
			buffer_textout_ex(normal, "Not enough players for this map.", players_x, players_y + (players_spacing * 4) + 5, RED);
			playersok = false;
		}
		else if (totalplayers > worldmap.get_num_players())
		{
			buffer_textout_ex(normal, "Too many players for this map.", players_x, players_y + (players_spacing * 4) + 5, RED);
			playersok = false;
		}

		allready = true;
		for (vector<hostclient>::iterator it = clients.begin(); it != clients.end(); it++)
			if (!it->ready && !it->isai) allready = false;

		if (playersok & allready)
		{
			start.check();
		}

		mapchange.check();
		startingcash.check();
		fundsperbuilding.check();
		showdamage.check();
		techlevel.check();
		cashturns.check();

		if (mapchange.clicked())
			changemap = true;
	}

	do_netai_options(ai, colors, colorcycle, teamcycle, mapbox.selected());

	do_hostchat(&chatbox, str, true);

	for (unsigned int i = 0; i < net.engine->getMaxConnections(); i++)
	{
		bool newclient = false;

		if (net.engine->acceptClient(i))
			newclient = true;
		net.chatengine->acceptClient(i);

		if (net.engine->isConnected(i))
		{
			if (newclient)
			{  //a new client has connected, send data to it

				if (clients.size() >= 3)
				{  //if we don't have room for this new connection
					disconnect(i);
				}
				else  //if this thread wasn't immediately disconnected
				{
					DyBytes askdata;
					askdata.appendByte(NET_ASK_PLAYERDATA);
					net.engine->sendTCP(i, askdata);

					broadcast_unreadyall();

					clients.push_back(hostclient());
					int c = clients.size() - 1;
					int pnum = c + 1;

					empdebug("New client");
					empdebug(c);
					empdebug("has connected on thread");
					empdebug(signed(i));

					player[pnum].name = "";
					net.address[pnum] = net.engine->getRemoteAddr(i);
					clients[c].thread = i;

					player[pnum].playing = 1;
					player[pnum].everplaying = 1;
					player[pnum].number = 0;
					player[pnum].controller = NET;
					player[pnum].team = pnum + 1;
					currentcharacter[pnum] = character[0];
					colors[pnum] = 0;
					net_assign_tintcolor(pnum, colors);

					//make sure the team, and tint buttons are set correctly (player button is always blank, with the army drawn over it later)
					teamcycle[pnum].picture = glb.bmp(13 + player[pnum].team);
					colorcycle[pnum].color = player[pnum].tintcolor;

					send_inidata(i);  //send the hosts's ini file settings
					send_menudata(i); //and the current game settings
					send_mapdata(i);  //and the map
				}
			}
		}
		else  //if the thread isn't connected, check if a client was using that thread
		{
			for (unsigned int j = 0; j < clients.size(); j++)
			{
				if (clients[j].thread == signed(i))
				{
					stringstream s;
					string playername = player[j + 1].name;

					s << "Client " << j << " on thread " << clients[j].thread << " is no longer connected";

					empdebug(s.str());

					if (playername == "")
						playername = "Unknown player";

					sprintf(c, "%s has left the game (Player %d disconnected)", playername.c_str(), j + 2);
					broadcast_chat_message(c);
					chatbox.additem(c, true);
					chatbox.scroll_to_end();

					data.clear();
					data.appendByte(NET_PLAYER_UPDATE);
					net.engine->broadcastTCP(data);

					broadcast_unreadyall();
					remove_player(j, colors, colorcycle, teamcycle, mapbox.selected());
				}
			}
		}
	}

	for (unsigned int j = 0; j < clients.size(); j++)
	{
		hostclient &client = clients[j];
		int pnum = j + 1;

		if (player[pnum].name != "")
		{  //if we have this player's name
			if (client.ready)
			{
				buffer_draw_sprite(glb.bmp(28), int(275 * xratio), int((183 + (orig_players_spacing * j)) * yratio));
			}
		}
		else if (!client.isai)
		{
			buffer_rectrounded(int(29 * xratio), int((27 + (orig_players_spacing * pnum)) * yratio), int(270 * xratio), int((133 + (orig_players_spacing * pnum)) * yratio + 1), BLACK, 0.7, 3.0);
			buffer_textout_ex(big, "Getting Player Data...", int(51 * xratio), int((65 + (orig_players_spacing * pnum)) * yratio), WHITE);
		}

		if (client.isai)
			continue;

		//
		// Everything further down this loop assumes there's a remote connection for this client (so, not AI)
		//

		if (client.thread < 0)
		{
			stringstream s;
			s << "ERROR: Netplay client " << j << " has thread == " << client.thread;
			empdebug(s.str());

			connectmsg = "Hit an error when hosting the game, returning to the game lobby";

			DyBytes leavedata;
			net.engine->disableTCPListen();
			net.chatengine->disableTCPListen();
			net.lobbyengine->disconnectHost(net.lobbythread);

			leavedata.appendByte(NET_DISCONNECT);
			net.engine->broadcastTCP(leavedata);

			returning_to_lobby = true;
			break;
		}

		boot[j].check();
		if (boot[j].clicked())
		{
			disconnect(client.thread);
			continue;
		}

		if (get_client_message(client.thread, data))
		{
			uint8_t id = data.getByte(0);
			data.removeFront(1);

			empdebug("Host received data");

			if (id != NET_PLAYERDATA)
			{  //if the client isn't sending player data right now
				if (player[pnum].name == "")
				{  //and if we're still waiting for the client's name, ask for it again
					DyBytes askdata;
					askdata.appendByte(NET_ASK_PLAYERDATA);
					net.engine->sendTCP(client.thread, askdata);
				}
			}

			switch(id)
			{
				case NET_PLAYERDATA:
					empdebug("  NET_PLAYERDATA");

					if (!data.decompress())
					{
						stringstream s;
						s << "Error decompressing data from client " << j;
						empdebug(s.str());

						got_bad_data(client);

						data.clear();
						data.appendByte(NET_ASK_PLAYERDATA);
						net.engine->sendTCP(j, data);
						continue;
					}

					if (!load_client_playerdata(pnum, &data))
					{  //if this client gave us bad data
						got_bad_data(client);

						data.clear();
						data.appendByte(NET_ASK_PLAYERDATA);
						net.engine->sendTCP(j, data);
						continue;
					}

					data.clear();
					data.appendByte(NET_PLAYER_UPDATE);
					net.engine->broadcastTCP(data);

					for (unsigned int k = 0; k < clients.size(); k++)
						if (!clients[k].isai)
							send_playerdata(clients[k].thread, k + 1);
					break;
				case NET_CHARACTER:
					empdebug("  NET_CHARACTER");
					update_player(pnum, 1, 0, 0, colors, plyrcycle[pnum], teamcycle[pnum], colorcycle[pnum]);
					break;
				case NET_CHARACTER_R:
					empdebug("  NET_CHARACTER");
					update_player(pnum, -1, 0, 0, colors, plyrcycle[pnum], teamcycle[pnum], colorcycle[pnum]);
					break;
				case NET_CLIENT_TINTCYCLE:
					empdebug("  NET_CLIENT_TINTCYCLE");
					update_player(pnum, 0, 0, 1, colors, plyrcycle[pnum], teamcycle[pnum], colorcycle[pnum]);
					break;
				case NET_CLIENT_TINTCYCLE_R:
					empdebug("  NET_CLIENT_TINTCYCLE_R");
					update_player(pnum, 0, 0, -1, colors, plyrcycle[pnum], teamcycle[pnum], colorcycle[pnum]);
					break;
				case NET_PLAYERTEAM:
					empdebug("  NET_PLAYERTEAM");
					update_player(pnum, 0, 1, 0, colors, plyrcycle[pnum], teamcycle[pnum], colorcycle[pnum]);
					break;
				case NET_PLAYERTEAM_R:
					empdebug("  NET_PLAYERTEAM_R");
					update_player(pnum, 0, -1, 0, colors, plyrcycle[pnum], teamcycle[pnum], colorcycle[pnum]);
					break;
				case NET_MAPLOADED:
					empdebug("  NET_MAPLOADED");
					client.maploaded = true;
					break;
				case NET_CLIENTREADY:
					empdebug("  NET_CLIENTREADY");

					if (data.size() < 1)
					{
						got_bad_data(client);
						continue;
					}

					client.ready = data.getByte(0);
					data.clear();
					data.appendByte(NET_CLIENTREADY);
					data.appendByte(pnum);
					data.appendByte(client.ready);
					net.engine->broadcastTCP(data);
					break;
				default:  //the client sent us something we don't know how to handle
					got_bad_data(client);
					break;
			}
		}
	}

	for (unsigned int i = 0; i < clients.size() + 1; i++)
	{
		if (player[i].name != "")
		{  //if we have the player's name
			const float player_y_pos = players_y + (orig_players_spacing * i);
			const int teamlabel_x = int(157 * xratio);
			const int teamlabel_y = int((56 + orig_players_spacing * i) * yratio);

			// draw over the "X?" in the background image, draw the player's army, and check the transparent button that's over it
			ol::Bitmap *player_image = glb.bmp(51);  //set the player army image to the "random dice"

			buffer_rectfill(int(players_x * xratio), int(player_y_pos * yratio), int((players_x + players_size) * xratio), int((player_y_pos + players_size) * yratio), WHITE);

			if (currentcharacter[i] != NULL)
				player_image = currentcharacter[i]->picture;

			buffer_stretch_sprite(player_image, plyrcycle[i].x, plyrcycle[i].y, plyrcycle[i].w, plyrcycle[i].h);

			plyrcycle[i].check();

			//create a black-with-white-outline "Team" label
			buffer_textout_ex(med, "Team", teamlabel_x - 1, teamlabel_y - 1, WHITE);
			buffer_textout_ex(med, "Team", teamlabel_x + 1, teamlabel_y - 1, WHITE);
			buffer_textout_ex(med, "Team", teamlabel_x - 1, teamlabel_y + 1, WHITE);
			buffer_textout_ex(med, "Team", teamlabel_x + 1, teamlabel_y + 1, WHITE);
			buffer_textout_ex(med, "Team", teamlabel_x, teamlabel_y, BLACK);

			buffer_textout_ex(normal, player[i].name, int(145 * xratio), int((34 + (orig_players_spacing * i)) * yratio), BLACK);

			teamcycle[i].check();
			if (currentcharacter[i] != NULL) colorcycle[i].check();
		}

		int cycleplayer = (pcycleclicked[i] ? 1 : 0) + (pcyclerclicked[i] ? -1 : 0);
		int cycleteam   = (tcycleclicked[i] ? 1 : 0) + (tcyclerclicked[i] ? -1 : 0);
		int cyclecolor  = (ccycleclicked[i] ? 1 : 0) + (ccyclerclicked[i] ? -1 : 0);

		update_player(i, cycleplayer, cycleteam, cyclecolor, colors, plyrcycle[i], teamcycle[i], colorcycle[i]);
	}


	if (!changemap)
	{
		if (startingcash.clicked())  cycle_startcash(5000, startingcash);
		if (startingcash.rclicked()) cycle_startcash(-5000, startingcash);

		if (fundsperbuilding.clicked())  cycle_funding(1000, fundsperbuilding);
		if (fundsperbuilding.rclicked()) cycle_funding(-1000, fundsperbuilding);

		if (showdamage.clicked() || showdamage.rclicked()) toggle_showdamage(showdamage);

		if (techlevel.clicked())  cycle_techlevel(1, techlevel);
		if (techlevel.rclicked()) cycle_techlevel(-1, techlevel);

		if (cashturns.clicked())  cycle_cashturns(5, cashturns);
		if (cashturns.rclicked()) cycle_cashturns(-5, cashturns);

		if (allready && (worldmap.get_num_players() == signed(clients.size()) + 1) && start.clicked())
		{  //if the start button was clicked and everything's ready to go
			global.lastmap = mapbox.selected();

			net.engine->disableTCPListen();  //don't allow anyone else to connect
			net.chatengine->disableTCPListen();

			//starting a game, so the lobby connection is no longer needed
			net.lobbyengine->disconnectHost(net.lobbythread);

			player[0].controller = HUMAN;
			player[0].name = global.netname;

			if (currentcharacter[0] == NULL)
			{
				player[0].number = rand()%3;
				currentcharacter[0] = character[player[0].number];

				colors[0] = 0;
				net_assign_tintcolor(0, colors);

				data.clear();
				data.appendByte(NET_CHARACTER);
				data.appendByte(0);
				data.appendByte(player[0].number);
				net.engine->broadcastTCP(data);

				data.clear();
				data.appendByte(NET_PLAYERTINT);
				data.appendByte(0);
				data.appendVariable(player[0].tintcolor);
				net.engine->broadcastTCP(data);
			}

			for (unsigned int i = 0; i < clients.size(); i++)
			{
				int pnum = i + 1;

				net.clientthread[i] = clients[i].thread;

				if (clients[i].isai)
					player[pnum].controller = AI;
				else
					player[pnum].controller = NET;

				if (currentcharacter[pnum] == NULL)
				{
					player[pnum].number = rand()%character.size();
					currentcharacter[pnum] = character[player[pnum].number];
					colors[pnum]++;

					net_assign_tintcolor(pnum, colors);

					data.clear();
					data.appendByte(NET_CHARACTER);
					data.appendByte(pnum);
					data.appendByte(player[pnum].number);
					net.engine->broadcastTCP(data);

					data.clear();
					data.appendByte(NET_PLAYERTINT);
					data.appendByte(pnum);
					data.appendVariable(player[pnum].tintcolor);
					net.engine->broadcastTCP(data);
				}
			}

			//load_ai is called when the map is first selected, no need to repeat it here

			create_sidebars();
			get_connected_beaches();
			get_teleports();

			data.clear();
			data.appendByte(NET_GO);
			net.engine->broadcastTCP(data);

			game->game_just_started = true;
			game->loaded_from_file = false;
			first = true;
			return NET_PLAY;
		}
	}

	if (back.clicked())
	{  //disconnect from the lobby, and give the clients some time to disconnect before leaving
		DyBytes leavedata;
		net.engine->disableTCPListen();
		net.chatengine->disableTCPListen();
		net.lobbyengine->disconnectHost(net.lobbythread);

		leavedata.appendByte(NET_DISCONNECT);
		net.engine->broadcastTCP(leavedata);

		returning_to_lobby = true;
	}

	if (!net.lobbyengine->isConnected(net.lobbythread))
	{
		buffer_textout_right_ex(normal, "Error: Lost connection to server, your game won't be listed!", 750, 10, BLACK);
	}
	else
	{
		DyBytes lobbydata;
		if (net.lobbyengine->getMessage(net.lobbythread, lobbydata))
		{
			string id = getID(lobbydata);

			if (id == PORT_ERROR)
			{
				chatbox.additem("Warning: You don't seem to be available through ports 41136/41137.  This probably means nobody will be able to connect to your game!", true);
				chatbox.additem("If you're behind a router, make sure those ports are forwarded", true);
				chatbox.scroll_to_end();
			}
		}

		if (last_lobby_update <= 0)
		{
			last_lobby_update = 250;
			send_lobbydata(mapbox.selected());
		}
		else last_lobby_update--;
	}

	draw_mouse();

	return NET_HOST;
}

bool hostcommand(_listbox *chatbox, string str)
{
	if (str.substr(0, str.find(" ")) == "/list")
	{
		for (int i = 0; i < 3; i++)
		{
			if (net.clientthread[i] != -1)
			{
				char c[100];

				sprintf(c, "Network slot %d: %s", i, player[i + 1].name.c_str());
				chatbox->additem(c, true);
				chatbox->scroll_to_end();
			}
		}
		return true;
	}

	if (str.substr(0, str.find(" ")) == "/AIify")
	{
		stringstream s;
		string dummy;
		int p;

		s << str;
		s >> dummy >> p;

		if ((player[p].controller == AI) || (player[p].controller == NETAI))
			return true;  //already AI, don't do anything

		if ((p > -1) && (p < worldmap.get_num_players()))
		{

			if (p == 0)
				player[p].controller = AI;
			else
				player[p].controller = NETAI;

			for (int i = 0; i < 3; i++)
			{
				if (net.clientthread[i] != -1)
				{
					DyBytes data;

					data.appendByte(NET_NEWCONTROLLER);
					data.appendByte(p);

					if (i + 1 == p)
						data.appendByte(AI);
					else
						data.appendByte(NETAI);

					net.engine->sendTCP(net.clientthread[i], data);
				}
			}



		}
		else
		{
			chatbox->additem("Can't AIify slot, number given was bad.  The format is \"/AIify <player (0-3)>\"", true);
			chatbox->scroll_to_end();
		}

		return true;
	}
	return false;
}

bool do_hostchat(_listbox *chatbox, string str, bool inhostmenu)
{  //returns true if a new item has been added
	bool result = false;
	unsigned int i;
	string chatstr;
	DyBytes data;

	if ((str != "/NOTHING YET/") && (str != ""))
	{
		if (!inhostmenu && hostcommand(chatbox, str))
		{  //if we're not just in the host game setup screen, and if this string was a command by the host to do something, it isn't a chat message
			return false;
		}

		chatstr = global.netname;
		chatstr += ": ";
		chatstr += str;
		chatbox->additem(chatstr.c_str(), true);
		chatbox->scroll_to_end();

		broadcast_chat_message(chatstr);
	}

	i = 0;
	while (i < net.chatengine->getMaxConnections())
	{
		if (net.chatengine->isConnected(i))
		{
			if (net.chatengine->getMessage(i, data))
			{
				chatstr = data.getAsString();

				broadcast_chat_message(chatstr);
				chatbox->additem(chatstr.c_str(), true);
				chatbox->scroll_to_end();
				result = true;
			}
		}
		i++;
	}
	return result;
}

void broadcast_chat_message(string mesg)
{
	unsigned int i = 0;
	DyBytes data;
	data.appendString(mesg);

	while (i < net.chatengine->getMaxConnections())
	{
		if (net.chatengine->isConnected(i))
		{
			net.chatengine->sendTCP(i, data);
		}
		i++;
	}
}

void broadcast_unreadyall()
{
	DyBytes data;

	for (vector<hostclient>::iterator it = clients.begin(); it != clients.end(); it++)
		it->ready = false;

	data.appendByte(NET_UNREADYALL);
	net.engine->broadcastTCP(data);
}

void send_inidata(int thread)
{
	int i = 0, j, k;
	DyBytes data, alldata;
	alldata.clear();
	data.clear();

	while (i < 3)
	{
		data.appendVariable(armystats[i].pricefraction);
		data.appendVariable(armystats[i].movemod);
		data.appendVariable(armystats[i].rangemaxmod);
		j = 0;
		while (j < 3)
		{
			data.appendVariable(armystats[i].attackfraction[j]);
			data.appendVariable(armystats[i].resistfraction[j]);
			j++;
		}
		i++;
	}

	data.appendByte(NUM_UNITS);
	i = 0;
	while (i < NUM_UNITS)
	{
		j = unitstats[i].name.length();
		data.appendVariable(j);
		k = 0;
		while (k < j)
		{
			data.appendByte(unitstats[i].name[k]);
			k++;
		}
		j = unitstats[i].description.length();
		data.appendVariable(j);
		k = 0;
		while (k < j)
		{
			data.appendByte(unitstats[i].description[k]);
			k++;
		}

		data.appendVariable(unitstats[i]._move);
		data.appendVariable(unitstats[i].movetype);
		data.appendVariable(unitstats[i].attacktype);
		data.appendVariable(unitstats[i].basetype);
		data.appendVariable(unitstats[i]._rangemin);
		data.appendVariable(unitstats[i]._rangemax);
		data.appendVariable(unitstats[i]._price);

		data.appendVariable(unitstats[i].loadcapacity);
		data.appendVariable(unitstats[i].loadtype);
		data.appendVariable(unitstats[i].canhit);
		data.appendVariable(unitstats[i].attackparticle);
		data.appendVariable(unitstats[i].attacksound);
		data.appendVariable(unitstats[i].movesound);
		data.appendVariable(unitstats[i].moveparticle);
		data.appendVariable(unitstats[i].cancapture);
		data.appendVariable(unitstats[i].isarmy[0]);
		data.appendVariable(unitstats[i].isarmy[1]);
		data.appendVariable(unitstats[i].isarmy[2]);
		data.appendVariable(unitstats[i].techlevel);
		data.appendVariable(unitstats[i].attacksburn);
		//don't transfer unitstats[i].healthdrawhack, that's something that depends on the client's pngs
		data.appendVariable(unitstats[i].blastradius);
		data.appendVariable(unitstats[i].attackdelay);

		j = 0;
		while (j < 3)
		{
			data.appendVariable(unitstats[i]._attack[j]);
			data.appendVariable(unitstats[i]._resist[j]);
			j++;
		}
		i++;
	}
	data.compress(9);

	alldata.appendByte(NET_INIDATA);
	alldata.append(&data);
	net.engine->sendTCP(thread, alldata);
}

void send_lobbydata(string mapname)
{
	static string prev_mapname = "";
	listedgame l;
	DyBytes data;

	if (mapname == "")
	{
		prev_mapname = "";
		return;
	}

	if (mapname != prev_mapname)
	{
		l.update(clients.size() + 1, worldmap.get_num_players(), "", mapname, worldmap.get_length(), worldmap.get_height(), minimap.data_set, minimap.data_num);
		l.append_to(data);

		prev_mapname = mapname;
		empdebug("Sending full update to lobby server");
	}
	else  //no change to the map since last time, just send the number of players
	{  //maxplayers hasn't changed either, but whatever
		uint8_t players = clients.size() + 1;
		uint8_t maxplayers = worldmap.get_num_players();

		data.appendVariable(players);
		data.appendVariable(maxplayers);
		empdebug("Sending minimal update to lobby server");
	}

	data.compress(9);
	prependID(data, GAME_UPDATE);

	net.lobbyengine->sendTCP(net.lobbythread, data);
}

void send_playerdata(int thread, int placement)
{
	unsigned int j;
	string s;
	DyBytes data;

	data.clear();
	data.appendByte(placement);

	if (thread < 0)
		return;

	for (unsigned int i = 0; i < 4; i++)
	{
		if ((i == 0) || ((i < clients.size() + 1)))
		{
			if ((i != 0) && !clients[i - 1].isai)
			{  //if it's not the host player or an AI player
				if (player[i].name == "")
				{  //haven't received this player's name yet
					data.appendByte(0);  //add a 0 to data and go to the next player
					continue;
				}
			}

			j = player[i].name.length();
			data.appendByte(j);
			data.appendString(player[i].name);
			data.appendByte(player[i].number);

			if (player[i].controller == HUMAN) data.appendByte(NET);
			else if (player[i].controller == AI) data.appendByte(NETAI);
			else data.appendByte(player[i].controller);

			data.appendByte(player[i].team);
			data.appendVariable(player[i].tintcolor);

			s = net.address[i];
			j = s.length();
			data.appendByte(j);
			data.appendString(s);
		}
		else
		{  //no player for this slot, add a 0 and continue
			data.appendByte(0);
		}
	}
	data.compress(9);
	data.insertByte(NET_PLAYERDATA);

	net.engine->sendTCP(thread, data);
}

DyBytes menudata()
{
	DyBytes data;
	data.clear();

	data.appendByte(game->cashturns);
	data.appendByte(player[0].cash / 1000);
	data.appendByte(game->techlevel);
	data.appendByte(gameoptions.funding / 1000);
	data.appendByte(gameoptions.show_damage);
	data.compress(9);
	data.insertByte(NET_MENUDATA);
	return data;
}

void send_menudata(int thread)
{
	DyBytes data = menudata();
	net.engine->sendTCP(thread, data);
}

void broadcast_menudata()
{
	DyBytes data = menudata();
	net.engine->broadcastTCP(data);
}

bool load_client_playerdata(int p, DyBytes *data)
{
	unsigned int namelen, i;
	char str[50];

	namelen = data->getByte(0);
	data->removeFront(1);
	if (namelen > data->size()) return false;
	if (namelen >= 50) return false;

	i = 0;
	while (i < namelen)
	{
		str[i] = data->getByte(0);
		data->removeFront(1);
		i++;
	}
	str[i] = '\0';
	player[p].name = str;

	return true;
}

void do_netai_options(_button *b, int *colors, _button *ccyc, _button *tcyc, const string &mapname)
{
	const float xratio = float(global.screen_width) / 800.0;
	const float yratio = float(global.screen_height) / 600.0;
	const int orig_players_spacing = 116;

	for (unsigned int i = 0; ((i < clients.size() + 1) && (i < 3)); i++)
	{
		if (i == clients.size() || clients[i].isai)
		{  //if this client doesn't exist yet, or exists and is an AI player

			if (i < clients.size())
			{  //client exists, must be an AI player
				glb.draw(55, int(200 * xratio), int((70 + (orig_players_spacing * (i + 1))) * yratio));  //draw the little AI image
			}

			b[i].check();
			if (b[i].clicked())
			{
				if (i == clients.size())
				{  //create a new AI player
					int pnum = i + 1;

					clients.push_back(hostclient());
					clients[i].isai = true;

					player[pnum].number = 0;
					player[pnum].team = i + 2;
					player[pnum].name = "AI";
					player[pnum].controller = AI;
					player[pnum].playing = 1;
					player[pnum].everplaying = 1;
					colors[pnum] = 0;
					net_assign_tintcolor(pnum, colors);
					currentcharacter[pnum] = character[player[pnum].number];

					tcyc[pnum].picture = glb.bmp(13 + player[pnum].team);
					ccyc[pnum].color = player[pnum].tintcolor;

					for (unsigned int j = 0; j < clients.size(); j++)
						if (!clients[j].isai)
							send_playerdata(clients[j].thread, j + 1);
				}
				else
				{  //remove an existing AI player
					remove_player(i, colors, ccyc, tcyc, mapname);
					i--;
				}
			}
		}
	}
}

void remove_player(int client_index, int *colors, _button *ccyc, _button *tcyc, const string &mapname)
{
	cerr << "Remove player " << client_index + 1 << "\n";

	if (!clients[client_index].isai)  //if this isn't an AI player, the player's connection needs to be removed
		disconnect(clients[client_index].thread);

	//if the guy who was removed was in the middle, move everyone below him up one to fill the gap
	//HOWEVER, this doesn't handle moving units around, so the map needs to be reloaded after this
	for (unsigned int moveup = client_index; moveup < clients.size() - 1; moveup++)
	{
		clients[moveup] = clients[moveup + 1];
		player[moveup + 1] = player[moveup + 2];
		currentcharacter[moveup + 1] = currentcharacter[moveup + 2];
	}
	clients.pop_back();

	{
		const unsigned int removed_player_index = clients.size() + 1;

		player[removed_player_index].name = "";
		player[removed_player_index].playing = 0;
		player[removed_player_index].everplaying = 0;
		player[removed_player_index].team = removed_player_index;
		player[removed_player_index].number = 0;
		player[removed_player_index].tintcolor = player_color(0, 0);
		currentcharacter[removed_player_index] = NULL;
	}

	for (unsigned int k = 1; k < clients.size() + 1; k++)
	{
		tcyc[k].picture = glb.bmp(13 + player[k].team);
		ccyc[k].color = player[k].tintcolor;
	}

	for (unsigned int k = 0; k < clients.size(); k++)
		if (!clients[k].isai)
			send_playerdata(clients[k].thread, k + 1);

	{
		string s = mapname;
		stringstream str;

		s.erase(0, 5); //get rid of the (2P) / (3P) / (4P)
		str << "maps/" << s << ".map";

		load_map(mapname, str.str().c_str(), false);
		minimap.create();
		minimap.a = 1.0;
	}
}

void net_assign_tintcolor(int p, int *colors)
{
	if (colors[p] > 3) colors[p] = 0;
	player[p].tintcolor = player_color(player[p].number, colors[p]);

	for (int k = 0; k < 4; k++)
	{  //go through and ensure that everyone has different colors
		if (unsigned(k) > clients.size())  //if we're out of players to compare colors with, break out of the loop
			break;

		if (p != k)
		{  //if this isn't the player being assigned the new color
			if (player[k].tintcolor == player[p].tintcolor)
			{  //duplicate color, set the player to the next possible color and try again
				colors[p]++;
				if (colors[p] > 3) colors[p] = 0;

				player[p].tintcolor = player_color(player[p].number, colors[p]);

				k = -1;  //have to go through all the players over again
			}
		}
	}
}

void disconnect(int thread)
{
	if (net.engine->isConnected(thread))
		net.engine->disconnectHost(thread);

	if (net.chatengine->isConnected(thread))
		net.chatengine->disconnectHost(thread);
}

void cycle_startcash(int amount, _button &startingcash)
{
	char c[50];
	DyBytes data;

	player[0].cash += amount;

	if (player[0].cash > 50000) player[0].cash = 0;
	if (player[0].cash < 0)     player[0].cash = 50000;

	for (unsigned int i = 1; i < 4; i++)
		player[i].cash = player[0].cash;

	sprintf(c, "Starting Cash: %d", player[0].cash);
	startingcash.rename(c);
	broadcast_unreadyall();

	data.appendByte(NET_STARTINGCASH);
	data.appendByte(player[0].cash / 1000);
	net.engine->broadcastTCP(data);
}

void cycle_funding(int amount, _button &fundsperbuilding)
{
	char c[50];
	DyBytes data;

	gameoptions.funding += amount;

	if (gameoptions.funding > 10000) gameoptions.funding = 1000;
	if (gameoptions.funding < 1000)  gameoptions.funding = 10000;

	sprintf(c, "Funds per Building: %d", gameoptions.funding);
	fundsperbuilding.rename(c);
	broadcast_unreadyall();

	data.appendByte(NET_FUNDSPERBUILDING);
	data.appendByte(gameoptions.funding / 1000);
	net.engine->broadcastTCP(data);
}

void cycle_techlevel(int amount, _button &techlevel)
{
	char c[50];
	DyBytes data;

	game->techlevel += amount;

	if (game->techlevel > 3) game->techlevel = 1;
	if (game->techlevel < 1) game->techlevel = 3;

	sprintf(c, "Tech Level: %d", game->techlevel);
	techlevel.rename(c);
	broadcast_unreadyall();

	data.appendByte(NET_TECHLEVEL);
	data.appendByte(game->techlevel);
	net.engine->broadcastTCP(data);
}

void cycle_cashturns(int amount, _button &cashturns)
{
	char c[50];
	DyBytes data;

	game->cashturns += amount;
	if (game->cashturns > 30) game->cashturns = 0;
	if (game->cashturns < 0) game->cashturns = 30;

	if (game->cashturns == 0)
	{
		cashturns.rename("Derricks Last: Forever");
	}
	else
	{
		sprintf(c, "Derricks Last: %d Turns", game->cashturns);
		cashturns.rename(c);
	}
	broadcast_unreadyall();

	data.appendByte(NET_CASHTURNS);
	data.appendByte(game->cashturns);
	net.engine->broadcastTCP(data);
}

void toggle_showdamage(_button &showdamage)
{
	DyBytes data;

	if (gameoptions.show_damage)
	{
		gameoptions.show_damage = false;
		showdamage.rename("Show Attacking Damage: No");
	}
	else
	{
		gameoptions.show_damage = true;
		showdamage.rename("Show Attacking Damage: Yes");
	}
	broadcast_unreadyall();

	data.clear();
	data.appendByte(NET_SHOWDAMAGE);
	data.appendByte(gameoptions.show_damage);
	net.engine->broadcastTCP(data);
}

void got_bad_data(hostclient &client)
{
	client.baddata++;
	if (client.baddata > 2)
	{  //three strikes and you're kicked
		disconnect(client.thread);
	}
}

void update_player(int pnum, int playerinc, int teaminc, int colorinc,
		           int *colors, _button &plyrbutton, _button &teambutton, _button &colorbutton)
{
	DyBytes data;

	if (playerinc != 0)
	{
		player[pnum].number += playerinc;

		if (player[pnum].number >= signed(character.size())) player[pnum].number = -1;
		if (player[pnum].number < -1) player[pnum].number = character.size() - 1;

		if (player[pnum].number != -1)
			currentcharacter[pnum] = character[player[pnum].number];
		else
			currentcharacter[pnum] = NULL;

		broadcast_unreadyall();

		data.clear();
		data.appendByte(NET_CHARACTER);
		data.appendByte(pnum);
		data.appendByte(player[pnum].number);
		net.engine->broadcastTCP(data);

		if (currentcharacter[pnum] != NULL)
		{
			colors[pnum] = 0;
			net_assign_tintcolor(pnum, colors);
		}
		else player[pnum].tintcolor = WHITE;
		colorbutton.color = player[pnum].tintcolor;

		minimap.create();  //update the minimap to reflect the new player color
		minimap.a = 1.0;

		data.clear();
		data.appendByte(NET_PLAYERTINT);
		data.appendByte(pnum);
		data.appendVariable(player[pnum].tintcolor);
		net.engine->broadcastTCP(data);
	}

	if (teaminc != 0)
	{
		player[pnum].team += teaminc;

		if (player[pnum].team > 4) player[pnum].team = 1;
		if (player[pnum].team < 1) player[pnum].team = 4;

		teambutton.picture = glb.bmp(13 + player[pnum].team);
		broadcast_unreadyall();

		data.clear();
		data.appendByte(NET_PLAYERTEAM);
		data.appendByte(pnum);
		data.appendByte(player[pnum].team);
		net.engine->broadcastTCP(data);
	}

	if ((currentcharacter[pnum] != NULL) && (colorinc!= 0))  //ignore tint cycle if current army is random
	{
		colors[pnum] += colorinc;

		if (colors[pnum] < 0) colors[pnum] = 3;

		net_assign_tintcolor(pnum, colors);  //handles the case where colors[i] > 3
		colorbutton.color = player[pnum].tintcolor;

		minimap.create();  //update the minimap to reflect the new player color
		minimap.a = 1.0;

		data.clear();
		data.appendByte(NET_PLAYERTINT);
		data.appendByte(pnum);
		data.appendVariable(player[pnum].tintcolor);
		net.engine->broadcastTCP(data);
	}
}
