SilvDev / Left4DHooks

Left 4 Downtown and L4D Direct conversion and merger.
GNU General Public License v3.0
50 stars 11 forks source link

L4D2 version of L4DDirect_RecomputeTeamScores #10

Closed armanossiloko closed 2 months ago

armanossiloko commented 4 months ago

Potentially not the right place to ask this, but calling L4D2_SetVersusCampaignScores updates the scores properly, however that change is not propagated to the scoreboard. Looking into the hooks, there is a native void L4DDirect_RecomputeTeamScores(); which is labeled for L4D1 use only.

Is there something similar (or planned to be added) in existence for L4D2 as well?

spumer commented 3 months ago

We are use this approach to set scores for L4D2

SetScores( iSurvScore, iInfScore )
{
    new bool:bAreTeamsFlipped = bool:GameRules_GetProp( "m_bAreTeamsFlipped" );

    new iSurvTeamIndex = bAreTeamsFlipped ? 1 : 0;
    new iInfTeamIndex = bAreTeamsFlipped ? 0 : 1;

    /*new scores[2];
    scores[0] = iSurvScore;
    scores[1] = iInfScore;*/

    //L4D2_SetVersusCampaignScores( scores ); // visible scores
    L4D2_SetVersusCampaignScores123( iSurvScore, iInfScore ); // visible scores
    L4D2Direct_SetVSCampaignScore( iSurvTeamIndex, iSurvScore ); // real scores
    L4D2Direct_SetVSCampaignScore( iInfTeamIndex, iInfScore );

    CPrintToChatAll( "Set Scores > (Survivors) {olive}%d{default} : {olive}%d{default} (Infected)", iSurvScore, iInfScore );
}

bool:L4D2_SetVersusCampaignScores123( surv_score, inf_score )
{
    static Handle:hSetVersusCampaignScores = INVALID_HANDLE;

    if( hSetVersusCampaignScores == INVALID_HANDLE )
    {
        new Handle:hGameData = LoadGameConfigFile( FILE_DOWNTOWN_GAMEDATA );

        if( hGameData == INVALID_HANDLE )
        {
            LogError( "Unable to find 'gamedata/%s.txt'", FILE_DOWNTOWN_GAMEDATA );
            return false;
        }

        StartPrepSDKCall( SDKCall_GameRules );
        PrepSDKCall_SetFromConf( hGameData, SDKConf_Signature, "SetCampaignScores" );
        PrepSDKCall_AddParameter( SDKType_PlainOldData, SDKPass_Plain );
        PrepSDKCall_AddParameter( SDKType_PlainOldData, SDKPass_Plain );
        hSetVersusCampaignScores = EndPrepSDKCall();
        CloseHandle( hGameData );

        if( hSetVersusCampaignScores == INVALID_HANDLE )
        {
            LogError( "Signature SetCampaignScores is broken. Check 'gamedata/%s.txt'", FILE_DOWNTOWN_GAMEDATA );
            return false;
        }
    }

    SDKCall( hSetVersusCampaignScores, surv_score, inf_score );
    return true;
}

Gamedata:

            /*
             * CTerrorGameRules::SetCampaignScores(int,int)
             * Search for unique string "singlechapter"
             * -> has two xref from same function, CTerrorGameRules::IsSingleChapterMode()
             * -> has two xref, one is CRestartGameIssue::ExecuteCommand() (exclude the other, CServerGameDLL::ServerHibernationUpdate(), which has string "FCVAR_NEVER_AS_STRING")
             * -> CRestartGameIssue::ExecuteCommand() calls CDirectorVersusMode::VoteRestartVersusLevel() (fourth call..?)
             * -> first call is CTerrorGameRules::SetCampaignScores()
             * make sure to double check uniqueness when done
             */
            "SetCampaignScores" 
            {
                "library"  "server"
                "linux"    "@_ZN16CTerrorGameRules17SetCampaignScoresEii"
                "windows"  "\x55\x8B\xEC\x56\x57\x8B\x7D\x08\x8B\xF1\x39\xBE\x2A\x2A\x2A\x2A\x74\x2A\xE8\x2A\x2A\x2A\x2A\x89\xBE\x2A\x2A\x2A\x2A\x8B"
                /* 55 8B EC 56 57 8B 7D 08 8B F1 39 BE ? ? ? ? 74 ? E8 ? ? ? ? 89 BE ? ? ? ? 8B */
            }
