• Please make sure you are familiar with the forum rules. You can find them here: https://forums.tripwireinteractive.com/index.php?threads/forum-rules.2334636/

Code Serious compatibility problem of few mutators and ServerPerks

Dr_Killjoy

Grizzled Veteran
Sep 30, 2012
259
0
I encountered this problem when I was writing new version of AdminControlv2. I finished this new version, tested it and give it public access. But after that, many people using it on their servers with ServerPerks noticed serious bug - some random players didnt received perks to select list and trader list is empty. After some time I met this bug in the new versions of ScrnBalance and in one more mutators. One man supposed that this issue is related to fast connection of that players and lowering MaxClientRate can help. Maybe it helps in some cases, but mostly lowering MaxClientRate is uacceptable due to productiveness problems. As I can see, something happens with ClientPerkRepLink that is responsible for trader list, but I didnt find out more. This problem doesnt allow to properly use AdminControlv2 and other important mutators. I wanted to make thread for AdminControlv2 on tripwire forums but I cant do this until this bug isnt fixed. So I asking for help of experienced coders, maybe someone can find out something about this issue.
 
I don't have the full view over serverperks code, but I have had the same issue with one of my mods last year. This happens when the server already wants to send the stuff but the client has not received the respective actor for that (ClientPerkRepLink in our case).
I think this can be solved by simply increasing the delay time within ClientPerkRepLink:
Code:
Auto state RepSetup
{
    final function InitDLCCheck()
    {
        local int i;
        
        for( i=(ShopInventory.Length-1); i>=0; --i )
        {
            if( class<KFWeapon>(ShopInventory[i].PC.Default.InventoryType)!=none
             && (class<KFWeapon>(ShopInventory[i].PC.Default.InventoryType).Default.AppID>0
             || class<KFWeapon>(ShopInventory[i].PC.Default.InventoryType).Default.UnlockedByAchievement!=-1) )
                ShopInventory[i].bDLCLocked = 1;
        }
    }
Begin:
    if( Level.NetMode==NM_Client )
        Stop;
    Sleep(1.f); [COLOR=DarkOrange]//Set this to Sleep(5.f), so clients have enough time to receive the actor[/COLOR]
[...]
 
Last edited:
Upvote 0
To prevent it 100% you have to create some kind of handshake between client and server to achnowledge that the client is ready to receive data.
A 5 seconds delay, however, should cover 99,99% of all cases unless there are players with 5000+ pings or crazy lag-spikes.
Well, it is up to Marco how to address it eventually, but what seems weird to me is that lowering 'MaxClientRate' helps whereas it should be the other way round to my understanding.
 
Upvote 0
Increasing sleep time can't help, because that isn't a case. If client didn't received all weapons or categories, the server will be sending them again and again (it is checking ClientAccknowledged variables). This isn't the case.

The worst part as I didn't encountered this bug myself. Never. So I couldn't do any checks.

I suspect one of the following:
  1. ServerStStats couldn't spawn for some reason.
  2. ClientPerkRepLink actor wasn't replicated at all.
  3. ClientPerkRepLink stuck in RepSetup state. bRepCompleted flag wasn't set, disabling showing content of buy menu.

You can check #1 by typing "MUTATE DEBUG" in console, when you get this bug. If ServerStStats is spawned, there should be the following line in the output:
Code:
Debug info: ... [U]ServerStStats[/U]
 
Upvote 0
Increasing sleep time can't help, because that isn't a case. If client didn't received all weapons or categories, the server will be sending them again and again (it is checking ClientAccknowledged variables). This isn't the case.
[...]
Well I don't have the full overview and understanding how all works together as I have not cared about server perks that much (so far).
But maybe the process of sending it over and over again does somehow not work (just to cover all possibilities).

Edit: Do the perks to be sent also have some kind of client acknowledgement?
 
Last edited:
Upvote 0
I'll try to explain you, how this works. Hoping that after explaining to you I'll be able to understand it myself too :)

  1. ClientPerkRepLink is spawned in ServerStStats.PreBeginPlay().ServerPerksMut.CheckReplacement() catches ClientPerkRepLink and fills it with data: weapons, custom chars, smiles.
  2. ServerPerksMut.CheckReplacement() catches ServerStStats and fills it with perk data.
  3. ClientPerkRepLink sends all weapons, then categories to the client, taking 0.1s pauses between subsequent sends. For example, it takes 10 seconds to send 100 weapons.
  4. ClientPerkRepLink.ClientSendAcknowledge() is called, waiting 1s for client to acknowledge server how many weapons and categories are received.
  5. If client didn't received all weapons and categories (or didn't sent ack in time), the entire weapon sending process will be repeated (steps 4-6).
  6. When client confirms that all weapons and categories are successfully received, server starts sending custom characters, taking 0.15s pauses in between.
  7. After that smiles are being sent. There are no ACK requests on smile tags, and that's is absolutely right, because player can normally play even without seeing any dumb pictures in the chat :cool:
  8. After all weapons, categories, characters and smile are being sent, servers calls ClientAllReceived(), which sets bRepCompleted=true on client side. Starting from this point trader menu items can be displayed.

