SirPlease / L4D2-Competitive-Rework

Just refreshing and optimizing the core files a bit, eh?
GNU General Public License v3.0
242 stars 129 forks source link

Invisible players; not moving; cloned. #319

Closed lunatixxx closed 1 year ago

lunatixxx commented 3 years ago

You have to type "record demo" and "stop" in console to fix it and when it happens multiple times in a round it is annoying. It happens mostly because you had a connection loss and or the player who became invisible, is it not possible resync automatically the player with the server correctly or something like that?

oethanx commented 3 years ago

It can also happen if players minimize the game or if you minimize. Any solution for this?

theletterjwithadot commented 3 years ago

It's more of a client-sided issue than a server-sided issue (the player appears correctly for everyone except you). I also seem to notice that it happens more frequent if you alt-tab a lot in the game.

The easiest solution is to bind the record demo to keys. I also have it bound to my !ready bind, and it has been very useful. Never encountered the invisible players issue for a long time.

alias "recordfix" "record demofix; stop"

bind "F5" "recordfix"
bind "]" "sm_ready; recordfix"
lunatixxx commented 3 years ago

Not really a fix in my opinion, the time that you figure out that a player is invisible it can ruin a game easily.

oethanx commented 3 years ago

I agree with lunatixxx, it isn't really a fix. It might be inconvenient for new players to learn the bind and understand when to use it

lunatixxx commented 3 years ago

I would like to add that there are other results related to this problem. Sometimes you will see a player not moving, and in very rares cases you will see the clone of survivor (one extra zoey with broke nanimations and bill missing for example).

SirPlease commented 3 years ago

I would like to add that there are other results related to this problem. Sometimes you will see a player not moving, and in very rares cases you will see the clone of survivor (one extra zoey with broke nanimations and bill missing for example).

I've never once seen a clone of a survivor that wasn't caused by server-side issues or the jointeam exploit (which is fixed in this repo, and I believe even in vanilla after TLS)

"Bill missing" is the same as invisible, unless you're talking about something else, then I'd again refer to your own server configuration causing issues.

lunatixxx commented 3 years ago

No i assure you that this was the same issue, i was the only one to have the problem and fixed it with record and stop command. I was seeing two zoey and bill disppeared from the game. (the one who replaced bill had all his animations broken because the model did not correspond to the actual player). But it is not specific to survivor as i remember once i had two bill and no zoey but at this time i did not know how to fix it, it fixed by itself when the concerned player left i think.

Mart-User commented 2 years ago

Similar to running the cl_fullupdate command. (cheat, Forces the server to send a full update packet)

This is rare but happens more often when the game hungs for a while in gameplay. I don't think there is a fix for it. (is more a client-side bug)

Krevik commented 2 years ago

For me and my friends it often occurs because of some packet looses for e.g. after some ping spikes. Especially after ping spikes. Also I think another problem caused by this ping spike is infected displacement or even invisible infected (for example sometimes tank gets invisible or another SI, or sometimes SI position is shifted by few meters). Probably it can be fixed by some plugin detecting momentary ping jump and then sending to such client some force cl_fullupdate? Also probably when such packet loss occurs during startup (or maybe even loading) and bug with standing survivors occur it could be worth forcing cl_fullupdate during readyup maybe when the round just got started? Not sure if it's possible to force cl_fullupdate from server side tho

lunatixxx commented 2 years ago

This glitch is out of control it happens almost at every match to one person. It does not happens on other source games so this is pure lazyness from devs, you can't blame someone for doing a alt tab.

lunatixxx commented 2 years ago

This is the default whitelist:

left4dead2\pak01_dir.vpk        check_crc
left4dead2\steam.inf            check_crc

left4dead2_dlc1\pak01_dir.vpk       check_crc
left4dead2_dlc2\pak01_dir.vpk       check_crc
left4dead2_dlc3\pak01_dir.vpk       check_crc

update\pak01_dir.vpk            check_crc

I feel like check_crc on every map could cause a load on clients that desync them more than usual, correct me if i'm wrong but i don't see people complaining so much about this issue on official servers. I am all for security but if it ruins the experience of players no.

