/*
**	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/playermanager.cpp                  $*
 *                                                                                             *
 *                      $Author:: Denzil_l                                                    $*
 *                                                                                             *
 *                     $Modtime:: 2/27/02 6:08p                                               $*
 *                                                                                             *
 *                    $Revision:: 128                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "playermanager.h"

#include <win.h>
#include <stdio.h>
#include <float.h>

#include "teammanager.h"
#include "miscutil.h"
#include "_globals.h"
#include "assets.h"
#include "cnetwork.h"
#include "multihud.h"
#include "textdisplay.h"
#include "font3d.h"
#include "gamedata.h"
#include "wwdebug.h"
#include "chunkio.h"
#include "useroptions.h"
#include "smartgameobj.h"
#include "playertype.h"
#include "devoptions.h"
#include "render2d.h"
#include "wwprofile.h"
#include "gametype.h"
#include "translatedb.h"
#include "string_ids.h"
#include "systeminfolog.h"
#include "consolemode.h"
#include "gamespyadmin.h"
#include "demosupport.h"

//
// Class statics
//
SList<cPlayer>				cPlayerManager::PlayerList;
cPlayer *					cPlayerManager::Player_Array[];
Render2DTextClass	*		cPlayerManager::PTextRenderer				= NULL;
Font3DInstanceClass *	cPlayerManager::PFont						= NULL;
const float					cPlayerManager::Y_INCREMENT_FACTOR		= 1.2f;
int							cPlayerManager::XPos							= 0;
int							cPlayerManager::YPos							= 0;
Notifier<PlayerMgrEvent> cPlayerManager::mNotifier;

//------------------------------------------------------------------------------------
void cPlayerManager::Onetime_Init(void)
{
	WWDEBUG_SAY(("cPlayerManager::Onetime_Init\n"));
	if (!ConsoleBox.Is_Exclusive()) {
		WWASSERT(PFont == NULL);
		WWASSERT(WW3DAssetManager::Get_Instance() != NULL);
   	PFont = WW3DAssetManager::Get_Instance()->Get_Font3DInstance("FONT6x8.TGA");
   	WWASSERT(PFont != NULL);
		PFont->Set_Mono_Spaced();
		SET_REF_OWNER(PFont);

		WWASSERT(PTextRenderer == NULL);
		PTextRenderer = new Render2DTextClass(PFont);
   	WWASSERT(PTextRenderer != NULL);
		PTextRenderer->Set_Coordinate_Range(Render2DClass::Get_Screen_Resolution());
	}
   ZeroMemory(Player_Array, sizeof(Player_Array));
}

//------------------------------------------------------------------------------------
void cPlayerManager::Onetime_Shutdown(void)
{
	WWDEBUG_SAY(("cPlayerManager::Onetime_Shutdown\n"));

	if (!ConsoleBox.Is_Exclusive()) {
		WWASSERT(PTextRenderer != NULL);
		delete PTextRenderer;
		PTextRenderer = NULL;

		WWASSERT(PFont != NULL);
		PFont->Release_Ref();
		PFont = NULL;
	}
}

//------------------------------------------------------------------------------------
void cPlayerManager::Think(void)
{
	WWPROFILE("cPlayerManager::Think");

	if (MultiHUDClass::Is_On()) {
		WWASSERT(PTextRenderer != NULL);
		Render_Player_List();
	}
	else {
		if (PTextRenderer) PTextRenderer->Reset();
	}
}

//-----------------------------------------------------------------------------------
void cPlayerManager::Render(void)
{
	WWPROFILE("cPlayerManager::Render");
	if (PTextRenderer != NULL) {
		WWASSERT(PTextRenderer != NULL);
		PTextRenderer->Render();
	}
}

//------------------------------------------------------------------------------------
cPlayer * cPlayerManager::Find_Player(int id)
{
   SLNode<cPlayer> * objnode;
	for (objnode = PlayerList.Head(); objnode; objnode = objnode->Next()) {
		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
		if (p_player->Get_Is_Active().Is_True() &&
			 p_player->Get_Id() == id) {
			return p_player; // found it
		}
	}

	return NULL; // Not found
}

//------------------------------------------------------------------------------------
cPlayer * cPlayerManager::Find_Player(const WideStringClass & name)
{
   SLNode<cPlayer> * objnode;
	for (objnode = PlayerList.Head(); objnode; objnode = objnode->Next()) {
		cPlayer *p_player = objnode->Data();
      WWASSERT(p_player != NULL);

      if (p_player->Get_Is_Active().Is_True() &&
			 !name.Compare_No_Case(p_player->Get_Name())) {

			return p_player; // found it
		}
	}

	return NULL; // Not found
}

//------------------------------------------------------------------------------------
cPlayer * cPlayerManager::Find_Inactive_Player(const WideStringClass & name)
{
	cPlayer * p_result = NULL;

	for (
		SLNode<cPlayer> * objnode = PlayerList.Head();
		objnode != NULL;
		objnode = objnode->Next())
	{
		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);

      if (p_player->Get_Is_Active().Is_False() &&
			!p_player->Get_Name().Compare_No_Case(name)) {

			p_result = p_player;
			break;
		}
	}

	return p_result;
}

//------------------------------------------------------------------------------------
cPlayer * cPlayerManager::Find_Team_Player(int team_number)
{
	//WWASSERT(The_Game()->Is_Team_Game());
	WWASSERT(team_number >= 0 && team_number < MAX_TEAMS);

   SLNode<cPlayer> * objnode;
	for (objnode = PlayerList.Head(); objnode; objnode = objnode->Next()) {
		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
		if (p_player->Get_Is_Active().Is_True() &&
			 p_player->Get_Player_Type() == team_number) {
			return p_player; // found it
		}
	}

	return NULL; // Not found
}


//------------------------------------------------------------------------------------
cPlayer * cPlayerManager::Find_Random_Team_Player(int team_number)
{
	//WWASSERT(The_Game()->Is_Team_Game());
	WWASSERT(team_number >= 0 && team_number < MAX_TEAMS);

   int chosen_player = rand() % Tally_Team_Size(team_number);

	int count = 0;
	SLNode<cPlayer> * objnode;
	for (objnode = PlayerList.Head(); objnode; objnode = objnode->Next()) {
		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
		if (p_player->Get_Is_Active().Is_True() &&
			 p_player->Get_Player_Type() == team_number &&
			 count++ == chosen_player) {
			return p_player; // found it
		}
	}

	return NULL; // Not found
}

//------------------------------------------------------------------------------------
cPlayer * cPlayerManager::Find_Team_Mate(cPlayer * p_player1)
{
	WWASSERT(p_player1 != NULL);
	WWASSERT(p_player1->Get_Is_Active().Is_True());
	//WWASSERT(The_Game()->Is_Team_Game());

	int team_number = p_player1->Get_Player_Type();
	WWASSERT(team_number >= 0 && team_number < MAX_TEAMS);

   SLNode<cPlayer> * objnode;
	for (objnode = PlayerList.Head(); objnode; objnode = objnode->Next()) {
		cPlayer * p_player2 = objnode->Data();
      WWASSERT(p_player2 != NULL);
		if (p_player2->Get_Is_Active().Is_True() &&
			p_player2->Get_Id() != p_player1->Get_Id() &&
			p_player2->Get_Player_Type() == team_number) {
			return p_player2; // found it
		}
	}

	return NULL; // Not found
}

cPlayer* cPlayerManager::Find_Clan_Mate(cPlayer* player)
{
	if (player != NULL) {
		unsigned long clan = player->Get_WOL_ClanID();
		int playerID = player->Get_Id();

		SLNode<cPlayer>* node = PlayerList.Head();

		while (node) {
			cPlayer* mate = node->Data();
			WWASSERT(mate != NULL);

			if (playerID != mate->Get_Id()) {
				if (clan == mate->Get_WOL_ClanID()) {
					return mate;
				}
			}

			node = node->Next();
		}
	}

	return NULL;
}

//------------------------------------------------------------------------------------
bool cPlayerManager::Is_Player_Present(int id)
{
   return (Find_Player(id) != NULL);
}

//------------------------------------------------------------------------------------
bool cPlayerManager::Is_Player_Present(WideStringClass & name)
{
   return (Find_Player(name) != NULL);
}

//------------------------------------------------------------------------------------
const WideStringClass & cPlayerManager::Get_Player_Name(int id)
{
   cPlayer * p_player = Find_Player(id);
   WWASSERT(p_player != NULL);
   WWASSERT(p_player->Get_Is_Active().Is_True());
   return p_player->Get_Name();
}

//------------------------------------------------------------------------------------
void cPlayerManager::Add(cPlayer * p_player)
{
   WWASSERT(p_player != NULL);
   PlayerList.Add_Tail(p_player);

	PlayerMgrEvent event(PLAYER_ADDED, p_player);
	mNotifier.NotifyObservers(event);
}

//------------------------------------------------------------------------------------
void cPlayerManager::Remove(cPlayer * p_player)
{
	WWASSERT(p_player != NULL);
   PlayerList.Remove(p_player);

	PlayerMgrEvent event(PLAYER_REMOVED, p_player);
	mNotifier.NotifyObservers(event);
}

void cPlayerManager::Deactivated(cPlayer* p_player)
{
	PlayerMgrEvent event(PLAYER_DEACTIVATED, p_player);
	mNotifier.NotifyObservers(event);
}

void cPlayerManager::Activated(cPlayer* p_player)
{
	PlayerMgrEvent event(PLAYER_ACTIVATED, p_player);
	mNotifier.NotifyObservers(event);
}

//------------------------------------------------------------------------------------
int cPlayerManager::Get_Average_Ladder_Points(void)
{
	int numPlayers = 0;
	int totalPoints = 0;

	SList<cPlayer>* playerList = Get_Player_Object_List();
	SLNode<cPlayer>* playerNode = playerList->Head();

	while (playerNode) {
		cPlayer* player = playerNode->Data();

		if (player && player->Get_Is_Active().Is_True() && player->Is_Human()) {
			++numPlayers;
			totalPoints += player->Get_Ladder_Points();
		}

		playerNode = playerNode->Next();
	}

	if (numPlayers) {
		return totalPoints / numPlayers;
	}

	return 0;
}

//------------------------------------------------------------------------------------
unsigned short cPlayerManager::Get_Average_WOL_Points(void)
{
	unsigned long numPlayers = 0;
	unsigned long totalPoints = 0;

	SList<cPlayer>* playerList = Get_Player_Object_List();
	SLNode<cPlayer>* playerNode = playerList->Head();

	while (playerNode) {
		cPlayer* player = playerNode->Data();

		if (player && player->Get_Is_Active().Is_True() && player->Is_Human()) {
			++numPlayers;
			totalPoints += player->Get_WOL_Points();
		}

		playerNode = playerNode->Next();
	}

	if (numPlayers) {
		return (unsigned short)(totalPoints / numPlayers);
	}

	return 0;
}

//------------------------------------------------------------------------------------
int cPlayerManager::Get_Average_Games_Played(void)
{
	int numPlayers = 0;
	int totalPlayed = 0;

	SList<cPlayer>* playerList = Get_Player_Object_List();
	SLNode<cPlayer>* playerNode = playerList->Head();

	while (playerNode) {
		cPlayer* player = playerNode->Data();

		if (player && player->Get_Is_Active().Is_True() && player->Is_Human()) {
			++numPlayers;
			totalPlayed += player->Get_Num_Wol_Games();
		}

		playerNode = playerNode->Next();
	}

	if (numPlayers) {
		return (totalPlayed / numPlayers);
	}

	return 0;
}

//------------------------------------------------------------------------------------
int cPlayerManager::Get_Average_Ping(void)
{
	int numPlayers = 0;
	int totalPing = 0;

	SList<cPlayer>* playerList = Get_Player_Object_List();
	SLNode<cPlayer>* playerNode = playerList->Head();

	while (playerNode) {
		cPlayer* player = playerNode->Data();

		//if (player && player->Get_Is_Active().Is_True() && player->Is_Human()) {
		if (player && player->Get_Is_Active().Is_True() && player->Is_Human() && player->Get_Ping() >= 0) {
			++numPlayers;
			//totalPing += player->Get_Avg_Ping();
			totalPing += player->Get_Ping();
		}

		playerNode = playerNode->Next();
	}

	if (numPlayers) {
		return totalPing / numPlayers;
	}

	return 0;
}


//------------------------------------------------------------------------------------
int cPlayerManager::Get_Average_FPS(void)
{
	int numPlayers = 0;
	int totalFPS = 0;

	SList<cPlayer>* playerList = Get_Player_Object_List();
	SLNode<cPlayer>* playerNode = playerList->Head();

	while (playerNode) {
		cPlayer* player = playerNode->Data();

		if (player && player->Get_Is_Active().Is_True() && player->Is_Human()) {
			++numPlayers;
			totalFPS += player->Get_Fps();
		}

		playerNode = playerNode->Next();
	}

	if (numPlayers) {
		return totalFPS / numPlayers;
	}

	return 0;
}

//------------------------------------------------------------------------------------
static int Sum_Positions(int position)
{
	WWASSERT(position >= 1);

	int retval = 0;

	for (int i = 0; i < position; i++) {
		retval += i;
	}

	return retval;
}

//------------------------------------------------------------------------------------
void cPlayerManager::Compute_Ladder_Points(int winning_team)
{
	WWASSERT(cNetwork::I_Am_Server());

	WWASSERT(PTheGameData != NULL);
	if (The_Game()->IsLaddered.Is_False())	{
		return;
	}

	cPlayerManager::Sort_Players(false);

	DWORD min_qualifying_time_ms = The_Game()->Get_Min_Qualifying_Time_Minutes() * 60 * 1000;

	int i = 0;

	//
	// Count qualifying winners and losers
	//
	int num_winners = 0;
	int num_losers = 0;
   for (i = 0; i < MAX_PLAYERS; i++) {

		if (Player_Array[i] == NULL ||
			 Player_Array[i]->Get_Total_Time() < min_qualifying_time_ms) {
			continue;
		}

		if (Player_Array[i]->Get_Player_Type() == winning_team) {
			num_winners++;
		} else {
			num_losers++;
		}
	}

	int win_pos = num_winners;
	int lose_pos = 1;

   for (i = 0; i < MAX_PLAYERS; i++) {

		if (Player_Array[i] == NULL ||
			 Player_Array[i]->Get_Total_Time() < min_qualifying_time_ms) {
			continue;
		}

		if (Player_Array[i]->Get_Player_Type() == winning_team) {
			Player_Array[i]->Set_Ladder_Points(Sum_Positions(win_pos));
			win_pos--;
			WWASSERT(win_pos >= 0);
		} else {
			Player_Array[i]->Set_Ladder_Points(-Sum_Positions(lose_pos));
			lose_pos++;
		}
	}

	//
	// TSS121501
	// Additional step. Let's now post-multiply ladder points by the fraction of
	// gametime that each player was present.
	//
	float game_duration_s = The_Game()->Get_Game_Duration_S();
	//WWASSERT(game_duration_s > 0);
	if (game_duration_s == 0)
	{
		game_duration_s = 1;
	}
   for (i = 0; i < MAX_PLAYERS; i++) {

		if (Player_Array[i] == NULL ||
			 Player_Array[i]->Get_Total_Time() < min_qualifying_time_ms) {
			continue;
		}

		float player_duration_s = Player_Array[i]->Get_Total_Time() / 1000.0;
		float ratio_present = player_duration_s / game_duration_s;
		float ladder_points = ratio_present * Player_Array[i]->Get_Ladder_Points();
		/*
		WWDEBUG_SAY(("%5.2f * %-4d = %5.2f (%d)\n",
			ratio_present,
			Player_Array[i]->Get_Ladder_Points(),
			ladder_points,
			cMathUtil::Round(ladder_points)
			));
		*/
		Player_Array[i]->Set_Ladder_Points(cMathUtil::Round(ladder_points));
	}

	//
	// Do another sort so that the list is sorted on these newly calculated ladder points
	//
	cPlayerManager::Sort_Players(false);
}

