IW4M-Admin/GameFiles/GameInterface/_integration_shared.gsc
2022-10-23 14:39:39 -05:00

468 lines
14 KiB
Plaintext

Init()
{
level thread Setup();
}
Setup()
{
level endon( "game_ended" );
level.commonFunctions.changeTeam = "ChangeTeam";
level.commonFunctions.getTeamCounts = "GetTeamCounts";
level.commonFunctions.getMaxClients = "GetMaxClients";
level.commonFunctions.getTeamBased = "GetTeamBased";
level.commonFunctions.getClientTeam = "GetClientTeam";
level.commonFunctions.getClientKillStreak = "GetClientKillStreak";
level.commonFunctions.backupRestoreClientKillStreakData = "BackupRestoreClientKillStreakData";
level.commonFunctions.waitTillAnyTimeout = "WaitTillAnyTimeout";
level.overrideMethods[level.commonFunctions.changeTeam] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getTeamCounts] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getTeamBased] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getMaxClients] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getClientTeam] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getClientKillStreak] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = scripts\_integration_base::NotImplementedFunction;
// these can be overridden per game if needed
level.commonKeys.team1 = "allies";
level.commonKeys.team2 = "axis";
level.commonKeys.teamSpectator = "spectator";
level.eventTypes.connect = "connected";
level.eventTypes.disconnect = "disconnect";
level.eventTypes.joinTeam = "joined_team";
level.eventTypes.spawned = "spawned_player";
level.eventTypes.gameEnd = "game_ended";
level.iw4madminIntegrationDefaultPerformance = 200;
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
{
return;
}
if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 )
{
return;
}
level thread OnPlayerConnect();
}
OnPlayerConnect()
{
level endon( level.eventTypes.gameEnd );
for ( ;; )
{
level waittill( level.eventTypes.connect, player );
if ( ![[level.overrideMethods[level.commonFunctions.getTeamBased]]]() )
{
continue;
}
teamToJoin = player GetTeamToJoin();
player [[level.overrideMethods[level.commonFunctions.changeTeam]]]( teamToJoin );
player thread OnClientFirstSpawn();
player thread OnClientJoinedTeam();
player thread OnClientDisconnect();
player thread WaitForClientEvents();
}
}
OnClientDisconnect()
{
level endon( level.eventTypes.gameEnd );
self endon( "disconnect_logic_end" );
for ( ;; )
{
self waittill( level.eventTypes.disconnect );
scripts\_integration_base::LogDebug( "client is disconnecting" );
OnTeamSizeChanged();
self notify( "disconnect_logic_end" );
}
}
OnClientJoinedTeam()
{
self endon( level.eventTypes.disconnect );
for( ;; )
{
self waittill( level.eventTypes.joinTeam );
if ( IsDefined( self.wasAutoBalanced ) && self.wasAutoBalanced )
{
self.wasAutoBalanced = false;
continue;
}
newTeam = self [[level.overrideMethods[level.commonFunctions.getClientTeam]]]();
scripts\_integration_base::LogDebug( self.name + " switched to " + newTeam );
if ( newTeam != level.commonKeys.team1 && newTeam != level.commonKeys.team2 )
{
OnTeamSizeChanged();
scripts\_integration_base::LogDebug( "not force balancing " + self.name + " because they switched to spec" );
continue;
}
properTeam = self GetTeamToJoin();
if ( newTeam != properTeam )
{
self [[level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData]]]( false );
self [[level.overrideMethods[level.commonFunctions.changeTeam]]]( properTeam );
wait ( 0.1 );
self [[level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData]]]( true );
}
}
}
OnClientFirstSpawn()
{
self endon( level.eventTypes.disconnect );
timeoutResult = self [[level.overrideMethods[level.commonFunctions.waitTillAnyTimeout]]]( 30, level.eventTypes.spawned );
if ( timeoutResult != "timeout" )
{
return;
}
scripts\_integration_base::LogDebug( "moving " + self.name + " to spectator because they did not spawn within expected duration" );
self [[level.overrideMethods[level.commonFunctions.changeTeam]]]( level.commonKeys.teamSpectator );
}
OnTeamSizeChanged()
{
if ( level.players.size < 3 )
{
scripts\_integration_base::LogDebug( "not enough clients to autobalance" );
return;
}
if ( !IsDefined( GetSmallerTeam( 1 ) ) )
{
scripts\_integration_base::LogDebug( "teams are not unbalanced enough to auto balance" );
return;
}
toSwap = FindClientToSwap();
curentTeam = toSwap [[level.overrideMethods[level.commonFunctions.getClientTeam]]]();
otherTeam = level.commonKeys.team1;
if ( curentTeam == otherTeam )
{
otherTeam = level.commonKeys.team2;
}
toSwap.wasAutoBalanced = true;
if ( !IsDefined( toSwap.autoBalanceCount ) )
{
toSwap.autoBalanceCount = 1;
}
else
{
toSwap.autoBalanceCount++;
}
toSwap [[level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData]]]( false );
scripts\_integration_base::LogDebug( "swapping " + toSwap.name + " from " + curentTeam + " to " + otherTeam );
toSwap [[level.overrideMethods[level.commonFunctions.changeTeam]]]( otherTeam );
wait ( 0.1 ); // give the killstreak on team switch clear event time to execute
toSwap [[level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData]]]( true );
}
FindClientToSwap()
{
smallerTeam = GetSmallerTeam( 1 );
teamPool = level.commonKeys.team1;
if ( IsDefined( smallerTeam ) )
{
if ( smallerTeam == teamPool )
{
teamPool = level.commonKeys.team2;
}
}
else
{
teamPerformances = GetTeamPerformances();
team1Perf = teamPerformances[level.commonKeys.team1];
team2Perf = teamPerformances[level.commonKeys.team2];
teamPool = level.commonKeys.team1;
if ( team2Perf > team1Perf )
{
teamPool = level.commonKeys.team2;
}
}
client = GetBestSwapCandidate( teamPool );
if ( !IsDefined( client ) )
{
scripts\_integration_base::LogDebug( "could not find candidate to swap teams" );
}
else
{
scripts\_integration_base::LogDebug( "best candidate to swap teams is " + client.name );
}
return client;
}
GetBestSwapCandidate( team )
{
candidates = [];
maxClients = [[level.overrideMethods[level.commonFunctions.getMaxClients]]]();
for ( i = 0; i < maxClients; i++ )
{
candidates[i] = GetClosestPerformanceClientForTeam( team, candidates );
}
candidate = undefined;
foundCandidate = false;
for ( i = 0; i < maxClients; i++ )
{
if ( !IsDefined( candidates[i] ) )
{
continue;
}
candidate = candidates[i];
candidateKillStreak = candidate [[level.overrideMethods[level.commonFunctions.getClientKillStreak]]]();
scripts\_integration_base::LogDebug( "candidate killstreak is " + candidateKillStreak );
if ( candidateKillStreak > 3 )
{
scripts\_integration_base::LogDebug( "skipping candidate selection for " + candidate.name + " because their kill streak is too high" );
continue;
}
if ( IsDefined( candidate.autoBalanceCount ) && candidate.autoBalanceCount > 2 )
{
scripts\_integration_base::LogDebug( "skipping candidate selection for " + candidate.name + " they have been swapped too many times" );
continue;
}
foundCandidate = true;
break;
}
if ( foundCandidate )
{
return candidate;
}
return undefined;
}
GetClosestPerformanceClientForTeam( sourceTeam, excluded )
{
if ( !IsDefined( excluded ) )
{
excluded = [];
}
otherTeam = level.commonKeys.team1;
if ( sourceTeam == otherTeam )
{
otherTeam = level.commonKeys.team2;
}
teamPerformances = GetTeamPerformances();
players = level.players;
choice = undefined;
closest = 9999999;
for ( i = 0; i < players.size; i++ )
{
isExcluded = false;
for ( j = 0; j < excluded.size; j++ )
{
if ( excluded[j] == players[i] )
{
isExcluded = true;
break;
}
}
if ( isExcluded )
{
continue;
}
if ( players[i] [[level.overrideMethods[level.commonFunctions.getClientTeam]]]() != sourceTeam )
{
continue;
}
clientPerformance = players[i] GetClientPerformanceOrDefault();
sourceTeamNewPerformance = teamPerformances[sourceTeam] - clientPerformance;
otherTeamNewPerformance = teamPerformances[otherTeam] + clientPerformance;
candidateValue = Abs( sourceTeamNewPerformance - otherTeamNewPerformance );
scripts\_integration_base::LogDebug( "perf=" + clientPerformance + " candidateValue=" + candidateValue + " src=" + sourceTeamNewPerformance + " dst=" + otherTeamNewPerformance );
if ( !IsDefined( choice ) )
{
choice = players[i];
closest = candidateValue;
}
else if ( candidateValue < closest )
{
scripts\_integration_base::LogDebug( candidateValue + " is the new best value ");
choice = players[i];
closest = candidateValue;
}
}
scripts\_integration_base::LogDebug( choice.name + " is the best candidate to swap" + " with closest=" + closest );
return choice;
}
GetTeamToJoin()
{
smallerTeam = GetSmallerTeam( 1 );
if ( IsDefined( smallerTeam ) )
{
return smallerTeam;
}
teamPerformances = GetTeamPerformances( self );
if ( teamPerformances[level.commonKeys.team1] < teamPerformances[level.commonKeys.team2] )
{
scripts\_integration_base::LogDebug( "Team1 performance is lower, so selecting Team1" );
return level.commonKeys.team1;
}
else
{
scripts\_integration_base::LogDebug( "Team2 performance is lower, so selecting Team2" );
return level.commonKeys.team2;
}
}
GetSmallerTeam( minDiff )
{
teamCounts = [[level.overrideMethods[level.commonFunctions.getTeamCounts]]]();
team1Count = teamCounts[level.commonKeys.team1];
team2Count = teamCounts[level.commonKeys.team2];
maxClients = [[level.overrideMethods[level.commonFunctions.getMaxClients]]]();
if ( team1Count == team2Count )
{
return undefined;
}
if ( team2Count == maxClients / 2 )
{
scripts\_integration_base::LogDebug( "Team2 is full, so selecting Team1" );
return level.commonKeys.team1;
}
if ( team1Count == maxClients / 2 )
{
scripts\_integration_base::LogDebug( "Team1 is full, so selecting Team2" );
return level.commonKeys.team2;
}
sizeDiscrepancy = Abs( team1Count - team2Count );
if ( sizeDiscrepancy > minDiff )
{
scripts\_integration_base::LogDebug( "Team size differs by more than 1" );
if ( team1Count < team2Count )
{
scripts\_integration_base::LogDebug( "Team1 is smaller, so selecting Team1" );
return level.commonKeys.team1;
}
else
{
scripts\_integration_base::LogDebug( "Team2 is smaller, so selecting Team2" );
return level.commonKeys.team2;
}
}
return undefined;
}
GetTeamPerformances( ignoredClient )
{
players = level.players;
team1 = 0;
team2 = 0;
for ( i = 0; i < players.size; i++ )
{
if ( IsDefined( ignoredClient ) && players[i] == ignoredClient )
{
continue;
}
performance = players[i] GetClientPerformanceOrDefault();
clientTeam = players[i] [[level.overrideMethods[level.commonFunctions.getClientTeam]]]();
if ( clientTeam == level.commonKeys.team1 )
{
team1 = team1 + performance;
}
else
{
team2 = team2 + performance;
}
}
result = [];
result[level.commonKeys.team1] = team1;
result[level.commonKeys.team2] = team2;
return result;
}
GetClientPerformanceOrDefault()
{
clientData = self.pers[level.clientDataKey];
performance = level.iw4madminIntegrationDefaultPerformance;
if ( IsDefined( clientData ) && IsDefined( clientData.performance ) )
{
performance = int( clientData.performance );
}
return performance;
}
WaitForClientEvents()
{
self endon( level.eventTypes.disconnect );
for ( ;; )
{
self waittill( level.eventTypes.localClientEvent, event );
if ( event.type == level.eventTypes.clientDataReceived )
{
clientData = self.pers[level.clientDataKey];
}
}
}