The idea to force a fullupdate on round start seems good: https://forums.alliedmods.net/showthread.php?t=315307

lunatixxx commented 2 years ago

Here it is and thanks to zynda for searching the missing signatures:

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <sdktools>
#include <FullUpdate>
#include <readyup>

Handle g_hCBaseClient_UpdateAcknowledgedFramecount;

public Plugin myinfo = 
{
    name = "FullUpdate", 
    author = "BotoX", 
    description = "Serverside cl_fullupdate", 
    version = "1.0"
}

public void OnPluginStart()
{
    Handle hGameConf = LoadGameConfigFile("FullUpdate.games");
    if (hGameConf == INVALID_HANDLE)
    {
        SetFailState("Couldn't load FullUpdate.games game config!");
        return;
    }

    // void CBaseClient::UpdateAcknowledgedFramecount()
    StartPrepSDKCall(SDKCall_Raw);

    if (!PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "CBaseClient::UpdateAcknowledgedFramecount"))
    {
        CloseHandle(hGameConf);
        SetFailState("PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, \"CBaseClient::UpdateAcknowledgedFramecount\" failed!");
        return;
    }
    CloseHandle(hGameConf);

    PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);

    g_hCBaseClient_UpdateAcknowledgedFramecount = EndPrepSDKCall();

}

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    CreateNative("ClientFullUpdate", Native_FullUpdate);
    RegPluginLibrary("FullUpdate");

    return APLRes_Success;
}

bool FullUpdate(int client)
{
    if(IsFakeClient(client) || IsClientSourceTV(client) || IsClientReplay(client)) {
        return false;
    }

    // The IClient vtable is +4 from the IGameEventListener2 (CBaseClient) vtable due to multiple inheritance.
    Address pIClient = GetClientIClient(client) - view_as<Address>(4);

    if (!pIClient)
        return false;

    SDKCall(g_hCBaseClient_UpdateAcknowledgedFramecount, pIClient, -1);

    return true;
}

public int Native_FullUpdate(Handle plugin, int numParams)
{
    int client = GetNativeCell(1);

    if (client > MaxClients || client <= 0)
    {
        ThrowNativeError(SP_ERROR_NATIVE, "Client is not valid.");
        return 0;
    }

    if (!IsClientInGame(client))
    {
        ThrowNativeError(SP_ERROR_NATIVE, "Client is not in-game.");
        return 0;
    }

    if (IsFakeClient(client))
    {
        ThrowNativeError(SP_ERROR_NATIVE, "Client is fake-client.");
        return 0;
    }

    return FullUpdate(client);
}

public Action Command_FullUpdate(int client, int args)
{
    AdminId aId = GetUserAdmin(client);
    bool bOwner = GetAdminFlag(aId, Admin_Custom6);

    if(!bOwner) {
        return Plugin_Handled;
    }

    FullUpdate(client);
    return Plugin_Handled;
}

public void OnRoundIsLive()
{
    for(int i = 1; i <= MaxClients; i++) 
    {
        if(IsClientInGame(i) && !IsFakeClient(i)) 
        {
            ClientFullUpdate(i);
        }

    }
}

stock Handle GetConfig()
{
    static Handle hGameConf = INVALID_HANDLE;

    if (hGameConf == INVALID_HANDLE)
    {
        hGameConf = LoadGameConfigFile("FullUpdate.games");
    }

    return hGameConf;
}

stock Address GetBaseServer()
{
    static Address pBaseServer = Address_Null;

    if (pBaseServer == Address_Null)
    {
        pBaseServer = GameConfGetAddress(GetConfig(), "CBaseServer");
    }

    return pBaseServer;
}

stock Address GetIClient(int slot)
{
    static Handle hGetClient = INVALID_HANDLE;

    if (hGetClient == INVALID_HANDLE)
    {
        StartPrepSDKCall(SDKCall_Raw);
        PrepSDKCall_SetFromConf(GetConfig(), SDKConf_Virtual, "CBaseServer::GetClient");
        PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
        PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
        hGetClient = EndPrepSDKCall();
    }

    return view_as<Address>(SDKCall(hGetClient, GetBaseServer(), slot));
}

stock int GetPlayerSlot(Address pIClient)
{
    static Handle hPlayerSlot = INVALID_HANDLE;

    if (hPlayerSlot == INVALID_HANDLE)
    {
        StartPrepSDKCall(SDKCall_Raw);
        PrepSDKCall_SetFromConf(GetConfig(), SDKConf_Virtual, "CBaseClient::GetPlayerSlot");
        PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
        hPlayerSlot = EndPrepSDKCall();
    }

    return SDKCall(hPlayerSlot, pIClient);
}

stock Address GetClientIClient(int client)
{
    if(IsFakeClient(client) || IsClientSourceTV(client) || IsClientReplay(client)) {
        return view_as<Address>(-1);
    }

    //Engine client index is entity index minus one
    return GetIClient(client - 1);
} 
"Games"
{
        "#default"
        {
                "#supported"
                {
                        "engine"        "orangebox_valve"
                        "engine"        "css"
                        "engine"        "csgo"
                        "engine"        "left4dead2"
                }

                "Addresses"
                {
                        "CBaseServer"
                        {
                                "windows"
                                {
                                        "signature"     "CVEngineServer::CreateFakeClient"                              
                                        "read"          "8"
                                }
                                "linux"
                                {
                                        "signature"     "sv"
                                }
                                "mac"
                                {
                                        "signature"     "sv"
                                }
                        }
                }

                "Signatures"
                {
                        "CVEngineServer::CreateFakeClient"
                        {
                                "library"       "engine"
                                "windows"       "\x55\x8B\xEC\x56\xFF\x2A\x2A\xB9\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x8B"
                        }

                        "sv"
                        {
                                "library"       "engine"
                                "linux"         "@sv"
                                "mac"           "@sv"
                        }
                }
        }

        "left4dead2"
        {
                "Signatures"
                {
                        "CBaseClient::OnRequestFullUpdate"
                        {
                                "library"       "engine"
                                "windows"       "\x55\x8B\xEC\x56\x8B\xF1\x8B\x8E\x30\x02\x00\x00"
                                "linux"         "@_ZN11CBaseClient19OnRequestFullUpdateEPKc"
                        }
                }

                "Offsets"
                {
                        "CBaseServer::GetClient"
                        {
                                "windows"       "6"
                                "linux"         "7"
                                "mac"           "7"
                        }

                        "CBaseClient::GetPlayerSlot"
                        {
                                // Windows not in l4d2 vtable
                                "linux"         "4"
                                "mac"           "4"
                        }

                        "CBaseClient::UpdateAcknowledgedFramecount"
                        {
                                "windows"       "4"
                                "linux"         "50"
                        }
                }
        }
}
#if defined _FullUpdate_Included
    #endinput
#endif
#define _FullUpdate_Included

native bool ClientFullUpdate(int client);

public SharedPlugin __pl_FullUpdate =
{
    name = "FullUpdate",
    file = "FullUpdate.smx",
#if defined REQUIRE_PLUGIN
    required = 1,
#else
    required = 0,
#endif
};

#if !defined REQUIRE_PLUGIN
public __pl_FullUpdate_SetNTVOptional()
{
    MarkNativeAsOptional("ClientFullUpdate");
}
#endif
lunatixxx commented 2 years ago

First try and i still had invisible infecteds for two rounds in a row, i did not alt tab or had lags. At least other people did not complain for now.

SirPlease commented 2 years ago

You'd be better off doing it when they leave the saferoom, not on round start.

lunatixxx commented 1 year ago

It seems to create weird bugs client side like a door was invisible and i could not interact with it, so yes maybe forcing fullupdate after round start is better.

Maybe the consistency check should not happen on connect but after round start too?

lunatixxx commented 1 year ago

It seems to create weird bugs client side like a door was invisible and i could not interact with it, so yes maybe forcing fullupdate after round start is better.

Maybe the consistency check should not happen on connect but after round start too?

Oh maybe remove that i did not see

AdminId aId = GetUserAdmin(client);
    bool bOwner = GetAdminFlag(aId, Admin_Custom6);

    if(!bOwner) {
        return Plugin_Handled;
    }

Also i don't think that dong it when survivors leave saferoom is a good idea, create frustration.

Maybe:

public void OnRoundIsLive()
{
    PrintToConsoleAll("[SM] All players got correctly resynchronized with the server");

    CreateTimer(1.0, TimeToSyncEveryone);
}

public Action TimeToSyncEveryone(Handle timer)
{
    for(int i = 1; i <= MaxClients; i++) 
    {
        if(IsClientInGame(i) && !IsFakeClient(i)) 
        {
            ClientFullUpdate(i);
        }

    }
}
lunatixxx commented 1 year ago

Yep no don't use that never, it's creating more problems than it resolves from what i saw.

I am still curious about what they could have changed in the code of CSGO so this thing with invisible players does not happen, any idea?

This was a problem too in TF2 apparently https://www.reddit.com/r/tf2/comments/2jf2f7/psa_getting_killed_by_invisible_players_try_this/

This is obviously an engine bug, that valve will never fix.

SirPlease commented 1 year ago

I'm fairly positive that if we can't get the forced client update to work reliably, then there's no solving this one on our end. I'll give this a bit of testing later, thanks for providing the code.

lunatixxx commented 1 year ago

Maybe it will be interesting to create a plugin that reply to a player saying in chat the words "invisible" to provide him this reddit link, so people not used to it understand what happens.

I can't believe that the netcode of this game is so sensitive as ping of players is stable no lag, it happens mostly after a map change. And sometime nothing will happen for a while even if a player have unstable ping.

lunatixxx commented 1 year ago

And why does it only happen on humans players? I never saw invisible bots.

lunatixxx commented 1 year ago

This happened to a friend and his ping was stable for the whole game, once again after a map loading like 95% of cases that i have see. Typically he said that he had stuttering while map loadings with his linux build, but anyway everyone had this bug at least once.

I don't think it is related to connection at all but because of something in the map loading and for some reason it seems that you can not fix this bug before it happened to you, as i tested with the fullupdate for everyone before round start. Player still had the bug, only fixed once he complain about invisible infected and type the command in console (or reconnect to server).

lunatixxx commented 1 year ago

sv_pure 0 does not help to fix the issue, as this is is the value which is on official servers. Maybe something about sourcemod? tickrate extension? lerp?

By the way record stop command can also fix entity bug like the smoker tongue staying on a survivor after a round end, and this was not a client side issue many players were seeing it.

Anyway as you can not fix the bug before it happens it seems, maybe giving access to it with a command only once by round to each person will be a step up for those that do not know the command or how to access the console.

lunatixxx commented 1 year ago

Ok this is a lost cause and definitely an engine bug, this happen too on csgo but very rarely...

https://www.reddit.com/r/GlobalOffensive/comments/53hvu5/i_got_killed_by_an_invisible_player_wtf/

lunatixxx commented 1 year ago

I modified the code a little:

public void OnRoundIsLive()
{
    if (InSecondHalfOfRound() == false)
    {
        CreateTimer(0.5, SyncEveryoneFirstRound);
    }
}

public void OnReadyUpInitiate() // early fix for some weird entities bugs spawning and affectings only certains client on second round  caused by the full update
{
     if (InSecondHalfOfRound() == true)
     {
        CreateTimer(1.0, SyncEveryoneSecondRound);
     }
}

public Action SyncEveryoneFirstRound(Handle timer)
{
    for(int i = 1; i <= MaxClients; i++) 
    {
        if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) 
        {
            PrintToConsoleAll("[SM] All players got resynchronized to server, it should fix some bugs.");
            ClientFullUpdate(i);
        }
    }

    return Plugin_Stop;
}

public Action SyncEveryoneSecondRound(Handle timer)
{
    for(int i = 1; i <= MaxClients; i++) 
    {
        if (IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) 
        {
            PrintToConsoleAll("[SM] All players got resynchronized to server, it should fix some bugs.");
            ClientFullUpdate(i);
        }

    }

    return Plugin_Stop;
}
lunatixxx commented 1 year ago

This is not necessary client sided as sometimes the bug happens at the same time for multiple players, could be server or engine.

lunatixxx commented 1 year ago

Can DDOS protection block some network packets intermittently and cause this problem?

lunatixxx commented 1 year ago

This now happen with bots and even the witch.