/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***                            Confidential - Westwood Studios                              ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Commando/messages.cpp                      $*
 *                                                                                             *
 *                      $Author:: Steve_t                                                     $*
 *                                                                                             *
 *                     $Modtime:: 2/22/02 7:15p                                               $*
 *                                                                                             *
 *                    $Revision:: 229                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "cnetwork.h"

#include <stdio.h>

#include "playermanager.h"
#include "gameobjmanager.h"
#include "textdisplay.h"
#include "input.h"
#include "combatgmode.h"
#include "WWAudio.h"
#include "systimer.h"
#include "multihud.h"
#include "useroptions.h"
#include "devoptions.h"
#include "god.h"
#include "playertype.h"
#include "translatedb.h"
#include "string_ids.h"
#include "wolgmode.h"
#include "bitpackids.h"
#include "msgstatlistgroup.h"
#include "vistable.h"
#include "pscene.h"
#include "staticanimphys.h"
#include "combat.h"
#include "gametype.h"
#include "colors.h"
#include "networkobject.h"
#include "building.h"
#include "vendor.h"
#include "networkobjectfactorymgr.h"
#include "networkobjectfactory.h"
#include "networkobjectmgr.h"
#include "cstextobj.h"
#include "loadingevent.h"
#include "clientcontrol.h"
#include "wwprofile.h"
#include "changeteamevent.h"
#include "DlgMPTeamSelect.h"
#include "dlgmessagebox.h"
#include "apppacketstats.h"
#include "clientpingmanager.h"
#include "priority.h"
#include "crandom.h"
#include "wwmath.h"
#include "clienthintmanager.h"
#include "packetmgr.h"
#include "specialbuilds.h"
#include "gameinitmgr.h"
#include "dlgcncwinscreen.h"
#include "consolemode.h"
#include "CDKeyAuth.h"

static int LastSortedSecond;

static const float max_update_rate = 140.0f;						// Priority 1 update rate
static const float min_update_rate = 5000.0f;					// Priority 0.001 update rate
static const float unseen_update_rate = 10000.0f;				// Priority 0 update rate.
static const unsigned short infinity_update_rate = 0xffff;	// Lowest update rate - no updates at all.