armanossiloko commented 3 months ago

We are use this approach to set scores for L4D2

SetScores( iSurvScore, iInfScore )
{
  new bool:bAreTeamsFlipped = bool:GameRules_GetProp( "m_bAreTeamsFlipped" );

  new iSurvTeamIndex = bAreTeamsFlipped ? 1 : 0;
  new iInfTeamIndex = bAreTeamsFlipped ? 0 : 1;

  /*new scores[2];
  scores[0] = iSurvScore;
  scores[1] = iInfScore;*/

  //L4D2_SetVersusCampaignScores( scores ); // visible scores
  L4D2_SetVersusCampaignScores123( iSurvScore, iInfScore ); // visible scores
  L4D2Direct_SetVSCampaignScore( iSurvTeamIndex, iSurvScore ); // real scores
  L4D2Direct_SetVSCampaignScore( iInfTeamIndex, iInfScore );

  CPrintToChatAll( "Set Scores > (Survivors) {olive}%d{default} : {olive}%d{default} (Infected)", iSurvScore, iInfScore );
}

bool:L4D2_SetVersusCampaignScores123( surv_score, inf_score )
{
  static Handle:hSetVersusCampaignScores = INVALID_HANDLE;

  if( hSetVersusCampaignScores == INVALID_HANDLE )
  {
      new Handle:hGameData = LoadGameConfigFile( FILE_DOWNTOWN_GAMEDATA );

      if( hGameData == INVALID_HANDLE )
      {
          LogError( "Unable to find 'gamedata/%s.txt'", FILE_DOWNTOWN_GAMEDATA );
          return false;
      }

      StartPrepSDKCall( SDKCall_GameRules );
      PrepSDKCall_SetFromConf( hGameData, SDKConf_Signature, "SetCampaignScores" );
      PrepSDKCall_AddParameter( SDKType_PlainOldData, SDKPass_Plain );
      PrepSDKCall_AddParameter( SDKType_PlainOldData, SDKPass_Plain );
      hSetVersusCampaignScores = EndPrepSDKCall();
      CloseHandle( hGameData );

      if( hSetVersusCampaignScores == INVALID_HANDLE )
      {
          LogError( "Signature SetCampaignScores is broken. Check 'gamedata/%s.txt'", FILE_DOWNTOWN_GAMEDATA );
          return false;
      }
  }

  SDKCall( hSetVersusCampaignScores, surv_score, inf_score );
  return true;
}

Gamedata:

          /*
           * CTerrorGameRules::SetCampaignScores(int,int)
           * Search for unique string "singlechapter"
           * -> has two xref from same function, CTerrorGameRules::IsSingleChapterMode()
           * -> has two xref, one is CRestartGameIssue::ExecuteCommand() (exclude the other, CServerGameDLL::ServerHibernationUpdate(), which has string "FCVAR_NEVER_AS_STRING")
           * -> CRestartGameIssue::ExecuteCommand() calls CDirectorVersusMode::VoteRestartVersusLevel() (fourth call..?)
           * -> first call is CTerrorGameRules::SetCampaignScores()
           * make sure to double check uniqueness when done
           */
          "SetCampaignScores" 
          {
              "library"  "server"
              "linux"    "@_ZN16CTerrorGameRules17SetCampaignScoresEii"
              "windows"  "\x55\x8B\xEC\x56\x57\x8B\x7D\x08\x8B\xF1\x39\xBE\x2A\x2A\x2A\x2A\x74\x2A\xE8\x2A\x2A\x2A\x2A\x89\xBE\x2A\x2A\x2A\x2A\x8B"
              /* 55 8B EC 56 57 8B 7D 08 8B F1 39 BE ? ? ? ? 74 ? E8 ? ? ? ? 89 BE ? ? ? ? 8B */
          }

Does that automatically show the updated scores in the scoreboard for everyone?

spumer commented 3 months ago

Does that automatically show the updated scores in the scoreboard for everyone?

Yes

armanossiloko commented 3 months ago

Does that automatically show the updated scores in the scoreboard for everyone?

Yes

So, gave that a shot and it indeed updates the scoreboard properly. The scores work fine. However, if I switch to the Spectators team, the scoreboard still shows the "old" scores. xD It's not too big of a deal as spectators usually don't monitor the scores, but I figured it'd be worth mentioning.

