diff --git a/Application/Application.csproj b/Application/Application.csproj index 78ebd5fb8..fffa5b9ce 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -32,11 +32,13 @@ + + - false + true true true Latest diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index fe1de1f9b..98e395127 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -581,9 +581,9 @@ namespace IW4MAdmin.Application throw lastException; } - if (successServers != config.Servers.Length) + if (successServers != config.Servers.Length && !AppContext.TryGetSwitch("NoConfirmPrompt", out _)) { - if (!Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"])) + if (!Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"].PromptBool()) { throw lastException; } diff --git a/Application/DefaultSettings.json b/Application/DefaultSettings.json index 7a6cc131c..a454bca99 100644 --- a/Application/DefaultSettings.json +++ b/Application/DefaultSettings.json @@ -311,6 +311,10 @@ { "Name": "tdm", "Alias": "Team Deathmatch" + }, + { + "Name": "zom", + "Alias": "Zombies" } ] }, @@ -1230,7 +1234,43 @@ { "Alias": "Zoo", "Name": "mp_zoo" - } + }, + { + "Alias": "Kino der Toten", + "Name": "zombie_theater" + }, + { + "Alias": "Five", + "Name": "zombie_pentagon" + }, + { + "Alias": "Ascension", + "Name": "zombie_cosmodrome" + }, + { + "Alias": "Call of the Dead", + "Name": "zombie_coast" + }, + { + "Alias": "Moon", + "Name": "zombie_moon" + }, + { + "Alias": "Nacht Der Untoten", + "Name": "zombie_cod5_prototype" + }, + { + "Alias": "Verrückt", + "Name": "zombie_cod5_asylum" + }, + { + "Alias": "Shi No Numa", + "Name": "zombie_cod5_sumpf" + }, + { + "Alias": "Der Riese", + "Name": "zombie_cod5_factory" + } ] }, { diff --git a/Application/Main.cs b/Application/Main.cs index 83101a043..41eb9901c 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -58,7 +58,7 @@ namespace IW4MAdmin.Application /// entrypoint of the application /// /// - public static async Task Main(string[] args) + public static async Task Main(bool noConfirm = false, int? maxConcurrentRequests = 25, int? requestQueueLimit = 25) { AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) => @@ -73,7 +73,15 @@ namespace IW4MAdmin.Application // added to be a bit more permissive with plugin references return AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(asm => asm.FullName?.StartsWith(libraryName) ?? false); - }; + }; + + if (noConfirm) + { + AppContext.SetSwitch("NoConfirmPrompt", true); + } + + Environment.SetEnvironmentVariable("MaxConcurrentRequests", (maxConcurrentRequests * Environment.ProcessorCount).ToString()); + Environment.SetEnvironmentVariable("RequestQueueLimit", requestQueueLimit.ToString()); Console.OutputEncoding = Encoding.UTF8; Console.ForegroundColor = ConsoleColor.Gray; @@ -86,7 +94,7 @@ namespace IW4MAdmin.Application Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine("====================================================="); - await LaunchAsync(args); + await LaunchAsync(); } /// @@ -112,13 +120,13 @@ namespace IW4MAdmin.Application /// task that initializes application and starts the application monitoring and runtime tasks /// /// - private static async Task LaunchAsync(string[] args) + private static async Task LaunchAsync() { restart: ITranslationLookup translationLookup = null; var logger = BuildDefaultLogger(new ApplicationConfiguration()); Utilities.DefaultLogger = logger; - logger.LogInformation("Begin IW4MAdmin startup. Version is {Version} {@Args}", Version, args); + logger.LogInformation("Begin IW4MAdmin startup. Version is {Version}", Version); try { @@ -426,9 +434,9 @@ namespace IW4MAdmin.Application commandConfigHandler.BuildAsync().GetAwaiter().GetResult(); var appConfig = appConfigHandler.Configuration(); - var masterUri = /*Utilities.IsDevelopment + var masterUri = Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") - : appConfig?.MasterUrl ?? */new ApplicationConfiguration().MasterUrl; + : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl; var httpClient = new HttpClient { BaseAddress = masterUri, diff --git a/Application/Plugin/Script/ScriptPluginHelper.cs b/Application/Plugin/Script/ScriptPluginHelper.cs index 660a91d2f..be15c580a 100644 --- a/Application/Plugin/Script/ScriptPluginHelper.cs +++ b/Application/Plugin/Script/ScriptPluginHelper.cs @@ -14,8 +14,8 @@ public class ScriptPluginHelper { private readonly IManager _manager; private readonly ScriptPluginV2 _scriptPlugin; - private readonly SemaphoreSlim _onRequestRunning = new(1, 5); - private const int RequestTimeout = 500; + private readonly SemaphoreSlim _onRequestRunning = new(1, 1); + private const int RequestTimeout = 5000; public ScriptPluginHelper(IManager manager, ScriptPluginV2 scriptPlugin) { diff --git a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc b/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc index 909b02921..133a6a59f 100644 Binary files a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc and b/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc differ diff --git a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src b/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src deleted file mode 100644 index fbdf34fff..000000000 --- a/GameFiles/AntiCheat/PT6/storage/t6/scripts/mp/_customcallbacks.gsc.src +++ /dev/null @@ -1,263 +0,0 @@ -#include maps\mp\_utility; -#include maps\mp\gametypes\_hud_util; -#include common_scripts\utility; - -init() -{ - SetDvarIfUninitialized( "sv_customcallbacks", true ); - SetDvarIfUninitialized( "sv_framewaittime", 0.05 ); - SetDvarIfUninitialized( "sv_additionalwaittime", 0.1 ); - SetDvarIfUninitialized( "sv_maxstoredframes", 12 ); - SetDvarIfUninitialized( "sv_printradarupdates", 0 ); - SetDvarIfUninitialized( "sv_printradar_updateinterval", 500 ); - SetDvarIfUninitialized( "sv_iw4madmin_url", "http://127.0.0.1:1624" ); - - level thread onPlayerConnect(); - if (getDvarInt("sv_printradarupdates") == 1) - { - level thread runRadarUpdates(); - } - - level waittill( "prematch_over" ); - level.callbackPlayerKilled = ::Callback_PlayerKilled; - level.callbackPlayerDamage = ::Callback_PlayerDamage; - level.callbackPlayerDisconnect = ::Callback_PlayerDisconnect; -} - -//It's called slightly different in T6 -//set_dvar_if_unset(dvar, val, reset) -SetDvarIfUninitialized(dvar, val) -{ - set_dvar_if_unset(dvar,val); -} - -onPlayerConnect( player ) -{ - for( ;; ) - { - level waittill( "connected", player ); - player thread waitForFrameThread(); - player thread waitForAttack(); - } -} - -//Got added to T6 on April 2020 -waitForAttack() -{ - self endon( "disconnect" ); - - self notifyOnPlayerCommand( "player_shot", "+attack" ); - self.lastAttackTime = 0; - - for( ;; ) - { - self waittill( "player_shot" ); - - self.lastAttackTime = getTime(); - } -} - -runRadarUpdates() -{ - interval = getDvarInt( "sv_printradar_updateinterval" ); - - for ( ;; ) - { - for ( i = 0; i <= 17; i++ ) - { - player = level.players[i]; - - if ( isDefined( player ) ) - { - payload = player.guid + ";" + player.origin + ";" + player getPlayerAngles() + ";" + player.team + ";" + player.kills + ";" + player.deaths + ";" + player.score + ";" + player GetCurrentWeapon() + ";" + player.health + ";" + isAlive(player) + ";" + player.timePlayed["total"]; - logPrint( "LiveRadar;" + payload + "\n" ); - } - } - - wait( interval / 1000 ); - } -} - -hitLocationToBone( hitloc ) -{ - switch( hitloc ) - { - case "helmet": - return "j_helmet"; - case "head": - return "j_head"; - case "neck": - return "j_neck"; - case "torso_upper": - return "j_spineupper"; - case "torso_lower": - return "j_spinelower"; - case "right_arm_upper": - return "j_shoulder_ri"; - case "left_arm_upper": - return "j_shoulder_le"; - case "right_arm_lower": - return "j_elbow_ri"; - case "left_arm_lower": - return "j_elbow_le"; - case "right_hand": - return "j_wrist_ri"; - case "left_hand": - return "j_wrist_le"; - case "right_leg_upper": - return "j_hip_ri"; - case "left_leg_upper": - return "j_hip_le"; - case "right_leg_lower": - return "j_knee_ri"; - case "left_leg_lower": - return "j_knee_le"; - case "right_foot": - return "j_ankle_ri"; - case "left_foot": - return "j_ankle_le"; - default: - return "tag_origin"; - } -} - -waitForFrameThread() -{ - self endon( "disconnect" ); - - self.currentAnglePosition = 0; - self.anglePositions = []; - - for (i = 0; i < getDvarInt( "sv_maxstoredframes" ); i++) - { - self.anglePositions[i] = self getPlayerAngles(); - } - - for( ;; ) - { - self.anglePositions[self.currentAnglePosition] = self getPlayerAngles(); - wait( getDvarFloat( "sv_framewaittime" ) ); - self.currentAnglePosition = (self.currentAnglePosition + 1) % getDvarInt( "sv_maxstoredframes" ); - } -} - -waitForAdditionalAngles( logString, beforeFrameCount, afterFrameCount ) -{ - currentIndex = self.currentAnglePosition; - wait( 0.05 * afterFrameCount ); - - self.angleSnapshot = []; - - for( j = 0; j < self.anglePositions.size; j++ ) - { - self.angleSnapshot[j] = self.anglePositions[j]; - } - - anglesStr = ""; - collectedFrames = 0; - i = currentIndex - beforeFrameCount; - - while (collectedFrames < beforeFrameCount) - { - fixedIndex = i; - if (i < 0) - { - fixedIndex = self.angleSnapshot.size - abs(i); - } - anglesStr += self.angleSnapshot[int(fixedIndex)] + ":"; - collectedFrames++; - i++; - } - - if (i == currentIndex) - { - anglesStr += self.angleSnapshot[i] + ":"; - i++; - } - - collectedFrames = 0; - - while (collectedFrames < afterFrameCount) - { - fixedIndex = i; - if (i > self.angleSnapshot.size - 1) - { - fixedIndex = i % self.angleSnapshot.size; - } - anglesStr += self.angleSnapshot[int(fixedIndex)] + ":"; - collectedFrames++; - i++; - } - - lastAttack = getTime() - self.lastAttackTime; - isAlive = isAlive(self); - - logPrint(logString + ";" + anglesStr + ";" + isAlive + ";" + lastAttack + "\n" ); -} - -vectorScale( vector, scale ) -{ - return ( vector[0] * scale, vector[1] * scale, vector[2] * scale ); -} - -Process_Hit( type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ) -{ - if (sMeansOfDeath == "MOD_FALLING" || !isPlayer(attacker)) - { - return; - } - - victim = self; - _attacker = attacker; - - if ( !isPlayer( attacker ) && isDefined( attacker.owner ) ) - { - _attacker = attacker.owner; - } - - else if( !isPlayer( attacker ) && sMeansOfDeath == "MOD_FALLING" ) - { - _attacker = victim; - } - - location = victim GetTagOrigin( hitLocationToBone( sHitLoc ) ); - isKillstreakKill = false; - if(!isPlayer(attacker)) - { - isKillstreakKill = true; - } - if(maps/mp/killstreaks/_killstreaks::iskillstreakweapon(sWeapon)) - { - isKillstreakKill = true; - } - - logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + int(gettime()) + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";0;0"; - attacker thread waitForAdditionalAngles( logLine, 2, 2 ); -} - -Callback_PlayerDamage( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ) -{ - if ( level.teamBased && isDefined( attacker ) && ( self != attacker ) && isDefined( attacker.team ) && ( self.pers[ "team" ] == attacker.team ) ) - { - return; - } - - if ( self.health - iDamage > 0 ) - { - self Process_Hit( "Damage", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ); - } - - self [[maps/mp/gametypes/_globallogic_player::callback_playerdamage]]( eInflictor, attacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex ); -} - -Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration) -{ - Process_Hit( "Kill", attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon ); - self [[maps/mp/gametypes/_globallogic_player::callback_playerkilled]]( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration ); -} - -Callback_PlayerDisconnect() -{ - level notify( "disconnected", self ); - self [[maps/mp/gametypes/_globallogic_player::callback_playerdisconnect]](); -} \ No newline at end of file diff --git a/GameFiles/GameInterface/_integration_base.gsc b/GameFiles/GameInterface/_integration_base.gsc index ef16b1047..fac8a6688 100644 --- a/GameFiles/GameInterface/_integration_base.gsc +++ b/GameFiles/GameInterface/_integration_base.gsc @@ -1,6 +1,4 @@ #include common_scripts\utility; -#include maps\mp\_utility; -#include maps\mp\gametypes\_hud_util; Init() { @@ -19,14 +17,17 @@ Setup() level.eventBus.timeoutKey = "timeout"; level.eventBus.timeout = 30; - level.commonFunctions = spawnstruct(); - level.commonFunctions.setDvar = "SetDvarIfUninitialized"; - level.commonFunctions.isBot = "IsBot"; + level.commonFunctions = spawnstruct(); + level.commonFunctions.setDvar = "SetDvarIfUninitialized"; + level.commonFunctions.isBot = "IsBot"; + level.commonFunctions.getXuid = "GetXuid"; + level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum"; level.commonKeys = spawnstruct(); level.notifyTypes = spawnstruct(); level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized"; + level.notifyTypes.sharedFunctionsInitialized = "SharedFunctionsInitialized"; level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized"; level.clientDataKey = "clientData"; @@ -102,9 +103,6 @@ OnPlayerConnect() } player thread OnPlayerSpawned(); - player thread OnPlayerJoinedTeam(); - player thread OnPlayerJoinedSpectators(); - player thread PlayerTrackingOnInterval(); } } @@ -119,30 +117,6 @@ OnPlayerSpawned() } } -OnPlayerJoinedTeam() -{ - self endon( "disconnect" ); - - for( ;; ) - { - self waittill( "joined_team" ); - // join spec and join team occur at the same moment - out of order logging would be problematic - wait( 0.25 ); - LogPrint( GenerateJoinTeamString( false ) ); - } -} - -OnPlayerJoinedSpectators() -{ - self endon( "disconnect" ); - - for( ;; ) - { - self waittill( "joined_spectators" ); - LogPrint( GenerateJoinTeamString( true ) ); - } -} - OnGameEnded() { for ( ;; ) @@ -187,20 +161,6 @@ PlayerSpawnEvents() self RequestClientBasicData(); } -PlayerTrackingOnInterval() -{ - self endon( "disconnect" ); - - for ( ;; ) - { - wait ( 120 ); - if ( IsAlive( self ) ) - { - self SaveTrackingMetrics(); - } - } -} - MonitorClientEvents() { level endon( "game_ended" ); @@ -344,37 +304,6 @@ DecrementClientMeta( metaKey, decrementValue, clientId ) SetClientMeta( metaKey, decrementValue, clientId, "decrement" ); } -GenerateJoinTeamString( isSpectator ) -{ - team = self.team; - - if ( IsDefined( self.joining_team ) ) - { - team = self.joining_team; - } - else - { - if ( isSpectator || !IsDefined( team ) ) - { - team = "spectator"; - } - } - - guid = self GetXuid(); - - if ( guid == "0" ) - { - guid = self.guid; - } - - if ( !IsDefined( guid ) || guid == "0" ) - { - guid = "undefined"; - } - - return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n"; -} - SetClientMeta( metaKey, metaValue, clientId, direction ) { data = "key=" + metaKey + "|value=" + metaValue; @@ -400,39 +329,6 @@ SetClientMeta( metaKey, metaValue, clientId, direction ) level thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self ); } -SaveTrackingMetrics() -{ - if ( !IsDefined( self.persistentClientId ) ) - { - return; - } - - LogDebug( "Saving tracking metrics for " + self.persistentClientId ); - - if ( !IsDefined( self.lastShotCount ) ) - { - self.lastShotCount = 0; - } - - currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]](); - change = currentShotCount - self.lastShotCount; - self.lastShotCount = currentShotCount; - - LogDebug( "Total Shots Fired increased by " + change ); - - if ( !IsDefined( change ) ) - { - change = 0; - } - - if ( change == 0 ) - { - return; - } - - IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId ); -} - BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) { if ( !IsDefined( data ) ) @@ -565,8 +461,8 @@ NotifyClientEventTimeout( eventType ) NotifyClientEvent( eventInfo ) { - origin = getPlayerFromClientNum( int( eventInfo[3] ) ); - target = getPlayerFromClientNum( int( eventInfo[4] ) ); + origin = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[3] ) ); + target = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[4] ) ); event = spawnstruct(); event.type = eventInfo[1]; @@ -608,24 +504,6 @@ NotifyClientEvent( eventInfo ) level notify( level.eventTypes.localClientEvent, client ); } -GetPlayerFromClientNum( clientNum ) -{ - if ( clientNum < 0 ) - { - return undefined; - } - - for ( i = 0; i < level.players.size; i++ ) - { - if ( level.players[i] getEntityNumber() == clientNum ) - { - return level.players[i]; - } - } - - return undefined; -} - AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite ) { if ( IsDefined( level.clientCommandCallbacks[commandName] ) && IsDefined( shouldOverwrite ) && !shouldOverwrite ) diff --git a/GameFiles/GameInterface/_integration_iw4x.gsc b/GameFiles/GameInterface/_integration_iw4x.gsc index 34970de19..a072e7347 100644 --- a/GameFiles/GameInterface/_integration_iw4x.gsc +++ b/GameFiles/GameInterface/_integration_iw4x.gsc @@ -2,8 +2,6 @@ Init() { - level.eventBus.gamename = "IW4"; - thread Setup(); } @@ -12,13 +10,15 @@ Setup() level endon( "game_ended" ); // it's possible that the notify type has not been defined yet so we have to hard code it - level waittill( "IntegrationBootstrapInitialized" ); + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "IW4"; scripts\_integration_base::RegisterLogger( ::Log2Console ); level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired; level.overrideMethods[level.commonFunctions.setDvar] = ::_SetDvarIfUninitialized; level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient; + level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID; level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout; level.overrideMethods[level.commonFunctions.changeTeam] = ::ChangeTeam; level.overrideMethods[level.commonFunctions.getTeamCounts] = ::CountPlayers; @@ -31,6 +31,8 @@ Setup() RegisterClientCommands(); + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + level notify( level.notifyTypes.gameFunctionsInitialized ); if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) @@ -199,6 +201,11 @@ Log2Console( logLevel, message ) PrintConsole( "[" + logLevel + "] " + message + "\n" ); } +_GetXUID() +{ + return self GetXUID(); +} + ////////////////////////////////// // GUID helpers ///////////////////////////////// diff --git a/GameFiles/GameInterface/_integration_iw5.gsc b/GameFiles/GameInterface/_integration_iw5.gsc index ed92e8c9b..75057e375 100644 --- a/GameFiles/GameInterface/_integration_iw5.gsc +++ b/GameFiles/GameInterface/_integration_iw5.gsc @@ -2,8 +2,6 @@ Init() { - level.eventBus.gamename = "IW5"; - thread Setup(); } @@ -12,17 +10,21 @@ Setup() level endon( "game_ended" ); // it's possible that the notify type has not been defined yet so we have to hard code it - level waittill( "IntegrationBootstrapInitialized" ); + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "IW5"; - scripts\mp\_integration_base::RegisterLogger( ::Log2Console ); + 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[level.commonFunctions.isBot] = ::IsTestClient; + level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID; RegisterClientCommands(); + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + level notify( level.notifyTypes.gameFunctionsInitialized ); if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) @@ -54,17 +56,17 @@ OnPlayerConnect() RegisterClientCommands() { - scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); - scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); - scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); - scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); - scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); - scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); - scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); - scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); - scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); - scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); - scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); } WaitForClientEvents() @@ -73,13 +75,13 @@ WaitForClientEvents() // example of requesting a meta value lastServerMetaKey = "LastServerPlayed"; - // self scripts\mp\_integration_base::RequestClientMeta( lastServerMetaKey ); + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); for ( ;; ) { self waittill( level.eventTypes.localClientEvent, event ); - scripts\mp\_integration_base::LogDebug( "Received client event " + event.type ); + scripts\_integration_base::LogDebug( "Received client event " + event.type ); if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) { @@ -109,6 +111,11 @@ Log2Console( logLevel, message ) Print( "[" + logLevel + "] " + message + "\n" ); } +_GetXUID() +{ + return self GetXUID(); +} + ////////////////////////////////// // GUID helpers ///////////////////////////////// @@ -126,14 +133,14 @@ SetPersistentData() { // give IW4MAdmin time to collect IP wait( 15 ); - scripts\mp\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); - scripts\mp\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); return; } guid = self SplitGuid(); - scripts\mp\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); self SetPlayerData( "bests", "none", guid["high"] ); self SetPlayerData( "awards", "none", guid["low"] ); diff --git a/GameFiles/GameInterface/_integration_shared.gsc b/GameFiles/GameInterface/_integration_shared.gsc index 36a0ded01..2c0d87bd1 100644 --- a/GameFiles/GameInterface/_integration_shared.gsc +++ b/GameFiles/GameInterface/_integration_shared.gsc @@ -8,6 +8,9 @@ Setup() { level endon( "game_ended" ); + // it's possible that the notify type has not been defined yet so we have to hard code it + level waittill( "IntegrationBootstrapInitialized" ); + level.commonFunctions.changeTeam = "ChangeTeam"; level.commonFunctions.getTeamCounts = "GetTeamCounts"; level.commonFunctions.getMaxClients = "GetMaxClients"; @@ -25,6 +28,7 @@ Setup() 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; + level.overrideMethods["GetPlayerFromClientNum"] = ::GetPlayerFromClientNum; // these can be overridden per game if needed level.commonKeys.team1 = "allies"; @@ -39,15 +43,13 @@ Setup() level.iw4madminIntegrationDefaultPerformance = 200; + level notify( level.notifyTypes.sharedFunctionsInitialized ); + level waittill( level.notifyTypes.gameFunctionsInitialized ); + if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) { return; } - - if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 ) - { - return; - } level thread OnPlayerConnect(); } @@ -59,7 +61,22 @@ OnPlayerConnect() for ( ;; ) { level waittill( level.eventTypes.connect, player ); + + if ( scripts\_integration_base::_IsBot( player ) ) + { + // we don't want to track bots + continue; + } + + player thread OnPlayerJoinedTeam(); + player thread OnPlayerJoinedSpectators(); + player thread PlayerTrackingOnInterval(); + if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 || !IsDefined( [[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) ) + { + continue; + } + if ( ![[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) { continue; @@ -449,3 +466,123 @@ GetClientPerformanceOrDefault() return performance; } + +GetPlayerFromClientNum( clientNum ) +{ + if ( clientNum < 0 ) + { + return undefined; + } + + for ( i = 0; i < level.players.size; i++ ) + { + if ( level.players[i] getEntityNumber() == clientNum ) + { + return level.players[i]; + } + } + + return undefined; +} + +OnPlayerJoinedTeam() +{ + self endon( "disconnect" ); + + for( ;; ) + { + self waittill( "joined_team" ); + // join spec and join team occur at the same moment - out of order logging would be problematic + wait( 0.25 ); + LogPrint( GenerateJoinTeamString( false ) ); + } +} + +OnPlayerJoinedSpectators() +{ + self endon( "disconnect" ); + + for( ;; ) + { + self waittill( "joined_spectators" ); + LogPrint( GenerateJoinTeamString( true ) ); + } +} + +GenerateJoinTeamString( isSpectator ) +{ + team = self.team; + + if ( IsDefined( self.joining_team ) ) + { + team = self.joining_team; + } + else + { + if ( isSpectator || !IsDefined( team ) ) + { + team = "spectator"; + } + } + + guid = self [[level.overrideMethods[level.commonFunctions.getXuid]]](); + + if ( guid == "0" ) + { + guid = self.guid; + } + + if ( !IsDefined( guid ) || guid == "0" ) + { + guid = "undefined"; + } + + return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n"; +} + +PlayerTrackingOnInterval() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + wait ( 120 ); + if ( IsAlive( self ) ) + { + self SaveTrackingMetrics(); + } + } +} + +SaveTrackingMetrics() +{ + if ( !IsDefined( self.persistentClientId ) ) + { + return; + } + + scripts\_integration_base::LogDebug( "Saving tracking metrics for " + self.persistentClientId ); + + if ( !IsDefined( self.lastShotCount ) ) + { + self.lastShotCount = 0; + } + + currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]](); + change = currentShotCount - self.lastShotCount; + self.lastShotCount = currentShotCount; + + scripts\_integration_base::LogDebug( "Total Shots Fired increased by " + change ); + + if ( !IsDefined( change ) ) + { + change = 0; + } + + if ( change == 0 ) + { + return; + } + + scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId ); +} \ No newline at end of file diff --git a/GameFiles/GameInterface/_integration_t5.gsc b/GameFiles/GameInterface/_integration_t5.gsc index 9d5e5f3f7..139847179 100644 --- a/GameFiles/GameInterface/_integration_t5.gsc +++ b/GameFiles/GameInterface/_integration_t5.gsc @@ -2,8 +2,6 @@ Init() { - level.eventBus.gamename = "T5"; - thread Setup(); } @@ -12,16 +10,20 @@ Setup() level endon( "game_ended" ); // it's possible that the notify type has not been defined yet so we have to hard code it - level waittill( "IntegrationBootstrapInitialized" ); + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "T5"; - scripts\mp\_integration_base::RegisterLogger( ::Log2Console ); + 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[level.commonFunctions.getXuid] = ::_GetXUID; RegisterClientCommands(); + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + level notify( level.notifyTypes.gameFunctionsInitialized ); if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) @@ -40,7 +42,7 @@ OnPlayerConnect() { level waittill( "connected", player ); - if ( scripts\mp\_integration_base::_IsBot( player ) ) + if ( scripts\_integration_base::_IsBot( player ) ) { // we don't want to track bots continue; @@ -53,17 +55,17 @@ OnPlayerConnect() RegisterClientCommands() { - scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); - scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); - scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); - scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); - scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); - scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); - scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); - scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); - scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); - scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); - scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); } WaitForClientEvents() @@ -72,13 +74,13 @@ WaitForClientEvents() // example of requesting a meta value lastServerMetaKey = "LastServerPlayed"; - // self scripts\mp\_integration_base::RequestClientMeta( lastServerMetaKey ); + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); for ( ;; ) { self waittill( level.eventTypes.localClientEvent, event ); - scripts\mp\_integration_base::LogDebug( "Received client event " + event.type ); + scripts\_integration_base::LogDebug( "Received client event " + event.type ); if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) { @@ -129,6 +131,11 @@ God() } } +_GetXUID() +{ + return self GetXUID(); +} + ////////////////////////////////// // GUID helpers ///////////////////////////////// @@ -146,14 +153,14 @@ God() { // give IW4MAdmin time to collect IP wait( 15 ); - scripts\mp\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); - scripts\mp\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); return; } guid = self SplitGuid(); - scripts\mp\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); self SetPlayerData( "bests", "none", guid["high"] ); self SetPlayerData( "awards", "none", guid["low"] ); @@ -395,7 +402,7 @@ NoClipImpl( event, data ) self IPrintLnBold( "NoClip enabled" );*/ - scripts\mp\_integration_base::LogWarning( "NoClip is not supported on T5!" ); + scripts\_integration_base::LogWarning( "NoClip is not supported on T5!" ); } diff --git a/GameFiles/GameInterface/_integration_t5zm.gsc b/GameFiles/GameInterface/_integration_t5zm.gsc new file mode 100644 index 000000000..d01e9dc28 --- /dev/null +++ b/GameFiles/GameInterface/_integration_t5zm.gsc @@ -0,0 +1,552 @@ +#include common_scripts\utility; + +Init() +{ + thread Setup(); +} + +Setup() +{ + level endon( "game_ended" ); + + // it's possible that the notify type has not been defined yet so we have to hard code it + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "T5"; + + 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["GetPlayerFromClientNum"] = ::_GetPlayerFromClientNum; + + RegisterClientCommands(); + + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + + level notify( level.notifyTypes.gameFunctionsInitialized ); + + if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) + { + return; + } + + level thread OnPlayerConnect(); +} + +OnPlayerConnect() +{ + level endon ( "game_ended" ); + + for ( ;; ) + { + level waittill( "connected", player ); + + if ( scripts\_integration_base::_IsBot( player ) ) + { + // we don't want to track bots + continue; + } + + //player thread SetPersistentData(); + player thread WaitForClientEvents(); + } +} + +RegisterClientCommands() +{ + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); +} + +WaitForClientEvents() +{ + self endon( "disconnect" ); + + // example of requesting a meta value + lastServerMetaKey = "LastServerPlayed"; + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); + + for ( ;; ) + { + self waittill( level.eventTypes.localClientEvent, event ); + + scripts\_integration_base::LogDebug( "Received client event " + event.type ); + + if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) + { + clientData = self.pers[level.clientDataKey]; + lastServerPlayed = clientData.meta[lastServerMetaKey]; + } + } +} + +GetTotalShotsFired() +{ + return 0; //ZM has no shot tracking. TODO: add tracking function for event weapon_fired +} + +_SetDvarIfUninitialized(dvar, value) +{ + if (GetDvar(dvar)=="" ) + { + SetDvar(dvar, value); + return value; + } + + return GetDvar(dvar); +} + +_waittill_notify_or_timeout( msg, timer ) +{ + self endon( msg ); + wait( timer ); +} + +Log2Console( logLevel, message ) +{ + Print( "[" + logLevel + "] " + message + "\n" ); +} + +God() +{ + + if ( !IsDefined( self.godmode ) ) + { + self.godmode = false; + } + + if (!self.godmode ) + { + self enableInvulnerability(); + self.godmode = true; + } + else + { + self.godmode = false; + self disableInvulnerability(); + } +} + +_GetPlayerFromClientNum( clientNum ) +{ + if ( clientNum < 0 ) + { + return undefined; + } + + players = GetPlayers("all"); + + for ( i = 0; i < players.size; i++ ) + { + scripts\_integration_base::LogDebug(i+"/"+players.size+ "=" + players[i].name); + if ( players[i] getEntityNumber() == clientNum ) + { + return players[i]; + } + } + + return undefined; +} + +////////////////////////////////// +// GUID helpers +///////////////////////////////// + +/*SetPersistentData() +{ + self endon( "disconnect" ); + + guidHigh = self GetPlayerData( "bests", "none" ); + guidLow = self GetPlayerData( "awards", "none" ); + persistentGuid = guidHigh + "," + guidLow; + guidIsStored = guidHigh != 0 && guidLow != 0; + + if ( guidIsStored ) + { + // give IW4MAdmin time to collect IP + wait( 15 ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + return; + } + + guid = self SplitGuid(); + + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + + self SetPlayerData( "bests", "none", guid["high"] ); + self SetPlayerData( "awards", "none", guid["low"] ); +} + +SplitGuid() +{ + guid = self GetGuid(); + + if ( isDefined( self.guid ) ) + { + guid = self.guid; + } + + firstPart = 0; + secondPart = 0; + stringLength = 17; + firstPartExp = 0; + secondPartExp = 0; + + for ( i = stringLength - 1; i > 0; i-- ) + { + char = GetSubStr( guid, i - 1, i ); + if ( char == "" ) + { + char = "0"; + } + + if ( i > stringLength / 2 ) + { + value = GetIntForHexChar( char ); + power = Pow( 16, secondPartExp ); + secondPart = secondPart + ( value * power ); + secondPartExp++; + } + else + { + value = GetIntForHexChar( char ); + power = Pow( 16, firstPartExp ); + firstPart = firstPart + ( value * power ); + firstPartExp++; + } + } + + split = []; + split["low"] = int( secondPart ); + split["high"] = int( firstPart ); + + return split; +} + +Pow( num, exponent ) +{ + result = 1; + while( exponent != 0 ) + { + result = result * num; + exponent--; + } + + return result; +} + +GetIntForHexChar( char ) +{ + char = ToLower( char ); + // generated by co-pilot because I can't be bothered to make it more "elegant" + switch( char ) + { + case "0": + return 0; + case "1": + return 1; + case "2": + return 2; + case "3": + return 3; + case "4": + return 4; + case "5": + return 5; + case "6": + return 6; + case "7": + return 7; + case "8": + return 8; + case "9": + return 9; + case "a": + return 10; + case "b": + return 11; + case "c": + return 12; + case "d": + return 13; + case "e": + return 14; + case "f": + return 15; + default: + return 0; + } +}*/ + +////////////////////////////////// +// Command Implementations +///////////////////////////////// + +GiveWeaponImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + self IPrintLnBold( "You have been given a new weapon" ); + self GiveWeapon( data["weaponName"] ); + self SwitchToWeapon( data["weaponName"] ); + + return self.name + "^7 has been given ^5" + data["weaponName"]; +} + +TakeWeaponsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + self TakeAllWeapons(); + self IPrintLnBold( "All your weapons have been taken" ); + + return "Took weapons from " + self.name; +} + +TeamSwitchImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self + "^7 is not alive"; + } + + team = level.allies; + + if ( self.team == "allies" ) + { + team = level.axis; + } + + self IPrintLnBold( "You are being team switched" ); + wait( 2 ); + self [[team]](); + + return self.name + "^7 switched to " + self.team; +} + +LockControlsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + if ( !IsDefined ( self.isControlLocked ) ) + { + self.isControlLocked = false; + } + + if ( !self.isControlLocked ) + { + self freezeControls( true ); + self God(); + self Hide(); + + info = []; + info[ "alertType" ] = "Alert!"; + info[ "message" ] = "You have been frozen!"; + + self AlertImpl( undefined, info ); + + self.isControlLocked = true; + + return self.name + "\'s controls are locked"; + } + else + { + self freezeControls( false ); + self God(); + self Show(); + + self.isControlLocked = false; + + return self.name + "\'s controls are unlocked"; + } +} + +NoClipImpl( event, data ) +{ + /*if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + } + + if ( !IsDefined ( self.isNoClipped ) ) + { + self.isNoClipped = false; + } + + if ( !self.isNoClipped ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = true; + + self IPrintLnBold( "NoClip enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = false; + + self IPrintLnBold( "NoClip disabled" ); + } + + self IPrintLnBold( "NoClip enabled" );*/ + + scripts\_integration_base::LogWarning( "NoClip is not supported on T5!" ); + +} + +HideImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + if ( !IsDefined ( self.isHidden ) ) + { + self.isHidden = false; + } + + if ( !self.isHidden ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Hide(); + + self.isHidden = true; + + self IPrintLnBold( "Hide enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 0 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Show(); + + self.isHidden = false; + + self IPrintLnBold( "Hide disabled" ); + } +} + +AlertImpl( event, data ) +{ + //self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 ); + self IPrintLnBold(data["message"]); + + return "Sent alert to " + self.name; +} + +GotoImpl( event, data ) +{ + if ( IsDefined( event.target ) ) + { + return self GotoPlayerImpl( event.target ); + } + else + { + return self GotoCoordImpl( data ); + } +} + +GotoCoordImpl( data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + position = ( int( data["x"] ), int( data["y"] ), int( data["z"]) ); + self SetOrigin( position ); + self IPrintLnBold( "Moved to " + "("+ position[0] + "," + position[1] + "," + position[2] + ")" ); +} + +GotoPlayerImpl( target ) +{ + if ( !IsAlive( target ) ) + { + self IPrintLnBold( target.name + " is not alive" ); + return; + } + + self SetOrigin( target GetOrigin() ); + self IPrintLnBold( "Moved to " + target.name ); +} + +PlayerToMeImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self SetOrigin( event.origin GetOrigin() ); + return "Moved here " + self.name; +} + +KillImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self Suicide(); + self IPrintLnBold( "You were killed by " + self.name ); + + return "You killed " + self.name; +} + +SetSpectatorImpl( event, data ) +{ + if ( self.pers["team"] == "spectator" ) + { + return self.name + " is already spectating"; + } + + self [[level.spectator]](); + self IPrintLnBold( "You have been moved to spectator" ); + + return self.name + " has been moved to spectator"; +} diff --git a/GameFiles/GameInterface/_integration_t6.gsc b/GameFiles/GameInterface/_integration_t6.gsc new file mode 100644 index 000000000..07b67b649 --- /dev/null +++ b/GameFiles/GameInterface/_integration_t6.gsc @@ -0,0 +1,571 @@ +#include common_scripts\utility; +#include maps\mp\_utility; + +Init() +{ + thread Setup(); +} + +Setup() +{ + level endon( "game_ended" ); + + // it's possible that the notify type has not been defined yet so we have to hard code it + level waittill( "SharedFunctionsInitialized" ); + level.eventBus.gamename = "T6"; + + 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[level.commonFunctions.getXuid] = ::_GetXUID; + + RegisterClientCommands(); + + _SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 ); + + level notify( level.notifyTypes.gameFunctionsInitialized ); + + if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 ) + { + return; + } + + level thread OnPlayerConnect(); +} + +OnPlayerConnect() +{ + level endon ( "game_ended" ); + + for ( ;; ) + { + level waittill( "connected", player ); + + if ( scripts\_integration_base::_IsBot( player ) ) + { + // we don't want to track bots + continue; + } + + //player thread SetPersistentData(); + player thread WaitForClientEvents(); + } +} + +RegisterClientCommands() +{ + scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl ); + scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl ); + scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl ); + scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl ); + scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl ); + scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl ); + scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl ); + scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl ); + scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl ); + scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl ); + scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl ); +} + +WaitForClientEvents() +{ + self endon( "disconnect" ); + + // example of requesting a meta value + lastServerMetaKey = "LastServerPlayed"; + // self scripts\_integration_base::RequestClientMeta( lastServerMetaKey ); + + for ( ;; ) + { + self waittill( level.eventTypes.localClientEvent, event ); + + scripts\_integration_base::LogDebug( "Received client event " + event.type ); + + if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey ) + { + clientData = self.pers[level.clientDataKey]; + lastServerPlayed = clientData.meta[lastServerMetaKey]; + } + } +} + +GetTotalShotsFired() +{ + return self.pers[ "total_shots" ]; +} + +_SetDvarIfUninitialized(dvar, value) +{ + maps\mp\_utility::set_dvar_if_unset(dvar, value); +} + +_waittill_notify_or_timeout( msg, timer ) +{ + self endon( msg ); + wait( timer ); +} + +Log2Console( logLevel, message ) +{ + Print( "[" + logLevel + "] " + message + "\n" ); +} + +God() +{ + + if ( !IsDefined( self.godmode ) ) + { + self.godmode = false; + } + + if (!self.godmode ) + { + self enableInvulnerability(); + self.godmode = true; + } + else + { + self.godmode = false; + self disableInvulnerability(); + } +} + +_GetXUID() +{ + return self GetXUID(); +} + +////////////////////////////////// +// GUID helpers +///////////////////////////////// + +/*SetPersistentData() +{ + self endon( "disconnect" ); + + guidHigh = self GetPlayerData( "bests", "none" ); + guidLow = self GetPlayerData( "awards", "none" ); + persistentGuid = guidHigh + "," + guidLow; + guidIsStored = guidHigh != 0 && guidLow != 0; + + if ( guidIsStored ) + { + // give IW4MAdmin time to collect IP + wait( 15 ); + scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid ); + scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid ); + return; + } + + guid = self SplitGuid(); + + scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow ); + + self SetPlayerData( "bests", "none", guid["high"] ); + self SetPlayerData( "awards", "none", guid["low"] ); +} + +SplitGuid() +{ + guid = self GetGuid(); + + if ( isDefined( self.guid ) ) + { + guid = self.guid; + } + + firstPart = 0; + secondPart = 0; + stringLength = 17; + firstPartExp = 0; + secondPartExp = 0; + + for ( i = stringLength - 1; i > 0; i-- ) + { + char = GetSubStr( guid, i - 1, i ); + if ( char == "" ) + { + char = "0"; + } + + if ( i > stringLength / 2 ) + { + value = GetIntForHexChar( char ); + power = Pow( 16, secondPartExp ); + secondPart = secondPart + ( value * power ); + secondPartExp++; + } + else + { + value = GetIntForHexChar( char ); + power = Pow( 16, firstPartExp ); + firstPart = firstPart + ( value * power ); + firstPartExp++; + } + } + + split = []; + split["low"] = int( secondPart ); + split["high"] = int( firstPart ); + + return split; +} + +Pow( num, exponent ) +{ + result = 1; + while( exponent != 0 ) + { + result = result * num; + exponent--; + } + + return result; +} + +GetIntForHexChar( char ) +{ + char = ToLower( char ); + // generated by co-pilot because I can't be bothered to make it more "elegant" + switch( char ) + { + case "0": + return 0; + case "1": + return 1; + case "2": + return 2; + case "3": + return 3; + case "4": + return 4; + case "5": + return 5; + case "6": + return 6; + case "7": + return 7; + case "8": + return 8; + case "9": + return 9; + case "a": + return 10; + case "b": + return 11; + case "c": + return 12; + case "d": + return 13; + case "e": + return 14; + case "f": + return 15; + default: + return 0; + } +}*/ + +////////////////////////////////// +// Command Implementations +///////////////////////////////// + +GiveWeaponImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + if ( isDefined( level.player_too_many_weapons_monitor ) && level.player_too_many_weapons_monitor ) + { + level.player_too_many_weapons_monitor = false; + self notify( "stop_player_too_many_weapons_monitor" ); + } + + self IPrintLnBold( "You have been given a new weapon" ); + self GiveWeapon( data["weaponName"] ); + self SwitchToWeapon( data["weaponName"] ); + + return self.name + "^7 has been given ^5" + data["weaponName"]; +} + +TakeWeaponsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + self TakeAllWeapons(); + self IPrintLnBold( "All your weapons have been taken" ); + + return "Took weapons from " + self.name; +} + +TeamSwitchImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self + "^7 is not alive"; + } + + team = level.allies; + + if ( self.team == "allies" ) + { + team = level.axis; + } + + self IPrintLnBold( "You are being team switched" ); + wait( 2 ); + self [[team]](); + + return self.name + "^7 switched to " + self.team; +} + +LockControlsImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + "^7 is not alive"; + } + + if ( !IsDefined ( self.isControlLocked ) ) + { + self.isControlLocked = false; + } + + if ( !self.isControlLocked ) + { + self freezeControls( true ); + self God(); + self Hide(); + + info = []; + info[ "alertType" ] = "Alert!"; + info[ "message" ] = "You have been frozen!"; + + self AlertImpl( undefined, info ); + + self.isControlLocked = true; + + return self.name + "\'s controls are locked"; + } + else + { + self freezeControls( false ); + self God(); + self Show(); + + self.isControlLocked = false; + + return self.name + "\'s controls are unlocked"; + } +} + +NoClipImpl( event, data ) +{ + /*if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + } + + if ( !IsDefined ( self.isNoClipped ) ) + { + self.isNoClipped = false; + } + + if ( !self.isNoClipped ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = true; + + self IPrintLnBold( "NoClip enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Noclip(); + self Hide(); + + self.isNoClipped = false; + + self IPrintLnBold( "NoClip disabled" ); + } + + self IPrintLnBold( "NoClip enabled" );*/ + + scripts\_integration_base::LogWarning( "NoClip is not supported on T6!" ); + +} + +HideImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + if ( !IsDefined ( self.isHidden ) ) + { + self.isHidden = false; + } + + if ( !self.isHidden ) + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 1 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Hide(); + + self.isHidden = true; + + self IPrintLnBold( "Hide enabled" ); + } + else + { + self SetClientDvar( "sv_cheats", 1 ); + self SetClientDvar( "cg_thirdperson", 0 ); + self SetClientDvar( "sv_cheats", 0 ); + + self God(); + self Show(); + + self.isHidden = false; + + self IPrintLnBold( "Hide disabled" ); + } +} + +AlertImpl( event, data ) +{ + /*if ( !sessionmodeiszombiesgame() ) + {*/ + self thread oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 ); + /*} + else + { + self IPrintLnBold( data["alertType"] ); + self IPrintLnBold( data["message"] ); + }*/ + + + return "Sent alert to " + self.name; +} + +GotoImpl( event, data ) +{ + if ( IsDefined( event.target ) ) + { + return self GotoPlayerImpl( event.target ); + } + else + { + return self GotoCoordImpl( data ); + } +} + +GotoCoordImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + self IPrintLnBold( "You are not alive" ); + return; + } + + position = ( int( data["x"] ), int( data["y"] ), int( data["z"]) ); + self SetOrigin( position ); + self IPrintLnBold( "Moved to " + "("+ position[0] + "," + position[1] + "," + position[2] + ")" ); +} + +GotoPlayerImpl( target ) +{ + if ( !IsAlive( target ) ) + { + self IPrintLnBold( target.name + " is not alive" ); + return; + } + + self SetOrigin( target GetOrigin() ); + self IPrintLnBold( "Moved to " + target.name ); +} + +PlayerToMeImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self SetOrigin( event.origin GetOrigin() ); + return "Moved here " + self.name; +} + +KillImpl( event, data ) +{ + if ( !IsAlive( self ) ) + { + return self.name + " is not alive"; + } + + self Suicide(); + self IPrintLnBold( "You were killed by " + self.name ); + + return "You killed " + self.name; +} + +SetSpectatorImpl( event, data ) +{ + if ( self.pers["team"] == "spectator" ) + { + return self.name + " is already spectating"; + } + + self [[level.spectator]](); + self IPrintLnBold( "You have been moved to spectator" ); + + return self.name + " has been moved to spectator"; +} + + +////////////////////////////////// +// T6 specific functions +///////////////////////////////// + +/* +1:1 the same on MP and ZM but in different includes. Since we probably want to be able to send Alerts on non teambased wagermatechs use our own copy. +*/ +oldnotifymessage( titletext, notifytext, iconname, glowcolor, sound, duration ) +{ + /*if ( level.wagermatch && !level.teambased ) + { + return; + }*/ + notifydata = spawnstruct(); + notifydata.titletext = titletext; + notifydata.notifytext = notifytext; + notifydata.iconname = iconname; + notifydata.sound = sound; + notifydata.duration = duration; + self.startmessagenotifyqueue[ self.startmessagenotifyqueue.size ] = notifydata; + self notify( "received award" ); +} + + diff --git a/GameFiles/GameInterface/_integration_t6zm_helper.gsc b/GameFiles/GameInterface/_integration_t6zm_helper.gsc new file mode 100644 index 000000000..69a26dbda --- /dev/null +++ b/GameFiles/GameInterface/_integration_t6zm_helper.gsc @@ -0,0 +1,86 @@ +init() +{ + + level.startmessagedefaultduration = 2; + level.regulargamemessages = spawnstruct(); + level.regulargamemessages.waittime = 6; + + + level thread onplayerconnect(); +} + +onplayerconnect() +{ + for ( ;; ) + { + level waittill( "connecting", player ); + player thread displaypopupswaiter(); + } +} + +displaypopupswaiter() +{ + self endon( "disconnect" ); + self.ranknotifyqueue = []; + if ( !isDefined( self.pers[ "challengeNotifyQueue" ] ) ) + { + self.pers[ "challengeNotifyQueue" ] = []; + } + if ( !isDefined( self.pers[ "contractNotifyQueue" ] ) ) + { + self.pers[ "contractNotifyQueue" ] = []; + } + self.messagenotifyqueue = []; + self.startmessagenotifyqueue = []; + self.wagernotifyqueue = []; + while ( !level.gameended ) + { + if ( self.startmessagenotifyqueue.size == 0 && self.messagenotifyqueue.size == 0 ) + { + self waittill( "received award" ); + } + waittillframeend; + if ( level.gameended ) + { + return; + } + else + { + if ( self.startmessagenotifyqueue.size > 0 ) + { + nextnotifydata = self.startmessagenotifyqueue[ 0 ]; + arrayremoveindex( self.startmessagenotifyqueue, 0, 0 ); + if ( isDefined( nextnotifydata.duration ) ) + { + duration = nextnotifydata.duration; + } + else + { + duration = level.startmessagedefaultduration; + } + self maps\mp\gametypes_zm\_hud_message::shownotifymessage( nextnotifydata, duration ); + wait duration; + continue; + } + else if ( self.messagenotifyqueue.size > 0 ) + { + nextnotifydata = self.messagenotifyqueue[ 0 ]; + arrayremoveindex( self.messagenotifyqueue, 0, 0 ); + if ( isDefined( nextnotifydata.duration ) ) + { + duration = nextnotifydata.duration; + } + else + { + duration = level.regulargamemessages.waittime; + } + self maps\mp\gametypes_zm\_hud_message::shownotifymessage( nextnotifydata, duration ); + continue; + } + else + { + wait 1; + } + } + } +} \ No newline at end of file diff --git a/GameFiles/deploy.bat b/GameFiles/deploy.bat index cbb7b1f60..7f9cd833d 100644 --- a/GameFiles/deploy.bat +++ b/GameFiles/deploy.bat @@ -1,14 +1,20 @@ @echo off ECHO "Pluto IW5" -xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" -xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" +xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" +xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" +xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" xcopy /y .\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" ECHO "Pluto T5" -xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp" +xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts" +xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts" xcopy /y .\GameInterface\_integration_t5.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp" +xcopy /y .\GameInterface\_integration_t5zm.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\sp\zom" ECHO "Pluto T6" xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp" -xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc.src "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp" +xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" +xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" +xcopy /y .\GameInterface\_integration_t6.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" +xcopy /y .\GameInterface\_integration_t6zm_helper.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\zm" diff --git a/Plugins/ScriptPlugins/GameInterface.js b/Plugins/ScriptPlugins/GameInterface.js index 79f49409b..5fd82d993 100644 --- a/Plugins/ScriptPlugins/GameInterface.js +++ b/Plugins/ScriptPlugins/GameInterface.js @@ -385,7 +385,7 @@ const commands = [{ required: true } ], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -405,7 +405,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -423,7 +423,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -441,7 +441,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -471,7 +471,7 @@ const commands = [{ permission: 'SeniorAdmin', targetRequired: false, arguments: [], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -494,7 +494,7 @@ const commands = [{ required: true } ], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -515,7 +515,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -533,7 +533,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -560,7 +560,7 @@ const commands = [{ required: true } ], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -584,7 +584,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; @@ -602,7 +602,7 @@ const commands = [{ name: 'player', required: true }], - supportedGames: ['IW4', 'IW5', 'T5'], + supportedGames: ['IW4', 'IW5', 'T5', 'T6'], execute: (gameEvent) => { if (!validateEnabled(gameEvent.owner, gameEvent.origin)) { return; diff --git a/SharedLibraryCore/Dtos/ClientHistoryInfo.cs b/SharedLibraryCore/Dtos/ClientHistoryInfo.cs index 876be62f5..cf764219e 100644 --- a/SharedLibraryCore/Dtos/ClientHistoryInfo.cs +++ b/SharedLibraryCore/Dtos/ClientHistoryInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace SharedLibraryCore.Dtos { @@ -11,11 +12,17 @@ namespace SharedLibraryCore.Dtos public class ClientCountSnapshot { + [JsonIgnore] public DateTime Time { get; set; } + [JsonPropertyName("ts")] public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ"); + [JsonPropertyName("cc")] public int ClientCount { get; set; } + [JsonPropertyName("ci")] public bool ConnectionInterrupted { get;set; } + [JsonIgnore] public string Map { get; set; } + [JsonPropertyName("ma")] public string MapAlias { get; set; } } } diff --git a/WebfrontCore/Controllers/API/Server.cs b/WebfrontCore/Controllers/API/Server.cs index 66ca820aa..023274fbd 100644 --- a/WebfrontCore/Controllers/API/Server.cs +++ b/WebfrontCore/Controllers/API/Server.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; using WebfrontCore.Controllers.API.Models; @@ -12,9 +16,14 @@ namespace WebfrontCore.Controllers.API [Route("api/[controller]")] public class Server : BaseController { - - public Server(IManager manager) : base(manager) + private readonly IServerDataViewer _serverDataViewer; + private readonly ApplicationConfiguration _applicationConfiguration; + + public Server(IManager manager, IServerDataViewer serverDataViewer, + ApplicationConfiguration applicationConfiguration) : base(manager) { + _serverDataViewer = serverDataViewer; + _applicationConfiguration = applicationConfiguration; } [HttpGet] @@ -110,5 +119,48 @@ namespace WebfrontCore.Controllers.API completedEvent.Output }); } + + [HttpGet("{id}/history")] + public async Task GetClientHistory(string id) + { + var foundServer = Manager.GetServers().FirstOrDefault(server => server.Id == id); + + if (foundServer == null) + { + return new NotFoundResult(); + } + + var clientHistory = (await _serverDataViewer.ClientHistoryAsync(_applicationConfiguration.MaxClientHistoryTime, + CancellationToken.None))? + .FirstOrDefault(history => history.ServerId == foundServer.LegacyDatabaseId) ?? + new ClientHistoryInfo + { + ServerId = foundServer.LegacyDatabaseId, + ClientCounts = new List() + }; + + var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty(); + + if (foundServer.ClientHistory.ClientCounts.Any()) + { + counts = counts.Union(foundServer.ClientHistory.ClientCounts.Where(history => + history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue))) + .Where(history => history.Time >= DateTime.UtcNow - _applicationConfiguration.MaxClientHistoryTime); + } + + if (ViewBag.Maps?.Count == 0) + { + return Json(counts.ToList()); + } + + var clientCountSnapshots = counts.ToList(); + foreach (var count in clientCountSnapshots) + { + count.MapAlias = foundServer.Maps.FirstOrDefault(map => map.Name == count.Map)?.Alias ?? + count.Map; + } + + return Json(clientCountSnapshots); + } } } diff --git a/WebfrontCore/Controllers/DynamicFileController.cs b/WebfrontCore/Controllers/DynamicFileController.cs deleted file mode 100644 index 9b5bbb00a..000000000 --- a/WebfrontCore/Controllers/DynamicFileController.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using SharedLibraryCore; -using SharedLibraryCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace WebfrontCore.Controllers -{ - [Route("dynamic")] - public class DynamicFileController : BaseController - { - private static readonly IDictionary _fileCache = new Dictionary(); - - public DynamicFileController(IManager manager) : base(manager) - { - - } - - [Route("css/{fileName}")] - public async Task Css(string fileName) - { - if (fileName.EndsWith(".css")) - { - if (Utilities.IsDevelopment) - { - var path = Path.Join(Utilities.OperatingDirectory, "..", "..", "..", "..", "WebfrontCore", "wwwroot", "css", fileName); - string cssData = await System.IO.File.ReadAllTextAsync(path); - cssData = await Manager.MiddlewareActionHandler.Execute(cssData, "custom_css_accent"); - return Content(cssData, "text/css"); - } - - if (!_fileCache.ContainsKey(fileName)) - { - - string path = $"wwwroot{Path.DirectorySeparatorChar}css{Path.DirectorySeparatorChar}{fileName}"; - string data = await System.IO.File.ReadAllTextAsync(path); - data = await Manager.MiddlewareActionHandler.Execute(data, "custom_css_accent"); - _fileCache.Add(fileName, data); - } - - return Content(_fileCache[fileName], "text/css"); - } - - return StatusCode(400); - } - } -} diff --git a/WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs b/WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs deleted file mode 100644 index cf98a45bd..000000000 --- a/WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs +++ /dev/null @@ -1,104 +0,0 @@ -using SharedLibraryCore.Interfaces; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; - -namespace WebfrontCore.Middleware -{ - public class CustomCssAccentMiddlewareAction : IMiddlewareAction - { - private readonly List ColorReplacements = new List(); - - private class ColorMap - { - public Color Original { get; set; } - public Color Replacement { get; set; } - } - - public CustomCssAccentMiddlewareAction(string originalPrimaryColor, string originalSecondaryColor, string primaryColor, string secondaryColor) - { - primaryColor = string.IsNullOrWhiteSpace(primaryColor) ? originalPrimaryColor : primaryColor; - secondaryColor = string.IsNullOrWhiteSpace(secondaryColor) ? originalSecondaryColor : secondaryColor; - try - { - ColorReplacements.AddRange(new[] - { - new ColorMap() - { - Original = Color.FromArgb(Convert.ToInt32(originalPrimaryColor.Substring(1).ToString(), 16)), - Replacement = Color.FromArgb(Convert.ToInt32(primaryColor.Substring(1).ToString(), 16)) - }, - new ColorMap() - { - Original = Color.FromArgb(Convert.ToInt32(originalSecondaryColor.Substring(1).ToString(), 16)), - Replacement = Color.FromArgb(Convert.ToInt32(secondaryColor.Substring(1).ToString(), 16)) - } - }); - } - - catch (FormatException) - { - - } - } - - public Task Invoke(string original) - { - foreach (var color in ColorReplacements) - { - foreach (var shade in new[] { 0, -19, -25 }) - { - original = original - .Replace(ColorToHex(LightenDarkenColor(color.Original, shade)), ColorToHex(LightenDarkenColor(color.Replacement, shade)), StringComparison.OrdinalIgnoreCase) - .Replace(ColorToDec(LightenDarkenColor(color.Original, shade)), ColorToDec(LightenDarkenColor(color.Replacement, shade)), StringComparison.OrdinalIgnoreCase); - } - } - - return Task.FromResult(original); - } - - /// - /// converts color to the hex string representation - /// - /// - /// - private string ColorToHex(Color color) => $"#{color.R.ToString("X2")}{color.G.ToString("X2")}{color.B.ToString("X2")}"; - - /// - /// converts color to the rgb tuples representation - /// - /// - /// - private string ColorToDec(Color color) => $"{(int)color.R}, {(int)color.G}, {(int)color.B}"; - - /// - /// lightens or darkens a color on the given amount - /// Based off SASS darken/lighten function - /// - /// - /// - /// - private Color LightenDarkenColor(Color color, float amount) - { - int r = color.R + (int)((amount / 100.0f) * color.R); - - if (r > 255) r = 255; - else if (r < 0) r = 0; - - int g = color.G + (int)((amount / 100.0f) * color.G); - - if (g > 255) g = 255; - else if (g < 0) g = 0; - - int b = color.B + (int)((amount / 100.0f) * color.B); - - if (b > 255) b = 255; - else if (b < 0) b = 0; - - return Color.FromArgb(r, g, b); - } - } -} diff --git a/WebfrontCore/NUglify.dll b/WebfrontCore/NUglify.dll new file mode 100644 index 000000000..2df185d62 Binary files /dev/null and b/WebfrontCore/NUglify.dll differ diff --git a/WebfrontCore/Newtonsoft.Json.dll b/WebfrontCore/Newtonsoft.Json.dll new file mode 100644 index 000000000..5f2336e6c Binary files /dev/null and b/WebfrontCore/Newtonsoft.Json.dll differ diff --git a/WebfrontCore/Program.cs b/WebfrontCore/Program.cs index 12e6784bf..e4127878a 100644 --- a/WebfrontCore/Program.cs +++ b/WebfrontCore/Program.cs @@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; -using WebfrontCore.Middleware; namespace WebfrontCore { @@ -24,11 +23,6 @@ namespace WebfrontCore public static Task GetWebHostTask(CancellationToken cancellationToken) { - var config = _webHost.Services.GetRequiredService(); - Manager.MiddlewareActionHandler.Register(null, - new CustomCssAccentMiddlewareAction("#007ACC", "#fd7e14", config.WebfrontPrimaryColor, - config.WebfrontSecondaryColor), "custom_css_accent"); - return _webHost?.RunAsync(cancellationToken); } @@ -41,7 +35,12 @@ namespace WebfrontCore .UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory) #endif .UseUrls(bindUrl) - .UseKestrel() + .UseKestrel(cfg => + { + cfg.Limits.MaxConcurrentConnections = + int.Parse(Environment.GetEnvironmentVariable("MaxConcurrentRequests") ?? "1"); + cfg.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(30); + }) .ConfigureServices(registerDependenciesAction) .UseStartup() .Build(); diff --git a/WebfrontCore/Startup.cs b/WebfrontCore/Startup.cs index 2eb8ddd8f..5d6bed659 100644 --- a/WebfrontCore/Startup.cs +++ b/WebfrontCore/Startup.cs @@ -4,7 +4,6 @@ using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -24,9 +23,6 @@ using System.Reflection; using System.Threading.Tasks; using Data.Abstractions; using Data.Helpers; -using IW4MAdmin.Plugins.Stats.Helpers; -using Stats.Client.Abstractions; -using Stats.Config; using WebfrontCore.Controllers.API.Validation; using WebfrontCore.Middleware; using WebfrontCore.QueryHelpers; @@ -50,6 +46,12 @@ namespace WebfrontCore .AllowAnyHeader(); }); }); + + services.AddStackPolicy(options => + { + options.MaxConcurrentRequests = int.Parse(Environment.GetEnvironmentVariable("MaxConcurrentRequests") ?? "1"); + options.RequestQueueLimit = int.Parse(Environment.GetEnvironmentVariable("RequestQueueLimit") ?? "1"); + }); IEnumerable pluginAssemblies() { @@ -132,6 +134,7 @@ namespace WebfrontCore app.UseMiddleware(serviceProvider.GetService>(), serviceProvider.GetRequiredService().WebfrontConnectionWhitelist); } + app.UseConcurrencyLimiter(); app.UseStaticFiles(); app.UseAuthentication(); app.UseCors("AllowAll"); diff --git a/WebfrontCore/ViewComponents/ServerListViewComponent.cs b/WebfrontCore/ViewComponents/ServerListViewComponent.cs index aac16b78e..94970cc5b 100644 --- a/WebfrontCore/ViewComponents/ServerListViewComponent.cs +++ b/WebfrontCore/ViewComponents/ServerListViewComponent.cs @@ -1,29 +1,21 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Dtos; using System.Linq; -using System.Threading; using Data.Models; using Data.Models.Client.Stats; using IW4MAdmin.Plugins.Stats.Helpers; using SharedLibraryCore.Configuration; -using SharedLibraryCore.Interfaces; namespace WebfrontCore.ViewComponents { public class ServerListViewComponent : ViewComponent { - private readonly IServerDataViewer _serverDataViewer; - private readonly ApplicationConfiguration _appConfig; private readonly DefaultSettings _defaultSettings; - public ServerListViewComponent(IServerDataViewer serverDataViewer, - ApplicationConfiguration applicationConfiguration, DefaultSettings defaultSettings) + public ServerListViewComponent(DefaultSettings defaultSettings) { - _serverDataViewer = serverDataViewer; - _appConfig = applicationConfiguration; _defaultSettings = defaultSettings; } @@ -46,25 +38,6 @@ namespace WebfrontCore.ViewComponents foreach (var server in servers) { - var serverId = server.GetIdForServer().Result; - var clientHistory = _serverDataViewer.ClientHistoryAsync(_appConfig.MaxClientHistoryTime, - CancellationToken.None).Result? - .FirstOrDefault(history => history.ServerId == serverId) ?? - new ClientHistoryInfo - { - ServerId = serverId, - ClientCounts = new List() - }; - - var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty(); - - if (server.ClientHistory.ClientCounts.Any()) - { - counts = counts.Union(server.ClientHistory.ClientCounts.Where(history => - history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue))) - .Where(history => history.Time >= DateTime.UtcNow - _appConfig.MaxClientHistoryTime); - } - serverInfo.Add(new ServerInfo { Name = server.Hostname, @@ -76,11 +49,7 @@ namespace WebfrontCore.ViewComponents MaxClients = server.MaxClients, PrivateClientSlots = server.PrivateClientSlots, GameType = server.GametypeName, - ClientHistory = new ClientHistoryInfo - { - ServerId = server.EndPoint, - ClientCounts = counts.ToList() - }, + ClientHistory = new ClientHistoryInfo(), Players = server.GetClientsAsList() .Select(client => new PlayerInfo { diff --git a/WebfrontCore/Views/Server/_Server.cshtml b/WebfrontCore/Views/Server/_Server.cshtml index 77674ab25..3ee7d02fe 100644 --- a/WebfrontCore/Views/Server/_Server.cshtml +++ b/WebfrontCore/Views/Server/_Server.cshtml @@ -82,8 +82,7 @@ }
-
diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index 910054a7c..510703229 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -25,7 +25,7 @@ - false + true true true Latest @@ -47,6 +47,7 @@ + diff --git a/WebfrontCore/dotnet-bundle.deps.json b/WebfrontCore/dotnet-bundle.deps.json new file mode 100644 index 000000000..e67ccf397 --- /dev/null +++ b/WebfrontCore/dotnet-bundle.deps.json @@ -0,0 +1,669 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": { + "dotnet-bundle/1.0.0": { + "dependencies": { + "Microsoft.SourceLink.GitHub": "1.0.0", + "NUglify": "1.17.10", + "Newtonsoft.Json": "9.0.1" + }, + "runtime": { + "dotnet-bundle.dll": {} + } + }, + "Microsoft.Build.Tasks.Git/1.0.0": {}, + "Microsoft.CSharp/4.0.1": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Dynamic.Runtime": "4.0.11", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "Microsoft.NETCore.Platforms/1.0.1": {}, + "Microsoft.NETCore.Targets/1.0.1": {}, + "Microsoft.SourceLink.Common/1.0.0": {}, + "Microsoft.SourceLink.GitHub/1.0.0": { + "dependencies": { + "Microsoft.Build.Tasks.Git": "1.0.0", + "Microsoft.SourceLink.Common": "1.0.0" + } + }, + "Newtonsoft.Json/9.0.1": { + "dependencies": { + "Microsoft.CSharp": "4.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Dynamic.Runtime": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Extensions": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", + "System.Xml.XDocument": "4.0.11" + }, + "runtime": { + "lib/netstandard1.0/Newtonsoft.Json.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.1.19813" + } + } + }, + "NUglify/1.17.10": { + "runtime": { + "lib/net5.0/NUglify.dll": { + "assemblyVersion": "1.17.10.0", + "fileVersion": "1.17.10.0" + } + } + }, + "System.Collections/4.0.11": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Diagnostics.Debug/4.0.11": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Diagnostics.Tools/4.0.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Dynamic.Runtime/4.0.11": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Emit": "4.0.1", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.Globalization/4.0.11": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.IO/4.1.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.IO.FileSystem/4.0.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.IO.FileSystem.Primitives/4.0.1": { + "dependencies": { + "System.Runtime": "4.1.0" + } + }, + "System.Linq/4.1.0": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0" + } + }, + "System.Linq.Expressions/4.1.0": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Emit": "4.0.1", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Emit.Lightweight": "4.0.1", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.ObjectModel/4.0.12": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.Reflection/4.1.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Emit/4.0.1": { + "dependencies": { + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Emit.ILGeneration/4.0.1": { + "dependencies": { + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Emit.Lightweight/4.0.1": { + "dependencies": { + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Extensions/4.0.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.Primitives/4.0.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Reflection.TypeExtensions/4.1.0": { + "dependencies": { + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Resources.ResourceManager/4.0.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime/4.1.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + } + }, + "System.Runtime.Extensions/4.1.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime.Handles/4.0.1": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Runtime.InteropServices/4.1.0": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1" + } + }, + "System.Runtime.Serialization.Primitives/4.1.1": { + "dependencies": { + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Text.Encoding/4.0.11": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Text.Encoding.Extensions/4.0.11": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11" + } + }, + "System.Text.RegularExpressions/4.1.0": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + } + }, + "System.Threading/4.0.11": { + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Threading.Tasks/4.0.11": { + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + } + }, + "System.Threading.Tasks.Extensions/4.0.0": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + } + }, + "System.Xml.ReaderWriter/4.0.11": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Extensions": "4.0.0" + } + }, + "System.Xml.XDocument/4.0.11": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + } + } + } + }, + "libraries": { + "dotnet-bundle/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Microsoft.Build.Tasks.Git/1.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-z2fpmmt+1Jfl+ZnBki9nSP08S1/tbEOxFdsK1rSR+LBehIJz1Xv9/6qOOoGNqlwnAGGVGis1Oj6S8Kt9COEYlQ==", + "path": "microsoft.build.tasks.git/1.0.0", + "hashPath": "microsoft.build.tasks.git.1.0.0.nupkg.sha512" + }, + "Microsoft.CSharp/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-17h8b5mXa87XYKrrVqdgZ38JefSUqLChUQpXgSnpzsM0nDOhE40FTeNWOJ/YmySGV6tG6T8+hjz6vxbknHJr6A==", + "path": "microsoft.csharp/4.0.1", + "hashPath": "microsoft.csharp.4.0.1.nupkg.sha512" + }, + "Microsoft.NETCore.Platforms/1.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2G6OjjJzwBfNOO8myRV/nFrbTw5iA+DEm0N+qUqhrOmaVtn4pC77h38I1jsXGw5VH55+dPfQsqHD0We9sCl9FQ==", + "path": "microsoft.netcore.platforms/1.0.1", + "hashPath": "microsoft.netcore.platforms.1.0.1.nupkg.sha512" + }, + "Microsoft.NETCore.Targets/1.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==", + "path": "microsoft.netcore.targets/1.0.1", + "hashPath": "microsoft.netcore.targets.1.0.1.nupkg.sha512" + }, + "Microsoft.SourceLink.Common/1.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-G8DuQY8/DK5NN+3jm5wcMcd9QYD90UV7MiLmdljSJixi3U/vNaeBKmmXUqI4DJCOeWizIUEh4ALhSt58mR+5eg==", + "path": "microsoft.sourcelink.common/1.0.0", + "hashPath": "microsoft.sourcelink.common.1.0.0.nupkg.sha512" + }, + "Microsoft.SourceLink.GitHub/1.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-aZyGyGg2nFSxix+xMkPmlmZSsnGQ3w+mIG23LTxJZHN+GPwTQ5FpPgDo7RMOq+Kcf5D4hFWfXkGhoGstawX13Q==", + "path": "microsoft.sourcelink.github/1.0.0", + "hashPath": "microsoft.sourcelink.github.1.0.0.nupkg.sha512" + }, + "Newtonsoft.Json/9.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-U82mHQSKaIk+lpSVCbWYKNavmNH1i5xrExDEquU1i6I5pV6UMOqRnJRSlKO3cMPfcpp0RgDY+8jUXHdQ4IfXvw==", + "path": "newtonsoft.json/9.0.1", + "hashPath": "newtonsoft.json.9.0.1.nupkg.sha512" + }, + "NUglify/1.17.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-gWQYFaWZsYwr8Iz5dcrudn8fyWXgu5NKyPVDyGzF2peXqLLnlz65PEfIhiT0hs1VWCgCRU1Y+/mTp9zsMmFKBg==", + "path": "nuglify/1.17.10", + "hashPath": "nuglify.1.17.10.nupkg.sha512" + }, + "System.Collections/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", + "path": "system.collections/4.0.11", + "hashPath": "system.collections.4.0.11.nupkg.sha512" + }, + "System.Diagnostics.Debug/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", + "path": "system.diagnostics.debug/4.0.11", + "hashPath": "system.diagnostics.debug.4.0.11.nupkg.sha512" + }, + "System.Diagnostics.Tools/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", + "path": "system.diagnostics.tools/4.0.1", + "hashPath": "system.diagnostics.tools.4.0.1.nupkg.sha512" + }, + "System.Dynamic.Runtime/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", + "path": "system.dynamic.runtime/4.0.11", + "hashPath": "system.dynamic.runtime.4.0.11.nupkg.sha512" + }, + "System.Globalization/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", + "path": "system.globalization/4.0.11", + "hashPath": "system.globalization.4.0.11.nupkg.sha512" + }, + "System.IO/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "path": "system.io/4.1.0", + "hashPath": "system.io.4.1.0.nupkg.sha512" + }, + "System.IO.FileSystem/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", + "path": "system.io.filesystem/4.0.1", + "hashPath": "system.io.filesystem.4.0.1.nupkg.sha512" + }, + "System.IO.FileSystem.Primitives/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", + "path": "system.io.filesystem.primitives/4.0.1", + "hashPath": "system.io.filesystem.primitives.4.0.1.nupkg.sha512" + }, + "System.Linq/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", + "path": "system.linq/4.1.0", + "hashPath": "system.linq.4.1.0.nupkg.sha512" + }, + "System.Linq.Expressions/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", + "path": "system.linq.expressions/4.1.0", + "hashPath": "system.linq.expressions.4.1.0.nupkg.sha512" + }, + "System.ObjectModel/4.0.12": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", + "path": "system.objectmodel/4.0.12", + "hashPath": "system.objectmodel.4.0.12.nupkg.sha512" + }, + "System.Reflection/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", + "path": "system.reflection/4.1.0", + "hashPath": "system.reflection.4.1.0.nupkg.sha512" + }, + "System.Reflection.Emit/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", + "path": "system.reflection.emit/4.0.1", + "hashPath": "system.reflection.emit.4.0.1.nupkg.sha512" + }, + "System.Reflection.Emit.ILGeneration/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", + "path": "system.reflection.emit.ilgeneration/4.0.1", + "hashPath": "system.reflection.emit.ilgeneration.4.0.1.nupkg.sha512" + }, + "System.Reflection.Emit.Lightweight/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", + "path": "system.reflection.emit.lightweight/4.0.1", + "hashPath": "system.reflection.emit.lightweight.4.0.1.nupkg.sha512" + }, + "System.Reflection.Extensions/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", + "path": "system.reflection.extensions/4.0.1", + "hashPath": "system.reflection.extensions.4.0.1.nupkg.sha512" + }, + "System.Reflection.Primitives/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "path": "system.reflection.primitives/4.0.1", + "hashPath": "system.reflection.primitives.4.0.1.nupkg.sha512" + }, + "System.Reflection.TypeExtensions/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", + "path": "system.reflection.typeextensions/4.1.0", + "hashPath": "system.reflection.typeextensions.4.1.0.nupkg.sha512" + }, + "System.Resources.ResourceManager/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "path": "system.resources.resourcemanager/4.0.1", + "hashPath": "system.resources.resourcemanager.4.0.1.nupkg.sha512" + }, + "System.Runtime/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "path": "system.runtime/4.1.0", + "hashPath": "system.runtime.4.1.0.nupkg.sha512" + }, + "System.Runtime.Extensions/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "path": "system.runtime.extensions/4.1.0", + "hashPath": "system.runtime.extensions.4.1.0.nupkg.sha512" + }, + "System.Runtime.Handles/4.0.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "path": "system.runtime.handles/4.0.1", + "hashPath": "system.runtime.handles.4.0.1.nupkg.sha512" + }, + "System.Runtime.InteropServices/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "path": "system.runtime.interopservices/4.1.0", + "hashPath": "system.runtime.interopservices.4.1.0.nupkg.sha512" + }, + "System.Runtime.Serialization.Primitives/4.1.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HZ6Du5QrTG8MNJbf4e4qMO3JRAkIboGT5Fk804uZtg3Gq516S7hAqTm2UZKUHa7/6HUGdVy3AqMQKbns06G/cg==", + "path": "system.runtime.serialization.primitives/4.1.1", + "hashPath": "system.runtime.serialization.primitives.4.1.1.nupkg.sha512" + }, + "System.Text.Encoding/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "path": "system.text.encoding/4.0.11", + "hashPath": "system.text.encoding.4.0.11.nupkg.sha512" + }, + "System.Text.Encoding.Extensions/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", + "path": "system.text.encoding.extensions/4.0.11", + "hashPath": "system.text.encoding.extensions.4.0.11.nupkg.sha512" + }, + "System.Text.RegularExpressions/4.1.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", + "path": "system.text.regularexpressions/4.1.0", + "hashPath": "system.text.regularexpressions.4.1.0.nupkg.sha512" + }, + "System.Threading/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "path": "system.threading/4.0.11", + "hashPath": "system.threading.4.0.11.nupkg.sha512" + }, + "System.Threading.Tasks/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "path": "system.threading.tasks/4.0.11", + "hashPath": "system.threading.tasks.4.0.11.nupkg.sha512" + }, + "System.Threading.Tasks.Extensions/4.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", + "path": "system.threading.tasks.extensions/4.0.0", + "hashPath": "system.threading.tasks.extensions.4.0.0.nupkg.sha512" + }, + "System.Xml.ReaderWriter/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", + "path": "system.xml.readerwriter/4.0.11", + "hashPath": "system.xml.readerwriter.4.0.11.nupkg.sha512" + }, + "System.Xml.XDocument/4.0.11": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", + "path": "system.xml.xdocument/4.0.11", + "hashPath": "system.xml.xdocument.4.0.11.nupkg.sha512" + } + } +} \ No newline at end of file diff --git a/WebfrontCore/dotnet-bundle.dll b/WebfrontCore/dotnet-bundle.dll new file mode 100644 index 000000000..e1edd02b7 Binary files /dev/null and b/WebfrontCore/dotnet-bundle.dll differ diff --git a/WebfrontCore/dotnet-bundle.exe b/WebfrontCore/dotnet-bundle.exe new file mode 100644 index 000000000..428b750f0 Binary files /dev/null and b/WebfrontCore/dotnet-bundle.exe differ diff --git a/WebfrontCore/dotnet-bundle.runtimeconfig.json b/WebfrontCore/dotnet-bundle.runtimeconfig.json new file mode 100644 index 000000000..4986d16ee --- /dev/null +++ b/WebfrontCore/dotnet-bundle.runtimeconfig.json @@ -0,0 +1,9 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0" + } + } +} \ No newline at end of file diff --git a/WebfrontCore/wwwroot/js/server.js b/WebfrontCore/wwwroot/js/server.js index a60db1d26..c7cace248 100644 --- a/WebfrontCore/wwwroot/js/server.js +++ b/WebfrontCore/wwwroot/js/server.js @@ -27,25 +27,25 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { let lastMap = ''; playerHistory.forEach((elem, i) => { - if (elem.map !== lastMap) { + if (elem.ma !== lastMap) { mapChange.push(i); - lastMap = elem.map; + lastMap = elem; } if (elem.connectionInterrupted) { offlineTime.push({ clientCount: maxClients, - timeString: elem.timeString + timeString: elem.ts }); onlineTime.push({ clientCount: 0, - timeString: elem.timeString + timeString: elem.ts }) } else { offlineTime.push({ clientCount: 0, - timeString: elem.timeString + timeString: elem.ts }); onlineTime.push(elem) @@ -60,9 +60,9 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { return new Chart(document.getElementById(`server_history_canvas_${i}`), { type: 'line', data: { - labels: playerHistory.map(history => history.timeString), + labels: playerHistory.map(history => history.ts), datasets: [{ - data: onlineTime.map(history => history.clientCount), + data: onlineTime.map(history => history.cc), backgroundColor: fillColor, borderColor: primaryColor, borderWidth: 2, @@ -70,7 +70,7 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { hoverBorderWidth: 2 }, { - data: offlineTime.map(history => history.clientCount), + data: offlineTime.map(history => history.cc), backgroundColor: createDiagonalPattern(offlineFillColor), borderColor: offlineFillColor, borderWidth: 2, @@ -88,7 +88,7 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) { callbacks: { // todo: localization at some point title: context => moment(context[0].label).local().calendar(), - label: context => context.datasetIndex !== 1 ? `${context.value} ${_localization['WEBFRONT_SCRIPT_SERVER_PLAYERS']} | ${playerHistory[context.index].mapAlias}` : context.value === '0' ? '' : _localization['WEBFRONT_SCRIPT_SERVER_UNREACHABLE'], + label: context => context.datasetIndex !== 1 ? `${context.value} ${_localization['WEBFRONT_SCRIPT_SERVER_PLAYERS']} | ${playerHistory[context.index].ma}` : context.value === '0' ? '' : _localization['WEBFRONT_SCRIPT_SERVER_UNREACHABLE'], }, mode: 'nearest', intersect: false, @@ -153,13 +153,14 @@ $(document).ready(function () { $(this).parent().parent().find('.server-header-ip-address').show(); }); - $('.server-history-row').each(function (index, element) { - let clientHistory = $(this).data('clienthistory-ex'); + $('.server-history-row').each(async function (index, element) { const serverId = $(this).data('serverid'); + const serverEp = $(this).data('server-endpoint'); setInterval(() => refreshClientActivity(serverId), 2000 + (index * 100)); let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text()); let width = $('.server-header').first().width(); - getPlayerHistoryChart(clientHistory, serverId, width, maxClients); + const clientHistory = await fetch(`/api/server/${serverEp}/history`); + getPlayerHistoryChart(await clientHistory.json(), serverId, width, maxClients); }); $('.moment-date').each((index, element) => {