//-----------------------------------------------------------------------------
//
// This is the most crucial place for server filtering
//
void cNetwork::Tell_Client_About_Dynamic_Objects
(
	int			client_id,
	Vector3 &	dest_pos
)
{
#ifndef BETACLIENT


if (cDevOptions::UseNewTCADO.Is_False()) {

	WWPROFILE("TCADO");

	WWASSERT(client_id >= 0);
   WWASSERT(cNetwork::I_Am_Server());
	if (Get_Server_Rhost(client_id) == NULL) {
		return;
	}

	if (cNetwork::I_Am_Client() && client_id == cNetwork::Get_My_Id())
	{
		//
		// Server does not send to his own client.
		//
		return;
	}

	int i;
	VisTableClass *pvs = NULL;
	int count = 0;

	{
 		WWPROFILE("GetVis");
		pvs = COMBAT_SCENE->Get_Vis_Table (dest_pos);
		count = NetworkObjectMgrClass::Get_Object_Count();
	}

	/*
	** List of objects requiring frequent updates.
	*/
	static DynamicVectorClass<NetworkObjectClass *> object_list;

	/*
	** List of objects requiring guaranteed updates. We can't schedule these.
	*/
	static DynamicVectorClass<NetworkObjectClass *> g_object_list;

	object_list.Clear();
	g_object_list.Clear();

	SoldierGameObj * player_ptr = GameObjManager::Find_Soldier_Of_Client_ID(client_id);

	{
		WWPROFILE("ListBuild");
		/*
		** Go through the object list once and figure out all the priorities.
		*/
		for (int index = 0; index < count; index ++) {

			NetworkObjectClass * p_object = NetworkObjectMgrClass::Get_Object(index);

			if (p_object == NULL) {
				continue;
			}

			float priority = 0.0f;


			if (p_object->Get_App_Packet_Type() == APPPACKETTYPE_SERVERFPS) {
				priority = 0.05f;
				p_object->Set_Cached_Priority(priority);
				object_list.Add(p_object);
			} else {

				/*
				** Get the base priority. This is just distance from where the server thinks the client is.
				*/
				priority = cPriority::Compute_Object_Priority(client_id, dest_pos, p_object);
				p_object->Set_Cached_Priority(priority);

				/*
				** If we can't see it at all, ignore it unless the priority is really high. A really high priority might indicate
				** an object update request from the client.
				*/
				if (priority < 1.0f) {
					int vis_id = p_object->Get_Vis_ID ();
					if (pvs && vis_id != -1 && !pvs->Get_Bit(vis_id)) {
						priority = 0.0f;
					}
				}


				if (p_object->Get_Object_Dirty_Bit(client_id, NetworkObjectClass::BIT_CREATION) ||
					p_object->Get_Object_Dirty_Bit(client_id, NetworkObjectClass::BIT_RARE) ||
					p_object->Get_Object_Dirty_Bit(client_id, NetworkObjectClass::BIT_OCCASIONAL)) {
					g_object_list.Add(p_object);
				} else {

					if (p_object == player_ptr) {
						priority = 0.8f;
					}
					p_object->Set_Cached_Priority(priority);

					if (priority > 0.001f) {

						/*
						** Work out the export size if we don't know it already.
						*/
						if (p_object->Get_Frequent_Update_Export_Size() == 0) {
							cPacket packet;
							int bits_before = packet.Get_Bit_Write_Position();
							packet.Add(p_object->Get_Network_ID());
							packet.Add(p_object->Get_Object_Dirty_Bits(client_id));
							packet.Add(p_object->Is_Delete_Pending());
							int bits_now = packet.Get_Bit_Write_Position();
							p_object->Export_Frequent (packet);
							int bits_after = packet.Get_Bit_Write_Position();
							int packet_size = 0;
							if (bits_now < bits_after) {
								packet_size = (bits_after - bits_before) / 8;

								/*
								** Add the packet header.
								*/
								packet_size += cPacket::Get_Packet_Header_Size();
								p_object->Set_Frequent_Update_Export_Size(packet_size);
							} else {
								/*
								** For some reason, some objects have a frequent bit set but there is no frequent update export.
								*/
								p_object->Set_Frequent_Update_Export_Size(0);
							}
						}
						object_list.Add(p_object);
					}
				}
			}
		}
	}

	/*
	** Now we have two lists. One list of non-guaranteed objects that we would like to send and another list of guaranteed
	** objects we 'must' send.
	*/

	/*
	** Sort the non-guaranteed list.
	** Bubble sort for now, quick sort later.
	*/
	NetworkObjectClass *temp_obj;

	/*
	** Get our budget in bits per second for this guy.
	*/
	cRemoteHost *r_host = cNetwork::Get_Server_Rhost(client_id);
	int bits_per_second = r_host->Get_Target_Bps();

	/*
	** Convert to bytes.
	*/
	int bytes_per_second = bits_per_second >> 3;

	/*
	** We have NetUpdateRate updates per second.
	*/
	int net_update_rate = cUserOptions::NetUpdateRate.Get();

	/*
	** Work out how many bytes we can send per update.
	*/
	int avail_bytes_per_update = bytes_per_second / net_update_rate;

	/*
	** Factor in the bandwidth multiplier.
	*/
	float mult = Get_Server_Rhost(client_id)->Get_Bandwidth_Multiplier();
	if (r_host->Get_Flood()) {
		if (r_host->Get_Target_Bps() < 14400) {
			mult = 0.7f;
		} else {
			mult = 1.0f;
		}
	}
	avail_bytes_per_update = (int) (mult * (float)avail_bytes_per_update);

	unsigned long time = TIMEGETTIME();

	/*
	** Don't use more than 50% of the available bytes per update for guaranteed packets.
	*/
	unsigned long bytes_out = 0;
	unsigned long max_bytes = avail_bytes_per_update;	//(avail_bytes_per_update >> 1);	//* 3) / 4;

	/*
	** Figure a compression ratio of 2:1
	*/
	max_bytes += bytes_out << 1;

	{
		WWPROFILE("SendG");
		/*
		** Send the guaranteed stuff first. This has to be done anyway so let's see how much of it there is.
		*/
		bool full = false;
		for (i=0 ; i<g_object_list.Count() ; i++) {
			temp_obj = g_object_list[i];
			if (!full || temp_obj->Get_App_Packet_Type() == APPPACKETTYPE_CLIENTBBOEVENT) {
				bytes_out += (Send_Object_Update(temp_obj, client_id) >> 3);
				temp_obj->Set_Last_Update_Time(client_id, time);
			}

			/*
			** See if we are using too much bandwidth and don't send more if we are.
			*/
			if (!full && bytes_out > max_bytes) {
	#ifdef WWDEBUG
				if (cDevOptions::ExtraNetDebug.Is_True()) {
					WWDEBUG_SAY(("*** WARNING: Tell_Client_About_Dynamic_Objects - Insufficient bandwidth to send all guaranteed packets ***\n"));
					WWDEBUG_SAY(("*** After %d objects, bytes_out = %d, max_bytes = %d\n", i, bytes_out, max_bytes));
				}
	#endif //WWDEBUG
				avail_bytes_per_update >>= 1;
				full = true;
				//break;
			}
		}
	}

	/*
	** Work out the average priority. We will need this later when balancing bandwidth budgets between clients.
	*/
	float average_priority = 0.0f;
	int num_priorities = 0;

	{
		WWPROFILE("CalcRate");
		for (i=0 ; i<object_list.Count() ; i++) {
			temp_obj = object_list[i];
			if (temp_obj->Get_App_Packet_Type() != APPPACKETTYPE_SERVERFPS) {
				float pri = temp_obj->Get_Cached_Priority();
				/*
				** Don't count unseen objects or objects we are getting hints for.
				*/
				if (pri > 0.001f && pri < 1.0f) {
					average_priority += pri;
					num_priorities++;
				}
			}
		}

		if (num_priorities) {
			average_priority = average_priority / (float)num_priorities;
			Get_Server_Rhost(client_id)->Set_Average_Priority(average_priority);

			/*
			** Remaining objects get updated based on priority.
			** Just set the rate based on priority then we will go back in a second pass and adjust it according to remaining bandwidth.
			*/
			float ms_low = max_update_rate;
			float ms_high = min_update_rate;
			float spread = ms_high - ms_low;
			int total_bps = 0;

			for (i=0 ; i<object_list.Count() ; i++) {
				temp_obj = object_list[i];
				float pri = temp_obj->Get_Cached_Priority();
				unsigned long update_rate = infinity_update_rate;	//0;
				if (pri > 0.025f) {	//01f) {
					update_rate = (unsigned long)(((1.0f - pri) * spread) + ms_low);
				}
				temp_obj->Set_Update_Rate(client_id, (unsigned short) update_rate);
				if (update_rate != infinity_update_rate) {
					int bps = (1000.0f / update_rate) * temp_obj->Get_Frequent_Update_Export_Size();
					total_bps += bps;
				}
			}

			/*
			** Make that number a per update figure.
			*/
			total_bps = total_bps / net_update_rate;

			/*
			** Now, scale the update rate based on available bandwidth.
			*/
			float factor = 1.0;
			if (total_bps) {
				factor = avail_bytes_per_update / total_bps;
				if (factor < 0.00001f) {
					factor = 0.00001f;
				}
			}

			unsigned short rate;
			float float_rate;
			for (i=0 ; i<object_list.Count() ; i++) {
				temp_obj = object_list[i];
				float obj_upd_rate = (float)temp_obj->Get_Update_Rate(client_id);
				if (obj_upd_rate != infinity_update_rate) {
					float_rate = obj_upd_rate / factor;
					rate = (unsigned short) float_rate;
					temp_obj->Set_Update_Rate(client_id, rate);
				}
			}
		} else {
			Get_Server_Rhost(client_id)->Set_Average_Priority(0.0f);
		}
	}

	{
		WWPROFILE("SendN");
		/*
		** Send those packets whos time has come.
		*/
		for (i=0 ; i<object_list.Count() ; i++) {
			temp_obj = object_list[i];
			unsigned long rate =  (unsigned long)temp_obj->Get_Update_Rate(client_id);
			if (rate != (unsigned long)infinity_update_rate) {
				if (time - temp_obj->Get_Last_Update_Time(client_id) > rate) {
					Send_Object_Update(temp_obj, client_id);
					temp_obj->Set_Last_Update_Time(client_id, time);
				}
			}
		}
	}

	REF_PTR_RELEASE(pvs);

} else {











	/*
	**
	**    Optimized version of TCADO
	**
	**
	**
	**
	*/


	const unsigned char dirty_check = (NetworkObjectClass::BIT_FREQUENT ^ 0xffffffff) & (NetworkObjectClass::BIT_CREATION | NetworkObjectClass::BIT_RARE | NetworkObjectClass::BIT_OCCASIONAL);

	WWPROFILE("TCADO");

	WWASSERT(client_id >= 0);
   WWASSERT(cNetwork::I_Am_Server());
	if (Get_Server_Rhost(client_id) == NULL) {
		return;
	}

	if (cNetwork::I_Am_Client() && client_id == cNetwork::Get_My_Id())
	{
		//
		// Server does not send to his own client.
		//
		return;
	}

	cRemoteHost *r_host = cNetwork::Get_Server_Rhost(client_id);

	bool update_priorities = (r_host->Get_Priority_Update_Counter() == 0) ? true : false;
	r_host->Increment_Priority_Count();

	int i;
	VisTableClass *pvs = NULL;
	int count = 0;
	bool global_packet_allowance_full = false;
	NetworkObjectClass *temp_obj;
	unsigned long time = TIMEGETTIME();
	int global_count = 0;

	/*
	** Get our budget in bits per second for this guy.
	*/
	int bits_per_second = r_host->Get_Target_Bps();

	/*
	** Adjust the cut off threshold for vis hidden objects further out if more bandwidth is available.
	*/
	float min_vis_distance = 15.0f;
	if (bits_per_second > 60000) {
		min_vis_distance = 50.0f;
	}

	/*
	** Convert to bytes.
	*/
	int bytes_per_second = bits_per_second >> 3;

	/*
	** We have NetUpdateRate updates per second.
	*/
	int net_update_rate = cUserOptions::NetUpdateRate.Get();

	/*
	** Work out how many bytes we can send per update.
	*/
	int avail_bytes_per_update = bytes_per_second / net_update_rate;

	/*
	** Don't use more than 50% of the available bytes per update for guaranteed packets.
	*/
	unsigned long bytes_out = 0;
	unsigned long max_bytes = avail_bytes_per_update;	//(avail_bytes_per_update >> 1);	//* 3) / 4;

	/*
	** Figure a compression ratio of 2:1 for guaranteed packets.
	*/
	//max_bytes <<= 1;

	{
		WWPROFILE("GetVis");
		if (update_priorities) {
			pvs = COMBAT_SCENE->Get_Vis_Table(dest_pos);
		}
	}
	count = NetworkObjectMgrClass::Get_Object_Count();

	/*
	** List of objects requiring frequent updates.
	*/
	static DynamicVectorClass<NetworkObjectClass *> object_list(500);
	object_list.Reset_Active();
	object_list.Set_Growth_Step(100);

	SoldierGameObj * player_ptr = GameObjManager::Find_Soldier_Of_Client_ID(client_id);

	{
		WWPROFILE("ListBuild");
		/*
		** Go through the object list once and figure out all the priorities.
		*/
		for (int index = 0; index < count; index ++) {

			NetworkObjectClass * p_object = NetworkObjectMgrClass::Get_Object(index);

			if (p_object == NULL) {
				continue;
			}

			float priority = 0.0f;

			/*
			** SERVERFPS events are low priority but must have some kind of priority.
			*/
			if (p_object->Get_App_Packet_Type() == APPPACKETTYPE_SERVERFPS) {
				priority = 0.05f;
				p_object->Set_Cached_Priority_2(client_id, priority);
				object_list.Add(p_object);
			} else {

				unsigned char dirty = p_object->Get_Object_Dirty_Bits(client_id);

				if (dirty & dirty_check) {
					/*
					** This is a guaranteed packet update. Just send it right away.
					*/
					if (!global_packet_allowance_full || p_object->Get_App_Packet_Type() == APPPACKETTYPE_CLIENTBBOEVENT) {
						bytes_out += (Send_Object_Update(p_object, client_id) >> 3);
						p_object->Set_Last_Update_Time(client_id, time);
					}
					global_count++;

					/*
					** See if we are using too much bandwidth and don't send any more guaranteed packets if we are.
					*/
					if (!global_packet_allowance_full && bytes_out > max_bytes) {
#ifdef WWDEBUG
						if (cDevOptions::ExtraNetDebug.Is_True()) {
							WWDEBUG_SAY(("*** WARNING: Tell_Client_About_Dynamic_Objects - Insufficient bandwidth to send all guaranteed packets ***\n"));
							WWDEBUG_SAY(("*** After %d objects, bytes_out = %d, max_bytes = %d\n", global_count, bytes_out, max_bytes));
						}
#endif //WWDEBUG
						global_packet_allowance_full = true;
					}


				} else {

					/*
					** Get the base priority. This is how important we think this object is to this client.
					*/
					if ((dirty & NetworkObjectClass::BIT_FREQUENT) != 0) {
						priority = p_object->Get_Cached_Priority_2(client_id);

						if (p_object == player_ptr) {
							if (player_ptr->Is_In_Vehicle()) {
								priority = 0.1f;
							} else {
								priority = 0.8f;
							}
						} else {

							if (p_object->Get_Client_Hint_Count(client_id) > 0) {
								priority = 1.0f;
								p_object->Reset_Client_Hint_Count(client_id);
							} else {

								/*
								** If we can't see it at all, ignore it unless the priority is really high. A really high priority might indicate
								** an object update request from the client.
								*/
								/*
								** Even if the client can't see the object, it's probably a good idea to update it if it's really
								** close to the client. Say 15 meters.
								*/
								if (update_priorities) {
									int vis_id = p_object->Get_Vis_ID();
									bool hidden = false;
									if (pvs && vis_id != -1 && !pvs->Get_Bit(vis_id)) {
										hidden = true;
									}
									if (hidden) {
										int distance = cPriority::Get_Object_Distance_2(dest_pos, p_object);
										if (distance > min_vis_distance) {
											/*
											** Allow some very infrequent updates for distant, hidden objects on broadband connections.
											*/
											if (bits_per_second > 100000 && distance < 150.0f) {
												priority = 0.01f;
											} else {
												priority = 0.0f;
											}
										} else {
											/*
											** It's hidden, but close (maybe as close at 15 meters), so it's probably somewhat important to us.
											*/
											priority = 0.2f;
										}
									} else {

										/*
										** Figure out the priority if it's time or if we couldn't see the object last frame but we can now.
										*/
										//if (update_priorities || priority == 0.0f) {
											priority = cPriority::Compute_Object_Priority_2(client_id, dest_pos, p_object, false, player_ptr);
											p_object->Set_Cached_Priority_2(client_id, priority);
										//}
									}
								}
							}
						}

						p_object->Set_Cached_Priority_2(client_id, priority);

						if (priority > 0.001f) {

							/*
							** Work out the export size if we don't know it already.
							*/
							if (p_object->Get_Frequent_Update_Export_Size() == 0) {
								cPacket packet;
								int bits_before = packet.Get_Bit_Write_Position();
								packet.Add(p_object->Get_Network_ID());
								packet.Add(p_object->Get_Object_Dirty_Bits_2(client_id));
								packet.Add(p_object->Is_Delete_Pending());
								int bits_now = packet.Get_Bit_Write_Position();
								p_object->Export_Frequent (packet);
								int bits_after = packet.Get_Bit_Write_Position();
								int packet_size = 0;
								if (bits_now < bits_after) {
									packet_size = (bits_after - bits_before) / 8;

									/*
									** Add the packet header.
									*/
									packet_size += cPacket::Get_Packet_Header_Size();
									p_object->Set_Frequent_Update_Export_Size(packet_size);
								} else {
									/*
									** For some reason, some objects have a frequent bit set but there is no frequent update export.
									*/
									p_object->Set_Frequent_Update_Export_Size(0xff);
								}
							}

							/*
							** Add this object to our update list.
							*/
							if (p_object->Get_Frequent_Update_Export_Size() > 0 && p_object->Get_Frequent_Update_Export_Size() < 0xff) {
								object_list.Add(p_object);
							}
						}
					}
				}
			}
		}
	}



	/*
	** Now we have a list of non-guaranteed objects that we would like to send.
	*/


	/*
	** Factor in the bandwidth multiplier.
	*/
	float mult = Get_Server_Rhost(client_id)->Get_Bandwidth_Multiplier();
	if (r_host->Get_Flood()) {
		if (r_host->Get_Target_Bps() < 14400) {
			mult = 0.7f;
		} else {
			mult = 1.0f;
		}
	}
	avail_bytes_per_update = (int) (mult * (float)avail_bytes_per_update);


	/*
	** If we used tons of bandwidth on guaranteed packets then throttle back on non-guaranteed stuff.
	*/
	if (global_packet_allowance_full) {
		avail_bytes_per_update >>= 1;
	}

	/*
	** Work out the average priority. We will need this later when balancing bandwidth budgets between clients.
	*/
	float average_priority = 0.0f;
	int num_priorities = 0;

	{
		WWPROFILE("CalcRate");
		for (i=0 ; i<object_list.Count() ; i++) {
			temp_obj = object_list[i];
			if (temp_obj->Get_App_Packet_Type() != APPPACKETTYPE_SERVERFPS) {
				float pri = temp_obj->Get_Cached_Priority_2(client_id);
				/*
				** Don't count unseen objects or objects we are getting hints for.
				*/
				if (pri > 0.001f && pri < 1.0f) {
					average_priority += pri;
					num_priorities++;
				}
			}
		}

		if (num_priorities) {
			average_priority = average_priority / (float)num_priorities;
			Get_Server_Rhost(client_id)->Set_Average_Priority(average_priority);

			/*
			** Remaining objects get updated based on priority.
			** Just set the rate based on priority then we will go back in a second pass and adjust it according to remaining bandwidth.
			*/
			float ms_low = max_update_rate;
			float ms_high = min_update_rate;
			float spread = ms_high - ms_low;
			int total_bps = 0;

			for (i=0 ; i<object_list.Count() ; i++) {
				temp_obj = object_list[i];
				float pri = temp_obj->Get_Cached_Priority_2(client_id);
				unsigned long update_rate = infinity_update_rate;	//0;
				if (pri > 0.025f) {	//01f) {
					update_rate = (unsigned long)(((1.0f - pri) * spread) + ms_low);
				} else {
					if (pri > 0.009f) {
						update_rate = min_update_rate;
					}
				}
				temp_obj->Set_Update_Rate(client_id, (unsigned short) update_rate);
				if (update_rate != infinity_update_rate) {
					int bps = (1000.0f / update_rate) * temp_obj->Get_Frequent_Update_Export_Size();
					total_bps += bps;
				}
			}

			/*
			** Make that number a per update figure.
			*/
			total_bps = total_bps / net_update_rate;

			/*
			** Now, scale the update rate based on available bandwidth.
			*/
			float factor = 1.0;
			if (total_bps) {
				factor = avail_bytes_per_update / total_bps;
				if (factor < 0.00001f) {
					factor = 0.00001f;
				}
			}

			unsigned short rate;
			float float_rate;
			for (i=0 ; i<object_list.Count() ; i++) {
				temp_obj = object_list[i];
				float obj_upd_rate = (float)temp_obj->Get_Update_Rate(client_id);
				if (obj_upd_rate != infinity_update_rate && obj_upd_rate < (min_update_rate + WWMATH_EPSILON)) {
					float_rate = obj_upd_rate / factor;
					rate = (unsigned short) float_rate;
					temp_obj->Set_Update_Rate(client_id, rate);
				}
			}
		} else {
			Get_Server_Rhost(client_id)->Set_Average_Priority(0.0f);
		}
	}

	{
		WWPROFILE("SendN");
		/*
		** Send those packets whos time has come.
		*/
		for (i=0 ; i<object_list.Count() ; i++) {
			temp_obj = object_list[i];
			unsigned long rate =  (unsigned long)temp_obj->Get_Update_Rate(client_id);
			if (rate != (unsigned long)infinity_update_rate) {
				if (time - temp_obj->Get_Last_Update_Time(client_id) > rate) {
					Send_Object_Update(temp_obj, client_id);
					temp_obj->Set_Last_Update_Time(client_id, time);
				}
			}
		}
	}

	if (pvs) {
		REF_PTR_RELEASE(pvs);
	}


}

#endif // not BETACLIENT
}

