void SizzlingStats::SS_PlayerDisconnect( CSizzPluginContext *pPluginContext, edict_t *pEdict )
{
	Msg( "SS_DeletePlayer\n" );
	int ent_index = SCHelpers::EntIndexFromEdict(pEdict);
	if (ent_index != -1)
	{
		if (m_PlayerDataManager.IsValidPlayer(ent_index))
		{
			playerAndExtra_t data = m_PlayerDataManager.GetPlayerData(ent_index);
			data.m_pPlayerData->UpdateRoundStatsData(m_aPropOffsets);
			data.m_pPlayerData->UpdateRoundExtraData(*data.m_pExtraData);

			IPlayerInfo *pInfo = pPluginContext->GetPlayerInfo(ent_index);

			if (m_bTournamentMatchRunning)
			{
				playerWebStats_t stats;
				stats.m_scoreData = data.m_pPlayerData->GetRoundStatsData();
				V_strncpy(stats.m_playerInfo.m_name, pInfo->GetName(), sizeof(stats.m_playerInfo.m_name));
				V_strncpy(stats.m_playerInfo.m_steamid, pInfo->GetNetworkIDString(), sizeof(stats.m_playerInfo.m_steamid));
				V_strncpy(stats.m_playerInfo.m_ip, pPluginContext->GetPlayerIPPortString(ent_index), sizeof(stats.m_playerInfo.m_ip));
				stats.m_playerInfo.m_teamid = pInfo->GetTeamIndex();
				CPlayerClassTracker *pTracker = data.m_pPlayerData->GetClassTracker();
				stats.m_playerInfo.m_mostPlayedClass = pTracker->GetMostPlayedClass();
				stats.m_playerInfo.m_playedClasses = pTracker->GetPlayedClasses();
				m_pWebStatsHandler->EnqueuePlayerStats(stats);
			}
		}

		m_vecMedics.FindAndRemove(ent_index);
		m_PlayerDataManager.RemovePlayer(ent_index);
	}
}
Ejemplo n.º 2
0
const char *CClient :: getName ()
{
	IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( m_pPlayer );

	if ( playerinfo )
		return playerinfo->GetName();

	return NULL;
}
Ejemplo n.º 3
0
//---------------------------------------------------------------------------------
// Purpose: called on level start
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::ClientSettingsChanged( edict_t *pEdict )
{
	if ( playerinfomanager )
	{
		IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEdict );

		const char * name = engine->GetClientConVarValue( engine->IndexOfEdict(pEdict), "name" );

		if ( playerinfo && name && playerinfo->GetName() && 
			 Q_stricmp( name, playerinfo->GetName()) ) // playerinfo may be NULL if the MOD doesn't support access to player data 
													   // OR if you are accessing the player before they are fully connected
		{
			ClientPrint( pEdict, "Your name changed to \"%s\" (from \"%s\"\n", name, playerinfo->GetName() );
						// this is the bad way to check this, the better option it to listen for the "player_changename" event in FireGameEvent()
						// this is here to give a real example of how to use the playerinfo interface
		}
	}
}
//---------------------------------------------------------------------------------
// Purpose: called when a client joins a server
//---------------------------------------------------------------------------------
PLUGIN_RESULT CEmptyServerPlugin::ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen )
{
	IPlayerInfo *pPlayerInfo = playerinfomanager->GetPlayerInfo( pEntity );
	if (pPlayerInfo)
		Msg("player: %s connected\n", pPlayerInfo->GetName() );
	else
		Msg("ClientConnect error\n");

	return PLUGIN_CONTINUE;
}
Ejemplo n.º 5
0
void PlayerManager::OnClientSettingsChanged(edict_t *pEntity)
{
	cell_t res;
	int client = engine->IndexOfEdict(pEntity);
	CPlayer *pPlayer = &m_Players[client];

	if (!pPlayer->IsConnected())
	{
		return;
	}

	m_clinfochanged->PushCell(engine->IndexOfEdict(pEntity));
	m_clinfochanged->Execute(&res, NULL);

	IPlayerInfo *info = pPlayer->GetPlayerInfo();
	const char *new_name = info ? info->GetName() : engine->GetClientConVarValue(client, "name");
	const char *old_name = pPlayer->m_Name.c_str();

	if (strcmp(old_name, new_name) != 0)
	{
		AdminId id = g_Admins.FindAdminByIdentity("name", new_name);
		if (id != INVALID_ADMIN_ID && pPlayer->GetAdminId() != id)
		{
			if (!CheckSetAdminName(client, pPlayer, id))
			{
				pPlayer->Kick("Your name is reserved by SourceMod; set your password to use it.");
				RETURN_META(MRES_IGNORED);
			}
		} else if ((id = g_Admins.FindAdminByIdentity("name", old_name)) != INVALID_ADMIN_ID) {
			if (id == pPlayer->GetAdminId())
			{
				/* This player is changing their name; force them to drop admin privileges! */
				pPlayer->SetAdminId(INVALID_ADMIN_ID, false);
			}
		}
		pPlayer->SetName(new_name);
	}
	
	if (m_PassInfoVar.size() > 0)
	{
		/* Try for a password change */
		const char *old_pass = pPlayer->m_LastPassword.c_str();
		const char *new_pass = engine->GetClientConVarValue(client, m_PassInfoVar.c_str());
		if (strcmp(old_pass, new_pass) != 0)
		{
			pPlayer->m_LastPassword.assign(new_pass);
			if (pPlayer->IsInGame() && pPlayer->IsAuthorized())
			{
				/* If there is already an admin id assigned, this will just bail out. */
				pPlayer->DoBasicAdminChecks();
			}
		}
	}
}
//---------------------------------------------------------------------------------
// Purpose: called on 
//---------------------------------------------------------------------------------
void CEmptyServerPlugin::ClientPutInServer( edict_t *pEntity, char const *playername )
{
	if( !pEntity || pEntity->IsFree() )
		return;

	IPlayerInfo *pPlayerInfo = playerinfomanager->GetPlayerInfo( pEntity );
	if (pPlayerInfo)
		Msg("player: %s put in server\n", pPlayerInfo->GetName() );
	else
		Msg("ClientPutInServer error\n");
}
Ejemplo n.º 7
0
void SamplePlugin::Hook_ClientSettingsChanged(edict_t *pEdict)
{
	if (playerinfomanager)
	{
		IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo(pEdict);

		const char *name = engine->GetClientConVarValue(IndexOfEdict(pEdict), "name");

		if (playerinfo != NULL
			&& name != NULL
			&& strcmp(engine->GetPlayerNetworkIDString(pEdict), "BOT") != 0
			&& playerinfo->GetName() != NULL
			&& strcmp(name, playerinfo->GetName()) == 0)
		{
			char msg[128];
			MM_Format(msg, sizeof(msg), "Your name changed to \"%s\" (from \"%s\")\n", name, playerinfo->GetName());
			engine->ClientPrintf(pEdict, msg);
		}
	}
}
Ejemplo n.º 8
0
	int __thiscall RevivedByDefib::OnRevived(CBaseEntity *pInitiator, void *deathModel)
	{
		CBaseEntity* pTarget = reinterpret_cast<CBaseEntity*>(this);
		cell_t client = gamehelpers->EntityToBCompatRef(pTarget);

		IGamePlayer* pGamePlayer = playerhelpers->GetGamePlayer(client);
		if(pGamePlayer) {
			IPlayerInfo* pInfo = pGamePlayer->GetPlayerInfo();
			if(pInfo) {
				r_nowAlive(pInfo->GetAbsOrigin(), g_dead_players, arraysize(g_dead_players));
				L4D_DEBUG_LOG("RevivedByDefib called for: %s", pInfo->GetName());
			}
		}


		return (this->*(GetTrampoline()))(pInitiator, deathModel);
	}
