/*
**	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/>.
*/

/******************************************************************************
*
* FILE
*     $Archive: /Commando/Code/Commando/WOLGameInfo.cpp $
*
* DESCRIPTION
*
* PROGRAMMER
*     $Author: Steve_t $
*
* VERSION INFO
*     $Revision: 18 $
*     $Modtime: 10/31/02 4:30p $
*
******************************************************************************/

#include "WOLGameInfo.h"
#include "GameData.h"
#include "CNetwork.h"
#include <WWOnline\WOLUser.h>
#include <WWOnline\WOLChannel.h>
#include <WWOnline\PingProfile.h>
#include <WWLib\RealCRC.h>
#include "modpackagemgr.h"

using namespace WWOnline;

/******************************************************************************
*
* NAME
*     WOLGameInfo::WOLGameInfo
*
* DESCRIPTION
*     Default constructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

WOLGameInfo::WOLGameInfo(void) :
		mIsDataValid(false),
		mIsMapValid(true),
		mPingTime(-1)
	{
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::WOLGameInfo
*
* DESCRIPTION
*     Import information about the game.
*
* INPUTS
*     Game - Game to import information from.
*
* RESULT
*     NONE
*
******************************************************************************/

WOLGameInfo::WOLGameInfo(const cGameData& theGame)
	{
	ImportFromGame(theGame);
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::WOLGameInfo
*
* DESCRIPTION
*     Import information about the game from a channel.
*
* INPUTS
*     Channel - Channel to import game information from.
*
* RESULT
*     NONE
*
******************************************************************************/

WOLGameInfo::WOLGameInfo(const RefPtr<ChannelData>& channel)
	{
	ImportFromChannel(channel);
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::~WOLGameInfo
*
* DESCRIPTION
*     Destructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

WOLGameInfo::~WOLGameInfo(void)
	{
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::Reset
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLGameInfo::Reset(void)
	{
	mIsDataValid = false;
	mIsMapValid = true;

	mVersion = 0;
	mGameType = 0;

	mMapName[0] = 0;
	mModName[0] = 0;
	mTitle[0] = 0;

	mMinPlayers = 0;
	mMaxPlayers = 0;
	mNumPlayers = 0;
		
	mClanID1 = 0;
	mClanID2 = 0;
		
	mIsMod = false;
	mIsLaddered = false;
	mIsPassworded = false;
	mIsQuickmatch = false;
	mIsDedicated = false;
	mIsFriendlyFire = false;
	mIsFreeWeapons = false;
	mIsTeamRemix = false;
	mIsTeamChange = false;
	mIsClanGame = false;
	mIsRepairBuildings = false;
	mIsDriverGunner = false;
	mSpawnWeapons = false;
		
	mPingTime = -1;
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::ImportFromGame
*
* DESCRIPTION
*     Import information about the game.
*
* INPUTS
*     Game - Game to import information from.
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLGameInfo::ImportFromGame(const cGameData& theGame)
	{
	Reset();

	mIsDataValid = true;
	mIsMapValid = true;

	mVersion  = theGame.Get_Version_Number();
	mGameType = theGame.Get_Game_Type();

	strncpy(mMapName, theGame.Get_Map_Name(), MAX_TEXT_LENGTH);
	mMapName[MAX_TEXT_LENGTH - 1] = 0;
	
	strncpy(mModName, theGame.Get_Mod_Name(), MAX_TEXT_LENGTH);
	mModName[MAX_TEXT_LENGTH - 1] = 0;

	wcstombs(mTitle, theGame.Get_Game_Title(), MAX_TEXT_LENGTH);
	mTitle[MAX_TEXT_LENGTH - 1] = 0;

	mMinPlayers = theGame.Get_Min_Players();
	mMaxPlayers = theGame.Get_Max_Players();
	mNumPlayers = theGame.Get_Current_Players();

	mClanID1 = theGame.Get_Clan(0);
	mClanID2 = theGame.Get_Clan(1);

	const WCHAR* password = theGame.Get_Password();
	mIsPassworded = ((password != NULL) && (wcslen(password) > 0));
	mIsLaddered   = theGame.IsLaddered.Get();
	mIsQuickmatch = theGame.Is_QuickMatch_Server();
	mIsDedicated  = theGame.IsDedicated.Get();

	mIsFriendlyFire = theGame.IsFriendlyFirePermitted.Get();
	mIsFreeWeapons  = theGame.IsFreeWeapons.Get();
	mIsTeamRemix    = theGame.RemixTeams.Get();
	mIsTeamChange   = theGame.IsTeamChangingAllowed.Get();
	mIsClanGame     = theGame.IsClanGame.Get();

	// C&C mode flags
	mIsRepairBuildings = theGame.CanRepairBuildings.Get();
	mIsDriverGunner    = theGame.DriverIsAlwaysGunner.Get();
	mSpawnWeapons      = theGame.SpawnWeapons.Get();

	mIsMod = (theGame.Get_Mod_Name().Is_Empty() == false);

	mPingTime = -1;
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::ImportFromChannel
*
* DESCRIPTION
*     Import information about the game from a channel.
*
* INPUTS
*     Channel - Channel to import game information from.
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLGameInfo::ImportFromChannel(const RefPtr<ChannelData>& channel)
	{
	Reset();

	if (channel.IsValid() == false)
		{
		mIsDataValid = false;
		return;
		}
					
	//ST - Test code
	//if (channel->GetName() != WideStringClass(L"ladtest07")) {
	//	mIsDataValid = false;
	//	return;
	//}
		

	const char* exInfo = channel->GetExtraInfo();

	if ((exInfo == NULL) || (exInfo && (strlen(exInfo) != 36)))
		{
		WWDEBUG_SAY(("WOLERROR: Channel ExInfo NULL or wrong size\n"));
		mIsDataValid = false;
		return;
		}

	// Assume the data will be valid
	mIsDataValid = true;

	// Extract ExInfo settings
	unsigned long fileCRC = 0;
	unsigned long version = 0;
	unsigned long clanID1 = 0;
	unsigned long clanID2 = 0;
	unsigned char gameType = 0;
	unsigned char gameFlags1 = 0;
	unsigned char gameFlags2 = 0;
	unsigned char modMapIndex = 0;

	int count = sscanf(exInfo, "%08lX%08lX%08lX%08lX%c%c%c%c", &version, &fileCRC,
			&clanID1, &clanID2, &gameType, &gameFlags1, &gameFlags2, &modMapIndex);

	// There should be 8 parameters in the exinfo
	if (count != 8)
		{
		WWDEBUG_SAY(("WOLERROR: Channel ExInfo malformed\n"));
		mIsDataValid = false;
		}

	if (version != cNetwork::Get_Exe_Key()) {
		mIsDataValid = false;
		return;
	}

	mVersion  = version;
	mGameType = (cGameData::GameTypeEnum)(gameType - '0');

	if (mGameType != cGameData::GAME_TYPE_CNC)
		{
		WWDEBUG_SAY(("WOLERROR: GameType '%d' out of range\n", mGameType));
		mIsDataValid = false;
		}

	// Extract game flags
	mIsLaddered   = (channel->GetTournament() != 0);
	mIsPassworded = channel->IsPassworded();

	mIsDedicated    = ((gameFlags1 & 0x40) == 0x40);
	mIsFriendlyFire = ((gameFlags1 & 0x10) == 0x10);
	mIsFreeWeapons  = ((gameFlags1 & 0x08) == 0x08);
	mIsTeamRemix    = ((gameFlags1 & 0x04) == 0x04);
	mIsTeamChange   = ((gameFlags1 & 0x02) == 0x02);
	mIsClanGame     = ((gameFlags1 & 0x01) == 0x01);

	mIsMod             = ((gameFlags2 & 0x40) == 0x40);
	mSpawnWeapons      = ((gameFlags2 & 0x04) == 0x04);
	mIsRepairBuildings = ((gameFlags2 & 0x02) == 0x02);
	mIsDriverGunner    = ((gameFlags2 & 0x01) == 0x01);
	
	// Find the mod and map names from their CRC
	StringClass mapName(0, true);
	mapName = "<Unknown>";

	StringClass modName(0, true);

	bool got_map_name = true;
	if (mIsMod)
		{
		// Get the true index (+32 put the index into the printable range)
		modMapIndex -= 32;

		// Find mod based on its crc and get map filename based on the index.
		if (ModPackageMgrClass::Get_Mod_Map_Name_From_CRC_Index(fileCRC, modMapIndex, &modName, &mapName) == false)
			{
			//mIsDataValid = false;
			got_map_name = false;
			}
		}
	else
		{
		// Find mod based on its crc and get map filename based on the index.
		if (ModPackageMgrClass::Get_Mod_Map_Name_From_CRC (0, fileCRC, &modName, &mapName) == false)
			{
			//mIsDataValid = false;
			got_map_name = false;
			}
		}

	strncpy(mModName, modName, MAX_TEXT_LENGTH);
	mModName[MAX_TEXT_LENGTH - 1] = 0;

	strncpy(mMapName, mapName, MAX_TEXT_LENGTH);
	mMapName[MAX_TEXT_LENGTH - 1] = 0;

	mMinPlayers = channel->GetMinUsers();
	mMaxPlayers = channel->GetMaxUsers();
	mNumPlayers = channel->GetCurrentUsers();

	// Do not count the host as a player for dedicated servers.
	if (mIsDedicated)
		{
		WWASSERT(mNumPlayers > 0);
		--mNumPlayers;

		WWASSERT(mMaxPlayers > 0);
		--mMaxPlayers;
		}

	mClanID1 = clanID1;
	mClanID2 = clanID2;

	const char* topic = channel->GetTopic();

	if (topic == NULL)
		{
		WWDEBUG_SAY(("WOLERROR: Channel Topic is NULL\n"));
		mIsDataValid = false;
		}

	// Quickmatch settings are marked by a pipe '|' character
	mIsQuickmatch = (strchr(topic, '|') != NULL);

	// Extract game title from topic
	unsigned int titleLength = (topic[0] - 0x20);
	WWASSERT(titleLength <= 32);
	titleLength = min<unsigned int>(titleLength, 32);
	++topic;

	strncpy(mTitle, topic, titleLength);
	mTitle[titleLength] = 0;
	WWASSERT(strlen(mTitle) == titleLength);
	topic += titleLength;

	unsigned int mapLength = (topic[0] - 0x20);
	WWASSERT(mapLength <= 16);
	mapLength = min<unsigned int>(mapLength, 16);
	++topic;

	if (!got_map_name) {
		strncpy(mMapName, topic, mapLength);
		mMapName[mapLength] = 0;
		WWASSERT(strlen(mMapName) == mapLength);
		mIsMapValid = false;
	}
	topic += mapLength;

	// Calculate ping
	PingProfile pings;
	DecodePingProfile(topic, pings);

	const PingProfile& localPings = GetLocalPingProfile();
	mPingTime = ComparePingProfile(localPings, pings);
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::ExportToChannel
*
* DESCRIPTION
*     Export game information into a channel.
*
* INPUTS
*     Channel - Channel to export game information into.
*
* RESULT
*     NONE
*
******************************************************************************/

void WOLGameInfo::ExportToChannel(const RefPtr<ChannelData>& channel)
	{
	if (channel.IsValid() && mIsDataValid)
		{
		// If this is a dedicated server then the max players must be incremented
		// by one to account for the host.
		unsigned int maxPlayers = (mIsDedicated == true) ? (mMaxPlayers + 1) : mMaxPlayers;

		// Set the channels min and max players
		channel->SetMinMaxUsers(mMinPlayers, maxPlayers);

		// Set the tournament type
		unsigned int tournamentType = (mIsLaddered ? 1 : 0);
		channel->SetTournament(tournamentType);

		//-------------------------------------------------------------------------
		// Encode ExInfo
		//-------------------------------------------------------------------------

		// For flags bit 6 must be set to keep the overall value at least a space character
		unsigned char gameFlags1 = 0x20;
		gameFlags1 |= mIsDedicated ? 0x40 : 0x00;
		gameFlags1 |= mIsFriendlyFire ? 0x10 : 0x00;
		gameFlags1 |= mIsFreeWeapons ? 0x08 : 0x00;
		gameFlags1 |= mIsTeamRemix ? 0x04 : 0x00;
		gameFlags1 |= mIsTeamChange ? 0x02 : 0x00;
		gameFlags1 |= mIsClanGame ? 0x01 : 0x00;

		unsigned char gameFlags2 = 0x20;
		gameFlags2 |= mIsMod ? 0x40 : 0x00;
		gameFlags2 |= mSpawnWeapons ? 0x04: 0x00;
		gameFlags2 |= mIsRepairBuildings ? 0x02: 0x00;
		gameFlags2 |= mIsDriverGunner ? 0x01: 0x00;

		// The file CRC is either the map name or the mod name depending on if
		// the mod flag is set.
		unsigned long fileCRC = 0;
		unsigned char modMapIndex = 0;

		if (mIsMod)
			{
			ModPackageClass& package = ModPackageMgrClass::Get_Current_Package();
			fileCRC = package.Get_CRC();
			modMapIndex = package.Get_Map_Index(mMapName);
			}
		else
			{
			fileCRC = ::CRC_Stringi(mMapName);
			}

		// Put the map index into the printable ASCII range
		modMapIndex += 32;

		// WARNING: The channels ExInfo field has a maximum size of 40 bytes.
		char exInfo[41];
		sprintf(exInfo, "%08lX%08lX%08lX%08lX%c%c%c%c", mVersion, fileCRC,
				mClanID1, mClanID2, (0x30 + mGameType), gameFlags1, gameFlags2, modMapIndex);

		channel->SetExtraInfo(exInfo);

		//-------------------------------------------------------------------------
		// Encode topic
		//-------------------------------------------------------------------------
		unsigned int titleLength = min<unsigned int>(strlen(mTitle), 32);
		titleLength += 0x20;

		// WARNING: The channels topic field has a maximum size of 80 bytes.
		// Ping profile and quickmatch settings are encoded in the topic in
		// addition to the the game title. The combined maximum of ALL these
		// entries MUST NEVER exceed 80 bytes.
		char topic[81];
		sprintf(topic, "%c%.32s", titleLength, mTitle);
		
		//
		// Only using 61 max right now. Room for a map name maybe? ST - 10/31/2002 2:55PM
		//
		unsigned int mapLength = min<unsigned int>(strlen(mMapName), 16);
		mapLength += 0x20;

		// WARNING: The channels topic field has a maximum size of 80 bytes.
		// Ping profile and quickmatch settings are encoded in the topic in
		// addition to the the game title. The combined maximum of ALL these
		// entries MUST NEVER exceed 80 bytes.
		char mapinfo[81];
		sprintf(mapinfo, "%c%.16s", mapLength, mMapName);
		strcat(topic, mapinfo);

		// Add our ping profile
		const PingProfile& localPings = GetLocalPingProfile();
		EncodePingProfile(localPings, topic);

		channel->SetTopic(topic);
		}
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

bool WOLGameInfo::IsValidGameChannel(const RefPtr<ChannelData>& channel)
	{
	WOLGameInfo gameInfo(channel);
	return (gameInfo.IsDataValid() && (gameInfo.mVersion == (unsigned long)cNetwork::Get_Exe_Key()));
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::IsClanCompeting
*
* DESCRIPTION
*     Check if the specified clan is competing in the game.
*
* INPUTS
*     Clan - ID of clan to check.
*
* RESULT
*     True if clan is competing.
*
******************************************************************************/

bool WOLGameInfo::IsClanCompeting(unsigned long clanID) const
	{
	return (mIsClanGame && (clanID != 0) && ((clanID == mClanID1) || (clanID == mClanID2)));
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::IsClanGameOpen
*
* DESCRIPTION
*     Check if the clan game is open for new clans to join.
*
* INPUTS
*     NONE
*
* RESULT
*     True if the game is open to clans.
*
******************************************************************************/

bool WOLGameInfo::IsClanGameOpen(void) const
	{
	return (mIsClanGame && ((0 == mClanID1) || (0 == mClanID2)));
	}


/******************************************************************************
*
* NAME
*     WOLGameInfo::CanUserJoin
*
* DESCRIPTION
*     Check if the user is able to join this game.
*
* INPUTS
*     User
*
* RESULT
*     True if user can join this game.
*
******************************************************************************/

bool WOLGameInfo::CanUserJoin(const RefPtr<UserData>& user)
	{
	if (!user.IsValid() || !mIsDataValid || !mIsMapValid)
		{
		return false;
		}

	if (mVersion != (unsigned long)cNetwork::Get_Exe_Key())
		{
		return false;
		}

	if (mIsClanGame)
		{
		unsigned long userClanID = user->GetSquadID();
		return ((0 != userClanID) && (IsClanGameOpen() || IsClanCompeting(userClanID)));
		}

	return true;
	}