//-----------------------------------------------------------------------------
void cNetwork::Tell_Server_About_Dynamic_Objects
(
	void
)
{
#ifndef FREEDEDICATEDSERVER

   WWASSERT (cNetwork::I_Am_Client());

	//
	//	Loop over each network object
	//
	int count = NetworkObjectMgrClass::Get_Object_Count();

	//int debug_count = 0;

	for (int index = 0; index < count; index ++) {

		NetworkObjectClass * object = NetworkObjectMgrClass::Get_Object(index);

		//
		//	Should we send update information for this object?
		//
		if (object != NULL && object->Is_Client_Dirty(0))
		{
			//
			//	Transmit the object's data to the server
			//
			Send_Object_Update(object, 0);
			//debug_count++;
		}
	}
	//unsigned long time = TIMEGETTIME() / 1000;
	//WWDEBUG_SAY(("Updated %d objects at %d\n", debug_count, time));

#endif // !FREEDEDICATEDSERVER
}

//-----------------------------------------------------------------------------
void cNetwork::Tell_Client_About_Delete_Notifications(int client_id)
{
#ifndef BETACLIENT

	WWASSERT (client_id >= 0);
   WWASSERT (cNetwork::I_Am_Server ());

	if (Get_Server_Rhost (client_id) == NULL) {
		return;
	}

	//
	//	Loop over each network object
	//
	int count = NetworkObjectMgrClass::Get_Object_Count ();
	for (int index = 0; index < count; index ++) {
		NetworkObjectClass *object = NetworkObjectMgrClass::Get_Object (index);

		//
		//	Is this object going to be deleted soon?
		//
		if (object != NULL && object->Is_Delete_Pending ()) {

			//
			//	Does the local client need this information?
			//
			bool send = true;
			if (cNetwork::I_Am_Client() &&
				 client_id == cNetwork::Get_My_Id ())
			{
				send = false;
			}

			if (send)
			{
			   //
				//	Transmit the object's data to the client
				//
				Send_Object_Update (object, client_id);
			}
		}
	}

	return ;

#endif // not BETACLIENT
}