//------------------------------------------------------------------------------------
void cPlayerManager::Increment_Player_Times(void)
{
	WWASSERT(cNetwork::I_Am_Server());

	//
	// Increment the participation time of all active players
	//

   for (int i = 0; i < MAX_PLAYERS; i++) {
      if (Player_Array[i] != NULL &&
			 Player_Array[i]->Is_Active()) {

			Player_Array[i]->Increment_Total_Time();
		}
	}
}

//------------------------------------------------------------------------------------
WideStringClass cPlayerManager::Determine_Mvp_Name(void)
{
	WWASSERT(cNetwork::I_Am_Server());

	WideStringClass mvp_name;

	WWASSERT(The_Game() != NULL);
	DWORD min_qualifying_time_ms = The_Game()->Get_Min_Qualifying_Time_Minutes() * 60 * 1000;

	//
	// Find the top active, time-qualifying player.
	// Player_Array has already been sorted on score.
	//
   for (int i = 0; i < MAX_PLAYERS; i++) {
      if (Player_Array[i] != NULL) {

			//Player_Array[i]->Increment_Total_Time();

			if (Player_Array[i]->Is_Active() &&
				 Player_Array[i]->Get_Total_Time() > min_qualifying_time_ms) {

				mvp_name = Player_Array[i]->Get_Name();
				break;
			}
		}
	}

	return mvp_name;
}

