Raised This Month: $51 Target: $400
 12% 

[ANY] How to properly switch team


  
 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
Author Message
Benoist3012
Veteran Member
Join Date: Mar 2014
Location: CWave::ForceFinish()
Old 02-11-2019 , 03:26   [ANY] How to properly switch team
Reply With Quote #1

Introduction:
This snippet is aimed at those not willing to use ChangeClientTeam due to undesired side effects, but still wish to change a player team. And without any crash!

How to find out if you're concerned:
If you're doing
Code:
SetEntProp(iClient, Prop_Send, "m_iTeamNum", iNewTeam);
somewhere in your plugin then this is monstrously wrong and you're gonna crash the server hosting your plugin in following minutes.
I was myself doing it and that's how I found how to fix it as well why I'm making this snippet
Here's a list of plugins I can think of that are/were affected by the crash(all from tf2 but I'm sure it's happening on other games):
How to fix:
Luckily the solution is very simple:
Code:
#define TEAM_CLASSNAME "tf_team" Handle g_hSDKTeamAddPlayer; Handle g_hSDKTeamRemovePlayer; public void OnPluginStart() {     Handle hGameData = LoadGameConfigFile("yourgamedata");         StartPrepSDKCall(SDKCall_Entity);     PrepSDKCall_SetFromConf(hGameData, SDKConf_Virtual, "CTeam::AddPlayer");     PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);     g_hSDKTeamAddPlayer = EndPrepSDKCall();     if(g_hSDKTeamAddPlayer == INVALID_HANDLE)         SetFailState("Could not find CTeam::AddPlayer!");         StartPrepSDKCall(SDKCall_Entity);     PrepSDKCall_SetFromConf(hGameData, SDKConf_Virtual, "CTeam::RemovePlayer");     PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);     g_hSDKTeamRemovePlayer = EndPrepSDKCall();     if(g_hSDKTeamRemovePlayer == INVALID_HANDLE)         SetFailState("Could not find CTeam::RemovePlayer!");         delete hGameData; } void ChangeClientTeamEx(iClient, int iNewTeamNum) {     int iTeamNum = GetEntProp(iClient, Prop_Send, "m_iTeamNum");         // Safely swap team     int iTeam = MaxClients+1;     while ((iTeam = FindEntityByClassname(iTeam, TEAM_CLASSNAME)) != -1)     {         int iAssociatedTeam = GetEntProp(iTeam, Prop_Send, "m_iTeamNum");         if (iAssociatedTeam == iTeamNum)             SDK_Team_RemovePlayer(iTeam, iClient);         else if (iAssociatedTeam == iNewTeamNum)             SDK_Team_AddPlayer(iTeam, iClient);     }         SetEntProp(iClient, Prop_Send, "m_iTeamNum", iNewTeamNum); } void SDK_Team_AddPlayer(int iTeam, int iClient) {     if (g_hSDKTeamAddPlayer != INVALID_HANDLE)     {         SDKCall(g_hSDKTeamAddPlayer, iTeam, iClient);     } } void SDK_Team_RemovePlayer(int iTeam, int iClient) {     if (g_hSDKTeamRemovePlayer != INVALID_HANDLE)     {         SDKCall(g_hSDKTeamRemovePlayer, iTeam, iClient);     } }

Gamedata (for tf2, I leave you find the correct team entity classname and offsets for other games)
Code:
"Games" {     "tf"     {         "Offsets"         {             "CTeam::AddPlayer"             {                 "linux"  "202"                 "windows"   "201"             }             "CTeam::RemovePlayer"             {                 "linux"  "203"                 "windows"   "202"             }         }     } }

More details on the crash:
In source MP games, each team is governed by an entity. Those entities contains an array of CBasePlayer *, of course when ChangeClientTeam is called everything is properly handled and the player pointer is removed from one, and added to the other. However some plugins want to move players to spec team but keep them alive, which is impossible to do with ChangeClientTeam even by changing the player alive state, as they will be forcibly be put in observer mode.
So most will do:
PHP Code:
SetEntProp(iClientProp_Send"m_iTeamNum"0); 
Which doesn't notify the previous team entity that the player is now gone, and doesn't notify the receiving team of the new player. And so when the player leaves the server, the game code will clear out the stored ptr in the last assigned team entity, but not in the team you forcibly removed the player from. And that's where the issue is as now the server will play with a garbage pointer.

Here's a piece code I reversed from TF2 server.
Code:
CTeam *pTeam = pOwner->GetTeam(); if ( pTeam ) {     for (int i = 0; i < pTeam->GetNumPlayers(); ++i )     {         CBaseCombatCharacter *pPlayer = pTeam->GetPlayer(i);         if ( pPlayer ) // This check passes "fine"         {             if ( pPlayer->IsPlayer() ) // Uh oh, we have a problem => crash             {                 if ( pPlayer->IsPlayerClass(TFClass_Medic) && pPlayer != pOwner )                 {                 }             }         }     } }
You can easily see why the server will crash as CTeam contains a ptr to a destroyed CBasePlayer object.



That's all, thanks for reading, hopefully you learned something or you knew already either way my job is done here
__________________
Benoist3012 is offline
 



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 20:58.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode