50DKP / FF2-Official

Freak Fortress 2 is a one versus all mod for Team Fortress 2. It is the successor to the Vs. Saxton Hale plugin.
https://forums.alliedmods.net/forumdisplay.php?f=154
GNU General Public License v3.0
54 stars 27 forks source link

A way to fix the spectator issue! #310

Closed naydef closed 9 years ago

naydef commented 9 years ago

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.

winstliu commented 9 years ago

:tada:

naydef commented 9 years ago

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;
}
winstliu commented 9 years ago

Nice work @naydef. I'll add these changes when I have time, or you can make a PR.

naydef commented 9 years ago

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);
winstliu commented 9 years ago

VFormat is my worst enemy. Thanks (and :cry: to know that the civilian bug is still happening).

shadow93 commented 9 years ago

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);
naydef commented 9 years ago

I have more suggestions for Freak Fortress 2:

  1. Instead of:
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;
}
  1. And why Freak Fortress checks for "vo" string on line 8128? Instead it can just check if the sound is from the voice channel
//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;
}
shadow93 commented 9 years ago

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);
    }
}
winstliu commented 9 years ago

Hey @naydef, can you please make separate issues for the other two things you reported? Thanks!

winstliu commented 9 years ago

Fixed by #312 (hopefully).