//------------------------------------------------------------------------------------
int cPlayerManager::Compute_Full_Player_List_Height(void)
{
	bool show_inactive = false;
#ifdef WWDEBUG
	if (cDevOptions::ShowInactivePlayers.Is_True()) {
		show_inactive = true;
	}
#endif // WWDEBUG

	int count = 0;
	if (show_inactive) {
		count = PlayerList.Get_Count();
	} else {
		count = Count();
	}

   WWASSERT(PFont != NULL);
	int height = (int)((count + 1) * PFont->Char_Height() * Y_INCREMENT_FACTOR);

	return height;
}

//------------------------------------------------------------------------------------
void cPlayerManager::Remove_Inactive(void)
{
	for (
		SLNode<cPlayer> * objnode = PlayerList.Head();
		objnode != NULL;)
	{
		cPlayer * p_player = objnode->Data();
		WWASSERT(p_player != NULL);
		objnode = objnode->Next();

		if (p_player->Get_Is_Active().Is_False())
		{
			PlayerList.Remove(p_player);
			delete p_player;
		}
	}
}

//------------------------------------------------------------------------------------
void cPlayerManager::Remove_All(void)
{
	WWDEBUG_SAY(("cPlayerManager::Remove_All\n"));

	for (SLNode<cPlayer> * objnode = PlayerList.Head(); objnode != NULL;) {

		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
		objnode = objnode->Next();
		PlayerList.Remove(p_player);
		delete p_player;
	}

   WWASSERT(PlayerList.Get_Count() == 0);

	//Remove_Inactive();
}