//-----------------------------------------------------------------------------
int
cNetwork::Send_Object_Update(NetworkObjectClass *object, int client_id)
{
	WWPROFILE( "send obj upd" );
	WWASSERT(client_id >= 0);

   Debug_Network_Prolific((
      "Sending update for object %d to %s",
      object->Get_Network_ID (), Get_Client_String(client_id)));

	WWASSERT(object->Get_Network_ID() > 0);

	// Track number of bits sent.
	int bits_sent = 0;

	//
	//	Build a packet that will contain enough information about
	// the object so the client will be able to import the data
	//
	cPacket packet;
	packet.Add(object->Get_Network_ID());
	packet.Add(object->Get_Object_Dirty_Bits(client_id));
	packet.Add(object->Is_Delete_Pending());
	//packet.Add(object->Get_App_Packet_Type());

	BYTE type = object->Get_App_Packet_Type();
	int bits_start = packet.Get_Bit_Write_Position();

	//
	// Send mode is unreliable unless creation/rare/occasional data is present.
	//
	int mode = SEND_UNRELIABLE;

	//
	// TSS101101
	//
	if (object->Is_Delete_Pending()) {
		mode = SEND_RELIABLE;
	}

	//
	//	Add creation information to the packet (if necessary)
	//
	if (object->Get_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_CREATION)) {

		int bits_before = packet.Get_Bit_Write_Position();

		//
		//	Add the class id of the object so it can be created on the client.
		//
		uint32 net_classid = object->Get_Network_Class_ID ();
		packet.Add (net_classid);

		//
		//	Lookup the factory for this object
		//
		NetworkObjectFactoryClass *factory = NetworkObjectFactoryMgrClass::Find_Factory (net_classid);
		WWASSERT (factory != NULL);

		//
		//	Store any data in the packet that's needed in order
		// to create the object on the client
		//
		factory->Prep_Packet (object, packet);

		//
		//	Send object-specific data to the client
		//
		object->Export_Creation (packet);

		int bits_after = packet.Get_Bit_Write_Position();
		cAppPacketStats::Increment_Bits_Sent_Tier(type, PACKET_TIER_CREATION, bits_after - bits_before);

		mode = SEND_RELIABLE;
	}

	//
	//	Add rare information to the packet (if necessary)
	//
	if (object->Get_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_RARE)) {
		int bits_before = packet.Get_Bit_Write_Position();
		object->Export_Rare (packet);
		int bits_after = packet.Get_Bit_Write_Position();
		cAppPacketStats::Increment_Bits_Sent_Tier(type, PACKET_TIER_RARE, bits_after - bits_before);
		mode = SEND_RELIABLE;
	}

	//
	//	Add occasional information to the packet (if necessary)
	//
	if (object->Get_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_OCCASIONAL)) {
		int bits_before = packet.Get_Bit_Write_Position();
		object->Export_Occasional (packet);
		int bits_after = packet.Get_Bit_Write_Position();
		cAppPacketStats::Increment_Bits_Sent_Tier(type, PACKET_TIER_OCCASIONAL, bits_after - bits_before);
		mode = SEND_RELIABLE;
	}

	//
	//	Add frequent information to the packet (if necessary)
	//
	if (object->Get_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_FREQUENT)) {
		int bits_before = packet.Get_Bit_Write_Position();
		object->Export_Frequent (packet);
		int bits_after = packet.Get_Bit_Write_Position();
		cAppPacketStats::Increment_Bits_Sent_Tier(type, PACKET_TIER_FREQUENT, bits_after - bits_before);
	}

	int bits_end = packet.Get_Bit_Write_Position();

	if (mode == SEND_RELIABLE && object->Get_Unreliable_Override() == true) {
		//
		// The normal rules would make us send this packet reliably, but this
		// has been explicitly overridden.
		//
		mode = SEND_UNRELIABLE;
	}

	//
	// Trying just not sending the packet if there is nothing in it.
	//
	if (object->Is_Delete_Pending() || (bits_end > bits_start)) {

		bits_sent = packet.Get_Bit_Write_Position();

		//
		//	Send the packet to the client or server
		//
		if (client_id > 0) {
			//WWDEBUG_SAY(("Sending update for object %d\n", object->Get_Network_ID()));
			Server_Send_Packet(packet, mode, client_id);
		} else {
			Client_Send_Packet(packet, mode);
		}
	}

