#include "empire.h"
#include "logic.h"

extern int SIDEBAR_WIDTH;
extern int BOTTOMBAR_HEIGHT;

void disconnect(int thread);
void get_battledata(DyBytes *data);
void send_client_playerdata();
int unit_price(int type, int army);
bool valid_client_movepath(_unit *u, vector<int> movelist);
string netcommand(unsigned char byte);

void netplayer()
{
	static bool scroll = false;
	static int scrollx, scrolly;

	bool err = false;
	int i, k, x, y, z;
	DyBytes data;
	_unit *v, *w;
	vector<int> movelist;

	if (scroll)
	{
		if (scroll_to_location(scrollx, scrolly) == 1) scroll = false;
		else return;
	}  //if it's in the process of scrolling somewhere, don't read in any new data yet

	if (net.server)
	{
		for (int j = 0; j < 3; j++)
		{
			if (clientconnected(j))
			{
				DyBytes indata_copy;
				data.clear();
				bool newdata = get_client_message(j, data);
				indata_copy = data;

				//The server gets data from all clients, but only acts on the ones
				//from the client who's turn it is.  This prevents data from other
				//clients (who shouldn't be sending anything when it's not their
				//turn anyway) from building up a backlog of old data.

				if (newdata && j != game->pturn - 1)
				{  //if we received new data from this client, but it's not this client's turn
					char c[200];
					sprintf(c, "Received command %s from client %d, but it isn't that client's turn!", netcommand(data.getByte(0)).c_str(), j);
					empdebug(c);

					data.clear();
					data.appendByte(NET_OHNOYOUDONT);
					net.engine->sendTCP(j, data);
					continue;
				}
				else if (newdata)
				{  //if we received new data from this client, and it's the client's turn
					i = data.getByte(0);
					data.removeFront(1);
					empdebug("Host received data");
					empdebug(i);
					empdebug("from client");
					empdebug(j);
					switch(i)
					{
						case NET_CLIENT_ENDTURN:
							game->next_turn();

							data.clear();
							data.appendByte(NET_NEWTURN);
							data.appendByte(game->pturn);
							net.engine->broadcastTCP(data);
							break;
						case NET_CLIENT_NEWUNIT:
							if (player[game->pturn].units_in_play() < 50)
							{
								_player *p = &player[game->pturn];
								k = data.getByte(0);
								x = data.getByte(1);
								y = data.getByte(2);
								data.removeFront(3);

								{
									char c[200];
									sprintf(c, "Client %d asked to create a %s at (%d, %d)", j, unit_name(k).c_str(), x, y);
									empdebug(c);
								}

								if (p->cash >= unit_price(k, p->number))
								{
									if ((k >= 0) && (k < NUM_UNITS) && (moves_needed(unitstats[k].movetype, x, y, game->pturn) < UNREACHABLE))
									{
										if ((x >= 0) && (x < worldmap.get_length()) && (y >= 0) && (y < worldmap.get_height()))
										{
											if (any_unit_here(x, y) == -1)
											{
												p->cash -= unit_price(k, p->number);
												p->create_unit(k, x, y, game->pturn);

												p->stats.spent += unit_price(k, p->number);
												p->stats.produced++;

												update_cities();

												data.clear();
												data.appendByte(NET_NEWUNIT);
												data.appendByte(game->pturn);
												data.appendByte(k);
												data.appendByte(x);
												data.appendByte(y);
												data.appendVariable(p->cash);
												net.engine->broadcastTCP(data);
											}
											else
											{
												empdebug("ERROR: Client asked to create a unit, but there's a unit in that spot!");
												err = true;
											}
										}
										else
										{
											empdebug("ERROR: Client asked to create a unit, but gave off-map coordinates!");
											err = true;
										}
									}
									else
									{
										empdebug("ERROR: Client asked to create a unit, but the unit couldn't be built!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: Client asked to create a unit, but didn't have enough cash!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client asked to create a unit, but it has too many units in play!");
								err = true;
							}
							break;
						case NET_CLIENT_MOVEUNIT:
							x = data.getByte(0);
							y = data.getByte(1);
							data.removeFront(2);
							i = any_unit_here(x, y);
							if ((i != -1) && (i / 100 == game->pturn))
							{
								v = &player[i / 100].unit[i % 100];
								if ((v->exists == 1) && (v->canmove))
								{
									worldmap.create_limited_pathmap(game->pturn, unitstats[v->type].movetype, v->tilex, v->tiley, v->move + 1);
									k = data.getByte(0);
									data.removeFront(1);
									movelist.clear();
									x = 0;
									while (x < k)
									{
										movelist.push_back(data.getByte(x));
										x++;
									}
									if (valid_client_movepath(v, movelist))
									{
										game->unitselected = v;  //necessary for the unit_moving function
										play_moving_sound(v->type);
										v->set_moves(movelist);
										v->canmove = false;
										game->state = UNIT_MOVING;

										scroll = true;
										scrollx = v->tilex;
										scrolly = v->tiley;

										data.clear();
										data.appendByte(NET_MOVEUNIT);
										data.appendByte(v->tilex);
										data.appendByte(v->tiley);
										data.appendByte(movelist.size());
										x = 0;
										while ((unsigned)x < movelist.size())
										{
											data.appendByte(movelist[x]);
											x++;
										}
										net.engine->broadcastTCP(data);
									}
									else
									{
										empdebug("ERROR: Client wanted to move a unit, but the movelist was invalid!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: Client wanted to move a unit, but the unit can't move or doesn't exist!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client wanted to move a unit, but the unit wasn't there or belongs to someone else!");
								err = true;
							}
							break;
						case NET_CLIENT_STANDBY:
							i = data.getByte(1);
							if ((i >= 0) && (i < 50))
							{
								if (player[game->pturn].unit[i].exists == 1)
								{
									player[game->pturn].unit[i].ready = 0;

									data.clear();
									data.appendByte(NET_STANDBY);
									data.appendByte(game->pturn);
									data.appendByte(i);
									net.engine->broadcastTCP(data);
								}
							}  //standby doesn't really do anything, so even if the unit can't go into standby, don't bother with the errors
							break;
						case NET_CLIENT_DEPLOY:
							i = data.getByte(0);
							if ((i >= 0) && (i < 50))
							{
								v = &player[game->pturn].unit[i];
								if (unitstats[v->type].name == "Deployable Portal")
								{
									if (worldmap.get_tile(v->tilex, v->tiley)->get_set() == 0)
									{  //if it's a land tile
										k = get_ltype(v->tilex, v->tiley);
										if ((k == 1) || (k == 6))
										{
											v->exists = 0;
											v->health = 0;
											worldmap.get_tile(v->tilex, v->tiley)->set_unit(-1, -1);
											worldmap.get_tile(v->tilex, v->tiley)->change_type(6, 0);
											game->unitselected = NULL;
											game->state = NOTHING_HAPPENING;
											create_particle(v->tilex * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, v->tiley * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, P_SHOCKWAVE);

											scroll = true;
											scrollx = v->tilex;
											scrolly = v->tiley;

											data.clear();
											data.appendByte(NET_DEPLOYPORTAL);
											data.appendByte(game->pturn);
											data.appendByte(i);
											net.engine->broadcastTCP(data);
										}
									}
								}
								else
								{
									empdebug("ERROR: Client wanted to deploy a portal, but the unit wasn't a Deployable Portal!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client wanted to deploy a portal, but gave an out-of-range index!");
								err = true;
							}
							break;
						case NET_CLIENT_MECHCAP:
							i = data.getByte(0);
							if ((i >= 0) && (i < 50))
							{
								v = &player[game->pturn].unit[i];
								x = data.getByte(1);
								y = data.getByte(2);
								if ((x > 0) && (x < worldmap.get_length()) && (y > 0) && (y < worldmap.get_height()))
								{
									k = any_unit_here(x, y);
									if ((k != -1) && (player[k / 100].team != player[game->pturn].team))
									{
										i = player[k / 100].unit[k % 100].type;
										if ((unitstats[i].movetype != M_INFANTRY) && (unitstats[i].movetype != M_MECH))
										{
											if (unitstats[i].basetype != AIR)
											{
												mechanic_capture(v, x, y);

												scroll = true;
												scrollx = v->tilex;
												scrolly = v->tiley;

												data.clear();
												data.appendByte(NET_MECHANIC_CAPTURE);
												data.appendByte(game->pturn);
												data.appendByte(v->number);
												data.appendByte(x);
												data.appendByte(y);
												net.engine->broadcastTCP(data);
											}
											else
											{
												empdebug("ERROR: Client wanted to do a mechanic capture, but the enemy unit was an air unit!");
												err = true;
											}
										}
										else
										{
											empdebug("ERROR: Client wanted to do a mechanic capture, but the enemy unit was an infantry type!");
											err = true;
										}
									}
									else
									{
										empdebug("ERROR: Client wanted to do a mechanic capture, but there wasn't an enemy unit there!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: Client wanted to do a mechanic capture, but gave off-map coordinates!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client wanted to do a mechanic capture, but gave an out-of-range index!");
								err = true;
							}
							break;
						case NET_CLIENT_UNLOAD:
							z = any_unit_here(data.getByte(0), data.getByte(1));
							if ((z != -1) || (z / 100 != game->pturn))
							{
								v = &player[z / 100].unit[z % 100];
								k = data.getByte(2);
								if ((k == 0) || (k == 1))
								{
									x = data.getByte(3);
									y = data.getByte(4);
									if (unit_unloadable_here(v->tilex, v->tiley, x, y, v->load[k].type))
									{
										v->unload_unit(k, x, y);

										scroll = true;
										scrollx = v->tilex;
										scrolly = v->tiley;

										data.clear();
										data.appendByte(NET_UNLOAD);
										data.appendByte(v->tilex);
										data.appendByte(v->tiley);
										data.appendByte(k);
										data.appendByte(x);
										data.appendByte(y);
										net.engine->broadcastTCP(data);
									}
									else
									{
										empdebug("ERROR: Client wanted to unload a unit, but the unit wasn't unloadable here!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: Client wanted to unload a unit, but gave a bad unload number!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client wanted to unload a unit, but gave a tile with no unit or a unit it doesn't control!");
								err = true;
							}
							break;
						case NET_CLIENT_TELEPORT:
							i = data.getByte(0);
							if ((i >= 0) && (i < 50))
							{
								v = &player[game->pturn].unit[i];
								x = data.getByte(1);
								y = data.getByte(2);
								if ((x >= 0) && (x < worldmap.get_length()) && (y >= 0) && (y < worldmap.get_height()))
								{
									if ((worldmap.get_tile(x, y)->same_type(6, 0)) && (worldmap.get_tile(v->tilex, v->tiley)->same_type(6, 0)))
									{
										if (unitstats[v->type].basetype == LAND)
										{
											start_teleport_animation(v, x, y);

											data.clear();
											data.appendByte(NET_TELEPORT);
											data.appendByte(game->pturn);
											data.appendByte(i);
											data.appendByte(x);
											data.appendByte(y);
											net.engine->broadcastTCP(data);
										}
										else
										{
											empdebug("ERROR: Client wanted to teleport a unit, but it wasn't a land unit!");
											err = true;
										}
									}
									else
									{
										empdebug("ERROR: Client wanted to teleport a unit, but the tile isn't a teleporter!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: Client wanted to teleport a unit, but gave off-map coordinates!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client wanted to teleport a unit, but gave an out-of-range index!");
								err = true;
							}
							break;
						case NET_CLIENT_ATTACK:
							x = data.getByte(0);
							y = data.getByte(1);
							data.removeFront(2);
							z = any_unit_here(x, y);
							if ((z != -1) && (z / 100 == game->pturn))
							{
								v = &player[z / 100].unit[z % 100];
								if ((v->exists == 1) && (v->ready == 1))
								{
									x = data.getByte(0);
									y = data.getByte(1);
									data.removeFront(2);
									z = any_unit_here(x, y);
									if ((z != -1) && (z / 100 != game->pturn))
									{
										w = &player[z / 100].unit[z % 100];
										if (w->exists == 1)
										{
											if (player[v->owner].team != player[w->owner].team)
											{
												k = attackable_unit_here(v, w->tilex, w->tiley, true);
												if (k != -1)
												{  //scrolling to the battle is done in the battle function
													game->state = IN_BATTLE;
													game->attacker = v;
													game->defender = w;

													//sending battle data is done in the actual battle function
												}
												else
												{
													empdebug("ERROR: Client wanted to attack, but the attacker can't hit the defender!");
													err = true;
												}
											}
											else
											{
												empdebug("ERROR: Client wanted to attack, but the defender is on the same team!");
												err = true;
											}
										}
										else
										{
											empdebug("ERROR: Client wanted to attack, but the defender doesn't exist!");
											err = true;
										}
									}
									else
									{
										empdebug("ERROR: Client wanted to attack, but gave a tile with no defender!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: Client wanted to attack, but the attacker isn't ready or doesn't exist!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: Client wanted to attack, but gave a tile with no unit or a unit it doesn't control!");
								err = true;
							}
							break;
						case NET_CLIENT_SURRENDER:
							x = (signed char)data.getByte(0);
							if (player[game->pturn].playing == 1)
							{
								if ((x >= -1) && (x < 4))
								{
									if ((x == -1) || (player[x].playing == 1))
									{
										data.clear();
										data.appendByte(NET_PLAYER_SURRENDER);
										data.appendByte(game->pturn);
										data.appendByte(x);
										net.engine->broadcastTCP(data);

										do_surrender(game->pturn, x);
									}
									else
									{
										empdebug("Error: Client wanted to surrender to a player that isn't playing!");
										err = true;
									}
								}
								else
								{
									empdebug("ERROR: The client wanted to surrender, but gave an out-of-range index!");
									err = true;
								}
							}
							else
							{
								empdebug("ERROR: The client wanted to surrender, but is no longer playing!");
								err = true;
							}
							break;
					}
					if (err)
					{  //the client will be waiting for a response after sending the request
						data.clear();
						data.appendByte(NET_OHNOYOUDONT);  //so make sure the client knows its request has been DENIED
						data.append(indata_copy);
						net.engine->sendTCP(net.clientthread[j], data);
					}
				}  //end of "if j == pturn - 1 && data.size > 0"
			}  //end of "if clientconnected(j)"
		}
	}
	else
	{
		int j;

		if (get_host_message(data))
		{
			i = data.getByte(0);
			data.removeFront(1);
			empdebug("Received data:");
			empdebug(i);

			if (game->state == NET_WAITING_FOR_RESPONSE)
			{  //if this client was waiting for a response from the server, here it is
				game->state = NOTHING_HAPPENING;
			}

			switch(i)
			{
				case NET_PLAYERGONE:
					j = data.getByte(0);
					if (player[j].playing == 1)
					{
						player[j].playing = 0;
						transform_buildings(j, -1);
						game->state = PLAYER_DEFEATED;
						destroy_player_units(j);
					}
					break;
				case NET_NEWTURN:
					do_sidebar(0, NULL, false, true);  //reset sidebar

					game->pturn = data.getByte(0);
					game->new_turn = true;
					game->unitselected = NULL;
					game->state = NOTHING_HAPPENING;
					break;
				case NET_PLAYERCASH:
					data.extractVariable(1, player[data.getByte(0)].cash);
					break;
				case NET_NEWUNIT:
					j = data.getByte(0);
					k = data.getByte(1);
					x = data.getByte(2);
					y = data.getByte(3);

					z = player[j].create_unit(k, x, y, j);

					{
						char c[200];
						sprintf(c, "Server says: Create unit %s for player %d at (%d, %d)", unit_name(k).c_str(), j, x, y);
						empdebug(c);
					}

					if (z != -1)
					{
						player[j].stats.produced++;
						player[j].stats.spent += player[j].unit[z].price;
						player[j].cash -= player[j].unit[z].price;
					}
					else
					{
						empdebug("ERROR! The server said to create a new unit, but I couldn't!");
					}

					data.removeFront(4);
					data.extractVariable(0, player[j].cash);

					update_cities();
					break;
				case NET_MOVEUNIT:
					movelist.clear();
					x = data.getByte(0);
					y = data.getByte(1);
					data.removeFront(2);
					k = any_unit_here(x, y);
					v = &player[k / 100].unit[k % 100];
					k = data.getByte(0);
					data.removeFront(1);
					j = 0;
					while (j < k)
					{
						movelist.push_back(data.getByte(j));
						j++;
					}

					scroll = true;
					scrollx = v->tilex;
					scrolly = v->tiley;

					game->unitselected = v;  //necessary for the unit_moving function
					v->set_moves(movelist);
					v->canmove = false;
					game->state = UNIT_MOVING;
					play_moving_sound(v->type);
					break;
				case NET_STANDBY:
					j = data.getByte(0);
					k = data.getByte(1);
					player[j].unit[k].ready = 0;
					break;
				case NET_DEPLOYPORTAL:
					j = data.getByte(0);
					k = data.getByte(1);
					v = &player[j].unit[k];

					scroll = true;
					scrollx = v->tilex;
					scrolly = v->tiley;

					v->exists = 0;
					v->health = 0;
					worldmap.get_tile(v->tilex, v->tiley)->set_unit(-1, -1);
					worldmap.get_tile(v->tilex, v->tiley)->change_type(6, 0);
					create_particle(v->tilex * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, v->tiley * MAP_TILE_SIZE + MAP_TILE_SIZE / 2, P_SHOCKWAVE);
					break;
				case NET_MECHANIC_CAPTURE:
					j = data.getByte(0);
					k = data.getByte(1);
					x = data.getByte(2);
					y = data.getByte(3);

					scroll = true;
					scrollx = player[j].unit[k].tilex;
					scrolly = player[j].unit[k].tiley;

					mechanic_capture(&player[j].unit[k], x, y);
					break;
				case NET_UNLOAD:
					j = data.getByte(0);
					k = data.getByte(1);
					z = any_unit_here(j, k);
					v = &player[z / 100].unit[z % 100];
					j = data.getByte(2);
					x = data.getByte(3);
					y = data.getByte(4);
					v->unload_unit(j, x, y);

					scroll = true;
					scrollx = v->tilex;
					scrolly = v->tiley;

					if (player[j].controller == HUMAN)
					{  //if it was this client that asked to unload a unit
						game->state = UNIT_SELECTED;
					}
					break;
				case NET_TELEPORT:
					j = data.getByte(0);
					k = data.getByte(1);
					x = data.getByte(2);
					y = data.getByte(3);
					start_teleport_animation(&player[j].unit[k], x, y);
					break;
				case NET_BATTLEDATA:
					game->state = IN_BATTLE;
					get_battledata(&data);
					empdebug("Entering battle");
					break;
				case NET_PLAYER_SURRENDER:
					x = (signed char)data.getByte(0);
					y = (signed char)data.getByte(1);
					do_surrender(x, y);
					break;
				case NET_OHNOYOUDONT:
					empdebug("ERROR!  Tried to do something, but the server wouldn't allow it!");
					{
						bool debugfirst = true;
						while (data.size() > 0)
						{
							if (debugfirst)
							{
								empdebug(netcommand(data.getByte(0)));
								debugfirst = false;
							}
							else
								empdebug(int(data.getByte(0)));

							data.removeFront(1);
						}
					}
					logic_netchat("ERROR: Sent a command/action that the host thinks can't happen!");
					game->state = NOTHING_HAPPENING;
					break;
				case NET_NEWCONTROLLER:
					empdebug("Received new controller message for player");
					x = data.getByte(0);
					y = data.getByte(1);
					empdebug(x);
					empdebug(y);
					player[x].controller = y;
					break;
			}
		}
	}
}

bool valid_client_movepath(_unit *u, vector<int> movelist)
{
	unsigned int i = 0;
	int x = u->tilex, y = u->tiley, z;

	while (i < movelist.size())
	{
		switch(movelist[i])
		{
			case UP:
				y--;
				break;
			case DOWN:
				y++;
				break;
			case LEFT:
				x--;
				break;
			case RIGHT:
				x++;
				break;
		}
		i++;
	}

	z = any_unit_here(x, y);
	if (z == -1) return true;
	if (can_merge(u, &player[z / 100].unit[z % 100])) return true;
	if (player[z / 100].unit[z % 100].can_load_unit(u)) return true;

	return false;
}

void logic_netchat(string message)
{
	static bool first = true, holdingenter;
	static int chattimer;
	static float chatalpha;
	static _listbox chatbox;
	static _textline chattext;
	string chatstr;

	if (first)
	{
		chatbox.init(0, global.screen_height - BOTTOMBAR_HEIGHT - 13 - 121, global.screen_width - SIDEBAR_WIDTH, 121);
		chattext.init(0, global.screen_height - BOTTOMBAR_HEIGHT - 13, global.screen_width - SIDEBAR_WIDTH, 13, true);
		chatalpha = 0.0;
		chattimer = 0;
		holdingenter = false;
		first = false;
	}

	if (message != "")
	{
		chatbox.additem(message.c_str(), true);
		chatbox.scroll_to_end();
		chatalpha = 1.0;
		return;
	}

	if (game->typingchat)
	{
		chatbox.draw(0.8, true, true);
		chatstr = chattext.check(0.8);
	}
	else
	{
		if (holdingenter)
		{
			if (!key[KEY_ENTER])
			{
				holdingenter = false;
			}
		}
		else
		{
			if (key[KEY_ENTER]) game->typingchat = true;
		}
		chattext.draw(0.2);
		clear_keybuf();
		chatstr = "/NOTHING YET/";
	}

	if (chatstr != "/NOTHING YET/")
	{
		chatalpha = 1.0;
		chattimer = 0;
		game->typingchat = false;
		holdingenter = true;
	}
	else
	{
		if (chattimer > 250)
		{
			chatalpha -= 0.004;
			if (chatalpha < 0) chatalpha = 0;
		}
		else chattimer++;
	}

	chatbox.draw(chatalpha, true, true);
	if (net.server)
	{
		if (do_hostchat(&chatbox, chatstr))
		{
			chatalpha = 1.0;
			chattimer = 0;
		}
	}
	else
	{
		if (do_clientchat(&chatbox, chatstr))
		{
			chatalpha = 1.0;
			chattimer = 0;
		}
	}
}

int reconnect()
{
	const float xratio = float(global.screen_width) / 800.0;
	const float yratio = float(global.screen_height) / 600.0;
	const int middlex = global.screen_width / 2;

	const int cancelw = int(180 * xratio);
	const int cancelh = int(40 * yratio);
	const int cancelx = (global.screen_width - cancelw) / 2;
	const int cancely = int(450 * yratio);

	static bool first = true, nohost, listenerror, pollingerror, waiting, nobodyleft, allconnected, connecterror[3];
	static int timer = 0;
	static _button cancel;
	int i, j;
	DyBytes data;

	if (first)
	{
		int oldhost = net.hostnum;
		listenerror = false;
		pollingerror = false;
		nohost = false;
		waiting = false;
		first = false;
		nobodyleft = false;
		timer = 0;

		connecterror[0] = false;
		connecterror[1] = false;
		connecterror[2] = false;

		net.hostnum++;

		cancel.init(cancelx, cancely, cancelw, cancelh, "");

		while (player[net.hostnum].controller == NETAI) net.hostnum++;
		if (net.hostnum >= 4)
		{
			nohost = true;
		}
		else if ((player[net.hostnum].controller == HUMAN) || (player[net.hostnum].controller == AI))
		{  //if the player designated to be the next host is controlled by this machine
			empdebug("Lost connection to host, and now the new host");

			player[oldhost].controller = AI;

			j = 0;
			i = 0;
			while (i < 4)
			{
				if (player[i].playing == 1)
				{
					j++;
					if ((player[i].controller == NETAI) || (i < net.hostnum)) player[i].controller = AI;  //AIs controlled by people who disconnected are controlled by the new host
				}
				i++;
			}
			if (j == 0)
			{  //no other players besides the new host are left in the game
				nobodyleft = true;
				cancel.rename("Return to Netplay Menu");
			}
			else cancel.rename("Stop Waiting");
			allconnected = false;

			net.server = true;
			net.engine->disconnectHost(net.thread);
			net.chatengine->disconnectHost(net.chatthread);

			if (!net.engine->enableTCPListen(41136) || !net.chatengine->enableTCPListen(41137))
				listenerror = true;

			net.clientthread[0] = -1;
			net.clientthread[1] = -1;
			net.clientthread[2] = -1;
		}
		else
		{
			empdebug("Lost connection to host, trying to connect to the new host");

			cancel.rename("Exit to Netplay Menu");
			if (net.address[net.hostnum] != "")
			{
				net.engine->disconnectHost(net.thread);
				net.chatengine->disconnectHost(net.chatthread);
				net.thread = -1;
				net.chatthread = -1;
			}
			else
			{  //next designated host doesn't have an address - game over
				nohost = true;
			}
		}
	}

	timer++;

	buffer_rectfill(0, 0, global.screen_width, global.screen_height, BLACK, 0.65);
	cancel.check();

	buffer_textout_centre_ex(big, "Lost connection to host", middlex, int(100 * yratio), WHITE);
	if (net.server)
	{
		for (unsigned int thread = 0; thread < net.engine->getMaxConnections(); thread++)
		{
			bool newclient = false;

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

			if (net.engine->isConnected(thread))
			{
				DyBytes data;

				if (newclient)
				{  //a client has connected
					stringstream s;
					s << "New connection on thread " << thread;
					empdebug(s.str());

					DyBytes askdata;
					askdata.appendByte(NET_ASK_PLAYERDATA);  //ask the client for its name
					net.engine->sendTCP(thread, askdata);
				}

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

					{
						stringstream s;
						s << "Received data (ID = " << id << ") from network thread " << thread;
						empdebug(s.str());
					}

					if (id != NET_PLAYERDATA)
					{  //if the client didn't send its player name
						{
							stringstream s;
							s << "Received data other than NET_PLAYERDATA from network thread " << thread << ", disconnecting it";
							empdebug(s.str());
						}

						disconnect(thread);  //then we don't want to deal with it
					}
					else
					{
						if (!data.decompress())
							disconnect(thread);
						else
						{
							char str[50];
							string playername;

							uint8_t namelen = data.getByte(0);
							data.removeFront(1);
							if ((namelen > data.size()) || (namelen >= 50))
							{
								disconnect(thread);
								namelen = 0;
								str[0] = '\0';
							}

							for (int namechar = 0; namechar < namelen; namechar++)
							{
								str[namechar] = data.getByte(0);
								data.removeFront(1);

								if (namechar == namelen - 1)
									str[namechar + 1] = '\0';
							}
							playername = str;

							for (int p = net.hostnum + 1; p <= 4; p++)
							{
								if (p == 4)
								{  //we don't recognize this player name
									disconnect(thread);
									break;
								}

								if (player[p].name == playername)
								{  //this client is a player from before
									{
										stringstream s;
										s << "Reconnected with player " << p << " on network thread " << thread;
										empdebug(s.str());
									}

									net.clientthread[p - 1] = thread;
									break;
								}
							}
						}
					}
				}  //end of "if we got a message from this thread"
			}
			else
			{  //if nothing is connected here, make sure nothing was connected previously
				for (int c = 0; c < 3; c++)
				{
					if (net.clientthread[c] == signed(thread))  //a client connected, then disconnected
					{
						stringstream s;
						s << "Player " << c + 1 << " WAS connected on thread " << thread << ", but got disconnected somehow";
						empdebug(s.str());

						connecterror[c] = true;
						net.clientthread[c] = -1;
					}
				}
			}
		}

		buffer_textout_centre_ex(big, "You are the new host.", middlex, int(200 * yratio), WHITE);

		if (nobodyleft)
			buffer_textout_centre_ex(big, "No other players left.  Can't continue the game.", middlex, 250, WHITE);
		else if (listenerror)
			buffer_textout_centre_ex(big, "Error listening on port 41136/41137.  Other players can't connect!", middlex, 250, WHITE);
		else
		{
			buffer_textout_centre_ex(big, "Waiting for the other clients to connect...", middlex, 250, WHITE);

			allconnected = true;
			i = 1;
			while (i < 4)
			{
				if ((player[i].playing == 1) && (player[i].controller == NET))
				{
					buffer_textout_right_ex(big, (player[i].name + ": ") + net.address[i], middlex - 10, int(280 * yratio + (20 * i)), WHITE);
					if (clientconnected(i - 1)) buffer_textout_ex(big, "Connected", middlex + 10, int(280 * yratio + (20 * i)), GREEN);
					else if (connecterror[i - 1]) buffer_textout_ex(big, "Connection Error", middlex + 10, int(280 * yratio + (20 * i)), RED);
					else
					{
						buffer_textout_ex(big, "Not Connected Yet", middlex + 10, int(280 * yratio + (20 * i)), WHITE);
						allconnected = false;
					}
				}
				else if ((player[i].playing == 1) && (player[i].controller != HUMAN))
				{
					buffer_textout_right_ex(big, player[i].name, middlex - 10, int(280 * yratio + (20 * i)), WHITE);
					buffer_textout_ex(big, "Controlled by AI", middlex + 10, int(280 * yratio + (20 * i)), WHITE);
				}
				i++;
			}

			if (allconnected)
			{
				buffer_textout_centre_ex(big, "All Players Connected / Transferred to AI", middlex, int(500 * yratio), WHITE);
				if (cancel.get_text() != "Continue Game") cancel.rename("Continue Game");
			}
			else buffer_textout_centre_ex(med, "Hitting \"Stop Waiting\" will replace any not-yet-reconnected players with AI", middlex, int(500 * yratio), WHITE);
		}

		if (cancel.clicked())
		{
			net.engine->disableTCPListen();
			net.chatengine->disableTCPListen();
			if (nobodyleft)
			{
				net.thread = -1;
				net.chatthread = -1;
				net.clientthread[0] = -1;
				net.clientthread[1] = -1;
				net.clientthread[2] = -1;
				first = true;
				return -1;
			}
			else
			{
				i = net.hostnum + 1;
				while (i < 4)
				{
					if (player[i].playing == 1)
					{  //make sure everyone has the right controller data
						//replace players that haven't connected with AI
						if ((i > 0) && (!clientconnected(i - 1))) player[i].controller = AI;

						data.clear();
						data.appendByte(NET_NEWCONTROLLER);
						data.appendByte(i);

						if (player[i].controller == HUMAN) data.appendByte(NET);
						else if (player[i].controller == AI) data.appendByte(NETAI);
						else data.appendByte(NET);
						net.engine->broadcastTCP(data);
					}
					i++;
				}
				data.clear();
				data.appendByte(NET_RESUMEGAME);
				net.engine->broadcastTCP(data);
				first = true;
				return 1;
			}
		}
	}
	else  //not the new host
	{
		if (nohost)
		{
			buffer_textout_centre_ex(big, "Error: There is no new host.  Can't continue the game.", middlex, int(200 * yratio), WHITE);

			if (cancel.clicked())
			{
				first = true;
				return -1;
			}
		}
		else if (pollingerror)
		{
			buffer_textout_centre_ex(big, "Error connecting to the new host.  Can't continue the game.", middlex, int(200 * yratio), WHITE);
			if (cancel.clicked())
			{
				first = true;
				return -1;
			}
		}
		else
		{
			int temp;
			waiting = false;

			//we actually try to make the connection when timer hits 50 (to give the host some time to start listening)
			if ((net.thread < 0) && (timer > 50))
			{
				waiting = true;
				i = net.engine->checkConnection(temp);
				if (i >= 0)
				{
					net.thread = i;
				}
				else if (i != DY_BUSYUNREACHABLE)
				{
					pollingerror = true;
				}
			}
			if ((net.chatthread < 0) && (timer > 50))
			{
				waiting = true;
				i = net.chatengine->checkConnection(temp);
				if (i >= 0)
				{
					net.chatthread = i;
				}
				else if (i != DY_BUSYUNREACHABLE)
				{
					pollingerror = true;
				}
			}
			if (timer <= 50)
				waiting = true;  //if the timer is < 50, we're waiting for the timer to hit 50 before we do the connection dance

			if (waiting) buffer_textout_centre_ex(big, "Attempting to connect to the new host...", middlex, int(200 * yratio), WHITE);
			else
			{
				buffer_textout_centre_ex(big, "Connected to new host!", middlex, int(200 * yratio), WHITE);
				buffer_textout_centre_ex(big, "Game will continue when the host stops waiting for others...", middlex, int(220 * yratio), WHITE);

				if (get_host_message(data))
				{
					{
						stringstream s;
						s << "Received message from new host, ID = " << int(data.getByte(0));
						empdebug(s.str());
					}

					if (data.getByte(0) == NET_NEWCONTROLLER)
					{
						if (data.getByte(1) != net.client_pnum)
						{  //our player's controller is always human, so ignore anything to the contrary
							player[data.getByte(1)].controller = data.getByte(2);
						}
					}
					else if (data.getByte(0) == NET_RESUMEGAME)
					{
						first = true;
						return 1;
					}
					else if (data.getByte(0) == NET_ASK_PLAYERDATA)
					{
						send_client_playerdata();
					}
				}

				if (!net.engine->isConnected(net.thread))
				{  //we WERE connected, but then the connection died
					empdebug("Well, we WERE connected to the host, but we just lost the connection somehow");

					pollingerror = true;
				}
			}

			if (cancel.clicked())
			{
				if (net.thread >= 0)
				{
					net.engine->disconnectHost(net.thread);
					net.thread = -1;
				}
				if (net.chatthread >= 0)
				{
					net.engine->disconnectHost(net.chatthread);
					net.chatthread = -1;
				}
				first = true;
				return -1;
			}

			if (timer == 50)
			{  //timer hit 50, try to connect to the new host
				empdebug("Attempting to connect to " + net.address[net.hostnum]);

				net.engine->connectToHost(net.address[net.hostnum], 41136);
				net.chatengine->connectToHost(net.address[net.hostnum], 41137);
			}
			else if (timer > 50)
				timer = 51;  //just to make sure timer never overflows
		}
	}

	return 0;
}

string netcommand(unsigned char byte)
{
	switch (byte)
	{
		case NET_CLIENT_ENDTURN:
			return "NET_CLIENT_ENDTURN";
			break;
		case NET_CLIENT_NEWUNIT:
			return "NET_CLIENT_NEWUNIT";
			break;
		case NET_CLIENT_MOVEUNIT:
			return "NET_CLIENT_MOVEUNIT";
			break;
		case NET_CLIENT_STANDBY:
			return "NET_CLIENT_STANDBY";
			break;
		case NET_CLIENT_DEPLOY:
			return "NET_CLIENT_DEPLOY";
			break;
		case NET_CLIENT_MECHCAP:
			return "NET_CLIENT_MECHCAP";
			break;
		case NET_CLIENT_UNLOAD:
			return "NET_CLIENT_UNLOAD";
			break;
		case NET_CLIENT_TELEPORT:
			return "NET_CLIENT_TELEPORT";
			break;
		case NET_CLIENT_ATTACK:
			return "NET_CLIENT_ATTACK";
			break;
		case NET_CLIENT_SURRENDER:
			return "NET_CLIENT_SURRENDER";
			break;
		default:
			break;
	}

	char s[5];
	sprintf(s, "%d", byte);
	return s;
}