//-----------------------------------------------------------------------------
int cPlayerManager::Count(void)
{
	int count = 0;

   for (
		SLNode<cPlayer> * objnode = PlayerList.Head();
		objnode != NULL;
		objnode = objnode->Next()) {

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

		if (p_player->Get_Is_Active().Is_True())
		{
			count++;
		}
   }

	return count;
}

//-----------------------------------------------------------------------------
void cPlayerManager::Reset_Players(void)
{
   //
	// Call reset on active players.
	// Remove all inactive players.
	//

	Remove_Inactive();

   for (
		SLNode<cPlayer> * objnode = PlayerList.Head();
		objnode != NULL;
		objnode = objnode->Next()) {

		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
		p_player->Reset_Player();
   }
}

//-----------------------------------------------------------------------------
int cPlayerManager::Tally_Team_Size(int type)
{
	WWROOTPROFILE("Tally_Team_Size");
   //WWASSERT(team >= 0 && team < MAX_TEAMS);
   //WWASSERT(The_Game()->Is_Team_Game());

   int tally = 0;
   SLNode<cPlayer> * objnode;
	cPlayer * p_player = NULL;
   for (objnode = PlayerList.Head(); objnode != NULL; objnode = objnode->Next()) {
		p_player = objnode->Data();
      WWASSERT(p_player != NULL);
      if (p_player->Get_Is_Active().Is_True() &&
			 p_player->Get_Player_Type() == type) {
         tally++;
      }
   }
	PlayerInfoLog::Report_Tally_Size(type,tally);

   return tally;
}

//-----------------------------------------------------------------------------
bool cPlayerManager::Is_Kill_Treasonous(cPlayer * p_killer, cPlayer * p_victim)
{
   return (
		//The_Game()->Is_Team_Game() &&
		//The_Game()->Is_Team_Game() &&
		p_killer != NULL &&
      p_victim != NULL &&
		p_killer != p_victim &&
		p_killer->Get_Player_Type() == p_victim->Get_Player_Type());
}