The problems I see:[/U]
Code:
Auto state RepSetup
{
Begin:
    if( Level.NetMode==NM_Client )
        Stop;
    Sleep(1.f);
    [COLOR="Orange"]NetUpdateFrequency = 0.5f;[/COLOR]

    if( NetConnection(StatObject.PlayerOwner.Player)!=None ) // Network client.
    {
        ClientReceiveURL(ServerWebSite,StatObject.PlayerOwner.GetPlayerIDHash());

        // Now MAKE SURE client receives the full inventory list.
        [COLOR="Yellow"]while( ClientAccknowledged[0]<ShopInventory.Length || ClientAccknowledged[1]<ShopCategories.Length )[/COLOR]
        {
            for( SendIndex=0; SendIndex<ShopInventory.Length; ++SendIndex )
            {
                ClientReceiveWeapon(SendIndex,ShopInventory[SendIndex].PC,ShopInventory[SendIndex].CatNum);
                Sleep(0.1f);
            }
            for( SendIndex=0; SendIndex<ShopCategories.Length; ++SendIndex )
            {
                ClientReceiveCategory(SendIndex,ShopCategories[SendIndex]);
                Sleep(0.1f);
            }
            ClientSendAcknowledge();
            [COLOR="orange"]Sleep(1.f);[/COLOR]
        }

        // Send client all the custom characters.
        while( ClientAckSkinNum<CustomChars.Length )
        {
            ClientReceiveChar(CustomChars[ClientAckSkinNum],ClientAckSkinNum);
            Sleep(0.15f);
        }
        
        // Send all chat icons.
        for( SendIndex=0; SendIndex<SmileyTags.Length; ++SendIndex )
        {
            ClientReceiveTag(SmileyTags[SendIndex].SmileyTex,SmileyTags[SendIndex].SmileyTag,SmileyTags[SendIndex].bInCAPS);
            Sleep(0.1f);
        }
        SmileyTags.Length = 0;
    }
    else
    {
        bReceivedURL = true;
        InitDLCCheck();
    }

    ClientAllReceived();

    GoToState('UpdatePerkProgress');
}

First one, why weapon categories and weapons are put together in the same loop? For example, if client hasn't received one category only (or most likely haven't managed to send ACK to server in time), then server re-sends all weapons again, which takes another 10+ seconds.

Second, server-to-client replication happens once per 2 seconds (NetUpdateFrequency=0.5f;), client-to-server replication happens once per second (default LinkedReplicationInfo.NetUpdateFrequency=1). So in worst scenario it may take up to 3 seconds to receive ACK back from client. Server waits only 1 second (Sleep(1.f);) after calling ClientSendAcknowledge(). Most likely it will start re-sending weapons again before ACK received.

Third, SmileyTags should be sent after calling ClientAllReceived(), because this data is unimportant. Unless you can't do shopping before seeing a portrait of Chuck Norris first :cool:


But even after fixing all issues above I don't like the way, how weapons are replicated, or, to be precise, how ACKs are being sent. Imo it would be much better to do checks on client side. Server should send total amount of weapons, categories and custom characters to the client during the initial replication. Then client checks, if all this data is received, and if not, it requests server to send missing data.
 
