SilvDev / Left4DHooks

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

L4D2 version of L4DDirect_RecomputeTeamScores #10

Closed armanossiloko closed 2 days ago

armanossiloko commented 2 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 1 month 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 1 month 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 1 month ago

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

Yes

armanossiloko commented 1 month 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 1 month 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 1 month 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 1 month ago

As i know it can't be full for ChangeClientTeam

armanossiloko commented 1 month 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 days ago

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