//-----------------------------------------------------------------------------
void cPlayerManager::Sort_Players(bool fast_sort)
{
	WWPROFILE("cPlayerManager::Sort_Players");

   ZeroMemory(Player_Array, sizeof(Player_Array));

   //
   // Copy non-spectators from SList into array usable by qsort
   //
   int num_players = 0;
	//int active_players = 0;
   cPlayer * p_player;
   SLNode<cPlayer> * objnode;
   for (objnode = Get_Player_Object_List()->Head(); objnode; objnode = objnode->Next()) {
		p_player = objnode->Data();
      WWASSERT(p_player != NULL);

		//if (p_player->Is_Living()) {
			//active_players++;
		//}

      WWASSERT(num_players < MAX_PLAYERS);
      Player_Array[num_players] = p_player;
      num_players++;
   }

	if (fast_sort) {

		//
		// If we're sorting the list in-game, use the fast sort
		//
		for (int i=0; i<num_players; i++) {
			Player_Array[i]->Set_Fast_Sort_Key(Compute_Fast_Sort_Key(Player_Array[i]));
		}

		qsort(&Player_Array[0], num_players, sizeof(cPlayer *), &Fast_Player_Compare);

	} else {

		//
		// Otherwise use the full-blown Player_Compare sort
		//
		qsort(&Player_Array[0], num_players, sizeof(cPlayer *), &Player_Compare);
	}

	//
	//	Set rungs. Only increment for active players.
	//
	/*
	for (int index = 0; index < num_players; index ++) {
		Player_Array[index]->Set_Rung (index + 1);
	}
	*/
	int rung = 1;
	for (int index = 0; index < num_players; index ++) {
		Player_Array[index]->Set_Rung(rung);
		if (Player_Array[index]->IsActive.Is_True()) {
			rung++;
		}
	}
}

//-----------------------------------------------------------------------------
int cPlayerManager::Player_Compare(const void * elem1, const void * elem2)
{
   //
   // Used by qsort
	// (gth) Note, this function is very expensive due to the number of
	// data-safe variables being accessed.
   //

   WWASSERT(elem1 != NULL);
   WWASSERT(elem2 != NULL);

   cPlayer * p_player1 = *((cPlayer **)elem1);
   cPlayer * p_player2 = *((cPlayer **)elem2);
   WWASSERT(p_player1 != NULL);
   WWASSERT(p_player2 != NULL);

   int result;

	int p1_type = p_player1->Get_Player_Type();
	if (p1_type > PLAYERTYPE_NOD) {
		p1_type = PLAYERTYPE_NOD;
	}
	int p2_type = p_player2->Get_Player_Type();
	if (p2_type > PLAYERTYPE_NOD) {
		p2_type = PLAYERTYPE_NOD;
	}

	//
	// Sort on Ladder Points
	//
	if (p_player1->Get_Ladder_Points() > p_player2->Get_Ladder_Points()) {
		result = -1;
	} else if (p_player1->Get_Ladder_Points() == p_player2->Get_Ladder_Points()) {
		//
		// Sort on Score
		//
		if (p_player1->Get_Score() > p_player2->Get_Score()) {
			result = -1;
		} else if (p_player1->Get_Score() == p_player2->Get_Score()) {
			//
			// Sort on KTD ratio
			//
			//if (p_player1->Get_Kill_To_Death_Ratio() > p_player2->Get_Kill_To_Death_Ratio()) {
			//	result = -1;
			//} else if (p_player1->Get_Kill_To_Death_Ratio() == p_player2->Get_Kill_To_Death_Ratio()) {
				//
				// Sort on Kills
				//
				if (p_player1->Get_Kills() > p_player2->Get_Kills()) {
					result = -1;
				} else if (p_player1->Get_Kills() == p_player2->Get_Kills()) {
					//
					// Sort on Deaths (less is better)
					//
					if (p_player1->Get_Deaths() < p_player2->Get_Deaths()) {
						result = -1;
					} else if (p_player1->Get_Deaths() == p_player2->Get_Deaths()) {
						//
						// Sort on team number. For non-teammembers this is a
						// redundant but harmless comparison
						//
						if (p_player1->Get_Player_Type() < p_player2->Get_Player_Type()) {
							result = -1;
						} else if (p_player1->Get_Player_Type() == p_player2->Get_Player_Type()) {
							//
							// Sort lexicographically on name
							//
							//if (stricmp(p_player1->Get_Name(), p_player2->Get_Name()) < 0) {
							if (p_player1->Get_Name() < p_player2->Get_Name()) {
								result = -1;
							//} else if (stricmp(p_player1->Get_Name(), p_player2->Get_Name()) == 0) {
							} else if (p_player1->Get_Name() == p_player2->Get_Name()) {
								//
								// Enough!
								//
								result = 0;
							} else {
								result = 1;
							}
						} else {
							result = 1;
						}
					} else {
						result = 1;
					}
				} else {
					result = 1;
				}
			//} else {
			//	result = 1;
			//}
		} else {
			result = 1;
		}
	} else {
		result = 1;
	}

   return result;
}


//-----------------------------------------------------------------------------
int cPlayerManager::Fast_Player_Compare(const void * elem1, const void * elem2)
{
   //
   // Used by qsort
   //
   WWASSERT(elem1 != NULL);
   WWASSERT(elem2 != NULL);

   cPlayer * p_player1 = *((cPlayer **)elem1);
   cPlayer * p_player2 = *((cPlayer **)elem2);

   WWASSERT(p_player1 != NULL);
   WWASSERT(p_player2 != NULL);

	if (p_player1->Get_Fast_Sort_Key() > p_player2->Get_Fast_Sort_Key()) {
		return -1;
	} else {
		return 1;
	}
}

//-----------------------------------------------------------------------------
int cPlayerManager::Compute_Fast_Sort_Key(cPlayer * player)
{
	int key = 0;
	key = player->Get_Score();
	return key;
}