#ifdef WWDEBUG
	if (client_id > 0) {
		//
		// It is useful to be able to generate a bunch of
		// extra bandwidth
		//
		for (int i = 0; i < cDevOptions::SpamCount.Get (); i ++) {
			WWDEBUG_SAY(("Sending spam\n"));
			Server_Send_Packet(packet, mode, client_id);
		}
	}
#endif // WWDEBUG

	//
	//	Reset the dirty bits for this client
	//
	object->Set_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_CREATION, false);
	object->Set_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_RARE, false);
	object->Set_Object_Dirty_Bit (client_id, NetworkObjectClass::BIT_OCCASIONAL, false);

	//if (client_id > 0)
	{
		//
		// This is a server send. Record stats.
		//

		/*
		//if (type == APPPACKETTYPE_UNKNOWN)
		if (type == APPPACKETTYPE_SIMPLE)
		{
			WWDEBUG_SAY(("WTF is this?\n"));
		}
		/**/

		//
		// Record stats
		//
		cAppPacketStats::Increment_Packets_Sent(type);
		cAppPacketStats::Increment_Bits_Sent(type, bits_end - bits_start);
	}
	return(bits_sent);
}


//-----------------------------------------------------------------------------
void cNetwork::Intermission_Over_Processing(void)
{
	WWDEBUG_SAY(("cNetwork::Intermission_Over_Processing\n"));

	WWASSERT(cNetwork::I_Am_Server());

	//
	//	Terminate the win screen dialog
	//
	CNCWinScreenMenuClass::Close_Dialog ();

   //
   // Destroy any commando's, let them be recreated afresh
   //
   for (
		SLNode<cPlayer> * objnode = cPlayerManager::Get_Player_Object_List()->Head();
		objnode; objnode =
		objnode->Next()) {

		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
      Delete_Player_Objects(p_player->Get_Id());

		if (p_player->Is_Human()) {
			//
			// Set their ingame flag to false.
			// They will each notify us when they have finished loading.
			//
			p_player->Set_Is_In_Game(false);
			if (cNetwork::PServerConnection) {
				cNetwork::PServerConnection->Set_Rhost_Is_In_Game(p_player->Get_Id(), false);
			}

		}
   }

	if (cGameData::Is_Manual_Exit()) {
		GameInitMgrClass::Set_Needs_Game_Exit_All(true);
	} else {

		//
		// If we have to quit out now then don't do a core restart.
		//
		if (The_Game () != NULL && The_Game ()->Is_Map_Cycle_Over () == false) {
			extern bool g_b_core_restart;
			g_b_core_restart = true;
			GameModeClass *game_mode = GameModeManager::Find("WOL");
			if (game_mode && game_mode->Is_Active()) {
				WolGameModeClass* wol_game = (WolGameModeClass*) game_mode;
				if (wol_game->Post_Game_Check()) {
					g_b_core_restart = false;
				}
			}
		} else {
			GameInitMgrClass::Set_Needs_Game_Exit (true);
		}
	}

	//
	// TSS092601
	//
	WWASSERT(PTheGameData != NULL);
	The_Game()->IsIntermission.Set(false);
}

//-----------------------------------------------------------------------------
void cNetwork::End_Game_Test(void)
{
#ifndef BETACLIENT

   WWASSERT(cNetwork::I_Am_Server());

	if (IS_MISSION || !GameModeManager::Find("Combat")->Is_Active()) {
		return;
	}

	WWASSERT(PTheGameData != NULL);
	if (The_Game()->IsIntermission.Is_True()) {

      if (The_Game()->Get_Intermission_Time_Remaining() < WWMATH_EPSILON) {
         Intermission_Over_Processing();
      }

   } else {

		//
		// Sort teams and players once a second
		//
		bool is_game_over = The_Game()->Is_Game_Over();

		bool sort=is_game_over;
		int seconds=cMathUtil::Round(TimeManager::Get_Seconds());
		if (!sort) {
			if (seconds!=LastSortedSecond) {
				LastSortedSecond=seconds;
				sort=true;
			}
		}

		if (sort) {
			cTeamManager::Sort_Teams();
			cPlayerManager::Sort_Players(true);
		}

		if (is_game_over) {
			The_Game()->Game_Over_Processing();
		}
	}

#endif // not BETACLIENT
}

//-----------------------------------------------------------------------------
void cNetwork::Shared_Client_And_Server_Think(void)
{
	WWASSERT(I_Am_Client() || I_Am_Server());

	WWASSERT(PTheGameData != NULL);
	The_Game()->Think();

	Hibernation_Think();
}

