create shared integration for performance-based autobalance support
This commit is contained in:
parent
7c1c2e719b
commit
565f22b42e
@ -24,7 +24,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2041" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2042" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Data.Models.Client.Stats;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
|
||||
namespace IW4MAdmin.Application.Extensions;
|
||||
|
||||
@ -18,4 +20,15 @@ public static class ScriptPluginExtensions
|
||||
client.NetworkId
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public static IEnumerable<object> GetClientsStatData(this DbSet<EFClientStatistics> set, int[] clientIds,
|
||||
double serverId)
|
||||
{
|
||||
return set.Where(stat => clientIds.Contains(stat.ClientId) && stat.ServerId == (long)serverId).ToList();
|
||||
}
|
||||
|
||||
public static object GetId(this Server server)
|
||||
{
|
||||
return server.GetIdForServer().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ namespace Data.Context
|
||||
public DbSet<EFClientMessage> ClientMessages { get; set; }
|
||||
|
||||
public DbSet<EFServerStatistics> ServerStatistics { get; set; }
|
||||
public DbSet<EFClientStatistics> ClientStatistics { get; set; }
|
||||
public DbSet<EFHitLocation> HitLocations { get; set; }
|
||||
public DbSet<EFClientHitStatistic> HitStatistics { get; set; }
|
||||
public DbSet<EFWeapon> Weapons { get; set; }
|
||||
|
@ -19,8 +19,10 @@ Setup()
|
||||
level.eventBus.timeoutKey = "timeout";
|
||||
level.eventBus.timeout = 30;
|
||||
|
||||
level.commonFunctions = spawnstruct();
|
||||
level.commonFunctions.SetDvar = "SetDvarIfUninitialized";
|
||||
level.commonFunctions = spawnstruct();
|
||||
level.commonFunctions.setDvar = "SetDvarIfUninitialized";
|
||||
|
||||
level.commonKeys = spawnstruct();
|
||||
|
||||
level.notifyTypes = spawnstruct();
|
||||
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
|
||||
@ -116,17 +118,6 @@ OnPlayerSpawned()
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerDisconnect()
|
||||
{
|
||||
self endon ( "disconnect" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( "disconnect" );
|
||||
self SaveTrackingMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerJoinedTeam()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
@ -245,7 +236,12 @@ _IsBot( entity )
|
||||
|
||||
_SetDvarIfUninitialized( dvarName, dvarValue )
|
||||
{
|
||||
[[level.overrideMethods[level.commonFunctions.SetDvar]]]( dvarName, dvarValue );
|
||||
[[level.overrideMethods[level.commonFunctions.setDvar]]]( dvarName, dvarValue );
|
||||
}
|
||||
|
||||
NotImplementedFunction( a, b, c, d, e, f )
|
||||
{
|
||||
LogWarning( "Function not implemented" );
|
||||
}
|
||||
|
||||
// Not every game can output to console or even game log.
|
||||
@ -675,6 +671,7 @@ OnClientDataReceived( event )
|
||||
clientData.clientId = event.data["clientId"];
|
||||
clientData.lastConnection = event.data["lastConnection"];
|
||||
clientData.tag = event.data["tag"];
|
||||
clientData.performance = event.data["performance"];
|
||||
clientData.state = "complete";
|
||||
self.persistentClientId = event.data["clientId"];
|
||||
|
||||
|
@ -16,9 +16,17 @@ Setup()
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods[level.commonFunctions.changeTeam] = ::ChangeTeam;
|
||||
level.overrideMethods[level.commonFunctions.getTeamCounts] = ::CountPlayers;
|
||||
level.overrideMethods[level.commonFunctions.getMaxClients] = ::GetMaxClients;
|
||||
level.overrideMethods[level.commonFunctions.getTeamBased] = ::GetTeamBased;
|
||||
level.overrideMethods[level.commonFunctions.getClientTeam] = ::GetClientTeam;
|
||||
level.overrideMethods[level.commonFunctions.getClientKillStreak] = ::GetClientKillStreak;
|
||||
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = ::BackupRestoreClientKillStreakData;
|
||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
@ -88,6 +96,88 @@ WaitForClientEvents()
|
||||
}
|
||||
}
|
||||
|
||||
GetMaxClients()
|
||||
{
|
||||
return level.maxClients;
|
||||
}
|
||||
|
||||
GetTeamBased()
|
||||
{
|
||||
return level.teamBased;
|
||||
}
|
||||
|
||||
CountPlayers()
|
||||
{
|
||||
return maps\mp\gametypes\_teams::CountPlayers();
|
||||
}
|
||||
|
||||
GetClientTeam()
|
||||
{
|
||||
if ( IsDefined( self.pers["team"] ) && self.pers["team"] == "allies" )
|
||||
{
|
||||
return "allies";
|
||||
}
|
||||
|
||||
else if ( IsDefined( self.pers["team"] ) && self.pers["team"] == "axis" )
|
||||
{
|
||||
return "axis";
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
GetClientKillStreak()
|
||||
{
|
||||
return int( self.pers["cur_kill_streak"] );
|
||||
}
|
||||
|
||||
BackupRestoreClientKillStreakData( restore )
|
||||
{
|
||||
if ( restore )
|
||||
{
|
||||
foreach ( index, streakStruct in self.pers["killstreaks_backup"] )
|
||||
{
|
||||
self.pers["killstreaks"][index] = self.pers["killstreaks_backup"][index];
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
self.pers["killstreaks_backup"] = [];
|
||||
|
||||
foreach ( index, streakStruct in self.pers["killstreaks"] )
|
||||
{
|
||||
self.pers["killstreaks_backup"][index] = self.pers["killstreaks"][index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
||||
{
|
||||
return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 );
|
||||
}
|
||||
|
||||
ChangeTeam( team )
|
||||
{
|
||||
switch ( team )
|
||||
{
|
||||
case "allies":
|
||||
self [[level.allies]]();
|
||||
break;
|
||||
|
||||
case "axis":
|
||||
self [[level.axis]]();
|
||||
break;
|
||||
|
||||
case "spectator":
|
||||
self [[level.spectator]]();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||
|
466
GameFiles/GameInterface/_integration_shared.gsc
Normal file
466
GameFiles/GameInterface/_integration_shared.gsc
Normal file
@ -0,0 +1,466 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
@ -77,6 +77,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterf
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\GameInterface\_integration_base.gsc = GameFiles\GameInterface\_integration_base.gsc
|
||||
GameFiles\GameInterface\_integration_iw4x.gsc = GameFiles\GameInterface\_integration_iw4x.gsc
|
||||
GameFiles\GameInterface\_integration_iw5.gsc = GameFiles\GameInterface\_integration_iw5.gsc
|
||||
GameFiles\GameInterface\_integration_shared.gsc = GameFiles\GameInterface\_integration_shared.gsc
|
||||
GameFiles\GameInterface\_integration_t5.gsc = GameFiles\GameInterface\_integration_t5.gsc
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntiCheat", "AntiCheat", "{AB83BAC0-C539-424A-BF00-78487C10753C}"
|
||||
|
@ -72,27 +72,27 @@ let plugin = {
|
||||
};
|
||||
|
||||
let commands = [{
|
||||
name: 'giveweapon',
|
||||
description: 'gives specified weapon',
|
||||
alias: 'gw',
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: true,
|
||||
arguments: [{
|
||||
name: 'player',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'weapon name',
|
||||
name: 'giveweapon',
|
||||
description: 'gives specified weapon',
|
||||
alias: 'gw',
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: true,
|
||||
arguments: [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
},
|
||||
{
|
||||
name: 'weapon name',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
}
|
||||
sendScriptCommand(gameEvent.Owner, 'GiveWeapon', gameEvent.Origin, gameEvent.Target, {weaponName: gameEvent.Data});
|
||||
}
|
||||
sendScriptCommand(gameEvent.Owner, 'GiveWeapon', gameEvent.Origin, gameEvent.Target, {weaponName: gameEvent.Data});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'takeweapons',
|
||||
description: 'take all weapons from specified player',
|
||||
@ -374,6 +374,15 @@ const initialize = (server) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const getClientStats = (client, server) => {
|
||||
const contextFactory = _serviceResolver.ResolveService('IDatabaseContextFactory');
|
||||
const context = contextFactory.CreateContext(false);
|
||||
const stats = context.ClientStatistics.GetClientsStatData([client.ClientId], server.GetId()); // .Find(client.ClientId, serverId);
|
||||
context.Dispose();
|
||||
|
||||
return stats.length > 0 ? stats[0] : undefined;
|
||||
}
|
||||
|
||||
function onReceivedDvar(server, dvarName, dvarValue, success) {
|
||||
const logger = _serviceResolver.ResolveService('ILogger');
|
||||
logger.WriteDebug(`Received ${dvarName}=${dvarValue} success=${success}`);
|
||||
@ -422,12 +431,14 @@ function onReceivedDvar(server, dvarName, dvarValue, success) {
|
||||
data[event.data] = meta === null ? '' : meta.Value;
|
||||
logger.WriteDebug(`event data is ${event.data}`);
|
||||
} else {
|
||||
const clientStats = getClientStats(client, server);
|
||||
const tagMeta = metaService.GetPersistentMetaByLookup('ClientTagV2', 'ClientTagNameV2', client.ClientId, token).GetAwaiter().GetResult();
|
||||
data = {
|
||||
level: client.Level,
|
||||
clientId: client.ClientId,
|
||||
lastConnection: client.LastConnection,
|
||||
tag: tagMeta?.Value ?? ''
|
||||
tag: tagMeta?.Value ?? '',
|
||||
performance: clientStats?.Performance ?? 200.0
|
||||
};
|
||||
}
|
||||
|
||||
@ -456,13 +467,15 @@ function onReceivedDvar(server, dvarName, dvarValue, success) {
|
||||
} else {
|
||||
if (event.subType === 'Meta') {
|
||||
try {
|
||||
logger.WriteDebug(`Key=${event.data['key']}, Value=${event.data['value']}, Direction=${event.data['direction']} ${token}`);
|
||||
if (event.data['direction'] != null) {
|
||||
event.data['direction'] = 'up'
|
||||
? metaService.IncrementPersistentMeta(event.data['key'], parseInt(event.data['value']), clientId, token).GetAwaiter().GetResult()
|
||||
: metaService.DecrementPersistentMeta(event.data['key'], parseInt(event.data['value']), clientId, token).GetAwaiter().GetResult();
|
||||
} else {
|
||||
metaService.SetPersistentMeta(event.data['key'], event.data['value'], clientId, token).GetAwaiter().GetResult();
|
||||
if (event.data['value'] != null && event.data['key'] != null) {
|
||||
logger.WriteDebug(`Key=${event.data['key']}, Value=${event.data['value']}, Direction=${event.data['direction']} ${token}`);
|
||||
if (event.data['direction'] != null) {
|
||||
event.data['direction'] = 'up'
|
||||
? metaService.IncrementPersistentMeta(event.data['key'], parseInt(event.data['value']), clientId, token).GetAwaiter().GetResult()
|
||||
: metaService.DecrementPersistentMeta(event.data['key'], parseInt(event.data['value']), clientId, token).GetAwaiter().GetResult();
|
||||
} else {
|
||||
metaService.SetPersistentMeta(event.data['key'], event.data['value'], clientId, token).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
sendEvent(server, false, 'SetClientDataCompleted', 'Meta', {ClientNumber: event.clientNumber}, undefined, {status: 'Complete'});
|
||||
} catch (error) {
|
||||
@ -500,11 +513,13 @@ const pollForEvents = server => {
|
||||
}
|
||||
|
||||
if (server.Throttled) {
|
||||
logger.WriteDebug('Server is throttled so we are not polling for game data');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.waitingOnInput) {
|
||||
state.waitingOnInput = true;
|
||||
logger.WriteDebug('Attempting to get in dvar value');
|
||||
getDvar(server, inDvar, onReceivedDvar);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user