//-----------------------------------------------------------------------------
void cPlayerManager::Construct_Heading(WideStringClass & string, bool force_verbose)
{
	string.Format(L"");

	WWASSERT(PTheGameData != NULL);
	//bool is_verbose = force_verbose || The_Game()->IsIntermission.Get() || MultiHUDClass::Get_Verbose_Lists();
	bool is_verbose = force_verbose ||
		               The_Game()->IsIntermission.Is_True() ||
							//MultiHUDClass::Get_Verbose_Lists();
							(MultiHUDClass::Get_Playerlist_Format() == PLAYERLIST_FORMAT_FULL);

	WideStringClass substring(0,true);

	//
	// Standing
	//
	substring.Format(L"%-5s", L"");
   string += substring;

	//
	// Name
	// 
	//GAMESPY
	//substring.Format(L"%-11s", TRANSLATION(IDS_MP_PLAYER));
	if (cGameSpyAdmin::Is_Gamespy_Game()) {
		substring.Format(L"%-36s", TRANSLATION(IDS_MP_PLAYER));
	} else {
		substring.Format(L"%-11s", TRANSLATION(IDS_MP_PLAYER));
	}
   string += substring;

	//
	// Kills
	//
   if (is_verbose) {
		substring.Format(L"%-8s", TRANSLATION(IDS_MP_KILLS));
	   string += substring;
   }

	//
	// Deaths
	//
   if (is_verbose) {
		substring.Format(L"%-8s", TRANSLATION(IDS_MP_DEATHS));
	   string += substring;
   }

	//
	// Kill to Death ratio
	//
   if (is_verbose) {
		substring.Format(L"%-8s", TRANSLATION(IDS_MP_KILL_TO_DEATH_RATIO));
	   string += substring;
   }

	//
	// Money
	//
   if ((The_Game()->Is_Cnc() || The_Game()->Is_Skirmish()) && is_verbose) {
		substring.Format(L"%-8s", TRANSLATION(IDS_MP_MONEY));
	   string += substring;
   }

	//
	// Score
	//
	substring.Format(L"%-8s", TRANSLATION(IDS_MP_SCORE));
	string += substring;

	//
	// Ladder Points
	//
   if (force_verbose && The_Game()->IsLaddered.Is_True()) {
		substring.Format(L"%-8s", TRANSLATION(IDS_MP_LADDER));
	   string += substring;
	}

   //
	// WOL rank
	//
   if (GameModeManager::Find("WOL")->Is_Active() && is_verbose) {
		substring.Format(L"%-8s", TRANSLATION(IDS_MP_RANK));
	   string += substring;
   }

#ifdef WWDEBUG
   //
   // Ping
   //
   if (cDevOptions::ShowPing.Is_True()) {
		substring.Format(L"%-8s", L"Ping");
	   string += substring;
   }

   //
   // Player Id
   //
   if (cDevOptions::ShowId.Is_True()) {
		substring.Format(L"%-8s", L"Id");
	   string += substring;
   }

   //
   // Fps
   //
   if (cNetwork::I_Am_Server() && cDevOptions::ShowClientFps.Is_True()) {
		substring.Format(L"%-8s", L"Fps");
	   string += substring;
   }

	//
	// GameSpy auth. state
	//
   if (cNetwork::I_Am_Server() && cGameSpyAdmin::Is_Gamespy_Game() && 
		cDevOptions::ShowGameSpyAuthState.Is_True()) {
      substring.Format(L"%-12s", L"GS_AUTH");
      string += substring;
   }

	//
	// IP Address
	//
	if (cNetwork::I_Am_Server() && cDevOptions::ShowIpAddresses.Is_True()) {
		substring.Format(L"%-30s", L"IP Address");
	   string += substring;
	}
#endif // WWDEBUG
}

//-----------------------------------------------------------------------------
void cPlayerManager::List_Print(WideStringClass & text, Vector3 color)
{
   //WWASSERT(text != NULL);
   //WWASSERT(::strlen(text) > 0);

	if (PTextRenderer == NULL) {
		return;
	}

	WWASSERT(PTextRenderer != NULL);

	PTextRenderer->Set_Location(Vector2(cMathUtil::Round(XPos), cMathUtil::Round(YPos)));

	int c = ((int)(color[0]*255)&0xFF) << 16 | ((int)(color[1]*255)&0xFF) << 8 | ((int)(color[2]*255)&0xFF) << 0 | 0xFF000000;

	PTextRenderer->Draw_Text(text, c);

   WWASSERT(PFont != NULL);
   YPos += PFont->Char_Height() * Y_INCREMENT_FACTOR;
}

//-----------------------------------------------------------------------------
void cPlayerManager::Line(float x, float length, int line_color)
{
   WWASSERT(length > 0);

	if (PTextRenderer == NULL) {
		return;
	}

	WWASSERT(PTextRenderer != NULL);

	float y = YPos + PTextRenderer->Peek_Font()->Char_Height() / 2.0f;

	PTextRenderer->Draw_Block(RectClass(x, y, x + length, y + 1), line_color);
}

//-----------------------------------------------------------------------------
void cPlayerManager::Render_Player_List(void)
{
   //
   // Format:
   // AAAAAAAAAxxNNNNxxNNNxNNNxNNN.NxxNNNxxNNNNN, e.g:
   // Charlie    220   25  13    5.3    3     12
   //
   // Fields are name, ping, kills, death, kills-to-deaths, num-lives, score
   //

	if (PTextRenderer == NULL) {
		return;
	}

	WWASSERT(PTheGameData != NULL);
	if (GameModeManager::Find("Combat") == NULL ||
		!GameModeManager::Find("Combat")->Is_Active() ||
		The_Game()->IsIntermission.Is_True()) {

		PTextRenderer->Reset();
		return;
	}

	//bool is_verbose = The_Game()->IsIntermission.Get() || MultiHUDClass::Get_Verbose_Lists();
	bool is_verbose = The_Game()->IsIntermission.Get() || (MultiHUDClass::Get_Playerlist_Format() == PLAYERLIST_FORMAT_FULL);

	bool changed=false;

	static WideStringClass renderer_player_heading;
	static WideStringClass renderer_player_strings[MAX_PLAYERS];
	static Vector3 renderer_player_colors[MAX_PLAYERS];
	static int renderer_player_count;
	static int renderer_displayed_player_count;
	static int renderer_star_index = -1;
	static float renderer_star_x;
	static float renderer_star_y;
	static bool renderer_is_intermission_true;
	static int LastSortedSecond = 0;

   //
   // Sort the list of players once a second
   //

	bool sort=is_verbose;
	//bool sort = false;//XXX
	int seconds = cMathUtil::Round(TimeManager::Get_Seconds());
	if (!sort) {
		if (seconds != LastSortedSecond) {
			LastSortedSecond = seconds;
			sort=true;
		}
	}
	sort=true;//XXX

	if (sort) {
		WWPROFILE("Team & Player sorts");
		cTeamManager::Sort_Teams();
		cPlayerManager::Sort_Players(true);
	}

   //
   // Build heading
   //
	WideStringClass heading(0,true);
	Construct_Heading(heading);
	if (heading!=renderer_player_heading) {
		renderer_player_heading=heading;
		changed=true;
	}

	if (The_Game()->IsIntermission.Is_True()!=renderer_is_intermission_true) {
		changed=true;
		renderer_is_intermission_true=The_Game()->IsIntermission.Is_True();
	}

	// Build player list

	//int current_count = Count();
	int current_count = PlayerList.Get_Count();

	if (renderer_player_count!= current_count) {
		renderer_player_count = current_count;
		changed=true;
	}

	LPCSTR star_text = "*";

	int displayed_player_count=0;
	int count = current_count;

	bool show_inactive = false;
#ifdef WWDEBUG
	if (cDevOptions::ShowInactivePlayers.Is_True()) {
		show_inactive = true;
	}
#endif // WWDEBUG

	for (int j = 0; j < count; j++) {
      WWASSERT(j < MAX_PLAYERS);
	   cPlayer * p_player = Player_Array[j];
		if (!p_player) continue;

		//
		// In the abbreviated form we only show the score leader and ourself.
		// 09/26/01 And the guy at the bottom of the list !
		// 10/29/01 Now we just show me.
		//
		bool is_me = cNetwork::I_Am_Client() && (p_player->Get_Id() == cNetwork::Get_My_Id());
		//if (!is_verbose && (j != 0) && (j != count - 1) && !is_me) continue;
		//if (!is_verbose && !is_me) {
		if ((MultiHUDClass::Get_Playerlist_Format() == PLAYERLIST_FORMAT_TINY) && !is_me) {
			continue;
		}

		if (p_player->Get_Is_Active().Is_False() && !show_inactive) {
			continue;
		}

      if (changed) {
	      p_player->Get_Player_String(j + 1, renderer_player_strings[displayed_player_count]);
			renderer_player_colors[displayed_player_count]=p_player->Get_Color();
		}
		else {
			WideStringClass player_string(0,true);
			p_player->Get_Player_String(j + 1, player_string);
			if (player_string!=renderer_player_strings[displayed_player_count]) {
				renderer_player_strings[displayed_player_count]=player_string;
				changed=true;
			}
			// If list hasn't changed so far, check if the player color is the same
			if (renderer_player_colors[displayed_player_count]!=p_player->Get_Color()) {
				renderer_player_colors[displayed_player_count]=p_player->Get_Color();
				changed=true;
			}
		}

		if (cNetwork::I_Am_Client() && p_player->Get_Id() == cNetwork::Get_My_Id()) {
         //
         // Put a symbol next to my name so that it stands out
         //
			if (renderer_star_index!=displayed_player_count) {
				renderer_star_index=displayed_player_count;
				changed=true;
			}
      }

		displayed_player_count++;
   }

	if (renderer_displayed_player_count!=displayed_player_count) {
		renderer_displayed_player_count=displayed_player_count;
		changed=true;
	}

	if (!changed) return;

	PTextRenderer->Reset();


   WWASSERT(PFont != NULL);
	WWASSERT(PTextRenderer != NULL);

	DEMO_SECURITY_CHECK;

   //
   // Determine left edge placement
   //
	XPos = 0;
	float text_len = PFont->String_Width(heading);
	if (The_Game()->IsIntermission.Is_True()) {
		XPos = Render2DClass::Get_Screen_Resolution().Center().X - text_len / 2.0f;
	} else {
		XPos = Render2DClass::Get_Screen_Resolution().Right - 20 - text_len;
	}

	//
	// Top placement
	//
	if (The_Game()->IsIntermission.Is_True()) {
		//YPos = 70;

		int combined_height =
			cTeamManager::Compute_Team_List_Height() +
			cPlayerManager::Compute_Full_Player_List_Height() +
			2 * PFont->Char_Height() * Y_INCREMENT_FACTOR;

		YPos = (int)(Render2DClass::Get_Screen_Resolution().Height() / 2.0 - combined_height / 2.0);
		if (YPos < 10) {
			YPos = 10;
		}

		/*
		if (The_Game()->Is_Team_Game()) {
			YPos += 2 * PFont->Char_Height() * Y_INCREMENT_FACTOR;
			YPos += cTeamManager::Compute_Team_List_Height();
		} else {
			//
			// Show the win text
			//
			WideStringClass text(cGameData::Get_Win_Text(),true);

			float x = Render2DClass::Get_Screen_Resolution().Center().X -
				PFont->String_Width(text) / 2.0f;
			float y = YPos;
			PTextRenderer->Set_Location(Vector2(cMathUtil::Round(x), cMathUtil::Round(y)));
			PTextRenderer->Draw_Text(text);

			YPos += 2 * PFont->Char_Height() * Y_INCREMENT_FACTOR;
		}
		*/

		YPos += 2 * PFont->Char_Height() * Y_INCREMENT_FACTOR;
		YPos += cTeamManager::Compute_Team_List_Height();

	} else {
		//YPos = 50;
		/*
		YPos = 10;
		if (The_Game()->Is_Team_Game())	{
			//YPos += 4 * PFont->Char_Height() * Y_INCREMENT_FACTOR;
			YPos += cTeamManager::Compute_Team_List_Height();
		}
		*/
		YPos = 10 + cTeamManager::Compute_Team_List_Height();
	}

	//
   // Draw the heading
   //
   /*
	if (The_Game()->Is_Team_Game()) {
      List_Print(heading, Vector3(1, 1, 1));
   } else {
      List_Print(heading, Vector3(1, 1, 0));
   }
	*/
   List_Print(heading, Vector3(1, 1, 1));

	for (j = 0; j < renderer_displayed_player_count; j++) {
		//
		// Put a symbol next to my name so that it stands out
		//
		//if (is_verbose && renderer_star_index==j) {
		if ((MultiHUDClass::Get_Playerlist_Format() != PLAYERLIST_FORMAT_TINY) &&
			 (renderer_star_index == j)) {
			float x = XPos - 2 * PFont->String_Width(star_text);
			float y = YPos;
			PTextRenderer->Set_Location(Vector2(cMathUtil::Round(x), cMathUtil::Round(y)));
			PTextRenderer->Draw_Text(star_text);
		}

		List_Print(renderer_player_strings[j], renderer_player_colors[j]);
	}
}

