Compare commits
No commits in common. "release/pre" and "feature/eventing-update" have entirely different histories.
release/pr
...
feature/ev
@ -55,7 +55,7 @@ public class AlertManager : IAlertManager
|
|||||||
alerts = alerts.Concat(_states[client.ClientId].AsReadOnly());
|
alerts = alerts.Concat(_states[client.ClientId].AsReadOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
return alerts.OrderByDescending(alert => alert.OccuredAt).ToList();
|
return alerts.OrderByDescending(alert => alert.OccuredAt);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Jint" Version="3.0.0-beta-2049" />
|
<PackageReference Include="Jint" Version="3.0.0-beta-2047" />
|
||||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@ -32,13 +32,11 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="RestEase" Version="1.5.7" />
|
<PackageReference Include="RestEase" Version="1.5.7" />
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
|
||||||
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.4.0-alpha.22272.1" />
|
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||||
<TieredCompilation>true</TieredCompilation>
|
<TieredCompilation>true</TieredCompilation>
|
||||||
<LangVersion>Latest</LangVersion>
|
<LangVersion>Latest</LangVersion>
|
||||||
|
@ -309,7 +309,6 @@ namespace IW4MAdmin.Application
|
|||||||
#region EVENTS
|
#region EVENTS
|
||||||
IGameServerEventSubscriptions.ServerValueRequested += OnServerValueRequested;
|
IGameServerEventSubscriptions.ServerValueRequested += OnServerValueRequested;
|
||||||
IGameServerEventSubscriptions.ServerValueSetRequested += OnServerValueSetRequested;
|
IGameServerEventSubscriptions.ServerValueSetRequested += OnServerValueSetRequested;
|
||||||
IGameServerEventSubscriptions.ServerCommandExecuteRequested += OnServerCommandExecuteRequested;
|
|
||||||
await IManagementEventSubscriptions.InvokeLoadAsync(this, CancellationToken);
|
await IManagementEventSubscriptions.InvokeLoadAsync(this, CancellationToken);
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
@ -581,9 +580,9 @@ namespace IW4MAdmin.Application
|
|||||||
throw lastException;
|
throw lastException;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (successServers != config.Servers.Length && !AppContext.TryGetSwitch("NoConfirmPrompt", out _))
|
if (successServers != config.Servers.Length)
|
||||||
{
|
{
|
||||||
if (!Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"].PromptBool())
|
if (!Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"]))
|
||||||
{
|
{
|
||||||
throw lastException;
|
throw lastException;
|
||||||
}
|
}
|
||||||
@ -789,63 +788,9 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OnServerValueSetRequested(ServerValueSetRequestEvent requestEvent, CancellationToken token)
|
private async Task OnServerValueSetRequested(ServerValueSetRequestEvent requestEvent, CancellationToken token)
|
||||||
{
|
{
|
||||||
return ExecuteWrapperForServerQuery(requestEvent, token, async (innerEvent) =>
|
if (requestEvent.Server is not IW4MServer server)
|
||||||
{
|
|
||||||
if (innerEvent.DelayMs.HasValue)
|
|
||||||
{
|
|
||||||
await Task.Delay(innerEvent.DelayMs.Value, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (innerEvent.TimeoutMs is not null)
|
|
||||||
{
|
|
||||||
using var timeoutTokenSource = new CancellationTokenSource(innerEvent.TimeoutMs.Value);
|
|
||||||
using var linkedTokenSource =
|
|
||||||
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
|
|
||||||
token = linkedTokenSource.Token;
|
|
||||||
}
|
|
||||||
|
|
||||||
await innerEvent.Server.SetDvarAsync(innerEvent.ValueName, innerEvent.Value, token);
|
|
||||||
}, (completed, innerEvent) =>
|
|
||||||
{
|
|
||||||
QueueEvent(new ServerValueSetCompleteEvent
|
|
||||||
{
|
|
||||||
Server = innerEvent.Server,
|
|
||||||
Source = innerEvent.Server,
|
|
||||||
Success = completed,
|
|
||||||
Value = innerEvent.Value,
|
|
||||||
ValueName = innerEvent.ValueName
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task OnServerCommandExecuteRequested(ServerCommandRequestExecuteEvent executeEvent, CancellationToken token)
|
|
||||||
{
|
|
||||||
return ExecuteWrapperForServerQuery(executeEvent, token, async (innerEvent) =>
|
|
||||||
{
|
|
||||||
if (innerEvent.DelayMs.HasValue)
|
|
||||||
{
|
|
||||||
await Task.Delay(innerEvent.DelayMs.Value, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (innerEvent.TimeoutMs is not null)
|
|
||||||
{
|
|
||||||
using var timeoutTokenSource = new CancellationTokenSource(innerEvent.TimeoutMs.Value);
|
|
||||||
using var linkedTokenSource =
|
|
||||||
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
|
|
||||||
token = linkedTokenSource.Token;
|
|
||||||
}
|
|
||||||
|
|
||||||
await innerEvent.Server.ExecuteCommandAsync(innerEvent.Command, token);
|
|
||||||
}, (_, __) => Task.CompletedTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ExecuteWrapperForServerQuery<TEventType>(TEventType serverEvent, CancellationToken token,
|
|
||||||
Func<TEventType, Task> action, Func<bool, TEventType, Task> complete) where TEventType : GameServerEvent
|
|
||||||
{
|
|
||||||
if (serverEvent.Server is not IW4MServer)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -853,7 +798,20 @@ namespace IW4MAdmin.Application
|
|||||||
var completed = false;
|
var completed = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await action(serverEvent);
|
if (requestEvent.DelayMs.HasValue)
|
||||||
|
{
|
||||||
|
await Task.Delay(requestEvent.DelayMs.Value, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestEvent.TimeoutMs is not null)
|
||||||
|
{
|
||||||
|
using var timeoutTokenSource = new CancellationTokenSource(requestEvent.TimeoutMs.Value);
|
||||||
|
using var linkedTokenSource =
|
||||||
|
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
|
||||||
|
token = linkedTokenSource.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.SetDvarAsync(requestEvent.ValueName, requestEvent.Value, token);
|
||||||
completed = true;
|
completed = true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -862,7 +820,14 @@ namespace IW4MAdmin.Application
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await complete(completed, serverEvent);
|
QueueEvent(new ServerValueSetCompleteEvent
|
||||||
|
{
|
||||||
|
Server = server,
|
||||||
|
Source = server,
|
||||||
|
Success = completed,
|
||||||
|
Value = requestEvent.Value,
|
||||||
|
ValueName = requestEvent.ValueName
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Data.Models.Client;
|
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
using SharedLibraryCore;
|
|
||||||
using SharedLibraryCore.Commands;
|
|
||||||
using SharedLibraryCore.Configuration;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Commands;
|
|
||||||
|
|
||||||
public class SetLogLevelCommand : Command
|
|
||||||
{
|
|
||||||
private readonly Func<string, LoggingLevelSwitch> _levelSwitchResolver;
|
|
||||||
|
|
||||||
public SetLogLevelCommand(CommandConfiguration config, ITranslationLookup layout, Func<string, LoggingLevelSwitch> levelSwitchResolver) : base(config, layout)
|
|
||||||
{
|
|
||||||
_levelSwitchResolver = levelSwitchResolver;
|
|
||||||
|
|
||||||
Name = "loglevel";
|
|
||||||
Alias = "ll";
|
|
||||||
Description = "set minimum logging level";
|
|
||||||
Permission = EFClient.Permission.Owner;
|
|
||||||
Arguments = new CommandArgument[]
|
|
||||||
{
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Name = "Log Level",
|
|
||||||
Required = true
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Name = "Override",
|
|
||||||
Required = false
|
|
||||||
},
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Name = "IsDevelopment",
|
|
||||||
Required = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
|
||||||
{
|
|
||||||
var args = gameEvent.Data.Split(" ");
|
|
||||||
if (!Enum.TryParse<LogEventLevel>(args[0], out var minLevel))
|
|
||||||
{
|
|
||||||
await gameEvent.Origin.TellAsync(new[]
|
|
||||||
{
|
|
||||||
$"Valid log values: {string.Join(",", Enum.GetValues<LogEventLevel>())}"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = string.Empty;
|
|
||||||
|
|
||||||
if (args.Length > 1)
|
|
||||||
{
|
|
||||||
context = args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
var loggingSwitch = _levelSwitchResolver(context);
|
|
||||||
loggingSwitch.MinimumLevel = minLevel;
|
|
||||||
|
|
||||||
if (args.Length > 2 && (args[2] == "1" || args[2].ToLower() == "true"))
|
|
||||||
{
|
|
||||||
AppContext.SetSwitch("IsDevelop", true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AppContext.SetSwitch("IsDevelop", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
await gameEvent.Origin.TellAsync(new[]
|
|
||||||
{ $"Set minimum log level to {loggingSwitch.MinimumLevel.ToString()}" });
|
|
||||||
}
|
|
||||||
}
|
|
@ -311,10 +311,6 @@
|
|||||||
{
|
{
|
||||||
"Name": "tdm",
|
"Name": "tdm",
|
||||||
"Alias": "Team Deathmatch"
|
"Alias": "Team Deathmatch"
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "zom",
|
|
||||||
"Alias": "Zombies"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -848,23 +844,7 @@
|
|||||||
{
|
{
|
||||||
"Alias": "Upheaval",
|
"Alias": "Upheaval",
|
||||||
"Name": "mp_suburban"
|
"Name": "mp_suburban"
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"Alias": "Nacht Der Untoten",
|
|
||||||
"Name": "nazi_zombie_prototype"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Alias": "Verrückt",
|
|
||||||
"Name": "nazi_zombie_asylum"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Alias": "Shi No Numa",
|
|
||||||
"Name": "nazi_zombie_sumpf"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Alias": "Der Riese",
|
|
||||||
"Name": "nazi_zombie_factory"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1234,47 +1214,7 @@
|
|||||||
{
|
{
|
||||||
"Alias": "Zoo",
|
"Alias": "Zoo",
|
||||||
"Name": "mp_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": "Shangri-La",
|
|
||||||
"Name": "zombie_temple"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1759,7 +1699,7 @@
|
|||||||
"Name": "zm_theater"
|
"Name": "zm_theater"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Alias": "Moon",
|
"Alias": "Moom",
|
||||||
"Name": "zm_moon"
|
"Name": "zm_moon"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -141,7 +141,7 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
|
|
||||||
if (timeMatch.Success)
|
if (timeMatch.Success)
|
||||||
{
|
{
|
||||||
if (timeMatch.Values[0].Contains(':'))
|
if (timeMatch.Values[0].Contains(":"))
|
||||||
{
|
{
|
||||||
gameTime = timeMatch
|
gameTime = timeMatch
|
||||||
.Values
|
.Values
|
||||||
@ -180,16 +180,6 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
case GameEvent.EventType.MapChange:
|
case GameEvent.EventType.MapChange:
|
||||||
return ParseMatchStartEvent(logLine, gameTime);
|
return ParseMatchStartEvent(logLine, gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logLine.StartsWith("GSE;"))
|
|
||||||
{
|
|
||||||
return new GameScriptEvent
|
|
||||||
{
|
|
||||||
ScriptData = logLine,
|
|
||||||
GameTime = gameTime,
|
|
||||||
Source = GameEvent.EventSource.Log
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventKey is null || !_customEventRegistrations.ContainsKey(eventKey))
|
if (eventKey is null || !_customEventRegistrations.ContainsKey(eventKey))
|
||||||
{
|
{
|
||||||
@ -588,15 +578,11 @@ namespace IW4MAdmin.Application.EventParsers
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var message = new string(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
var message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||||
.Where(c => !char.IsControl(c)).ToArray());
|
.Replace(Configuration.LocalizeText, "")
|
||||||
|
.Trim();
|
||||||
|
|
||||||
if (message.StartsWith("/"))
|
if (message.Length <= 0)
|
||||||
{
|
|
||||||
message = message[1..];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(message))
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
@ -18,10 +17,7 @@ namespace IW4MAdmin.Application.Extensions
|
|||||||
{
|
{
|
||||||
public static class StartupExtensions
|
public static class StartupExtensions
|
||||||
{
|
{
|
||||||
private static ILogger _defaultLogger;
|
private static ILogger _defaultLogger = null;
|
||||||
private static readonly LoggingLevelSwitch LevelSwitch = new();
|
|
||||||
private static readonly LoggingLevelSwitch MicrosoftLevelSwitch = new();
|
|
||||||
private static readonly LoggingLevelSwitch SystemLevelSwitch = new();
|
|
||||||
|
|
||||||
public static IServiceCollection AddBaseLogger(this IServiceCollection services,
|
public static IServiceCollection AddBaseLogger(this IServiceCollection services,
|
||||||
ApplicationConfiguration appConfig)
|
ApplicationConfiguration appConfig)
|
||||||
@ -33,37 +29,21 @@ namespace IW4MAdmin.Application.Extensions
|
|||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var loggerConfig = new LoggerConfiguration()
|
var loggerConfig = new LoggerConfiguration()
|
||||||
.ReadFrom.Configuration(configuration);
|
.ReadFrom.Configuration(configuration)
|
||||||
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning);
|
||||||
LevelSwitch.MinimumLevel = Enum.Parse<LogEventLevel>(configuration["Serilog:MinimumLevel:Default"]);
|
|
||||||
MicrosoftLevelSwitch.MinimumLevel = Enum.Parse<LogEventLevel>(configuration["Serilog:MinimumLevel:Override:Microsoft"]);
|
|
||||||
SystemLevelSwitch.MinimumLevel = Enum.Parse<LogEventLevel>(configuration["Serilog:MinimumLevel:Override:System"]);
|
|
||||||
|
|
||||||
loggerConfig = loggerConfig.MinimumLevel.ControlledBy(LevelSwitch);
|
|
||||||
loggerConfig = loggerConfig.MinimumLevel.Override("Microsoft", MicrosoftLevelSwitch)
|
|
||||||
.MinimumLevel.Override("System", SystemLevelSwitch);
|
|
||||||
|
|
||||||
if (Utilities.IsDevelopment)
|
if (Utilities.IsDevelopment)
|
||||||
{
|
{
|
||||||
loggerConfig = loggerConfig.WriteTo.Console(
|
loggerConfig = loggerConfig.WriteTo.Console(
|
||||||
outputTemplate:
|
outputTemplate:
|
||||||
"[{Timestamp:HH:mm:ss} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||||
.MinimumLevel.Debug();
|
.MinimumLevel.Debug();
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaultLogger = loggerConfig.CreateLogger();
|
_defaultLogger = loggerConfig.CreateLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddSingleton((string context) =>
|
|
||||||
{
|
|
||||||
return context.ToLower() switch
|
|
||||||
{
|
|
||||||
"microsoft" => MicrosoftLevelSwitch,
|
|
||||||
"system" => SystemLevelSwitch,
|
|
||||||
_ => LevelSwitch
|
|
||||||
};
|
|
||||||
});
|
|
||||||
services.AddLogging(builder => builder.AddSerilog(_defaultLogger, dispose: true));
|
services.AddLogging(builder => builder.AddSerilog(_defaultLogger, dispose: true));
|
||||||
services.AddSingleton(new LoggerFactory()
|
services.AddSingleton(new LoggerFactory()
|
||||||
.AddSerilog(_defaultLogger, true));
|
.AddSerilog(_defaultLogger, true));
|
||||||
|
@ -118,8 +118,6 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action<TConfigurationType> Updated;
|
|
||||||
|
|
||||||
private async Task InternalSet(TConfigurationType configuration, bool awaitSemaphore)
|
private async Task InternalSet(TConfigurationType configuration, bool awaitSemaphore)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -129,7 +127,7 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
|
|||||||
await _onIo.WaitAsync();
|
await _onIo.WaitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var fileStream = File.Create(_path);
|
await using var fileStream = File.OpenWrite(_path);
|
||||||
await JsonSerializer.SerializeAsync(fileStream, configuration, _serializerOptions);
|
await JsonSerializer.SerializeAsync(fileStream, configuration, _serializerOptions);
|
||||||
await fileStream.DisposeAsync();
|
await fileStream.DisposeAsync();
|
||||||
_configurationInstance = configuration;
|
_configurationInstance = configuration;
|
||||||
@ -165,7 +163,6 @@ public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHand
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
CopyUpdatedProperties(readConfiguration);
|
CopyUpdatedProperties(readConfiguration);
|
||||||
Updated?.Invoke(readConfiguration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -53,7 +53,6 @@ namespace IW4MAdmin
|
|||||||
private readonly CommandConfiguration _commandConfiguration;
|
private readonly CommandConfiguration _commandConfiguration;
|
||||||
private EFServer _cachedDatabaseServer;
|
private EFServer _cachedDatabaseServer;
|
||||||
private readonly StatManager _statManager;
|
private readonly StatManager _statManager;
|
||||||
private readonly ApplicationConfiguration _appConfig;
|
|
||||||
|
|
||||||
public IW4MServer(
|
public IW4MServer(
|
||||||
ServerConfiguration serverConfiguration,
|
ServerConfiguration serverConfiguration,
|
||||||
@ -78,7 +77,6 @@ namespace IW4MAdmin
|
|||||||
_serverCache = serverCache;
|
_serverCache = serverCache;
|
||||||
_commandConfiguration = commandConfiguration;
|
_commandConfiguration = commandConfiguration;
|
||||||
_statManager = serviceProvider.GetRequiredService<StatManager>();
|
_statManager = serviceProvider.GetRequiredService<StatManager>();
|
||||||
_appConfig = serviceProvider.GetService<ApplicationConfiguration>();
|
|
||||||
|
|
||||||
IGameServerEventSubscriptions.MonitoringStarted += async (gameEvent, token) =>
|
IGameServerEventSubscriptions.MonitoringStarted += async (gameEvent, token) =>
|
||||||
{
|
{
|
||||||
@ -377,6 +375,7 @@ namespace IW4MAdmin
|
|||||||
if (E.Origin.State != ClientState.Connected)
|
if (E.Origin.State != ClientState.Connected)
|
||||||
{
|
{
|
||||||
E.Origin.State = ClientState.Connected;
|
E.Origin.State = ClientState.Connected;
|
||||||
|
E.Origin.LastConnection = DateTime.UtcNow;
|
||||||
E.Origin.Connections += 1;
|
E.Origin.Connections += 1;
|
||||||
|
|
||||||
ChatHistory.Add(new ChatInfo()
|
ChatHistory.Add(new ChatInfo()
|
||||||
@ -539,7 +538,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
E.Target.SetLevel(Permission.User, E.Origin);
|
E.Target.SetLevel(Permission.User, E.Origin);
|
||||||
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
|
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
|
||||||
E.Target.GameName, E.Target.CurrentAlias?.IPAddress, new[] {EFPenalty.PenaltyType.Flag});
|
E.Target.GameName, E.Target.CurrentAlias?.IPAddress);
|
||||||
await Manager.GetPenaltyService().Create(unflagPenalty);
|
await Manager.GetPenaltyService().Create(unflagPenalty);
|
||||||
|
|
||||||
Manager.QueueEvent(new ClientPenaltyRevokeEvent
|
Manager.QueueEvent(new ClientPenaltyRevokeEvent
|
||||||
@ -726,12 +725,12 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
if (dict.ContainsKey("gametype"))
|
if (dict.ContainsKey("gametype"))
|
||||||
{
|
{
|
||||||
UpdateGametype(dict["gametype"]);
|
Gametype = dict["gametype"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dict.ContainsKey("hostname"))
|
if (dict.ContainsKey("hostname"))
|
||||||
{
|
{
|
||||||
UpdateHostname(dict["hostname"]);
|
Hostname = dict["hostname"];
|
||||||
}
|
}
|
||||||
|
|
||||||
var newMapName = dict.ContainsKey("mapname")
|
var newMapName = dict.ContainsKey("mapname")
|
||||||
@ -744,30 +743,29 @@ namespace IW4MAdmin
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dict = (Dictionary<string, string>)E.Extra;
|
var dict = (Dictionary<string, string>)E.Extra;
|
||||||
|
|
||||||
if (dict.ContainsKey("g_gametype"))
|
if (dict.ContainsKey("g_gametype"))
|
||||||
{
|
{
|
||||||
UpdateGametype(dict["g_gametype"]);
|
Gametype = dict["g_gametype"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dict.ContainsKey("sv_hostname"))
|
if (dict.ContainsKey("sv_hostname"))
|
||||||
{
|
{
|
||||||
UpdateHostname(dict["sv_hostname"]);
|
Hostname = dict["sv_hostname"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dict.ContainsKey("sv_maxclients"))
|
if (dict.ContainsKey("sv_maxclients"))
|
||||||
{
|
{
|
||||||
UpdateMaxPlayers(int.Parse(dict["sv_maxclients"]));
|
MaxClients = int.Parse(dict["sv_maxclients"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (dict.ContainsKey("com_maxclients"))
|
else if (dict.ContainsKey("com_maxclients"))
|
||||||
{
|
{
|
||||||
UpdateMaxPlayers(int.Parse(dict["com_maxclients"]));
|
MaxClients = int.Parse(dict["com_maxclients"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (dict.ContainsKey("com_maxplayers"))
|
else if (dict.ContainsKey("com_maxplayers"))
|
||||||
{
|
{
|
||||||
UpdateMaxPlayers(int.Parse(dict["com_maxplayers"]));
|
MaxClients = int.Parse(dict["com_maxplayers"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dict.ContainsKey("mapname"))
|
if (dict.ContainsKey("mapname"))
|
||||||
@ -980,44 +978,23 @@ namespace IW4MAdmin
|
|||||||
return id < 0 ? Math.Abs(id) : id;
|
return id < 0 ? Math.Abs(id) : id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMap(string mapName)
|
private void UpdateMap(string mapname)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(mapName))
|
if (!string.IsNullOrEmpty(mapname))
|
||||||
{
|
{
|
||||||
return;
|
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map()
|
||||||
}
|
{
|
||||||
|
Alias = mapname,
|
||||||
var foundMap = Maps.Find(m => m.Name == mapName) ?? new Map
|
Name = mapname
|
||||||
{
|
};
|
||||||
Alias = mapName,
|
|
||||||
Name = mapName
|
|
||||||
};
|
|
||||||
|
|
||||||
if (foundMap == CurrentMap)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentMap = foundMap;
|
|
||||||
|
|
||||||
using(LogContext.PushProperty("Server", Id))
|
|
||||||
{
|
|
||||||
ServerLogger.LogDebug("Updating map to {@CurrentMap}", CurrentMap);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateGametype(string gameType)
|
private void UpdateGametype(string gameType)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(gameType))
|
if (!string.IsNullOrEmpty(gameType))
|
||||||
{
|
{
|
||||||
return;
|
Gametype = gameType;
|
||||||
}
|
|
||||||
|
|
||||||
Gametype = gameType;
|
|
||||||
|
|
||||||
using(LogContext.PushProperty("Server", Id))
|
|
||||||
{
|
|
||||||
ServerLogger.LogDebug("Updating gametype to {Gametype}", gameType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1028,7 +1005,7 @@ namespace IW4MAdmin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using(LogContext.PushProperty("Server", Id))
|
using(LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
ServerLogger.LogDebug("Updating hostname to {HostName}", hostname);
|
ServerLogger.LogDebug("Updating hostname to {HostName}", hostname);
|
||||||
}
|
}
|
||||||
@ -1043,7 +1020,7 @@ namespace IW4MAdmin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using(LogContext.PushProperty("Server", Id))
|
using(LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
ServerLogger.LogDebug("Updating max clients to {MaxPlayers}", maxPlayers);
|
ServerLogger.LogDebug("Updating max clients to {MaxPlayers}", maxPlayers);
|
||||||
}
|
}
|
||||||
@ -1270,13 +1247,15 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
private void RunServerCollection()
|
private void RunServerCollection()
|
||||||
{
|
{
|
||||||
if (DateTime.Now - _lastPlayerCount < _appConfig?.ServerDataCollectionInterval)
|
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
|
||||||
|
|
||||||
|
if (DateTime.Now - _lastPlayerCount < appConfig?.ServerDataCollectionInterval)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxItems = Math.Ceiling(_appConfig!.MaxClientHistoryTime.TotalMinutes /
|
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
|
||||||
_appConfig.ServerDataCollectionInterval.TotalMinutes);
|
appConfig.ServerDataCollectionInterval.TotalMinutes);
|
||||||
|
|
||||||
while (ClientHistory.ClientCounts.Count > maxItems)
|
while (ClientHistory.ClientCounts.Count > maxItems)
|
||||||
{
|
{
|
||||||
@ -1627,12 +1606,6 @@ namespace IW4MAdmin
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<string[]> ExecuteCommandAsync(string command, CancellationToken token = default) =>
|
|
||||||
Utilities.ExecuteCommandAsync(this, command, token);
|
|
||||||
|
|
||||||
public override Task SetDvarAsync(string name, object value, CancellationToken token = default) =>
|
|
||||||
Utilities.SetDvarAsync(this, name, value, token);
|
|
||||||
|
|
||||||
public override async Task TempBan(string reason, TimeSpan length, EFClient targetClient, EFClient originClient)
|
public override async Task TempBan(string reason, TimeSpan length, EFClient targetClient, EFClient originClient)
|
||||||
{
|
{
|
||||||
// ensure player gets kicked if command not performed on them in the same server
|
// ensure player gets kicked if command not performed on them in the same server
|
||||||
|
@ -38,7 +38,6 @@ using Microsoft.Extensions.Logging;
|
|||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
||||||
using IW4MAdmin.Plugins.Stats.Client;
|
using IW4MAdmin.Plugins.Stats.Client;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Client;
|
using Stats.Client;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
@ -58,30 +57,9 @@ namespace IW4MAdmin.Application
|
|||||||
/// entrypoint of the application
|
/// entrypoint of the application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task Main(bool noConfirm = false, int? maxConcurrentRequests = 25, int? requestQueueLimit = 25)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
|
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
|
|
||||||
{
|
|
||||||
var libraryName = eventArgs.Name.Split(",").First();
|
|
||||||
|
|
||||||
var overrides = new[] { nameof(SharedLibraryCore), nameof(Stats) };
|
|
||||||
if (!overrides.Contains(libraryName))
|
|
||||||
{
|
|
||||||
return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(asm => asm.FullName == eventArgs.Name);
|
|
||||||
}
|
|
||||||
// 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.OutputEncoding = Encoding.UTF8;
|
||||||
Console.ForegroundColor = ConsoleColor.Gray;
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
@ -94,7 +72,7 @@ namespace IW4MAdmin.Application
|
|||||||
Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
|
Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
|
||||||
Console.WriteLine("=====================================================");
|
Console.WriteLine("=====================================================");
|
||||||
|
|
||||||
await LaunchAsync();
|
await LaunchAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -120,13 +98,13 @@ namespace IW4MAdmin.Application
|
|||||||
/// task that initializes application and starts the application monitoring and runtime tasks
|
/// task that initializes application and starts the application monitoring and runtime tasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static async Task LaunchAsync()
|
private static async Task LaunchAsync(string[] args)
|
||||||
{
|
{
|
||||||
restart:
|
restart:
|
||||||
ITranslationLookup translationLookup = null;
|
ITranslationLookup translationLookup = null;
|
||||||
var logger = BuildDefaultLogger<Program>(new ApplicationConfiguration());
|
var logger = BuildDefaultLogger<Program>(new ApplicationConfiguration());
|
||||||
Utilities.DefaultLogger = logger;
|
Utilities.DefaultLogger = logger;
|
||||||
logger.LogInformation("Begin IW4MAdmin startup. Version is {Version}", Version);
|
logger.LogInformation("Begin IW4MAdmin startup. Version is {Version} {@Args}", Version, args);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -147,7 +125,8 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
await _serverManager.Init();
|
await _serverManager.Init();
|
||||||
|
|
||||||
_applicationTask = RunApplicationTasksAsync(logger, _serverManager, _serviceProvider);
|
_applicationTask = Task.WhenAll(RunApplicationTasksAsync(logger, _serviceProvider),
|
||||||
|
_serverManager.Start());
|
||||||
|
|
||||||
await _applicationTask;
|
await _applicationTask;
|
||||||
logger.LogInformation("Shutdown completed successfully");
|
logger.LogInformation("Shutdown completed successfully");
|
||||||
@ -206,49 +185,14 @@ namespace IW4MAdmin.Application
|
|||||||
/// runs the core application tasks
|
/// runs the core application tasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static Task RunApplicationTasksAsync(ILogger logger, ApplicationManager applicationManager,
|
private static Task RunApplicationTasksAsync(ILogger logger, IServiceProvider serviceProvider)
|
||||||
IServiceProvider serviceProvider)
|
|
||||||
{
|
{
|
||||||
var collectionService = serviceProvider.GetRequiredService<IServerDataCollector>();
|
|
||||||
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
|
|
||||||
var masterCommunicator = serviceProvider.GetRequiredService<IMasterCommunication>();
|
|
||||||
var webfrontLifetime = serviceProvider.GetRequiredService<IHostApplicationLifetime>();
|
|
||||||
using var onWebfrontErrored = new ManualResetEventSlim();
|
|
||||||
|
|
||||||
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
|
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
|
||||||
? WebfrontCore.Program.GetWebHostTask(_serverManager.CancellationToken).ContinueWith(continuation =>
|
? WebfrontCore.Program.GetWebHostTask(_serverManager.CancellationToken)
|
||||||
{
|
|
||||||
if (!continuation.IsFaulted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogCritical("Unable to start webfront task. {Message}",
|
|
||||||
continuation.Exception?.InnerException?.Message);
|
|
||||||
|
|
||||||
logger.LogDebug(continuation.Exception, "Unable to start webfront task");
|
|
||||||
|
|
||||||
onWebfrontErrored.Set();
|
|
||||||
|
|
||||||
})
|
|
||||||
: Task.CompletedTask;
|
: Task.CompletedTask;
|
||||||
|
|
||||||
if (_serverManager.GetApplicationSettings().Configuration().EnableWebFront)
|
var collectionService = serviceProvider.GetRequiredService<IServerDataCollector>();
|
||||||
{
|
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
|
||||||
try
|
|
||||||
{
|
|
||||||
onWebfrontErrored.Wait(webfrontLifetime.ApplicationStarted);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored when webfront successfully starts
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onWebfrontErrored.IsSet)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we want to run this one on a manual thread instead of letting the thread pool handle it,
|
// we want to run this one on a manual thread instead of letting the thread pool handle it,
|
||||||
// because we can't exit early from waiting on console input, and it prevents us from restarting
|
// because we can't exit early from waiting on console input, and it prevents us from restarting
|
||||||
@ -259,10 +203,10 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
var tasks = new[]
|
var tasks = new[]
|
||||||
{
|
{
|
||||||
applicationManager.Start(),
|
|
||||||
versionChecker.CheckVersion(),
|
versionChecker.CheckVersion(),
|
||||||
webfrontTask,
|
webfrontTask,
|
||||||
masterCommunicator.RunUploadStatus(_serverManager.CancellationToken),
|
serviceProvider.GetRequiredService<IMasterCommunication>()
|
||||||
|
.RunUploadStatus(_serverManager.CancellationToken),
|
||||||
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
|
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -432,12 +376,7 @@ namespace IW4MAdmin.Application
|
|||||||
appConfigHandler.BuildAsync().GetAwaiter().GetResult();
|
appConfigHandler.BuildAsync().GetAwaiter().GetResult();
|
||||||
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
|
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
|
||||||
commandConfigHandler.BuildAsync().GetAwaiter().GetResult();
|
commandConfigHandler.BuildAsync().GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (appConfigHandler.Configuration()?.MasterUrl == new Uri("http://api.raidmax.org:5000"))
|
|
||||||
{
|
|
||||||
appConfigHandler.Configuration().MasterUrl = new ApplicationConfiguration().MasterUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
var appConfig = appConfigHandler.Configuration();
|
var appConfig = appConfigHandler.Configuration();
|
||||||
var masterUri = Utilities.IsDevelopment
|
var masterUri = Utilities.IsDevelopment
|
||||||
? new Uri("http://127.0.0.1:8080")
|
? new Uri("http://127.0.0.1:8080")
|
||||||
@ -449,13 +388,6 @@ namespace IW4MAdmin.Application
|
|||||||
};
|
};
|
||||||
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
|
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
|
||||||
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
|
||||||
|
|
||||||
if (appConfig == null)
|
|
||||||
{
|
|
||||||
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
|
|
||||||
appConfigHandler.Set(appConfig);
|
|
||||||
appConfigHandler.Save().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
// register override level names
|
// register override level names
|
||||||
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
|
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
|
||||||
|
@ -13,10 +13,10 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
public class RemoteAssemblyHandler : IRemoteAssemblyHandler
|
public class RemoteAssemblyHandler : IRemoteAssemblyHandler
|
||||||
{
|
{
|
||||||
private const int KeyLength = 32;
|
private const int keyLength = 32;
|
||||||
private const int TagLength = 16;
|
private const int tagLength = 16;
|
||||||
private const int NonceLength = 12;
|
private const int nonceLength = 12;
|
||||||
private const int IterationCount = 10000;
|
private const int iterationCount = 10000;
|
||||||
|
|
||||||
private readonly ApplicationConfiguration _appconfig;
|
private readonly ApplicationConfiguration _appconfig;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@ -30,7 +30,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies)
|
public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies)
|
||||||
{
|
{
|
||||||
return DecryptContent(encryptedAssemblies)
|
return DecryptContent(encryptedAssemblies)
|
||||||
.Select(Assembly.Load);
|
.Select(decryptedAssembly => Assembly.Load(decryptedAssembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> DecryptScripts(string[] encryptedScripts)
|
public IEnumerable<string> DecryptScripts(string[] encryptedScripts)
|
||||||
@ -38,24 +38,24 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript));
|
return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<byte[]> DecryptContent(string[] content)
|
private byte[][] DecryptContent(string[] content)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId))
|
if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId))
|
||||||
{
|
{
|
||||||
_logger.LogWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts");
|
_logger.LogWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts");
|
||||||
return Array.Empty<byte[]>();
|
return new byte[0][];
|
||||||
}
|
}
|
||||||
|
|
||||||
var assemblies = content.Select(piece =>
|
var assemblies = content.Select(piece =>
|
||||||
{
|
{
|
||||||
var byteContent = Convert.FromBase64String(piece);
|
byte[] byteContent = Convert.FromBase64String(piece);
|
||||||
var encryptedContent = byteContent.Take(byteContent.Length - (TagLength + NonceLength)).ToArray();
|
byte[] encryptedContent = byteContent.Take(byteContent.Length - (tagLength + nonceLength)).ToArray();
|
||||||
var tag = byteContent.Skip(byteContent.Length - (TagLength + NonceLength)).Take(TagLength).ToArray();
|
byte[] tag = byteContent.Skip(byteContent.Length - (tagLength + nonceLength)).Take(tagLength).ToArray();
|
||||||
var nonce = byteContent.Skip(byteContent.Length - NonceLength).Take(NonceLength).ToArray();
|
byte[] nonce = byteContent.Skip(byteContent.Length - nonceLength).Take(nonceLength).ToArray();
|
||||||
var decryptedContent = new byte[encryptedContent.Length];
|
byte[] decryptedContent = new byte[encryptedContent.Length];
|
||||||
|
|
||||||
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id), IterationCount, HashAlgorithmName.SHA512);
|
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id.ToString()), iterationCount, HashAlgorithmName.SHA512);
|
||||||
var encryption = new AesGcm(keyGen.GetBytes(KeyLength));
|
var encryption = new AesGcm(keyGen.GetBytes(keyLength));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,6 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Models;
|
|
||||||
using Data.Models.Client;
|
using Data.Models.Client;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using Data.Models.Server;
|
using Data.Models.Server;
|
||||||
@ -41,31 +40,21 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(int?, DateTime?)>
|
public async Task<(int?, DateTime?)>
|
||||||
MaxConcurrentClientsAsync(long? serverId = null, Reference.Game? gameCode = null, TimeSpan? overPeriod = null,
|
MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
_snapshotCache.SetCacheItem(async (snapshots, ids, cancellationToken) =>
|
_snapshotCache.SetCacheItem(async (snapshots, cancellationToken) =>
|
||||||
{
|
{
|
||||||
Reference.Game? game = null;
|
|
||||||
long? id = null;
|
|
||||||
|
|
||||||
if (ids.Any())
|
|
||||||
{
|
|
||||||
game = (Reference.Game?)ids.First();
|
|
||||||
id = (long?)ids.Last();
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldestEntry = overPeriod.HasValue
|
var oldestEntry = overPeriod.HasValue
|
||||||
? DateTime.UtcNow - overPeriod.Value
|
? DateTime.UtcNow - overPeriod.Value
|
||||||
: DateTime.UtcNow.AddDays(-1);
|
: DateTime.UtcNow.AddDays(-1);
|
||||||
|
|
||||||
int? maxClients;
|
int? maxClients;
|
||||||
DateTime? maxClientsTime;
|
DateTime? maxClientsTime;
|
||||||
|
|
||||||
if (id != null)
|
if (serverId != null)
|
||||||
{
|
{
|
||||||
var clients = await snapshots.Where(snapshot => snapshot.ServerId == id)
|
var clients = await snapshots.Where(snapshot => snapshot.ServerId == serverId)
|
||||||
.Where(snapshot => game == null || snapshot.Server.GameName == game)
|
|
||||||
.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||||
.OrderByDescending(snapshot => snapshot.ClientCount)
|
.OrderByDescending(snapshot => snapshot.ClientCount)
|
||||||
.Select(snapshot => new
|
.Select(snapshot => new
|
||||||
@ -82,16 +71,15 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var clients = await snapshots.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
var clients = await snapshots.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||||
.Where(snapshot => game == null || snapshot.Server.GameName == game)
|
|
||||||
.GroupBy(snapshot => snapshot.PeriodBlock)
|
.GroupBy(snapshot => snapshot.PeriodBlock)
|
||||||
.Select(grp => new
|
.Select(grp => new
|
||||||
{
|
{
|
||||||
ClientCount = grp.Sum(snapshot => (int?)snapshot.ClientCount),
|
ClientCount = grp.Sum(snapshot => (int?) snapshot.ClientCount),
|
||||||
Time = grp.Max(snapshot => (DateTime?)snapshot.CapturedAt)
|
Time = grp.Max(snapshot => (DateTime?) snapshot.CapturedAt)
|
||||||
})
|
})
|
||||||
.OrderByDescending(snapshot => snapshot.ClientCount)
|
.OrderByDescending(snapshot => snapshot.ClientCount)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
maxClients = clients?.ClientCount;
|
maxClients = clients?.ClientCount;
|
||||||
maxClientsTime = clients?.Time;
|
maxClientsTime = clients?.Time;
|
||||||
}
|
}
|
||||||
@ -99,12 +87,11 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
|
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
|
||||||
|
|
||||||
return (maxClients, maxClientsTime);
|
return (maxClients, maxClientsTime);
|
||||||
}, nameof(MaxConcurrentClientsAsync), new object[] { gameCode, serverId }, _cacheTimeSpan, true);
|
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan, true);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync),
|
return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync), token);
|
||||||
new object[] { gameCode, serverId }, token);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -113,30 +100,22 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, Reference.Game? gameCode = null, CancellationToken token = default)
|
public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
_serverStatsCache.SetCacheItem(async (set, ids, cancellationToken) =>
|
_serverStatsCache.SetCacheItem(async (set, cancellationToken) =>
|
||||||
{
|
{
|
||||||
Reference.Game? game = null;
|
var count = await set.CountAsync(cancellationToken);
|
||||||
|
|
||||||
if (ids.Any())
|
|
||||||
{
|
|
||||||
game = (Reference.Game?)ids.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = await set.CountAsync(item => game == null || item.GameName == game,
|
|
||||||
cancellationToken);
|
|
||||||
var startOfPeriod =
|
var startOfPeriod =
|
||||||
DateTime.UtcNow.AddHours(-overPeriod?.TotalHours ?? -24);
|
DateTime.UtcNow.AddHours(-overPeriod?.TotalHours ?? -24);
|
||||||
var recentCount = await set.CountAsync(client => (game == null || client.GameName == game) && client.LastConnection >= startOfPeriod,
|
var recentCount = await set.CountAsync(client => client.LastConnection >= startOfPeriod,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
return (count, recentCount);
|
return (count, recentCount);
|
||||||
}, nameof(_serverStatsCache), new object[] { gameCode }, _cacheTimeSpan, true);
|
}, nameof(_serverStatsCache), _cacheTimeSpan, true);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _serverStatsCache.GetCacheItem(nameof(_serverStatsCache), new object[] { gameCode }, token);
|
return await _serverStatsCache.GetCacheItem(nameof(_serverStatsCache), token);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -187,28 +166,21 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
public async Task<int> RankedClientsCountAsync(long? serverId = null, CancellationToken token = default)
|
public async Task<int> RankedClientsCountAsync(long? serverId = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
_rankedClientsCache.SetCacheItem((set, ids, cancellationToken) =>
|
_rankedClientsCache.SetCacheItem(async (set, cancellationToken) =>
|
||||||
{
|
{
|
||||||
long? id = null;
|
|
||||||
|
|
||||||
if (ids.Any())
|
|
||||||
{
|
|
||||||
id = (long?)ids.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
|
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
|
||||||
return set
|
return await set
|
||||||
.Where(rating => rating.Newest)
|
.Where(rating => rating.Newest)
|
||||||
.Where(rating => rating.ServerId == id)
|
.Where(rating => rating.ServerId == serverId)
|
||||||
.Where(rating => rating.CreatedDateTime >= fifteenDaysAgo)
|
.Where(rating => rating.CreatedDateTime >= fifteenDaysAgo)
|
||||||
.Where(rating => rating.Client.Level != EFClient.Permission.Banned)
|
.Where(rating => rating.Client.Level != EFClient.Permission.Banned)
|
||||||
.Where(rating => rating.Ranking != null)
|
.Where(rating => rating.Ranking != null)
|
||||||
.CountAsync(cancellationToken);
|
.CountAsync(cancellationToken);
|
||||||
}, nameof(_rankedClientsCache), new object[] { serverId }, _cacheTimeSpan);
|
}, nameof(_rankedClientsCache), serverId is null ? null: new[] { (object)serverId }, _cacheTimeSpan);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), new object[] { serverId }, token);
|
return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), serverId, token);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -20,21 +20,13 @@ namespace IW4MAdmin.Application.Plugin
|
|||||||
public class PluginImporter : IPluginImporter
|
public class PluginImporter : IPluginImporter
|
||||||
{
|
{
|
||||||
private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
|
private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
|
||||||
private const string PluginDir = "Plugins";
|
private static readonly string PluginDir = "Plugins";
|
||||||
private const string PluginV2Match = "^ *((?:var|const|let) +init)|function init";
|
private const string PluginV2Match = "^ *((?:var|const|let) +init)|function init";
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
|
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
|
||||||
private readonly IMasterApi _masterApi;
|
private readonly IMasterApi _masterApi;
|
||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
private static readonly Type[] FilterTypes =
|
|
||||||
{
|
|
||||||
typeof(IPlugin),
|
|
||||||
typeof(IPluginV2),
|
|
||||||
typeof(Command),
|
|
||||||
typeof(IBaseConfiguration)
|
|
||||||
};
|
|
||||||
|
|
||||||
public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi,
|
public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi,
|
||||||
IRemoteAssemblyHandler remoteAssemblyHandler)
|
IRemoteAssemblyHandler remoteAssemblyHandler)
|
||||||
{
|
{
|
||||||
@ -85,80 +77,74 @@ namespace IW4MAdmin.Application.Plugin
|
|||||||
public (IEnumerable<Type>, IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations()
|
public (IEnumerable<Type>, IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations()
|
||||||
{
|
{
|
||||||
var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}";
|
var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}";
|
||||||
var pluginTypes = new List<Type>();
|
var pluginTypes = Enumerable.Empty<Type>();
|
||||||
var commandTypes = new List<Type>();
|
var commandTypes = Enumerable.Empty<Type>();
|
||||||
var configurationTypes = new List<Type>();
|
var configurationTypes = Enumerable.Empty<Type>();
|
||||||
|
|
||||||
if (!Directory.Exists(pluginDir))
|
if (Directory.Exists(pluginDir))
|
||||||
{
|
{
|
||||||
return (pluginTypes, commandTypes, configurationTypes);
|
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
|
||||||
}
|
_logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length);
|
||||||
|
|
||||||
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
|
if (dllFileNames.Length > 0)
|
||||||
_logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length);
|
|
||||||
|
|
||||||
if (!dllFileNames.Any())
|
|
||||||
{
|
|
||||||
return (pluginTypes, commandTypes, configurationTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we only want to load the most recent assembly in case of duplicates
|
|
||||||
var assemblies = dllFileNames.Select(Assembly.LoadFrom)
|
|
||||||
.Union(GetRemoteAssemblies())
|
|
||||||
.GroupBy(assembly => assembly.FullName).Select(assembly =>
|
|
||||||
assembly.OrderByDescending(asm => asm.GetName().Version).First());
|
|
||||||
|
|
||||||
var eligibleAssemblyTypes = assemblies
|
|
||||||
.SelectMany(asm =>
|
|
||||||
{
|
{
|
||||||
try
|
// we only want to load the most recent assembly in case of duplicates
|
||||||
{
|
var assemblies = dllFileNames.Select(name => Assembly.LoadFrom(name))
|
||||||
return asm.GetTypes();
|
.Union(GetRemoteAssemblies())
|
||||||
}
|
.GroupBy(assembly => assembly.FullName).Select(assembly => assembly.OrderByDescending(asm => asm.GetName().Version).First());
|
||||||
catch
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<Type>();
|
|
||||||
}
|
|
||||||
}).Where(type =>
|
|
||||||
FilterTypes.Any(filterType => type.GetInterface(filterType.Name, false) != null) ||
|
|
||||||
(type.IsClass && FilterTypes.Contains(type.BaseType)));
|
|
||||||
|
|
||||||
foreach (var assemblyType in eligibleAssemblyTypes)
|
|
||||||
{
|
|
||||||
var isPlugin =
|
|
||||||
(assemblyType.GetInterface(nameof(IPlugin), false) ??
|
|
||||||
assemblyType.GetInterface(nameof(IPluginV2), false)) != null &&
|
|
||||||
(!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
|
|
||||||
|
|
||||||
if (isPlugin)
|
pluginTypes = assemblies
|
||||||
{
|
.SelectMany(asm =>
|
||||||
pluginTypes.Add(assemblyType);
|
{
|
||||||
continue;
|
try
|
||||||
}
|
{
|
||||||
|
return asm.GetTypes();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Type>();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Where(assemblyType => (assemblyType.GetInterface(nameof(IPlugin), false) ?? assemblyType.GetInterface(nameof(IPluginV2), false)) != null)
|
||||||
|
.Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
|
||||||
|
|
||||||
var isCommand = assemblyType.IsClass && assemblyType.BaseType == typeof(Command) &&
|
_logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count());
|
||||||
(!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
|
|
||||||
|
|
||||||
if (isCommand)
|
commandTypes = assemblies
|
||||||
{
|
.SelectMany(asm =>{
|
||||||
commandTypes.Add(assemblyType);
|
try
|
||||||
continue;
|
{
|
||||||
}
|
return asm.GetTypes();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Type>();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Where(assemblyType => assemblyType.IsClass && assemblyType.BaseType == typeof(Command))
|
||||||
|
.Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
|
||||||
|
|
||||||
var isConfiguration = assemblyType.IsClass &&
|
_logger.LogDebug("Discovered {Count} plugin commands", commandTypes.Count());
|
||||||
assemblyType.GetInterface(nameof(IBaseConfiguration), false) != null &&
|
|
||||||
(!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
|
|
||||||
|
|
||||||
if (isConfiguration)
|
configurationTypes = assemblies
|
||||||
{
|
.SelectMany(asm => {
|
||||||
configurationTypes.Add(assemblyType);
|
try
|
||||||
|
{
|
||||||
|
return asm.GetTypes();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Type>();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Where(asmType =>
|
||||||
|
asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null)
|
||||||
|
.Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
|
||||||
|
|
||||||
|
_logger.LogDebug("Discovered {Count} configuration implementations", configurationTypes.Count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Discovered {Count} plugin implementations", pluginTypes.Count);
|
|
||||||
_logger.LogDebug("Discovered {Count} plugin command implementations", commandTypes.Count);
|
|
||||||
_logger.LogDebug("Discovered {Count} plugin configuration implementations", configurationTypes.Count);
|
|
||||||
|
|
||||||
return (pluginTypes, commandTypes, configurationTypes);
|
return (pluginTypes, commandTypes, configurationTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,11 +152,10 @@ namespace IW4MAdmin.Application.Plugin
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pluginSubscription ??= _masterApi
|
if (_pluginSubscription == null)
|
||||||
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||||
|
|
||||||
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription
|
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
|
||||||
.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -184,11 +169,9 @@ namespace IW4MAdmin.Application.Plugin
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_pluginSubscription ??= _masterApi
|
_pluginSubscription ??= _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||||
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
|
||||||
|
|
||||||
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription
|
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
|
||||||
.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@ -7,20 +6,15 @@ using System.Threading.Tasks;
|
|||||||
using IW4MAdmin.Application.Configuration;
|
using IW4MAdmin.Application.Configuration;
|
||||||
using Jint;
|
using Jint;
|
||||||
using Jint.Native;
|
using Jint.Native;
|
||||||
using Jint.Native.Json;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Plugin.Script;
|
namespace IW4MAdmin.Application.Plugin.Script;
|
||||||
|
|
||||||
public class ScriptPluginConfigurationWrapper
|
public class ScriptPluginConfigurationWrapper
|
||||||
{
|
{
|
||||||
public event Action<JsValue, Delegate> ConfigurationUpdated;
|
|
||||||
|
|
||||||
private readonly ScriptPluginConfiguration _config;
|
private readonly ScriptPluginConfiguration _config;
|
||||||
private readonly IConfigurationHandlerV2<ScriptPluginConfiguration> _configHandler;
|
private readonly IConfigurationHandlerV2<ScriptPluginConfiguration> _configHandler;
|
||||||
private readonly Engine _scriptEngine;
|
private readonly Engine _scriptEngine;
|
||||||
private readonly JsonParser _engineParser;
|
|
||||||
private readonly List<(string, Delegate)> _updateCallbackActions = new();
|
|
||||||
private string _pluginName;
|
private string _pluginName;
|
||||||
|
|
||||||
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine, IConfigurationHandlerV2<ScriptPluginConfiguration> configHandler)
|
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine, IConfigurationHandlerV2<ScriptPluginConfiguration> configHandler)
|
||||||
@ -28,16 +22,9 @@ public class ScriptPluginConfigurationWrapper
|
|||||||
_pluginName = pluginName;
|
_pluginName = pluginName;
|
||||||
_scriptEngine = scriptEngine;
|
_scriptEngine = scriptEngine;
|
||||||
_configHandler = configHandler;
|
_configHandler = configHandler;
|
||||||
_configHandler.Updated += OnConfigurationUpdated;
|
|
||||||
_config = configHandler.Get("ScriptPluginSettings", new ScriptPluginConfiguration()).GetAwaiter().GetResult();
|
_config = configHandler.Get("ScriptPluginSettings", new ScriptPluginConfiguration()).GetAwaiter().GetResult();
|
||||||
_engineParser = new JsonParser(_scriptEngine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~ScriptPluginConfigurationWrapper()
|
|
||||||
{
|
|
||||||
_configHandler.Updated -= OnConfigurationUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetName(string name)
|
public void SetName(string name)
|
||||||
{
|
{
|
||||||
_pluginName = name;
|
_pluginName = name;
|
||||||
@ -76,10 +63,8 @@ public class ScriptPluginConfigurationWrapper
|
|||||||
|
|
||||||
await _configHandler.Set(_config);
|
await _configHandler.Set(_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsValue GetValue(string key) => GetValue(key, null);
|
|
||||||
|
|
||||||
public JsValue GetValue(string key, Delegate updateCallback)
|
public JsValue GetValue(string key)
|
||||||
{
|
{
|
||||||
if (!_config.ContainsKey(_pluginName))
|
if (!_config.ContainsKey(_pluginName))
|
||||||
{
|
{
|
||||||
@ -98,20 +83,6 @@ public class ScriptPluginConfigurationWrapper
|
|||||||
item = jElem.Deserialize<List<dynamic>>();
|
item = jElem.Deserialize<List<dynamic>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateCallback is not null)
|
|
||||||
{
|
|
||||||
_updateCallbackActions.Add((key, updateCallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return _engineParser.Parse(item!.ToString()!);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsValue.FromObject(_scriptEngine, item);
|
return JsValue.FromObject(_scriptEngine, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,12 +90,4 @@ public class ScriptPluginConfigurationWrapper
|
|||||||
{
|
{
|
||||||
return int.TryParse(value.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : null;
|
return int.TryParse(value.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConfigurationUpdated(ScriptPluginConfiguration config)
|
|
||||||
{
|
|
||||||
foreach (var callback in _updateCallbackActions)
|
|
||||||
{
|
|
||||||
ConfigurationUpdated?.Invoke(GetValue(callback.Item1), callback.Item2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ public class ScriptPluginHelper
|
|||||||
{
|
{
|
||||||
private readonly IManager _manager;
|
private readonly IManager _manager;
|
||||||
private readonly ScriptPluginV2 _scriptPlugin;
|
private readonly ScriptPluginV2 _scriptPlugin;
|
||||||
private readonly SemaphoreSlim _onRequestRunning = new(1, 1);
|
private readonly SemaphoreSlim _onRequestRunning = new(1, 5);
|
||||||
private const int RequestTimeout = 5000;
|
private const int RequestTimeout = 500;
|
||||||
|
|
||||||
public ScriptPluginHelper(IManager manager, ScriptPluginV2 scriptPlugin)
|
public ScriptPluginHelper(IManager manager, ScriptPluginV2 scriptPlugin)
|
||||||
{
|
{
|
||||||
@ -28,17 +28,14 @@ public class ScriptPluginHelper
|
|||||||
RequestUrl(new ScriptPluginWebRequest(url), callback);
|
RequestUrl(new ScriptPluginWebRequest(url), callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GetUrl(string url, string bearerToken, Delegate callback)
|
public void GetUrl(string url, Dictionary<string, string> headers, Delegate callback)
|
||||||
{
|
{
|
||||||
var headers = new Dictionary<string, string> { { "Authorization", $"Bearer {bearerToken}" } };
|
|
||||||
RequestUrl(new ScriptPluginWebRequest(url, Headers: headers), callback);
|
RequestUrl(new ScriptPluginWebRequest(url, Headers: headers), callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PostUrl(string url, string body, string bearerToken, Delegate callback)
|
public void PostUrl(string url, Dictionary<string, string> headers, Delegate callback)
|
||||||
{
|
{
|
||||||
var headers = new Dictionary<string, string> { { "Authorization", $"Bearer {bearerToken}" } };
|
RequestUrl(new ScriptPluginWebRequest(url, null, "POST", Headers: headers), callback);
|
||||||
RequestUrl(
|
|
||||||
new ScriptPluginWebRequest(url, body, "POST", Headers: headers), callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestUrl(ScriptPluginWebRequest request, Delegate callback)
|
public void RequestUrl(ScriptPluginWebRequest request, Delegate callback)
|
||||||
@ -67,7 +64,7 @@ public class ScriptPluginHelper
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(delayMs, _manager.CancellationToken);
|
await Task.Delay(delayMs, _manager.CancellationToken);
|
||||||
_scriptPlugin.ExecuteWithErrorHandling(_ => callback.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined }));
|
_scriptPlugin.ExecuteWithErrorHandling(_ => callback.DynamicInvoke(JsValue.Undefined));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -76,15 +73,11 @@ public class ScriptPluginHelper
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterDynamicCommand(JsValue command)
|
|
||||||
{
|
|
||||||
_scriptPlugin.RegisterDynamicCommand(command.ToObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
private object RequestInternal(ScriptPluginWebRequest request)
|
private object RequestInternal(ScriptPluginWebRequest request)
|
||||||
{
|
{
|
||||||
var entered = false;
|
var entered = false;
|
||||||
using var tokenSource = new CancellationTokenSource(RequestTimeout);
|
using var tokenSource = new CancellationTokenSource(RequestTimeout);
|
||||||
|
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -47,7 +47,6 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
private readonly List<string> _registeredCommandNames = new();
|
private readonly List<string> _registeredCommandNames = new();
|
||||||
private readonly List<string> _registeredInteractions = new();
|
private readonly List<string> _registeredInteractions = new();
|
||||||
private readonly Dictionary<MethodInfo, List<object>> _registeredEvents = new();
|
private readonly Dictionary<MethodInfo, List<object>> _registeredEvents = new();
|
||||||
private IManager _manager;
|
|
||||||
private bool _firstInitialization = true;
|
private bool _firstInitialization = true;
|
||||||
|
|
||||||
private record ScriptPluginDetails(string Name, string Author, string Version,
|
private record ScriptPluginDetails(string Name, string Author, string Version,
|
||||||
@ -113,15 +112,8 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
}, _logger, _fileName, _onProcessingScript);
|
}, _logger, _fileName, _onProcessingScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterDynamicCommand(object command)
|
|
||||||
{
|
|
||||||
var parsedCommand = ParseScriptCommandDetails(command);
|
|
||||||
RegisterCommand(_manager, parsedCommand.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnLoad(IManager manager, CancellationToken token)
|
private async Task OnLoad(IManager manager, CancellationToken token)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
|
||||||
var entered = false;
|
var entered = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -261,12 +253,8 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
command.Permission, command.TargetRequired,
|
command.Permission, command.TargetRequired,
|
||||||
command.Arguments, Execute, command.SupportedGames);
|
command.Arguments, Execute, command.SupportedGames);
|
||||||
|
|
||||||
manager.RemoveCommandByName(scriptCommand.Name);
|
|
||||||
manager.AddAdditionalCommand(scriptCommand);
|
manager.AddAdditionalCommand(scriptCommand);
|
||||||
if (!_registeredCommandNames.Contains(scriptCommand.Name))
|
_registeredCommandNames.Add(scriptCommand.Name);
|
||||||
{
|
|
||||||
_registeredCommandNames.Add(scriptCommand.Name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetEngineState()
|
private void ResetEngineState()
|
||||||
@ -290,7 +278,7 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
typeof(ScriptPluginExtensions), typeof(LoggerExtensions))
|
typeof(ScriptPluginExtensions), typeof(LoggerExtensions))
|
||||||
.AllowClr(typeof(System.Net.Http.HttpClient).Assembly, typeof(EFClient).Assembly,
|
.AllowClr(typeof(System.Net.Http.HttpClient).Assembly, typeof(EFClient).Assembly,
|
||||||
typeof(Utilities).Assembly, typeof(Encoding).Assembly, typeof(CancellationTokenSource).Assembly,
|
typeof(Utilities).Assembly, typeof(Encoding).Assembly, typeof(CancellationTokenSource).Assembly,
|
||||||
typeof(Data.Models.Client.EFClient).Assembly, typeof(IW4MAdmin.Plugins.Stats.Plugin).Assembly, typeof(ScriptPluginWebRequest).Assembly)
|
typeof(Data.Models.Client.EFClient).Assembly, typeof(IW4MAdmin.Plugins.Stats.Plugin).Assembly)
|
||||||
.CatchClrExceptions()
|
.CatchClrExceptions()
|
||||||
.AddObjectConverter(new EnumsToStringConverter()));
|
.AddObjectConverter(new EnumsToStringConverter()));
|
||||||
|
|
||||||
@ -303,15 +291,6 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
_scriptPluginConfigurationWrapper =
|
_scriptPluginConfigurationWrapper =
|
||||||
new ScriptPluginConfigurationWrapper(_fileName.Split(Path.DirectorySeparatorChar).Last(), ScriptEngine,
|
new ScriptPluginConfigurationWrapper(_fileName.Split(Path.DirectorySeparatorChar).Last(), ScriptEngine,
|
||||||
_configHandler);
|
_configHandler);
|
||||||
|
|
||||||
_scriptPluginConfigurationWrapper.ConfigurationUpdated += (configValue, callbackAction) =>
|
|
||||||
{
|
|
||||||
WrapJavaScriptErrorHandling(() =>
|
|
||||||
{
|
|
||||||
callbackAction.DynamicInvoke(JsValue.Undefined, new[] { configValue });
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}, _logger, _fileName, _onProcessingScript);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnregisterScriptEntities(IManager manager)
|
private void UnregisterScriptEntities(IManager manager)
|
||||||
@ -492,33 +471,6 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ScriptPluginDetails AsScriptPluginInstance(dynamic source)
|
private static ScriptPluginDetails AsScriptPluginInstance(dynamic source)
|
||||||
{
|
|
||||||
var commandDetails = ParseScriptCommandDetails(source);
|
|
||||||
|
|
||||||
var interactionDetails = Array.Empty<ScriptPluginInteractionDetails>();
|
|
||||||
if (HasProperty(source, "interactions") && source.interactions is dynamic[])
|
|
||||||
{
|
|
||||||
interactionDetails = ((dynamic[])source.interactions).Select(interaction =>
|
|
||||||
{
|
|
||||||
var name = HasProperty(interaction, "name") && interaction.name is string
|
|
||||||
? (string)interaction.name
|
|
||||||
: string.Empty;
|
|
||||||
var action = HasProperty(interaction, "action") && interaction.action is Delegate
|
|
||||||
? (Delegate)interaction.action
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return new ScriptPluginInteractionDetails(name, action);
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = HasProperty(source, "name") && source.name is string ? (string)source.name : string.Empty;
|
|
||||||
var author = HasProperty(source, "author") && source.author is string ? (string)source.author : string.Empty;
|
|
||||||
var version = HasProperty(source, "version") && source.version is string ? (string)source.author : string.Empty;
|
|
||||||
|
|
||||||
return new ScriptPluginDetails(name, author, version, commandDetails, interactionDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ScriptPluginCommandDetails[] ParseScriptCommandDetails(dynamic source)
|
|
||||||
{
|
{
|
||||||
var commandDetails = Array.Empty<ScriptPluginCommandDetails>();
|
var commandDetails = Array.Empty<ScriptPluginCommandDetails>();
|
||||||
if (HasProperty(source, "commands") && source.commands is dynamic[])
|
if (HasProperty(source, "commands") && source.commands is dynamic[])
|
||||||
@ -552,7 +504,7 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
(bool)command.targetRequired;
|
(bool)command.targetRequired;
|
||||||
var supportedGames =
|
var supportedGames =
|
||||||
HasProperty(command, "supportedGames") && command.supportedGames is IEnumerable<object>
|
HasProperty(command, "supportedGames") && command.supportedGames is IEnumerable<object>
|
||||||
? ((IEnumerable<object>)command.supportedGames).Where(game => !string.IsNullOrEmpty(game?.ToString()))
|
? ((IEnumerable<object>)command.supportedGames).Where(game => game?.ToString() is not null)
|
||||||
.Select(game =>
|
.Select(game =>
|
||||||
Enum.Parse<Reference.Game>(game.ToString()!))
|
Enum.Parse<Reference.Game>(game.ToString()!))
|
||||||
: Array.Empty<Reference.Game>();
|
: Array.Empty<Reference.Game>();
|
||||||
@ -562,10 +514,31 @@ public class ScriptPluginV2 : IPluginV2
|
|||||||
|
|
||||||
return new ScriptPluginCommandDetails(name, description, alias, permission, isTargetRequired,
|
return new ScriptPluginCommandDetails(name, description, alias, permission, isTargetRequired,
|
||||||
commandArgs, supportedGames, execute);
|
commandArgs, supportedGames, execute);
|
||||||
|
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return commandDetails;
|
var interactionDetails = Array.Empty<ScriptPluginInteractionDetails>();
|
||||||
|
if (HasProperty(source, "interactions") && source.interactions is dynamic[])
|
||||||
|
{
|
||||||
|
interactionDetails = ((dynamic[])source.interactions).Select(interaction =>
|
||||||
|
{
|
||||||
|
var name = HasProperty(interaction, "name") && interaction.name is string
|
||||||
|
? (string)interaction.name
|
||||||
|
: string.Empty;
|
||||||
|
var action = HasProperty(interaction, "action") && interaction.action is Delegate
|
||||||
|
? (Delegate)interaction.action
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return new ScriptPluginInteractionDetails(name, action);
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = HasProperty(source, "name") && source.name is string ? (string)source.name : string.Empty;
|
||||||
|
var author = HasProperty(source, "author") && source.author is string ? (string)source.author : string.Empty;
|
||||||
|
var version = HasProperty(source, "version") && source.version is string ? (string)source.author : string.Empty;
|
||||||
|
|
||||||
|
return new ScriptPluginDetails(name, author, version, commandDetails, interactionDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasProperty(dynamic source, string name)
|
private static bool HasProperty(dynamic source, string name)
|
||||||
|
@ -8,11 +8,9 @@ using Data.Abstractions;
|
|||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using WebfrontCore.Permissions;
|
|
||||||
using WebfrontCore.QueryHelpers.Models;
|
using WebfrontCore.QueryHelpers.Models;
|
||||||
using EFClient = Data.Models.Client.EFClient;
|
using EFClient = Data.Models.Client.EFClient;
|
||||||
|
|
||||||
@ -20,7 +18,6 @@ namespace IW4MAdmin.Application.QueryHelpers;
|
|||||||
|
|
||||||
public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequest, ClientResourceResponse>
|
public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequest, ClientResourceResponse>
|
||||||
{
|
{
|
||||||
public ApplicationConfiguration _appConfig { get; }
|
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly IGeoLocationService _geoLocationService;
|
private readonly IGeoLocationService _geoLocationService;
|
||||||
|
|
||||||
@ -30,10 +27,8 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
|
|||||||
public EFAlias Alias { get; set; }
|
public EFAlias Alias { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientResourceQueryHelper(IDatabaseContextFactory contextFactory, IGeoLocationService geoLocationService,
|
public ClientResourceQueryHelper(IDatabaseContextFactory contextFactory, IGeoLocationService geoLocationService)
|
||||||
ApplicationConfiguration appConfig)
|
|
||||||
{
|
{
|
||||||
_appConfig = appConfig;
|
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_geoLocationService = geoLocationService;
|
_geoLocationService = geoLocationService;
|
||||||
}
|
}
|
||||||
@ -80,9 +75,7 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.ClientIp))
|
if (!string.IsNullOrWhiteSpace(query.ClientIp))
|
||||||
{
|
{
|
||||||
clientAliases = SearchByIp(query, clientAliases,
|
clientAliases = SearchByIp(query, clientAliases);
|
||||||
_appConfig.HasPermission(query.RequesterPermission, WebfrontEntity.ClientIPAddress,
|
|
||||||
WebfrontPermission.Read));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var iqGroupedClientAliases = clientAliases.GroupBy(a => new { a.Client.ClientId, a.Client.LastConnection });
|
var iqGroupedClientAliases = clientAliases.GroupBy(a => new { a.Client.ClientId, a.Client.LastConnection });
|
||||||
@ -210,7 +203,7 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IQueryable<ClientAlias> SearchByIp(ClientResourceRequest query,
|
private static IQueryable<ClientAlias> SearchByIp(ClientResourceRequest query,
|
||||||
IQueryable<ClientAlias> clientAliases, bool canSearchIP)
|
IQueryable<ClientAlias> clientAliases)
|
||||||
{
|
{
|
||||||
var ipString = query.ClientIp.Trim();
|
var ipString = query.ClientIp.Trim();
|
||||||
var ipAddress = ipString.ConvertToIP();
|
var ipAddress = ipString.ConvertToIP();
|
||||||
@ -220,7 +213,7 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
|
|||||||
clientAliases = clientAliases.Where(clientAlias =>
|
clientAliases = clientAliases.Where(clientAlias =>
|
||||||
clientAlias.Alias.IPAddress != null && clientAlias.Alias.IPAddress == ipAddress);
|
clientAlias.Alias.IPAddress != null && clientAlias.Alias.IPAddress == ipAddress);
|
||||||
}
|
}
|
||||||
else if(canSearchIP)
|
else
|
||||||
{
|
{
|
||||||
clientAliases = clientAliases.Where(clientAlias =>
|
clientAliases = clientAliases.Where(clientAlias =>
|
||||||
EF.Functions.Like(clientAlias.Alias.SearchableIPAddress, $"{ipString}%"));
|
EF.Functions.Like(clientAlias.Alias.SearchableIPAddress, $"{ipString}%"));
|
||||||
|
@ -194,14 +194,10 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
foreach (var line in response)
|
foreach (var line in response)
|
||||||
{
|
{
|
||||||
var regex = Regex.Match(line, parserRegex.Pattern);
|
var regex = Regex.Match(line, parserRegex.Pattern);
|
||||||
|
if (regex.Success && parserRegex.GroupMapping.ContainsKey(groupType))
|
||||||
if (!regex.Success || !parserRegex.GroupMapping.ContainsKey(groupType))
|
|
||||||
{
|
{
|
||||||
continue;
|
value = regex.Groups[parserRegex.GroupMapping[groupType]].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
value = regex.Groups[parserRegex.GroupMapping[groupType]].ToString();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
@ -308,7 +304,7 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
{
|
{
|
||||||
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
networkIdString = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConNetworkId]];
|
||||||
|
|
||||||
networkId = networkIdString.IsBotGuid() || (ip == null && ping is 999 or 0) ?
|
networkId = networkIdString.IsBotGuid() || (ip == null && ping == 999) ?
|
||||||
name.GenerateGuidFromString() :
|
name.GenerateGuidFromString() :
|
||||||
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
networkIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,10 @@ namespace Data.Abstractions
|
|||||||
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
|
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
|
||||||
TimeSpan? expirationTime = null, bool autoRefresh = false);
|
TimeSpan? expirationTime = null, bool autoRefresh = false);
|
||||||
|
|
||||||
void SetCacheItem(Func<DbSet<TEntityType>, IEnumerable<object>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
|
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
|
||||||
IEnumerable<object> ids = null, TimeSpan? expirationTime = null, bool autoRefresh = false);
|
IEnumerable<object> ids = null, TimeSpan? expirationTime = null, bool autoRefresh = false);
|
||||||
|
|
||||||
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
|
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
|
||||||
|
Task<TReturnType> GetCacheItem(string keyName, object id = null, CancellationToken token = default);
|
||||||
Task<TReturnType> GetCacheItem(string keyName, IEnumerable<object> ids = null, CancellationToken token = default);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,8 +153,6 @@ namespace Data.Context
|
|||||||
|
|
||||||
modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime));
|
modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime));
|
||||||
|
|
||||||
modelBuilder.Entity<EFServerSnapshot>(ent => ent.HasIndex(snapshot => snapshot.CapturedAt));
|
|
||||||
|
|
||||||
// force full name for database conversion
|
// force full name for database conversion
|
||||||
modelBuilder.Entity<EFClient>().ToTable("EFClients");
|
modelBuilder.Entity<EFClient>().ToTable("EFClients");
|
||||||
modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
|
modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
|
||||||
|
@ -18,7 +18,7 @@ namespace Data.Helpers
|
|||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Dictionary<object, CacheState<TReturnType>>> _cacheStates = new();
|
private readonly ConcurrentDictionary<string, Dictionary<object, CacheState<TReturnType>>> _cacheStates = new();
|
||||||
private readonly string _defaultKey = null;
|
private readonly object _defaultKey = new();
|
||||||
|
|
||||||
private bool _autoRefresh;
|
private bool _autoRefresh;
|
||||||
private const int DefaultExpireMinutes = 15;
|
private const int DefaultExpireMinutes = 15;
|
||||||
@ -29,7 +29,7 @@ namespace Data.Helpers
|
|||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public DateTime LastRetrieval { get; set; }
|
public DateTime LastRetrieval { get; set; }
|
||||||
public TimeSpan ExpirationTime { get; set; }
|
public TimeSpan ExpirationTime { get; set; }
|
||||||
public Func<DbSet<TEntityType>, IEnumerable<object>, CancellationToken, Task<TCacheType>> Getter { get; set; }
|
public Func<DbSet<TEntityType>, CancellationToken, Task<TCacheType>> Getter { get; set; }
|
||||||
public TCacheType Value { get; set; }
|
public TCacheType Value { get; set; }
|
||||||
public bool IsSet { get; set; }
|
public bool IsSet { get; set; }
|
||||||
|
|
||||||
@ -53,58 +53,60 @@ namespace Data.Helpers
|
|||||||
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
|
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
|
||||||
TimeSpan? expirationTime = null, bool autoRefresh = false)
|
TimeSpan? expirationTime = null, bool autoRefresh = false)
|
||||||
{
|
{
|
||||||
SetCacheItem((set, _, token) => getter(set, token), key, null, expirationTime, autoRefresh);
|
SetCacheItem(getter, key, null, expirationTime, autoRefresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCacheItem(Func<DbSet<TEntityType>, IEnumerable<object>, CancellationToken, Task<TReturnType>> getter, string key,
|
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
|
||||||
IEnumerable<object> ids = null, TimeSpan? expirationTime = null, bool autoRefresh = false)
|
IEnumerable<object> ids = null, TimeSpan? expirationTime = null, bool autoRefresh = false)
|
||||||
{
|
{
|
||||||
ids ??= new[] { _defaultKey };
|
ids ??= new[] { _defaultKey };
|
||||||
|
|
||||||
if (!_cacheStates.ContainsKey(key))
|
if (!_cacheStates.ContainsKey(key))
|
||||||
{
|
{
|
||||||
_cacheStates.TryAdd(key, new Dictionary<object, CacheState<TReturnType>>());
|
_cacheStates.TryAdd(key, new Dictionary<object, CacheState<TReturnType>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheInstance = _cacheStates[key];
|
foreach (var id in ids)
|
||||||
var id = GenerateKeyFromIds(ids);
|
|
||||||
|
|
||||||
lock (_cacheStates)
|
|
||||||
{
|
{
|
||||||
if (cacheInstance.ContainsKey(id))
|
var cacheInstance = _cacheStates[key];
|
||||||
|
|
||||||
|
lock (_cacheStates)
|
||||||
|
{
|
||||||
|
if (cacheInstance.ContainsKey(id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = new CacheState<TReturnType>
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Getter = getter,
|
||||||
|
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (_cacheStates)
|
||||||
|
{
|
||||||
|
cacheInstance.Add(id, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
_autoRefresh = autoRefresh;
|
||||||
|
|
||||||
|
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_timer = new Timer(state.ExpirationTime.TotalMilliseconds);
|
||||||
|
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None);
|
||||||
|
_timer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = new CacheState<TReturnType>
|
|
||||||
{
|
|
||||||
Key = key,
|
|
||||||
Getter = getter,
|
|
||||||
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
|
|
||||||
};
|
|
||||||
|
|
||||||
lock (_cacheStates)
|
|
||||||
{
|
|
||||||
cacheInstance.Add(id, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
_autoRefresh = autoRefresh;
|
|
||||||
|
|
||||||
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = new Timer(state.ExpirationTime.TotalMilliseconds);
|
|
||||||
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, ids, CancellationToken.None);
|
|
||||||
_timer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default) =>
|
public Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default) =>
|
||||||
GetCacheItem(keyName, null, cancellationToken);
|
GetCacheItem(keyName, null, cancellationToken);
|
||||||
|
|
||||||
public async Task<TReturnType> GetCacheItem(string keyName, IEnumerable<object> ids = null,
|
public async Task<TReturnType> GetCacheItem(string keyName, object id = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!_cacheStates.ContainsKey(keyName))
|
if (!_cacheStates.ContainsKey(keyName))
|
||||||
@ -118,27 +120,27 @@ namespace Data.Helpers
|
|||||||
|
|
||||||
lock (_cacheStates)
|
lock (_cacheStates)
|
||||||
{
|
{
|
||||||
state = ids is null ? cacheInstance.Values.First() : _cacheStates[keyName][GenerateKeyFromIds(ids)];
|
state = id is null ? cacheInstance.Values.First() : _cacheStates[keyName][id];
|
||||||
}
|
}
|
||||||
|
|
||||||
// when auto refresh is off we want to check the expiration and value
|
// when auto refresh is off we want to check the expiration and value
|
||||||
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically
|
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically
|
||||||
if ((state.IsExpired || !state.IsSet) && !_autoRefresh || _autoRefresh && !state.IsSet)
|
if ((state.IsExpired || !state.IsSet) && !_autoRefresh || _autoRefresh && !state.IsSet)
|
||||||
{
|
{
|
||||||
await RunCacheUpdate(state, ids, cancellationToken);
|
await RunCacheUpdate(state, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.Value;
|
return state.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunCacheUpdate(CacheState<TReturnType> state, IEnumerable<object> ids, CancellationToken token)
|
private async Task RunCacheUpdate(CacheState<TReturnType> state, CancellationToken token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state);
|
_logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state);
|
||||||
await using var context = _contextFactory.CreateContext(false);
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
var set = context.Set<TEntityType>();
|
var set = context.Set<TEntityType>();
|
||||||
var value = await state.Getter(set, ids, token);
|
var value = await state.Getter(set, token);
|
||||||
state.Value = value;
|
state.Value = value;
|
||||||
state.IsSet = true;
|
state.IsSet = true;
|
||||||
state.LastRetrieval = DateTime.Now;
|
state.LastRetrieval = DateTime.Now;
|
||||||
@ -148,8 +150,5 @@ namespace Data.Helpers
|
|||||||
_logger.LogError(ex, "Could not get cached value for {Key}", state.Key);
|
_logger.LogError(ex, "Could not get cached value for {Key}", state.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateKeyFromIds(IEnumerable<object> ids) =>
|
|
||||||
string.Join("_", ids.Select(id => id?.ToString() ?? "null"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Data.Migrations.MySql
|
|
||||||
{
|
|
||||||
public partial class AddIndexToEFServerSnapshotCapturedAt : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_EFServerSnapshot_CapturedAt",
|
|
||||||
table: "EFServerSnapshot",
|
|
||||||
column: "CapturedAt");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_EFServerSnapshot_CapturedAt",
|
|
||||||
table: "EFServerSnapshot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -814,7 +814,6 @@ namespace Data.Migrations.MySql
|
|||||||
|
|
||||||
b.Property<string>("SearchableIPAddress")
|
b.Property<string>("SearchableIPAddress")
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
.HasMaxLength(255)
|
|
||||||
.HasColumnType("varchar(255)")
|
.HasColumnType("varchar(255)")
|
||||||
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
||||||
|
|
||||||
@ -1111,8 +1110,6 @@ namespace Data.Migrations.MySql
|
|||||||
|
|
||||||
b.HasKey("ServerSnapshotId");
|
b.HasKey("ServerSnapshotId");
|
||||||
|
|
||||||
b.HasIndex("CapturedAt");
|
|
||||||
|
|
||||||
b.HasIndex("MapId");
|
b.HasIndex("MapId");
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,52 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Data.Migrations.Postgresql
|
|
||||||
{
|
|
||||||
public partial class AddIndexToEFServerSnapshotCapturedAt : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "SearchableIPAddress",
|
|
||||||
table: "EFAlias",
|
|
||||||
type: "character varying(255)",
|
|
||||||
maxLength: 255,
|
|
||||||
nullable: true,
|
|
||||||
computedColumnSql: "((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)",
|
|
||||||
stored: true,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "text",
|
|
||||||
oldNullable: true,
|
|
||||||
oldComputedColumnSql: "((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)",
|
|
||||||
oldStored: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_EFServerSnapshot_CapturedAt",
|
|
||||||
table: "EFServerSnapshot",
|
|
||||||
column: "CapturedAt");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_EFServerSnapshot_CapturedAt",
|
|
||||||
table: "EFServerSnapshot");
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<string>(
|
|
||||||
name: "SearchableIPAddress",
|
|
||||||
table: "EFAlias",
|
|
||||||
type: "text",
|
|
||||||
nullable: true,
|
|
||||||
computedColumnSql: "((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)",
|
|
||||||
stored: true,
|
|
||||||
oldClrType: typeof(string),
|
|
||||||
oldType: "character varying(255)",
|
|
||||||
oldMaxLength: 255,
|
|
||||||
oldNullable: true,
|
|
||||||
oldComputedColumnSql: "((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)",
|
|
||||||
oldStored: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -853,8 +853,7 @@ namespace Data.Migrations.Postgresql
|
|||||||
|
|
||||||
b.Property<string>("SearchableIPAddress")
|
b.Property<string>("SearchableIPAddress")
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
.HasMaxLength(255)
|
.HasColumnType("text")
|
||||||
.HasColumnType("character varying(255)")
|
|
||||||
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
||||||
|
|
||||||
b.Property<string>("SearchableName")
|
b.Property<string>("SearchableName")
|
||||||
@ -1164,8 +1163,6 @@ namespace Data.Migrations.Postgresql
|
|||||||
|
|
||||||
b.HasKey("ServerSnapshotId");
|
b.HasKey("ServerSnapshotId");
|
||||||
|
|
||||||
b.HasIndex("CapturedAt");
|
|
||||||
|
|
||||||
b.HasIndex("MapId");
|
b.HasIndex("MapId");
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Data.Migrations.Sqlite
|
|
||||||
{
|
|
||||||
public partial class AddIndexToEFServerSnapshotCapturedAt : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_EFServerSnapshot_CapturedAt",
|
|
||||||
table: "EFServerSnapshot",
|
|
||||||
column: "CapturedAt");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_EFServerSnapshot_CapturedAt",
|
|
||||||
table: "EFServerSnapshot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -812,7 +812,6 @@ namespace Data.Migrations.Sqlite
|
|||||||
|
|
||||||
b.Property<string>("SearchableIPAddress")
|
b.Property<string>("SearchableIPAddress")
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
.HasMaxLength(255)
|
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
||||||
|
|
||||||
@ -1109,8 +1108,6 @@ namespace Data.Migrations.Sqlite
|
|||||||
|
|
||||||
b.HasKey("ServerSnapshotId");
|
b.HasKey("ServerSnapshotId");
|
||||||
|
|
||||||
b.HasIndex("CapturedAt");
|
|
||||||
|
|
||||||
b.HasIndex("MapId");
|
b.HasIndex("MapId");
|
||||||
|
|
||||||
b.HasIndex("ServerId");
|
b.HasIndex("ServerId");
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
T7 = 8,
|
T7 = 8,
|
||||||
SHG1 = 9,
|
SHG1 = 9,
|
||||||
CSGO = 10,
|
CSGO = 10,
|
||||||
H1 = 11,
|
H1 = 11
|
||||||
L4D2 = 12,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ConnectionType
|
public enum ConnectionType
|
||||||
|
@ -6,7 +6,6 @@ trigger:
|
|||||||
include:
|
include:
|
||||||
- release/pre
|
- release/pre
|
||||||
- master
|
- master
|
||||||
- develop
|
|
||||||
|
|
||||||
pr: none
|
pr: none
|
||||||
|
|
||||||
@ -21,233 +20,227 @@ variables:
|
|||||||
buildConfiguration: Stable
|
buildConfiguration: Stable
|
||||||
isPreRelease: false
|
isPreRelease: false
|
||||||
|
|
||||||
jobs:
|
steps:
|
||||||
- job: Build_Deploy
|
- task: UseDotNet@2
|
||||||
steps:
|
displayName: 'Install .NET Core 6 SDK'
|
||||||
- task: UseDotNet@2
|
inputs:
|
||||||
displayName: 'Install .NET Core 6 SDK'
|
packageType: 'sdk'
|
||||||
inputs:
|
version: '6.0.x'
|
||||||
packageType: 'sdk'
|
includePreviewVersions: true
|
||||||
version: '6.0.x'
|
|
||||||
includePreviewVersions: true
|
- task: NuGetToolInstaller@1
|
||||||
|
|
||||||
- task: NuGetToolInstaller@1
|
- task: PowerShell@2
|
||||||
|
displayName: 'Setup Pre-Release configuration'
|
||||||
- task: PowerShell@2
|
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release/pre')
|
||||||
displayName: 'Setup Pre-Release configuration'
|
inputs:
|
||||||
condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/release/pre'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
targetType: 'inline'
|
||||||
inputs:
|
script: |
|
||||||
targetType: 'inline'
|
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||||
script: |
|
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||||
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||||
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
failOnStderr: true
|
||||||
echo '##vso[task.setvariable variable=isPreRelease]true'
|
|
||||||
failOnStderr: true
|
- task: NuGetCommand@2
|
||||||
|
displayName: 'Restore nuget packages'
|
||||||
|
inputs:
|
||||||
|
restoreSolution: '$(solution)'
|
||||||
|
|
||||||
|
- task: PowerShell@2
|
||||||
|
displayName: 'Preload external resources'
|
||||||
|
inputs:
|
||||||
|
targetType: 'inline'
|
||||||
|
script: |
|
||||||
|
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||||
|
md -Force lib\open-iconic\font\css
|
||||||
|
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
|
||||||
|
cd lib\open-iconic\font\css
|
||||||
|
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
||||||
|
failOnStderr: true
|
||||||
|
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||||
|
|
||||||
|
- task: VSBuild@1
|
||||||
|
displayName: 'Build projects'
|
||||||
|
inputs:
|
||||||
|
solution: '$(solution)'
|
||||||
|
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber)'
|
||||||
|
platform: '$(buildPlatform)'
|
||||||
|
configuration: '$(buildConfiguration)'
|
||||||
|
|
||||||
- task: NuGetCommand@2
|
- task: PowerShell@2
|
||||||
displayName: 'Restore nuget packages'
|
displayName: 'Bundle JS Files'
|
||||||
inputs:
|
inputs:
|
||||||
restoreSolution: '$(solution)'
|
targetType: 'inline'
|
||||||
|
script: |
|
||||||
- task: PowerShell@2
|
Write-Host 'Getting dotnet bundle'
|
||||||
displayName: 'Preload external resources'
|
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip
|
||||||
inputs:
|
Write-Host 'Unzipping download'
|
||||||
targetType: 'inline'
|
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
||||||
script: |
|
Write-Host 'Executing dotnet-bundle'
|
||||||
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||||
md -Force lib\open-iconic\font\css
|
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
|
failOnStderr: true
|
||||||
cd lib\open-iconic\font\css
|
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
||||||
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
|
||||||
failOnStderr: true
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
|
||||||
|
|
||||||
- task: VSBuild@1
|
|
||||||
displayName: 'Build projects'
|
|
||||||
inputs:
|
|
||||||
solution: '$(solution)'
|
|
||||||
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber)'
|
|
||||||
platform: '$(buildPlatform)'
|
|
||||||
configuration: '$(buildConfiguration)'
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
displayName: 'Bundle JS Files'
|
|
||||||
inputs:
|
|
||||||
targetType: 'inline'
|
|
||||||
script: |
|
|
||||||
Write-Host 'Getting dotnet bundle'
|
|
||||||
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip
|
|
||||||
Write-Host 'Unzipping download'
|
|
||||||
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
|
||||||
Write-Host 'Executing dotnet-bundle'
|
|
||||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
|
||||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
|
||||||
failOnStderr: true
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
|
||||||
displayName: 'Publish projects'
|
|
||||||
inputs:
|
|
||||||
command: 'publish'
|
|
||||||
publishWebProjects: false
|
|
||||||
projects: |
|
|
||||||
**/WebfrontCore.csproj
|
|
||||||
**/Application.csproj
|
|
||||||
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
|
||||||
zipAfterPublish: false
|
|
||||||
modifyOutputPath: false
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
displayName: 'Run publish script 1'
|
|
||||||
inputs:
|
|
||||||
filePath: 'DeploymentFiles/PostPublish.ps1'
|
|
||||||
arguments: '$(outputFolder)'
|
|
||||||
failOnStderr: true
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
|
||||||
|
|
||||||
- task: BatchScript@1
|
|
||||||
displayName: 'Run publish script 2'
|
|
||||||
inputs:
|
|
||||||
filename: 'Application\BuildScripts\PostPublish.bat'
|
|
||||||
workingFolder: '$(Build.Repository.LocalPath)'
|
|
||||||
arguments: '$(outputFolder) $(Build.Repository.LocalPath)'
|
|
||||||
failOnStandardError: true
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
displayName: 'Download dos2unix for line endings'
|
|
||||||
inputs:
|
|
||||||
targetType: 'inline'
|
|
||||||
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
|
||||||
failOnStderr: true
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: 'Convert Linux start script line endings'
|
|
||||||
inputs:
|
|
||||||
script: |
|
|
||||||
echo changing to encoding for linux start script
|
|
||||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
|
||||||
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
|
|
||||||
echo creating website version filename
|
|
||||||
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Move script plugins into publish directory'
|
|
||||||
inputs:
|
|
||||||
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
|
||||||
Contents: '*.js'
|
|
||||||
TargetFolder: '$(outputFolder)\Plugins'
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Move binary plugins into publish directory'
|
|
||||||
inputs:
|
|
||||||
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
|
||||||
Contents: '*.dll'
|
|
||||||
TargetFolder: '$(outputFolder)\Plugins'
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: 'Move webfront resources into publish directory'
|
|
||||||
inputs:
|
|
||||||
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
|
||||||
failOnStderr: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: 'Move gamescript files into publish directory'
|
|
||||||
inputs:
|
|
||||||
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
|
||||||
failOnStderr: true
|
|
||||||
|
|
||||||
- task: ArchiveFiles@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Generate final zip file'
|
displayName: 'Publish projects'
|
||||||
inputs:
|
inputs:
|
||||||
rootFolderOrFile: '$(outputFolder)'
|
command: 'publish'
|
||||||
includeRootFolder: false
|
publishWebProjects: false
|
||||||
archiveType: 'zip'
|
projects: |
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
**/WebfrontCore.csproj
|
||||||
replaceExistingArchive: true
|
**/Application.csproj
|
||||||
|
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
||||||
|
zipAfterPublish: false
|
||||||
|
modifyOutputPath: false
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@1
|
- task: PowerShell@2
|
||||||
inputs:
|
displayName: 'Run publish script 1'
|
||||||
targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
inputs:
|
||||||
artifact: 'IW4MAdmin-$(Build.BuildNumber).zip'
|
filePath: 'DeploymentFiles/PostPublish.ps1'
|
||||||
|
arguments: '$(outputFolder)'
|
||||||
- task: PublishPipelineArtifact@1
|
failOnStderr: true
|
||||||
displayName: 'Publish artifact for analysis'
|
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||||
inputs:
|
|
||||||
targetPath: '$(outputFolder)'
|
|
||||||
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
|
||||||
publishLocation: 'pipeline'
|
|
||||||
|
|
||||||
- task: FtpUpload@2
|
- task: BatchScript@1
|
||||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
displayName: 'Run publish script 2'
|
||||||
displayName: 'Upload zip file to website'
|
inputs:
|
||||||
inputs:
|
filename: 'Application\BuildScripts\PostPublish.bat'
|
||||||
credentialsOption: 'inputs'
|
workingFolder: '$(Build.Repository.LocalPath)'
|
||||||
serverUrl: '$(FTPUrl)'
|
arguments: '$(outputFolder) $(Build.Repository.LocalPath)'
|
||||||
username: '$(FTPUsername)'
|
failOnStandardError: true
|
||||||
password: '$(FTPPassword)'
|
|
||||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
- task: PowerShell@2
|
||||||
filePatterns: '*.zip'
|
displayName: 'Download dos2unix for line endings'
|
||||||
remoteDirectory: 'IW4MAdmin/Download'
|
inputs:
|
||||||
clean: false
|
targetType: 'inline'
|
||||||
cleanContents: false
|
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
||||||
preservePaths: false
|
failOnStderr: true
|
||||||
trustSSL: false
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
|
|
||||||
- task: FtpUpload@2
|
- task: CmdLine@2
|
||||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
displayName: 'Convert Linux start script line endings'
|
||||||
displayName: 'Upload version info to website'
|
inputs:
|
||||||
inputs:
|
script: |
|
||||||
credentialsOption: 'inputs'
|
echo changing to encoding for linux start script
|
||||||
serverUrl: '$(FTPUrl)'
|
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||||
username: '$(FTPUsername)'
|
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
|
||||||
password: '$(FTPPassword)'
|
echo creating website version filename
|
||||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||||
filePatterns: 'version_$(releaseType).txt'
|
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||||
remoteDirectory: 'IW4MAdmin'
|
|
||||||
clean: false
|
- task: CopyFiles@2
|
||||||
cleanContents: false
|
displayName: 'Move script plugins into publish directory'
|
||||||
preservePaths: false
|
inputs:
|
||||||
trustSSL: false
|
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
||||||
|
Contents: '*.js'
|
||||||
- task: GitHubRelease@1
|
TargetFolder: '$(outputFolder)\Plugins'
|
||||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
|
||||||
displayName: 'Make GitHub release'
|
- task: CopyFiles@2
|
||||||
inputs:
|
displayName: 'Move binary plugins into publish directory'
|
||||||
gitHubConnection: 'github.com_RaidMax'
|
inputs:
|
||||||
repositoryName: 'RaidMax/IW4M-Admin'
|
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
||||||
action: 'create'
|
Contents: '*.dll'
|
||||||
target: '$(Build.SourceVersion)'
|
TargetFolder: '$(outputFolder)\Plugins'
|
||||||
tagSource: 'userSpecifiedTag'
|
|
||||||
tag: '$(Build.BuildNumber)-$(releaseType)'
|
- task: CmdLine@2
|
||||||
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
displayName: 'Move webfront resources into publish directory'
|
||||||
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
inputs:
|
||||||
isPreRelease: $(isPreRelease)
|
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
||||||
releaseNotesSource: 'inline'
|
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
||||||
releaseNotesInline: 'Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating)'
|
failOnStderr: true
|
||||||
changeLogCompareToRelease: 'lastNonDraftRelease'
|
|
||||||
changeLogType: 'commitBased'
|
- task: CmdLine@2
|
||||||
|
displayName: 'Move gamescript files into publish directory'
|
||||||
- task: PowerShell@2
|
inputs:
|
||||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
||||||
displayName: 'Update master version'
|
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||||
inputs:
|
failOnStderr: true
|
||||||
targetType: 'inline'
|
|
||||||
script: |
|
- task: ArchiveFiles@2
|
||||||
$payload = @{
|
displayName: 'Generate final zip file'
|
||||||
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
inputs:
|
||||||
'jwt-secret' = '$(JWTSecret)'
|
rootFolderOrFile: '$(outputFolder)'
|
||||||
} | ConvertTo-Json
|
includeRootFolder: false
|
||||||
|
archiveType: 'zip'
|
||||||
|
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||||
$params = @{
|
replaceExistingArchive: true
|
||||||
Uri = 'http://api.raidmax.org:5000/version'
|
|
||||||
Method = 'POST'
|
- task: PublishPipelineArtifact@1
|
||||||
Body = $payload
|
inputs:
|
||||||
ContentType = 'application/json'
|
targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||||
}
|
artifact: 'IW4MAdmin-$(Build.BuildNumber).zip'
|
||||||
|
|
||||||
Invoke-RestMethod @params
|
- task: FtpUpload@2
|
||||||
|
displayName: 'Upload zip file to website'
|
||||||
|
inputs:
|
||||||
|
credentialsOption: 'inputs'
|
||||||
|
serverUrl: '$(FTPUrl)'
|
||||||
|
username: '$(FTPUsername)'
|
||||||
|
password: '$(FTPPassword)'
|
||||||
|
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||||
|
filePatterns: '*.zip'
|
||||||
|
remoteDirectory: 'IW4MAdmin/Download'
|
||||||
|
clean: false
|
||||||
|
cleanContents: false
|
||||||
|
preservePaths: false
|
||||||
|
trustSSL: false
|
||||||
|
|
||||||
|
- task: FtpUpload@2
|
||||||
|
displayName: 'Upload version info to website'
|
||||||
|
inputs:
|
||||||
|
credentialsOption: 'inputs'
|
||||||
|
serverUrl: '$(FTPUrl)'
|
||||||
|
username: '$(FTPUsername)'
|
||||||
|
password: '$(FTPPassword)'
|
||||||
|
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||||
|
filePatterns: 'version_$(releaseType).txt'
|
||||||
|
remoteDirectory: 'IW4MAdmin'
|
||||||
|
clean: false
|
||||||
|
cleanContents: false
|
||||||
|
preservePaths: false
|
||||||
|
trustSSL: false
|
||||||
|
|
||||||
|
- task: GitHubRelease@1
|
||||||
|
displayName: 'Make GitHub release'
|
||||||
|
inputs:
|
||||||
|
gitHubConnection: 'github.com_RaidMax'
|
||||||
|
repositoryName: 'RaidMax/IW4M-Admin'
|
||||||
|
action: 'create'
|
||||||
|
target: '$(Build.SourceVersion)'
|
||||||
|
tagSource: 'userSpecifiedTag'
|
||||||
|
tag: '$(Build.BuildNumber)-$(releaseType)'
|
||||||
|
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
||||||
|
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
||||||
|
isPreRelease: $(isPreRelease)
|
||||||
|
releaseNotesSource: 'inline'
|
||||||
|
releaseNotesInline: 'todo'
|
||||||
|
changeLogCompareToRelease: 'lastNonDraftRelease'
|
||||||
|
changeLogType: 'commitBased'
|
||||||
|
|
||||||
|
- task: PowerShell@2
|
||||||
|
displayName: 'Update master version'
|
||||||
|
inputs:
|
||||||
|
targetType: 'inline'
|
||||||
|
script: |
|
||||||
|
$payload = @{
|
||||||
|
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
||||||
|
'jwt-secret' = '$(JWTSecret)'
|
||||||
|
} | ConvertTo-Json
|
||||||
|
|
||||||
|
|
||||||
|
$params = @{
|
||||||
|
Uri = 'http://api.raidmax.org:5000/version'
|
||||||
|
Method = 'POST'
|
||||||
|
Body = $payload
|
||||||
|
ContentType = 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-RestMethod @params
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish artifact for analysis'
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(outputFolder)'
|
||||||
|
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
||||||
|
publishLocation: 'pipeline'
|
||||||
|
Binary file not shown.
@ -0,0 +1,263 @@
|
|||||||
|
#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]]();
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
#include common_scripts\utility;
|
#include common_scripts\utility;
|
||||||
|
#include maps\mp\_utility;
|
||||||
|
#include maps\mp\gametypes\_hud_util;
|
||||||
|
|
||||||
Init()
|
Init()
|
||||||
{
|
{
|
||||||
@ -8,7 +10,7 @@ Init()
|
|||||||
Setup()
|
Setup()
|
||||||
{
|
{
|
||||||
level endon( "game_ended" );
|
level endon( "game_ended" );
|
||||||
|
|
||||||
// setup default vars
|
// setup default vars
|
||||||
level.eventBus = spawnstruct();
|
level.eventBus = spawnstruct();
|
||||||
level.eventBus.inVar = "sv_iw4madmin_in";
|
level.eventBus.inVar = "sv_iw4madmin_in";
|
||||||
@ -16,54 +18,29 @@ Setup()
|
|||||||
level.eventBus.failKey = "fail";
|
level.eventBus.failKey = "fail";
|
||||||
level.eventBus.timeoutKey = "timeout";
|
level.eventBus.timeoutKey = "timeout";
|
||||||
level.eventBus.timeout = 30;
|
level.eventBus.timeout = 30;
|
||||||
|
|
||||||
level.commonFunctions = spawnstruct();
|
level.commonFunctions = spawnstruct();
|
||||||
level.commonFunctions.setDvar = "SetDvarIfUninitialized";
|
level.commonFunctions.setDvar = "SetDvarIfUninitialized";
|
||||||
level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum";
|
level.commonFunctions.isBot = "IsBot";
|
||||||
level.commonFunctions.waittillNotifyOrTimeout = "WaittillNotifyOrTimeout";
|
|
||||||
level.commonFunctions.getInboundData = "GetInboundData";
|
|
||||||
level.commonFunctions.getOutboundData = "GetOutboundData";
|
|
||||||
level.commonFunctions.setInboundData = "SetInboundData";
|
|
||||||
level.commonFunctions.setOutboundData = "SetOutboundData";
|
|
||||||
|
|
||||||
level.overrideMethods = [];
|
|
||||||
level.overrideMethods[level.commonFunctions.setDvar] = scripts\_integration_base::NotImplementedFunction;
|
|
||||||
level.overrideMethods[level.commonFunctions.getPlayerFromClientNum] = ::_GetPlayerFromClientNum;
|
|
||||||
level.overrideMethods[level.commonFunctions.getInboundData] = ::_GetInboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.getOutboundData] = ::_GetOutboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.setInboundData] = ::_SetInboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.setOutboundData] = ::_SetOutboundData;
|
|
||||||
|
|
||||||
level.busMethods = [];
|
|
||||||
level.busMethods[level.commonFunctions.getInboundData] = ::_GetInboundData;
|
|
||||||
level.busMethods[level.commonFunctions.getOutboundData] = ::_GetOutboundData;
|
|
||||||
level.busMethods[level.commonFunctions.setInboundData] = ::_SetInboundData;
|
|
||||||
level.busMethods[level.commonFunctions.setOutboundData] = ::_SetOutboundData;
|
|
||||||
|
|
||||||
level.commonKeys = spawnstruct();
|
level.commonKeys = spawnstruct();
|
||||||
level.commonKeys.enabled = "sv_iw4madmin_integration_enabled";
|
|
||||||
level.commonKeys.busMode = "sv_iw4madmin_integration_busmode";
|
|
||||||
level.commonKeys.busDir = "sv_iw4madmin_integration_busdir";
|
|
||||||
level.eventBus.inLocation = "";
|
|
||||||
level.eventBus.outLocation = "";
|
|
||||||
|
|
||||||
level.notifyTypes = spawnstruct();
|
level.notifyTypes = spawnstruct();
|
||||||
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
|
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
|
||||||
level.notifyTypes.sharedFunctionsInitialized = "SharedFunctionsInitialized";
|
|
||||||
level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized";
|
level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized";
|
||||||
|
|
||||||
level.clientDataKey = "clientData";
|
level.clientDataKey = "clientData";
|
||||||
|
|
||||||
level.eventTypes = spawnstruct();
|
level.eventTypes = spawnstruct();
|
||||||
level.eventTypes.eventAvailable = "EventAvailable";
|
level.eventTypes.localClientEvent = "client_event";
|
||||||
level.eventTypes.clientDataReceived = "ClientDataReceived";
|
level.eventTypes.clientDataReceived = "ClientDataReceived";
|
||||||
level.eventTypes.clientDataRequested = "ClientDataRequested";
|
level.eventTypes.clientDataRequested = "ClientDataRequested";
|
||||||
level.eventTypes.setClientDataRequested = "SetClientDataRequested";
|
level.eventTypes.setClientDataRequested = "SetClientDataRequested";
|
||||||
level.eventTypes.setClientDataCompleted = "SetClientDataCompleted";
|
level.eventTypes.setClientDataCompleted = "SetClientDataCompleted";
|
||||||
level.eventTypes.executeCommandRequested = "ExecuteCommandRequested";
|
level.eventTypes.executeCommandRequested = "ExecuteCommandRequested";
|
||||||
|
|
||||||
level.iw4madminIntegrationDebug = 0;
|
level.iw4madminIntegrationDebug = 0;
|
||||||
|
|
||||||
// map the event type to the handler
|
// map the event type to the handler
|
||||||
level.eventCallbacks = [];
|
level.eventCallbacks = [];
|
||||||
level.eventCallbacks[level.eventTypes.clientDataReceived] = ::OnClientDataReceived;
|
level.eventCallbacks[level.eventTypes.clientDataReceived] = ::OnClientDataReceived;
|
||||||
@ -73,71 +50,177 @@ Setup()
|
|||||||
level.clientCommandCallbacks = [];
|
level.clientCommandCallbacks = [];
|
||||||
level.clientCommandRusAsTarget = [];
|
level.clientCommandRusAsTarget = [];
|
||||||
level.logger = spawnstruct();
|
level.logger = spawnstruct();
|
||||||
|
level.overrideMethods = [];
|
||||||
|
|
||||||
level.iw4madminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
|
level.iw4madminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
|
||||||
InitializeLogger();
|
InitializeLogger();
|
||||||
|
|
||||||
wait ( 0.05 * 2 ); // needed to give script engine time to propagate notifies
|
wait ( 0.05 ); // needed to give script engine time to propagate notifies
|
||||||
|
|
||||||
level notify( level.notifyTypes.integrationBootstrapInitialized );
|
level notify( level.notifyTypes.integrationBootstrapInitialized );
|
||||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
||||||
|
|
||||||
LogDebug( "Integration received notify that game functions are initialized" );
|
LogDebug( "Integration received notify that game functions are initialized" );
|
||||||
|
|
||||||
_SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
_SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
||||||
_SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
_SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
||||||
_SetDvarIfUninitialized( level.commonKeys.enabled, 1 );
|
_SetDvarIfUninitialized( "sv_iw4madmin_integration_enabled", 1 );
|
||||||
_SetDvarIfUninitialized( level.commonKeys.busMode, "rcon" );
|
|
||||||
_SetDvarIfUninitialized( level.commonKeys.busdir, "" );
|
|
||||||
_SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
|
_SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
|
||||||
_SetDvarIfUninitialized( "GroupSeparatorChar", "" );
|
|
||||||
_SetDvarIfUninitialized( "RecordSeparatorChar", "" );
|
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||||
_SetDvarIfUninitialized( "UnitSeparatorChar", "" );
|
|
||||||
|
|
||||||
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start long running tasks
|
// start long running tasks
|
||||||
thread MonitorEvents();
|
level thread MonitorClientEvents();
|
||||||
thread MonitorBus();
|
level thread MonitorBus();
|
||||||
|
level thread OnPlayerConnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
MonitorEvents()
|
//////////////////////////////////
|
||||||
|
// Client Methods
|
||||||
|
//////////////////////////////////
|
||||||
|
|
||||||
|
OnPlayerConnect()
|
||||||
{
|
{
|
||||||
level endon( level.eventTypes.gameEnd );
|
level endon ( "game_ended" );
|
||||||
|
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
level waittill( "connected", player );
|
||||||
|
|
||||||
|
if ( _IsBot( player ) )
|
||||||
|
{
|
||||||
|
// we don't want to track bots
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !IsDefined( player.pers[level.clientDataKey] ) )
|
||||||
|
{
|
||||||
|
player.pers[level.clientDataKey] = spawnstruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
player thread OnPlayerSpawned();
|
||||||
|
player thread OnPlayerJoinedTeam();
|
||||||
|
player thread OnPlayerJoinedSpectators();
|
||||||
|
player thread PlayerTrackingOnInterval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPlayerSpawned()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
self waittill( "spawned_player" );
|
||||||
|
self PlayerSpawnEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ( ;; )
|
||||||
|
{
|
||||||
|
level waittill( "game_ended" );
|
||||||
|
// note: you can run data code here but it's possible for
|
||||||
|
// data to get truncated, so we will try a timer based approach for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayWelcomeData()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
clientData = self.pers[level.clientDataKey];
|
||||||
|
|
||||||
|
if ( clientData.permissionLevel == "User" || clientData.permissionLevel == "Flagged" )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self IPrintLnBold( "Welcome, your level is ^5" + clientData.permissionLevel );
|
||||||
|
wait( 2.0 );
|
||||||
|
self IPrintLnBold( "You were last seen ^5" + clientData.lastConnection );
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerSpawnEvents()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
clientData = self.pers[level.clientDataKey];
|
||||||
|
|
||||||
|
// this gives IW4MAdmin some time to register the player before making the request;
|
||||||
|
// although probably not necessary some users might have a slow database or poll rate
|
||||||
|
wait ( 2 );
|
||||||
|
|
||||||
|
if ( IsDefined( clientData.state ) && clientData.state == "complete" )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self RequestClientBasicData();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerTrackingOnInterval()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
wait ( 120 );
|
||||||
|
if ( IsAlive( self ) )
|
||||||
|
{
|
||||||
|
self SaveTrackingMetrics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitorClientEvents()
|
||||||
|
{
|
||||||
|
level endon( "game_ended" );
|
||||||
|
|
||||||
for ( ;; )
|
for ( ;; )
|
||||||
{
|
{
|
||||||
level waittill( level.eventTypes.eventAvailable, event );
|
level waittill( level.eventTypes.localClientEvent, client );
|
||||||
|
|
||||||
LogDebug( "Processing Event " + event.type + "-" + event.subtype );
|
LogDebug( "Processing Event " + client.event.type + "-" + client.event.subtype );
|
||||||
|
|
||||||
eventHandler = level.eventCallbacks[event.type];
|
eventHandler = level.eventCallbacks[client.event.type];
|
||||||
|
|
||||||
if ( IsDefined( eventHandler ) )
|
if ( IsDefined( eventHandler ) )
|
||||||
{
|
{
|
||||||
if ( IsDefined( event.entity ) )
|
client [[eventHandler]]( client.event );
|
||||||
{
|
LogDebug( "notify client for " + client.event.type );
|
||||||
event.entity [[eventHandler]]( event );
|
client notify( level.eventTypes.localClientEvent, client.event );
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[[eventHandler]]( event );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( event.entity ) )
|
|
||||||
{
|
|
||||||
LogDebug( "Notify client for " + event.type );
|
|
||||||
event.entity notify( event.type, event );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogDebug( "Notify level for " + event.type );
|
|
||||||
level notify( event.type, event );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.eventData = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,13 +228,11 @@ MonitorEvents()
|
|||||||
// Helper Methods
|
// Helper Methods
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
|
|
||||||
NotImplementedFunction( a, b, c, d, e, f )
|
_IsBot( entity )
|
||||||
{
|
{
|
||||||
LogWarning( "Function not implemented" );
|
// there already is a cgame function exists as "IsBot", for IW4, but unsure what all titles have it defined,
|
||||||
if ( IsDefined ( a ) )
|
// so we are defining it here
|
||||||
{
|
return IsDefined( entity.pers["isBot"] ) && entity.pers["isBot"];
|
||||||
LogWarning( a );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_SetDvarIfUninitialized( dvarName, dvarValue )
|
_SetDvarIfUninitialized( dvarName, dvarValue )
|
||||||
@ -159,44 +240,9 @@ _SetDvarIfUninitialized( dvarName, dvarValue )
|
|||||||
[[level.overrideMethods[level.commonFunctions.setDvar]]]( dvarName, dvarValue );
|
[[level.overrideMethods[level.commonFunctions.setDvar]]]( dvarName, dvarValue );
|
||||||
}
|
}
|
||||||
|
|
||||||
_GetPlayerFromClientNum( clientNum )
|
NotImplementedFunction( a, b, c, d, e, f )
|
||||||
{
|
{
|
||||||
assertEx( clientNum >= 0, "clientNum cannot be negative" );
|
LogWarning( "Function not implemented" );
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
_GetInboundData( location )
|
|
||||||
{
|
|
||||||
return GetDvar( level.eventBus.inVar );
|
|
||||||
}
|
|
||||||
|
|
||||||
_GetOutboundData( location )
|
|
||||||
{
|
|
||||||
return GetDvar( level.eventBus.outVar );
|
|
||||||
}
|
|
||||||
|
|
||||||
_SetInboundData( location, data )
|
|
||||||
{
|
|
||||||
return SetDvar( level.eventBus.inVar, data );
|
|
||||||
}
|
|
||||||
|
|
||||||
_SetOutboundData( location, data )
|
|
||||||
{
|
|
||||||
return SetDvar( level.eventBus.outVar, data );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not every game can output to console or even game log.
|
// Not every game can output to console or even game log.
|
||||||
@ -217,7 +263,7 @@ _Log( LogLevel, message )
|
|||||||
{
|
{
|
||||||
for( i = 0; i < level.logger._logger.size; i++ )
|
for( i = 0; i < level.logger._logger.size; i++ )
|
||||||
{
|
{
|
||||||
[[level.logger._logger[i]]]( LogLevel, GetSubStr( message, 0, 1000 ) );
|
[[level.logger._logger[i]]]( LogLevel, message );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,13 +325,13 @@ RegisterLogger( logger )
|
|||||||
RequestClientMeta( metaKey )
|
RequestClientMeta( metaKey )
|
||||||
{
|
{
|
||||||
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
||||||
thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
|
level thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestClientBasicData()
|
RequestClientBasicData()
|
||||||
{
|
{
|
||||||
getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" );
|
getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" );
|
||||||
thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
|
level thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
|
||||||
}
|
}
|
||||||
|
|
||||||
IncrementClientMeta( metaKey, incrementValue, clientId )
|
IncrementClientMeta( metaKey, incrementValue, clientId )
|
||||||
@ -298,22 +344,51 @@ DecrementClientMeta( metaKey, decrementValue, clientId )
|
|||||||
SetClientMeta( metaKey, decrementValue, clientId, "decrement" );
|
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 )
|
SetClientMeta( metaKey, metaValue, clientId, direction )
|
||||||
{
|
{
|
||||||
data = [];
|
data = "key=" + metaKey + "|value=" + metaValue;
|
||||||
data["key"] = metaKey;
|
|
||||||
data["value"] = metaValue;
|
|
||||||
clientNumber = -1;
|
clientNumber = -1;
|
||||||
|
|
||||||
if ( IsDefined ( clientId ) )
|
if ( IsDefined ( clientId ) )
|
||||||
{
|
{
|
||||||
data["clientId"] = clientId;
|
data = data + "|clientId=" + clientId;
|
||||||
clientNumber = -1;
|
clientNumber = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( IsDefined( direction ) )
|
if ( IsDefined( direction ) )
|
||||||
{
|
{
|
||||||
data["direction"] = direction;
|
data = data + "|direction=" + direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( IsPlayer( self ) )
|
if ( IsPlayer( self ) )
|
||||||
@ -322,7 +397,40 @@ SetClientMeta( metaKey, metaValue, clientId, direction )
|
|||||||
}
|
}
|
||||||
|
|
||||||
setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data );
|
setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data );
|
||||||
thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self );
|
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 )
|
BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
|
||||||
@ -331,97 +439,79 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
|
|||||||
{
|
{
|
||||||
data = "";
|
data = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !IsDefined( eventSubtype ) )
|
if ( !IsDefined( eventSubtype ) )
|
||||||
{
|
{
|
||||||
eventSubtype = "None";
|
eventSubtype = "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !IsDefined( entOrId ) )
|
|
||||||
{
|
|
||||||
entOrId = "-1";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsPlayer( entOrId ) )
|
if ( IsPlayer( entOrId ) )
|
||||||
{
|
{
|
||||||
entOrId = entOrId getEntityNumber();
|
entOrId = entOrId getEntityNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
request = "0";
|
request = "0";
|
||||||
|
|
||||||
if ( responseExpected )
|
if ( responseExpected )
|
||||||
{
|
{
|
||||||
request = "1";
|
request = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
data = BuildDataString( data );
|
request = request + ";" + eventType + ";" + eventSubtype + ";" + entOrId + ";" + data;
|
||||||
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
|
|
||||||
request = request + groupSeparator + eventType + groupSeparator + eventSubtype + groupSeparator + entOrId + groupSeparator + data;
|
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
MonitorBus()
|
MonitorBus()
|
||||||
{
|
{
|
||||||
level endon( level.eventTypes.gameEnd );
|
level endon( "game_ended" );
|
||||||
|
|
||||||
level.eventBus.inLocation = level.eventBus.inVar + "_" + GetDvar( "net_port" );
|
|
||||||
level.eventBus.outLocation = level.eventBus.outVar + "_" + GetDvar( "net_port" );
|
|
||||||
|
|
||||||
[[level.overrideMethods[level.commonFunctions.SetInboundData]]]( level.eventBus.inLocation, "" );
|
|
||||||
[[level.overrideMethods[level.commonFunctions.SetOutboundData]]]( level.eventBus.outLocation, "" );
|
|
||||||
|
|
||||||
for( ;; )
|
for( ;; )
|
||||||
{
|
{
|
||||||
wait ( 0.1 );
|
wait ( 0.1 );
|
||||||
|
|
||||||
// check to see if IW4MAdmin is ready to receive more data
|
// check to see if IW4MAdmin is ready to receive more data
|
||||||
inVal = [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation );
|
if ( getDvar( level.eventBus.inVar ) == "" )
|
||||||
|
|
||||||
if ( !IsDefined( inVal ) || inVal == "" )
|
|
||||||
{
|
{
|
||||||
level notify( "bus_ready" );
|
level notify( "bus_ready" );
|
||||||
}
|
}
|
||||||
|
|
||||||
eventString = [[level.busMethods[level.commonFunctions.getOutboundData]]]( level.eventBus.outLocation );
|
eventString = getDvar( level.eventBus.outVar );
|
||||||
|
|
||||||
if ( !IsDefined( eventString ) || eventString == "" )
|
if ( eventString == "" )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogDebug( "-> " + eventString );
|
LogDebug( "-> " + eventString );
|
||||||
|
|
||||||
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
|
NotifyClientEvent( strtok( eventString, ";" ) );
|
||||||
NotifyEvent( strtok( eventString, groupSeparator ) );
|
|
||||||
|
SetDvar( level.eventBus.outVar, "" );
|
||||||
[[level.busMethods[level.commonFunctions.SetOutboundData]]]( level.eventBus.outLocation, "" );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QueueEvent( request, eventType, notifyEntity )
|
QueueEvent( request, eventType, notifyEntity )
|
||||||
{
|
{
|
||||||
level endon( level.eventTypes.gameEnd );
|
level endon( "game_ended" );
|
||||||
|
|
||||||
start = GetTime();
|
start = GetTime();
|
||||||
maxWait = level.eventBus.timeout * 1000; // 30 seconds
|
maxWait = level.eventBus.timeout * 1000; // 30 seconds
|
||||||
timedOut = "";
|
timedOut = "";
|
||||||
|
|
||||||
while ( [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ) != "" && ( GetTime() - start ) < maxWait )
|
while ( GetDvar( level.eventBus.inVar ) != "" && ( GetTime() - start ) < maxWait )
|
||||||
{
|
{
|
||||||
level [[level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout]]]( "bus_ready", 1 );
|
level [[level.overrideMethods["waittill_notify_or_timeout"]]]( "bus_ready", 1 );
|
||||||
|
|
||||||
if ( [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ) != "" )
|
if ( GetDvar( level.eventBus.inVar ) != "" )
|
||||||
{
|
{
|
||||||
LogDebug( "A request is already in progress..." );
|
LogDebug( "A request is already in progress..." );
|
||||||
timedOut = "set";
|
timedOut = "set";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
timedOut = "unset";
|
timedOut = "unset";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( timedOut == "set" )
|
if ( timedOut == "set")
|
||||||
{
|
{
|
||||||
LogDebug( "Timed out waiting for response..." );
|
LogDebug( "Timed out waiting for response..." );
|
||||||
|
|
||||||
@ -430,14 +520,14 @@ QueueEvent( request, eventType, notifyEntity )
|
|||||||
notifyEntity NotifyClientEventTimeout( eventType );
|
notifyEntity NotifyClientEventTimeout( eventType );
|
||||||
}
|
}
|
||||||
|
|
||||||
[[level.busMethods[level.commonFunctions.SetInboundData]]]( level.eventBus.inLocation, "" );
|
SetDvar( level.eventBus.inVar, "" );
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogDebug( "<- " + request );
|
LogDebug("<- " + request );
|
||||||
|
|
||||||
[[level.busMethods[level.commonFunctions.setInboundData]]]( level.eventBus.inLocation, request );
|
SetDvar( level.eventBus.inVar, request );
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseDataString( data )
|
ParseDataString( data )
|
||||||
@ -447,43 +537,23 @@ ParseDataString( data )
|
|||||||
LogDebug( "No data to parse" );
|
LogDebug( "No data to parse" );
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
dataParts = strtok( data, GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 ) );
|
dataParts = strtok( data, "|" );
|
||||||
dict = [];
|
dict = [];
|
||||||
|
|
||||||
for ( i = 0; i < dataParts.size; i++ )
|
for ( i = 0; i < dataParts.size; i++ )
|
||||||
{
|
{
|
||||||
part = dataParts[i];
|
part = dataParts[i];
|
||||||
splitPart = strtok( part, GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 ) );
|
splitPart = strtok( part, "=" );
|
||||||
key = splitPart[0];
|
key = splitPart[0];
|
||||||
value = splitPart[1];
|
value = splitPart[1];
|
||||||
dict[key] = value;
|
dict[key] = value;
|
||||||
dict[i] = key;
|
dict[i] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildDataString( data )
|
|
||||||
{
|
|
||||||
if ( IsString( data ) )
|
|
||||||
{
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataString = "";
|
|
||||||
keys = GetArrayKeys( data );
|
|
||||||
unitSeparator = GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 );
|
|
||||||
recordSeparator = GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 );
|
|
||||||
|
|
||||||
for ( i = 0; i < keys.size; i++ )
|
|
||||||
{
|
|
||||||
dataString = dataString + keys[i] + unitSeparator + data[keys[i]] + recordSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataString;
|
|
||||||
}
|
|
||||||
|
|
||||||
NotifyClientEventTimeout( eventType )
|
NotifyClientEventTimeout( eventType )
|
||||||
{
|
{
|
||||||
// todo: make this actual eventing
|
// todo: make this actual eventing
|
||||||
@ -493,18 +563,23 @@ NotifyClientEventTimeout( eventType )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotifyEvent( eventInfo )
|
NotifyClientEvent( eventInfo )
|
||||||
{
|
{
|
||||||
origin = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[3] ) );
|
origin = getPlayerFromClientNum( int( eventInfo[3] ) );
|
||||||
target = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[4] ) );
|
target = getPlayerFromClientNum( int( eventInfo[4] ) );
|
||||||
|
|
||||||
event = spawnstruct();
|
event = spawnstruct();
|
||||||
event.type = eventInfo[1];
|
event.type = eventInfo[1];
|
||||||
event.subtype = eventInfo[2];
|
event.subtype = eventInfo[2];
|
||||||
event.data = ParseDataString( eventInfo[5] );
|
event.data = eventInfo[5];
|
||||||
event.origin = origin;
|
event.origin = origin;
|
||||||
event.target = target;
|
event.target = target;
|
||||||
|
|
||||||
|
if ( IsDefined( event.data ) )
|
||||||
|
{
|
||||||
|
LogDebug( "NotifyClientEvent->" + event.data );
|
||||||
|
}
|
||||||
|
|
||||||
if ( int( eventInfo[3] ) != -1 && !IsDefined( origin ) )
|
if ( int( eventInfo[3] ) != -1 && !IsDefined( origin ) )
|
||||||
{
|
{
|
||||||
LogDebug( "origin is null but the slot id is " + int( eventInfo[3] ) );
|
LogDebug( "origin is null but the slot id is " + int( eventInfo[3] ) );
|
||||||
@ -514,15 +589,41 @@ NotifyEvent( eventInfo )
|
|||||||
LogDebug( "target is null but the slot id is " + int( eventInfo[4] ) );
|
LogDebug( "target is null but the slot id is " + int( eventInfo[4] ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
client = event.origin;
|
if ( IsDefined( target ) )
|
||||||
|
|
||||||
if ( !IsDefined( client ) )
|
|
||||||
{
|
{
|
||||||
client = event.target;
|
client = event.target;
|
||||||
}
|
}
|
||||||
|
else if ( IsDefined( origin ) )
|
||||||
|
{
|
||||||
|
client = event.origin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogDebug( "Neither origin or target are set but we are a Client Event, aborting" );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.event = event;
|
||||||
|
level notify( level.eventTypes.localClientEvent, client );
|
||||||
|
}
|
||||||
|
|
||||||
event.entity = client;
|
GetPlayerFromClientNum( clientNum )
|
||||||
level notify( level.eventTypes.eventAvailable, event );
|
{
|
||||||
|
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 )
|
AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
||||||
@ -531,7 +632,7 @@ AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
level.clientCommandCallbacks[commandName] = callback;
|
level.clientCommandCallbacks[commandName] = callback;
|
||||||
level.clientCommandRusAsTarget[commandName] = shouldRunAsTarget == true; //might speed up things later in case someone gives us a string or number instead of a boolean
|
level.clientCommandRusAsTarget[commandName] = shouldRunAsTarget == true; //might speed up things later in case someone gives us a string or number instead of a boolean
|
||||||
}
|
}
|
||||||
@ -542,7 +643,7 @@ AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
|||||||
|
|
||||||
OnClientDataReceived( event )
|
OnClientDataReceived( event )
|
||||||
{
|
{
|
||||||
assertEx( isDefined( self ), "player entity is not defined");
|
event.data = ParseDataString( event.data );
|
||||||
clientData = self.pers[level.clientDataKey];
|
clientData = self.pers[level.clientDataKey];
|
||||||
|
|
||||||
if ( event.subtype == "Fail" )
|
if ( event.subtype == "Fail" )
|
||||||
@ -558,15 +659,15 @@ OnClientDataReceived( event )
|
|||||||
{
|
{
|
||||||
clientData.meta = [];
|
clientData.meta = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
metaKey = event.data[0];
|
metaKey = event.data[0];
|
||||||
clientData.meta[metaKey] = event.data[metaKey];
|
clientData.meta[metaKey] = event.data[metaKey];
|
||||||
|
|
||||||
LogDebug( "Meta Key=" + CoerceUndefined( metaKey ) + ", Meta Value=" + CoerceUndefined( event.data[metaKey] ) );
|
LogDebug( "Meta Key=" + metaKey + ", Meta Value=" + event.data[metaKey] );
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clientData.permissionLevel = event.data["level"];
|
clientData.permissionLevel = event.data["level"];
|
||||||
clientData.clientId = event.data["clientId"];
|
clientData.clientId = event.data["clientId"];
|
||||||
clientData.lastConnection = event.data["lastConnection"];
|
clientData.lastConnection = event.data["lastConnection"];
|
||||||
@ -574,13 +675,15 @@ OnClientDataReceived( event )
|
|||||||
clientData.performance = event.data["performance"];
|
clientData.performance = event.data["performance"];
|
||||||
clientData.state = "complete";
|
clientData.state = "complete";
|
||||||
self.persistentClientId = event.data["clientId"];
|
self.persistentClientId = event.data["clientId"];
|
||||||
|
|
||||||
|
self thread DisplayWelcomeData();
|
||||||
}
|
}
|
||||||
|
|
||||||
OnExecuteCommand( event )
|
OnExecuteCommand( event )
|
||||||
{
|
{
|
||||||
data = event.data;
|
data = ParseDataString( event.data );
|
||||||
response = "";
|
response = "";
|
||||||
|
|
||||||
command = level.clientCommandCallbacks[event.subtype];
|
command = level.clientCommandCallbacks[event.subtype];
|
||||||
runAsTarget = level.clientCommandRusAsTarget[event.subtype];
|
runAsTarget = level.clientCommandRusAsTarget[event.subtype];
|
||||||
executionContextEntity = event.origin;
|
executionContextEntity = event.origin;
|
||||||
@ -589,23 +692,16 @@ OnExecuteCommand( event )
|
|||||||
{
|
{
|
||||||
executionContextEntity = event.target;
|
executionContextEntity = event.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( IsDefined( command ) )
|
if ( IsDefined( command ) )
|
||||||
{
|
{
|
||||||
if ( IsDefined( executionContextEntity ) )
|
response = executionContextEntity [[command]]( event, data );
|
||||||
{
|
|
||||||
response = executionContextEntity thread [[command]]( event, data );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
thread [[command]]( event );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LogDebug( "Unknown Client command->" + event.subtype );
|
LogDebug( "Unknown Client command->" + event.subtype );
|
||||||
}
|
}
|
||||||
|
|
||||||
// send back the response to the origin, but only if they're not the target
|
// send back the response to the origin, but only if they're not the target
|
||||||
if ( IsDefined( response ) && response != "" && IsPlayer( event.origin ) && event.origin != event.target )
|
if ( IsDefined( response ) && response != "" && IsPlayer( event.origin ) && event.origin != event.target )
|
||||||
{
|
{
|
||||||
@ -615,15 +711,6 @@ OnExecuteCommand( event )
|
|||||||
|
|
||||||
OnSetClientDataCompleted( event )
|
OnSetClientDataCompleted( event )
|
||||||
{
|
{
|
||||||
LogDebug( "Set Client Data -> subtype = " + CoerceUndefined( event.subType ) + ", status = " + CoerceUndefined( event.data["status"] ) );
|
// IW4MAdmin let us know it persisted (success or fail)
|
||||||
}
|
LogDebug( "Set Client Data -> subtype = " + event.subType + " status = " + event.data["status"] );
|
||||||
|
|
||||||
CoerceUndefined( object )
|
|
||||||
{
|
|
||||||
if ( !IsDefined( object ) )
|
|
||||||
{
|
|
||||||
return "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
}
|
||||||
|
@ -2,23 +2,24 @@
|
|||||||
|
|
||||||
Init()
|
Init()
|
||||||
{
|
{
|
||||||
|
level.eventBus.gamename = "IW4";
|
||||||
|
|
||||||
thread Setup();
|
thread Setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
Setup()
|
Setup()
|
||||||
{
|
{
|
||||||
level endon( "game_ended" );
|
level endon( "game_ended" );
|
||||||
waittillframeend;
|
|
||||||
|
|
||||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||||
level.eventBus.gamename = "IW4";
|
level waittill( "IntegrationBootstrapInitialized" );
|
||||||
|
|
||||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||||
|
|
||||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
level.overrideMethods[level.commonFunctions.setDvar] = ::_SetDvarIfUninitialized;
|
||||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient;
|
||||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||||
level.overrideMethods[level.commonFunctions.changeTeam] = ::ChangeTeam;
|
level.overrideMethods[level.commonFunctions.changeTeam] = ::ChangeTeam;
|
||||||
level.overrideMethods[level.commonFunctions.getTeamCounts] = ::CountPlayers;
|
level.overrideMethods[level.commonFunctions.getTeamCounts] = ::CountPlayers;
|
||||||
level.overrideMethods[level.commonFunctions.getMaxClients] = ::GetMaxClients;
|
level.overrideMethods[level.commonFunctions.getMaxClients] = ::GetMaxClients;
|
||||||
@ -27,25 +28,17 @@ Setup()
|
|||||||
level.overrideMethods[level.commonFunctions.getClientKillStreak] = ::GetClientKillStreak;
|
level.overrideMethods[level.commonFunctions.getClientKillStreak] = ::GetClientKillStreak;
|
||||||
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = ::BackupRestoreClientKillStreakData;
|
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = ::BackupRestoreClientKillStreakData;
|
||||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
||||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
|
||||||
|
|
||||||
level.overrideMethods[level.commonFunctions.getInboundData] = ::GetInboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.getOutboundData] = ::GetOutboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.setInboundData] = ::SetInboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.setOutboundData] = ::SetOutboundData;
|
|
||||||
|
|
||||||
RegisterClientCommands();
|
RegisterClientCommands();
|
||||||
|
|
||||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||||
|
|
||||||
scripts\_integration_base::_SetDvarIfUninitialized( level.commonKeys.busdir, GetDvar( "fs_homepath" ) + "userraw/" + "scriptdata" );
|
|
||||||
|
|
||||||
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
|
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread OnPlayerConnect();
|
level thread OnPlayerConnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPlayerConnect()
|
OnPlayerConnect()
|
||||||
@ -56,12 +49,12 @@ OnPlayerConnect()
|
|||||||
{
|
{
|
||||||
level waittill( "connected", player );
|
level waittill( "connected", player );
|
||||||
|
|
||||||
if ( player IsTestClient() )
|
if ( player call [[ level.overrideMethods[ level.commonFunctions.isBot ] ]]() )
|
||||||
{
|
{
|
||||||
// we don't want to track bots
|
// we don't want to track bots
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
player thread SetPersistentData();
|
player thread SetPersistentData();
|
||||||
player thread WaitForClientEvents();
|
player thread WaitForClientEvents();
|
||||||
}
|
}
|
||||||
@ -92,7 +85,7 @@ WaitForClientEvents()
|
|||||||
|
|
||||||
for ( ;; )
|
for ( ;; )
|
||||||
{
|
{
|
||||||
self waittill( level.eventTypes.eventAvailable, event );
|
self waittill( level.eventTypes.localClientEvent, event );
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||||
|
|
||||||
@ -104,26 +97,6 @@ WaitForClientEvents()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GetInboundData( location )
|
|
||||||
{
|
|
||||||
return FileRead( location );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetOutboundData( location )
|
|
||||||
{
|
|
||||||
return FileRead( location );
|
|
||||||
}
|
|
||||||
|
|
||||||
SetInboundData( location, data )
|
|
||||||
{
|
|
||||||
FileWrite( location, data, "write" );
|
|
||||||
}
|
|
||||||
|
|
||||||
SetOutboundData( location, data )
|
|
||||||
{
|
|
||||||
FileWrite( location, data, "write" );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetMaxClients()
|
GetMaxClients()
|
||||||
{
|
{
|
||||||
return level.maxClients;
|
return level.maxClients;
|
||||||
@ -211,7 +184,12 @@ GetTotalShotsFired()
|
|||||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitillNotifyOrTimeoutWrapper( _notify, timeout )
|
_SetDvarIfUninitialized( dvar, value )
|
||||||
|
{
|
||||||
|
SetDvarIfUninitialized( dvar, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
_waittill_notify_or_timeout( _notify, timeout )
|
||||||
{
|
{
|
||||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||||
}
|
}
|
||||||
@ -221,21 +199,6 @@ Log2Console( logLevel, message )
|
|||||||
PrintConsole( "[" + logLevel + "] " + message + "\n" );
|
PrintConsole( "[" + logLevel + "] " + message + "\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
SetDvarIfUninitializedWrapper( dvar, value )
|
|
||||||
{
|
|
||||||
SetDvarIfUninitialized( dvar, value );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetXuidWrapper()
|
|
||||||
{
|
|
||||||
return self GetXUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBotWrapper( client )
|
|
||||||
{
|
|
||||||
return client IsTestClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
// GUID helpers
|
// GUID helpers
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
@ -549,7 +512,11 @@ HideImpl()
|
|||||||
|
|
||||||
AlertImpl( event, data )
|
AlertImpl( event, data )
|
||||||
{
|
{
|
||||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
if ( level.eventBus.gamename == "IW4" )
|
||||||
|
{
|
||||||
|
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||||
|
}
|
||||||
|
|
||||||
return "Sent alert to " + self.name;
|
return "Sent alert to " + self.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +1,92 @@
|
|||||||
#include common_scripts\utility;
|
#include common_scripts\utility;
|
||||||
|
|
||||||
#inline scripts\_integration_utility;
|
|
||||||
|
|
||||||
Init()
|
Init()
|
||||||
{
|
{
|
||||||
|
level.eventBus.gamename = "IW5";
|
||||||
|
|
||||||
thread Setup();
|
thread Setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
Setup()
|
Setup()
|
||||||
{
|
{
|
||||||
level endon( "game_ended" );
|
level endon( "game_ended" );
|
||||||
waittillframeend;
|
|
||||||
|
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
level waittill( "IntegrationBootstrapInitialized" );
|
||||||
level.eventBus.gamename = "IW5";
|
|
||||||
|
scripts\mp\_integration_base::RegisterLogger( ::Log2Console );
|
||||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
|
||||||
|
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient;
|
||||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
|
||||||
RegisterClientCommands();
|
RegisterClientCommands();
|
||||||
|
|
||||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
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 ( player call [[ level.overrideMethods[ level.commonFunctions.isBot ] ]]() )
|
||||||
|
{
|
||||||
|
// we don't want to track bots
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
player thread SetPersistentData();
|
||||||
|
player thread WaitForClientEvents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterClientCommands()
|
RegisterClientCommands()
|
||||||
{
|
{
|
||||||
scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForClientEvents()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
// example of requesting a meta value
|
||||||
|
lastServerMetaKey = "LastServerPlayed";
|
||||||
|
// self scripts\mp\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||||
|
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
self waittill( level.eventTypes.localClientEvent, event );
|
||||||
|
|
||||||
|
scripts\mp\_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()
|
GetTotalShotsFired()
|
||||||
@ -48,12 +94,12 @@ GetTotalShotsFired()
|
|||||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||||
}
|
}
|
||||||
|
|
||||||
SetDvarIfUninitializedWrapper( dvar, value )
|
_SetDvarIfUninitialized( dvar, value )
|
||||||
{
|
{
|
||||||
SetDvarIfUninitialized( dvar, value );
|
SetDvarIfUninitialized( dvar, value );
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitillNotifyOrTimeoutWrapper( _notify, timeout )
|
_waittill_notify_or_timeout( _notify, timeout )
|
||||||
{
|
{
|
||||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||||
}
|
}
|
||||||
@ -63,19 +109,135 @@ Log2Console( logLevel, message )
|
|||||||
Print( "[" + logLevel + "] " + message + "\n" );
|
Print( "[" + logLevel + "] " + message + "\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
IsBotWrapper( client )
|
//////////////////////////////////
|
||||||
|
// GUID helpers
|
||||||
|
/////////////////////////////////
|
||||||
|
|
||||||
|
SetPersistentData()
|
||||||
{
|
{
|
||||||
return client IsTestClient();
|
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\mp\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||||
|
scripts\mp\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
guid = self SplitGuid();
|
||||||
|
|
||||||
|
scripts\mp\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||||
|
|
||||||
|
self SetPlayerData( "bests", "none", guid["high"] );
|
||||||
|
self SetPlayerData( "awards", "none", guid["low"] );
|
||||||
}
|
}
|
||||||
|
|
||||||
GetXuidWrapper()
|
SplitGuid()
|
||||||
{
|
{
|
||||||
return self GetXUID();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
Pow( num, exponent )
|
||||||
{
|
{
|
||||||
return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 );
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
@ -84,36 +246,45 @@ WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
|||||||
|
|
||||||
GiveWeaponImpl( event, data )
|
GiveWeaponImpl( event, data )
|
||||||
{
|
{
|
||||||
_IS_ALIVE( self );
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + "^7 is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
self IPrintLnBold( "You have been given a new weapon" );
|
self IPrintLnBold( "You have been given a new weapon" );
|
||||||
self GiveWeapon( data["weaponName"] );
|
self GiveWeapon( data["weaponName"] );
|
||||||
self SwitchToWeapon( data["weaponName"] );
|
self SwitchToWeapon( data["weaponName"] );
|
||||||
|
|
||||||
return self.name + "^7 has been given ^5" + data["weaponName"];
|
return self.name + "^7 has been given ^5" + data["weaponName"];
|
||||||
}
|
}
|
||||||
|
|
||||||
TakeWeaponsImpl()
|
TakeWeaponsImpl()
|
||||||
{
|
{
|
||||||
_IS_ALIVE( self );
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + "^7 is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
self TakeAllWeapons();
|
self TakeAllWeapons();
|
||||||
self IPrintLnBold( "All your weapons have been taken" );
|
self IPrintLnBold( "All your weapons have been taken" );
|
||||||
|
|
||||||
return "Took weapons from " + self.name;
|
return "Took weapons from " + self.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamSwitchImpl()
|
TeamSwitchImpl()
|
||||||
{
|
{
|
||||||
_IS_ALIVE( self );
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self + "^7 is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
team = level.allies;
|
team = level.allies;
|
||||||
|
|
||||||
if ( self.team == "allies" )
|
if ( self.team == "allies" )
|
||||||
{
|
{
|
||||||
team = level.axis;
|
team = level.axis;
|
||||||
}
|
}
|
||||||
|
|
||||||
self IPrintLnBold( "You are being team switched" );
|
self IPrintLnBold( "You are being team switched" );
|
||||||
wait( 2 );
|
wait( 2 );
|
||||||
self [[team]]();
|
self [[team]]();
|
||||||
@ -123,7 +294,10 @@ TeamSwitchImpl()
|
|||||||
|
|
||||||
LockControlsImpl()
|
LockControlsImpl()
|
||||||
{
|
{
|
||||||
_IS_ALIVE( self );
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + "^7 is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
if ( !IsDefined ( self.isControlLocked ) )
|
if ( !IsDefined ( self.isControlLocked ) )
|
||||||
{
|
{
|
||||||
@ -139,11 +313,11 @@ LockControlsImpl()
|
|||||||
info = [];
|
info = [];
|
||||||
info[ "alertType" ] = "Alert!";
|
info[ "alertType" ] = "Alert!";
|
||||||
info[ "message" ] = "You have been frozen!";
|
info[ "message" ] = "You have been frozen!";
|
||||||
|
|
||||||
self AlertImpl( undefined, info );
|
self AlertImpl( undefined, info );
|
||||||
|
|
||||||
self.isControlLocked = true;
|
self.isControlLocked = true;
|
||||||
|
|
||||||
return self.name + "\'s controls are locked";
|
return self.name + "\'s controls are locked";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -160,13 +334,11 @@ LockControlsImpl()
|
|||||||
|
|
||||||
NoClipImpl()
|
NoClipImpl()
|
||||||
{
|
{
|
||||||
_VERIFY_PLAYER_ENT( self );
|
|
||||||
|
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
{
|
{
|
||||||
self IPrintLnBold( "You are not alive" );
|
self IPrintLnBold( "You are not alive" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !IsDefined ( self.isNoClipped ) )
|
if ( !IsDefined ( self.isNoClipped ) )
|
||||||
{
|
{
|
||||||
self.isNoClipped = false;
|
self.isNoClipped = false;
|
||||||
@ -176,29 +348,29 @@ NoClipImpl()
|
|||||||
{
|
{
|
||||||
SetDvar( "sv_cheats", 1 );
|
SetDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 1 );
|
self SetClientDvar( "cg_thirdperson", 1 );
|
||||||
|
|
||||||
self God();
|
self God();
|
||||||
self Noclip();
|
self Noclip();
|
||||||
self Hide();
|
self Hide();
|
||||||
SetDvar( "sv_cheats", 0 );
|
SetDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.isNoClipped = true;
|
self.isNoClipped = true;
|
||||||
|
|
||||||
self IPrintLnBold( "NoClip enabled" );
|
self IPrintLnBold( "NoClip enabled" );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetDvar( "sv_cheats", 1 );
|
SetDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 0 );
|
self SetClientDvar( "cg_thirdperson", 0 );
|
||||||
|
|
||||||
self God();
|
self God();
|
||||||
self Noclip();
|
self Noclip();
|
||||||
self Hide();
|
self Hide();
|
||||||
|
|
||||||
SetDvar( "sv_cheats", 0 );
|
SetDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.isNoClipped = false;
|
self.isNoClipped = false;
|
||||||
|
|
||||||
self IPrintLnBold( "NoClip disabled" );
|
self IPrintLnBold( "NoClip disabled" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,13 +379,12 @@ NoClipImpl()
|
|||||||
|
|
||||||
HideImpl()
|
HideImpl()
|
||||||
{
|
{
|
||||||
_VERIFY_PLAYER_ENT( self );
|
|
||||||
|
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
{
|
{
|
||||||
self IPrintLnBold( "You are not alive" );
|
self IPrintLnBold( "You are not alive" );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !IsDefined ( self.isHidden ) )
|
if ( !IsDefined ( self.isHidden ) )
|
||||||
{
|
{
|
||||||
self.isHidden = false;
|
self.isHidden = false;
|
||||||
@ -223,33 +394,36 @@ HideImpl()
|
|||||||
{
|
{
|
||||||
SetDvar( "sv_cheats", 1 );
|
SetDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 1 );
|
self SetClientDvar( "cg_thirdperson", 1 );
|
||||||
|
|
||||||
self God();
|
self God();
|
||||||
self Hide();
|
self Hide();
|
||||||
SetDvar( "sv_cheats", 0 );
|
SetDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.isHidden = true;
|
self.isHidden = true;
|
||||||
|
|
||||||
self IPrintLnBold( "Hide enabled" );
|
self IPrintLnBold( "Hide enabled" );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetDvar( "sv_cheats", 1 );
|
SetDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 0 );
|
self SetClientDvar( "cg_thirdperson", 0 );
|
||||||
|
|
||||||
self God();
|
self God();
|
||||||
self Show();
|
self Show();
|
||||||
SetDvar( "sv_cheats", 0 );
|
SetDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.isHidden = false;
|
self.isHidden = false;
|
||||||
|
|
||||||
self IPrintLnBold( "Hide disabled" );
|
self IPrintLnBold( "Hide disabled" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertImpl( event, data )
|
AlertImpl( event, data )
|
||||||
{
|
{
|
||||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
if ( level.eventBus.gamename == "IW5" ) {
|
||||||
|
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||||
|
}
|
||||||
|
|
||||||
return "Sent alert to " + self.name;
|
return "Sent alert to " + self.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,8 +441,6 @@ GotoImpl( event, data )
|
|||||||
|
|
||||||
GotoCoordImpl( data )
|
GotoCoordImpl( data )
|
||||||
{
|
{
|
||||||
_VERIFY_PLAYER_ENT( self );
|
|
||||||
|
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
{
|
{
|
||||||
self IPrintLnBold( "You are not alive" );
|
self IPrintLnBold( "You are not alive" );
|
||||||
@ -282,8 +454,6 @@ GotoCoordImpl( data )
|
|||||||
|
|
||||||
GotoPlayerImpl( target )
|
GotoPlayerImpl( target )
|
||||||
{
|
{
|
||||||
_VERIFY_PLAYER_ENT( self );
|
|
||||||
|
|
||||||
if ( !IsAlive( target ) )
|
if ( !IsAlive( target ) )
|
||||||
{
|
{
|
||||||
self IPrintLnBold( target.name + " is not alive" );
|
self IPrintLnBold( target.name + " is not alive" );
|
||||||
@ -296,7 +466,10 @@ GotoPlayerImpl( target )
|
|||||||
|
|
||||||
PlayerToMeImpl( event )
|
PlayerToMeImpl( event )
|
||||||
{
|
{
|
||||||
_IS_ALIVE( self );
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + " is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
self SetOrigin( event.origin GetOrigin() );
|
self SetOrigin( event.origin GetOrigin() );
|
||||||
return "Moved here " + self.name;
|
return "Moved here " + self.name;
|
||||||
@ -304,7 +477,10 @@ PlayerToMeImpl( event )
|
|||||||
|
|
||||||
KillImpl()
|
KillImpl()
|
||||||
{
|
{
|
||||||
_IS_ALIVE( self );
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + " is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
self Suicide();
|
self Suicide();
|
||||||
self IPrintLnBold( "You were killed by " + self.name );
|
self IPrintLnBold( "You were killed by " + self.name );
|
||||||
@ -314,15 +490,13 @@ KillImpl()
|
|||||||
|
|
||||||
SetSpectatorImpl()
|
SetSpectatorImpl()
|
||||||
{
|
{
|
||||||
_VERIFY_PLAYER_ENT( self );
|
|
||||||
|
|
||||||
if ( self.pers["team"] == "spectator" )
|
if ( self.pers["team"] == "spectator" )
|
||||||
{
|
{
|
||||||
return self.name + " is already spectating";
|
return self.name + " is already spectating";
|
||||||
}
|
}
|
||||||
|
|
||||||
self [[level.spectator]]();
|
self [[level.spectator]]();
|
||||||
self IPrintLnBold( "You have been moved to spectator" );
|
self IPrintLnBold( "You have been moved to spectator" );
|
||||||
|
|
||||||
return self.name + " has been moved to spectator";
|
return self.name + " has been moved to spectator";
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
Init()
|
Init()
|
||||||
{
|
{
|
||||||
thread Setup();
|
thread Setup();
|
||||||
@ -5,11 +6,8 @@ Init()
|
|||||||
|
|
||||||
Setup()
|
Setup()
|
||||||
{
|
{
|
||||||
wait ( 0.05 );
|
|
||||||
level endon( "game_ended" );
|
level endon( "game_ended" );
|
||||||
|
|
||||||
level waittill( level.notifyTypes.integrationBootstrapInitialized );
|
|
||||||
|
|
||||||
level.commonFunctions.changeTeam = "ChangeTeam";
|
level.commonFunctions.changeTeam = "ChangeTeam";
|
||||||
level.commonFunctions.getTeamCounts = "GetTeamCounts";
|
level.commonFunctions.getTeamCounts = "GetTeamCounts";
|
||||||
level.commonFunctions.getMaxClients = "GetMaxClients";
|
level.commonFunctions.getMaxClients = "GetMaxClients";
|
||||||
@ -17,10 +15,7 @@ Setup()
|
|||||||
level.commonFunctions.getClientTeam = "GetClientTeam";
|
level.commonFunctions.getClientTeam = "GetClientTeam";
|
||||||
level.commonFunctions.getClientKillStreak = "GetClientKillStreak";
|
level.commonFunctions.getClientKillStreak = "GetClientKillStreak";
|
||||||
level.commonFunctions.backupRestoreClientKillStreakData = "BackupRestoreClientKillStreakData";
|
level.commonFunctions.backupRestoreClientKillStreakData = "BackupRestoreClientKillStreakData";
|
||||||
level.commonFunctions.getTotalShotsFired = "GetTotalShotsFired";
|
|
||||||
level.commonFunctions.waitTillAnyTimeout = "WaitTillAnyTimeout";
|
level.commonFunctions.waitTillAnyTimeout = "WaitTillAnyTimeout";
|
||||||
level.commonFunctions.isBot = "IsBot";
|
|
||||||
level.commonFunctions.getXuid = "GetXuid";
|
|
||||||
|
|
||||||
level.overrideMethods[level.commonFunctions.changeTeam] = scripts\_integration_base::NotImplementedFunction;
|
level.overrideMethods[level.commonFunctions.changeTeam] = scripts\_integration_base::NotImplementedFunction;
|
||||||
level.overrideMethods[level.commonFunctions.getTeamCounts] = scripts\_integration_base::NotImplementedFunction;
|
level.overrideMethods[level.commonFunctions.getTeamCounts] = scripts\_integration_base::NotImplementedFunction;
|
||||||
@ -30,52 +25,31 @@ Setup()
|
|||||||
level.overrideMethods[level.commonFunctions.getClientKillStreak] = scripts\_integration_base::NotImplementedFunction;
|
level.overrideMethods[level.commonFunctions.getClientKillStreak] = scripts\_integration_base::NotImplementedFunction;
|
||||||
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = scripts\_integration_base::NotImplementedFunction;
|
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = scripts\_integration_base::NotImplementedFunction;
|
||||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = scripts\_integration_base::NotImplementedFunction;
|
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = scripts\_integration_base::NotImplementedFunction;
|
||||||
level.overrideMethods[level.commonFunctions.getXuid] = scripts\_integration_base::NotImplementedFunction;
|
|
||||||
level.overrideMethods[level.commonFunctions.isBot] = scripts\_integration_base::NotImplementedFunction;
|
|
||||||
|
|
||||||
// these can be overridden per game if needed
|
// these can be overridden per game if needed
|
||||||
level.commonKeys.team1 = "allies";
|
level.commonKeys.team1 = "allies";
|
||||||
level.commonKeys.team2 = "axis";
|
level.commonKeys.team2 = "axis";
|
||||||
level.commonKeys.teamSpectator = "spectator";
|
level.commonKeys.teamSpectator = "spectator";
|
||||||
level.commonKeys.autoBalance = "sv_iw4madmin_autobalance";
|
|
||||||
|
|
||||||
level.eventTypes.connect = "connected";
|
level.eventTypes.connect = "connected";
|
||||||
level.eventTypes.disconnect = "disconnect";
|
level.eventTypes.disconnect = "disconnect";
|
||||||
level.eventTypes.joinTeam = "joined_team";
|
level.eventTypes.joinTeam = "joined_team";
|
||||||
level.eventTypes.joinSpec = "joined_spectators";
|
|
||||||
level.eventTypes.spawned = "spawned_player";
|
level.eventTypes.spawned = "spawned_player";
|
||||||
level.eventTypes.gameEnd = "game_ended";
|
level.eventTypes.gameEnd = "game_ended";
|
||||||
|
|
||||||
level.eventTypes.urlRequested = "UrlRequested";
|
|
||||||
level.eventTypes.urlRequestCompleted = "UrlRequestCompleted";
|
|
||||||
level.eventTypes.registerCommandRequested = "RegisterCommandRequested";
|
|
||||||
level.eventTypes.getCommandsRequested = "GetCommandsRequested";
|
|
||||||
level.eventTypes.getBusModeRequested = "GetBusModeRequested";
|
|
||||||
|
|
||||||
level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback;
|
|
||||||
level.eventCallbacks[level.eventTypes.getCommandsRequested] = ::OnCommandsRequestedCallback;
|
|
||||||
level.eventCallbacks[level.eventTypes.getBusModeRequested] = ::OnBusModeRequestedCallback;
|
|
||||||
|
|
||||||
level.iw4madminIntegrationDefaultPerformance = 200;
|
level.iw4madminIntegrationDefaultPerformance = 200;
|
||||||
level.notifyEntities = [];
|
|
||||||
level.customCommands = [];
|
|
||||||
|
|
||||||
level notify( level.notifyTypes.sharedFunctionsInitialized );
|
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
scripts\_integration_base::_SetDvarIfUninitialized( level.commonKeys.autoBalance, 0 );
|
if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 )
|
||||||
|
|
||||||
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread OnPlayerConnect();
|
level thread OnPlayerConnect();
|
||||||
}
|
|
||||||
|
|
||||||
_IsBot( player )
|
|
||||||
{
|
|
||||||
return [[level.overrideMethods[level.commonFunctions.isBot]]]( player );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPlayerConnect()
|
OnPlayerConnect()
|
||||||
@ -85,28 +59,7 @@ OnPlayerConnect()
|
|||||||
for ( ;; )
|
for ( ;; )
|
||||||
{
|
{
|
||||||
level waittill( level.eventTypes.connect, player );
|
level waittill( level.eventTypes.connect, player );
|
||||||
|
|
||||||
if ( _IsBot( player ) )
|
|
||||||
{
|
|
||||||
// we don't want to track bots
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !IsDefined( player.pers[level.clientDataKey] ) )
|
|
||||||
{
|
|
||||||
player.pers[level.clientDataKey] = spawnstruct();
|
|
||||||
}
|
|
||||||
|
|
||||||
player thread OnPlayerSpawned();
|
|
||||||
player thread OnPlayerJoinedTeam();
|
|
||||||
player thread OnPlayerJoinedSpectators();
|
|
||||||
player thread PlayerTrackingOnInterval();
|
|
||||||
|
|
||||||
if ( GetDvarInt( level.commonKeys.autoBalance ) != 1 || !IsDefined( [[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ![[level.overrideMethods[level.commonFunctions.getTeamBased]]]() )
|
if ( ![[level.overrideMethods[level.commonFunctions.getTeamBased]]]() )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -115,341 +68,13 @@ OnPlayerConnect()
|
|||||||
teamToJoin = player GetTeamToJoin();
|
teamToJoin = player GetTeamToJoin();
|
||||||
player [[level.overrideMethods[level.commonFunctions.changeTeam]]]( teamToJoin );
|
player [[level.overrideMethods[level.commonFunctions.changeTeam]]]( teamToJoin );
|
||||||
|
|
||||||
player thread OnPlayerFirstSpawn();
|
player thread OnClientFirstSpawn();
|
||||||
player thread OnPlayerDisconnect();
|
player thread OnClientJoinedTeam();
|
||||||
|
player thread OnClientDisconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerSpawnEvents()
|
OnClientDisconnect()
|
||||||
{
|
|
||||||
self endon( level.eventTypes.disconnect );
|
|
||||||
|
|
||||||
clientData = self.pers[level.clientDataKey];
|
|
||||||
|
|
||||||
// this gives IW4MAdmin some time to register the player before making the request;
|
|
||||||
// although probably not necessary some users might have a slow database or poll rate
|
|
||||||
wait ( 2 );
|
|
||||||
|
|
||||||
if ( IsDefined( clientData.state ) && clientData.state == "complete" )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self scripts\_integration_base::RequestClientBasicData();
|
|
||||||
|
|
||||||
self waittill( level.eventTypes.clientDataReceived, clientEvent );
|
|
||||||
|
|
||||||
if ( clientData.permissionLevel == "User" || clientData.permissionLevel == "Flagged" )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self IPrintLnBold( "Welcome, your level is ^5" + clientData.permissionLevel );
|
|
||||||
wait( 2.0 );
|
|
||||||
self IPrintLnBold( "You were last seen ^5" + clientData.lastConnection + " ago" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PlayerTrackingOnInterval()
|
|
||||||
{
|
|
||||||
self endon( level.eventTypes.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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBusModeRequestedCallback( event )
|
|
||||||
{
|
|
||||||
data = [];
|
|
||||||
data["mode"] = GetDvar( level.commonKeys.busMode );
|
|
||||||
data["directory"] = GetDvar( level.commonKeys.busDir );
|
|
||||||
data["inLocation"] = level.eventBus.inLocation;
|
|
||||||
data["outLocation"] = level.eventBus.outLocation;
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "Bus mode requested" );
|
|
||||||
|
|
||||||
busModeRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.getBusModeRequested, "", undefined, data );
|
|
||||||
scripts\_integration_base::QueueEvent( busModeRequest, level.eventTypes.getBusModeRequested, undefined );
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "Bus mode updated" );
|
|
||||||
|
|
||||||
if ( GetDvar( level.commonKeys.busMode ) == "file" && GetDvar( level.commonKeys.busDir ) != "" )
|
|
||||||
{
|
|
||||||
level.busMethods[level.commonFunctions.getInboundData] = level.overrideMethods[level.commonFunctions.getInboundData];
|
|
||||||
level.busMethods[level.commonFunctions.getOutboundData] = level.overrideMethods[level.commonFunctions.getOutboundData];
|
|
||||||
level.busMethods[level.commonFunctions.setInboundData] = level.overrideMethods[level.commonFunctions.setInboundData];
|
|
||||||
level.busMethods[level.commonFunctions.setOutboundData] = level.overrideMethods[level.commonFunctions.setOutboundData];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #region register script command
|
|
||||||
|
|
||||||
OnCommandsRequestedCallback( event )
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogDebug( "Get commands requested" );
|
|
||||||
thread SendCommands( event.data["name"] );
|
|
||||||
}
|
|
||||||
|
|
||||||
SendCommands( commandName )
|
|
||||||
{
|
|
||||||
level endon( level.eventTypes.gameEnd );
|
|
||||||
|
|
||||||
for ( i = 0; i < level.customCommands.size; i++ )
|
|
||||||
{
|
|
||||||
data = level.customCommands[i];
|
|
||||||
|
|
||||||
if ( IsDefined( commandName ) && commandName != data["name"] )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "Sending custom command " + ( i + 1 ) + "/" + level.customCommands.size + ": " + data["name"] );
|
|
||||||
commandRegisterRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.registerCommandRequested, "", undefined, data );
|
|
||||||
// not threading here as there might be a lot of commands to register
|
|
||||||
scripts\_integration_base::QueueEvent( commandRegisterRequest, level.eventTypes.registerCommandRequested, undefined );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterScriptCommandObject( command )
|
|
||||||
{
|
|
||||||
RegisterScriptCommand( command.eventKey, command.name, command.alias, command.description, command.minPermission, command.supportedGames, command.requiresTarget, command.handler );
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterScriptCommand( eventKey, name, alias, description, minPermission, supportedGames, requiresTarget, handler )
|
|
||||||
{
|
|
||||||
if ( !IsDefined( eventKey ) )
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogError( "eventKey must be provided for script command" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = [];
|
|
||||||
|
|
||||||
data["eventKey"] = eventKey;
|
|
||||||
|
|
||||||
if ( IsDefined( name ) )
|
|
||||||
{
|
|
||||||
data["name"] = name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogError( "name must be provided for script command" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( alias ) )
|
|
||||||
{
|
|
||||||
data["alias"] = alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( description ) )
|
|
||||||
{
|
|
||||||
data["description"] = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( minPermission ) )
|
|
||||||
{
|
|
||||||
data["minPermission"] = minPermission;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( supportedGames ) )
|
|
||||||
{
|
|
||||||
data["supportedGames"] = supportedGames;
|
|
||||||
}
|
|
||||||
|
|
||||||
data["requiresTarget"] = false;
|
|
||||||
|
|
||||||
if ( IsDefined( requiresTarget ) )
|
|
||||||
{
|
|
||||||
data["requiresTarget"] = requiresTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( handler ) )
|
|
||||||
{
|
|
||||||
level.clientCommandCallbacks[eventKey + "Execute"] = handler;
|
|
||||||
level.clientCommandRusAsTarget[eventKey + "Execute"] = data["requiresTarget"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogWarning( "handler not defined for script command " + name );
|
|
||||||
}
|
|
||||||
|
|
||||||
level.customCommands[level.customCommands.size] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #end region
|
|
||||||
|
|
||||||
// #region web requests
|
|
||||||
|
|
||||||
RequestUrlObject( request )
|
|
||||||
{
|
|
||||||
return RequestUrl( request.url, request.method, request.body, request.headers, request );
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestUrl( url, method, body, headers, webNotify )
|
|
||||||
{
|
|
||||||
if ( !IsDefined( webNotify ) )
|
|
||||||
{
|
|
||||||
webNotify = SpawnStruct();
|
|
||||||
webNotify.url = url;
|
|
||||||
webNotify.method = method;
|
|
||||||
webNotify.body = body;
|
|
||||||
webNotify.headers = headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
webNotify.index = GetNextNotifyEntity();
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "next notify index is " + webNotify.index );
|
|
||||||
level.notifyEntities[webNotify.index] = webNotify;
|
|
||||||
|
|
||||||
data = [];
|
|
||||||
data["url"] = webNotify.url;
|
|
||||||
data["entity"] = webNotify.index;
|
|
||||||
|
|
||||||
if ( IsDefined( method ) )
|
|
||||||
{
|
|
||||||
data["method"] = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( body ) )
|
|
||||||
{
|
|
||||||
data["body"] = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( headers ) )
|
|
||||||
{
|
|
||||||
headerString = "";
|
|
||||||
|
|
||||||
keys = GetArrayKeys( headers );
|
|
||||||
for ( i = 0; i < keys.size; i++ )
|
|
||||||
{
|
|
||||||
headerString = headerString + keys[i] + ":" + headers[keys[i]] + ",";
|
|
||||||
}
|
|
||||||
|
|
||||||
data["headers"] = headerString;
|
|
||||||
}
|
|
||||||
|
|
||||||
webNotifyEvent = scripts\_integration_base::BuildEventRequest( true, level.eventTypes.urlRequested, "", webNotify.index, data );
|
|
||||||
thread scripts\_integration_base::QueueEvent( webNotifyEvent, level.eventTypes.urlRequested, webNotify );
|
|
||||||
webNotify thread WaitForUrlRequestComplete();
|
|
||||||
|
|
||||||
return webNotify;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForUrlRequestComplete()
|
|
||||||
{
|
|
||||||
level endon( level.eventTypes.gameEnd );
|
|
||||||
|
|
||||||
timeoutResult = self [[level.overrideMethods[level.commonFunctions.waitTillAnyTimeout]]]( 30, level.eventTypes.urlRequestCompleted );
|
|
||||||
|
|
||||||
if ( timeoutResult == level.eventBus.timeoutKey )
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogWarning( "Request to " + self.url + " timed out" );
|
|
||||||
self notify ( level.eventTypes.urlRequestCompleted, "error" );
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "Request to " + self.url + " completed" );
|
|
||||||
|
|
||||||
level.notifyEntities[self.index] = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnUrlRequestCompletedCallback( event )
|
|
||||||
{
|
|
||||||
if ( !IsDefined( event ) || !IsDefined( event.data ) )
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogWarning( "Incomplete data for url request callback. [1]" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyEnt = event.data["entity"];
|
|
||||||
response = event.data["response"];
|
|
||||||
|
|
||||||
if ( !IsDefined( notifyEnt ) || !IsDefined( response ) )
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogWarning( "Incomplete data for url request callback. [2] " + scripts\_integration_base::CoerceUndefined( notifyEnt ) + " , " + scripts\_integration_base::CoerceUndefined( response ) );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
webNotify = level.notifyEntities[int( notifyEnt )];
|
|
||||||
|
|
||||||
if ( !IsDefined( webNotify.response ) )
|
|
||||||
{
|
|
||||||
webNotify.response = response;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
webNotify.response = webNotify.response + response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( int( event.data["remaining"] ) != 0 )
|
|
||||||
{
|
|
||||||
scripts\_integration_base::LogDebug( "Additional data available for url request " + notifyEnt + " (" + event.data["remaining"] + " chunks remaining)" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "Notifying " + notifyEnt + " that url request completed" );
|
|
||||||
webNotify notify( level.eventTypes.urlRequestCompleted, webNotify.response );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetNextNotifyEntity()
|
|
||||||
{
|
|
||||||
max = level.notifyEntities.size + 1;
|
|
||||||
|
|
||||||
for ( i = 0; i < max; i++ )
|
|
||||||
{
|
|
||||||
if ( !IsDefined( level.notifyEntities[i] ) )
|
|
||||||
{
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #end region
|
|
||||||
|
|
||||||
// #region team balance
|
|
||||||
|
|
||||||
OnPlayerDisconnect()
|
|
||||||
{
|
{
|
||||||
level endon( level.eventTypes.gameEnd );
|
level endon( level.eventTypes.gameEnd );
|
||||||
self endon( "disconnect_logic_end" );
|
self endon( "disconnect_logic_end" );
|
||||||
@ -464,7 +89,7 @@ OnPlayerDisconnect()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPlayerJoinedTeam()
|
OnClientJoinedTeam()
|
||||||
{
|
{
|
||||||
self endon( level.eventTypes.disconnect );
|
self endon( level.eventTypes.disconnect );
|
||||||
|
|
||||||
@ -472,14 +97,6 @@ OnPlayerJoinedTeam()
|
|||||||
{
|
{
|
||||||
self waittill( level.eventTypes.joinTeam );
|
self waittill( level.eventTypes.joinTeam );
|
||||||
|
|
||||||
wait( 0.25 );
|
|
||||||
LogPrint( GenerateJoinTeamString( false ) );
|
|
||||||
|
|
||||||
if ( GetDvarInt( level.commonKeys.autoBalance ) != 1 )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( IsDefined( self.wasAutoBalanced ) && self.wasAutoBalanced )
|
if ( IsDefined( self.wasAutoBalanced ) && self.wasAutoBalanced )
|
||||||
{
|
{
|
||||||
self.wasAutoBalanced = false;
|
self.wasAutoBalanced = false;
|
||||||
@ -492,7 +109,7 @@ OnPlayerJoinedTeam()
|
|||||||
if ( newTeam != level.commonKeys.team1 && newTeam != level.commonKeys.team2 )
|
if ( newTeam != level.commonKeys.team1 && newTeam != level.commonKeys.team2 )
|
||||||
{
|
{
|
||||||
OnTeamSizeChanged();
|
OnTeamSizeChanged();
|
||||||
scripts\_integration_base::LogDebug( "not force balancing " + self.name + " because they switched to spec" );
|
scripts\_integration_base::LogDebug( "not force balancing " + self.name + " because they switched to spec" );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,34 +124,12 @@ OnPlayerJoinedTeam()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPlayerSpawned()
|
OnClientFirstSpawn()
|
||||||
{
|
|
||||||
self endon( level.eventTypes.disconnect );
|
|
||||||
|
|
||||||
for ( ;; )
|
|
||||||
{
|
|
||||||
self waittill( level.eventTypes.spawned );
|
|
||||||
self thread PlayerSpawnEvents();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnPlayerJoinedSpectators()
|
|
||||||
{
|
|
||||||
self endon( level.eventTypes.disconnect );
|
|
||||||
|
|
||||||
for( ;; )
|
|
||||||
{
|
|
||||||
self waittill( level.eventTypes.joinSpec );
|
|
||||||
LogPrint( GenerateJoinTeamString( true ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnPlayerFirstSpawn()
|
|
||||||
{
|
{
|
||||||
self endon( level.eventTypes.disconnect );
|
self endon( level.eventTypes.disconnect );
|
||||||
timeoutResult = self [[level.overrideMethods[level.commonFunctions.waitTillAnyTimeout]]]( 30, level.eventTypes.spawned );
|
timeoutResult = self [[level.overrideMethods[level.commonFunctions.waitTillAnyTimeout]]]( 30, level.eventTypes.spawned );
|
||||||
|
|
||||||
if ( timeoutResult != level.eventBus.timeoutKey )
|
if ( timeoutResult != "timeout" )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -729,7 +324,7 @@ GetClosestPerformanceClientForTeam( sourceTeam, excluded )
|
|||||||
|
|
||||||
else if ( candidateValue < closest )
|
else if ( candidateValue < closest )
|
||||||
{
|
{
|
||||||
scripts\_integration_base::LogDebug( candidateValue + " is the new best value " );
|
scripts\_integration_base::LogDebug( candidateValue + " is the new best value ");
|
||||||
choice = players[i];
|
choice = players[i];
|
||||||
closest = candidateValue;
|
closest = candidateValue;
|
||||||
}
|
}
|
||||||
@ -854,36 +449,3 @@ GetClientPerformanceOrDefault()
|
|||||||
|
|
||||||
return performance;
|
return performance;
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
// #end region
|
|
||||||
|
@ -2,43 +2,90 @@
|
|||||||
|
|
||||||
Init()
|
Init()
|
||||||
{
|
{
|
||||||
|
level.eventBus.gamename = "T5";
|
||||||
|
|
||||||
thread Setup();
|
thread Setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
Setup()
|
Setup()
|
||||||
{
|
{
|
||||||
level endon( "game_ended" );
|
level endon( "game_ended" );
|
||||||
waittillframeend;
|
|
||||||
|
|
||||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||||
level.eventBus.gamename = "T5";
|
level waittill( "IntegrationBootstrapInitialized" );
|
||||||
|
|
||||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
scripts\mp\_integration_base::RegisterLogger( ::Log2Console );
|
||||||
|
|
||||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
|
||||||
|
|
||||||
RegisterClientCommands();
|
RegisterClientCommands();
|
||||||
|
|
||||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
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\mp\_integration_base::_IsBot( player ) )
|
||||||
|
{
|
||||||
|
// we don't want to track bots
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//player thread SetPersistentData();
|
||||||
|
player thread WaitForClientEvents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterClientCommands()
|
RegisterClientCommands()
|
||||||
{
|
{
|
||||||
scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
||||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForClientEvents()
|
||||||
|
{
|
||||||
|
self endon( "disconnect" );
|
||||||
|
|
||||||
|
// example of requesting a meta value
|
||||||
|
lastServerMetaKey = "LastServerPlayed";
|
||||||
|
// self scripts\mp\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||||
|
|
||||||
|
for ( ;; )
|
||||||
|
{
|
||||||
|
self waittill( level.eventTypes.localClientEvent, event );
|
||||||
|
|
||||||
|
scripts\mp\_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()
|
GetTotalShotsFired()
|
||||||
@ -46,12 +93,12 @@ GetTotalShotsFired()
|
|||||||
return maps\mp\gametypes\_persistence::statGet( "total_shots" );
|
return maps\mp\gametypes\_persistence::statGet( "total_shots" );
|
||||||
}
|
}
|
||||||
|
|
||||||
SetDvarIfUninitializedWrapper( dvar, value )
|
_SetDvarIfUninitialized(dvar, value)
|
||||||
{
|
{
|
||||||
maps\mp\_utility::set_dvar_if_unset( dvar, value );
|
maps\mp\_utility::set_dvar_if_unset(dvar, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitillNotifyOrTimeoutWrapper( msg, timer )
|
_waittill_notify_or_timeout( msg, timer )
|
||||||
{
|
{
|
||||||
self endon( msg );
|
self endon( msg );
|
||||||
wait( timer );
|
wait( timer );
|
||||||
@ -64,6 +111,7 @@ Log2Console( logLevel, message )
|
|||||||
|
|
||||||
God()
|
God()
|
||||||
{
|
{
|
||||||
|
|
||||||
if ( !IsDefined( self.godmode ) )
|
if ( !IsDefined( self.godmode ) )
|
||||||
{
|
{
|
||||||
self.godmode = false;
|
self.godmode = false;
|
||||||
@ -81,16 +129,137 @@ God()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IsBotWrapper( client )
|
//////////////////////////////////
|
||||||
|
// GUID helpers
|
||||||
|
/////////////////////////////////
|
||||||
|
|
||||||
|
/*SetPersistentData()
|
||||||
{
|
{
|
||||||
return client maps\mp\_utility::is_bot();
|
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\mp\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||||
|
scripts\mp\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
guid = self SplitGuid();
|
||||||
|
|
||||||
|
scripts\mp\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||||
|
|
||||||
|
self SetPlayerData( "bests", "none", guid["high"] );
|
||||||
|
self SetPlayerData( "awards", "none", guid["low"] );
|
||||||
}
|
}
|
||||||
|
|
||||||
GetXuidWrapper()
|
SplitGuid()
|
||||||
{
|
{
|
||||||
return self GetGuid();
|
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
|
// Command Implementations
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
@ -226,7 +395,7 @@ NoClipImpl( event, data )
|
|||||||
|
|
||||||
self IPrintLnBold( "NoClip enabled" );*/
|
self IPrintLnBold( "NoClip enabled" );*/
|
||||||
|
|
||||||
scripts\_integration_base::LogWarning( "NoClip is not supported on T5!" );
|
scripts\mp\_integration_base::LogWarning( "NoClip is not supported on T5!" );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,384 +0,0 @@
|
|||||||
#include common_scripts\utility;
|
|
||||||
|
|
||||||
Init()
|
|
||||||
{
|
|
||||||
thread Setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
Setup()
|
|
||||||
{
|
|
||||||
level endon( "end_game" );
|
|
||||||
waittillframeend;
|
|
||||||
|
|
||||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
|
||||||
level.eventBus.gamename = "T5";
|
|
||||||
level.eventTypes.gameEnd = "end_game";
|
|
||||||
|
|
||||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
|
||||||
|
|
||||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
|
||||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
|
||||||
level.overrideMethods[level.commonFunction.getPlayerFromClientNum] = ::_GetPlayerFromClientNum;
|
|
||||||
|
|
||||||
RegisterClientCommands();
|
|
||||||
|
|
||||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
|
||||||
}
|
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetTotalShotsFired()
|
|
||||||
{
|
|
||||||
return 0; //ZM has no shot tracking. TODO: add tracking function for event weapon_fired
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDvarIfUninitializedWrapper( dvar, value )
|
|
||||||
{
|
|
||||||
if ( GetDvar( dvar ) == "" )
|
|
||||||
{
|
|
||||||
SetDvar( dvar, value );
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetDvar( dvar );
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitillNotifyOrTimeoutWrapper( 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBotWrapper( client )
|
|
||||||
{
|
|
||||||
return ( IsDefined ( client.pers["isBot"] ) && client.pers["isBot"] != 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetXuidWrapper()
|
|
||||||
{
|
|
||||||
return self GetXUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// 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";
|
|
||||||
}
|
|
@ -1,395 +0,0 @@
|
|||||||
#include common_scripts\utility;
|
|
||||||
#include maps\mp\_utility;
|
|
||||||
|
|
||||||
Init()
|
|
||||||
{
|
|
||||||
thread Setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
Setup()
|
|
||||||
{
|
|
||||||
level endon( "game_ended" );
|
|
||||||
level endon( "end_game" );
|
|
||||||
waittillframeend;
|
|
||||||
|
|
||||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
|
||||||
level.eventBus.gamename = "T6";
|
|
||||||
|
|
||||||
if ( sessionmodeiszombiesgame() )
|
|
||||||
{
|
|
||||||
level.eventTypes.gameEnd = "end_game";
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
|
||||||
|
|
||||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
|
||||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
|
||||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
|
||||||
|
|
||||||
RegisterClientCommands();
|
|
||||||
|
|
||||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
|
||||||
}
|
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetTotalShotsFired()
|
|
||||||
{
|
|
||||||
return self.pers["total_shots"];
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDvarIfUninitializedWrapper( dvar, value )
|
|
||||||
{
|
|
||||||
maps\mp\_utility::set_dvar_if_unset( dvar, value );
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitillNotifyOrTimeoutWrapper( 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBotWrapper( client )
|
|
||||||
{
|
|
||||||
return client maps\mp\_utility::is_bot();
|
|
||||||
}
|
|
||||||
|
|
||||||
GetXuidWrapper()
|
|
||||||
{
|
|
||||||
return self GetXUID();
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
|
||||||
{
|
|
||||||
return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 );
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// 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 )
|
|
||||||
{
|
|
||||||
self thread oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 );
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////
|
|
||||||
// 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 wagermatches 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" );
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
/*********************************************************************************
|
|
||||||
* DISCLAIMER: *
|
|
||||||
* *
|
|
||||||
* This script is optional and not required for *
|
|
||||||
* standard functionality. To use this script, a third-party *
|
|
||||||
* plugin named "t6-gsc-utils" must be installed on the *
|
|
||||||
* game server in the "*\storage\t6\plugins" folder *
|
|
||||||
* *
|
|
||||||
* The "t6-gsc-utils" plugin can be obtained from the GitHub *
|
|
||||||
* repository at: *
|
|
||||||
* https://github.com/fedddddd/t6-gsc-utils *
|
|
||||||
* *
|
|
||||||
* Please make sure to install the plugin before running this *
|
|
||||||
* script. *
|
|
||||||
*********************************************************************************/
|
|
||||||
|
|
||||||
/*********************************************************************************
|
|
||||||
* FUNCTIONALITY: *
|
|
||||||
* *
|
|
||||||
* This script extends the game interface to support the "file" *
|
|
||||||
* bus mode for Plutonium T6, which allows the game server and IW4M-Admin *
|
|
||||||
* to communicate via files, rather than over rcon using *
|
|
||||||
* dvars. *
|
|
||||||
* *
|
|
||||||
* By enabling the "file" bus mode, IW4M-Admin can send *
|
|
||||||
* commands and receive responses from the game server by *
|
|
||||||
* reading and writing to specific files. This provides a *
|
|
||||||
* flexible and efficient communication channel. *
|
|
||||||
* *
|
|
||||||
* Make sure to configure the server to use the "file" bus *
|
|
||||||
* mode and set the appropriate file path to *
|
|
||||||
* establish the communication between IW4M-Admin and the *
|
|
||||||
* game server. *
|
|
||||||
* *
|
|
||||||
* The wiki page for the setup of the game interface, and the bus mode *
|
|
||||||
* can be found on GitHub at: *
|
|
||||||
* https://github.com/RaidMax/IW4M-Admin/wiki/GameInterface#configuring-bus-mode *
|
|
||||||
*********************************************************************************/
|
|
||||||
|
|
||||||
Init()
|
|
||||||
{
|
|
||||||
thread Setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
Setup()
|
|
||||||
{
|
|
||||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
|
||||||
level.overrideMethods[level.commonFunctions.getInboundData] = ::GetInboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.getOutboundData] = ::GetOutboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.setInboundData] = ::SetInboundData;
|
|
||||||
level.overrideMethods[level.commonFunctions.setOutboundData] = ::SetOutboundData;
|
|
||||||
scripts\_integration_base::_SetDvarIfUninitialized( level.commonKeys.busdir, GetDvar( "fs_homepath" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetInboundData( location )
|
|
||||||
{
|
|
||||||
return readFile( location );
|
|
||||||
}
|
|
||||||
|
|
||||||
GetOutboundData( location )
|
|
||||||
{
|
|
||||||
return readFile( location );
|
|
||||||
}
|
|
||||||
|
|
||||||
SetInboundData( location, data )
|
|
||||||
{
|
|
||||||
writeFile( location, data );
|
|
||||||
}
|
|
||||||
|
|
||||||
SetOutboundData( location, data )
|
|
||||||
{
|
|
||||||
writeFile( location, data );
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
Init()
|
|
||||||
{
|
|
||||||
level.startmessagedefaultduration = 2;
|
|
||||||
level.regulargamemessages = spawnstruct();
|
|
||||||
level.regulargamemessages.waittime = 6;
|
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file contains reusable preprocessor directives meant to be used on
|
|
||||||
* Plutonium & AlterWare clients that are up to date with the latest version.
|
|
||||||
* Older versions of Plutonium or other clients do not have support for loading
|
|
||||||
* or parsing "gsh" files.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Turn off assertions by removing the following define
|
|
||||||
* gsc-tool will only emit assertions if developer_script dvar is set to 1
|
|
||||||
* In short, you should not need to remove this define. Just turn them off
|
|
||||||
* by using the dvar
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define _INTEGRATION_DEBUG
|
|
||||||
|
|
||||||
#ifdef _INTEGRATION_DEBUG
|
|
||||||
|
|
||||||
#define _VERIFY( cond, msg ) \
|
|
||||||
assertEx( cond, msg )
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
// This works as an "empty" define here with gsc-tool
|
|
||||||
#define _VERIFY( cond, msg )
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This function is meant to be used inside "client commands"
|
|
||||||
// If the client is not alive it shall return an error message
|
|
||||||
#define _IS_ALIVE( ent ) \
|
|
||||||
_VERIFY( ent, "player entity is not defined" ); \
|
|
||||||
if ( !IsAlive( ent ) ) \
|
|
||||||
{ \
|
|
||||||
return ent.name + "^7 is not alive"; \
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function should be used to verify if a player entity is defined
|
|
||||||
#define _VERIFY_PLAYER_ENT( ent ) \
|
|
||||||
_VERIFY( ent, "player entity is not defined" )
|
|
@ -1,88 +0,0 @@
|
|||||||
Init()
|
|
||||||
{
|
|
||||||
// this gives the game interface time to setup
|
|
||||||
waittillframeend;
|
|
||||||
thread ModuleSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ModuleSetup()
|
|
||||||
{
|
|
||||||
// waiting until the game specific functions are ready
|
|
||||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
|
||||||
|
|
||||||
RegisterCustomCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterCustomCommands()
|
|
||||||
{
|
|
||||||
command = SpawnStruct();
|
|
||||||
|
|
||||||
// unique key for each command (how iw4madmin identifies the command)
|
|
||||||
command.eventKey = "PrintLineCommand";
|
|
||||||
|
|
||||||
// name of the command (cannot conflict with existing command names)
|
|
||||||
command.name = "println";
|
|
||||||
|
|
||||||
// short version of the command (cannot conflcit with existing command aliases)
|
|
||||||
command.alias = "pl";
|
|
||||||
|
|
||||||
// description of what the command does
|
|
||||||
command.description = "prints line to game";
|
|
||||||
|
|
||||||
// minimum permision required to execute
|
|
||||||
// valid values: User, Trusted, Moderator, Administrator, SeniorAdmin, Owner
|
|
||||||
command.minPermission = "Trusted";
|
|
||||||
|
|
||||||
// games the command is supported on
|
|
||||||
// separate with comma or don't define for all
|
|
||||||
// valid values: IW3, IW4, IW5, IW6, T4, T5, T6, T7, SHG1, CSGO, H1
|
|
||||||
command.supportedGames = "IW4,IW5,T5,T6";
|
|
||||||
|
|
||||||
// indicates if a target player must be provided to execvute on
|
|
||||||
command.requiresTarget = false;
|
|
||||||
|
|
||||||
// code to run when the command is executed
|
|
||||||
command.handler = ::PrintLnCommandCallback;
|
|
||||||
|
|
||||||
// register the command with integration to be send to iw4madmin
|
|
||||||
scripts\_integration_shared::RegisterScriptCommandObject( command );
|
|
||||||
|
|
||||||
// you can also register via parameters
|
|
||||||
scripts\_integration_shared::RegisterScriptCommand( "AffirmationCommand", "affirm", "af", "provide affirmations", "User", undefined, false, ::AffirmationCommandCallback );
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintLnCommandCallback( event )
|
|
||||||
{
|
|
||||||
if ( IsDefined( event.data["args"] ) )
|
|
||||||
{
|
|
||||||
IPrintLnBold( event.data["args"] );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts\_integration_base::LogDebug( "No data was provided for PrintLnCallback" );
|
|
||||||
}
|
|
||||||
|
|
||||||
AffirmationCommandCallback( event, _ )
|
|
||||||
{
|
|
||||||
level endon( level.eventTypes.gameEnd );
|
|
||||||
|
|
||||||
request = SpawnStruct();
|
|
||||||
request.url = "https://www.affirmations.dev";
|
|
||||||
request.method = "GET";
|
|
||||||
|
|
||||||
// If making a post request you can also provide more data
|
|
||||||
// request.body = "Body of the post message";
|
|
||||||
// request.headers = [];
|
|
||||||
// request.headers["Authorization"] = "api-key";
|
|
||||||
|
|
||||||
scripts\_integration_shared::RequestUrlObject( request );
|
|
||||||
request waittill( level.eventTypes.urlRequestCompleted, response );
|
|
||||||
|
|
||||||
// horrible json parsing.. but it's just an example
|
|
||||||
parsedResponse = strtok( response, "\"" );
|
|
||||||
|
|
||||||
if ( IsPlayer( self ) )
|
|
||||||
{
|
|
||||||
self IPrintLnBold ( "^5" + parsedResponse[parsedResponse.size - 2] );
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,14 @@
|
|||||||
Allows integration of IW4M-Admin to GSC, mainly used for special commands that need to use GSC in order to work.
|
Allows integration of IW4M-Admin to GSC, mainly used for special commands that need to use GSC in order to work.
|
||||||
But can also be used to read / write metadata from / to a profile and to get the player permission level.
|
But can also be used to read / write metadata from / to a profile and to get the player permission level.
|
||||||
|
|
||||||
## Installation Guide
|
|
||||||
|
## Installation Plutonium IW5
|
||||||
|
|
||||||
|
|
||||||
The documentation can be found here: [GameInterface](https://github.com/RaidMax/IW4M-Admin/wiki/GameInterface)
|
Move `_integration.gsc` to `%localappdata%\Plutonium\storage\iw5\scripts\`
|
||||||
|
|
||||||
|
|
||||||
|
## Installation IW4x
|
||||||
|
|
||||||
|
|
||||||
|
Move `_integration.gsc` to `IW4x/userraw/scripts`, `IW4x` being the root folder of your game server.
|
@ -1,21 +1,14 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
ECHO "Pluto IW5"
|
ECHO "Pluto IW5"
|
||||||
xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts"
|
xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
||||||
xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts"
|
xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
||||||
xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts"
|
|
||||||
xcopy /y .\GameInterface\_integration_utility.gsh "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts"
|
|
||||||
xcopy /y .\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
xcopy /y .\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
||||||
|
|
||||||
ECHO "Pluto T5"
|
ECHO "Pluto T5"
|
||||||
xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts"
|
xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp"
|
||||||
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_t5.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp"
|
||||||
xcopy /y .\GameInterface\_integration_t5zm.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\sp\zom"
|
|
||||||
|
|
||||||
ECHO "Pluto T6"
|
ECHO "Pluto T6"
|
||||||
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"
|
|
||||||
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 "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp"
|
||||||
|
xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc.src "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp"
|
||||||
|
@ -52,8 +52,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ParserH1MOD.js = Plugins\ScriptPlugins\ParserH1MOD.js
|
Plugins\ScriptPlugins\ParserH1MOD.js = Plugins\ScriptPlugins\ParserH1MOD.js
|
||||||
Plugins\ScriptPlugins\ParserPlutoniumT5.js = Plugins\ScriptPlugins\ParserPlutoniumT5.js
|
Plugins\ScriptPlugins\ParserPlutoniumT5.js = Plugins\ScriptPlugins\ParserPlutoniumT5.js
|
||||||
Plugins\ScriptPlugins\ServerBanner.js = Plugins\ScriptPlugins\ServerBanner.js
|
Plugins\ScriptPlugins\ServerBanner.js = Plugins\ScriptPlugins\ServerBanner.js
|
||||||
Plugins\ScriptPlugins\ParserBOIII.js = Plugins\ScriptPlugins\ParserBOIII.js
|
|
||||||
Plugins\ScriptPlugins\ParserL4D2SM.js = Plugins\ScriptPlugins\ParserL4D2SM.js
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
||||||
@ -73,9 +71,6 @@ EndProject
|
|||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mute", "Plugins\Mute\Mute.csproj", "{259824F3-D860-4233-91D6-FF73D4DD8B18}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mute", "Plugins\Mute\Mute.csproj", "{259824F3-D860-4233-91D6-FF73D4DD8B18}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameFiles", "GameFiles", "{6CBF412C-EFEE-45F7-80FD-AC402C22CDB9}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameFiles", "GameFiles", "{6CBF412C-EFEE-45F7-80FD-AC402C22CDB9}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
GameFiles\deploy.bat = GameFiles\deploy.bat
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterface", "{5C2BE2A8-EA1D-424F-88E1-7FC33EEC2E55}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterface", "{5C2BE2A8-EA1D-424F-88E1-7FC33EEC2E55}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
@ -84,11 +79,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterf
|
|||||||
GameFiles\GameInterface\_integration_iw5.gsc = GameFiles\GameInterface\_integration_iw5.gsc
|
GameFiles\GameInterface\_integration_iw5.gsc = GameFiles\GameInterface\_integration_iw5.gsc
|
||||||
GameFiles\GameInterface\_integration_shared.gsc = GameFiles\GameInterface\_integration_shared.gsc
|
GameFiles\GameInterface\_integration_shared.gsc = GameFiles\GameInterface\_integration_shared.gsc
|
||||||
GameFiles\GameInterface\_integration_t5.gsc = GameFiles\GameInterface\_integration_t5.gsc
|
GameFiles\GameInterface\_integration_t5.gsc = GameFiles\GameInterface\_integration_t5.gsc
|
||||||
GameFiles\GameInterface\_integration_t5zm.gsc = GameFiles\GameInterface\_integration_t5zm.gsc
|
|
||||||
GameFiles\GameInterface\_integration_t6.gsc = GameFiles\GameInterface\_integration_t6.gsc
|
|
||||||
GameFiles\GameInterface\_integration_t6zm_helper.gsc = GameFiles\GameInterface\_integration_t6zm_helper.gsc
|
|
||||||
GameFiles\GameInterface\example_module.gsc = GameFiles\GameInterface\example_module.gsc
|
|
||||||
GameFiles\GameInterface\_integration_t6_file_bus.gsc = GameFiles\GameInterface\_integration_t6_file_bus.gsc
|
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntiCheat", "AntiCheat", "{AB83BAC0-C539-424A-BF00-78487C10753C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntiCheat", "AntiCheat", "{AB83BAC0-C539-424A-BF00-78487C10753C}"
|
||||||
|
@ -8,7 +8,6 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Integrations.Cod.SecureRcon;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Serilog.Context;
|
using Serilog.Context;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
@ -25,7 +24,6 @@ namespace Integrations.Cod
|
|||||||
public class CodRConConnection : IRConConnection
|
public class CodRConConnection : IRConConnection
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new();
|
private static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new();
|
||||||
private const string PkPattern = "-----BEGIN PRIVATE KEY-----";
|
|
||||||
public IPEndPoint Endpoint { get; }
|
public IPEndPoint Endpoint { get; }
|
||||||
public string RConPassword { get; }
|
public string RConPassword { get; }
|
||||||
|
|
||||||
@ -127,7 +125,7 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Waiting for flood protect did not complete before timeout {Count}",
|
_log.LogDebug("Waiting for flood protect did not complete before timeout timeout {Count}",
|
||||||
connectionState.OnComplete.CurrentCount);
|
connectionState.OnComplete.CurrentCount);
|
||||||
throw new RConException("Timed out waiting for flood protect to expire", true);
|
throw new RConException("Timed out waiting for flood protect to expire", true);
|
||||||
}
|
}
|
||||||
@ -154,28 +152,32 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
case StaticHelpers.QueryType.GET_DVAR:
|
case StaticHelpers.QueryType.GET_DVAR:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = BuildPayload(_config.CommandPrefixes.RConGetDvar, convertedRConPassword,
|
payload = string
|
||||||
convertedParameters);
|
.Format(_config.CommandPrefixes.RConGetDvar, convertedRConPassword,
|
||||||
|
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.SET_DVAR:
|
case StaticHelpers.QueryType.SET_DVAR:
|
||||||
payload = BuildPayload(_config.CommandPrefixes.RConSetDvar, convertedRConPassword,
|
payload = string
|
||||||
convertedParameters);
|
.Format(_config.CommandPrefixes.RConSetDvar, convertedRConPassword,
|
||||||
|
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.COMMAND:
|
case StaticHelpers.QueryType.COMMAND:
|
||||||
payload = BuildPayload(_config.CommandPrefixes.RConCommand, convertedRConPassword,
|
payload = string
|
||||||
convertedParameters);
|
.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword,
|
||||||
|
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.GET_STATUS:
|
case StaticHelpers.QueryType.GET_STATUS:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(Helpers.SafeConversion).ToArray();
|
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.GET_INFO:
|
case StaticHelpers.QueryType.GET_INFO:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(Helpers.SafeConversion).ToArray();
|
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray();
|
||||||
break;
|
break;
|
||||||
case StaticHelpers.QueryType.COMMAND_STATUS:
|
case StaticHelpers.QueryType.COMMAND_STATUS:
|
||||||
waitForResponse = true;
|
waitForResponse = true;
|
||||||
payload = BuildPayload(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status");
|
payload = string.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0")
|
||||||
|
.Select(Convert.ToByte).ToArray();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,27 +322,6 @@ namespace Integrations.Cod
|
|||||||
return validatedResponse;
|
return validatedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] BuildPayload(string queryTemplate, string convertedRConPassword, string convertedParameters)
|
|
||||||
{
|
|
||||||
byte[] payload;
|
|
||||||
if (!RConPassword.StartsWith(PkPattern))
|
|
||||||
{
|
|
||||||
payload = string
|
|
||||||
.Format(queryTemplate, convertedRConPassword,
|
|
||||||
convertedParameters + '\0').Select(Helpers.SafeConversion).ToArray();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var textContent = string
|
|
||||||
.Format(queryTemplate, "", convertedParameters)
|
|
||||||
.Replace("rcon", "rconSafe ")
|
|
||||||
.Replace(" ", "").Split(" ");
|
|
||||||
payload = Helpers.BuildSafeRconPayload(textContent[0], textContent[1], RConPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<byte[][]> SendPayloadAsync(Socket rconSocket, byte[] payload, bool waitForResponse,
|
private async Task<byte[][]> SendPayloadAsync(Socket rconSocket, byte[] payload, bool waitForResponse,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
@ -377,7 +358,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
await ReceiveAndStoreSocketData(rconSocket, token, connectionState);
|
await ReceiveAndStoreSocketData(rconSocket, token, connectionState);
|
||||||
|
|
||||||
if (_parser.GameName is Server.Game.IW3 or Server.Game.T4)
|
if (_parser.GameName == Server.Game.IW3)
|
||||||
{
|
{
|
||||||
await Task.Delay(100, token); // CoD4x shenanigans
|
await Task.Delay(100, token); // CoD4x shenanigans
|
||||||
}
|
}
|
||||||
@ -394,18 +375,8 @@ namespace Integrations.Cod
|
|||||||
private async Task ReceiveAndStoreSocketData(Socket rconSocket, CancellationToken token,
|
private async Task ReceiveAndStoreSocketData(Socket rconSocket, CancellationToken token,
|
||||||
ConnectionState connectionState)
|
ConnectionState connectionState)
|
||||||
{
|
{
|
||||||
SocketReceiveFromResult result;
|
var result = await rconSocket.ReceiveFromAsync(connectionState.ReceiveBuffer,
|
||||||
try
|
SocketFlags.None, Endpoint, token);
|
||||||
{
|
|
||||||
result = await rconSocket.ReceiveFromAsync(connectionState.ReceiveBuffer,
|
|
||||||
SocketFlags.None, Endpoint, token);
|
|
||||||
}
|
|
||||||
// windows quirk that occurs when remote server returns ICMP port unreachable
|
|
||||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
|
|
||||||
{
|
|
||||||
await Task.Delay(Timeout.Infinite, token);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.ReceivedBytes == 0)
|
if (result.ReceivedBytes == 0)
|
||||||
{
|
{
|
||||||
@ -487,7 +458,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
return string.Join("", splitStatusStrings);
|
return string.Join("", splitStatusStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recombines multiple game messages into one
|
/// Recombines multiple game messages into one
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -520,7 +491,7 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
return connectionState.ReceivedBytes.ToArray();
|
return connectionState.ReceivedBytes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,4 @@
|
|||||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.26" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using ProtoBuf;
|
|
||||||
|
|
||||||
namespace Integrations.Cod.SecureRcon;
|
|
||||||
|
|
||||||
public static class Helpers
|
|
||||||
{
|
|
||||||
private static byte[] ToSerializedMessage(this SecureCommand command)
|
|
||||||
{
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
Serializer.Serialize(ms, command);
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] SignData(byte[] data, string privateKey)
|
|
||||||
{
|
|
||||||
using var rsa = new RSACryptoServiceProvider();
|
|
||||||
rsa.ImportFromPem(privateKey);
|
|
||||||
var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa);
|
|
||||||
rsaFormatter.SetHashAlgorithm("SHA512");
|
|
||||||
var hash = SHA512.Create();
|
|
||||||
var hashedData = hash.ComputeHash(data);
|
|
||||||
var signature = rsaFormatter.CreateSignature(hashedData);
|
|
||||||
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte SafeConversion(char c)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Convert.ToByte(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return (byte)'.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] BuildSafeRconPayload(string prefix, string command, string signingKey)
|
|
||||||
{
|
|
||||||
var message = command.Select(SafeConversion).ToArray();
|
|
||||||
var header = (prefix + "\n").Select(SafeConversion).ToArray();
|
|
||||||
|
|
||||||
var secureCommand = new SecureCommand
|
|
||||||
{
|
|
||||||
SecMessage = message,
|
|
||||||
Signature = SignData(message, signingKey)
|
|
||||||
};
|
|
||||||
|
|
||||||
return header.Concat(secureCommand.ToSerializedMessage()).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
using ProtoBuf;
|
|
||||||
|
|
||||||
namespace Integrations.Cod.SecureRcon;
|
|
||||||
|
|
||||||
[ProtoContract]
|
|
||||||
public class SecureCommand
|
|
||||||
{
|
|
||||||
[ProtoMember(1)]
|
|
||||||
public byte[] SecMessage { get; set; }
|
|
||||||
|
|
||||||
[ProtoMember(2)]
|
|
||||||
public byte[] Signature { get; set; }
|
|
||||||
}
|
|
@ -104,7 +104,7 @@ namespace Integrations.Source
|
|||||||
}
|
}
|
||||||
|
|
||||||
var split = response.TrimEnd('\n').Split('\n');
|
var split = response.TrimEnd('\n').Split('\n');
|
||||||
return split.Take(Math.Max(1, split.Length - 1)).ToArray();
|
return split.Take(split.Length - 1).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
|
@ -48,7 +48,7 @@ public class Plugin : IPluginV2
|
|||||||
|
|
||||||
private Task GameEventSubscriptionsOnClientMessaged(ClientMessageEvent clientEvent, CancellationToken token)
|
private Task GameEventSubscriptionsOnClientMessaged(ClientMessageEvent clientEvent, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!(_configuration?.EnableProfanityDeterment ?? false))
|
if (!_configuration?.EnableProfanityDeterment ?? false)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const init = (registerEventCallback, serviceResolver, configWrapper) => {
|
const init = (registerEventCallback, serviceResolver, _) => {
|
||||||
plugin.onLoad(serviceResolver, configWrapper);
|
plugin.onLoad(serviceResolver);
|
||||||
|
|
||||||
registerEventCallback('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => {
|
registerEventCallback('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => {
|
||||||
plugin.onPenalty(penaltyEvent);
|
plugin.onPenalty(penaltyEvent);
|
||||||
@ -10,20 +10,21 @@ const init = (registerEventCallback, serviceResolver, configWrapper) => {
|
|||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: '2.1',
|
version: '2.0',
|
||||||
name: 'Action on Report',
|
name: 'Action on Report',
|
||||||
config: {
|
enabled: false, // indicates if the plugin is enabled
|
||||||
enabled: false, // indicates if the plugin is enabled
|
reportAction: 'TempBan', // can be TempBan or Ban
|
||||||
reportAction: 'TempBan', // can be TempBan or Ban
|
maxReportCount: 5, // how many reports before action is taken
|
||||||
maxReportCount: 5, // how many reports before action is taken
|
tempBanDurationMinutes: 60, // how long to temporarily ban the player
|
||||||
tempBanDurationMinutes: 60 // how long to temporarily ban the player
|
penaltyType: {
|
||||||
|
'report': 0
|
||||||
},
|
},
|
||||||
|
|
||||||
onPenalty: function (penaltyEvent) {
|
onPenalty: function (penaltyEvent) {
|
||||||
if (!this.config.enabled || penaltyEvent.penalty.type !== 'Report') {
|
if (!this.enabled || penaltyEvent.penalty.type !== this.penaltyType['report']) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!penaltyEvent.client.isIngame || (penaltyEvent.client.level !== 'User' && penaltyEvent.client.level !== 'Flagged')) {
|
if (!penaltyEvent.client.isIngame || (penaltyEvent.client.level !== 'User' && penaltyEvent.client.level !== 'Flagged')) {
|
||||||
this.logger.logInformation(`Ignoring report for client (id) ${penaltyEvent.client.clientId} because they are privileged or not in-game`);
|
this.logger.logInformation(`Ignoring report for client (id) ${penaltyEvent.client.clientId} because they are privileged or not in-game`);
|
||||||
return;
|
return;
|
||||||
@ -33,11 +34,11 @@ const plugin = {
|
|||||||
reportCount++;
|
reportCount++;
|
||||||
this.reportCounts[penaltyEvent.client.networkId] = reportCount;
|
this.reportCounts[penaltyEvent.client.networkId] = reportCount;
|
||||||
|
|
||||||
if (reportCount >= this.config.maxReportCount) {
|
if (reportCount >= this.maxReportCount) {
|
||||||
switch (this.config.reportAction) {
|
switch (this.reportAction) {
|
||||||
case 'TempBan':
|
case 'TempBan':
|
||||||
this.logger.logInformation(`TempBanning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`);
|
this.logger.logInformation(`TempBanning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`);
|
||||||
penaltyEvent.client.tempBan(this.translations['PLUGINS_REPORT_ACTION'], System.TimeSpan.FromMinutes(this.config.tempBanDurationMinutes), penaltyEvent.Client.CurrentServer.asConsoleClient());
|
penaltyEvent.client.tempBan(this.translations['PLUGINS_REPORT_ACTION'], System.TimeSpan.FromMinutes(this.tempBanDurationMinutes), penaltyEvent.Client.CurrentServer.asConsoleClient());
|
||||||
break;
|
break;
|
||||||
case 'Ban':
|
case 'Ban':
|
||||||
this.logger.logInformation(`Banning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`);
|
this.logger.logInformation(`Banning client (id) ${penaltyEvent.client.clientId} because they received ${reportCount} reports`);
|
||||||
@ -47,25 +48,10 @@ const plugin = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad: function (serviceResolver, configWrapper) {
|
onLoad: function (serviceResolver) {
|
||||||
this.translations = serviceResolver.resolveService('ITranslationLookup');
|
this.translations = serviceResolver.resolveService('ITranslationLookup');
|
||||||
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
||||||
this.configWrapper = configWrapper;
|
this.logger.logInformation('ActionOnReport {version} by {author} loaded. Enabled={enabled}', this.version, this.author, this.enabled);
|
||||||
|
|
||||||
const storedConfig = this.configWrapper.getValue('config', newConfig => {
|
|
||||||
if (newConfig) {
|
|
||||||
plugin.logger.logInformation('ActionOnReport config reloaded. Enabled={Enabled}', newConfig.enabled);
|
|
||||||
plugin.config = newConfig;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (storedConfig != null) {
|
|
||||||
this.config = storedConfig
|
|
||||||
} else {
|
|
||||||
this.configWrapper.setValue('config', this.config);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.logInformation('ActionOnReport {version} by {author} loaded. Enabled={Enabled}', this.version, this.author, this.config.enabled);
|
|
||||||
this.reportCounts = {};
|
this.reportCounts = {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -7,16 +7,15 @@ const init = (registerNotify, serviceResolver, config) => {
|
|||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
author: 'Amos, RaidMax',
|
author: 'Amos, RaidMax',
|
||||||
version: '2.1',
|
version: '2.0',
|
||||||
name: 'Broadcast Bans',
|
name: 'Broadcast Bans',
|
||||||
config: null,
|
config: null,
|
||||||
logger: null,
|
logger: null,
|
||||||
translations: null,
|
translations: null,
|
||||||
manager: null,
|
manager: null,
|
||||||
enableBroadcastBans: false,
|
|
||||||
|
|
||||||
onClientPenalty: function (penaltyEvent) {
|
onClientPenalty: function (penaltyEvent) {
|
||||||
if (!this.enableBroadcastBans || penaltyEvent.penalty.type !== 'Ban') {
|
if (!this.enableBroadcastBans || penaltyEvent.penalty.type !== 5) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,10 +43,7 @@ const plugin = {
|
|||||||
onLoad: function (serviceResolver, config) {
|
onLoad: function (serviceResolver, config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.config.setName(this.name);
|
this.config.setName(this.name);
|
||||||
this.enableBroadcastBans = this.config.getValue('EnableBroadcastBans', newConfig => {
|
this.enableBroadcastBans = this.config.getValue('EnableBroadcastBans');
|
||||||
plugin.logger.logInformation('{Name} config reloaded. Enabled={Enabled}', plugin.name, newConfig);
|
|
||||||
plugin.enableBroadcastBans = newConfig;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.manager = serviceResolver.resolveService('IManager');
|
this.manager = serviceResolver.resolveService('IManager');
|
||||||
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
||||||
@ -58,7 +54,7 @@ const plugin = {
|
|||||||
this.config.setValue('EnableBroadcastBans', this.enableBroadcastBans);
|
this.config.setValue('EnableBroadcastBans', this.enableBroadcastBans);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={Enabled}', this.name, this.version,
|
this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={enabled}', this.name, this.version,
|
||||||
this.author, this.enableBroadcastBans);
|
this.author, this.enableBroadcastBans);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2,61 +2,34 @@
|
|||||||
const inDvar = 'sv_iw4madmin_in';
|
const inDvar = 'sv_iw4madmin_in';
|
||||||
const outDvar = 'sv_iw4madmin_out';
|
const outDvar = 'sv_iw4madmin_out';
|
||||||
const integrationEnabledDvar = 'sv_iw4madmin_integration_enabled';
|
const integrationEnabledDvar = 'sv_iw4madmin_integration_enabled';
|
||||||
const groupSeparatorChar = '\x1d';
|
const pollingRate = 300;
|
||||||
const recordSeparatorChar = '\x1e';
|
|
||||||
const unitSeparatorChar = '\x1f';
|
|
||||||
|
|
||||||
let busFileIn = '';
|
const init = (registerNotify, serviceResolver, config) => {
|
||||||
let busFileOut = '';
|
|
||||||
let busMode = 'rcon';
|
|
||||||
let busDir = '';
|
|
||||||
|
|
||||||
const init = (registerNotify, serviceResolver, config, scriptHelper) => {
|
|
||||||
registerNotify('IManagementEventSubscriptions.ClientStateInitialized', (clientEvent, _) => plugin.onClientEnteredMatch(clientEvent));
|
registerNotify('IManagementEventSubscriptions.ClientStateInitialized', (clientEvent, _) => plugin.onClientEnteredMatch(clientEvent));
|
||||||
registerNotify('IGameServerEventSubscriptions.ServerValueReceived', (serverValueEvent, _) => plugin.onServerValueReceived(serverValueEvent));
|
registerNotify('IGameServerEventSubscriptions.ServerValueReceived', (serverValueEvent, _) => plugin.onServerValueReceived(serverValueEvent));
|
||||||
registerNotify('IGameServerEventSubscriptions.ServerValueSetCompleted', (serverValueEvent, _) => plugin.onServerValueSetCompleted(serverValueEvent));
|
registerNotify('IGameServerEventSubscriptions.ServerValueSetCompleted', (serverValueEvent, _) => plugin.onServerValueSetCompleted(serverValueEvent));
|
||||||
registerNotify('IGameServerEventSubscriptions.MonitoringStarted', (monitorStartEvent, _) => plugin.onServerMonitoringStart(monitorStartEvent));
|
registerNotify('IGameServerEventSubscriptions.MonitoringStarted', (monitorStartEvent, _) => plugin.onServerMonitoringStart(monitorStartEvent));
|
||||||
registerNotify('IGameEventSubscriptions.MatchStarted', (matchStartEvent, _) => plugin.onMatchStart(matchStartEvent));
|
|
||||||
registerNotify('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => plugin.onPenalty(penaltyEvent));
|
registerNotify('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => plugin.onPenalty(penaltyEvent));
|
||||||
|
|
||||||
plugin.onLoad(serviceResolver, config, scriptHelper);
|
plugin.onLoad(serviceResolver, config);
|
||||||
return plugin;
|
return plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: '2.1',
|
version: '2.0',
|
||||||
name: 'Game Interface',
|
name: 'Game Interface',
|
||||||
serviceResolver: null,
|
serviceResolver: null,
|
||||||
eventManager: null,
|
eventManager: null,
|
||||||
logger: null,
|
logger: null,
|
||||||
commands: null,
|
commands: null,
|
||||||
scriptHelper: null,
|
|
||||||
configWrapper: null,
|
|
||||||
config: {
|
|
||||||
pollingRate: 300
|
|
||||||
},
|
|
||||||
|
|
||||||
onLoad: function (serviceResolver, configWrapper, scriptHelper) {
|
onLoad: function (serviceResolver, config) {
|
||||||
this.serviceResolver = serviceResolver;
|
this.serviceResolver = serviceResolver;
|
||||||
this.eventManager = serviceResolver.resolveService('IManager');
|
this.eventManager = serviceResolver.resolveService('IManager');
|
||||||
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
||||||
this.commands = commands;
|
this.commands = commands;
|
||||||
this.configWrapper = configWrapper;
|
this.config = config;
|
||||||
this.scriptHelper = scriptHelper;
|
|
||||||
|
|
||||||
const storedConfig = this.configWrapper.getValue('config', newConfig => {
|
|
||||||
if (newConfig) {
|
|
||||||
plugin.logger.logInformation('{Name} config reloaded.', plugin.name);
|
|
||||||
plugin.config = newConfig;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (storedConfig != null) {
|
|
||||||
this.config = storedConfig
|
|
||||||
} else {
|
|
||||||
this.configWrapper.setValue('config', this.config);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onClientEnteredMatch: function (clientEvent) {
|
onClientEnteredMatch: function (clientEvent) {
|
||||||
@ -92,9 +65,6 @@ const plugin = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onServerValueSetCompleted: async function (serverValueEvent) {
|
onServerValueSetCompleted: async function (serverValueEvent) {
|
||||||
this.logger.logDebug('Set {dvarName}={dvarValue} success={success} from {server}', serverValueEvent.valueName,
|
|
||||||
serverValueEvent.value, serverValueEvent.success, serverValueEvent.server.id);
|
|
||||||
|
|
||||||
if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) {
|
if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) {
|
||||||
this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName);
|
this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName);
|
||||||
return;
|
return;
|
||||||
@ -117,22 +87,15 @@ const plugin = {
|
|||||||
const input = serverState.inQueue.shift();
|
const input = serverState.inQueue.shift();
|
||||||
|
|
||||||
// if we queued an event then the next loop will be at the value set complete
|
// if we queued an event then the next loop will be at the value set complete
|
||||||
await this.processEventMessage(input, serverValueEvent.server);
|
if (await this.processEventMessage(input, serverValueEvent.server)) {
|
||||||
|
// return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.logDebug('loop complete');
|
this.logger.logDebug('loop complete');
|
||||||
// loop restarts
|
// loop restarts
|
||||||
this.requestGetDvar(inDvar, serverValueEvent.server);
|
this.requestGetDvar(inDvar, serverValueEvent.server);
|
||||||
},
|
},
|
||||||
|
|
||||||
onServerMonitoringStart: function (monitorStartEvent) {
|
|
||||||
this.initializeServer(monitorStartEvent.server);
|
|
||||||
},
|
|
||||||
|
|
||||||
onMatchStart: function (matchStartEvent) {
|
|
||||||
busMode = 'rcon';
|
|
||||||
this.sendEventMessage(matchStartEvent.server, true, 'GetBusModeRequested', null, null, null, {});
|
|
||||||
},
|
|
||||||
|
|
||||||
initializeServer: function (server) {
|
initializeServer: function (server) {
|
||||||
servers[server.id] = {
|
servers[server.id] = {
|
||||||
@ -162,12 +125,7 @@ const plugin = {
|
|||||||
serverState.enabled = true;
|
serverState.enabled = true;
|
||||||
serverState.running = true;
|
serverState.running = true;
|
||||||
serverState.initializationInProgress = false;
|
serverState.initializationInProgress = false;
|
||||||
|
|
||||||
// todo: this might not work for all games
|
|
||||||
responseEvent.server.rconParser.configuration.floodProtectInterval = 150;
|
|
||||||
|
|
||||||
this.sendEventMessage(responseEvent.server, true, 'GetBusModeRequested', null, null, null, {});
|
|
||||||
this.sendEventMessage(responseEvent.server, true, 'GetCommandsRequested', null, null, null, {});
|
|
||||||
this.requestGetDvar(inDvar, responseEvent.server);
|
this.requestGetDvar(inDvar, responseEvent.server);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -178,9 +136,7 @@ const plugin = {
|
|||||||
const serverState = servers[responseEvent.server.id];
|
const serverState = servers[responseEvent.server.id];
|
||||||
serverState.outQueue.shift();
|
serverState.outQueue.shift();
|
||||||
|
|
||||||
const utilities = importNamespace('SharedLibraryCore.Utilities');
|
if (responseEvent.server.connectedClients.count === 0) {
|
||||||
|
|
||||||
if (responseEvent.server.connectedClients.count === 0 && !utilities.isDevelopment) {
|
|
||||||
// no clients connected so we don't need to query
|
// no clients connected so we don't need to query
|
||||||
serverState.running = false;
|
serverState.running = false;
|
||||||
return;
|
return;
|
||||||
@ -223,8 +179,8 @@ const plugin = {
|
|||||||
let messageQueued = false;
|
let messageQueued = false;
|
||||||
const event = parseEvent(input);
|
const event = parseEvent(input);
|
||||||
|
|
||||||
this.logger.logDebug('Processing input... {eventType} {subType} {@data} {clientNumber}', event.eventType,
|
this.logger.logDebug('Processing input... {eventType} {subType} {data} {clientNumber}', event.eventType,
|
||||||
event.subType, event.data, event.clientNumber);
|
event.subType, event.data.toString(), event.clientNumber);
|
||||||
|
|
||||||
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
|
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
|
||||||
const threading = importNamespace('System.Threading');
|
const threading = importNamespace('System.Threading');
|
||||||
@ -252,7 +208,7 @@ const plugin = {
|
|||||||
data = {
|
data = {
|
||||||
level: client.level,
|
level: client.level,
|
||||||
clientId: client.clientId,
|
clientId: client.clientId,
|
||||||
lastConnection: client.timeSinceLastConnectionString,
|
lastConnection: client.lastConnection,
|
||||||
tag: tagMeta?.value ?? '',
|
tag: tagMeta?.value ?? '',
|
||||||
performance: clientStats?.performance ?? 200.0
|
performance: clientStats?.performance ?? 200.0
|
||||||
};
|
};
|
||||||
@ -331,74 +287,17 @@ const plugin = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.eventType === 'UrlRequested') {
|
|
||||||
const urlRequest = this.parseUrlRequest(event);
|
|
||||||
|
|
||||||
this.logger.logDebug('Making gamescript web request {@Request}', urlRequest);
|
|
||||||
|
|
||||||
this.scriptHelper.requestUrl(urlRequest, response => {
|
|
||||||
this.logger.logDebug('Got response for gamescript web request - {Response}', response);
|
|
||||||
|
|
||||||
if (typeof response !== 'string' && !(response instanceof String)) {
|
|
||||||
response = JSON.stringify(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const max = 10;
|
|
||||||
this.logger.logDebug(`response length ${response.length}`);
|
|
||||||
|
|
||||||
let quoteReplace = '\\"';
|
|
||||||
// todo: may be more than just T6
|
|
||||||
if (server.gameCode === 'T6') {
|
|
||||||
quoteReplace = '\\\\"';
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800);
|
|
||||||
if (chunks.length > max) {
|
|
||||||
this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`);
|
|
||||||
chunks = chunks.slice(0, max);
|
|
||||||
}
|
|
||||||
this.logger.logDebug(`chunk size ${chunks.length}`);
|
|
||||||
|
|
||||||
for (let i = 0; i < chunks.length; i++) {
|
|
||||||
this.sendEventMessage(server, false, 'UrlRequestCompleted', null, null,
|
|
||||||
null, { entity: event.data.entity, remaining: chunks.length - (i + 1), response: chunks[i]});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.eventType === 'RegisterCommandRequested') {
|
|
||||||
this.registerDynamicCommand(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.eventType === 'GetBusModeRequested') {
|
|
||||||
if (event.data?.directory && event.data?.mode) {
|
|
||||||
busMode = event.data.mode;
|
|
||||||
busDir = event.data.directory.replace('\'', '').replace('"', '');
|
|
||||||
if (event.data?.inLocation && event.data?.outLocation) {
|
|
||||||
busFileIn = event.data?.inLocation;
|
|
||||||
busFileOut = event.data?.outLocation;
|
|
||||||
}
|
|
||||||
this.logger.logDebug('Setting bus mode to {mode} {dir}', busMode, busDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenSource.dispose();
|
tokenSource.dispose();
|
||||||
return messageQueued;
|
return messageQueued;
|
||||||
},
|
},
|
||||||
|
|
||||||
sendEventMessage: function (server, responseExpected, event, subtype, origin, target, data) {
|
sendEventMessage: function (server, responseExpected, event, subtype, origin, target, data) {
|
||||||
let targetClientNumber = -1;
|
let targetClientNumber = -1;
|
||||||
let originClientNumber = -1;
|
|
||||||
|
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
targetClientNumber = target.clientNumber;
|
targetClientNumber = target.ClientNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (origin != null) {
|
const output = `${responseExpected ? '1' : '0'};${event};${subtype};${origin.ClientNumber};${targetClientNumber};${buildDataString(data)}`;
|
||||||
originClientNumber = origin.clientNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = `${responseExpected ? '1' : '0'}${groupSeparatorChar}${event}${groupSeparatorChar}${subtype}${groupSeparatorChar}${originClientNumber}${groupSeparatorChar}${targetClientNumber}${groupSeparatorChar}${buildDataString(data)}`;
|
|
||||||
this.logger.logDebug('Queuing output for server {output}', output);
|
this.logger.logDebug('Queuing output for server {output}', output);
|
||||||
|
|
||||||
servers[server.id].commandQueue.push(output);
|
servers[server.id].commandQueue.push(output);
|
||||||
@ -406,40 +305,9 @@ const plugin = {
|
|||||||
|
|
||||||
requestGetDvar: function (dvarName, server) {
|
requestGetDvar: function (dvarName, server) {
|
||||||
const serverState = servers[server.id];
|
const serverState = servers[server.id];
|
||||||
|
|
||||||
if (dvarName !== integrationEnabledDvar && busMode === 'file') {
|
|
||||||
this.scriptHelper.requestNotifyAfterDelay(250, () => {
|
|
||||||
const io = importNamespace('System.IO');
|
|
||||||
serverState.outQueue.push({});
|
|
||||||
try {
|
|
||||||
const content = io.File.ReadAllText(`${busDir}/${fileForDvar(dvarName)}`);
|
|
||||||
plugin.onServerValueReceived({
|
|
||||||
server: server,
|
|
||||||
source: server,
|
|
||||||
success: true,
|
|
||||||
response: {
|
|
||||||
name: dvarName,
|
|
||||||
value: content
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
plugin.logger.logError('Could not get bus data {exception}', e.toString());
|
|
||||||
plugin.onServerValueReceived({
|
|
||||||
server: server,
|
|
||||||
success: false,
|
|
||||||
response: {
|
|
||||||
name: dvarName
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
||||||
const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server);
|
const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server);
|
||||||
requestEvent.delayMs = this.config.pollingRate;
|
requestEvent.delayMs = pollingRate;
|
||||||
requestEvent.timeoutMs = 2000;
|
requestEvent.timeoutMs = 2000;
|
||||||
requestEvent.source = this.name;
|
requestEvent.source = this.name;
|
||||||
|
|
||||||
@ -449,7 +317,7 @@ const plugin = {
|
|||||||
const diff = new Date().getTime() - end.getTime();
|
const diff = new Date().getTime() - end.getTime();
|
||||||
|
|
||||||
if (diff < extraDelay) {
|
if (diff < extraDelay) {
|
||||||
requestEvent.delayMs = (extraDelay - diff) + this.config.pollingRate;
|
requestEvent.delayMs = (extraDelay - diff) + pollingRate;
|
||||||
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
|
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -467,39 +335,10 @@ const plugin = {
|
|||||||
|
|
||||||
requestSetDvar: function (dvarName, dvarValue, server) {
|
requestSetDvar: function (dvarName, dvarValue, server) {
|
||||||
const serverState = servers[server.id];
|
const serverState = servers[server.id];
|
||||||
|
|
||||||
if ( busMode === 'file' ) {
|
|
||||||
this.scriptHelper.requestNotifyAfterDelay(250, async () => {
|
|
||||||
const io = importNamespace('System.IO');
|
|
||||||
try {
|
|
||||||
const path = `${busDir}/${fileForDvar(dvarName)}`;
|
|
||||||
plugin.logger.logDebug('writing {value} to {file}', dvarValue, path);
|
|
||||||
io.File.WriteAllText(path, dvarValue);
|
|
||||||
serverState.outQueue.push({});
|
|
||||||
await plugin.onServerValueSetCompleted({
|
|
||||||
server: server,
|
|
||||||
source: server,
|
|
||||||
success: true,
|
|
||||||
value: dvarValue,
|
|
||||||
valueName: dvarName,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
plugin.logger.logError('Could not set bus data {exception}', e.toString());
|
|
||||||
await plugin.onServerValueSetCompleted({
|
|
||||||
server: server,
|
|
||||||
success: false,
|
|
||||||
valueName: dvarName,
|
|
||||||
value: dvarValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
||||||
const requestEvent = new serverEvents.ServerValueSetRequestEvent(dvarName, dvarValue, server);
|
const requestEvent = new serverEvents.ServerValueSetRequestEvent(dvarName, dvarValue, server);
|
||||||
requestEvent.delayMs = this.config.pollingRate;
|
requestEvent.delayMs = pollingRate;
|
||||||
requestEvent.timeoutMs = 2000;
|
requestEvent.timeoutMs = 2000;
|
||||||
requestEvent.source = this.name;
|
requestEvent.source = this.name;
|
||||||
|
|
||||||
@ -509,7 +348,7 @@ const plugin = {
|
|||||||
const diff = new Date().getTime() - end.getTime();
|
const diff = new Date().getTime() - end.getTime();
|
||||||
|
|
||||||
if (diff < extraDelay) {
|
if (diff < extraDelay) {
|
||||||
requestEvent.delayMs = (extraDelay - diff) + this.config.pollingRate;
|
requestEvent.delayMs = (extraDelay - diff) + pollingRate;
|
||||||
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
|
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -526,64 +365,8 @@ const plugin = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseUrlRequest: function(event) {
|
onServerMonitoringStart: function (monitorStartEvent) {
|
||||||
const url = event.data?.url;
|
this.initializeServer(monitorStartEvent.server);
|
||||||
|
|
||||||
if (url === undefined) {
|
|
||||||
this.logger.logWarning('No url provided for gamescript web request - {Event}', event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = event.data?.body;
|
|
||||||
const method = event.data?.method || 'GET';
|
|
||||||
const contentType = event.data?.contentType || 'text/plain';
|
|
||||||
const headers = event.data?.headers;
|
|
||||||
|
|
||||||
const dictionary = System.Collections.Generic.Dictionary(System.String, System.String);
|
|
||||||
const headerDict = new dictionary();
|
|
||||||
|
|
||||||
if (headers) {
|
|
||||||
const eachHeader = headers.split(',');
|
|
||||||
|
|
||||||
for (let eachKeyValue of eachHeader) {
|
|
||||||
const keyValueSplit = eachKeyValue.split(':');
|
|
||||||
if (keyValueSplit.length === 2) {
|
|
||||||
headerDict.add(keyValueSplit[0], keyValueSplit[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const script = importNamespace('IW4MAdmin.Application.Plugin.Script');
|
|
||||||
return new script.ScriptPluginWebRequest(url, body, method, contentType, headerDict);
|
|
||||||
},
|
|
||||||
|
|
||||||
registerDynamicCommand: function(event) {
|
|
||||||
const commandWrapper = {
|
|
||||||
commands: [{
|
|
||||||
name: event.data['name'] || 'DEFAULT',
|
|
||||||
description: event.data['description'] || 'DEFAULT',
|
|
||||||
alias: event.data['alias'] || 'DEFAULT',
|
|
||||||
permission: event.data['minPermission'] || 'DEFAULT',
|
|
||||||
targetRequired: (event.data['targetRequired'] || '0') === '1',
|
|
||||||
supportedGames: (event.data['supportedGames'] || '').split(','),
|
|
||||||
|
|
||||||
execute: (gameEvent) => {
|
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameEvent.data === '--reload' && gameEvent.origin.level === 'Owner') {
|
|
||||||
this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, { name: gameEvent.extra.name });
|
|
||||||
} else {
|
|
||||||
sendScriptCommand(gameEvent.owner, `${event.data['eventKey']}Execute`, gameEvent.origin, gameEvent.target, {
|
|
||||||
args: gameEvent.data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scriptHelper.registerDynamicCommand(commandWrapper);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -602,7 +385,7 @@ const commands = [{
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -622,7 +405,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -640,7 +423,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -658,7 +441,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -688,7 +471,7 @@ const commands = [{
|
|||||||
permission: 'SeniorAdmin',
|
permission: 'SeniorAdmin',
|
||||||
targetRequired: false,
|
targetRequired: false,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -711,7 +494,7 @@ const commands = [{
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -732,7 +515,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -750,7 +533,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -777,7 +560,7 @@ const commands = [{
|
|||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -801,7 +584,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -819,7 +602,7 @@ const commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4', 'IW5', 'T5', 'T6'],
|
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||||
return;
|
return;
|
||||||
@ -851,7 +634,7 @@ const parseEvent = (input) => {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventInfo = input.split(groupSeparatorChar);
|
const eventInfo = input.split(';');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
eventType: eventInfo[1],
|
eventType: eventInfo[1],
|
||||||
@ -869,7 +652,7 @@ const buildDataString = data => {
|
|||||||
let formattedData = '';
|
let formattedData = '';
|
||||||
|
|
||||||
for (let [key, value] of Object.entries(data)) {
|
for (let [key, value] of Object.entries(data)) {
|
||||||
formattedData += `${key}${unitSeparatorChar}${value}${recordSeparatorChar}`;
|
formattedData += `${key}=${value}|`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return formattedData.slice(0, -1);
|
return formattedData.slice(0, -1);
|
||||||
@ -881,11 +664,11 @@ const parseDataString = data => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dict = {};
|
const dict = {};
|
||||||
const split = data.split(recordSeparatorChar);
|
const split = data.split('|');
|
||||||
|
|
||||||
for (let i = 0; i < split.length; i++) {
|
for (let i = 0; i < split.length; i++) {
|
||||||
const segment = split[i];
|
const segment = split[i];
|
||||||
const keyValue = segment.split(unitSeparatorChar);
|
const keyValue = segment.split('=');
|
||||||
if (keyValue.length !== 2) {
|
if (keyValue.length !== 2) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -906,20 +689,3 @@ const validateEnabled = (server, origin) => {
|
|||||||
const isEmpty = (value) => {
|
const isEmpty = (value) => {
|
||||||
return value == null || false || value === '' || value === 'null';
|
return value == null || false || value === '' || value === 'null';
|
||||||
};
|
};
|
||||||
|
|
||||||
const chunkString = (str, chunkSize) => {
|
|
||||||
const result = [];
|
|
||||||
for (let i = 0; i < str.length; i += chunkSize) {
|
|
||||||
result.push(str.slice(i, i + chunkSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileForDvar = (dvar) => {
|
|
||||||
if (dvar === inDvar) {
|
|
||||||
return busFileIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
return busFileOut;
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ var eventParser;
|
|||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'Diamante',
|
author: 'Diamante',
|
||||||
version: 0.3,
|
version: 0.2,
|
||||||
name: 'BOIII Parser',
|
name: 'BOIII Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -15,9 +15,9 @@ var plugin = {
|
|||||||
|
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown)(?:\\(\\d+\\))? +(-*[0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown)(?:\\(\\d+\\))? +(-*[0-9]+) *$';
|
||||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +xuid +name +address +qport *';
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +xuid +name +address +qport *';
|
||||||
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff(\1|print) ?';
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff(\1|print) ?';
|
||||||
rconParser.Configuration.GametypeStatus.Pattern = 'Gametype: (.+)';
|
rconParser.Configuration.GametypeStatus.Pattern = 'Gametype: (.+)';
|
||||||
rconParser.Configuration.MapStatus.Pattern = 'Map: (.+)';
|
rconParser.Configuration.MapStatus.Pattern = 'Map: (.+)';
|
||||||
@ -43,7 +43,7 @@ var plugin = {
|
|||||||
eventParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef';
|
eventParser.Version = '[local] ship win64 CODBUILD8-764 (3421987) Mon Dec 16 10:44:20 2019 10d27bef';
|
||||||
eventParser.GameName = 8; // BO3
|
eventParser.GameName = 8; // BO3
|
||||||
eventParser.Configuration.GameDirectory = 'usermaps';
|
eventParser.Configuration.GameDirectory = 'usermaps';
|
||||||
eventParser.Configuration.Say.Pattern = '^(chat|chatteam);(?:[0-9]+);([a-f0-9]+);([0-9]+);(.+);(.*)$';
|
eventParser.Configuration.Say.Pattern = '^(chat|chatteam);(?:[0-9]+);([0-9]+);([0-9]+);(.+);(.*)$';
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function() {},
|
onUnloadAsync: function() {},
|
||||||
|
@ -22,10 +22,10 @@ const plugin = {
|
|||||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
||||||
|
|
||||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||||
rconParser.Configuration.HostnameStatus.AddMapping(113, 1);
|
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||||
|
|
||||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
||||||
rconParser.Configuration.MaxPlayersStatus.AddMapping(114, 1);
|
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||||
|
|
||||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
|
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
|
||||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||||
|
@ -3,7 +3,7 @@ let eventParser;
|
|||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 0.7,
|
version: 0.6,
|
||||||
name: 'CS:GO (SourceMod) Parser',
|
name: 'CS:GO (SourceMod) Parser',
|
||||||
engine: 'Source',
|
engine: 'Source',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
@ -22,10 +22,10 @@ const plugin = {
|
|||||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
||||||
|
|
||||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||||
rconParser.Configuration.HostnameStatus.AddMapping(113, 1);
|
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||||
|
|
||||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
||||||
rconParser.Configuration.MaxPlayersStatus.AddMapping(114, 1);
|
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||||
|
|
||||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
|
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
|
||||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||||
|
@ -1,135 +0,0 @@
|
|||||||
let rconParser;
|
|
||||||
let eventParser;
|
|
||||||
|
|
||||||
const plugin = {
|
|
||||||
author: 'RaidMax',
|
|
||||||
version: 0.1,
|
|
||||||
name: 'L4D2 (SourceMod) Parser',
|
|
||||||
engine: 'Source',
|
|
||||||
isParser: true,
|
|
||||||
|
|
||||||
onEventAsync: function (gameEvent, server) {
|
|
||||||
},
|
|
||||||
|
|
||||||
onLoadAsync: function (manager) {
|
|
||||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
|
||||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
|
||||||
rconParser.RConEngine = this.engine;
|
|
||||||
|
|
||||||
rconParser.Configuration.StatusHeader.Pattern = '# userid name uniqueid connected ping loss state rate adr';
|
|
||||||
|
|
||||||
rconParser.Configuration.MapStatus.Pattern = '^map *: +(.+)$';
|
|
||||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
|
||||||
|
|
||||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
|
||||||
rconParser.Configuration.HostnameStatus.AddMapping(113, 1);
|
|
||||||
|
|
||||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
|
||||||
rconParser.Configuration.MaxPlayersStatus.AddMapping(114, 1);
|
|
||||||
|
|
||||||
rconParser.Configuration.Dvar.Pattern = '^\\"(.+)\\" (?:=|is) \\"(.+)\\"(?: (?:\\( def. \\"(.*)\\" \\)))?$';
|
|
||||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
|
||||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
|
||||||
rconParser.Configuration.Dvar.AddMapping(108, 3);
|
|
||||||
rconParser.Configuration.Dvar.AddMapping(109, 3);
|
|
||||||
|
|
||||||
rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
|
||||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
|
||||||
rconParser.Configuration.Status.AddMapping(101, -1);
|
|
||||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
|
||||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
|
||||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
|
||||||
rconParser.Configuration.Status.AddMapping(105, 10);
|
|
||||||
rconParser.Configuration.Status.AddMapping(200, 1);
|
|
||||||
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('bugfix_no_version', this.engine);
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', '');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basegame', '');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('fs_homepath', '');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('g_log', '');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('net_ip', 'localhost');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('g_gametype', '');
|
|
||||||
rconParser.Configuration.DefaultDvarValues.Add('fs_game', '');
|
|
||||||
|
|
||||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'hostname');
|
|
||||||
rconParser.Configuration.OverrideDvarNameMapping.Add('mapname', 'host_map');
|
|
||||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_maxclients', 'maxplayers');
|
|
||||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
|
|
||||||
rconParser.Configuration.OverrideDvarNameMapping.Add('version', 'bugfix_no_version');
|
|
||||||
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Clear();
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('White', '\x01');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Red', '\x07');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('LightRed', '\x0F');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('DarkRed', '\x02');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Blue', '\x0B');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('DarkBlue', '\x0C');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Purple', '\x03');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Orchid', '\x0E');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Yellow', '\x09');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Gold', '\x10');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('LightGreen', '\x05');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Green', '\x04');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Lime', '\x06');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Grey', '\x08');
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Grey2', '\x0D');
|
|
||||||
// only adding there here for the default accent color
|
|
||||||
rconParser.Configuration.ColorCodeMapping.Add('Cyan', '\x0B');
|
|
||||||
|
|
||||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
|
||||||
rconParser.Configuration.DefaultRConPort = 27015;
|
|
||||||
rconParser.CanGenerateLogPath = false;
|
|
||||||
|
|
||||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
|
||||||
rconParser.Configuration.CommandPrefixes.Kick = 'sm_kick #{0} {1}';
|
|
||||||
rconParser.Configuration.CommandPrefixes.Ban = 'sm_kick #{0} {1}';
|
|
||||||
rconParser.Configuration.CommandPrefixes.TempBan = 'sm_kick #{0} {1}';
|
|
||||||
rconParser.Configuration.CommandPrefixes.Say = 'sm_say {0}';
|
|
||||||
rconParser.Configuration.CommandPrefixes.Tell = 'sm_psay #{0} "{1}"';
|
|
||||||
|
|
||||||
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" (?:say|say_team) "(.*)"$';
|
|
||||||
eventParser.Configuration.Say.AddMapping(5, 1);
|
|
||||||
eventParser.Configuration.Say.AddMapping(3, 2);
|
|
||||||
eventParser.Configuration.Say.AddMapping(1, 3);
|
|
||||||
eventParser.Configuration.Say.AddMapping(7, 4);
|
|
||||||
eventParser.Configuration.Say.AddMapping(13, 5);
|
|
||||||
|
|
||||||
eventParser.Configuration.Kill.Pattern = '^"(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] killed "(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] with "(\\S*)" *(?:\\((\\w+)((?: ).+)?\\))?$';
|
|
||||||
eventParser.Configuration.Kill.AddMapping(5, 1);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(3, 2);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(1, 3);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(7, 4);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(6, 5);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(4, 6);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(2, 7);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(8, 8);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(9, 9);
|
|
||||||
eventParser.Configuration.Kill.AddMapping(12, 10);
|
|
||||||
|
|
||||||
eventParser.Configuration.MapEnd.Pattern = '^World triggered "Match_Start" on "(.+)"$';
|
|
||||||
|
|
||||||
eventParser.Configuration.JoinTeam.Pattern = '^"(.+)<(\\d+)><(.*)>" switched from team <(.+)> to <(.+)>$';
|
|
||||||
eventParser.Configuration.JoinTeam.AddMapping(5, 1);
|
|
||||||
eventParser.Configuration.JoinTeam.AddMapping(3, 2);
|
|
||||||
eventParser.Configuration.JoinTeam.AddMapping(1, 3);
|
|
||||||
eventParser.Configuration.JoinTeam.AddMapping(7, 5);
|
|
||||||
|
|
||||||
eventParser.Configuration.TeamMapping.Add('CT', 2);
|
|
||||||
eventParser.Configuration.TeamMapping.Add('TERRORIST', 3);
|
|
||||||
|
|
||||||
eventParser.Configuration.Time.Pattern = '^L [01]\\d/[0-3]\\d/\\d+ - [0-2]\\d:[0-5]\\d:[0-5]\\d:';
|
|
||||||
|
|
||||||
rconParser.Version = 'L4D2SM';
|
|
||||||
rconParser.GameName = 12; // L4D2
|
|
||||||
eventParser.Version = 'L4D2SM';
|
|
||||||
eventParser.GameName = 12; // L4D2
|
|
||||||
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
|
|
||||||
},
|
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
|
||||||
},
|
|
||||||
|
|
||||||
onTickAsync: function (server) {
|
|
||||||
}
|
|
||||||
};
|
|
@ -2,8 +2,8 @@
|
|||||||
var eventParser;
|
var eventParser;
|
||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax, Chase, Future',
|
author: 'RaidMax, Chase',
|
||||||
version: 0.5,
|
version: 0.4,
|
||||||
name: 'Plutonium T4 MP Parser',
|
name: 'Plutonium T4 MP Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -14,9 +14,9 @@ var plugin = {
|
|||||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||||
rconParser.Configuration.GuidNumberStyle = 7; // Integer
|
rconParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||||
rconParser.Configuration.DefaultRConPort = 28960;
|
rconParser.Configuration.DefaultRConPort = 28960;
|
||||||
|
@ -2,8 +2,8 @@ var rconParser;
|
|||||||
var eventParser;
|
var eventParser;
|
||||||
|
|
||||||
var plugin = {
|
var plugin = {
|
||||||
author: 'RaidMax, Future',
|
author: 'RaidMax',
|
||||||
version: 0.6,
|
version: 0.5,
|
||||||
name: 'Black Ops 3 Parser',
|
name: 'Black Ops 3 Parser',
|
||||||
isParser: true,
|
isParser: true,
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ var plugin = {
|
|||||||
|
|
||||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown)(?:\\([0-9]+\\)) +(-*[0-9]+) *$';
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown)(?:\\([0-9]+\\)) +(-*[0-9]+) *$';
|
||||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +xuid +name +address +qport|---------- Live ----------';
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +xuid +name +address +qport|---------- Live ----------';
|
||||||
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick_for_reason {0} "{1}"';
|
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0}';
|
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0}';
|
||||||
rconParser.Configuration.CommandPrefixes.RConCommand = '\xff\xff\xff\xff\x00{0} {1}';
|
rconParser.Configuration.CommandPrefixes.RConCommand = '\xff\xff\xff\xff\x00{0} {1}';
|
||||||
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xff\x00{0} {1}';
|
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xff\x00{0} {1}';
|
||||||
|
@ -41,7 +41,6 @@ const plugin = {
|
|||||||
|
|
||||||
serverOrderCache[startEvent.server.gameCode].push(startEvent.server);
|
serverOrderCache[startEvent.server.gameCode].push(startEvent.server);
|
||||||
serverOrderCache[startEvent.server.gameCode].sort((a, b) => b.clientNum - a.clientNum);
|
serverOrderCache[startEvent.server.gameCode].sort((a, b) => b.clientNum - a.clientNum);
|
||||||
serverOrderCache.sort((a, b) => b[Object.keys(b)[0].clientNum] - b[Object.keys(a)[0].clientNum]);
|
|
||||||
|
|
||||||
if (lookupComplete) {
|
if (lookupComplete) {
|
||||||
return;
|
return;
|
||||||
@ -50,9 +49,9 @@ const plugin = {
|
|||||||
const lookupIp = startEvent.server.resolvedIpEndPoint.address.isInternal() ?
|
const lookupIp = startEvent.server.resolvedIpEndPoint.address.isInternal() ?
|
||||||
this.manager.externalIPAddress :
|
this.manager.externalIPAddress :
|
||||||
startEvent.server.resolvedIpEndPoint.toString().split(':')[0];
|
startEvent.server.resolvedIpEndPoint.toString().split(':')[0];
|
||||||
|
|
||||||
this.logger.logInformation('Looking up server location for IP {IP}', lookupIp);
|
this.logger.logInformation('Looking up server location for IP {IP}', lookupIp);
|
||||||
|
|
||||||
this.scriptHelper.getUrl(`https://ipinfo.io/${lookupIp}/country`, (result) => {
|
this.scriptHelper.getUrl(`https://ipinfo.io/${lookupIp}/country`, (result) => {
|
||||||
let error = true;
|
let error = true;
|
||||||
|
|
||||||
@ -81,9 +80,8 @@ const plugin = {
|
|||||||
interactionData.interactionType = 1;
|
interactionData.interactionType = 1;
|
||||||
interactionData.source = plugin.name;
|
interactionData.source = plugin.name;
|
||||||
|
|
||||||
interactionData.scriptAction = (sourceId, targetId, game, meta, _) => {
|
interactionData.scriptAction = (sourceId, targetId, game, meta, token) => {
|
||||||
const serverId = meta.serverId;
|
const serverId = meta['serverId'];
|
||||||
const isSmall = meta.size !== undefined && meta.size === 'small';
|
|
||||||
let server;
|
let server;
|
||||||
|
|
||||||
let colorLeft = 'color: #f5f5f5; text-shadow: -1px 1px 8px #000000cc;';
|
let colorLeft = 'color: #f5f5f5; text-shadow: -1px 1px 8px #000000cc;';
|
||||||
@ -146,185 +144,97 @@ const plugin = {
|
|||||||
colorRight = colorMappingOverride[gameCode]?.right || colorRight;
|
colorRight = colorMappingOverride[gameCode]?.right || colorRight;
|
||||||
|
|
||||||
const font = 'Noto Sans Mono';
|
const font = 'Noto Sans Mono';
|
||||||
let status = isSmall ? '<div class="status small online-checkmark"></div>' : '<div class="status-online subtitle">ONLINE</div>';
|
let status = '<div class="status-online subtitle">ONLINE</div>';
|
||||||
if (server.throttled) {
|
if (server.throttled) {
|
||||||
status = isSmall ? '<div class="status small offline-x"></div>' : '<div class="status-offline subtitle">OFFLINE</div>';
|
status = '<div class="status-offline subtitle">OFFLINE</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayIp = server.resolvedIpEndPoint.address.isInternal() ?
|
const displayIp = server.resolvedIpEndPoint.address.isInternal() ?
|
||||||
plugin.manager.externalIPAddress :
|
plugin.manager.externalIPAddress :
|
||||||
server.resolvedIpEndPoint.toString().split(':')[0];
|
server.resolvedIpEndPoint.toString().split(':')[0];
|
||||||
|
|
||||||
const head = `<head>
|
return `<html>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=${font}">
|
<head>
|
||||||
<style>
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=${font}">
|
||||||
* {
|
<style>
|
||||||
padding: 0;
|
* {
|
||||||
margin: 0;
|
padding: 0;
|
||||||
}
|
margin: 0;
|
||||||
.server-container {
|
}
|
||||||
font-family: '${font}';
|
.server-container {
|
||||||
background: url('https://raidmax.org/resources/images/banners/${gameCode}.jpg') no-repeat;
|
padding-left: 1rem;
|
||||||
align-items: center;
|
padding-right: 1rem;
|
||||||
}
|
width: calc(750px - 2rem);
|
||||||
.server-container.large {
|
height: 120px;
|
||||||
padding-left: 1rem;
|
display: flex;
|
||||||
padding-right: 1rem;
|
font-family: '${font}';
|
||||||
width: calc(750px - 2rem);
|
background: url('https://raidmax.org/resources/images/banners/${gameCode}.jpg') no-repeat;
|
||||||
height: 120px;
|
align-items: center;
|
||||||
display: flex;
|
}
|
||||||
background-position: center center;
|
.contrast {
|
||||||
}
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
.server-container.small {
|
}
|
||||||
padding: 0.5rem;
|
.game-icon {
|
||||||
background-position: left center;
|
border-radius: 10px;
|
||||||
}
|
width: 64px;
|
||||||
.game-icon {
|
height: 64px;
|
||||||
background: url('https://raidmax.org/resources/images/icons/games/${gameCode}.jpg') no-repeat;
|
}
|
||||||
background-size: contain;
|
.game-info {
|
||||||
}
|
padding: 0 0.75em;
|
||||||
.game-icon.large {
|
}
|
||||||
width: 64px;
|
.game-info .header {
|
||||||
height: 64px;
|
font-weight: bold;
|
||||||
border-radius: 10px;
|
}
|
||||||
}
|
.game-info .subtitle {
|
||||||
.game-icon.small {
|
font-size: 0.9rem;
|
||||||
width: 20px;
|
}
|
||||||
height: 20px;
|
.text-weight-lighter {
|
||||||
border-radius: 5px;
|
font-weight: lighter
|
||||||
}
|
}
|
||||||
.first-line.small, .second-line.small {
|
.status-online {
|
||||||
display: flex;
|
color: green;
|
||||||
flex-direction: row;
|
}
|
||||||
}
|
.status-offline {
|
||||||
.first-line.small .header {
|
color: red;
|
||||||
font-size: 10pt;
|
}
|
||||||
font-weight: bold;
|
.players-flag-section {
|
||||||
margin-left: 0.5rem;
|
flex: 1;
|
||||||
align-self: center;
|
display:flex;
|
||||||
}
|
flex-direction: row;
|
||||||
.second-line.small {
|
align-items: center;
|
||||||
align-self: center;
|
}
|
||||||
}
|
.players-flag-section img {
|
||||||
.game-info.small {
|
margin: 0 0.5rem;
|
||||||
margin-left: 0.5rem;
|
height: 0.75rem;
|
||||||
font-size: 9pt;
|
}
|
||||||
}
|
h3, div {
|
||||||
.game-info.large {
|
line-height: 1.5rem;
|
||||||
padding: 0 0.75em;
|
}
|
||||||
}
|
h2 {
|
||||||
.game-info .header {
|
line-height: 2rem;
|
||||||
font-weight: bold;
|
}
|
||||||
}
|
</style>
|
||||||
img.location-image {
|
</head>
|
||||||
width: 20px;
|
|
||||||
align-self: center;
|
<div class="server-container contrast" id="server">
|
||||||
}
|
<div class="game-icon"
|
||||||
.game-info.large .subtitle {
|
style="background: url('https://raidmax.org/resources/images/icons/games/${gameCode}.jpg');">
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.text-weight-lighter {
|
|
||||||
font-weight: lighter
|
|
||||||
}
|
|
||||||
.status-online {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
.status-offline {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.players-flag-section {
|
|
||||||
flex: 1;
|
|
||||||
display:flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.players-flag-section img {
|
|
||||||
margin: 0 0.5rem;
|
|
||||||
height: 0.75rem;
|
|
||||||
}
|
|
||||||
.status.small:after {
|
|
||||||
position: absolute;
|
|
||||||
width: 20px;
|
|
||||||
height: 15px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-size: 8pt;
|
|
||||||
margin-top: 0.1rem;
|
|
||||||
margin-left: -0.5rem;
|
|
||||||
}
|
|
||||||
.online-checkmark:after {
|
|
||||||
content: '\\2714';
|
|
||||||
color: white;
|
|
||||||
background: rgba(0, 128, 0, 0.5);
|
|
||||||
}
|
|
||||||
.offline-x:after {
|
|
||||||
content: '\\2715';
|
|
||||||
color: white;
|
|
||||||
background: rgba(128, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
h3, .server-container.large div {
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
line-height: 2rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<title>${displayIp}:${server.listenPort}</title>
|
|
||||||
</head>`;
|
|
||||||
|
|
||||||
|
|
||||||
if (isSmall) {
|
|
||||||
return `<html lang="en">
|
|
||||||
${head}
|
|
||||||
<body>
|
|
||||||
<div class="server-container small" id="server">
|
|
||||||
<div class="first-line small">
|
|
||||||
<div class="game-icon small"></div>
|
|
||||||
<div class="header" style="${colorLeft}">${server.serverName.stripColors()}</div>
|
|
||||||
</div>
|
|
||||||
<div class="third-line game-info small">
|
|
||||||
${status}
|
|
||||||
<div style="${colorLeft}; margin-left: 20px;">${displayIp}:${server.listenPort}</div>
|
|
||||||
</div>
|
|
||||||
<div class="second-line small">
|
|
||||||
<img src="https://flagcdn.com/w40/${serverLocationCache[server.listenAddress]?.toLowerCase()}.png"
|
|
||||||
alt="${serverLocationCache[server.listenAddress]}" class="location-image">
|
|
||||||
<div class="game-info small" style="${colorLeft}">
|
|
||||||
<span>${server.throttled ? '-' : server.clientNum}/${server.maxClients}</span>
|
|
||||||
•
|
|
||||||
<span>${server.map.alias}</span>
|
|
||||||
•
|
|
||||||
<span>${server.gametypeName}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
<div style="flex: 1; ${colorLeft}" class="game-info">
|
||||||
</html>`;
|
<div class="header">${server.serverName.stripColors()}</div>
|
||||||
}
|
<div class="text-weight-lighter subtitle">${displayIp}:${server.listenPort}</div>
|
||||||
|
<div class="players-flag-section">
|
||||||
return `<html lang="en">
|
<div class="subtitle">${server.throttled ? '-' : server.clientNum}/${server.maxClients} Players</div>
|
||||||
${head}
|
<img src="https://flagcdn.com/h20/${serverLocationCache[server.listenAddress]?.toLowerCase()}.png"
|
||||||
<body>
|
alt="${serverLocationCache[server.listenAddress]}"/>
|
||||||
<div class="server-container large" id="server">
|
|
||||||
<div class="game-icon large"
|
|
||||||
style="background: url('https://raidmax.org/resources/images/icons/games/${gameCode}.jpg');">
|
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1; ${colorLeft}" class="game-info large">
|
</div>
|
||||||
<div class="header">${server.serverName.stripColors()}</div>
|
<div style="${colorRight}; text-align: right;" class="game-info">
|
||||||
<div class="text-weight-lighter subtitle">${displayIp}:${server.listenPort}</div>
|
<div class="header">${server.map.alias}</div>
|
||||||
<div class="players-flag-section">
|
<div class="text-weight-lighter subtitle">${server.gametypeName}</div>
|
||||||
<div class="subtitle">${server.throttled ? '-' : server.clientNum}/${server.maxClients} Players</div>
|
${status}
|
||||||
<img src="https://flagcdn.com/h20/${serverLocationCache[server.listenAddress]?.toLowerCase()}.png"
|
</div>
|
||||||
alt="${serverLocationCache[server.listenAddress]}"/>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="${colorRight}; text-align: right;" class="game-info">
|
|
||||||
<div class="header">${server.map.alias}</div>
|
|
||||||
<div class="text-weight-lighter subtitle">${server.gametypeName}</div>
|
|
||||||
${status}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
</html>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -358,36 +268,18 @@ const plugin = {
|
|||||||
const servers = serverOrderCache[key];
|
const servers = serverOrderCache[key];
|
||||||
servers.forEach(eachServer => {
|
servers.forEach(eachServer => {
|
||||||
response += `<div class="w-full w-xl-half">
|
response += `<div class="w-full w-xl-half">
|
||||||
<div class="card m-10 p-20">
|
<div class="card m-10 p-20">
|
||||||
<div class="font-size-16 mb-10">
|
<div class="font-size-16 mb-10"> <div class="badge ml-10 float-right font-size-16">${eachServer.gameCode}</div>${eachServer.serverName.stripColors()}</div>
|
||||||
<div class="badge ml-10 float-right font-size-16">${eachServer.gameCode}</div>
|
|
||||||
${eachServer.serverName.stripColors()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="overflow: hidden">
|
<div style="overflow: hidden">
|
||||||
<iframe src="/Interaction/Render/Banner?serverId=${eachServer.id}" width="750"
|
<iframe src="/Interaction/Render/Banner?serverId=${eachServer.id}" width="750" height="120" style="border-width: 0; overflow: hidden;" class="rounded mb-5" ></iframe>
|
||||||
height="120" style="border-width: 0; overflow: hidden;" class="rounded mb-5"
|
</div>
|
||||||
title="${eachServer.id}"></iframe>
|
<div class="btn mb-10" onclick="document.getElementById('showCode${eachServer.id}').style.removeProperty('display')">Show Embed</div>
|
||||||
</div>
|
<div class="code p-5" id="showCode${eachServer.id}" style="display:none;"><iframe
|
||||||
<div class="btn mb-10" onclick="$(document.getElementById('showCode${eachServer.id}')).toggleClass('d-flex')">Show Embed</div>
|
<br/> src="${plugin.webfrontUrl}/Interaction/Render/Banner?serverId=${eachServer.id}"
|
||||||
<div class="code p-5 mb-10" id="showCode${eachServer.id}" style="display:none;">
|
<br/> width="750" height="120" style="border-width: 0; overflow: hidden;"><br/>
|
||||||
<iframe
|
</iframe></div>
|
||||||
<br/> src="${plugin.webfrontUrl}/Interaction/Render/Banner?serverId=${eachServer.id}"
|
</div></div>`;
|
||||||
<br/> width="750" height="120" style="border-width: 0; overflow: hidden;"><br/>
|
|
||||||
</iframe></div>
|
|
||||||
<div>
|
|
||||||
<iframe src="/Interaction/Render/Banner?serverId=${eachServer.id}&size=small" width="400"
|
|
||||||
height="70" style="border-width: 0; overflow: hidden;" class="rounded mb-5"
|
|
||||||
title="${eachServer.id}"></iframe>
|
|
||||||
</div>
|
|
||||||
<div class="btn mb-10" onclick="$(document.getElementById('showCode${eachServer.id}Small')).toggleClass('d-flex')">Show Embed</div>
|
|
||||||
<div class="code p-5" id="showCode${eachServer.id}Small" style="display:none;">
|
|
||||||
<iframe
|
|
||||||
<br/> src="${plugin.webfrontUrl}/Interaction/Render/Banner?serverId=${eachServer.id}&size=small"
|
|
||||||
<br/> width="400" height="70" style="border-width: 0; overflow: hidden;"><br/>
|
|
||||||
</iframe></div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,24 +2,23 @@ let vpnExceptionIds = [];
|
|||||||
const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList';
|
const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList';
|
||||||
const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist';
|
const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist';
|
||||||
|
|
||||||
const init = (registerNotify, serviceResolver, configWrapper, pluginHelper) => {
|
const init = (registerNotify, serviceResolver, config, pluginHelper) => {
|
||||||
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, token) => plugin.onClientAuthorized(authorizedEvent, token));
|
registerNotify('IManagementEventSubscriptions.ClientStateAuthorized', (authorizedEvent, token) => plugin.onClientAuthorized(authorizedEvent, token));
|
||||||
|
|
||||||
plugin.onLoad(serviceResolver, configWrapper, pluginHelper);
|
plugin.onLoad(serviceResolver, config, pluginHelper);
|
||||||
return plugin;
|
return plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: '2.1',
|
version: '2.0',
|
||||||
name: 'VPN Detection Plugin',
|
name: 'VPN Detection Plugin',
|
||||||
manager: null,
|
manager: null,
|
||||||
configWrapper: null,
|
config: null,
|
||||||
logger: null,
|
logger: null,
|
||||||
serviceResolver: null,
|
serviceResolver: null,
|
||||||
translations: null,
|
translations: null,
|
||||||
pluginHelper: null,
|
pluginHelper: null,
|
||||||
enabled: true,
|
|
||||||
|
|
||||||
commands: [{
|
commands: [{
|
||||||
name: 'whitelistvpn',
|
name: 'whitelistvpn',
|
||||||
@ -33,7 +32,7 @@ const plugin = {
|
|||||||
}],
|
}],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
vpnExceptionIds.push(gameEvent.Target.ClientId);
|
vpnExceptionIds.push(gameEvent.Target.ClientId);
|
||||||
plugin.configWrapper.setValue('vpnExceptionIds', vpnExceptionIds);
|
plugin.config.setValue('vpnExceptionIds', vpnExceptionIds);
|
||||||
|
|
||||||
gameEvent.origin.tell(`Successfully whitelisted ${gameEvent.target.name}`);
|
gameEvent.origin.tell(`Successfully whitelisted ${gameEvent.target.name}`);
|
||||||
}
|
}
|
||||||
@ -50,7 +49,7 @@ const plugin = {
|
|||||||
}],
|
}],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
vpnExceptionIds = vpnExceptionIds.filter(exception => parseInt(exception) !== parseInt(gameEvent.Target.ClientId));
|
vpnExceptionIds = vpnExceptionIds.filter(exception => parseInt(exception) !== parseInt(gameEvent.Target.ClientId));
|
||||||
plugin.configWrapper.setValue('vpnExceptionIds', vpnExceptionIds);
|
plugin.config.setValue('vpnExceptionIds', vpnExceptionIds);
|
||||||
|
|
||||||
gameEvent.origin.tell(`Successfully disallowed ${gameEvent.target.name} from connecting with VPN`);
|
gameEvent.origin.tell(`Successfully disallowed ${gameEvent.target.name} from connecting with VPN`);
|
||||||
}
|
}
|
||||||
@ -149,45 +148,30 @@ const plugin = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
onClientAuthorized: async function (authorizeEvent, token) {
|
onClientAuthorized: async function (authorizeEvent, token) {
|
||||||
if (authorizeEvent.client.isBot || !this.enabled) {
|
if (authorizeEvent.client.isBot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.checkForVpn(authorizeEvent.client, token);
|
await this.checkForVpn(authorizeEvent.client, token);
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad: function (serviceResolver, configWrapper, pluginHelper) {
|
onLoad: function (serviceResolver, config, pluginHelper) {
|
||||||
this.serviceResolver = serviceResolver;
|
this.serviceResolver = serviceResolver;
|
||||||
this.configWrapper = configWrapper;
|
this.config = config;
|
||||||
this.pluginHelper = pluginHelper;
|
this.pluginHelper = pluginHelper;
|
||||||
this.manager = this.serviceResolver.resolveService('IManager');
|
this.manager = this.serviceResolver.resolveService('IManager');
|
||||||
this.logger = this.serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
this.logger = this.serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
||||||
this.translations = this.serviceResolver.resolveService('ITranslationLookup');
|
this.translations = this.serviceResolver.resolveService('ITranslationLookup');
|
||||||
|
|
||||||
this.configWrapper.setName(this.name); // use legacy key
|
this.config.setName(this.name); // use legacy key
|
||||||
this.configWrapper.getValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(parseInt(element)));
|
this.config.getValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(parseInt(element)));
|
||||||
this.logger.logInformation(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
this.logger.logInformation(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
||||||
|
|
||||||
this.enabled = this.configWrapper.getValue('enabled', newValue => {
|
|
||||||
if (newValue) {
|
|
||||||
plugin.logger.logInformation('{Name} configuration updated. Enabled={Enabled}', newValue);
|
|
||||||
plugin.enabled = newValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.enabled === undefined) {
|
|
||||||
this.configWrapper.setValue('enabled', true);
|
|
||||||
this.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.interactionRegistration = this.serviceResolver.resolveService('IInteractionRegistration');
|
this.interactionRegistration = this.serviceResolver.resolveService('IInteractionRegistration');
|
||||||
this.interactionRegistration.unregisterInteraction(vpnWhitelistKey);
|
this.interactionRegistration.unregisterInteraction(vpnWhitelistKey);
|
||||||
this.interactionRegistration.unregisterInteraction(vpnAllowListKey);
|
this.interactionRegistration.unregisterInteraction(vpnAllowListKey);
|
||||||
|
|
||||||
this.logger.logInformation('{Name} {Version} by {Author} loaded. Enabled={Enabled}', this.name, this.version,
|
|
||||||
this.author, this.enabled);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
checkForVpn: async function (origin, _) {
|
checkForVpn: async function (origin, token) {
|
||||||
let exempt = false;
|
let exempt = false;
|
||||||
// prevent players that are exempt from being kicked
|
// prevent players that are exempt from being kicked
|
||||||
vpnExceptionIds.forEach(function (id) {
|
vpnExceptionIds.forEach(function (id) {
|
||||||
@ -207,15 +191,13 @@ const plugin = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userAgent = `IW4MAdmin-${this.manager.getApplicationSettings().configuration().id}`;
|
const userAgent = `IW4MAdmin-${this.manager.getApplicationSettings().configuration().id}`;
|
||||||
const stringDict = System.Collections.Generic.Dictionary(System.String, System.String);
|
const headers = {
|
||||||
const headers = new stringDict();
|
'User-Agent': userAgent
|
||||||
headers.add('User-Agent', userAgent);
|
};
|
||||||
const pluginScript = importNamespace('IW4MAdmin.Application.Plugin.Script');
|
|
||||||
const request = new pluginScript.ScriptPluginWebRequest(`https://api.xdefcon.com/proxy/check/?ip=${origin.IPAddressString}`,
|
|
||||||
null, 'GET', 'application/json', headers);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.pluginHelper.requestUrl(request, (response) => this.onVpnResponse(response, origin));
|
this.pluginHelper.getUrl(`https://api.xdefcon.com/proxy/check/?ip=${origin.IPAddressString}`, headers,
|
||||||
|
(response) => this.onVpnResponse(response, origin));
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.logger.logWarning('There was a problem checking client IP ({IP}) for VPN - {message}',
|
this.logger.logWarning('There was a problem checking client IP ({IP}) for VPN - {message}',
|
||||||
|
@ -25,25 +25,11 @@ namespace Stats.Dtos
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? SentAfter { get; set; }
|
public DateTime? SentAfter { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time associated with SentAfter date
|
|
||||||
/// </summary>
|
|
||||||
public string SentAfterTime { get; set; }
|
|
||||||
|
|
||||||
public DateTime? SentAfterDateTime => SentAfter?.Add(string.IsNullOrEmpty(SentAfterTime) ? TimeSpan.Zero : TimeSpan.Parse(SentAfterTime));
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// only look for messages sent before this date0
|
/// only look for messages sent before this date0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime SentBefore { get; set; } = DateTime.UtcNow.Date;
|
public DateTime SentBefore { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
public string SentBeforeTime { get; set; }
|
|
||||||
|
|
||||||
public DateTime? SentBeforeDateTime =>
|
|
||||||
SentBefore.Add(string.IsNullOrEmpty(SentBeforeTime) ? TimeSpan.Zero : TimeSpan.Parse(SentBeforeTime));
|
|
||||||
|
|
||||||
public bool IsExactMatch { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// indicates if the chat is on the meta page
|
/// indicates if the chat is on the meta page
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -13,6 +13,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
{
|
{
|
||||||
private const int ZScoreRange = 3;
|
private const int ZScoreRange = 3;
|
||||||
private const int RankIconDivisions = 24;
|
private const int RankIconDivisions = 24;
|
||||||
|
private const int MaxMessages = 100;
|
||||||
|
|
||||||
public class LogParams
|
public class LogParams
|
||||||
{
|
{
|
||||||
@ -126,5 +127,70 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// todo: lets abstract this out to a generic buildable query
|
||||||
|
/// this is just a dirty PoC
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ChatSearchQuery ParseSearchInfo(this string query, int count, int offset)
|
||||||
|
{
|
||||||
|
string[] filters = query.Split('|');
|
||||||
|
var searchRequest = new ChatSearchQuery
|
||||||
|
{
|
||||||
|
Filter = query,
|
||||||
|
Count = count,
|
||||||
|
Offset = offset
|
||||||
|
};
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
searchRequest.Count = Math.Min(searchRequest.Count, MaxMessages);
|
||||||
|
searchRequest.Count = Math.Max(searchRequest.Count, 0);
|
||||||
|
searchRequest.Offset = Math.Max(searchRequest.Offset, 0);
|
||||||
|
|
||||||
|
if (filters.Length > 1)
|
||||||
|
{
|
||||||
|
if (filters[0].ToLower() != "chat")
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Query is not compatible with chat");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string filter in filters.Skip(1))
|
||||||
|
{
|
||||||
|
string[] args = filter.Split(' ');
|
||||||
|
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
string recombinedArgs = string.Join(' ', args.Skip(1));
|
||||||
|
switch (args[0].ToLower())
|
||||||
|
{
|
||||||
|
case "before":
|
||||||
|
searchRequest.SentBefore = DateTime.Parse(recombinedArgs);
|
||||||
|
break;
|
||||||
|
case "after":
|
||||||
|
searchRequest.SentAfter = DateTime.Parse(recombinedArgs);
|
||||||
|
break;
|
||||||
|
case "server":
|
||||||
|
searchRequest.ServerId = args[1];
|
||||||
|
break;
|
||||||
|
case "client":
|
||||||
|
searchRequest.ClientId = int.Parse(args[1]);
|
||||||
|
break;
|
||||||
|
case "contains":
|
||||||
|
searchRequest.MessageContains = string.Join(' ', args.Skip(1));
|
||||||
|
break;
|
||||||
|
case "sort":
|
||||||
|
searchRequest.Direction = Enum.Parse<SortDirection>(args[1], ignoreCase: true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("No filters specified for chat search");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,11 +53,11 @@ namespace Stats.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var iqMessages = context.Set<EFClientMessage>()
|
var iqMessages = context.Set<EFClientMessage>()
|
||||||
.Where(message => message.TimeSent < query.SentBeforeDateTime);
|
.Where(message => message.TimeSent < query.SentBefore);
|
||||||
|
|
||||||
if (query.SentAfterDateTime is not null)
|
if (query.SentAfter is not null)
|
||||||
{
|
{
|
||||||
iqMessages = iqMessages.Where(message => message.TimeSent >= query.SentAfterDateTime);
|
iqMessages = iqMessages.Where(message => message.TimeSent >= query.SentAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ClientId is not null)
|
if (query.ClientId is not null)
|
||||||
@ -72,10 +72,7 @@ namespace Stats.Helpers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(query.MessageContains))
|
if (!string.IsNullOrEmpty(query.MessageContains))
|
||||||
{
|
{
|
||||||
iqMessages = query.IsExactMatch
|
iqMessages = iqMessages.Where(message => EF.Functions.Like(message.Message.ToLower(), $"%{query.MessageContains.ToLower()}%"));
|
||||||
? iqMessages.Where(message => message.Message.ToLower() == query.MessageContains.ToLower())
|
|
||||||
: iqMessages.Where(message =>
|
|
||||||
EF.Functions.Like(message.Message.ToLower(), $"%{query.MessageContains.ToLower()}%"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var iqResponse = iqMessages
|
var iqResponse = iqMessages
|
||||||
|
@ -469,8 +469,6 @@ public class Plugin : IPluginV2
|
|||||||
ClientId = request.ClientId,
|
ClientId = request.ClientId,
|
||||||
Before = request.Before,
|
Before = request.Before,
|
||||||
SentBefore = request.Before ?? DateTime.UtcNow,
|
SentBefore = request.Before ?? DateTime.UtcNow,
|
||||||
SentAfter = request.After,
|
|
||||||
After = request.After,
|
|
||||||
Count = request.Count,
|
Count = request.Count,
|
||||||
IsProfileMeta = true
|
IsProfileMeta = true
|
||||||
};
|
};
|
||||||
|
@ -56,4 +56,4 @@ Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
|
|||||||
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
|
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
|
||||||
|
|
||||||
|
|
||||||
#### Explore the [wiki](https://git.rimmyscorner.com/Parasyn/IW4M-Admin/wiki) to find more information.
|
#### Explore the [wiki](https://github.com/RaidMax/IW4M-Admin/wiki) to find more information.
|
||||||
|
@ -178,8 +178,7 @@ namespace SharedLibraryCore
|
|||||||
ViewBag.ReportCount = Manager.GetServers().Sum(server =>
|
ViewBag.ReportCount = Manager.GetServers().Sum(server =>
|
||||||
server.Reports.Count(report => DateTime.UtcNow - report.ReportedOn <= TimeSpan.FromHours(24)));
|
server.Reports.Count(report => DateTime.UtcNow - report.ReportedOn <= TimeSpan.FromHours(24)));
|
||||||
ViewBag.PermissionsSet = PermissionsSet;
|
ViewBag.PermissionsSet = PermissionsSet;
|
||||||
ViewBag.Alerts = AlertManager.RetrieveAlerts(Client);
|
ViewBag.Alerts = AlertManager.RetrieveAlerts(Client).ToList();
|
||||||
ViewBag.Manager = Manager;
|
|
||||||
|
|
||||||
base.OnActionExecuting(context);
|
base.OnActionExecuting(context);
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ namespace SharedLibraryCore.Configuration
|
|||||||
: ManualWebfrontUrl;
|
: ManualWebfrontUrl;
|
||||||
|
|
||||||
[ConfigurationIgnore] public bool IgnoreServerConnectionLost { get; set; }
|
[ConfigurationIgnore] public bool IgnoreServerConnectionLost { get; set; }
|
||||||
[ConfigurationIgnore] public Uri MasterUrl { get; set; } = new("https://master.iw4.zip");
|
[ConfigurationIgnore] public Uri MasterUrl { get; set; } = new("http://api.raidmax.org:5000");
|
||||||
|
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
|
@ -67,7 +67,7 @@ namespace SharedLibraryCore.Configuration.Validation
|
|||||||
|
|
||||||
RuleFor(_app => _app.MasterUrl)
|
RuleFor(_app => _app.MasterUrl)
|
||||||
.NotNull()
|
.NotNull()
|
||||||
.Must(_url => _url != null && (_url.Scheme == Uri.UriSchemeHttp || _url.Scheme == Uri.UriSchemeHttps));
|
.Must(_url => _url != null && _url.Scheme == Uri.UriSchemeHttp);
|
||||||
|
|
||||||
RuleFor(_app => _app.CommandPrefix)
|
RuleFor(_app => _app.CommandPrefix)
|
||||||
.NotEmpty();
|
.NotEmpty();
|
||||||
@ -80,4 +80,4 @@ namespace SharedLibraryCore.Configuration.Validation
|
|||||||
.SetValidator(new ServerConfigurationValidator());
|
.SetValidator(new ServerConfigurationValidator());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos
|
namespace SharedLibraryCore.Dtos
|
||||||
{
|
{
|
||||||
@ -12,17 +11,11 @@ namespace SharedLibraryCore.Dtos
|
|||||||
|
|
||||||
public class ClientCountSnapshot
|
public class ClientCountSnapshot
|
||||||
{
|
{
|
||||||
[JsonIgnore]
|
|
||||||
public DateTime Time { get; set; }
|
public DateTime Time { get; set; }
|
||||||
[JsonPropertyName("ts")]
|
|
||||||
public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||||
[JsonPropertyName("cc")]
|
|
||||||
public int ClientCount { get; set; }
|
public int ClientCount { get; set; }
|
||||||
[JsonPropertyName("ci")]
|
|
||||||
public bool ConnectionInterrupted { get;set; }
|
public bool ConnectionInterrupted { get;set; }
|
||||||
[JsonIgnore]
|
|
||||||
public string Map { get; set; }
|
public string Map { get; set; }
|
||||||
[JsonPropertyName("ma")]
|
|
||||||
public string MapAlias { get; set; }
|
public string MapAlias { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Data.Models;
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos
|
namespace SharedLibraryCore.Dtos
|
||||||
{
|
{
|
||||||
@ -15,11 +15,11 @@ namespace SharedLibraryCore.Dtos
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// specifies the game name filter
|
/// specifies the game name filter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Reference.Game? Game { get; set; }
|
public Game? Game { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// collection of unique game names being monitored
|
/// collection of unique game names being monitored
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Reference.Game[] ActiveServerGames { get; set; }
|
public Game[] ActiveServerGames { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events.Management;
|
||||||
|
|
||||||
|
public class NotifyAfterDelayCompleteEvent : ManagementEvent
|
||||||
|
{
|
||||||
|
public Delegate Action { get; init; }
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Events.Management;
|
||||||
|
|
||||||
|
public class NotifyAfterDelayRequestEvent : ManagementEvent
|
||||||
|
{
|
||||||
|
public int DelayMs { get; init; }
|
||||||
|
public Action Action { get; init; }
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Events.Server;
|
|
||||||
|
|
||||||
public class ServerCommandRequestExecuteEvent : GameServerEvent
|
|
||||||
{
|
|
||||||
public ServerCommandRequestExecuteEvent(string command, IGameServer server)
|
|
||||||
{
|
|
||||||
Command = command;
|
|
||||||
Server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Command { get; init; }
|
|
||||||
public int? DelayMs { get; init; }
|
|
||||||
public int? TimeoutMs { get; init; }
|
|
||||||
}
|
|
@ -38,11 +38,6 @@ public interface IGameServerEventSubscriptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
static event Func<ClientDataUpdateEvent, CancellationToken, Task> ClientDataUpdated;
|
static event Func<ClientDataUpdateEvent, CancellationToken, Task> ClientDataUpdated;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised when a command is requested to be executed on a game server
|
|
||||||
/// </summary>
|
|
||||||
static event Func<ServerCommandRequestExecuteEvent, CancellationToken, Task> ServerCommandExecuteRequested;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raised when a command was executed on a game server
|
/// Raised when a command was executed on a game server
|
||||||
/// <value><see cref="ServerCommandExecuteEvent"/></value>
|
/// <value><see cref="ServerCommandExecuteEvent"/></value>
|
||||||
@ -72,17 +67,16 @@ public interface IGameServerEventSubscriptions
|
|||||||
/// <value><see cref="ServerValueSetRequestEvent"/></value>
|
/// <value><see cref="ServerValueSetRequestEvent"/></value>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static event Func<ServerValueSetCompleteEvent, CancellationToken, Task> ServerValueSetCompleted;
|
static event Func<ServerValueSetCompleteEvent, CancellationToken, Task> ServerValueSetCompleted;
|
||||||
|
|
||||||
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||||
{
|
{
|
||||||
return coreEvent switch
|
return coreEvent switch
|
||||||
{
|
{
|
||||||
MonitorStartEvent monitoringStartEvent => MonitoringStarted?.InvokeAsync(monitoringStartEvent, token) ?? Task.CompletedTask,
|
MonitorStartEvent monitoringStartEvent => MonitoringStarted?.InvokeAsync(monitoringStartEvent, token) ?? Task.CompletedTask,
|
||||||
MonitorStopEvent monitorStopEvent => MonitoringStopped?.InvokeAsync(monitorStopEvent, CancellationToken.None) ?? Task.CompletedTask,
|
MonitorStopEvent monitorStopEvent => MonitoringStopped?.InvokeAsync(monitorStopEvent, token) ?? Task.CompletedTask,
|
||||||
ConnectionInterruptEvent connectionInterruptEvent => ConnectionInterrupted?.InvokeAsync(connectionInterruptEvent, token) ?? Task.CompletedTask,
|
ConnectionInterruptEvent connectionInterruptEvent => ConnectionInterrupted?.InvokeAsync(connectionInterruptEvent, token) ?? Task.CompletedTask,
|
||||||
ConnectionRestoreEvent connectionRestoreEvent => ConnectionRestored?.InvokeAsync(connectionRestoreEvent, token) ?? Task.CompletedTask,
|
ConnectionRestoreEvent connectionRestoreEvent => ConnectionRestored?.InvokeAsync(connectionRestoreEvent, token) ?? Task.CompletedTask,
|
||||||
ClientDataUpdateEvent clientDataUpdateEvent => ClientDataUpdated?.InvokeAsync(clientDataUpdateEvent, token) ?? Task.CompletedTask,
|
ClientDataUpdateEvent clientDataUpdateEvent => ClientDataUpdated?.InvokeAsync(clientDataUpdateEvent, token) ?? Task.CompletedTask,
|
||||||
ServerCommandRequestExecuteEvent serverCommandRequestExecuteEvent => ServerCommandExecuteRequested?.InvokeAsync(serverCommandRequestExecuteEvent, token) ?? Task.CompletedTask,
|
|
||||||
ServerCommandExecuteEvent dataReceiveEvent => ServerCommandExecuted?.InvokeAsync(dataReceiveEvent, token) ?? Task.CompletedTask,
|
ServerCommandExecuteEvent dataReceiveEvent => ServerCommandExecuted?.InvokeAsync(dataReceiveEvent, token) ?? Task.CompletedTask,
|
||||||
ServerValueRequestEvent serverValueRequestEvent => ServerValueRequested?.InvokeAsync(serverValueRequestEvent, token) ?? Task.CompletedTask,
|
ServerValueRequestEvent serverValueRequestEvent => ServerValueRequested?.InvokeAsync(serverValueRequestEvent, token) ?? Task.CompletedTask,
|
||||||
ServerValueReceiveEvent serverValueReceiveEvent => ServerValueReceived?.InvokeAsync(serverValueReceiveEvent, token) ?? Task.CompletedTask,
|
ServerValueReceiveEvent serverValueReceiveEvent => ServerValueReceived?.InvokeAsync(serverValueReceiveEvent, token) ?? Task.CompletedTask,
|
||||||
@ -99,7 +93,6 @@ public interface IGameServerEventSubscriptions
|
|||||||
ConnectionInterrupted = null;
|
ConnectionInterrupted = null;
|
||||||
ConnectionRestored = null;
|
ConnectionRestored = null;
|
||||||
ClientDataUpdated = null;
|
ClientDataUpdated = null;
|
||||||
ServerCommandExecuteRequested = null;
|
|
||||||
ServerCommandExecuted = null;
|
ServerCommandExecuted = null;
|
||||||
ServerValueReceived = null;
|
ServerValueReceived = null;
|
||||||
ServerValueRequested = null;
|
ServerValueRequested = null;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharedLibraryCore.Interfaces;
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
@ -8,5 +7,4 @@ public interface IConfigurationHandlerV2<TConfigurationType> where TConfiguratio
|
|||||||
Task<TConfigurationType> Get(string configurationName, TConfigurationType defaultConfiguration = null);
|
Task<TConfigurationType> Get(string configurationName, TConfigurationType defaultConfiguration = null);
|
||||||
Task Set(TConfigurationType configuration);
|
Task Set(TConfigurationType configuration);
|
||||||
Task Set();
|
Task Set();
|
||||||
event Action<TConfigurationType> Updated;
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
using SharedLibraryCore.Database.Models;
|
using SharedLibraryCore.Database.Models;
|
||||||
@ -18,23 +17,6 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="previousPenalty">previous penalty the kick is occuring for (if applicable)</param>
|
/// <param name="previousPenalty">previous penalty the kick is occuring for (if applicable)</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute a server command
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">Server command to execute</param>
|
|
||||||
/// <param name="token"><see cref="CancellationToken"/></param>
|
|
||||||
/// <returns>Collection of console command output lines</returns>
|
|
||||||
Task<string[]> ExecuteCommandAsync(string command, CancellationToken token = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set value for server dvar
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">Name of the server value to set</param>
|
|
||||||
/// <param name="value">Value of the server value</param>
|
|
||||||
/// <param name="token"><see cref="CancellationToken"/></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task SetDvarAsync(string name, object value, CancellationToken token = default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time the most recent match ended
|
/// Time the most recent match ended
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Interfaces
|
namespace SharedLibraryCore.Interfaces
|
||||||
@ -16,21 +15,19 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// Retrieves the max concurrent clients over a give time period for all servers or given server id
|
/// Retrieves the max concurrent clients over a give time period for all servers or given server id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serverId">ServerId to query on</param>
|
/// <param name="serverId">ServerId to query on</param>
|
||||||
/// <param name="gameCode"><see cref="Reference.Game"/></param>
|
|
||||||
/// <param name="overPeriod">how far in the past to search</param>
|
/// <param name="overPeriod">how far in the past to search</param>
|
||||||
/// <param name="token">CancellationToken</param>
|
/// <param name="token">CancellationToken</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, Reference.Game? gameCode = null, TimeSpan? overPeriod = null,
|
Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
||||||
CancellationToken token = default);
|
CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total number of clients connected and total clients connected in the given time frame
|
/// Gets the total number of clients connected and total clients connected in the given time frame
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="overPeriod">how far in the past to search</param>
|
/// <param name="overPeriod">how far in the past to search</param>
|
||||||
/// <param name="gameCode"><see cref="Reference.Game"/></param>
|
|
||||||
/// <param name="token">CancellationToken</param>
|
/// <param name="token">CancellationToken</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, Reference.Game? gameCode = null, CancellationToken token = default);
|
Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the client count and history over the given period
|
/// Retrieves the client count and history over the given period
|
||||||
|
@ -117,9 +117,6 @@ namespace SharedLibraryCore.Database.Models
|
|||||||
[NotMapped] public TeamType Team { get; set; }
|
[NotMapped] public TeamType Team { get; set; }
|
||||||
[NotMapped] public string TeamName { get; set; }
|
[NotMapped] public string TeamName { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public string TimeSinceLastConnectionString => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture();
|
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
// this is kinda dirty, but I need localizable level names
|
// this is kinda dirty, but I need localizable level names
|
||||||
public ClientPermission ClientPermission => new ClientPermission
|
public ClientPermission ClientPermission => new ClientPermission
|
||||||
|
@ -35,8 +35,7 @@ namespace SharedLibraryCore
|
|||||||
T7 = 8,
|
T7 = 8,
|
||||||
SHG1 = 9,
|
SHG1 = 9,
|
||||||
CSGO = 10,
|
CSGO = 10,
|
||||||
H1 = 11,
|
H1 = 11
|
||||||
L4D2 = 12
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// only here for performance
|
// only here for performance
|
||||||
@ -164,8 +163,6 @@ namespace SharedLibraryCore
|
|||||||
public int Port { get; protected set; }
|
public int Port { get; protected set; }
|
||||||
public int ListenPort => Port;
|
public int ListenPort => Port;
|
||||||
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
|
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
|
||||||
public abstract Task<string[]> ExecuteCommandAsync(string command, CancellationToken token = default);
|
|
||||||
public abstract Task SetDvarAsync(string name, object value, CancellationToken token = default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns list of all current players
|
/// Returns list of all current players
|
||||||
@ -173,10 +170,7 @@ namespace SharedLibraryCore
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<EFClient> GetClientsAsList()
|
public List<EFClient> GetClientsAsList()
|
||||||
{
|
{
|
||||||
lock (Clients)
|
return Clients.FindAll(x => x != null && x.NetworkId != 0);
|
||||||
{
|
|
||||||
return Clients.FindAll(client => client is not null && client.NetworkId != 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -215,8 +215,7 @@ namespace SharedLibraryCore.Services
|
|||||||
return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync();
|
return await activePenaltiesIds.Select(ids => ids.Penalty).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, Reference.Game game, int? ipAddress = null,
|
public virtual async Task RemoveActivePenalties(int aliasLinkId, long networkId, Reference.Game game, int? ipAddress = null)
|
||||||
EFPenalty.PenaltyType[] penaltyTypes = null)
|
|
||||||
{
|
{
|
||||||
await using var context = _contextFactory.CreateContext();
|
await using var context = _contextFactory.CreateContext();
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
@ -227,7 +226,6 @@ namespace SharedLibraryCore.Services
|
|||||||
{
|
{
|
||||||
var ids = activePenalties.Select(penalty => penalty.PenaltyId);
|
var ids = activePenalties.Select(penalty => penalty.PenaltyId);
|
||||||
await context.Penalties.Where(penalty => ids.Contains(penalty.PenaltyId))
|
await context.Penalties.Where(penalty => ids.Contains(penalty.PenaltyId))
|
||||||
.Where(pen => penaltyTypes == null || penaltyTypes.Contains(pen.Type))
|
|
||||||
.ForEachAsync(penalty =>
|
.ForEachAsync(penalty =>
|
||||||
{
|
{
|
||||||
penalty.Active = false;
|
penalty.Active = false;
|
||||||
|
@ -25,7 +25,6 @@ using SharedLibraryCore.Helpers;
|
|||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.Localization;
|
using SharedLibraryCore.Localization;
|
||||||
using SharedLibraryCore.RCon;
|
using SharedLibraryCore.RCon;
|
||||||
using static System.Threading.Tasks.Task;
|
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
using static Data.Models.Client.EFClient;
|
using static Data.Models.Client.EFClient;
|
||||||
using static Data.Models.EFPenalty;
|
using static Data.Models.EFPenalty;
|
||||||
@ -50,8 +49,8 @@ namespace SharedLibraryCore
|
|||||||
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
||||||
public static char CommandPrefix { get; set; } = '!';
|
public static char CommandPrefix { get; set; } = '!';
|
||||||
|
|
||||||
public static string ToStandardFormat(this DateTime? time) => time?.ToString("yyyy-MM-dd HH:mm:ss UTC");
|
public static string ToStandardFormat(this DateTime? time) => time?.ToString("yyyy-MM-dd H:mm:ss UTC");
|
||||||
public static string ToStandardFormat(this DateTime time) => time.ToString("yyyy-MM-dd HH:mm:ss UTC");
|
public static string ToStandardFormat(this DateTime time) => time.ToString("yyyy-MM-dd H:mm:ss UTC");
|
||||||
|
|
||||||
public static EFClient IW4MAdminClient(Server server = null)
|
public static EFClient IW4MAdminClient(Server server = null)
|
||||||
{
|
{
|
||||||
@ -195,7 +194,7 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
var output = str;
|
var output = str;
|
||||||
var colorCodeMatches = Regex.Matches(output, @"\(Color::(\w{1,16})\)",
|
var colorCodeMatches = Regex.Matches(output, @"\(Color::(.{1,16})\)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
foreach (var match in colorCodeMatches.Where(m => m.Success))
|
foreach (var match in colorCodeMatches.Where(m => m.Success))
|
||||||
{
|
{
|
||||||
@ -887,7 +886,7 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
if (delay != null)
|
if (delay != null)
|
||||||
{
|
{
|
||||||
await Delay(delay.Value);
|
await Task.Delay(delay.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = await server.RemoteConnection.SendQueryAsync(StaticHelpers.QueryType.GET_INFO);
|
var response = await server.RemoteConnection.SendQueryAsync(StaticHelpers.QueryType.GET_INFO);
|
||||||
@ -1054,7 +1053,7 @@ namespace SharedLibraryCore
|
|||||||
public static async Task WithWaitCancellation(this Task task,
|
public static async Task WithWaitCancellation(this Task task,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var completedTask = await WhenAny(task, Delay(Timeout.Infinite, cancellationToken));
|
var completedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, cancellationToken));
|
||||||
if (completedTask == task)
|
if (completedTask == task)
|
||||||
{
|
{
|
||||||
await task;
|
await task;
|
||||||
@ -1069,7 +1068,7 @@ namespace SharedLibraryCore
|
|||||||
public static async Task<T> WithWaitCancellation<T>(this Task<T> task,
|
public static async Task<T> WithWaitCancellation<T>(this Task<T> task,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var completedTask = await WhenAny(task, Delay(Timeout.Infinite, cancellationToken));
|
var completedTask = await Task.WhenAny(task, Task.Delay(Timeout.Infinite, cancellationToken));
|
||||||
if (completedTask == task)
|
if (completedTask == task)
|
||||||
{
|
{
|
||||||
return await task;
|
return await task;
|
||||||
@ -1081,13 +1080,13 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
|
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
|
||||||
{
|
{
|
||||||
await WhenAny(task, Delay(timeout));
|
await Task.WhenAny(task, Task.Delay(timeout));
|
||||||
return await task;
|
return await task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task WithTimeout(this Task task, TimeSpan timeout)
|
public static async Task WithTimeout(this Task task, TimeSpan timeout)
|
||||||
{
|
{
|
||||||
await WhenAny(task, Delay(timeout));
|
await Task.WhenAny(task, Task.Delay(timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ShouldHideLevel(this Permission perm)
|
public static bool ShouldHideLevel(this Permission perm)
|
||||||
@ -1145,7 +1144,7 @@ namespace SharedLibraryCore
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static bool IsDevelopment =>
|
public static bool IsDevelopment =>
|
||||||
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" || AppContext.TryGetSwitch("IsDevelop", out _);
|
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// replaces any directory separator chars with the platform specific character
|
/// replaces any directory separator chars with the platform specific character
|
||||||
@ -1304,7 +1303,7 @@ namespace SharedLibraryCore
|
|||||||
serviceProvider.GetRequiredService<IConfigurationHandlerV2<TConfigurationType>>();
|
serviceProvider.GetRequiredService<IConfigurationHandlerV2<TConfigurationType>>();
|
||||||
|
|
||||||
var configuration =
|
var configuration =
|
||||||
Run(() => configurationHandler.Get(fileName ?? typeof(TConfigurationType).Name, defaultConfig))
|
Task.Run(() => configurationHandler.Get(fileName ?? typeof(TConfigurationType).Name, defaultConfig))
|
||||||
.GetAwaiter().GetResult();
|
.GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (typeof(TConfigurationType).GetInterface(nameof(IBaseConfiguration)) is not null &&
|
if (typeof(TConfigurationType).GetInterface(nameof(IBaseConfiguration)) is not null &&
|
||||||
@ -1317,7 +1316,7 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
if (defaultConfig is not null && configuration is null)
|
if (defaultConfig is not null && configuration is null)
|
||||||
{
|
{
|
||||||
Run(() => configurationHandler.Set(defaultConfig)).GetAwaiter().GetResult();
|
Task.Run(() => configurationHandler.Set(defaultConfig)).GetAwaiter().GetResult();
|
||||||
configuration = defaultConfig;
|
configuration = defaultConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1333,20 +1332,17 @@ namespace SharedLibraryCore
|
|||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ExecuteAfterDelay(TimeSpan duration, Func<CancellationToken, Task> action, CancellationToken token = default) =>
|
public static void NotifyAfterDelay(TimeSpan duration, Func<Task> action) =>
|
||||||
ExecuteAfterDelay((int)duration.TotalMilliseconds, action, token);
|
NotifyAfterDelay((int)duration.TotalMilliseconds, action);
|
||||||
|
|
||||||
public static void ExecuteAfterDelay(int delayMs, Func<CancellationToken, Task> action, CancellationToken token = default)
|
public static void NotifyAfterDelay(int delayMs, Func<Task> action)
|
||||||
{
|
{
|
||||||
// ReSharper disable once MethodSupportsCancellation
|
Task.Run(async () =>
|
||||||
#pragma warning disable CA2016
|
|
||||||
_ = Run(async () =>
|
|
||||||
#pragma warning restore CA2016
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Delay(delayMs, token);
|
await Task.Delay(delayMs);
|
||||||
await action(token);
|
await action();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -1354,8 +1350,5 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ExecuteAfterDelay(this Func<CancellationToken, Task> action, int delayMs,
|
|
||||||
CancellationToken token = default) => ExecuteAfterDelay(delayMs, action, token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
@ -22,13 +21,13 @@ public class Info : BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Get(int period = 24, Reference.Game? game = null, CancellationToken token = default)
|
public async Task<IActionResult> Get(int period = 24, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// todo: this is hardcoded currently because the cache doesn't take into consideration the duration, so
|
// todo: this is hardcoded currently because the cache doesn't take into consideration the duration, so
|
||||||
// we could impact the webfront usage too
|
// we could impact the webfront usage too
|
||||||
var duration = TimeSpan.FromHours(24);
|
var duration = TimeSpan.FromHours(24);
|
||||||
var (totalClients, totalRecentClients) =
|
var (totalClients, totalRecentClients) =
|
||||||
await _serverDataViewer.ClientCountsAsync(duration, game, token);
|
await _serverDataViewer.ClientCountsAsync(duration, token);
|
||||||
var (maxConcurrent, maxConcurrentTime) = await _serverDataViewer.MaxConcurrentClientsAsync(overPeriod: duration, token: token);
|
var (maxConcurrent, maxConcurrentTime) = await _serverDataViewer.MaxConcurrentClientsAsync(overPeriod: duration, token: token);
|
||||||
var response = new InfoResponse
|
var response = new InfoResponse
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Configuration;
|
|
||||||
using SharedLibraryCore.Dtos;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using WebfrontCore.Controllers.API.Models;
|
using WebfrontCore.Controllers.API.Models;
|
||||||
|
|
||||||
@ -16,14 +12,9 @@ namespace WebfrontCore.Controllers.API
|
|||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class Server : BaseController
|
public class Server : BaseController
|
||||||
{
|
{
|
||||||
private readonly IServerDataViewer _serverDataViewer;
|
|
||||||
private readonly ApplicationConfiguration _applicationConfiguration;
|
public Server(IManager manager) : base(manager)
|
||||||
|
|
||||||
public Server(IManager manager, IServerDataViewer serverDataViewer,
|
|
||||||
ApplicationConfiguration applicationConfiguration) : base(manager)
|
|
||||||
{
|
{
|
||||||
_serverDataViewer = serverDataViewer;
|
|
||||||
_applicationConfiguration = applicationConfiguration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -119,48 +110,5 @@ namespace WebfrontCore.Controllers.API
|
|||||||
completedEvent.Output
|
completedEvent.Output
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/history")]
|
|
||||||
public async Task<IActionResult> 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<ClientCountSnapshot>()
|
|
||||||
};
|
|
||||||
|
|
||||||
var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty<ClientCountSnapshot>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,8 +249,7 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
ViewBag.Title = Localization["WEBFRONT_SEARCH_RESULTS_TITLE"];
|
ViewBag.Title = Localization["WEBFRONT_SEARCH_RESULTS_TITLE"];
|
||||||
ViewBag.ClientResourceRequest = request;
|
ViewBag.ClientResourceRequest = request;
|
||||||
|
|
||||||
request.RequesterPermission = Client.Level;
|
|
||||||
var response = await _clientResourceHelper.QueryResource(request);
|
var response = await _clientResourceHelper.QueryResource(request);
|
||||||
return request.Offset > 0
|
return request.Offset > 0
|
||||||
? PartialView("Find/_AdvancedFindList", response.Results)
|
? PartialView("Find/_AdvancedFindList", response.Results)
|
||||||
|
@ -17,7 +17,6 @@ using Microsoft.Extensions.Logging;
|
|||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using WebfrontCore.QueryHelpers.Models;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||||
{
|
{
|
||||||
@ -122,7 +121,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Message/Find")]
|
[HttpGet("Message/Find")]
|
||||||
public async Task<IActionResult> FindMessage([FromQuery] ChatResourceRequest query)
|
public async Task<IActionResult> FindMessage([FromQuery] string query)
|
||||||
{
|
{
|
||||||
ViewBag.Localization = _translationLookup;
|
ViewBag.Localization = _translationLookup;
|
||||||
ViewBag.EnableColorCodes = _manager.GetApplicationSettings().Configuration().EnableColorCodes;
|
ViewBag.EnableColorCodes = _manager.GetApplicationSettings().Configuration().EnableColorCodes;
|
||||||
@ -131,15 +130,53 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
ViewBag.Title = _translationLookup["WEBFRONT_STATS_MESSAGES_TITLE"];
|
ViewBag.Title = _translationLookup["WEBFRONT_STATS_MESSAGES_TITLE"];
|
||||||
ViewBag.Error = null;
|
ViewBag.Error = null;
|
||||||
ViewBag.IsFluid = true;
|
ViewBag.IsFluid = true;
|
||||||
|
ChatSearchQuery searchRequest = null;
|
||||||
var result = query != null ? await _chatResourceQueryHelper.QueryResource(query) : null;
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchRequest = query.ParseSearchInfo(int.MaxValue, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (ArgumentException e)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(e, "Could not parse chat message search query {query}", query);
|
||||||
|
ViewBag.Error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (FormatException e)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(e, "Could not parse chat message search query filter format {query}", query);
|
||||||
|
ViewBag.Error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = searchRequest != null ? await _chatResourceQueryHelper.QueryResource(searchRequest) : null;
|
||||||
return View("~/Views/Client/Message/Find.cshtml", result);
|
return View("~/Views/Client/Message/Find.cshtml", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Message/FindNext")]
|
[HttpGet("Message/FindNext")]
|
||||||
public async Task<IActionResult> FindNextMessages(ChatResourceRequest query)
|
public async Task<IActionResult> FindNextMessages([FromQuery] string query, [FromQuery] int count,
|
||||||
|
[FromQuery] int offset)
|
||||||
{
|
{
|
||||||
var result = await _chatResourceQueryHelper.QueryResource(query);
|
ChatSearchQuery searchRequest;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchRequest = query.ParseSearchInfo(count, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (ArgumentException e)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(e, "Could not parse chat message search query {query}", query);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (FormatException e)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(e, "Could not parse chat message search query filter format {query}", query);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _chatResourceQueryHelper.QueryResource(searchRequest);
|
||||||
return PartialView("~/Views/Client/Message/_Item.cshtml", result.Results);
|
return PartialView("~/Views/Client/Message/_Item.cshtml", result.Results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
50
WebfrontCore/Controllers/DynamicFileController.cs
Normal file
50
WebfrontCore/Controllers/DynamicFileController.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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<string, string> _fileCache = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public DynamicFileController(IManager manager) : base(manager)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("css/{fileName}")]
|
||||||
|
public async Task<IActionResult> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
@ -8,8 +7,8 @@ using SharedLibraryCore.Interfaces;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace WebfrontCore.Controllers
|
namespace WebfrontCore.Controllers
|
||||||
@ -19,44 +18,35 @@ namespace WebfrontCore.Controllers
|
|||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerDataViewer _serverDataViewer;
|
private readonly IServerDataViewer _serverDataViewer;
|
||||||
private readonly ILookup<Type, string> _pluginTypeNames;
|
|
||||||
|
|
||||||
public HomeController(ILogger<HomeController> logger, IManager manager, ITranslationLookup translationLookup,
|
public HomeController(ILogger<HomeController> logger, IManager manager, ITranslationLookup translationLookup,
|
||||||
IServerDataViewer serverDataViewer, IEnumerable<IPlugin> v1Plugins, IEnumerable<IPluginV2> v2Plugins) : base(manager)
|
IServerDataViewer serverDataViewer) : base(manager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
_serverDataViewer = serverDataViewer;
|
_serverDataViewer = serverDataViewer;
|
||||||
_pluginTypeNames = v1Plugins.Select(plugin => (plugin.GetType(), plugin.Name))
|
|
||||||
.Concat(v2Plugins.Select(plugin => (plugin.GetType(), plugin.Name)))
|
|
||||||
.ToLookup(selector => selector.Item1, selector => selector.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index(Reference.Game? game = null,
|
public async Task<IActionResult> Index(Game? game = null, CancellationToken cancellationToken = default)
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
{
|
||||||
ViewBag.Description = Localization["WEBFRONT_DESCRIPTION_HOME"];
|
ViewBag.Description = Localization["WEBFRONT_DESCRIPTION_HOME"];
|
||||||
ViewBag.Title = Localization["WEBFRONT_HOME_TITLE"];
|
ViewBag.Title = Localization["WEBFRONT_HOME_TITLE"];
|
||||||
ViewBag.Keywords = Localization["WEBFRONT_KEWORDS_HOME"];
|
ViewBag.Keywords = Localization["WEBFRONT_KEWORDS_HOME"];
|
||||||
|
|
||||||
var servers = Manager.GetServers().Where(server => game is null || server.GameName == (Server.Game?)game)
|
var servers = Manager.GetServers().Where(_server => !game.HasValue || _server.GameName == game);
|
||||||
.ToList();
|
var (clientCount, time) = await _serverDataViewer.MaxConcurrentClientsAsync(token: cancellationToken);
|
||||||
var (clientCount, time) =
|
var (count, recentCount) = await _serverDataViewer.ClientCountsAsync(token: cancellationToken);
|
||||||
await _serverDataViewer.MaxConcurrentClientsAsync(gameCode: game, token: cancellationToken);
|
|
||||||
var (count, recentCount) =
|
|
||||||
await _serverDataViewer.ClientCountsAsync(gameCode: game, token: cancellationToken);
|
|
||||||
|
|
||||||
var model = new IW4MAdminInfo
|
var model = new IW4MAdminInfo()
|
||||||
{
|
{
|
||||||
TotalAvailableClientSlots = servers.Sum(server => server.MaxClients),
|
TotalAvailableClientSlots = servers.Sum(_server => _server.MaxClients),
|
||||||
TotalOccupiedClientSlots = servers.SelectMany(server => server.GetClientsAsList()).Count(),
|
TotalOccupiedClientSlots = servers.SelectMany(_server => _server.GetClientsAsList()).Count(),
|
||||||
TotalClientCount = count,
|
TotalClientCount = count,
|
||||||
RecentClientCount = recentCount,
|
RecentClientCount = recentCount,
|
||||||
MaxConcurrentClients = clientCount ?? 0,
|
MaxConcurrentClients = clientCount ?? 0,
|
||||||
MaxConcurrentClientsTime = time ?? DateTime.UtcNow,
|
MaxConcurrentClientsTime = time ?? DateTime.UtcNow,
|
||||||
Game = game,
|
Game = game,
|
||||||
ActiveServerGames = Manager.GetServers().Select(server => (Reference.Game)server.GameName).Distinct()
|
ActiveServerGames = Manager.GetServers().Select(_server => _server.GameName).Distinct().ToArray()
|
||||||
.ToArray()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return View(model);
|
return View(model);
|
||||||
@ -101,9 +91,9 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pluginType = command.GetType().Assembly.GetTypes()
|
var pluginType = command.GetType().Assembly.GetTypes()
|
||||||
.FirstOrDefault(type => typeof(IPlugin).IsAssignableFrom(type) || typeof(IPluginV2).IsAssignableFrom(type));
|
.FirstOrDefault(type => typeof(IPlugin).IsAssignableFrom(type));
|
||||||
|
return Manager.Plugins.FirstOrDefault(plugin => plugin.GetType() == pluginType)?.Name ??
|
||||||
return _pluginTypeNames[pluginType].FirstOrDefault() ?? _translationLookup["WEBFRONT_HELP_COMMAND_NATIVE"];
|
_translationLookup["WEBFRONT_HELP_COMMAND_NATIVE"];
|
||||||
})
|
})
|
||||||
.Select(group => (group.Key, group.AsEnumerable()));
|
.Select(group => (group.Key, group.AsEnumerable()));
|
||||||
|
|
||||||
|
104
WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs
Normal file
104
WebfrontCore/Middleware/CustomCssAccentMiddlewareAction.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
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<string>
|
||||||
|
{
|
||||||
|
private readonly List<ColorMap> ColorReplacements = new List<ColorMap>();
|
||||||
|
|
||||||
|
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<string> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// converts color to the hex string representation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string ColorToHex(Color color) => $"#{color.R.ToString("X2")}{color.G.ToString("X2")}{color.B.ToString("X2")}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// converts color to the rgb tuples representation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string ColorToDec(Color color) => $"{(int)color.R}, {(int)color.G}, {(int)color.B}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// lightens or darkens a color on the given amount
|
||||||
|
/// Based off SASS darken/lighten function
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color"></param>
|
||||||
|
/// <param name="amount"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using WebfrontCore.Middleware;
|
||||||
|
|
||||||
namespace WebfrontCore
|
namespace WebfrontCore
|
||||||
{
|
{
|
||||||
@ -23,6 +24,11 @@ namespace WebfrontCore
|
|||||||
|
|
||||||
public static Task GetWebHostTask(CancellationToken cancellationToken)
|
public static Task GetWebHostTask(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var config = _webHost.Services.GetRequiredService<ApplicationConfiguration>();
|
||||||
|
Manager.MiddlewareActionHandler.Register(null,
|
||||||
|
new CustomCssAccentMiddlewareAction("#007ACC", "#fd7e14", config.WebfrontPrimaryColor,
|
||||||
|
config.WebfrontSecondaryColor), "custom_css_accent");
|
||||||
|
|
||||||
return _webHost?.RunAsync(cancellationToken);
|
return _webHost?.RunAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,12 +41,7 @@ namespace WebfrontCore
|
|||||||
.UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory)
|
.UseContentRoot(SharedLibraryCore.Utilities.OperatingDirectory)
|
||||||
#endif
|
#endif
|
||||||
.UseUrls(bindUrl)
|
.UseUrls(bindUrl)
|
||||||
.UseKestrel(cfg =>
|
.UseKestrel()
|
||||||
{
|
|
||||||
cfg.Limits.MaxConcurrentConnections =
|
|
||||||
int.Parse(Environment.GetEnvironmentVariable("MaxConcurrentRequests") ?? "1");
|
|
||||||
cfg.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(30);
|
|
||||||
})
|
|
||||||
.ConfigureServices(registerDependenciesAction)
|
.ConfigureServices(registerDependenciesAction)
|
||||||
.UseStartup<Startup>()
|
.UseStartup<Startup>()
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
using Stats.Dtos;
|
|
||||||
|
|
||||||
namespace WebfrontCore.QueryHelpers.Models;
|
|
||||||
|
|
||||||
public class ChatResourceRequest : ChatSearchQuery
|
|
||||||
{
|
|
||||||
public bool HasData => !string.IsNullOrEmpty(MessageContains) || !string.IsNullOrEmpty(ServerId) ||
|
|
||||||
ClientId is not null || SentAfterDateTime is not null;
|
|
||||||
}
|
|
@ -16,9 +16,4 @@ public class ClientResourceRequest : ClientPaginationRequest
|
|||||||
public EFClient.Permission? ClientLevel { get; set; }
|
public EFClient.Permission? ClientLevel { get; set; }
|
||||||
public Reference.Game? GameName { get; set; }
|
public Reference.Game? GameName { get; set; }
|
||||||
public bool IncludeGeolocationData { get; set; } = true;
|
public bool IncludeGeolocationData { get; set; } = true;
|
||||||
|
|
||||||
public EFClient.Permission RequesterPermission { get; set; } = EFClient.Permission.User;
|
|
||||||
|
|
||||||
public bool HasData => !string.IsNullOrEmpty(ClientName) || !string.IsNullOrEmpty(ClientIp) ||
|
|
||||||
!string.IsNullOrEmpty(ClientGuid) || ClientLevel is not null || GameName is not null;
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using FluentValidation.AspNetCore;
|
|||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -23,6 +24,9 @@ using System.Reflection;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Helpers;
|
using Data.Helpers;
|
||||||
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
|
using Stats.Client.Abstractions;
|
||||||
|
using Stats.Config;
|
||||||
using WebfrontCore.Controllers.API.Validation;
|
using WebfrontCore.Controllers.API.Validation;
|
||||||
using WebfrontCore.Middleware;
|
using WebfrontCore.Middleware;
|
||||||
using WebfrontCore.QueryHelpers;
|
using WebfrontCore.QueryHelpers;
|
||||||
@ -46,12 +50,6 @@ namespace WebfrontCore
|
|||||||
.AllowAnyHeader();
|
.AllowAnyHeader();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddStackPolicy(options =>
|
|
||||||
{
|
|
||||||
options.MaxConcurrentRequests = int.Parse(Environment.GetEnvironmentVariable("MaxConcurrentRequests") ?? "1");
|
|
||||||
options.RequestQueueLimit = int.Parse(Environment.GetEnvironmentVariable("RequestQueueLimit") ?? "1");
|
|
||||||
});
|
|
||||||
|
|
||||||
IEnumerable<Assembly> pluginAssemblies()
|
IEnumerable<Assembly> pluginAssemblies()
|
||||||
{
|
{
|
||||||
@ -134,7 +132,6 @@ namespace WebfrontCore
|
|||||||
app.UseMiddleware<IPWhitelist>(serviceProvider.GetService<ILogger<IPWhitelist>>(), serviceProvider.GetRequiredService<ApplicationConfiguration>().WebfrontConnectionWhitelist);
|
app.UseMiddleware<IPWhitelist>(serviceProvider.GetService<ILogger<IPWhitelist>>(), serviceProvider.GetRequiredService<ApplicationConfiguration>().WebfrontConnectionWhitelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseConcurrencyLimiter();
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseCors("AllowAll");
|
app.UseCors("AllowAll");
|
||||||
|
@ -1,43 +1,70 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace WebfrontCore.ViewComponents
|
namespace WebfrontCore.ViewComponents
|
||||||
{
|
{
|
||||||
public class ServerListViewComponent : ViewComponent
|
public class ServerListViewComponent : ViewComponent
|
||||||
{
|
{
|
||||||
|
private readonly IServerDataViewer _serverDataViewer;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private readonly DefaultSettings _defaultSettings;
|
private readonly DefaultSettings _defaultSettings;
|
||||||
|
|
||||||
public ServerListViewComponent(DefaultSettings defaultSettings)
|
public ServerListViewComponent(IServerDataViewer serverDataViewer,
|
||||||
|
ApplicationConfiguration applicationConfiguration, DefaultSettings defaultSettings)
|
||||||
{
|
{
|
||||||
|
_serverDataViewer = serverDataViewer;
|
||||||
|
_appConfig = applicationConfiguration;
|
||||||
_defaultSettings = defaultSettings;
|
_defaultSettings = defaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IViewComponentResult Invoke(Reference.Game? game)
|
public IViewComponentResult Invoke(Game? game)
|
||||||
{
|
{
|
||||||
if (game.HasValue)
|
if (game.HasValue)
|
||||||
{
|
{
|
||||||
ViewBag.Maps = _defaultSettings.Maps?.FirstOrDefault(map => map.Game == (Server.Game)game)?.Maps
|
ViewBag.Maps = _defaultSettings.Maps.FirstOrDefault(map => map.Game == game)?.Maps.ToList() ??
|
||||||
?.ToList() ?? new List<Map>();
|
new List<Map>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ViewBag.Maps = _defaultSettings.Maps?.SelectMany(maps => maps.Maps).ToList();
|
ViewBag.Maps = _defaultSettings.Maps.SelectMany(maps => maps.Maps).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var servers = Program.Manager.GetServers()
|
var servers = Program.Manager.GetServers().Where(server => !game.HasValue || server.GameName == game);
|
||||||
.Where(server => game is null || server.GameName == (Server.Game)game);
|
|
||||||
|
|
||||||
var serverInfo = new List<ServerInfo>();
|
var serverInfo = new List<ServerInfo>();
|
||||||
|
|
||||||
foreach (var server in servers)
|
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<ClientCountSnapshot>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty<ClientCountSnapshot>();
|
||||||
|
|
||||||
|
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
|
serverInfo.Add(new ServerInfo
|
||||||
{
|
{
|
||||||
Name = server.Hostname,
|
Name = server.Hostname,
|
||||||
@ -49,7 +76,11 @@ namespace WebfrontCore.ViewComponents
|
|||||||
MaxClients = server.MaxClients,
|
MaxClients = server.MaxClients,
|
||||||
PrivateClientSlots = server.PrivateClientSlots,
|
PrivateClientSlots = server.PrivateClientSlots,
|
||||||
GameType = server.GametypeName,
|
GameType = server.GametypeName,
|
||||||
ClientHistory = new ClientHistoryInfo(),
|
ClientHistory = new ClientHistoryInfo
|
||||||
|
{
|
||||||
|
ServerId = server.EndPoint,
|
||||||
|
ClientCounts = counts.ToList()
|
||||||
|
},
|
||||||
Players = server.GetClientsAsList()
|
Players = server.GetClientsAsList()
|
||||||
.Select(client => new PlayerInfo
|
.Select(client => new PlayerInfo
|
||||||
{
|
{
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var start = 1;
|
var start = 1;
|
||||||
<h5 class="text-primary mt-0 mb-0">
|
<h5 class="text-primary mt-0">
|
||||||
<color-code value="@serverName"></color-code>
|
<color-code value="@serverName"></color-code>
|
||||||
</h5>
|
</h5>
|
||||||
@foreach (var rule in rules)
|
@foreach (var rule in rules)
|
||||||
@ -86,7 +86,6 @@
|
|||||||
</div>
|
</div>
|
||||||
start++;
|
start++;
|
||||||
}
|
}
|
||||||
<div class="mb-20"></div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,14 +26,14 @@ else
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="loaderLoad" class="mt-10 m-auto text-center">
|
<div id="loaderLoad" class="mt-10 m-auto text-center d-none d-lg-block">
|
||||||
<i class="loader-load-more oi oi-chevron-bottom"></i>
|
<i class="loader-load-more oi oi-chevron-bottom"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@section scripts {
|
@section scripts {
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
initLoader(`/Message/FindNext${window.location.search}`, '#message_table_body', @Model.RetrievedResultCount, 30);
|
initLoader('/Message/FindNext?query=@ViewBag.Query', '#message_table_body', @Model.RetrievedResultCount, @ViewBag.QueryLimit);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user