spumer commented 3 months ago

Does that automatically show the updated scores in the scoreboard for everyone?

Yes

So, gave that a shot and it indeed updates the scoreboard properly. The scores work fine. However, if I switch to the Spectators team, the scoreboard still shows the "old" scores. xD It's not too big of a deal as spectators usually don't monitor the scores, but I figured it'd be worth mentioning.

Yea, we have workaround for that

public Action:Event_LeftStartArea( Handle:event, const String:name[], bool:dontBroadcast )
{
    // It could be wrong even on first found
    // if player enter surv team after map change
    // So we need to switch spec team on each round start
    CreateTimer( 1.0, Timer_FixSpecScoreBug );
}

// Spec Score Bug. Wrong Score by TAB, need switch team to fix score.
public Action:Timer_FixSpecScoreBug( Handle:timer )
{
    for( new i = 1; i <= MaxClients; i++ )
    {
        if( IsClientInGame( i ) && GetClientTeam( i ) == L4D_TEAM_SPECTATE )
        {
            // team switch will trigger NextFrame_HideSpecBars
            ChangeClientTeam( i, L4D_TEAM_INFECTED );
            ChangeClientTeam( i, L4D_TEAM_SPECTATE );
        }
    }
}
armanossiloko commented 3 months ago

Does that automatically show the updated scores in the scoreboard for everyone?

Yes

So, gave that a shot and it indeed updates the scoreboard properly. The scores work fine. However, if I switch to the Spectators team, the scoreboard still shows the "old" scores. xD It's not too big of a deal as spectators usually don't monitor the scores, but I figured it'd be worth mentioning.

Yea, we have workaround for that

public Action:Event_LeftStartArea( Handle:event, const String:name[], bool:dontBroadcast )
{
  // It could be wrong even on first found
  // if player enter surv team after map change
  // So we need to switch spec team on each round start
  CreateTimer( 1.0, Timer_FixSpecScoreBug );
}

// Spec Score Bug. Wrong Score by TAB, need switch team to fix score.
public Action:Timer_FixSpecScoreBug( Handle:timer )
{
  for( new i = 1; i <= MaxClients; i++ )
  {
      if( IsClientInGame( i ) && GetClientTeam( i ) == L4D_TEAM_SPECTATE )
      {
          // team switch will trigger NextFrame_HideSpecBars
          ChangeClientTeam( i, L4D_TEAM_INFECTED );
          ChangeClientTeam( i, L4D_TEAM_SPECTATE );
      }
  }
}

Would this work if the Infected team is full?

spumer commented 3 months ago

As i know it can't be full for ChangeClientTeam

armanossiloko commented 3 months ago

As i know it can't be full for ChangeClientTeam

Okay, fair enough. Thanks.

I think this overall is a decent workaround (though a bit hacky at times xD) for the time being until we perhaps get a proper L4D2 version of the RecomputeTeamScores if @SilvDev finds some spare time for it sometime in the future. Thanks, @spumer.

SilvDev commented 2 months ago

Thanks, added into 1.152. Please test and open this topic again if there are any issues.

armanossiloko commented 3 weeks ago

We are use this approach to set scores for L4D2

SetScores( iSurvScore, iInfScore )
{
  new bool:bAreTeamsFlipped = bool:GameRules_GetProp( "m_bAreTeamsFlipped" );

  new iSurvTeamIndex = bAreTeamsFlipped ? 1 : 0;
  new iInfTeamIndex = bAreTeamsFlipped ? 0 : 1;

  /*new scores[2];
  scores[0] = iSurvScore;
  scores[1] = iInfScore;*/

  //L4D2_SetVersusCampaignScores( scores ); // visible scores
  L4D2_SetVersusCampaignScores123( iSurvScore, iInfScore ); // visible scores
  L4D2Direct_SetVSCampaignScore( iSurvTeamIndex, iSurvScore ); // real scores
  L4D2Direct_SetVSCampaignScore( iInfTeamIndex, iInfScore );

  CPrintToChatAll( "Set Scores > (Survivors) {olive}%d{default} : {olive}%d{default} (Infected)", iSurvScore, iInfScore );
}