//-----------------------------------------------------------------------------
void cPlayerManager::Log_Player_List(void)
{
	WWDEBUG_SAY(("cPlayerManager::Log_Player_List\n"));

	StringClass results_filename;
	results_filename.Format("results%d.txt", cUserOptions::ResultsLogNumber.Get());
	FILE * file = ::fopen(results_filename, "at");

   if (file != NULL) {

		char line[2000] = "";

		WideStringClass wide_text(0, true);
		Construct_Heading(wide_text, true);

		StringClass text;
		wide_text.Convert_To(text);

		::sprintf(line, "%s\n", text.Peek_Buffer());
		::fwrite(line, 1, ::strlen(line), file);

		for (int j = 0; j < MAX_PLAYERS; j++) {
			cPlayer * p_player = Player_Array[j];
			if (p_player != NULL) {
				p_player->Get_Player_String(j + 1, wide_text, true);
				wide_text.Convert_To(text);
				::sprintf(line, "%s\n", text.Peek_Buffer());
				::fwrite(line, 1, ::strlen(line), file);
			}
		}

		::sprintf(line, "\n");
		::fwrite(line, 1, ::strlen(line), file);

		::fclose(file);
	}
}

//-----------------------------------------------------------------------------
enum {
	CHUNKID_PLAYERLIST = 1232345,
	CHUNKID_PLAYER,
};

//-----------------------------------------------------------------------------
bool cPlayerManager::Save(ChunkSaveClass & csave)
{
	csave.Begin_Chunk(CHUNKID_PLAYERLIST);

   SLNode<cPlayer> * objnode;
	for (objnode = PlayerList.Head(); objnode; objnode = objnode->Next()) {
		cPlayer * p_player = objnode->Data();
      WWASSERT(p_player != NULL);
		csave.Begin_Chunk(CHUNKID_PLAYER);
		p_player->Save(csave);
		csave.End_Chunk();
	}

	csave.End_Chunk();

	return true;
}

//-----------------------------------------------------------------------------
bool cPlayerManager::Load(ChunkLoadClass &cload)
{
	while (cload.Open_Chunk()) {
		switch(cload.Cur_Chunk_ID()) {

			case CHUNKID_PLAYERLIST:

				//Remove_All(); // TSS091401

				//TSS110101. WWASSERT(PlayerList.Get_Count() == 0);
				while (cload.Open_Chunk()) {
					switch(cload.Cur_Chunk_ID()) {

						case CHUNKID_PLAYER:
							cPlayer * p_player;
							p_player = new cPlayer();
							p_player->Load(cload);

							/*
							if (!The_Game()->Is_Valid_Player_Type(p_player->Get_Player_Type())) {
								Debug_Say(("* Removing loaded player due to invalid playertype for this game.\n"));
								Remove(p_player);
							}
							*/

							//
							// TSS110101.
							// Do not perturb the save/load code, but nullify it's effect.
							//
							if ( !IS_MISSION ) {
								Remove(p_player);
							}

							break;

						default:
							Debug_Say(( "Unrecognized cPlayerManager chunkID\n" ));
							break;

					}
					cload.Close_Chunk();
				}
				break;

			default:
				Debug_Say(( "Unrecognized cPlayerManager chunkID\n" ));
				break;

		}
		cload.Close_Chunk();
	}

	return true;
}

//-----------------------------------------------------------------------------