void SizzlingStats::SS_TournamentMatchStarted( CSizzPluginContext *pPluginContext )
{
	Msg( "tournament match started\n" );
	m_bTournamentMatchRunning = true;
	m_flMatchDuration = Plat_FloatTime();

	V_strncpy(m_pHostInfo->m_hostname, pPluginContext->GetHostName(), 64);
	V_strncpy(m_pHostInfo->m_mapname, pPluginContext->GetMapName(), 64);
	V_strncpy(m_pHostInfo->m_bluname, pPluginContext->GetBluTeamName(), 32);
	V_strncpy(m_pHostInfo->m_redname, pPluginContext->GetRedTeamName(), 32);
	m_pHostInfo->m_hostip = m_refHostIP.GetInt();
	V_strncpy(m_pHostInfo->m_ip, m_refIP.GetString(), 32);
	m_pHostInfo->m_hostport = m_refHostPort.GetInt();
	m_pHostInfo->m_roundduration = m_flRoundDuration;
	m_pWebStatsHandler->SetHostData(*m_pHostInfo);

	// record demos if the cvar is non-zero
	if (record_demos.GetBool())
	{
		m_STVRecorder.StartRecording(pPluginContext, m_pHostInfo->m_mapname);
	}

	CTFPlayerWrapper player;
	for (int i = 1; i <= MAX_PLAYERS; ++i)
	{
		if (m_PlayerDataManager.IsValidPlayer(i))
		{
			player.SetPlayer(pPluginContext->BaseEntityFromEntIndex(i));
			IPlayerInfo *pInfo = pPluginContext->GetPlayerInfo(i);
			playerInfo_t info;
			V_strncpy(info.m_name, pInfo->GetName(), sizeof(info.m_name));
			V_strncpy(info.m_steamid, pInfo->GetNetworkIDString(), sizeof(info.m_steamid));
			V_strncpy(info.m_ip, pPluginContext->GetPlayerIPPortString(i), sizeof(info.m_ip));
			info.m_teamid = pInfo->GetTeamIndex();
			info.m_mostPlayedClass = player.GetClass();
			m_pWebStatsHandler->EnqueuePlayerInfo(info);
		}
	}

	// set the api key
	m_pWebStatsHandler->SetApiKey(apikey.GetString());

	// send the initial match info to the web
	m_pWebStatsHandler->SendGameStartEvent();
}
Ejemplo n.º 10
0
bool SizzlingStats::SS_PlayerConnect( CSizzPluginContext *pPluginContext, edict_t *pEdict )
{
	Msg( "SS_InsertPlayer\n" );
	int ent_index = SCHelpers::EntIndexFromEdict(pEdict);
	if (ent_index != -1)
	{
		IPlayerInfo *pPlayerInfo = pPluginContext->GetPlayerInfo(ent_index);
		if (pPlayerInfo && pPlayerInfo->IsConnected())
		{
			CBaseEntity *pEnt = SCHelpers::EdictToBaseEntity(pEdict);
			m_PlayerDataManager.InsertPlayer(ent_index, pEnt);

			int acc_id = pPluginContext->SteamIDFromEntIndex(ent_index);
			SS_Msg("Stats for player #%i: '%s' will be tracked\n", acc_id, pPlayerInfo->GetName());
			return true;
		}
	}
	return false;
}
Ejemplo n.º 11
0
void SizzlingStats::SS_EndOfRound( CSizzPluginContext *pPluginContext )
{
	for (int i = 1; i <= MAX_PLAYERS; ++i)
	{
		if (m_PlayerDataManager.IsValidPlayer(i))
		{
			playerAndExtra_t data = m_PlayerDataManager.GetPlayerData(i);
			data.m_pPlayerData->UpdateRoundStatsData(m_aPropOffsets);
			data.m_pPlayerData->UpdateRoundExtraData(*data.m_pExtraData);

			IPlayerInfo *pInfo = pPluginContext->GetPlayerInfo(i);

			if (m_bTournamentMatchRunning)
			{
				playerWebStats_t stats;
				stats.m_scoreData = data.m_pPlayerData->GetRoundStatsData();
				V_strncpy(stats.m_playerInfo.m_name, pInfo->GetName(), sizeof(stats.m_playerInfo.m_name));
				V_strncpy(stats.m_playerInfo.m_steamid, pInfo->GetNetworkIDString(), sizeof(stats.m_playerInfo.m_steamid));
				V_strncpy(stats.m_playerInfo.m_ip, pPluginContext->GetPlayerIPPortString(i), sizeof(stats.m_playerInfo.m_ip));
				stats.m_playerInfo.m_teamid = pInfo->GetTeamIndex();
				CPlayerClassTracker *pTracker = data.m_pPlayerData->GetClassTracker();
				stats.m_playerInfo.m_mostPlayedClass = pTracker->GetMostPlayedClass();
				stats.m_playerInfo.m_playedClasses = pTracker->GetPlayedClasses();
				m_pWebStatsHandler->EnqueuePlayerStats(stats);
			}

			if (pInfo->GetTeamIndex() > 1)
			{
				SS_DisplayStats(pPluginContext, i);
			}
		}
	}

	if (m_bTournamentMatchRunning)
	{
		m_pHostInfo->m_roundduration = m_flRoundDuration;
		m_pWebStatsHandler->SetHostData(*m_pHostInfo);
		m_pWebStatsHandler->SendStatsToWeb();
	}
}
Ejemplo n.º 12
0
void XmlReport::writeJoueur(ticpp::Element * eJoueurs, ClanMember * player)
{
    IPlayerInfo * pInfo = player->getPlayerInfo();
    PlayerScore * stats = player->getCurrentScore();

    if (isValidPlayerInfo(pInfo)) // excludes SourceTv
    {
        ticpp::Element * eJoueur = new ticpp::Element("joueur");
        eJoueur->SetAttribute("steamid", pInfo->GetNetworkIDString());

        ticpp::Element * ePseudo = new ticpp::Element("pseudo", pInfo->GetName());
        eJoueur->LinkEndChild(ePseudo);

        ticpp::Element * eKills = new ticpp::Element("kills", stats->kills);
        eJoueur->LinkEndChild(eKills);

        ticpp::Element * eDeaths = new ticpp::Element("deaths", stats->deaths);
        eJoueur->LinkEndChild(eDeaths);

        eJoueurs->LinkEndChild(eJoueur);
    }
}
Ejemplo n.º 13
0
void SizzlingStats::SS_DisplayStats( CSizzPluginContext *pPluginContext, int ent_index )
{
	char pText[64] = {};
	SS_PlayerData &playerData = *m_PlayerDataManager.GetPlayerData(ent_index).m_pPlayerData;
	int kills = playerData.GetStat(Kills);
	int assists = playerData.GetStat(KillAssists);
	int deaths = playerData.GetStat(Deaths);
	int damagedone = playerData.GetStat(DamageDone);
	int amounthealed = playerData.GetStat(HealPoints);
	int points = playerData.GetStat(Points);
	int backstabs = playerData.GetStat(Backstabs);
	int headshots = playerData.GetStat(Headshots);
	int captures = playerData.GetStat(Captures);
	int defenses = playerData.GetStat(Defenses);
	int healsrecv = playerData.GetStat(HealsReceived);
	int ubers = playerData.GetStat(Invulns);
	int drops = playerData.GetStat(UbersDropped);
	int medpicks = playerData.GetStat(MedPicks);
	
	IPlayerInfo *pPlayerInfo = pPluginContext->GetPlayerInfo(ent_index);
	CTFPlayerWrapper player(pPluginContext->BaseEntityFromEntIndex(ent_index));

	V_snprintf( pText, 64, "\x04[\x05SizzlingStats\x04]\x06: \x03%s\n", pPlayerInfo->GetName() );
	SS_SingleUserChatMessage(pPluginContext, ent_index, pText);

	memset( pText, 0, sizeof(pText) );
	if ( deaths != 0 )
		V_snprintf( pText, 64, "K/D: %i:%i (%.2f), Assists: %i\n", kills, deaths, (double)kills/(double)deaths, assists );
	else
		V_snprintf( pText, 64, "K/D: %i:%i, Assists: %i\n", kills, deaths, assists );
	SS_SingleUserChatMessage(pPluginContext, ent_index, pText);

	memset( pText, 0, sizeof(pText) );
	//Msg( "class: %i\n", *((int *)(((unsigned char *)playerData.GetBaseEntity()) + m_PlayerClassOffset)) );
	if ( (player.GetClass() - 5) != 0 ) // if the player isn't a medic
	{
		V_snprintf( pText, 64, "Damage Done: %i, Heals Received (not incl. buffs): %i\n", damagedone, healsrecv );
	}
	else
	{
		V_snprintf( pText, 64, "Ubers/Kritz Used: %i, Ubers/Kritz Dropped: %i", ubers, drops );
		//SS_SingleUserChatMessage( pEntity, pText );
		//memset( pText, 0, size );
		//V_snprintf( pText, 64, "Avg Charge Time: %i seconds", chargetime );
		//SS_SingleUserChatMessage( pEntity, pText );
	}
	SS_SingleUserChatMessage(pPluginContext, ent_index, pText);

	if ( amounthealed != 0 )
	{
		memset( pText, 0, sizeof(pText) );
		V_snprintf( pText, 64, "Amount Healed: %i\n", amounthealed );
		SS_SingleUserChatMessage(pPluginContext, ent_index, pText);
	}

	if ( medpicks != 0 )
	{
		memset( pText, 0, sizeof(pText) );
		V_snprintf( pText, 64, "Medic Picks: %i\n", medpicks );
		SS_SingleUserChatMessage(pPluginContext, ent_index, pText);
	}

	if ( (backstabs != 0) && (headshots != 0) )
	{
		memset( pText, 0, sizeof(pText) );
		V_snprintf( pText, 64, "Backstabs: %i, Headshots: %i\n", backstabs, headshots );
		SS_SingleUserChatMessage(pPluginContext, ent_index, pText);
	}
	else if ( (backstabs != 0) && (headshots == 0) )
	{
		memset( pText, 0, sizeof(pText) );
		V_snprintf( pText, 64, "Backstabs: %i\n", backstabs );
		SS_SingleUserChatMessage(pPluginContext, ent_index, pText);
	}
	else if ( (backstabs == 0) && (headshots != 0) )
	{
		memset( pText, 0, sizeof(pText) );
		V_snprintf( pText, 64, "Headshots: %i\n", headshots );
		SS_SingleUserChatMessage(pPluginContext, ent_index, pText);
	}

	memset( pText, 0, sizeof(pText) );
	V_snprintf( pText, 64, "Captures: %i, Defenses: %i\n", captures, defenses );
	SS_SingleUserChatMessage(pPluginContext, ent_index, pText);

	memset( pText, 0, sizeof(pText) );
	V_snprintf( pText, 64, "Round Score: %i\n", points );
	SS_SingleUserChatMessage(pPluginContext, ent_index, pText);
}
Ejemplo n.º 14
0
//---------------------------------------------------------------------------------
// Purpose: Check Player on connect
//---------------------------------------------------------------------------------
bool ManiReservedSlot::NetworkIDValidated(player_t	*player_ptr)
{
	bool		is_reserve_player = false;
	player_t	temp_player;
	int			allowed_players;
	int			total_players;
	
	m_iUnaccountedPlayers--;
	
	if ((war_mode) || (!mani_reserve_slots.GetBool()) || (mani_reserve_slots_number_of_slots.GetInt() == 0))   // with the other method ( zero slots )
	{																		// other player is kicked BEFORE
		return true;														// NetworkIDValidated
	}

	total_players = m_iUnaccountedPlayers + GetNumberOfActivePlayers(true);
	// DirectLogCommand("[DEBUG] Total players on server [%i]\n", total_players);

	if (total_players <= (max_players - mani_reserve_slots_number_of_slots.GetInt()))
	{
		// DirectLogCommand("[DEBUG] No reserve slot action required\n");
		return true;
	}


	GetIPAddressFromPlayer(player_ptr);
	Q_strcpy (player_ptr->steam_id, engine->GetPlayerNetworkIDString(player_ptr->entity));
	IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo(player_ptr->entity);
	if (playerinfo && playerinfo->IsConnected())
	{
		Q_strcpy(player_ptr->name, playerinfo->GetName());
	}
	else
	{
		Q_strcpy(player_ptr->name,"");
	}

	if (FStrEq("BOT", player_ptr->steam_id))
		return true;

	player_ptr->is_bot = false;


	if (IsPlayerInReserveList(player_ptr))
		is_reserve_player = true;

	else if (mani_reserve_slots_include_admin.GetBool() && gpManiClient->HasAccess(player_ptr->index, ADMIN, ADMIN_BASIC_ADMIN))
		is_reserve_player = true;

	if (mani_reserve_slots_allow_slot_fill.GetInt() != 1)
	{
		// Keep reserve slots free at all times
		allowed_players = max_players - mani_reserve_slots_number_of_slots.GetInt();
		if (total_players > allowed_players)
		{
			if (!is_reserve_player) {
				DisconnectPlayer(player_ptr);
				return false;
			}

			temp_player.index = FindPlayerToKick();
			FindPlayerByIndex(&temp_player);
			DisconnectPlayer(&temp_player);
		}
	}
	
	return true;
}
Ejemplo n.º 15
0
//---------------------------------------------------------------------------------
// Purpose: Builds up a list of players that are 'kickable'
//---------------------------------------------------------------------------------
void ManiReservedSlot::BuildPlayerKickList( player_t *player_ptr, int *players_on_server )
{
	player_t	temp_player;
	active_player_t active_player;

	FreeList((void **) &active_player_list, &active_player_list_size);

	for (int i = 1; i <= max_players; i ++)
	{
#if defined ( GAME_CSGO )
		edict_t *pEntity = PEntityOfEntIndex(i);
#else
		edict_t *pEntity = engine->PEntityOfEntIndex(i);
#endif
		if( pEntity && !pEntity->IsFree())
		{
			if ( player_ptr && ( pEntity == player_ptr->entity ) )
				continue;

			IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity );
			if (playerinfo && playerinfo->IsConnected())
			{
				Q_strcpy(active_player.steam_id, playerinfo->GetNetworkIDString());
				if (FStrEq("BOT", active_player.steam_id))
				{
					continue;
				}

				INetChannelInfo *nci = engine->GetPlayerNetInfo(i);
				if (!nci)
				{
					continue;
				}

				active_player.entity = pEntity;

				active_player.ping = nci->GetAvgLatency(0);
				const char * szCmdRate = engine->GetClientConVarValue( i, "cl_cmdrate" );
				int nCmdRate = (20 > Q_atoi( szCmdRate )) ? 20 : Q_atoi(szCmdRate);
				active_player.ping -= (0.5f/nCmdRate) + TICKS_TO_TIME( 1.0f ); // correct latency

				// in GoldSrc we had a different, not fixed tickrate. so we have to adjust
				// Source pings by half a tick to match the old GoldSrc pings.
				active_player.ping -= TICKS_TO_TIME( 0.5f );
				active_player.ping = active_player.ping * 1000.0f; // as msecs
				active_player.ping = ((5 > active_player.ping) ? 5:active_player.ping); // set bounds, dont show pings under 5 msecs

				active_player.time_connected = nci->GetTimeConnected();
				Q_strcpy(active_player.ip_address, nci->GetAddress());
				if (gpManiGameType->IsSpectatorAllowed() &&
					playerinfo->GetTeamIndex () == gpManiGameType->GetSpectatorIndex())
				{
					active_player.is_spectator = true;
				}
				else
				{
					active_player.is_spectator = false;
				}
				active_player.user_id = playerinfo->GetUserID();
				Q_strcpy(active_player.name, playerinfo->GetName());

				if ( players_on_server )
					*players_on_server = *players_on_server + 1;

				active_player.kills = playerinfo->GetFragCount();
				active_player.deaths = playerinfo->GetDeathCount();
				Q_strcpy(temp_player.steam_id, active_player.steam_id);
				Q_strcpy(temp_player.ip_address, active_player.ip_address);
				Q_strcpy(temp_player.name, active_player.name);
				temp_player.is_bot = false;

				if (IsPlayerInReserveList(&temp_player))
				{
					continue;
				}

				active_player.index = i;

				if (mani_reserve_slots_include_admin.GetInt() == 1 &&
					gpManiClient->HasAccess(active_player.index, ADMIN, ADMIN_BASIC_ADMIN))
				{
					continue;
				}

				if (gpManiClient->HasAccess(active_player.index, IMMUNITY, IMMUNITY_RESERVE))
				{
					continue;
				}

				AddToList((void **) &active_player_list, sizeof(active_player_t), &active_player_list_size);
				active_player_list[active_player_list_size - 1] = active_player;
			}
		}
	}
}
Ejemplo n.º 16
0
//=================================================================================
// Callback for the 'webspec' protocol
// Manages spectator connections & sending Initial messages to new spectators
//=================================================================================
int webspec_callback(struct libwebsocket_context *ctx, struct libwebsocket *wsi, 
	enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len)
{
	switch (reason) {
		case LWS_CALLBACK_ESTABLISHED:
		{
			Msg("[WS] New connection\n");
			// New connection
			ws_spectators.AddToTail(wsi);
			
			// Send basic game info to let client set up
			// MapName, Server name (may remove), current team names
			char *buffer = (char*)malloc(MAX_BUFFER_SIZE);
			char *mapName = (char*) STRING(gpGlobals->mapname);
			ConVarRef hostNameCVar = ConVarRef("hostname");
			string_t hostname;
			if (hostNameCVar.IsValid())
				hostname = MAKE_STRING(hostNameCVar.GetString());
			else
				hostname = MAKE_STRING("WebSpec Demo Server"); //Can't imagine when hostname would be invalid, but this is Source
			int length = snprintf(buffer, MAX_BUFFER_SIZE, "%c%s:%s:%s:%s", WSPacket_Init, mapName, STRING(hostname), STRING(ws_teamName[1]), STRING(ws_teamName[0]));

			SendPacketToOne(buffer, length, wsi);
			free(buffer);

			//Send connected players
			IPlayerInfo *playerInfo;
			for (int i=1; i<=gpGlobals->maxClients; i++) {
				playerInfo = playerInfoManager->GetPlayerInfo(engine->PEntityOfEntIndex(i));
				if (playerInfo != NULL && playerInfo->IsConnected()) {
					buffer = (char *)malloc(MAX_BUFFER_SIZE);
					int userid = playerInfo->GetUserID();
					int teamid = playerInfo->GetTeamIndex();
					int health = playerInfo->GetHealth();
					int maxHealth = playerInfo->GetMaxHealth();
					bool alive = !playerInfo->IsDead();
					string_t playerName = MAKE_STRING(playerInfo->GetName());
					
					//Pointer magic to get TF2 class
					CBaseEntity *playerEntity = serverGameEnts->EdictToBaseEntity(engine->PEntityOfEntIndex(i));
					int playerClass = *MakePtr(int*, playerEntity, WSOffsets::pCTFPlayer__m_iClass);

					float uberCharge = 0.0f;
					
					if (playerClass == TFClass_Medic) { 
						//Way more pointer magic to get ubercharge from medigun
						CBaseCombatCharacter *playerCombatCharacter = CBaseEntity_MyCombatCharacterPointer(playerEntity);
						CBaseCombatWeapon *slot1Weapon = CBaseCombatCharacter_Weapon_GetSlot(playerCombatCharacter, 1);
						
						uberCharge = *MakePtr(float*, slot1Weapon, WSOffsets::pCWeaponMedigun__m_flChargeLevel);
					}

					int length = snprintf(buffer, MAX_BUFFER_SIZE, "%c%d:%d:%d:%d:%d:%d:0:%d:%s", 'C', userid, teamid, playerClass,
						health, maxHealth, alive, Round(uberCharge*100.0f), STRING(playerName));

					SendPacketToOne(buffer, length, wsi);
					free(buffer);
				}
			}

			break;
		}