bool:L4D2_SetVersusCampaignScores123( surv_score, inf_score )
{
  static Handle:hSetVersusCampaignScores = INVALID_HANDLE;

  if( hSetVersusCampaignScores == INVALID_HANDLE )
  {
      new Handle:hGameData = LoadGameConfigFile( FILE_DOWNTOWN_GAMEDATA );

      if( hGameData == INVALID_HANDLE )
      {
          LogError( "Unable to find 'gamedata/%s.txt'", FILE_DOWNTOWN_GAMEDATA );
          return false;
      }

      StartPrepSDKCall( SDKCall_GameRules );
      PrepSDKCall_SetFromConf( hGameData, SDKConf_Signature, "SetCampaignScores" );
      PrepSDKCall_AddParameter( SDKType_PlainOldData, SDKPass_Plain );
      PrepSDKCall_AddParameter( SDKType_PlainOldData, SDKPass_Plain );
      hSetVersusCampaignScores = EndPrepSDKCall();
      CloseHandle( hGameData );

      if( hSetVersusCampaignScores == INVALID_HANDLE )
      {
          LogError( "Signature SetCampaignScores is broken. Check 'gamedata/%s.txt'", FILE_DOWNTOWN_GAMEDATA );
          return false;
      }
  }

  SDKCall( hSetVersusCampaignScores, surv_score, inf_score );
  return true;
}

Gamedata:

          /*
           * CTerrorGameRules::SetCampaignScores(int,int)
           * Search for unique string "singlechapter"
           * -> has two xref from same function, CTerrorGameRules::IsSingleChapterMode()
           * -> has two xref, one is CRestartGameIssue::ExecuteCommand() (exclude the other, CServerGameDLL::ServerHibernationUpdate(), which has string "FCVAR_NEVER_AS_STRING")
           * -> CRestartGameIssue::ExecuteCommand() calls CDirectorVersusMode::VoteRestartVersusLevel() (fourth call..?)
           * -> first call is CTerrorGameRules::SetCampaignScores()
           * make sure to double check uniqueness when done
           */
          "SetCampaignScores" 
          {
              "library"  "server"
              "linux"    "@_ZN16CTerrorGameRules17SetCampaignScoresEii"
              "windows"  "\x55\x8B\xEC\x56\x57\x8B\x7D\x08\x8B\xF1\x39\xBE\x2A\x2A\x2A\x2A\x74\x2A\xE8\x2A\x2A\x2A\x2A\x89\xBE\x2A\x2A\x2A\x2A\x8B"
              /* 55 8B EC 56 57 8B 7D 08 8B F1 39 BE ? ? ? ? 74 ? E8 ? ? ? ? 89 BE ? ? ? ? 8B */
          }

It's been a while since this post, but I just wanted to let you know that in case you need the functionality you posted in the post on your servers, it appears to be broken on Linux. In Versus, when Team 1 is playing, if you set the score of the infected team (Team 2 that has not played as survivors yet) which currently is (e.g) 400 pts to 800pts, it will change the score of the infected team to 800 temporarily, but it will screw up the score of the survivors (aka Team 1). Then, at the end of the round, when the scores are computed by the game, the score of the survivor team will be ok, but the infected score will revert back to its original value prior to you setting it to anything (in this case 400).

SilvDev commented 1 week ago

Is that happening in Left4DHooks 1.153? Or are you referring to the alternative method posted in this comments section? If the latter, try Left4DHooks 1.153.

armanossiloko commented 1 week ago

Is that happening in Left4DHooks 1.153? Or are you referring to the alternative method posted in this comments section? If the latter, try Left4DHooks 1.153.

Left4DHooks does not update the scoreboard at all, so I was using the alternative that @spumer posted, which appears to be buggy. Not sure if I never tested some of the use cases or if Valve broke it in the meantime since the last time spumer posted here.

SilvDev commented 1 week ago

This fix was partially included in 1.153 and after numerous tests it all seems to work including updating the scoreboard. Please test 1.153 and see if that works, this fix is outdated now.

armanossiloko commented 1 week ago

This fix was partially included in 1.153 and after numerous tests it all seems to work including updating the scoreboard. Please test 1.153 and see if that works, this fix is outdated now.

I take it you are referring to using L4D2_SetVersusCampaignScores, correct? aka L4D2_SetVersusCampaignScores should be updating the scoreboard now?

SilvDev commented 1 week ago

Fixes were applied to all Versus score commands.