Last edited:
Upvote 0
Thanks for the full description, I got that by now but the perks is what I am concerned about.

ServerStStats:
Code:
function PostBeginPlay()
{
    if( Rep!=None )
        Rep.SpawnCustomLinks();

    bStatsReadyNow = !MutatorOwner.bUseRemoteDatabase;
    MyStatsObject = MutatorOwner.GetStatsForPlayer(PlayerOwner);
    if( MyStatsObject.bStatsChanged )
        bStatsReadyNow = true; // Server has already the most fresh stats for this client.
    KFPlayerReplicationInfo(PlayerOwner.PlayerReplicationInfo).ClientVeteranSkill = None;
    if( !bStatsReadyNow )
    {
        Timer();
        SetTimer(1+FRand(),true);
        return;
    }
    bSwitchIsOKNow = true;
    if( MyStatsObject!=None )
    {
        // Apply selected character.
        if( MyStatsObject.SelectedChar!="" )
            ApplyCharacter(MyStatsObject.SelectedChar);
        else if( Rep.bNoStandardChars && Rep.CustomChars.Length>0 )
        {
            MyStatsObject.SelectedChar = Rep.PickRandomCustomChar();
            ApplyCharacter(MyStatsObject.SelectedChar);
        }

        RepCopyStats();
        bHasChanged = true;
        CheckPerks(true);
        ServerSelectPerkName(MyStatsObject.GetSelectedPerk());
    }
    else CheckPerks(true);
    if( MutatorOwner.bForceGivePerk && KFPlayerReplicationInfo(PlayerOwner.PlayerReplicationInfo).ClientVeteranSkill==None )
        ServerSelectPerk(Rep.PickRandomPerk());
    bSwitchIsOKNow = false;
    bHadSwitchedVet = false;
    [COLOR=Orange]SetTimer(0.1,false);[/COLOR]
}

function Timer()
{
    if( !bStatsReadyNow )
    {
        MutatorOwner.RequestStats(Self);
        return;
    }
    if( !bStHasInit )
    {
        if( PlayerOwner.SteamStatsAndAchievements!=None && PlayerOwner.SteamStatsAndAchievements!=Self )
            PlayerOwner.SteamStatsAndAchievements.Destroy();
        PlayerOwner.SteamStatsAndAchievements = Self;
        PlayerOwner.PlayerReplicationInfo.SteamStatsAndAchievements = Self;
        bStHasInit = true;
        [COLOR=Orange]Rep.SendClientPerks();[/COLOR]
    }
    if( bStatsChecking )
    {
        bStatsChecking = false;
        CheckPerks(false);
    }
}
ClientPerkRepLink:
Code:
final function SendClientPerks()
{
    local byte i;

    if( !StatObject.bStatsReadyNow )
        return;
    NextRepTime = Level.TimeSeconds+2.f;
    for( i=0; i<CachePerks.Length; i++ )
        [COLOR=Orange]ClientReceivePerk(i,CachePerks[i].PerkClass,CachePerks[i].CurrentLevel);[/COLOR]
}

[COLOR=Orange]simulated function ClientReceivePerk( int Index, class<SRVeterancyTypes> V, byte Level )
[COLOR=White]{
    // Setup correct icon for trader.
    if( V.Default.PerkIndex<255 && V.Default.OnHUDIcon!=None )
    {
        if( ShopPerkIcons.Length<=V.Default.PerkIndex )
            ShopPerkIcons.Length = V.Default.PerkIndex+1;
        ShopPerkIcons[V.Default.PerkIndex] = V.Default.OnHUDIcon;
    }

    if( CachePerks.Length<=Index )
        CachePerks.Length = (Index+1);
    CachePerks[Index].PerkClass = V;
    CachePerks[Index].CurrentLevel = Level;
}[/COLOR][/COLOR]
I fail to see any acknowledgement here, so if a player has a higher ping (over 100) and or the ClientPerkRepLink will be put in a later package (lowest NetPriority) due to the big amount of data that has to be send to a player at startup it might happen that the server has already tried to send the perks before the UActorChannel has been build up.
 
Upvote 0