Closed naydef closed 9 years ago
:tada:
Ops. There is more about the problem. I have done some more tests today and I have found that the real origin of the problem is the netprop "m_iDesiredPlayerClass". If the value of this netprop is 0 (Invalid class), TF2_RespawnPlayer won't work, even if the plugin sets the player class (m_iClass) of the boss! The correct way to fix this problem is to remove every line:
SetEntProp(boss, Prop_Send, "m_lifeState", 0);
and replace it with TF2_RespawnPlayer and MakeBoss timer have to be:
Line 3357->public Action:MakeBoss(Handle:timer, any:boss)
public Action:MakeBoss(Handle:timer, any:boss)
{
new client=Boss[boss];
if(!IsValidClient(client) || CheckRoundState()==-1)
{
return Plugin_Continue;
}
if(!IsPlayerAlive(client))
{
if(!CheckRoundState())
{
TF2_RespawnPlayer(client); //Is this needed!
}
else
{
return Plugin_Continue;
}
}
KvRewind(BossKV[Special[boss]]);
if(!GetEntProp(client, Prop_Send, "m_iDesiredPlayerClass")) //Invalid desired class will prevent TF2_RespawnPlayer from working!
{
Debug("Attention! Boss %N has invalid desired class. Setting to boss class", client);
TF2_SetPlayerClass(client, TFClassType:KvGetNum(BossKV[Special[boss]], "class", 1), _, true); // Set the 4 argument to true to set their desired class!
}
else
{
TF2_SetPlayerClass(client, TFClassType:KvGetNum(BossKV[Special[boss]], "class", 1), _, false); //Early set their class!
}
if(GetClientTeam(client)!=BossTeam)
{
SetEntProp(client, Prop_Send, "m_lifeState", 2);
ChangeClientTeam(client, BossTeam);
//SetEntProp(client, Prop_Send, "m_lifeState", 0);
TF2_RespawnPlayer(client);
}
SetEntProp(client, Prop_Send, "m_bGlowEnabled", 0);
KvRewind(BossKV[Special[boss]]);
TF2_RemovePlayerDisguise(client);
//TF2_SetPlayerClass(client, TFClassType:KvGetNum(BossKV[Special[boss]], "class", 1), _, false);
SDKHook(client, SDKHook_GetMaxHealth, OnGetMaxHealth); //Temporary: Used to prevent boss overheal
switch(KvGetNum(BossKV[Special[boss]], "pickups", 0)) //Check if the boss is allowed to pickup health/ammo
{
case 1:
{
FF2flags[client]|=FF2FLAG_ALLOW_HEALTH_PICKUPS;
}
case 2:
{
FF2flags[client]|=FF2FLAG_ALLOW_AMMO_PICKUPS;
}
case 3:
{
FF2flags[client]|=FF2FLAG_ALLOW_HEALTH_PICKUPS|FF2FLAG_ALLOW_AMMO_PICKUPS;
}
}
CreateTimer(0.2, MakeModelTimer, boss, TIMER_FLAG_NO_MAPCHANGE);
if(!IsVoteInProgress() && GetClientClassinfoCookie(client))
{
HelpPanelBoss(boss);
}
if(!IsPlayerAlive(client))
{
return Plugin_Continue;
}
new entity=-1;
while((entity=FindEntityByClassname2(entity, "tf_wearable"))!=-1)
{
if(IsBoss(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity")))
{
switch(GetEntProp(entity, Prop_Send, "m_iItemDefinitionIndex"))
{
case 438, 463, 167, 477, 493, 233, 234, 241, 280, 281, 282, 283, 284, 286, 288, 362, 364, 365, 536, 542, 577, 599, 673, 729, 791, 839, 1015, 5607: //Action slot items
{
//NOOP
}
default:
{
TF2_RemoveWearable(client, entity);
}
}
}
}
entity=-1;
while((entity=FindEntityByClassname2(entity, "tf_powerup_bottle"))!=-1)
{
if(IsBoss(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity")))
{
TF2_RemoveWearable(client, entity);
}
}
entity=-1;
while((entity=FindEntityByClassname2(entity, "tf_wearable_demoshield"))!=-1)
{
if(IsBoss(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity")))
{
TF2_RemoveWearable(client, entity);
}
}
entity=-1;
while((entity=FindEntityByClassname2(entity, "tf_usableitem"))!=-1)
{
if(IsBoss(GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity")))
{
switch(GetEntProp(entity, Prop_Send, "m_iItemDefinitionIndex"))
{
case 438, 463, 167, 477, 493, 233, 234, 241, 280, 281, 282, 283, 284, 286, 288, 362, 364, 365, 536, 542: //Action slot items
{
//NOOP
}
default:
{
TF2_RemoveWearable(client, entity);
}
}
}
}
EquipBoss(boss);
KSpreeCount[boss]=0;
BossCharge[boss][0]=0.0;
SetClientQueuePoints(client, 0);
if(GetEntProp(client, Prop_Send, "m_iObserverMode") && IsPlayerAlive(client)) //This is not needed!
{
Debug("Boss client %N is a living spectator!", client);
}
return Plugin_Continue;
}
Nice work @naydef. I'll add these changes when I have time, or you can make a PR.
And because I don't want to open another issue, Freak Fortress errors at the moment on my tf2 server, because on line 4087 on build 328: Log:
L 11/03/2015 - 20:23:13: [SM] Plugin encountered error 4: Invalid parameter or parameter type
L 11/03/2015 - 20:23:13: [SM] Native "VFormat" reported: String formatted incorrectly - parameter 2 (total 1)
L 11/03/2015 - 20:23:13: [SM] Displaying call stack trace for plugin "freak_fortress_2.smx":
L 11/03/2015 - 20:23:13: [SM] [0] Line 556, C:\tf2server\tf2\tf\addons\sourcemod\scripting\include\freak_fortress_2.inc::Debug()
L 11/03/2015 - 20:23:13: [SM] [1] Line 4100, C:\tf2server\tf2\tf\addons\sourcemod\scripting\freak_fortress_2.sp::CheckItems()
Error code:
Debug("Respawning %N to avoid civilian bug");
There isn't an argument for the CLIENT!!
It has to be:
Debug("Respawning %N to avoid civilian bug", client);
VFormat is my worst enemy. Thanks (and :cry: to know that the civilian bug is still happening).
It can also be done like this:
TF2_SetPlayerClass(client, TFClassType:KvGetNum(BossKV[Special[boss]], "class", 1), _, !GetEntProp(client, Prop_Send, "m_iDesiredPlayerClass") ? true : false);
I have more suggestions for Freak Fortress 2:
public Action:OnChangeClass(client, const String:command[], args)
{
if(Enabled && IsBoss(client) && IsPlayerAlive(client))
{
//Don't allow the boss to switch classes but instead set their *desired* class (for the next round)
decl String:class[16];
GetCmdArg(1, class, sizeof(class));
if(StrEqual(class, "scout", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Scout);
}
else if(StrEqual(class, "soldier", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Soldier);
}
else if(StrEqual(class, "pyro", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Pyro);
}
else if(StrEqual(class, "demoman", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_DemoMan);
}
else if(StrEqual(class, "heavyweapons", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Heavy);
}
else if(StrEqual(class, "medic", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Medic);
}
else if(StrEqual(class, "sniper", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Sniper);
}
else if(StrEqual(class, "spy", false))
{
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", _:TFClass_Spy);
}
return Plugin_Handled;
}
return Plugin_Continue;
}
Freak Fortress 2 can use the native TFClassType:TF2_GetClass(const String:classname[]):
public Action:OnChangeClass(client, const String:command[], args)
{
if(Enabled && IsBoss(client) && IsPlayerAlive(client))
{
//Don't allow the boss to switch classes but instead set their *desired* class (for the next round)
decl String:class[16];
GetCmdArg(1, class, sizeof(class));
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", TF2_GetClass(class));
return Plugin_Handled;
}
return Plugin_Continue;
}
//Ugly compatability layer since HookSound's arguments changed in 1.8
#if SOURCEMOD_V_MAJOR==1 && SOURCEMOD_V_MINOR<=7
public Action:HookSound(clients[64], &numClients, String:sound[PLATFORM_MAX_PATH], &client, &channel, &Float:volume, &level, &pitch, &flags)
#else
public Action:HookSound(clients[64], &numClients, String:sound[PLATFORM_MAX_PATH], &client, &channel, &Float:volume, &level, &pitch, &flags, String:soundEntry[PLATFORM_MAX_PATH], &seed)
#endif
{
if(!Enabled || !IsValidClient(client) || channel<1)
{
return Plugin_Continue;
}
new boss=GetBossIndex(client);
if(boss==-1)
{
return Plugin_Continue;
}
if(channel==SNDCHAN_VOICE && !(FF2flags[Boss[boss]] & FF2FLAG_TALKING))
{
decl String:newSound[PLATFORM_MAX_PATH];
if(RandomSound("catch_phrase", newSound, PLATFORM_MAX_PATH, boss))
{
strcopy(sound, PLATFORM_MAX_PATH, newSound);
return Plugin_Changed;
}
if(bBlockVoice[Special[boss]])
{
return Plugin_Stop;
}
}
return Plugin_Continue;
}
So on a pending PR, i moved out all that team switch code into a stock, to make it easier to update.
stock AssignTeam(client, TFTeam:team, desiredclass=0) // Move all this team switching stuff into a single stock
{
if(!GetEntProp(client, Prop_Send, "m_iDesiredPlayerClass")) // Initial living spectator check. A value of 0 means that no class is selected
{
Debug("INVALID DESIRED CLASS FOR %N!", client);
SetEntProp(client, Prop_Send, "m_iDesiredPlayerClass", !desiredclass ? GetRandomInt(1,9) : desiredclass); // So we assign one to prevent living spectators
}
SetEntProp(client, Prop_Send, "m_lifeState", 2);
TF2_ChangeClientTeam(client, team);
// SetEntProp(client, Prop_Send, "m_lifeState", 0); // Is this even needed? According to naydef, this is the other cause of living spectators.
TF2_RespawnPlayer(client);
if(GetEntProp(client, Prop_Send, "m_iObserverMode") && IsPlayerAlive(client)) // If the initial checks fail, use brute-force. Probably not needed, unless Valve decides to do the usual.
{
Debug("%N IS A LIVING SPECTATOR!", client);
TF2_SetPlayerClass(client, TFClassType:(!desiredclass ? GetRandomInt(1,9) : desiredclass), _, true);
TF2_RespawnPlayer(client);
}
}
Hey @naydef, can you please make separate issues for the other two things you reported? Thanks!
Fixed by #312 (hopefully).
Hi! After some tests with my version of Freak Fortress, I have successfully fixed this bug. The key is to remove every line: 'SetEntProp(boss, Prop_Send, "m_lifeState", 0);' and replace it with TF2_RespawnPlayer(boss); Setting m_lifeState to 0 will just make the game think that the player is alive, while TF2_RespawnPlayer will actually make the boss alive and spawn them. For this reason TF2_RespawnPlayer doesn't work because setting m_lifeState to 0 already makes the player alive ,but they dont spawn. Also set the boss class before respawn, or he just won't respawn. And sorry about my English.