//-----------------------------------------------------------------------------
bool cNetwork::Client_Think(void)
{
	bool ret_code = false;

#ifndef FREEDEDICATEDSERVER

	if (PClientConnection->Is_Destroy()) {
		Cleanup_Client();
		return(ret_code);
	}

   if (!PClientConnection->Have_Id()) {
		return(ret_code);
	}

	//
	// Client reload if necessary
	//
	WWASSERT(PTheGameData != NULL);
	if (I_Am_Only_Client() &&
		 The_Game()->IsIntermission.Is_True() &&
		 The_Game()->Get_Intermission_Time_Remaining() < WWMATH_EPSILON)
	{
		//
		//	Terminate the win screen dialog
		//
		CNCWinScreenMenuClass::Close_Dialog ();

		//
		// See if we have the next map in the cycle.
		//
		bool quit = false;
		if (The_Game()->Is_Map_Cycle_Over()) {
			quit = true;
		} else {
			if (!The_Game()->Is_Map_Valid()) {
				quit = true;
			}
		}

		if (quit) {

			//
			//	If the map looping is over, then quit the game
			//
			GameInitMgrClass::Set_Needs_Game_Exit (true);

		} else {
			extern bool g_b_core_restart;
			g_b_core_restart = true;
		}
	}

	cClientPingManager::Think();

	cClientHintManager::Think();

	WWASSERT(Receiver!= NULL);
	ret_code = Receiver->Client_Update_Dynamic_Objects();

	//
	// Pop up the team change dialog if it is appropriate and we have not already
	// done so.
	//
	if (!HaveDoneTeamChangeDialog && cChangeTeamEvent::Is_Change_Team_Possible()
			&& DlgMsgBox::Get_Current_Count() == 0) {

		HaveDoneTeamChangeDialog = true;
		DlgMPTeamSelect::DoDialog(*The_Game());
	}

	//
	// Pop up the MOTD dialog if appropriate.
	//
	if (!HaveDoneMotdDialog) {
		if (I_Am_Only_Client()) {
			if ((DlgMsgBox::Get_Current_Count() == 0)
					&& GameModeManager::Find("Combat") != NULL && GameModeManager::Find("Combat")->Is_Active()
					&& The_Game() != NULL && ::wcslen(The_Game()->Get_Motd()) > 0) {

				DlgMsgBox::DoDialog(TRANSLATE (IDS_MENU_MOTD), The_Game()->Get_Motd());
				HaveDoneMotdDialog = true;
			}
		} else {
			HaveDoneMotdDialog = true;
		}
	}

#endif // !FREEDEDICATEDSERVER

	return(ret_code);
}

//-----------------------------------------------------------------------------
VisTableClass * cNetwork::Peek_Temp_Vis_Table(void)
{
	bool alloc_new_table = false;

	if (VisTable == NULL) {
		alloc_new_table = true;
	} else if (VisTable->Get_Bit_Count() != COMBAT_SCENE->Get_Vis_Table_Size()) {
		alloc_new_table = true;
	}

	if (alloc_new_table) {
		REF_PTR_RELEASE(VisTable);
		VisTable = new VisTableClass(COMBAT_SCENE->Get_Vis_Table_Size(), 0);
	}

	WWASSERT(VisTable != NULL);
	VisTable->Reset_All();
	return VisTable;
}

//-----------------------------------------------------------------------------
void cNetwork::Hibernation_Think(void)
{
	if (COMBAT_SCENE != NULL) {

		//
		// The server can't let any player go into hibernation or updates will cease to be sent for that player.
		//
		if (I_Am_Server() && !IS_MISSION) {
			for (
				SLNode<BaseGameObj> * p_objnode = GameObjManager::Get_Game_Obj_List()->Head();
				p_objnode;
				p_objnode = p_objnode->Next()) {

				PhysicalGameObj * p_phys_go = p_objnode->Data()->As_PhysicalGameObj();

				if (p_phys_go != NULL) {
					p_phys_go->Reset_Hibernating();
				}
			}
		} else {


			VisTableClass * p_vis_table = Peek_Temp_Vis_Table();
			WWASSERT(p_vis_table != NULL);

			//
			// Build a union of all players' PVS's
			//
			for (
				SLNode<SmartGameObj> * p_smart_objnode = GameObjManager::Get_Smart_Game_Obj_List()->Head();
				p_smart_objnode;
				p_smart_objnode = p_smart_objnode->Next()) {

				WWASSERT(p_smart_objnode->Data() != NULL);
				SoldierGameObj * p_soldier = p_smart_objnode->Data()->As_SoldierGameObj();

				if (p_soldier != NULL && p_soldier->Has_Player()) {

					Vector3 player_pos;
					p_soldier->Get_Position(&player_pos);
					player_pos.Z += 2; // Start near the player's head

					VisTableClass * p_player_pvs = COMBAT_SCENE->Get_Vis_Table(player_pos);

					if (p_player_pvs == NULL) {
						p_vis_table = NULL;
						break;
					} else {
						p_vis_table->Merge(*p_player_pvs);
						REF_PTR_RELEASE(p_player_pvs);
					}
				}
			}

			//
			// Reset hibernating on anything visible
			//
			Vector3	star_pos(0,0,0);
			if ( COMBAT_STAR ) {
				COMBAT_STAR->Get_Position( &star_pos );
			}

			for (
				SLNode<BaseGameObj> * p_objnode = GameObjManager::Get_Game_Obj_List()->Head();
				p_objnode;
				p_objnode = p_objnode->Next()) {

				PhysicalGameObj * p_phys_go = p_objnode->Data()->As_PhysicalGameObj();
				if (p_phys_go != NULL) {
					PhysClass * p_phys_obj = p_phys_go->Peek_Physical_Object();
					if (p_phys_obj != NULL &&
						(p_vis_table == NULL || p_vis_table->Get_Bit(p_phys_obj->Get_Vis_Object_ID()))) {

						// Only if within 300m of the star;
						#define		MIN_HIB_DISTANCE		300
						Vector3 pos;
						p_phys_go->Get_Position( &pos );
						pos -= star_pos;
						if ( pos.Length2() < MIN_HIB_DISTANCE * MIN_HIB_DISTANCE ) {
							p_phys_go->Reset_Hibernating();
						}
					}
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
bool cNetwork::Server_Think(void)
{
	WWASSERT(I_Am_Server());

	bool ret_code = false;
	{
		WWPROFILE( "God Think" );
		if (GameModeManager::Find( "Combat" )->Is_Active()) {
			cGod::Think();
		}
	}

	{
		WWPROFILE( "End_Game_Test" );
	   End_Game_Test();
	}

   //
	// Update clients about dynamic & static objects
	//
	WWASSERT(Receiver!= NULL);

	//
	// TSS121101
	// The following I_Am_Server test seems to be necessary. In v 85
	// we were getting occasional I_Am_Server asserts on the server
	// inside Server_Update_Dynamic_Objects.
	//
	if (I_Am_Server())
	{
		{
			WWPROFILE( "svrupd dyn" );
			ret_code = Receiver->Server_Update_Dynamic_Objects();
		}

		{
			WWPROFILE( "svrupd del" );
			Receiver->Server_Send_Delete_Notifications();
		}
	}

	return(ret_code);
}

//-----------------------------------------------------------------------------
void cNetwork::Server_Kill_Connection( int client_id )
{
	WWASSERT(client_id >= 0);
	WWASSERT(I_Am_Server());
   PServerConnection->Destroy_Connection( client_id );

   WWDEBUG_SAY(("cNetwork::Server_Kill_Connection for client %d\n", client_id));
}

//-----------------------------------------------------------------------------
void cNetwork::Delete_Player_Objects(int client_id)
{
	WWDEBUG_SAY(("cNetwork::Delete_Player_Objects %d\n", client_id));

   WWASSERT(I_Am_Server());

   //
   // Delete all objects controlled by this guy; inform all clients.
   //
	SLNode<BaseGameObj> * objnode;
	for (objnode = GameObjManager::Get_Game_Obj_List()->Head(); objnode; objnode = objnode->Next()) {
		WWASSERT(objnode->Data() != NULL);
      PhysicalGameObj * p_phys_obj = objnode->Data()->As_PhysicalGameObj();
		if (p_phys_obj != NULL) {

		   SmartGameObj * p_smart_obj = p_phys_obj->As_SmartGameObj();

         if (p_smart_obj != NULL &&
            (p_smart_obj->Get_Control_Owner() == client_id))
			{
				//
				//	Destroy the object
				//
				p_smart_obj->Set_Delete_Pending ();
			}
      }
   }
}

//-----------------------------------------------------------------------------
//
// Call this after a client leaves or breaks connection.
//
//void cNetwork::Cleanup_After_Client(int client_id, bool is_departure_graceful)
void cNetwork::Cleanup_After_Client(int client_id)
{
	WWDEBUG_SAY(("cNetwork::Cleanup_After_Client %d\n", client_id));

   WWASSERT(I_Am_Server());

   Remove_Player(client_id);

   Delete_Player_Objects(client_id);

	NetworkObjectMgrClass::Delete_Client_Objects(client_id);

	//
	// We need to special-case these new delete notifications or delete_pending
	// will clear them first.
	//
	WWASSERT(Receiver!= NULL);
	Receiver->Server_Send_Delete_Notifications();

	NetworkObjectMgrClass::Restore_Dirty_Bits(client_id);

	CCDKeyAuth::DisconnectUser(client_id);
}

//-----------------------------------------------------------------------------
void cNetwork::Remove_Player(int player_id)
{
	WWDEBUG_SAY(("cNetwork::Remove_Player %d\n", player_id));

	WWASSERT(I_Am_Server());

   cPlayer * p_player = cPlayerManager::Find_Player(player_id);

	//
	// We can't remove the player structure because we wish to retain
	// it for scoring etc
	//
	if (p_player != NULL) {
		p_player->Increment_Total_Time();
		p_player->Set_Is_Active(false);

		StringClass str(128, true);
		p_player->Get_Name().Convert_To(str);
		ConsoleBox.Print_Maybe("Player %s left the game\n", str.Peek_Buffer());
	}

	if (p_player != NULL && IS_MULTIPLAY) {
		Test_For_Team_Defaulting(p_player);
	}
}

//-----------------------------------------------------------------------------
void cNetwork::Test_For_Team_Defaulting(cPlayer * p_player)
{
	//
	// If all players on the winning team bail, take punitive action:
	// reverse the team scores of Nod and GDI.
	//

	WWDEBUG_SAY(("cNetwork::Test_For_Team_Defaulting\n"));

	WWASSERT(p_player != NULL);
	WWASSERT(IS_MULTIPLAY);

	int count_nod = cPlayerManager::Tally_Team_Size(PLAYERTYPE_NOD);
	int count_gdi = cPlayerManager::Tally_Team_Size(PLAYERTYPE_GDI);

	cTeam * p_team_nod = cTeamManager::Find_Team(PLAYERTYPE_NOD);
	WWASSERT(p_team_nod != NULL);
	cTeam * p_team_gdi = cTeamManager::Find_Team(PLAYERTYPE_GDI);
	WWASSERT(p_team_gdi != NULL);

	float score_nod = p_team_nod->Get_Score();
	float score_gdi = p_team_gdi->Get_Score();

	if (p_player->Get_Player_Type() == PLAYERTYPE_NOD &&
		 count_nod == 0 &&
		 score_nod > score_gdi) {

		WWDEBUG_SAY(("Reversing Nod and GDI scores due to Nod defaulting.\n"));

		p_team_gdi->Set_Score(score_nod);
		p_team_nod->Set_Score(score_gdi);
	}
	else if (p_player->Get_Player_Type() == PLAYERTYPE_GDI &&
		 count_gdi == 0 &&
		 score_gdi > score_nod) {

		WWDEBUG_SAY(("Reversing Nod and GDI scores due to GDI defaulting.\n"));

		p_team_gdi->Set_Score(score_nod);
		p_team_nod->Set_Score(score_gdi);
	}
}