Compare commits
40 Commits
2022.07.23
...
2022.10.13
Author | SHA1 | Date | |
---|---|---|---|
76925a78d4 | |||
7b869a3f43 | |||
0ce9dec3ea | |||
069e6a0517 | |||
778feb8024 | |||
44f22dae3a | |||
cf3209e1d0 | |||
a15da15d3e | |||
3b83729457 | |||
407ce2bc8f | |||
24d91f228b | |||
53cbd11008 | |||
186db53bad | |||
40466f84c4 | |||
bdb5a1c5f8 | |||
5d9e2b3bf1 | |||
1cf99869f6 | |||
12da0f463b | |||
e88071684d | |||
cd6097d133 | |||
d5cf4451a2 | |||
1e1e8bbe7b | |||
dadd236069 | |||
2380f23dbe | |||
3cffdfdd9d | |||
400c5d1f4d | |||
ca35fbb19f | |||
809cb0b7f4 | |||
18f23fd07d | |||
7526f86dab | |||
527ffbaced | |||
6f086ac565 | |||
cf4dd6a868 | |||
3efafa24ff | |||
fe919251fb | |||
a67f7f9351 | |||
e99ca3c140 | |||
ccedb01e8d | |||
841bcf6156 | |||
b381af5fba |
@ -26,12 +26,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2038" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="RestEase" Version="1.5.5" />
|
||||
<PackageReference Include="RestEase" Version="1.5.7" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -46,6 +46,8 @@ namespace IW4MAdmin.Application
|
||||
|
||||
public IList<IRConParser> AdditionalRConParsers { get; }
|
||||
public IList<IEventParser> AdditionalEventParsers { get; }
|
||||
public IList<Func<GameEvent, bool>> CommandInterceptors { get; set; } =
|
||||
new List<Func<GameEvent, bool>>();
|
||||
public ITokenAuthentication TokenAuthenticator { get; }
|
||||
public CancellationToken CancellationToken => _tokenSource.Token;
|
||||
public string ExternalIPAddress { get; private set; }
|
||||
|
@ -7,6 +7,6 @@ foreach($localization in $localizations)
|
||||
{
|
||||
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||
$filePath = "{0}Localization\IW4MAdmin.{1}.json" -f $OutputDir, $localization
|
||||
$response = Invoke-WebRequest $url
|
||||
$response = Invoke-WebRequest $url -UseBasicParsing
|
||||
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +105,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
{"say", GameEvent.EventType.Say},
|
||||
{"sayteam", GameEvent.EventType.Say},
|
||||
{"chat", GameEvent.EventType.Say},
|
||||
{"chatteam", GameEvent.EventType.Say},
|
||||
{"K", GameEvent.EventType.Kill},
|
||||
{"D", GameEvent.EventType.Damage},
|
||||
{"J", GameEvent.EventType.PreConnect},
|
||||
|
@ -158,8 +158,6 @@ namespace IW4MAdmin
|
||||
await E.Origin.Lock();
|
||||
}
|
||||
|
||||
var canExecuteCommand = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (!await ProcessEvent(E))
|
||||
@ -188,32 +186,31 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
var canExecuteCommand = Manager.CommandInterceptors.All(interceptor =>
|
||||
{
|
||||
var loginPlugin = Manager.Plugins.FirstOrDefault(plugin => plugin.Name == "Login");
|
||||
|
||||
if (loginPlugin != null)
|
||||
try
|
||||
{
|
||||
await loginPlugin.OnEventAsync(E, this);
|
||||
return interceptor(E);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!canExecuteCommand)
|
||||
{
|
||||
E.Origin.Tell(_translationLookup["SERVER_COMMANDS_INTERCEPTED"]);
|
||||
}
|
||||
|
||||
catch (AuthorizationException e)
|
||||
else if (E.Type == GameEvent.EventType.Command && E.Extra is Command cmd)
|
||||
{
|
||||
E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}");
|
||||
canExecuteCommand = false;
|
||||
}
|
||||
|
||||
// hack: this prevents commands from getting executing that 'shouldn't' be
|
||||
if (E.Type == GameEvent.EventType.Command && E.Extra is Command cmd &&
|
||||
(canExecuteCommand || E.Origin?.Level == Permission.Console))
|
||||
{
|
||||
ServerLogger.LogInformation("Executing command {Command} for {Client}", cmd.Name, E.Origin.ToString());
|
||||
ServerLogger.LogInformation("Executing command {Command} for {Client}", cmd.Name,
|
||||
E.Origin.ToString());
|
||||
await cmd.ExecuteAsync(E);
|
||||
}
|
||||
|
||||
var pluginTasks = Manager.Plugins
|
||||
.Where(plugin => plugin.Name != "Login")
|
||||
.Select(async plugin => await CreatePluginTask(plugin, E));
|
||||
|
||||
await Task.WhenAll(pluginTasks);
|
||||
|
@ -451,6 +451,8 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
||||
.AddSingleton<IAlertManager, AlertManager>()
|
||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
|
||||
.AddSingleton<IRemoteCommandService, RemoteCommandService>()
|
||||
.AddSingleton(translationLookup)
|
||||
.AddDatabaseContextOptions(appConfig);
|
||||
|
||||
|
131
Application/Misc/InteractionRegistration.cs
Normal file
131
Application/Misc/InteractionRegistration.cs
Normal file
@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using InteractionRegistrationCallback =
|
||||
System.Func<int?, Data.Models.Reference.Game?, System.Threading.CancellationToken,
|
||||
System.Threading.Tasks.Task<SharedLibraryCore.Interfaces.IInteractionData>>;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class InteractionRegistration : IInteractionRegistration
|
||||
{
|
||||
private readonly ILogger<InteractionRegistration> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ConcurrentDictionary<string, InteractionRegistrationCallback> _interactions = new();
|
||||
|
||||
public InteractionRegistration(ILogger<InteractionRegistration> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration)
|
||||
{
|
||||
var plugin = _serviceProvider.GetRequiredService<IEnumerable<IPlugin>>()
|
||||
.FirstOrDefault(plugin => plugin.Name == source);
|
||||
|
||||
if (plugin is not ScriptPlugin scriptPlugin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var wrappedDelegate = (int? clientId, Reference.Game? game, CancellationToken token) =>
|
||||
Task.FromResult(
|
||||
scriptPlugin.WrapDelegate<IInteractionData>(interactionRegistration, clientId, game, token));
|
||||
|
||||
if (!_interactions.ContainsKey(interactionName))
|
||||
{
|
||||
_interactions.TryAdd(interactionName, wrappedDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
_interactions[interactionName] = wrappedDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterInteraction(string interactionName, InteractionRegistrationCallback interactionRegistration)
|
||||
{
|
||||
if (!_interactions.ContainsKey(interactionName))
|
||||
{
|
||||
_interactions.TryAdd(interactionName, interactionRegistration);
|
||||
}
|
||||
else
|
||||
{
|
||||
_interactions[interactionName] = interactionRegistration;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterInteraction(string interactionName)
|
||||
{
|
||||
if (_interactions.ContainsKey(interactionName))
|
||||
{
|
||||
_interactions.TryRemove(interactionName, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IInteractionData>> GetInteractions(int? clientId = null,
|
||||
Reference.Game? game = null, CancellationToken token = default)
|
||||
{
|
||||
return (await Task.WhenAll(_interactions.Select(async kvp =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await kvp.Value(clientId, game, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex,
|
||||
"Could not get interaction for interaction {InteractionName} and ClientId {ClientId}", kvp.Key,
|
||||
clientId);
|
||||
return null;
|
||||
}
|
||||
}))).Where(interaction => interaction is not null);
|
||||
}
|
||||
|
||||
public async Task<string> ProcessInteraction(string interactionId, int originId, int? targetId = null,
|
||||
Reference.Game? game = null, IDictionary<string, string> meta = null, CancellationToken token = default)
|
||||
{
|
||||
if (!_interactions.ContainsKey(interactionId))
|
||||
{
|
||||
throw new ArgumentException($"Interaction with ID {interactionId} has not been registered");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var interaction = await _interactions[interactionId](targetId, game, token);
|
||||
|
||||
if (interaction.Action is not null)
|
||||
{
|
||||
return await interaction.Action(originId, targetId, game, meta, token);
|
||||
}
|
||||
|
||||
if (interaction.ScriptAction is not null)
|
||||
{
|
||||
foreach (var plugin in _serviceProvider.GetRequiredService<IEnumerable<IPlugin>>())
|
||||
{
|
||||
if (plugin is not ScriptPlugin scriptPlugin || scriptPlugin.Name != interaction.Source)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return scriptPlugin.ExecuteAction<string>(interaction.ScriptAction, originId, targetId, game, meta, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex,
|
||||
"Could not process interaction for interaction {InteractionName} and OriginId {ClientId}",
|
||||
interactionId, originId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
88
Application/Misc/RemoteCommandService.cs
Normal file
88
Application/Misc/RemoteCommandService.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Services;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class RemoteCommandService : IRemoteCommandService
|
||||
{
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly ClientService _clientService;
|
||||
|
||||
public RemoteCommandService(ApplicationConfiguration appConfig, ClientService clientService)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_clientService = clientService;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CommandResponseInfo>> Execute(int originId, int? targetId, string command,
|
||||
IEnumerable<string> arguments, Server server)
|
||||
{
|
||||
var client = await _clientService.Get(originId);
|
||||
client.CurrentServer = server;
|
||||
|
||||
command += $" {(targetId.HasValue ? $"@{targetId} " : "")}{string.Join(" ", arguments ?? Enumerable.Empty<string>())}";
|
||||
|
||||
var remoteEvent = new GameEvent
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = command.StartsWith(_appConfig.CommandPrefix) ||
|
||||
command.StartsWith(_appConfig.BroadcastCommandPrefix)
|
||||
? command
|
||||
: $"{_appConfig.CommandPrefix}{command}",
|
||||
Origin = client,
|
||||
Owner = server,
|
||||
IsRemote = true
|
||||
};
|
||||
|
||||
server.Manager.AddEvent(remoteEvent);
|
||||
CommandResponseInfo[] response;
|
||||
|
||||
try
|
||||
{
|
||||
// wait for the event to process
|
||||
var completedEvent =
|
||||
await remoteEvent.WaitAsync(Utilities.DefaultCommandTimeout, server.Manager.CancellationToken);
|
||||
|
||||
if (completedEvent.FailReason == GameEvent.EventFailReason.Timeout)
|
||||
{
|
||||
response = new[]
|
||||
{
|
||||
new CommandResponseInfo()
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
Response = Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
response = completedEvent.Output.Select(output => new CommandResponseInfo()
|
||||
{
|
||||
Response = output,
|
||||
ClientId = client.ClientId
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
catch (System.OperationCanceledException)
|
||||
{
|
||||
response = new[]
|
||||
{
|
||||
new CommandResponseInfo
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
Response = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RESTART_SUCCESS"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@ -42,6 +42,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
private Engine _scriptEngine;
|
||||
private readonly string _fileName;
|
||||
private readonly SemaphoreSlim _onProcessing = new(1, 1);
|
||||
private readonly SemaphoreSlim _onDvarActionComplete = new(1, 1);
|
||||
private bool _successfullyLoaded;
|
||||
private readonly List<string> _registeredCommandNames;
|
||||
private readonly ILogger _logger;
|
||||
@ -111,13 +112,16 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
|
||||
_scriptEngine = new Engine(cfg =>
|
||||
cfg.AllowClr(new[]
|
||||
cfg.AddExtensionMethods(typeof(Utilities), typeof(Enumerable), typeof(Queryable))
|
||||
.AllowClr(new[]
|
||||
{
|
||||
typeof(System.Net.Http.HttpClient).Assembly,
|
||||
typeof(EFClient).Assembly,
|
||||
typeof(Utilities).Assembly,
|
||||
typeof(Encoding).Assembly,
|
||||
typeof(CancellationTokenSource).Assembly
|
||||
typeof(CancellationTokenSource).Assembly,
|
||||
typeof(Data.Models.Client.EFClient).Assembly,
|
||||
typeof(IW4MAdmin.Plugins.Stats.Plugin).Assembly
|
||||
})
|
||||
.CatchClrExceptions()
|
||||
.AddObjectConverter(new PermissionLevelToStringConverter()));
|
||||
@ -338,6 +342,41 @@ namespace IW4MAdmin.Application.Misc
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public T ExecuteAction<T>(Delegate action, params object[] param)
|
||||
{
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait();
|
||||
var args = param.Select(p => JsValue.FromObject(_scriptEngine, p)).ToArray();
|
||||
var result = action.DynamicInvoke(JsValue.Undefined, args);
|
||||
return (T)(result as JsValue)?.ToObject();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T WrapDelegate<T>(Delegate act, params object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait();
|
||||
return (T)(act.DynamicInvoke(JsValue.Null,
|
||||
args.Select(arg => JsValue.FromObject(_scriptEngine, arg)).ToArray()) as ObjectWrapper)?.ToObject();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
{
|
||||
_onProcessing.Release(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// finds declared script commands in the script plugin
|
||||
/// </summary>
|
||||
@ -356,6 +395,11 @@ namespace IW4MAdmin.Application.Misc
|
||||
string name = dynamicCommand.name;
|
||||
string alias = dynamicCommand.alias;
|
||||
string description = dynamicCommand.description;
|
||||
|
||||
if (dynamicCommand.permission is Data.Models.Client.EFClient.Permission perm)
|
||||
{
|
||||
dynamicCommand.permission = perm.ToString();
|
||||
}
|
||||
string permission = dynamicCommand.permission;
|
||||
List<Server.Game> supportedGames = null;
|
||||
var targetRequired = false;
|
||||
@ -439,7 +483,6 @@ namespace IW4MAdmin.Application.Misc
|
||||
throw new PluginException("An error occured while executing action for script plugin");
|
||||
}
|
||||
|
||||
|
||||
finally
|
||||
{
|
||||
if (_onProcessing.CurrentCount == 0)
|
||||
@ -458,18 +501,11 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
private void BeginGetDvar(Server server, string dvarName, Delegate onCompleted)
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
|
||||
server.BeginGetDvar(dvarName, result =>
|
||||
void OnComplete(IAsyncResult result)
|
||||
{
|
||||
var shouldRelease = false;
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait(tokenSource.Token);
|
||||
shouldRelease = true;
|
||||
var (success, value) = (ValueTuple<bool, string>)result.AsyncState;
|
||||
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
new[]
|
||||
{
|
||||
@ -479,9 +515,36 @@ namespace IW4MAdmin.Application.Misc
|
||||
JsValue.FromObject(_scriptEngine, success)
|
||||
});
|
||||
}
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", server.ToString()))
|
||||
{
|
||||
_logger.LogError(ex, "Could not complete BeginGetDvar for {Filename} {@Location}",
|
||||
Path.GetFileName(_fileName), ex.Location);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not complete {BeginGetDvar} for {Class}", nameof(BeginGetDvar), Name);
|
||||
}
|
||||
}
|
||||
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
|
||||
server.BeginGetDvar(dvarName, result =>
|
||||
{
|
||||
var shouldRelease = false;
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait(tokenSource.Token);
|
||||
shouldRelease = true;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
OnComplete(result);
|
||||
|
||||
if (_onProcessing.CurrentCount == 0 && shouldRelease)
|
||||
{
|
||||
_onProcessing.Release();
|
||||
@ -493,17 +556,13 @@ namespace IW4MAdmin.Application.Misc
|
||||
private void BeginSetDvar(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
|
||||
server.BeginSetDvar(dvarName, dvarValue, result =>
|
||||
void OnComplete(IAsyncResult result)
|
||||
{
|
||||
var shouldRelease = false;
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait(tokenSource.Token);
|
||||
shouldRelease = true;
|
||||
var success = (bool)result.AsyncState;
|
||||
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
new[]
|
||||
{
|
||||
@ -513,8 +572,31 @@ namespace IW4MAdmin.Application.Misc
|
||||
JsValue.FromObject(_scriptEngine, success)
|
||||
});
|
||||
}
|
||||
catch (JavaScriptException ex)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", server.ToString()))
|
||||
{
|
||||
_logger.LogError(ex, "Could complete BeginSetDvar for {Filename} {@Location}",
|
||||
Path.GetFileName(_fileName), ex.Location);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not complete {BeginSetDvar} for {Class}", nameof(BeginSetDvar), Name);
|
||||
}
|
||||
}
|
||||
|
||||
server.BeginSetDvar(dvarName, dvarValue, result =>
|
||||
{
|
||||
var shouldRelease = false;
|
||||
try
|
||||
{
|
||||
_onProcessing.Wait(tokenSource.Token);
|
||||
shouldRelease = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnComplete(result);
|
||||
if (_onProcessing.CurrentCount == 0 && shouldRelease)
|
||||
{
|
||||
_onProcessing.Release();
|
||||
|
@ -111,7 +111,7 @@ public class ScriptPluginTimerHelper : IScriptPluginTimerHelper
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("-Releasing OnTick for timer");
|
||||
_onDependentAction?.Release(1);
|
||||
}
|
||||
private void OnTick()
|
||||
@ -128,7 +128,8 @@ public class ScriptPluginTimerHelper : IScriptPluginTimerHelper
|
||||
_onRunningTick.Reset();
|
||||
|
||||
// the js engine is not thread safe so we need to ensure we're not executing OnTick and OnEventAsync simultaneously
|
||||
_onDependentAction?.WaitAsync().Wait();
|
||||
_onDependentAction?.Wait();
|
||||
_logger.LogDebug("+Running OnTick for timer");
|
||||
var start = DateTime.Now;
|
||||
_jsAction.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined });
|
||||
_logger.LogDebug("OnTick took {Time}ms", (DateTime.Now - start).TotalMilliseconds);
|
||||
|
@ -53,7 +53,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
Configuration.Status.AddMapping(ParserRegex.GroupType.RConName, 5);
|
||||
Configuration.Status.AddMapping(ParserRegex.GroupType.RConIpAddress, 7);
|
||||
|
||||
Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)?\" default: \"(.+)?\"\n?(?:latched: \"(.+)?\"\n?)? *(.+)$";
|
||||
Configuration.Dvar.Pattern = "^\"(.+)\" is: \"(.+)?\" default: \"(.+)?\"\n?(?:latched: \"(.+)?\"\n?)? *(.+)?$";
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarName, 1);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarValue, 2);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDefaultValue, 3);
|
||||
@ -147,7 +147,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
GetDvarAsync<string>(connection, dvarName, token: token).ContinueWith(action =>
|
||||
{
|
||||
if (action.Exception is null)
|
||||
if (action.IsCompletedSuccessfully)
|
||||
{
|
||||
callback?.Invoke(new AsyncResult
|
||||
{
|
||||
@ -164,7 +164,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
AsyncState = (false, (string)null)
|
||||
});
|
||||
}
|
||||
}, token);
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
|
||||
@ -176,16 +176,16 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
return new StatusResponse
|
||||
{
|
||||
Clients = ClientsFromStatus(response).ToArray(),
|
||||
Map = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus.Pattern),
|
||||
GameType = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus.Pattern),
|
||||
Hostname = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusHostname, Configuration.HostnameStatus.Pattern),
|
||||
MaxClients = GetValueFromStatus<int?>(response, ParserRegex.GroupType.RConStatusMaxPlayers, Configuration.MaxPlayersStatus.Pattern)
|
||||
Map = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus),
|
||||
GameType = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus),
|
||||
Hostname = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusHostname, Configuration.HostnameStatus),
|
||||
MaxClients = GetValueFromStatus<int?>(response, ParserRegex.GroupType.RConStatusMaxPlayers, Configuration.MaxPlayersStatus)
|
||||
};
|
||||
}
|
||||
|
||||
private T GetValueFromStatus<T>(IEnumerable<string> response, ParserRegex.GroupType groupType, string groupPattern)
|
||||
private T GetValueFromStatus<T>(IEnumerable<string> response, ParserRegex.GroupType groupType, ParserRegex parserRegex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupPattern))
|
||||
if (string.IsNullOrEmpty(parserRegex.Pattern))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
@ -193,10 +193,10 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
string value = null;
|
||||
foreach (var line in response)
|
||||
{
|
||||
var regex = Regex.Match(line, groupPattern);
|
||||
if (regex.Success)
|
||||
var regex = Regex.Match(line, parserRegex.Pattern);
|
||||
if (regex.Success && parserRegex.GroupMapping.ContainsKey(groupType))
|
||||
{
|
||||
value = regex.Groups[Configuration.MapStatus.GroupMapping[groupType]].ToString();
|
||||
value = regex.Groups[parserRegex.GroupMapping[groupType]].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +227,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
SetDvarAsync(connection, dvarName, dvarValue, token).ContinueWith(action =>
|
||||
{
|
||||
if (action.Exception is null)
|
||||
if (action.Exception is null && !action.IsCanceled)
|
||||
{
|
||||
callback?.Invoke(new AsyncResult
|
||||
{
|
||||
@ -244,7 +244,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
AsyncState = false
|
||||
});
|
||||
}
|
||||
}, token);
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||
|
@ -18,6 +18,9 @@ namespace Data.Models
|
||||
Unban,
|
||||
Any,
|
||||
Unflag,
|
||||
Mute,
|
||||
TempMute,
|
||||
Unmute,
|
||||
Other = 100
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ foreach($localization in $localizations)
|
||||
{
|
||||
$url = "http://api.raidmax.org:5000/localization/{0}" -f $localization
|
||||
$filePath = "{0}\Localization\IW4MAdmin.{1}.json" -f $PublishDir, $localization
|
||||
$response = Invoke-WebRequest $url
|
||||
$response = Invoke-WebRequest $url -UseBasicParsing
|
||||
Out-File -FilePath $filePath -InputObject $response.Content -Encoding utf8
|
||||
}
|
||||
|
||||
@ -20,4 +20,4 @@ Minor = $versionInfo.ProductMinorPart
|
||||
Build = $versionInfo.ProductBuildPart
|
||||
Revision = $versionInfo.ProductPrivatePart
|
||||
}
|
||||
$json | ConvertTo-Json | Out-File -FilePath ("{0}\VersionInformation.json" -f $PublishDir) -Encoding ASCII
|
||||
$json | ConvertTo-Json | Out-File -FilePath ("{0}\VersionInformation.json" -f $PublishDir) -Encoding ASCII
|
||||
|
@ -39,7 +39,7 @@ else
|
||||
|
||||
Write-Output "Retrieving latest version info..."
|
||||
|
||||
$releaseInfo = (Invoke-WebRequest $releasesUri | ConvertFrom-Json) | Select -First 1
|
||||
$releaseInfo = (Invoke-WebRequest $releasesUri -UseBasicParsing | ConvertFrom-Json) | Select -First 1
|
||||
$asset = $releaseInfo.assets | Where-Object name -like $assetPattern | Select -First 1
|
||||
$downloadUri = $asset.browser_download_url
|
||||
$filename = Split-Path $downloadUri -leaf
|
||||
@ -55,7 +55,7 @@ if (!$Silent)
|
||||
|
||||
Write-Output "Downloading update. This might take a moment..."
|
||||
|
||||
$fileDownload = Invoke-WebRequest -Uri $downloadUri
|
||||
$fileDownload = Invoke-WebRequest -Uri $downloadUri -UseBasicParsing
|
||||
if ($fileDownload.StatusDescription -ne "OK")
|
||||
{
|
||||
throw "Could not update IW4MAdmin. ($fileDownload.StatusDescription)"
|
||||
|
23
GameFiles/AntiCheat/IW5/storage/iw5/scripts/README.MD
Normal file
23
GameFiles/AntiCheat/IW5/storage/iw5/scripts/README.MD
Normal file
@ -0,0 +1,23 @@
|
||||
# IW5
|
||||
|
||||
This expands IW4M-Admins's Anti-cheat to Plutonium IW5
|
||||
## Installation
|
||||
|
||||
Add ``_customcallbacks.gsc`` into the scripts folder. (%localappdata%\Plutonium\storage\iw5\scripts)
|
||||
|
||||
For more info check out Chase's [how-to guide](https://forum.plutonium.pw/topic/10738/tutorial-loading-custom-gsc-scripts).
|
||||
|
||||
You need to add this to you ``StatsPluginSettings.json`` found in your IW4M-Admin configuration folder.
|
||||
|
||||
```
|
||||
"IW5": {
|
||||
"Recoil": [
|
||||
"iw5_1887_mp.*",
|
||||
"turret_minigun_mp"
|
||||
],
|
||||
"Button": [
|
||||
".*akimbo.*"
|
||||
]
|
||||
}
|
||||
```
|
||||
[Example](https://imgur.com/Ji9AafI)
|
@ -24,12 +24,12 @@ Add this to the WeaponNameParserConfigurations List in the StatsPluginSettings.j
|
||||
}
|
||||
```
|
||||
|
||||
Now create the following entry for __EVERY__ T6 server you are using this on in the ServerDetectionTypes list:
|
||||
Now update the `GameDetectionTypes` list with the following, if it does not already exist:
|
||||
|
||||
```
|
||||
"1270014976": [
|
||||
"T6": [
|
||||
"Offset",
|
||||
"Strain",
|
||||
"Snap"
|
||||
]
|
||||
```
|
||||
"Snap",
|
||||
"Strain"
|
||||
]
|
||||
```
|
718
GameFiles/GameInterface/_integration_base.gsc
Normal file
718
GameFiles/GameInterface/_integration_base.gsc
Normal file
@ -0,0 +1,718 @@
|
||||
#include common_scripts\utility;
|
||||
#include maps\mp\_utility;
|
||||
#include maps\mp\gametypes\_hud_util;
|
||||
|
||||
Init()
|
||||
{
|
||||
level thread Setup();
|
||||
}
|
||||
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
|
||||
// setup default vars
|
||||
level.eventBus = spawnstruct();
|
||||
level.eventBus.inVar = "sv_iw4madmin_in";
|
||||
level.eventBus.outVar = "sv_iw4madmin_out";
|
||||
level.eventBus.failKey = "fail";
|
||||
level.eventBus.timeoutKey = "timeout";
|
||||
level.eventBus.timeout = 30;
|
||||
|
||||
level.commonFunctions = spawnstruct();
|
||||
level.commonFunctions.SetDvar = "SetDvarIfUninitialized";
|
||||
|
||||
level.notifyTypes = spawnstruct();
|
||||
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
|
||||
level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized";
|
||||
|
||||
level.clientDataKey = "clientData";
|
||||
|
||||
level.eventTypes = spawnstruct();
|
||||
level.eventTypes.localClientEvent = "client_event";
|
||||
level.eventTypes.clientDataReceived = "ClientDataReceived";
|
||||
level.eventTypes.clientDataRequested = "ClientDataRequested";
|
||||
level.eventTypes.setClientDataRequested = "SetClientDataRequested";
|
||||
level.eventTypes.setClientDataCompleted = "SetClientDataCompleted";
|
||||
level.eventTypes.executeCommandRequested = "ExecuteCommandRequested";
|
||||
|
||||
level.iw4madminIntegrationDebug = 0;
|
||||
|
||||
// map the event type to the handler
|
||||
level.eventCallbacks = [];
|
||||
level.eventCallbacks[level.eventTypes.clientDataReceived] = ::OnClientDataReceived;
|
||||
level.eventCallbacks[level.eventTypes.executeCommandRequested] = ::OnExecuteCommand;
|
||||
level.eventCallbacks[level.eventTypes.setClientDataCompleted] = ::OnSetClientDataCompleted;
|
||||
|
||||
level.clientCommandCallbacks = [];
|
||||
level.clientCommandRusAsTarget = [];
|
||||
level.logger = spawnstruct();
|
||||
level.overrideMethods = [];
|
||||
|
||||
level.iw4madminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
|
||||
InitializeLogger();
|
||||
|
||||
wait ( 0.05 ); // needed to give script engine time to propagate notifies
|
||||
|
||||
level notify( level.notifyTypes.integrationBootstrapInitialized );
|
||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
LogDebug( "Integration received notify that game functions are initialized" );
|
||||
|
||||
_SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
||||
_SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_integration_enabled", 1 );
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// start long running tasks
|
||||
level thread MonitorClientEvents();
|
||||
level thread MonitorBus();
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Client Methods
|
||||
//////////////////////////////////
|
||||
|
||||
OnPlayerConnect()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerDisconnect()
|
||||
{
|
||||
self endon ( "disconnect" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( "disconnect" );
|
||||
self SaveTrackingMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
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 ( ;; )
|
||||
{
|
||||
level waittill( level.eventTypes.localClientEvent, client );
|
||||
|
||||
LogDebug( "Processing Event " + client.event.type + "-" + client.event.subtype );
|
||||
|
||||
eventHandler = level.eventCallbacks[client.event.type];
|
||||
|
||||
if ( IsDefined( eventHandler ) )
|
||||
{
|
||||
client [[eventHandler]]( client.event );
|
||||
LogDebug( "notify client for " + client.event.type );
|
||||
client notify( level.eventTypes.localClientEvent, client.event );
|
||||
}
|
||||
|
||||
client.eventData = [];
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Helper Methods
|
||||
//////////////////////////////////
|
||||
|
||||
_IsBot( entity )
|
||||
{
|
||||
// there already is a cgame function exists as "IsBot", for IW4, but unsure what all titles have it defined,
|
||||
// so we are defining it here
|
||||
return IsDefined( entity.pers["isBot"] ) && entity.pers["isBot"];
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized( dvarName, dvarValue )
|
||||
{
|
||||
[[level.overrideMethods[level.commonFunctions.SetDvar]]]( dvarName, dvarValue );
|
||||
}
|
||||
|
||||
// Not every game can output to console or even game log.
|
||||
// Adds a very basic logging system that every
|
||||
// game specific script can extend.accumulate
|
||||
// Logging to dvars used as example.
|
||||
InitializeLogger()
|
||||
{
|
||||
level.logger._logger = [];
|
||||
RegisterLogger( ::Log2Dvar );
|
||||
RegisterLogger( ::Log2IngamePrint );
|
||||
level.logger.debug = ::LogDebug;
|
||||
level.logger.error = ::LogError;
|
||||
level.logger.warning = ::LogWarning;
|
||||
}
|
||||
|
||||
_Log( LogLevel, message )
|
||||
{
|
||||
for( i = 0; i < level.logger._logger.size; i++ )
|
||||
{
|
||||
[[level.logger._logger[i]]]( LogLevel, message );
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug( message )
|
||||
{
|
||||
if ( level.iw4madminIntegrationDebug )
|
||||
{
|
||||
_Log( "debug", level.eventBus.gamename + ": " + message );
|
||||
}
|
||||
}
|
||||
|
||||
LogError( message )
|
||||
{
|
||||
_Log( "error", message );
|
||||
}
|
||||
|
||||
LogWarning( message )
|
||||
{
|
||||
_Log( "warning", message );
|
||||
}
|
||||
|
||||
Log2Dvar( LogLevel, message )
|
||||
{
|
||||
switch ( LogLevel )
|
||||
{
|
||||
case "debug":
|
||||
SetDvar( "sv_iw4madmin_last_debug", message );
|
||||
break;
|
||||
case "error":
|
||||
SetDvar( "sv_iw4madmin_last_error", message );
|
||||
break;
|
||||
case "warning":
|
||||
SetDvar( "sv_iw4madmin_last_warning", message );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Log2IngamePrint( LogLevel, message )
|
||||
{
|
||||
switch ( LogLevel )
|
||||
{
|
||||
case "debug":
|
||||
IPrintLn( "[DEBUG] " + message );
|
||||
break;
|
||||
case "error":
|
||||
IPrintLn( "[ERROR] " + message );
|
||||
break;
|
||||
case "warning":
|
||||
IPrintLn( "[WARN] " + message );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterLogger( logger )
|
||||
{
|
||||
level.logger._logger[level.logger._logger.size] = logger;
|
||||
}
|
||||
|
||||
RequestClientMeta( metaKey )
|
||||
{
|
||||
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
||||
level thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
|
||||
}
|
||||
|
||||
RequestClientBasicData()
|
||||
{
|
||||
getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" );
|
||||
level thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
|
||||
}
|
||||
|
||||
IncrementClientMeta( metaKey, incrementValue, clientId )
|
||||
{
|
||||
SetClientMeta( metaKey, incrementValue, clientId, "increment" );
|
||||
}
|
||||
|
||||
DecrementClientMeta( metaKey, decrementValue, clientId )
|
||||
{
|
||||
SetClientMeta( metaKey, decrementValue, clientId, "decrement" );
|
||||
}
|
||||
|
||||
GenerateJoinTeamString( isSpectator )
|
||||
{
|
||||
team = self.team;
|
||||
|
||||
if ( IsDefined( self.joining_team ) )
|
||||
{
|
||||
team = self.joining_team;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isSpectator || !IsDefined( team ) )
|
||||
{
|
||||
team = "spectator";
|
||||
}
|
||||
}
|
||||
|
||||
guid = self GetXuid();
|
||||
|
||||
if ( guid == "0" )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
if ( !IsDefined( guid ) || guid == "0" )
|
||||
{
|
||||
guid = "undefined";
|
||||
}
|
||||
|
||||
return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n";
|
||||
}
|
||||
|
||||
SetClientMeta( metaKey, metaValue, clientId, direction )
|
||||
{
|
||||
data = "key=" + metaKey + "|value=" + metaValue;
|
||||
clientNumber = -1;
|
||||
|
||||
if ( IsDefined ( clientId ) )
|
||||
{
|
||||
data = data + "|clientId=" + clientId;
|
||||
clientNumber = -1;
|
||||
}
|
||||
|
||||
if ( IsDefined( direction ) )
|
||||
{
|
||||
data = data + "|direction=" + direction;
|
||||
}
|
||||
|
||||
if ( IsPlayer( self ) )
|
||||
{
|
||||
clientNumber = self getEntityNumber();
|
||||
}
|
||||
|
||||
setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data );
|
||||
level thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self );
|
||||
}
|
||||
|
||||
SaveTrackingMetrics()
|
||||
{
|
||||
if ( !IsDefined( self.persistentClientId ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogDebug( "Saving tracking metrics for " + self.persistentClientId );
|
||||
|
||||
if ( !IsDefined( self.lastShotCount ) )
|
||||
{
|
||||
self.lastShotCount = 0;
|
||||
}
|
||||
|
||||
currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]]();
|
||||
change = currentShotCount - self.lastShotCount;
|
||||
self.lastShotCount = currentShotCount;
|
||||
|
||||
LogDebug( "Total Shots Fired increased by " + change );
|
||||
|
||||
if ( !IsDefined( change ) )
|
||||
{
|
||||
change = 0;
|
||||
}
|
||||
|
||||
if ( change == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId );
|
||||
}
|
||||
|
||||
BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
|
||||
{
|
||||
if ( !IsDefined( data ) )
|
||||
{
|
||||
data = "";
|
||||
}
|
||||
|
||||
if ( !IsDefined( eventSubtype ) )
|
||||
{
|
||||
eventSubtype = "None";
|
||||
}
|
||||
|
||||
if ( IsPlayer( entOrId ) )
|
||||
{
|
||||
entOrId = entOrId getEntityNumber();
|
||||
}
|
||||
|
||||
request = "0";
|
||||
|
||||
if ( responseExpected )
|
||||
{
|
||||
request = "1";
|
||||
}
|
||||
|
||||
request = request + ";" + eventType + ";" + eventSubtype + ";" + entOrId + ";" + data;
|
||||
return request;
|
||||
}
|
||||
|
||||
MonitorBus()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
wait ( 0.1 );
|
||||
|
||||
// check to see if IW4MAdmin is ready to receive more data
|
||||
if ( getDvar( level.eventBus.inVar ) == "" )
|
||||
{
|
||||
level notify( "bus_ready" );
|
||||
}
|
||||
|
||||
eventString = getDvar( level.eventBus.outVar );
|
||||
|
||||
if ( eventString == "" )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
LogDebug( "-> " + eventString );
|
||||
|
||||
NotifyClientEvent( strtok( eventString, ";" ) );
|
||||
|
||||
SetDvar( level.eventBus.outVar, "" );
|
||||
}
|
||||
}
|
||||
|
||||
QueueEvent( request, eventType, notifyEntity )
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
|
||||
start = GetTime();
|
||||
maxWait = level.eventBus.timeout * 1000; // 30 seconds
|
||||
timedOut = "";
|
||||
|
||||
while ( GetDvar( level.eventBus.inVar ) != "" && ( GetTime() - start ) < maxWait )
|
||||
{
|
||||
level [[level.overrideMethods["waittill_notify_or_timeout"]]]( "bus_ready", 1 );
|
||||
|
||||
if ( GetDvar( level.eventBus.inVar ) != "" )
|
||||
{
|
||||
LogDebug( "A request is already in progress..." );
|
||||
timedOut = "set";
|
||||
continue;
|
||||
}
|
||||
|
||||
timedOut = "unset";
|
||||
}
|
||||
|
||||
if ( timedOut == "set")
|
||||
{
|
||||
LogDebug( "Timed out waiting for response..." );
|
||||
|
||||
if ( IsDefined( notifyEntity ) )
|
||||
{
|
||||
notifyEntity NotifyClientEventTimeout( eventType );
|
||||
}
|
||||
|
||||
SetDvar( level.eventBus.inVar, "" );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LogDebug("<- " + request );
|
||||
|
||||
SetDvar( level.eventBus.inVar, request );
|
||||
}
|
||||
|
||||
ParseDataString( data )
|
||||
{
|
||||
if ( !IsDefined( data ) )
|
||||
{
|
||||
LogDebug( "No data to parse" );
|
||||
return [];
|
||||
}
|
||||
|
||||
dataParts = strtok( data, "|" );
|
||||
dict = [];
|
||||
|
||||
for ( i = 0; i < dataParts.size; i++ )
|
||||
{
|
||||
part = dataParts[i];
|
||||
splitPart = strtok( part, "=" );
|
||||
key = splitPart[0];
|
||||
value = splitPart[1];
|
||||
dict[key] = value;
|
||||
dict[i] = key;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
NotifyClientEventTimeout( eventType )
|
||||
{
|
||||
// todo: make this actual eventing
|
||||
if ( eventType == level.eventTypes.clientDataRequested )
|
||||
{
|
||||
self.pers["clientData"].state = level.eventBus.timeoutKey;
|
||||
}
|
||||
}
|
||||
|
||||
NotifyClientEvent( eventInfo )
|
||||
{
|
||||
origin = getPlayerFromClientNum( int( eventInfo[3] ) );
|
||||
target = getPlayerFromClientNum( int( eventInfo[4] ) );
|
||||
|
||||
event = spawnstruct();
|
||||
event.type = eventInfo[1];
|
||||
event.subtype = eventInfo[2];
|
||||
event.data = eventInfo[5];
|
||||
event.origin = origin;
|
||||
event.target = target;
|
||||
|
||||
if ( IsDefined( event.data ) )
|
||||
{
|
||||
LogDebug( "NotifyClientEvent->" + event.data );
|
||||
}
|
||||
|
||||
if ( int( eventInfo[3] ) != -1 && !IsDefined( origin ) )
|
||||
{
|
||||
LogDebug( "origin is null but the slot id is " + int( eventInfo[3] ) );
|
||||
}
|
||||
if ( int( eventInfo[4] ) != -1 && !IsDefined( target ) )
|
||||
{
|
||||
LogDebug( "target is null but the slot id is " + int( eventInfo[4] ) );
|
||||
}
|
||||
|
||||
if ( IsDefined( 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 );
|
||||
}
|
||||
|
||||
GetPlayerFromClientNum( clientNum )
|
||||
{
|
||||
if ( clientNum < 0 )
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for ( i = 0; i < level.players.size; i++ )
|
||||
{
|
||||
if ( level.players[i] getEntityNumber() == clientNum )
|
||||
{
|
||||
return level.players[i];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
||||
{
|
||||
if ( IsDefined( level.clientCommandCallbacks[commandName] ) && IsDefined( shouldOverwrite ) && !shouldOverwrite )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Event Handlers
|
||||
/////////////////////////////////
|
||||
|
||||
OnClientDataReceived( event )
|
||||
{
|
||||
event.data = ParseDataString( event.data );
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
|
||||
if ( event.subtype == "Fail" )
|
||||
{
|
||||
LogDebug( "Received fail response" );
|
||||
clientData.state = level.eventBus.failKey;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( event.subtype == "Meta" )
|
||||
{
|
||||
if ( !IsDefined( clientData.meta ) )
|
||||
{
|
||||
clientData.meta = [];
|
||||
}
|
||||
|
||||
metaKey = event.data[0];
|
||||
clientData.meta[metaKey] = event.data[metaKey];
|
||||
|
||||
LogDebug( "Meta Key=" + metaKey + ", Meta Value=" + event.data[metaKey] );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
clientData.permissionLevel = event.data["level"];
|
||||
clientData.clientId = event.data["clientId"];
|
||||
clientData.lastConnection = event.data["lastConnection"];
|
||||
clientData.tag = event.data["tag"];
|
||||
clientData.state = "complete";
|
||||
self.persistentClientId = event.data["clientId"];
|
||||
|
||||
self thread DisplayWelcomeData();
|
||||
}
|
||||
|
||||
OnExecuteCommand( event )
|
||||
{
|
||||
data = ParseDataString( event.data );
|
||||
response = "";
|
||||
|
||||
command = level.clientCommandCallbacks[event.subtype];
|
||||
runAsTarget = level.clientCommandRusAsTarget[event.subtype];
|
||||
executionContextEntity = event.origin;
|
||||
|
||||
if ( runAsTarget )
|
||||
{
|
||||
executionContextEntity = event.target;
|
||||
}
|
||||
|
||||
if ( IsDefined( command ) )
|
||||
{
|
||||
response = executionContextEntity [[command]]( event, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
LogDebug( "Unknown Client command->" + event.subtype );
|
||||
}
|
||||
|
||||
// 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 )
|
||||
{
|
||||
event.origin IPrintLnBold( response );
|
||||
}
|
||||
}
|
||||
|
||||
OnSetClientDataCompleted( event )
|
||||
{
|
||||
// IW4MAdmin let us know it persisted (success or fail)
|
||||
LogDebug( "Set Client Data -> subtype = " + event.subType + " status = " + event.data["status"] );
|
||||
}
|
500
GameFiles/GameInterface/_integration_iw4x.gsc
Normal file
500
GameFiles/GameInterface/_integration_iw4x.gsc
Normal file
@ -0,0 +1,500 @@
|
||||
#include common_scripts\iw4x_utility;
|
||||
|
||||
Init()
|
||||
{
|
||||
level.eventBus.gamename = "IW4";
|
||||
|
||||
level thread Setup();
|
||||
}
|
||||
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "IntegrationBootstrapInitialized" );
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
{
|
||||
level endon ( "game_ended" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( scripts\_integration_base::_IsBot( player ) )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
player thread SetPersistentData();
|
||||
player thread WaitForClientEvents();
|
||||
}
|
||||
}
|
||||
|
||||
RegisterClientCommands()
|
||||
{
|
||||
scripts\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
||||
scripts\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
||||
scripts\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
||||
scripts\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
||||
scripts\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
||||
scripts\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
||||
scripts\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
||||
scripts\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
||||
scripts\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
||||
scripts\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
WaitForClientEvents()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
// example of requesting a meta value
|
||||
lastServerMetaKey = "LastServerPlayed";
|
||||
// self scripts\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.localClientEvent, event );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||
|
||||
if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey )
|
||||
{
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
lastServerPlayed = clientData.meta[lastServerMetaKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized( dvar, value )
|
||||
{
|
||||
SetDvarIfUninitialized( dvar, value );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( _notify, timeout )
|
||||
{
|
||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||
}
|
||||
|
||||
Log2Console( logLevel, message )
|
||||
{
|
||||
PrintConsole( "[" + logLevel + "] " + message + "\n" );
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
SetPersistentData()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||
scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||
return;
|
||||
}
|
||||
|
||||
guid = self SplitGuid();
|
||||
|
||||
scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||
|
||||
self SetPlayerData( "bests", "none", guid["high"] );
|
||||
self SetPlayerData( "awards", "none", guid["low"] );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
|
||||
GiveWeaponImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + "^7 is not alive";
|
||||
}
|
||||
|
||||
self IPrintLnBold( "You have been given a new weapon" );
|
||||
self GiveWeapon( data["weaponName"] );
|
||||
self SwitchToWeapon( data["weaponName"] );
|
||||
|
||||
return self.name + "^7 has been given ^5" + data["weaponName"];
|
||||
}
|
||||
|
||||
TakeWeaponsImpl()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
self IPrintLnBold( "You are not alive" );
|
||||
return;
|
||||
}
|
||||
|
||||
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", 0 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
self God();
|
||||
self Noclip();
|
||||
self Show();
|
||||
|
||||
self.isNoClipped = false;
|
||||
|
||||
self IPrintLnBold( "NoClip disabled" );
|
||||
}
|
||||
}
|
||||
|
||||
HideImpl()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
self IPrintLnBold( "You are not alive" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !IsDefined ( self.isHidden ) )
|
||||
{
|
||||
self.isHidden = false;
|
||||
}
|
||||
|
||||
if ( !self.isHidden )
|
||||
{
|
||||
self SetClientDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 1 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
self God();
|
||||
self Hide();
|
||||
|
||||
self.isHidden = true;
|
||||
|
||||
self IPrintLnBold( "Hide enabled" );
|
||||
}
|
||||
else
|
||||
{
|
||||
self SetClientDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 0 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
self God();
|
||||
self Show();
|
||||
|
||||
self.isHidden = false;
|
||||
|
||||
self IPrintLnBold( "Hide disabled" );
|
||||
}
|
||||
}
|
||||
|
||||
AlertImpl( event, data )
|
||||
{
|
||||
if ( 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;
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + " is not alive";
|
||||
}
|
||||
|
||||
self SetOrigin( event.origin GetOrigin() );
|
||||
return "Moved here " + self.name;
|
||||
}
|
||||
|
||||
KillImpl()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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";
|
||||
}
|
501
GameFiles/GameInterface/_integration_iw5.gsc
Normal file
501
GameFiles/GameInterface/_integration_iw5.gsc
Normal file
@ -0,0 +1,501 @@
|
||||
#include common_scripts\utility;
|
||||
|
||||
Init()
|
||||
{
|
||||
level.eventBus.gamename = "IW5";
|
||||
|
||||
level thread Setup();
|
||||
}
|
||||
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "IntegrationBootstrapInitialized" );
|
||||
|
||||
scripts\mp\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
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()
|
||||
{
|
||||
scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized( dvar, value )
|
||||
{
|
||||
SetDvarIfUninitialized( dvar, value );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( _notify, timeout )
|
||||
{
|
||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||
}
|
||||
|
||||
Log2Console( logLevel, message )
|
||||
{
|
||||
Print( "[" + logLevel + "] " + message + "\n" );
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
SetPersistentData()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\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"] );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
|
||||
GiveWeaponImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + "^7 is not alive";
|
||||
}
|
||||
|
||||
self IPrintLnBold( "You have been given a new weapon" );
|
||||
self GiveWeapon( data["weaponName"] );
|
||||
self SwitchToWeapon( data["weaponName"] );
|
||||
|
||||
return self.name + "^7 has been given ^5" + data["weaponName"];
|
||||
}
|
||||
|
||||
TakeWeaponsImpl()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
self IPrintLnBold( "You are not alive" );
|
||||
}
|
||||
|
||||
if ( !IsDefined ( self.isNoClipped ) )
|
||||
{
|
||||
self.isNoClipped = false;
|
||||
}
|
||||
|
||||
if ( !self.isNoClipped )
|
||||
{
|
||||
SetDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 1 );
|
||||
|
||||
self God();
|
||||
self Noclip();
|
||||
self Hide();
|
||||
SetDvar( "sv_cheats", 0 );
|
||||
|
||||
self.isNoClipped = true;
|
||||
|
||||
self IPrintLnBold( "NoClip enabled" );
|
||||
}
|
||||
else
|
||||
{
|
||||
SetDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 0 );
|
||||
|
||||
self God();
|
||||
self Noclip();
|
||||
self Hide();
|
||||
|
||||
SetDvar( "sv_cheats", 0 );
|
||||
|
||||
self.isNoClipped = false;
|
||||
|
||||
self IPrintLnBold( "NoClip disabled" );
|
||||
}
|
||||
|
||||
self IPrintLnBold( "NoClip enabled" );
|
||||
}
|
||||
|
||||
HideImpl()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
self IPrintLnBold( "You are not alive" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !IsDefined ( self.isHidden ) )
|
||||
{
|
||||
self.isHidden = false;
|
||||
}
|
||||
|
||||
if ( !self.isHidden )
|
||||
{
|
||||
SetDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 1 );
|
||||
|
||||
self God();
|
||||
self Hide();
|
||||
SetDvar( "sv_cheats", 0 );
|
||||
|
||||
self.isHidden = true;
|
||||
|
||||
self IPrintLnBold( "Hide enabled" );
|
||||
}
|
||||
else
|
||||
{
|
||||
SetDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 0 );
|
||||
|
||||
self God();
|
||||
self Show();
|
||||
SetDvar( "sv_cheats", 0 );
|
||||
|
||||
self.isHidden = false;
|
||||
|
||||
self IPrintLnBold( "Hide disabled" );
|
||||
}
|
||||
}
|
||||
|
||||
AlertImpl( event, data )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + " is not alive";
|
||||
}
|
||||
|
||||
self SetOrigin( event.origin GetOrigin() );
|
||||
return "Moved here " + self.name;
|
||||
}
|
||||
|
||||
KillImpl()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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";
|
||||
}
|
522
GameFiles/GameInterface/_integration_t5.gsc
Normal file
522
GameFiles/GameInterface/_integration_t5.gsc
Normal file
@ -0,0 +1,522 @@
|
||||
#include common_scripts\utility;
|
||||
|
||||
Init()
|
||||
{
|
||||
level.eventBus.gamename = "T5";
|
||||
|
||||
level thread Setup();
|
||||
}
|
||||
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "IntegrationBootstrapInitialized" );
|
||||
|
||||
scripts\mp\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
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()
|
||||
{
|
||||
scripts\mp\_integration_base::AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Hide", false, ::HideImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Alert", true, ::AlertImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Goto", false, ::GotoImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "Kill", true, ::KillImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
||||
scripts\mp\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return maps\mp\gametypes\_persistence::statGet( "total_shots" );
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized(dvar, value)
|
||||
{
|
||||
maps\mp\_utility::set_dvar_if_unset(dvar, value);
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( msg, timer )
|
||||
{
|
||||
self endon( msg );
|
||||
wait( timer );
|
||||
}
|
||||
|
||||
Log2Console( logLevel, message )
|
||||
{
|
||||
Print( "[" + logLevel + "] " + message + "\n" );
|
||||
}
|
||||
|
||||
God()
|
||||
{
|
||||
|
||||
if ( !IsDefined( self.godmode ) )
|
||||
{
|
||||
self.godmode = false;
|
||||
}
|
||||
|
||||
if (!self.godmode )
|
||||
{
|
||||
self enableInvulnerability();
|
||||
self.godmode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.godmode = false;
|
||||
self disableInvulnerability();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
/*SetPersistentData()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\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"] );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
//////////////////////////////////
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
|
||||
GiveWeaponImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + "^7 is not alive";
|
||||
}
|
||||
|
||||
self IPrintLnBold( "You have been given a new weapon" );
|
||||
self GiveWeapon( data["weaponName"] );
|
||||
self SwitchToWeapon( data["weaponName"] );
|
||||
|
||||
return self.name + "^7 has been given ^5" + data["weaponName"];
|
||||
}
|
||||
|
||||
TakeWeaponsImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + "^7 is not alive";
|
||||
}
|
||||
|
||||
self TakeAllWeapons();
|
||||
self IPrintLnBold( "All your weapons have been taken" );
|
||||
|
||||
return "Took weapons from " + self.name;
|
||||
}
|
||||
|
||||
TeamSwitchImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self + "^7 is not alive";
|
||||
}
|
||||
|
||||
team = level.allies;
|
||||
|
||||
if ( self.team == "allies" )
|
||||
{
|
||||
team = level.axis;
|
||||
}
|
||||
|
||||
self IPrintLnBold( "You are being team switched" );
|
||||
wait( 2 );
|
||||
self [[team]]();
|
||||
|
||||
return self.name + "^7 switched to " + self.team;
|
||||
}
|
||||
|
||||
LockControlsImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + "^7 is not alive";
|
||||
}
|
||||
|
||||
if ( !IsDefined ( self.isControlLocked ) )
|
||||
{
|
||||
self.isControlLocked = false;
|
||||
}
|
||||
|
||||
if ( !self.isControlLocked )
|
||||
{
|
||||
self freezeControls( true );
|
||||
self God();
|
||||
self Hide();
|
||||
|
||||
info = [];
|
||||
info[ "alertType" ] = "Alert!";
|
||||
info[ "message" ] = "You have been frozen!";
|
||||
|
||||
self AlertImpl( undefined, info );
|
||||
|
||||
self.isControlLocked = true;
|
||||
|
||||
return self.name + "\'s controls are locked";
|
||||
}
|
||||
else
|
||||
{
|
||||
self freezeControls( false );
|
||||
self God();
|
||||
self Show();
|
||||
|
||||
self.isControlLocked = false;
|
||||
|
||||
return self.name + "\'s controls are unlocked";
|
||||
}
|
||||
}
|
||||
|
||||
NoClipImpl( event, data )
|
||||
{
|
||||
/*if ( !IsAlive( self ) )
|
||||
{
|
||||
self IPrintLnBold( "You are not alive" );
|
||||
}
|
||||
|
||||
if ( !IsDefined ( self.isNoClipped ) )
|
||||
{
|
||||
self.isNoClipped = false;
|
||||
}
|
||||
|
||||
if ( !self.isNoClipped )
|
||||
{
|
||||
self SetClientDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 1 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
self God();
|
||||
self Noclip();
|
||||
self Hide();
|
||||
|
||||
self.isNoClipped = true;
|
||||
|
||||
self IPrintLnBold( "NoClip enabled" );
|
||||
}
|
||||
else
|
||||
{
|
||||
self SetClientDvar( "sv_cheats", 1 );
|
||||
self SetClientDvar( "cg_thirdperson", 1 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
self God();
|
||||
self Noclip();
|
||||
self Hide();
|
||||
|
||||
self.isNoClipped = false;
|
||||
|
||||
self IPrintLnBold( "NoClip disabled" );
|
||||
}
|
||||
|
||||
self IPrintLnBold( "NoClip enabled" );*/
|
||||
|
||||
scripts\mp\_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 );
|
||||
|
||||
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";
|
||||
}
|
File diff suppressed because it is too large
Load Diff
14
GameFiles/deploy.bat
Normal file
14
GameFiles/deploy.bat
Normal file
@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
|
||||
ECHO "Pluto IW5"
|
||||
xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
||||
xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
||||
xcopy /y .\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp"
|
||||
|
||||
ECHO "Pluto T5"
|
||||
xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp"
|
||||
xcopy /y .\GameInterface\_integration_t5.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\mp"
|
||||
|
||||
ECHO "Pluto T6"
|
||||
xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp"
|
||||
xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc.src "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp"
|
@ -6,14 +6,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\IW4x\userraw\scripts\_customcallbacks.gsc
|
||||
DeploymentFiles\deployment-pipeline.yml = DeploymentFiles\deployment-pipeline.yml
|
||||
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
|
||||
README.md = README.md
|
||||
version.txt = version.txt
|
||||
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
|
||||
DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh
|
||||
GameFiles\_integration.gsc = GameFiles\_integration.gsc
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
||||
@ -71,6 +69,36 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integrations.Cod", "Integra
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integrations.Source", "Integrations\Source\Integrations.Source.csproj", "{9512295B-3045-40E0-9B7E-2409F2173E9D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mute", "Plugins\Mute\Mute.csproj", "{259824F3-D860-4233-91D6-FF73D4DD8B18}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameFiles", "GameFiles", "{6CBF412C-EFEE-45F7-80FD-AC402C22CDB9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterface", "{5C2BE2A8-EA1D-424F-88E1-7FC33EEC2E55}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\GameInterface\_integration_base.gsc = GameFiles\GameInterface\_integration_base.gsc
|
||||
GameFiles\GameInterface\_integration_iw4x.gsc = GameFiles\GameInterface\_integration_iw4x.gsc
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntiCheat", "AntiCheat", "{AB83BAC0-C539-424A-BF00-78487C10753C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IW4x", "IW4x", "{3EA564BD-3AC6-479B-96B6-CB059DCD0C77}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\AntiCheat\IW4x\userraw\scripts\_customcallbacks.gsc = GameFiles\AntiCheat\IW4x\userraw\scripts\_customcallbacks.gsc
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pluto T6", "Pluto T6", "{866F453D-BC89-457F-8B55-485494759B31}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc = GameFiles\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc
|
||||
GameFiles\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc.src = GameFiles\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc.src
|
||||
GameFiles\AntiCheat\PT6\README.MD = GameFiles\AntiCheat\PT6\README.MD
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pluto IW5", "Pluto IW5", "{603725A4-BC0B-423B-955B-762C89E1C4C2}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc = GameFiles\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc
|
||||
GameFiles\AntiCheat\IW5\README.MD = GameFiles\AntiCheat\IW5\README.MD
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -375,6 +403,30 @@ Global
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|x64.Build.0 = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Release|x86.Build.0 = Release|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -389,6 +441,13 @@ Global
|
||||
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D} = {A2AE33B4-0830-426A-9E11-951DAB12BE5B}
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D} = {A2AE33B4-0830-426A-9E11-951DAB12BE5B}
|
||||
{259824F3-D860-4233-91D6-FF73D4DD8B18} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{6CBF412C-EFEE-45F7-80FD-AC402C22CDB9} = {8C8F3945-0AEF-4949-A1F7-B18E952E50BC}
|
||||
{5C2BE2A8-EA1D-424F-88E1-7FC33EEC2E55} = {6CBF412C-EFEE-45F7-80FD-AC402C22CDB9}
|
||||
{AB83BAC0-C539-424A-BF00-78487C10753C} = {6CBF412C-EFEE-45F7-80FD-AC402C22CDB9}
|
||||
{3EA564BD-3AC6-479B-96B6-CB059DCD0C77} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||
{866F453D-BC89-457F-8B55-485494759B31} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||
{603725A4-BC0B-423B-955B-762C89E1C4C2} = {AB83BAC0-C539-424A-BF00-78487C10753C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -243,7 +243,11 @@ namespace Integrations.Cod
|
||||
CancellationToken = token
|
||||
};
|
||||
|
||||
connectionState.ConnectionAttempts++;
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
connectionState.ConnectionAttempts++;
|
||||
}
|
||||
|
||||
connectionState.BytesReadPerSegment.Clear();
|
||||
|
||||
_log.LogDebug(
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -26,49 +26,22 @@ namespace IW4MAdmin.Plugins.Login
|
||||
_configHandler = configurationHandlerFactory.GetConfigurationHandler<Configuration>("LoginPluginSettings");
|
||||
}
|
||||
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
public Task OnEventAsync(GameEvent gameEvent, Server server)
|
||||
{
|
||||
if (E.IsRemote || _configHandler.Configuration().RequirePrivilegedClientLogin == false)
|
||||
if (gameEvent.IsRemote || _configHandler.Configuration().RequirePrivilegedClientLogin == false)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
if (gameEvent.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
AuthorizedClients.TryAdd(E.Origin.ClientId, false);
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
|
||||
AuthorizedClients.TryAdd(gameEvent.Origin.ClientId, false);
|
||||
gameEvent.Origin.SetAdditionalProperty("IsLoggedIn", false);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Disconnect)
|
||||
if (gameEvent.Type == GameEvent.EventType.Disconnect)
|
||||
{
|
||||
AuthorizedClients.TryRemove(E.Origin.ClientId, out bool value);
|
||||
AuthorizedClients.TryRemove(gameEvent.Origin.ClientId, out _);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Command)
|
||||
{
|
||||
if (E.Origin.Level < EFClient.Permission.Moderator ||
|
||||
E.Origin.Level == EFClient.Permission.Console)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Extra.GetType() == typeof(SetPasswordCommand) &&
|
||||
E.Origin?.Password == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Extra.GetType() == typeof(LoginCommand))
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Extra.GetType() == typeof(RequestTokenCommand))
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!AuthorizedClients[E.Origin.ClientId])
|
||||
{
|
||||
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -76,6 +49,36 @@ namespace IW4MAdmin.Plugins.Login
|
||||
{
|
||||
AuthorizedClients = new ConcurrentDictionary<int, bool>();
|
||||
|
||||
manager.CommandInterceptors.Add(gameEvent =>
|
||||
{
|
||||
if (gameEvent.Type != GameEvent.EventType.Command || gameEvent.Extra is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (gameEvent.Origin.Level < EFClient.Permission.Moderator ||
|
||||
gameEvent.Origin.Level == EFClient.Permission.Console)
|
||||
return true;
|
||||
|
||||
if (gameEvent.Extra.GetType() == typeof(SetPasswordCommand) &&
|
||||
gameEvent.Origin?.Password == null)
|
||||
return true;
|
||||
|
||||
if (gameEvent.Extra.GetType() == typeof(LoginCommand))
|
||||
return true;
|
||||
|
||||
if (gameEvent.Extra.GetType() == typeof(RequestTokenCommand))
|
||||
return true;
|
||||
|
||||
if (!AuthorizedClients[gameEvent.Origin.ClientId])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gameEvent.Origin.SetAdditionalProperty("IsLoggedIn", true);
|
||||
return true;
|
||||
});
|
||||
|
||||
await _configHandler.BuildAsync();
|
||||
if (_configHandler.Configuration() == null)
|
||||
{
|
||||
|
49
Plugins/Mute/Commands/MuteCommand.cs
Normal file
49
Plugins/Mute/Commands/MuteCommand.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace Mute.Commands;
|
||||
|
||||
public class MuteCommand : Command
|
||||
{
|
||||
public MuteCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "mute";
|
||||
Description = translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_DESC"];
|
||||
Alias = "mu";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
if (await Plugin.MuteManager.Mute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, null, gameEvent.Data))
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_MUTED"]
|
||||
.FormatExt(gameEvent.Target.CleanedName));
|
||||
gameEvent.Target.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_TARGET_MUTED"]
|
||||
.FormatExt(gameEvent.Data));
|
||||
return;
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_NOT_UNMUTED"]
|
||||
.FormatExt(gameEvent.Target.CleanedName));
|
||||
}
|
||||
}
|
67
Plugins/Mute/Commands/TempMuteCommand.cs
Normal file
67
Plugins/Mute/Commands/TempMuteCommand.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace Mute.Commands;
|
||||
|
||||
public class TempMuteCommand : Command
|
||||
{
|
||||
private const string TempBanRegex = @"([0-9]+\w+)\ (.+)";
|
||||
|
||||
public TempMuteCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "tempmute";
|
||||
Description = translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_DESC"];
|
||||
Alias = "tm";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_DURATION"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
var match = Regex.Match(gameEvent.Data, TempBanRegex);
|
||||
if (match.Success)
|
||||
{
|
||||
var expiration = DateTime.UtcNow + match.Groups[1].ToString().ParseTimespan();
|
||||
var reason = match.Groups[2].ToString();
|
||||
|
||||
if (await Plugin.MuteManager.Mute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, expiration, reason))
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_TEMPMUTED"]
|
||||
.FormatExt(gameEvent.Target.CleanedName));
|
||||
gameEvent.Target.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_TARGET_TEMPMUTED"]
|
||||
.FormatExt(expiration.HumanizeForCurrentCulture(), reason));
|
||||
return;
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_MUTE_NOT_UNMUTED"]
|
||||
.FormatExt(gameEvent.Target.CleanedName));
|
||||
return;
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_TEMPMUTE_BAD_FORMAT"]);
|
||||
}
|
||||
}
|
49
Plugins/Mute/Commands/UnmuteCommand.cs
Normal file
49
Plugins/Mute/Commands/UnmuteCommand.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace Mute.Commands;
|
||||
|
||||
public class UnmuteCommand : Command
|
||||
{
|
||||
public UnmuteCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
|
||||
translationLookup)
|
||||
{
|
||||
Name = "unmute";
|
||||
Description = translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_DESC"];
|
||||
Alias = "um";
|
||||
Permission = EFClient.Permission.Moderator;
|
||||
RequiresTarget = true;
|
||||
SupportedGames = Plugin.SupportedGames;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_PLAYER"],
|
||||
Required = true
|
||||
},
|
||||
new CommandArgument
|
||||
{
|
||||
Name = translationLookup["COMMANDS_ARGS_REASON"],
|
||||
Required = true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
if (await Plugin.MuteManager.Unmute(gameEvent.Owner, gameEvent.Origin, gameEvent.Target, gameEvent.Data))
|
||||
{
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_UNMUTED"]
|
||||
.FormatExt(gameEvent.Target.CleanedName));
|
||||
gameEvent.Target.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"]
|
||||
.FormatExt(gameEvent.Data));
|
||||
return;
|
||||
}
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_MUTE_COMMANDS_UNMUTE_NOT_MUTED"]
|
||||
.FormatExt(gameEvent.Target.CleanedName));
|
||||
}
|
||||
}
|
20
Plugins/Mute/Mute.csproj
Normal file
20
Plugins/Mute/Mute.csproj
Normal file
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Authors>MrAmos123</Authors>
|
||||
<OutputType>Library</OutputType>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||
</Target>
|
||||
</Project>
|
170
Plugins/Mute/MuteManager.cs
Normal file
170
Plugins/Mute/MuteManager.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using Data.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using static System.Enum;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Mute;
|
||||
|
||||
public class MuteManager
|
||||
{
|
||||
private readonly IMetaServiceV2 _metaService;
|
||||
private readonly ITranslationLookup _translationLookup;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MuteManager(IMetaServiceV2 metaService, ITranslationLookup translationLookup, ILogger logger)
|
||||
{
|
||||
_metaService = metaService;
|
||||
_translationLookup = translationLookup;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public static bool IsExpiredMute(MuteStateMeta muteStateMeta) =>
|
||||
muteStateMeta.Expiration is not null && muteStateMeta.Expiration < DateTime.UtcNow;
|
||||
|
||||
public async Task<MuteStateMeta> GetCurrentMuteState(EFClient client)
|
||||
{
|
||||
var clientMuteMeta = await ReadPersistentDataV2(client);
|
||||
if (clientMuteMeta is not null) return clientMuteMeta;
|
||||
|
||||
// Return null if the client doesn't have old or new meta.
|
||||
var muteState = await ReadPersistentDataV1(client);
|
||||
clientMuteMeta = new MuteStateMeta
|
||||
{
|
||||
Reason = muteState is null ? string.Empty : _translationLookup["PLUGINS_MUTE_MIGRATED"],
|
||||
Expiration = muteState switch
|
||||
{
|
||||
null => DateTime.UtcNow,
|
||||
MuteState.Muted => null,
|
||||
_ => DateTime.UtcNow
|
||||
},
|
||||
MuteState = muteState ?? MuteState.Unmuted,
|
||||
CommandExecuted = true
|
||||
};
|
||||
|
||||
// Migrate old mute meta, else, client has no state, so set a generic one, but don't write it to database.
|
||||
if (muteState is not null)
|
||||
{
|
||||
clientMuteMeta.CommandExecuted = false;
|
||||
await WritePersistentData(client, clientMuteMeta);
|
||||
await CreatePenalty(muteState.Value, Utilities.IW4MAdminClient(), client, clientMuteMeta.Expiration,
|
||||
clientMuteMeta.Reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||
}
|
||||
|
||||
return clientMuteMeta;
|
||||
}
|
||||
|
||||
public async Task<bool> Mute(Server server, EFClient origin, EFClient target, DateTime? dateTime, string reason)
|
||||
{
|
||||
var clientMuteMeta = await GetCurrentMuteState(target);
|
||||
if (clientMuteMeta.MuteState is MuteState.Muted && clientMuteMeta.CommandExecuted) return false;
|
||||
|
||||
await CreatePenalty(MuteState.Muted, origin, target, dateTime, reason);
|
||||
|
||||
clientMuteMeta = new MuteStateMeta
|
||||
{
|
||||
Expiration = dateTime,
|
||||
MuteState = MuteState.Muted,
|
||||
Reason = reason,
|
||||
CommandExecuted = false
|
||||
};
|
||||
await WritePersistentData(target, clientMuteMeta);
|
||||
|
||||
// Handle game command
|
||||
var client = server.GetClientsAsList().FirstOrDefault(client => client.NetworkId == target.NetworkId);
|
||||
await PerformGameCommand(server, client, clientMuteMeta);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Unmute(Server server, EFClient origin, EFClient target, string reason)
|
||||
{
|
||||
var clientMuteMeta = await GetCurrentMuteState(target);
|
||||
if (clientMuteMeta.MuteState is MuteState.Unmuted && clientMuteMeta.CommandExecuted) return false;
|
||||
if (!target.IsIngame && clientMuteMeta.MuteState is MuteState.Unmuting) return false;
|
||||
|
||||
await CreatePenalty(MuteState.Unmuted, origin, target, DateTime.UtcNow, reason);
|
||||
|
||||
clientMuteMeta = new MuteStateMeta
|
||||
{
|
||||
Expiration = DateTime.UtcNow,
|
||||
MuteState = target.IsIngame ? MuteState.Unmuted : MuteState.Unmuting,
|
||||
Reason = reason,
|
||||
CommandExecuted = false
|
||||
};
|
||||
await WritePersistentData(target, clientMuteMeta);
|
||||
|
||||
// Handle game command
|
||||
var client = server.GetClientsAsList().FirstOrDefault(client => client.NetworkId == target.NetworkId);
|
||||
await PerformGameCommand(server, client, clientMuteMeta);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task CreatePenalty(MuteState muteState, EFClient origin, EFClient target, DateTime? dateTime,
|
||||
string reason)
|
||||
{
|
||||
var newPenalty = new EFPenalty
|
||||
{
|
||||
Type = muteState is MuteState.Unmuted ? EFPenalty.PenaltyType.Unmute :
|
||||
dateTime is null ? EFPenalty.PenaltyType.Mute : EFPenalty.PenaltyType.TempMute,
|
||||
Expires = muteState is MuteState.Unmuted ? DateTime.UtcNow : dateTime,
|
||||
Offender = target,
|
||||
Offense = reason,
|
||||
Punisher = origin,
|
||||
Active = true,
|
||||
Link = target.AliasLink
|
||||
};
|
||||
_logger.LogDebug("Creating new {MuteState} Penalty for {Target} with reason {Reason}",
|
||||
nameof(muteState), target.Name, reason);
|
||||
await newPenalty.TryCreatePenalty(Plugin.Manager.GetPenaltyService(), _logger);
|
||||
}
|
||||
|
||||
public static async Task PerformGameCommand(Server server, EFClient? client, MuteStateMeta muteStateMeta)
|
||||
{
|
||||
if (client is null || !client.IsIngame) return;
|
||||
|
||||
switch (muteStateMeta.MuteState)
|
||||
{
|
||||
case MuteState.Muted:
|
||||
await server.ExecuteCommandAsync($"muteClient {client.ClientNumber}");
|
||||
muteStateMeta.CommandExecuted = true;
|
||||
break;
|
||||
case MuteState.Unmuted:
|
||||
await server.ExecuteCommandAsync($"unmute {client.ClientNumber}");
|
||||
muteStateMeta.CommandExecuted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<MuteState?> ReadPersistentDataV1(EFClient client) => TryParse<MuteState>(
|
||||
(await _metaService.GetPersistentMeta(Plugin.MuteKey, client.ClientId))?.Value,
|
||||
out var muteState)
|
||||
? muteState
|
||||
: null;
|
||||
|
||||
private async Task<MuteStateMeta?> ReadPersistentDataV2(EFClient client)
|
||||
{
|
||||
// Get meta from client
|
||||
var clientMuteMeta = client.GetAdditionalProperty<MuteStateMeta>(Plugin.MuteKey);
|
||||
if (clientMuteMeta is not null) return clientMuteMeta;
|
||||
|
||||
// Get meta from database and store in client if exists
|
||||
clientMuteMeta = await _metaService.GetPersistentMetaValue<MuteStateMeta>(Plugin.MuteKey, client.ClientId);
|
||||
if (clientMuteMeta is not null) client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||
|
||||
return clientMuteMeta;
|
||||
}
|
||||
|
||||
private async Task WritePersistentData(EFClient client, MuteStateMeta clientMuteMeta)
|
||||
{
|
||||
client.SetAdditionalProperty(Plugin.MuteKey, clientMuteMeta);
|
||||
await _metaService.SetPersistentMetaValue(Plugin.MuteKey, clientMuteMeta, client.ClientId);
|
||||
}
|
||||
}
|
18
Plugins/Mute/MuteStateMeta.cs
Normal file
18
Plugins/Mute/MuteStateMeta.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Mute;
|
||||
|
||||
public class MuteStateMeta
|
||||
{
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
public DateTime? Expiration { get; set; }
|
||||
public MuteState MuteState { get; set; }
|
||||
[JsonIgnore] public bool CommandExecuted { get; set; }
|
||||
}
|
||||
|
||||
public enum MuteState
|
||||
{
|
||||
Muted,
|
||||
Unmuting,
|
||||
Unmuted
|
||||
}
|
304
Plugins/Mute/Plugin.cs
Normal file
304
Plugins/Mute/Plugin.cs
Normal file
@ -0,0 +1,304 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Mute.Commands;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace Mute;
|
||||
|
||||
public class Plugin : IPlugin
|
||||
{
|
||||
public string Name => "Mute";
|
||||
public float Version => (float) Utilities.GetVersionAsDouble();
|
||||
public string Author => "Amos";
|
||||
|
||||
public const string MuteKey = "IW4MMute";
|
||||
public static MuteManager MuteManager { get; private set; } = null!;
|
||||
public static IManager Manager { get; private set; } = null!;
|
||||
public static readonly Server.Game[] SupportedGames = {Server.Game.IW4};
|
||||
private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"};
|
||||
private readonly IInteractionRegistration _interactionRegistration;
|
||||
private readonly IRemoteCommandService _remoteCommandService;
|
||||
private static readonly string MuteInteraction = nameof(MuteInteraction);
|
||||
|
||||
public Plugin(ILogger<Plugin> logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration,
|
||||
ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService)
|
||||
{
|
||||
_interactionRegistration = interactionRegistration;
|
||||
_remoteCommandService = remoteCommandService;
|
||||
MuteManager = new MuteManager(metaService, translationLookup, logger);
|
||||
}
|
||||
|
||||
public async Task OnEventAsync(GameEvent gameEvent, Server server)
|
||||
{
|
||||
if (!SupportedGames.Contains(server.GameName)) return;
|
||||
|
||||
switch (gameEvent.Type)
|
||||
{
|
||||
case GameEvent.EventType.Command:
|
||||
|
||||
break;
|
||||
case GameEvent.EventType.Join:
|
||||
// Check if user has any meta set, else ignore (unmuted)
|
||||
var muteMetaJoin = await MuteManager.GetCurrentMuteState(gameEvent.Origin);
|
||||
|
||||
switch (muteMetaJoin.MuteState)
|
||||
{
|
||||
case MuteState.Muted:
|
||||
// Let the client know when their mute expires.
|
||||
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||
.LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt(
|
||||
muteMetaJoin.Expiration is not null
|
||||
? muteMetaJoin.Expiration.Value.HumanizeForCurrentCulture()
|
||||
: Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"],
|
||||
muteMetaJoin.Reason));
|
||||
break;
|
||||
case MuteState.Unmuting:
|
||||
// Handle unmute of unmuted players.
|
||||
await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), gameEvent.Origin,
|
||||
muteMetaJoin.Reason);
|
||||
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||
.LocalizationIndex["PLUGINS_MUTE_COMMANDS_UNMUTE_TARGET_UNMUTED"]
|
||||
.FormatExt(muteMetaJoin.Reason));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case GameEvent.EventType.Say:
|
||||
var muteMetaSay = await MuteManager.GetCurrentMuteState(gameEvent.Origin);
|
||||
|
||||
switch (muteMetaSay.MuteState)
|
||||
{
|
||||
case MuteState.Muted:
|
||||
// Let the client know when their mute expires.
|
||||
gameEvent.Origin.Tell(Utilities.CurrentLocalization
|
||||
.LocalizationIndex["PLUGINS_MUTE_REMAINING_TIME"].FormatExt(
|
||||
muteMetaSay.Expiration is not null
|
||||
? muteMetaSay.Expiration.Value.HumanizeForCurrentCulture()
|
||||
: Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_NEVER"],
|
||||
muteMetaSay.Reason));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case GameEvent.EventType.Update:
|
||||
// Get correct EFClient object
|
||||
var client = server.GetClientsAsList()
|
||||
.FirstOrDefault(client => client.NetworkId == gameEvent.Origin.NetworkId);
|
||||
if (client == null) break;
|
||||
|
||||
var muteMetaUpdate = await MuteManager.GetCurrentMuteState(client);
|
||||
if (!muteMetaUpdate.CommandExecuted)
|
||||
{
|
||||
await MuteManager.PerformGameCommand(server, client, muteMetaUpdate);
|
||||
}
|
||||
|
||||
switch (muteMetaUpdate.MuteState)
|
||||
{
|
||||
case MuteState.Muted:
|
||||
// Handle unmute if expired.
|
||||
if (MuteManager.IsExpiredMute(muteMetaUpdate))
|
||||
{
|
||||
await MuteManager.Unmute(server, Utilities.IW4MAdminClient(), client,
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_EXPIRED"]);
|
||||
client.Tell(
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_MUTE_TARGET_EXPIRED"]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnLoadAsync(IManager manager)
|
||||
{
|
||||
Manager = manager;
|
||||
|
||||
manager.CommandInterceptors.Add(gameEvent =>
|
||||
{
|
||||
if (gameEvent.Extra is not Command command)
|
||||
return true;
|
||||
return !DisabledCommands.Contains(command.GetType().Name) && !command.IsBroadcast;
|
||||
});
|
||||
|
||||
_interactionRegistration.RegisterInteraction(MuteInteraction, async (targetClientId, game, token) =>
|
||||
{
|
||||
if (!targetClientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game) game.Value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var clientMuteMetaState =
|
||||
(await MuteManager.GetCurrentMuteState(new EFClient {ClientId = targetClientId.Value}))
|
||||
.MuteState;
|
||||
var server = manager.GetServers().First();
|
||||
|
||||
string GetCommandName(Type commandType) =>
|
||||
manager.Commands.FirstOrDefault(command => command.GetType() == commandType)?.Name ?? "";
|
||||
|
||||
return clientMuteMetaState is MuteState.Unmuted or MuteState.Unmuting
|
||||
? CreateMuteInteraction(targetClientId.Value, server, GetCommandName)
|
||||
: CreateUnmuteInteraction(targetClientId.Value, server, GetCommandName);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private InteractionData CreateMuteInteraction(int targetClientId, Server server,
|
||||
Func<Type, string> getCommandNameFunc)
|
||||
{
|
||||
var reasonInput = new
|
||||
{
|
||||
Name = "Reason",
|
||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
Type = "text",
|
||||
Values = (Dictionary<string, string>?) null
|
||||
};
|
||||
|
||||
var durationInput = new
|
||||
{
|
||||
Name = "Duration",
|
||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_DURATION"],
|
||||
Type = "select",
|
||||
Values = (Dictionary<string, string>?) new Dictionary<string, string>
|
||||
{
|
||||
{"5m", TimeSpan.FromMinutes(5).HumanizeForCurrentCulture()},
|
||||
{"30m", TimeSpan.FromMinutes(30).HumanizeForCurrentCulture()},
|
||||
{"1h", TimeSpan.FromHours(1).HumanizeForCurrentCulture()},
|
||||
{"6h", TimeSpan.FromHours(6).HumanizeForCurrentCulture()},
|
||||
{"1d", TimeSpan.FromDays(1).HumanizeForCurrentCulture()},
|
||||
{"p", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_SELECTION_PERMANENT"]}
|
||||
}
|
||||
};
|
||||
|
||||
var inputs = new[] {reasonInput, durationInput};
|
||||
var inputsJson = JsonSerializer.Serialize(inputs);
|
||||
|
||||
return new InteractionData
|
||||
{
|
||||
EntityId = targetClientId,
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"],
|
||||
DisplayMeta = "oi-volume-off",
|
||||
ActionPath = "DynamicAction",
|
||||
ActionMeta = new()
|
||||
{
|
||||
{"InteractionId", MuteInteraction},
|
||||
{"Inputs", inputsJson},
|
||||
{
|
||||
"ActionButtonLabel",
|
||||
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"]
|
||||
},
|
||||
{
|
||||
"Name",
|
||||
Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"]
|
||||
},
|
||||
{"ShouldRefresh", true.ToString()}
|
||||
},
|
||||
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||
Source = Name,
|
||||
Action = async (originId, targetId, gameName, meta, cancellationToken) =>
|
||||
{
|
||||
if (!targetId.HasValue)
|
||||
{
|
||||
return "No target client id specified";
|
||||
}
|
||||
|
||||
var isTempMute = meta.ContainsKey(durationInput.Name) &&
|
||||
meta[durationInput.Name] != durationInput.Values?.Last().Key;
|
||||
var muteCommand = getCommandNameFunc(isTempMute ? typeof(TempMuteCommand) : typeof(MuteCommand));
|
||||
var args = new List<string>();
|
||||
|
||||
if (meta.TryGetValue(durationInput.Name, out var duration) &&
|
||||
duration != durationInput.Values?.Last().Key)
|
||||
{
|
||||
args.Add(duration);
|
||||
}
|
||||
|
||||
if (meta.TryGetValue(reasonInput.Name, out var reason))
|
||||
{
|
||||
args.Add(reason);
|
||||
}
|
||||
|
||||
var commandResponse =
|
||||
await _remoteCommandService.Execute(originId, targetId, muteCommand, args, server);
|
||||
return string.Join(".", commandResponse.Select(result => result.Response));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private InteractionData CreateUnmuteInteraction(int targetClientId, Server server,
|
||||
Func<Type, string> getCommandNameFunc)
|
||||
{
|
||||
var reasonInput = new
|
||||
{
|
||||
Name = "Reason",
|
||||
Label = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
Type = "text",
|
||||
};
|
||||
|
||||
var inputs = new[] {reasonInput};
|
||||
var inputsJson = JsonSerializer.Serialize(inputs);
|
||||
|
||||
return new InteractionData
|
||||
{
|
||||
EntityId = targetClientId,
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex[
|
||||
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"],
|
||||
DisplayMeta = "oi-volume-high",
|
||||
ActionPath = "DynamicAction",
|
||||
ActionMeta = new()
|
||||
{
|
||||
{"InteractionId", MuteInteraction},
|
||||
{"Outputs", reasonInput.Name},
|
||||
{"Inputs", inputsJson},
|
||||
{
|
||||
"ActionButtonLabel",
|
||||
Utilities.CurrentLocalization.LocalizationIndex[
|
||||
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"]
|
||||
},
|
||||
{
|
||||
"Name",
|
||||
Utilities.CurrentLocalization.LocalizationIndex[
|
||||
"WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"]
|
||||
},
|
||||
{"ShouldRefresh", true.ToString()}
|
||||
},
|
||||
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||
Source = Name,
|
||||
Action = async (originId, targetId, gameName, meta, cancellationToken) =>
|
||||
{
|
||||
if (!targetId.HasValue)
|
||||
{
|
||||
return "No target client id specified";
|
||||
}
|
||||
|
||||
var args = new List<string>();
|
||||
|
||||
if (meta.TryGetValue(reasonInput.Name, out var reason))
|
||||
{
|
||||
args.Add(reason);
|
||||
}
|
||||
|
||||
var commandResponse =
|
||||
await _remoteCommandService.Execute(originId, targetId, getCommandNameFunc(typeof(UnmuteCommand)),
|
||||
args, server);
|
||||
return string.Join(".", commandResponse.Select(result => result.Response));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Task OnUnloadAsync()
|
||||
{
|
||||
_interactionRegistration.UnregisterInteraction(MuteInteraction);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnTickAsync(Server server)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -85,7 +85,7 @@ let commands = [{
|
||||
name: 'weapon name',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -103,7 +103,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -121,7 +121,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -139,7 +139,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -147,24 +147,6 @@ let commands = [{
|
||||
sendScriptCommand(gameEvent.Owner, 'LockControls', gameEvent.Origin, gameEvent.Target, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'unlockcontrols',
|
||||
description: 'unlocks target player\'s controls',
|
||||
alias: 'ulc',
|
||||
permission: 'Administrator',
|
||||
targetRequired: true,
|
||||
arguments: [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
}
|
||||
sendScriptCommand(gameEvent.Owner, 'UnlockControls', gameEvent.Origin, gameEvent.Target, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'noclip',
|
||||
description: 'enable noclip on yourself ingame',
|
||||
@ -180,21 +162,6 @@ let commands = [{
|
||||
sendScriptCommand(gameEvent.Owner, 'NoClip', gameEvent.Origin, gameEvent.Origin, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'noclipoff',
|
||||
description: 'disable noclip on yourself ingame',
|
||||
alias: 'nco',
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
}
|
||||
sendScriptCommand(gameEvent.Owner, 'NoClipOff', gameEvent.Origin, gameEvent.Origin, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'hide',
|
||||
description: 'hide yourself ingame',
|
||||
@ -202,7 +169,7 @@ let commands = [{
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -210,21 +177,6 @@ let commands = [{
|
||||
sendScriptCommand(gameEvent.Owner, 'Hide', gameEvent.Origin, gameEvent.Origin, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'unhide',
|
||||
description: 'unhide yourself ingame',
|
||||
alias: 'unh',
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
}
|
||||
sendScriptCommand(gameEvent.Owner, 'Unhide', gameEvent.Origin, gameEvent.Origin, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'alert',
|
||||
description: 'alert a player',
|
||||
@ -239,7 +191,7 @@ let commands = [{
|
||||
name: 'message',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -260,7 +212,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -278,7 +230,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -304,7 +256,7 @@ let commands = [{
|
||||
name: 'z',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -328,7 +280,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -336,21 +288,6 @@ let commands = [{
|
||||
sendScriptCommand(gameEvent.Owner, 'Kill', gameEvent.Origin, gameEvent.Target, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'nightmode',
|
||||
description: 'sets server into nightmode',
|
||||
alias: 'nitem',
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
}
|
||||
sendScriptCommand(gameEvent.Owner, 'NightMode', gameEvent.Origin, undefined, undefined);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'setspectator',
|
||||
description: 'sets a player as spectator',
|
||||
@ -361,7 +298,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
supportedGames: ['IW4', 'IW5', 'T5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -478,15 +415,19 @@ function onReceivedDvar(server, dvarName, dvarValue, success) {
|
||||
|
||||
let data = [];
|
||||
|
||||
const metaService = _serviceResolver.ResolveService('IMetaServiceV2');
|
||||
|
||||
if (event.subType === 'Meta') {
|
||||
const metaService = _serviceResolver.ResolveService('IMetaServiceV2');
|
||||
const meta = metaService.GetPersistentMeta(event.data, client, token).GetAwaiter().GetResult();
|
||||
const meta = metaService.GetPersistentMeta(event.data, client.ClientId, token).GetAwaiter().GetResult();
|
||||
data[event.data] = meta === null ? '' : meta.Value;
|
||||
logger.WriteDebug(`event data is ${event.data}`);
|
||||
} else {
|
||||
const tagMeta = metaService.GetPersistentMetaByLookup('ClientTagV2', 'ClientTagNameV2', client.ClientId, token).GetAwaiter().GetResult();
|
||||
data = {
|
||||
level: client.Level,
|
||||
clientId: client.ClientId,
|
||||
lastConnection: client.LastConnection
|
||||
lastConnection: client.LastConnection,
|
||||
tag: tagMeta?.Value ?? ''
|
||||
};
|
||||
}
|
||||
|
||||
@ -627,7 +568,7 @@ const parseDataString = data => {
|
||||
dict[keyValue[0]] = keyValue[1];
|
||||
}
|
||||
|
||||
return dict.length === 0 ? data : dict;
|
||||
return Object.keys(dict).length === 0 ? data : dict;
|
||||
}
|
||||
|
||||
const validateEnabled = (server, origin) => {
|
||||
|
@ -3,7 +3,7 @@ var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'FrenchFry, RaidMax',
|
||||
version: 0.8,
|
||||
version: 0.9,
|
||||
name: 'CoD4x Parser',
|
||||
isParser: true,
|
||||
|
||||
@ -15,7 +15,7 @@ var plugin = {
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +playerid +steamid +name +lastmsg +address +qport +rate *';
|
||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]{16,32})|0) +([[0-9]+|0]) +(.{0,32}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|bot) +(-*[0-9]+) +([0-9]+) *$';
|
||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]{16,32})|0) +([[0-9]+|0]) +(.{0,34}) +([0-9]+) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|bot) +(-*[0-9]+) +([0-9]+) *$';
|
||||
rconParser.Configuration.Status.AddMapping(104, 6); // RConName
|
||||
rconParser.Configuration.Status.AddMapping(105, 8); // RConIPAddress
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||
@ -41,4 +41,4 @@ var plugin = {
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.4,
|
||||
version: 0.5,
|
||||
name: 'Black Ops 3 Parser',
|
||||
isParser: true,
|
||||
|
||||
@ -15,7 +15,7 @@ var plugin = {
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
|
||||
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';
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +ping +xuid +name +address +qport|---------- Live ----------';
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0}';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0}';
|
||||
@ -30,6 +30,7 @@ var plugin = {
|
||||
rconParser.Configuration.DefaultRConPort = 27016;
|
||||
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'live_steam_server_name');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'live_steam_server_password');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('g_gametype', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', '');
|
||||
|
@ -1,12 +1,12 @@
|
||||
let vpnExceptionIds = [];
|
||||
const commands = [{
|
||||
name: "whitelistvpn",
|
||||
description: "whitelists a player's client id from VPN detection",
|
||||
alias: "wv",
|
||||
permission: "SeniorAdmin",
|
||||
name: 'whitelistvpn',
|
||||
description: 'whitelists a player\'s client id from VPN detection',
|
||||
alias: 'wv',
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: true,
|
||||
arguments: [{
|
||||
name: "player",
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
execute: (gameEvent) => {
|
||||
@ -19,12 +19,11 @@ const commands = [{
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.3,
|
||||
version: 1.5,
|
||||
name: 'VPN Detection Plugin',
|
||||
manager: null,
|
||||
logger: null,
|
||||
|
||||
|
||||
checkForVpn: function (origin) {
|
||||
let exempt = false;
|
||||
// prevent players that are exempt from being kicked
|
||||
@ -80,11 +79,37 @@ const plugin = {
|
||||
this.logger = manager.GetLogger(0);
|
||||
|
||||
this.configHandler = _configHandler;
|
||||
this.configHandler.GetValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(element));
|
||||
this.configHandler.GetValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(parseInt(element)));
|
||||
this.logger.WriteInfo(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
||||
|
||||
this.interactionRegistration = _serviceResolver.ResolveService('IInteractionRegistration');
|
||||
this.interactionRegistration.RegisterScriptInteraction('WhitelistVPN', this.name, (targetId, game, token) => {
|
||||
if (vpnExceptionIds.includes(targetId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const helpers = importNamespace('SharedLibraryCore.Helpers');
|
||||
const interactionData = new helpers.InteractionData();
|
||||
|
||||
interactionData.EntityId = targetId;
|
||||
interactionData.Name = 'Whitelist VPN';
|
||||
interactionData.DisplayMeta = 'oi-circle-check';
|
||||
|
||||
interactionData.ActionMeta.Add('InteractionId', 'command');
|
||||
interactionData.ActionMeta.Add('Data', `whitelistvpn`);
|
||||
interactionData.ActionMeta.Add('ActionButtonLabel', 'Allow');
|
||||
interactionData.ActionMeta.Add('Name', 'Allow VPN Connection');
|
||||
interactionData.ActionMeta.Add('ShouldRefresh', true.toString());
|
||||
|
||||
interactionData.ActionPath = 'DynamicAction';
|
||||
interactionData.MinimumPermission = 3;
|
||||
interactionData.Source = this.name;
|
||||
return interactionData;
|
||||
});
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
this.interactionRegistration.UnregisterInteraction('WhitelistVPN');
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
|
@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -20,7 +20,7 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.10.13.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -7,6 +7,7 @@ namespace SharedLibraryCore.Dtos
|
||||
public class PenaltyInfo : SharedInfo
|
||||
{
|
||||
public string OffenderName { get; set; }
|
||||
public Permission OffenderLevel { get; set; }
|
||||
public int OffenderId { get; set; }
|
||||
public ulong OffenderNetworkId { get; set; }
|
||||
public string OffenderIPAddress { get; set; }
|
||||
@ -38,4 +39,4 @@ namespace SharedLibraryCore.Dtos
|
||||
public string AdditionalPenaltyInformation =>
|
||||
$"{(!string.IsNullOrEmpty(AutomatedOffense) ? $" ({AutomatedOffense})" : "")}{(IsEvade ? $" ({Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_EVADE"]})" : "")}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,5 +34,6 @@ namespace SharedLibraryCore.Dtos
|
||||
public string CurrentServerName { get; set; }
|
||||
public IGeoLocationResult GeoLocationInfo { get; set; }
|
||||
public ClientNoteMetaResponse NoteMeta { get; set; }
|
||||
public List<IInteractionData> Interactions { get; set; }
|
||||
}
|
||||
}
|
||||
|
26
SharedLibraryCore/Helpers/InteractionData.cs
Normal file
26
SharedLibraryCore/Helpers/InteractionData.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Data.Models.Client;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using InteractionCallback = System.Func<int, int?, Data.Models.Reference.Game?, System.Collections.Generic.IDictionary<string,string>, System.Threading.CancellationToken, System.Threading.Tasks.Task<string>>;
|
||||
|
||||
namespace SharedLibraryCore.Helpers;
|
||||
|
||||
public class InteractionData : IInteractionData
|
||||
{
|
||||
public int? EntityId { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string DisplayMeta { get; set; }
|
||||
public string ActionPath { get; set; }
|
||||
public Dictionary<string, string> ActionMeta { get; set; } = new();
|
||||
public string ActionUri => ActionPath + "?" + string.Join('&', ActionMeta.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||
public EFClient.Permission? MinimumPermission { get; set; }
|
||||
public string PermissionEntity { get; set; } = "Interaction";
|
||||
public string PermissionAccess { get; set; } = "Read";
|
||||
public string Source { get; set; }
|
||||
public InteractionCallback Action { get; set; }
|
||||
public Delegate ScriptAction { get; set; }
|
||||
}
|
24
SharedLibraryCore/Interfaces/IInteractionData.cs
Normal file
24
SharedLibraryCore/Interfaces/IInteractionData.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Data.Models.Client;
|
||||
using InteractionCallback = System.Func<int, int?, Data.Models.Reference.Game?, System.Collections.Generic.IDictionary<string,string>, System.Threading.CancellationToken, System.Threading.Tasks.Task<string>>;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
public interface IInteractionData
|
||||
{
|
||||
int? EntityId { get; }
|
||||
bool Enabled { get; }
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
string DisplayMeta { get; }
|
||||
string ActionPath { get; }
|
||||
Dictionary<string, string> ActionMeta { get; }
|
||||
string ActionUri { get; }
|
||||
EFClient.Permission? MinimumPermission { get; }
|
||||
string PermissionEntity { get; }
|
||||
string PermissionAccess { get; }
|
||||
string Source { get; }
|
||||
InteractionCallback Action { get; }
|
||||
Delegate ScriptAction { get; }
|
||||
}
|
17
SharedLibraryCore/Interfaces/IInteractionRegistration.cs
Normal file
17
SharedLibraryCore/Interfaces/IInteractionRegistration.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
public interface IInteractionRegistration
|
||||
{
|
||||
void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration);
|
||||
void RegisterInteraction(string interactionName, Func<int?, Reference.Game?, CancellationToken, Task<IInteractionData>> interactionRegistration);
|
||||
void UnregisterInteraction(string interactionName);
|
||||
Task<IEnumerable<IInteractionData>> GetInteractions(int? clientId = null,
|
||||
Reference.Game? game = null, CancellationToken token = default);
|
||||
Task<string> ProcessInteraction(string interactionId, int originId, int? targetId = null, Reference.Game? game = null, IDictionary<string, string> meta = null, CancellationToken token = default);
|
||||
}
|
@ -23,6 +23,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
IList<IRConParser> AdditionalRConParsers { get; }
|
||||
IList<IEventParser> AdditionalEventParsers { get; }
|
||||
IMiddlewareActionHandler MiddlewareActionHandler { get; }
|
||||
IList<Func<GameEvent, bool>> CommandInterceptors { get; }
|
||||
string Version { get; }
|
||||
ITokenAuthentication TokenAuthenticator { get; }
|
||||
string ExternalIPAddress { get; }
|
||||
@ -102,7 +103,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// event executed when event has finished executing
|
||||
/// </summary>
|
||||
event EventHandler<GameEvent> OnGameEventExecuted;
|
||||
|
||||
|
||||
IAlertManager AlertManager { get; }
|
||||
}
|
||||
}
|
||||
|
10
SharedLibraryCore/Interfaces/IRemoteCommandService.cs
Normal file
10
SharedLibraryCore/Interfaces/IRemoteCommandService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Dtos;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
public interface IRemoteCommandService
|
||||
{
|
||||
Task<IEnumerable<CommandResponseInfo>> Execute(int originId, int? targetId, string command, IEnumerable<string> arguments, Server server);
|
||||
}
|
@ -286,7 +286,10 @@ namespace SharedLibraryCore.Services
|
||||
entity.PasswordSalt = temporalClient.PasswordSalt;
|
||||
}
|
||||
|
||||
entity.GameName = temporalClient.GameName;
|
||||
if (entity.GameName == Reference.Game.UKN && temporalClient.GameName != entity.GameName)
|
||||
{
|
||||
entity.GameName = temporalClient.GameName;
|
||||
}
|
||||
|
||||
// update in database
|
||||
await context.SaveChangesAsync();
|
||||
|
@ -117,6 +117,7 @@ namespace SharedLibraryCore.Services
|
||||
AutomatedOffense = _penalty.AutomatedOffense,
|
||||
OffenderId = _penalty.OffenderId,
|
||||
OffenderName = _penalty.Offender.CurrentAlias.Name,
|
||||
OffenderLevel = _penalty.Offender.Level,
|
||||
PunisherId = _penalty.PunisherId,
|
||||
PunisherName = _penalty.Punisher.CurrentAlias.Name,
|
||||
PunisherLevel = _penalty.Punisher.Level,
|
||||
|
@ -4,7 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2022.6.16.1</Version>
|
||||
<Version>2022.10.13.1</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -19,7 +19,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<PackageVersion>2022.6.16.1</PackageVersion>
|
||||
<PackageVersion>2022.10.13.1</PackageVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
@ -34,23 +34,23 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="10.3.6" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.13.14" />
|
||||
<PackageReference Include="Humanizer.Core.ru" Version="2.13.14" />
|
||||
<PackageReference Include="Humanizer.Core.de" Version="2.13.14" />
|
||||
<PackageReference Include="Humanizer.Core.es" Version="2.13.14" />
|
||||
<PackageReference Include="Humanizer.Core.pt" Version="2.13.14" />
|
||||
<PackageReference Include="FluentValidation" Version="11.2.1" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.ru" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.de" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.es" Version="2.14.1" />
|
||||
<PackageReference Include="Humanizer.Core.pt" Version="2.14.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
@ -24,6 +25,8 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly IMetaServiceV2 _metaService;
|
||||
private readonly IInteractionRegistration _interactionRegistration;
|
||||
private readonly IRemoteCommandService _remoteCommandService;
|
||||
private readonly string _banCommandName;
|
||||
private readonly string _tempbanCommandName;
|
||||
private readonly string _unbanCommandName;
|
||||
@ -37,10 +40,13 @@ namespace WebfrontCore.Controllers
|
||||
private readonly string _addClientNoteCommandName;
|
||||
|
||||
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
|
||||
ApplicationConfiguration appConfig, IMetaServiceV2 metaService) : base(manager)
|
||||
ApplicationConfiguration appConfig, IMetaServiceV2 metaService,
|
||||
IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService) : base(manager)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_metaService = metaService;
|
||||
_interactionRegistration = interactionRegistration;
|
||||
_remoteCommandService = remoteCommandService;
|
||||
|
||||
foreach (var cmd in registeredCommands)
|
||||
{
|
||||
@ -86,6 +92,129 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
public IActionResult DynamicActionForm(int? id, string meta)
|
||||
{
|
||||
var metaDict = JsonSerializer.Deserialize<Dictionary<string, string>>(meta);
|
||||
|
||||
if (metaDict is null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
metaDict.TryGetValue(nameof(ActionInfo.ActionButtonLabel), out var label);
|
||||
metaDict.TryGetValue(nameof(ActionInfo.Name), out var name);
|
||||
metaDict.TryGetValue(nameof(ActionInfo.ShouldRefresh), out var refresh);
|
||||
metaDict.TryGetValue("Data", out var data);
|
||||
metaDict.TryGetValue("InteractionId", out var interactionId);
|
||||
metaDict.TryGetValue("Inputs", out var template);
|
||||
|
||||
List<InputInfo> additionalInputs = null;
|
||||
var inputKeys = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(template))
|
||||
{
|
||||
additionalInputs = JsonSerializer.Deserialize<List<InputInfo>>(template);
|
||||
}
|
||||
|
||||
if (additionalInputs is not null)
|
||||
{
|
||||
inputKeys = string.Join(",", additionalInputs.Select(input => input.Name));
|
||||
}
|
||||
|
||||
bool.TryParse(refresh, out var shouldRefresh);
|
||||
|
||||
var inputs = new List<InputInfo>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "InteractionId",
|
||||
Value = interactionId,
|
||||
Type = "hidden"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "data",
|
||||
Value = data,
|
||||
Type = "hidden"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "TargetId",
|
||||
Value = id?.ToString(),
|
||||
Type = "hidden"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "CustomInputKeys",
|
||||
Value = inputKeys,
|
||||
Type = "hidden"
|
||||
}
|
||||
};
|
||||
|
||||
if (additionalInputs?.Any() ?? false)
|
||||
{
|
||||
inputs.AddRange(additionalInputs);
|
||||
}
|
||||
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = label,
|
||||
Name = name,
|
||||
Action = nameof(DynamicActionAsync),
|
||||
ShouldRefresh = shouldRefresh,
|
||||
Inputs = inputs
|
||||
};
|
||||
|
||||
return View("_ActionForm", info);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> DynamicActionAsync(CancellationToken token = default)
|
||||
{
|
||||
HttpContext.Request.Query.TryGetValue("InteractionId", out var interactionId);
|
||||
HttpContext.Request.Query.TryGetValue("CustomInputKeys", out var inputKeys);
|
||||
HttpContext.Request.Query.TryGetValue("Data", out var data);
|
||||
HttpContext.Request.Query.TryGetValue("TargetId", out var targetIdString);
|
||||
|
||||
var inputs = new Dictionary<string, string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(inputKeys.ToString()))
|
||||
{
|
||||
foreach (var key in inputKeys.ToString().Split(","))
|
||||
{
|
||||
HttpContext.Request.Query.TryGetValue(key, out var input);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
inputs.Add(key, HttpContext.Request.Query[key]);
|
||||
}
|
||||
}
|
||||
|
||||
var game = (Reference.Game?)null;
|
||||
var targetId = (int?)null;
|
||||
|
||||
if (int.TryParse(targetIdString.ToString().Split(",").Last(), out var parsedTargetId))
|
||||
{
|
||||
targetId = parsedTargetId;
|
||||
}
|
||||
|
||||
if (targetId.HasValue)
|
||||
{
|
||||
game = (await Manager.GetClientService().Get(targetId.Value))?.GameName;
|
||||
}
|
||||
|
||||
if (interactionId.ToString() != "command")
|
||||
{
|
||||
return Ok(await _interactionRegistration.ProcessInteraction(interactionId, Client.ClientId, targetId, game, inputs,
|
||||
token));
|
||||
}
|
||||
|
||||
var server = Manager.GetServers().First();
|
||||
return Ok(await _remoteCommandService.Execute(Client.ClientId, targetId, data, inputs.Values.Select(input => input), server));
|
||||
}
|
||||
|
||||
public IActionResult BanForm()
|
||||
{
|
||||
var info = new ActionInfo
|
||||
@ -292,7 +421,7 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
ClientId = Client.ClientId
|
||||
});
|
||||
|
||||
|
||||
return string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GENERATETOKEN_SUCCESS"],
|
||||
state.Token,
|
||||
$"{state.RemainingTime} {Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_MINUTES"]}",
|
||||
@ -645,7 +774,7 @@ namespace WebfrontCore.Controllers
|
||||
$"{_appConfig.CommandPrefix}{_setClientTagCommandName} @{targetId} {clientTag}"
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
public async Task<IActionResult> AddClientNoteForm(int id)
|
||||
{
|
||||
var existingNote = await _metaService.GetPersistentMetaValue<ClientNoteMetaResponse>("ClientNotes", id);
|
||||
@ -682,7 +811,7 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var server = Manager.GetServers().First();
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
|
@ -25,14 +25,16 @@ namespace WebfrontCore.Controllers
|
||||
private readonly StatsConfiguration _config;
|
||||
private readonly IGeoLocationService _geoLocationService;
|
||||
private readonly ClientService _clientService;
|
||||
private readonly IInteractionRegistration _interactionRegistration;
|
||||
|
||||
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
||||
IGeoLocationService geoLocationService, ClientService clientService) : base(manager)
|
||||
IGeoLocationService geoLocationService, ClientService clientService, IInteractionRegistration interactionRegistration) : base(manager)
|
||||
{
|
||||
_metaService = metaService;
|
||||
_config = config;
|
||||
_geoLocationService = geoLocationService;
|
||||
_clientService = clientService;
|
||||
_interactionRegistration = interactionRegistration;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
@ -75,10 +77,13 @@ namespace WebfrontCore.Controllers
|
||||
note.OriginEntityName = await _clientService.GetClientNameById(note.OriginEntityId);
|
||||
}
|
||||
|
||||
var interactions = await _interactionRegistration.GetInteractions(id, client.GameName, token);
|
||||
|
||||
// even though we haven't set their level to "banned" yet
|
||||
// (ie they haven't reconnected with the infringing player identifier)
|
||||
// we want to show them as banned as to not confuse people.
|
||||
if (activePenalties.Any(penalty => penalty.Type == EFPenalty.PenaltyType.Ban))
|
||||
var hasActiveBan = activePenalties.Any(penalty => penalty.Type == EFPenalty.PenaltyType.Ban);
|
||||
if (hasActiveBan)
|
||||
{
|
||||
client.Level = Data.Models.Client.EFClient.Permission.Banned;
|
||||
}
|
||||
@ -86,7 +91,9 @@ namespace WebfrontCore.Controllers
|
||||
var displayLevelInt = (int)client.Level;
|
||||
var displayLevel = client.Level.ToLocalizedLevelName();
|
||||
|
||||
if (!Authorized && client.Level.ShouldHideLevel())
|
||||
// if a linked ban has been revoked but they haven't reconnected, we should not show them as still banned
|
||||
var shouldHideBanLevel = !hasActiveBan && client.Level == Data.Models.Client.EFClient.Permission.Banned;
|
||||
if (!Authorized && client.Level.ShouldHideLevel() || shouldHideBanLevel)
|
||||
{
|
||||
displayLevelInt = (int)Data.Models.Client.EFClient.Permission.User;
|
||||
displayLevel = Data.Models.Client.EFClient.Permission.User.ToLocalizedLevelName();
|
||||
@ -134,7 +141,8 @@ namespace WebfrontCore.Controllers
|
||||
ingameClient.CurrentServer.Port),
|
||||
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
|
||||
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString),
|
||||
NoteMeta = string.IsNullOrWhiteSpace(note?.Note) ? null: note
|
||||
NoteMeta = string.IsNullOrWhiteSpace(note?.Note) ? null: note,
|
||||
Interactions = interactions.ToList()
|
||||
};
|
||||
|
||||
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
||||
|
@ -1,22 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
|
||||
namespace WebfrontCore.Controllers
|
||||
{
|
||||
public class ConsoleController : BaseController
|
||||
{
|
||||
private readonly ApplicationConfiguration _appconfig;
|
||||
private readonly IRemoteCommandService _remoteCommandService;
|
||||
|
||||
public ConsoleController(IManager manager) : base(manager)
|
||||
public ConsoleController(IManager manager, IRemoteCommandService remoteCommandService) : base(manager)
|
||||
{
|
||||
_appconfig = manager.GetApplicationSettings().Configuration();
|
||||
_remoteCommandService = remoteCommandService;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
@ -37,75 +34,8 @@ namespace WebfrontCore.Controllers
|
||||
public async Task<IActionResult> Execute(long serverId, string command)
|
||||
{
|
||||
var server = Manager.GetServers().First(s => s.EndPoint == serverId);
|
||||
|
||||
var client = new EFClient
|
||||
{
|
||||
ClientId = Client.ClientId,
|
||||
Level = Client.Level,
|
||||
NetworkId = Client.NetworkId,
|
||||
CurrentServer = server,
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = Client.Name
|
||||
}
|
||||
};
|
||||
|
||||
var remoteEvent = new GameEvent
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = command.StartsWith(_appconfig.CommandPrefix) ||
|
||||
command.StartsWith(_appconfig.BroadcastCommandPrefix)
|
||||
? command
|
||||
: $"{_appconfig.CommandPrefix}{command}",
|
||||
Origin = client,
|
||||
Owner = server,
|
||||
IsRemote = true
|
||||
};
|
||||
|
||||
Manager.AddEvent(remoteEvent);
|
||||
CommandResponseInfo[] response = null;
|
||||
|
||||
try
|
||||
{
|
||||
// wait for the event to process
|
||||
var completedEvent =
|
||||
await remoteEvent.WaitAsync(Utilities.DefaultCommandTimeout, server.Manager.CancellationToken);
|
||||
|
||||
if (completedEvent.FailReason == GameEvent.EventFailReason.Timeout)
|
||||
{
|
||||
response = new[]
|
||||
{
|
||||
new CommandResponseInfo()
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
Response = Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
response = completedEvent.Output.Select(output => new CommandResponseInfo()
|
||||
{
|
||||
Response = output,
|
||||
ClientId = client.ClientId
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
catch (System.OperationCanceledException)
|
||||
{
|
||||
response = new[]
|
||||
{
|
||||
new CommandResponseInfo
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
Response = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RESTART_SUCCESS"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return remoteEvent.Failed ? StatusCode(400, response) : Ok(response);
|
||||
var response = await _remoteCommandService.Execute(Client.ClientId, null, command, Enumerable.Empty<string>(), server);
|
||||
return !response.Any() ? StatusCode(400, response) : Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
@ -8,6 +10,7 @@ using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using SharedLibraryCore.Commands;
|
||||
using static SharedLibraryCore.GameEvent;
|
||||
|
||||
namespace WebfrontCore.Middleware
|
||||
@ -18,68 +21,25 @@ namespace WebfrontCore.Middleware
|
||||
internal class ClaimsPermissionRemoval
|
||||
{
|
||||
private readonly IManager _manager;
|
||||
private readonly List<int> _privilegedClientIds;
|
||||
private static readonly ConcurrentDictionary<int, (ClaimsState, DateTimeOffset?)> PrivilegedClientIds = new();
|
||||
private readonly RequestDelegate _nextRequest;
|
||||
|
||||
private enum ClaimsState
|
||||
{
|
||||
Current,
|
||||
Tainted
|
||||
}
|
||||
|
||||
public ClaimsPermissionRemoval(RequestDelegate nextRequest, IManager manager)
|
||||
{
|
||||
_manager = manager;
|
||||
_manager.OnGameEventExecuted += OnGameEvent;
|
||||
_privilegedClientIds = new List<int>();
|
||||
_nextRequest = nextRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for the game event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="gameEvent"></param>
|
||||
private void OnGameEvent(object sender, GameEvent gameEvent)
|
||||
{
|
||||
if (gameEvent.Type != EventType.ChangePermission || gameEvent.Extra is not EFClient.Permission perm)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_privilegedClientIds)
|
||||
{
|
||||
switch (perm)
|
||||
{
|
||||
// we want to remove the claims when the client is demoted
|
||||
case < EFClient.Permission.Trusted:
|
||||
{
|
||||
_privilegedClientIds.RemoveAll(id => id == gameEvent.Target.ClientId);
|
||||
break;
|
||||
}
|
||||
// and add if promoted
|
||||
case > EFClient.Permission.Trusted when !_privilegedClientIds.Contains(gameEvent.Target.ClientId):
|
||||
{
|
||||
_privilegedClientIds.Add(gameEvent.Target.ClientId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
// we want to load the initial list of privileged clients
|
||||
bool hasAny;
|
||||
lock (_privilegedClientIds)
|
||||
{
|
||||
hasAny = _privilegedClientIds.Any();
|
||||
}
|
||||
|
||||
if (!hasAny)
|
||||
{
|
||||
var ids = (await _manager.GetClientService().GetPrivilegedClients())
|
||||
.Select(client => client.ClientId);
|
||||
|
||||
lock (_privilegedClientIds)
|
||||
{
|
||||
_privilegedClientIds.AddRange(ids);
|
||||
}
|
||||
}
|
||||
await Initialize();
|
||||
|
||||
// sid stores the clientId
|
||||
var claimsId = context.User.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Sid)?.Value;
|
||||
@ -87,14 +47,16 @@ namespace WebfrontCore.Middleware
|
||||
if (!string.IsNullOrEmpty(claimsId))
|
||||
{
|
||||
var clientId = int.Parse(claimsId);
|
||||
bool hasKey;
|
||||
lock (_privilegedClientIds)
|
||||
bool isTainted;
|
||||
bool hasPrivilege;
|
||||
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
hasKey = _privilegedClientIds.Contains(clientId);
|
||||
hasPrivilege = PrivilegedClientIds.ContainsKey(clientId);
|
||||
isTainted = hasPrivilege && PrivilegedClientIds[clientId].Item1 == ClaimsState.Tainted;
|
||||
}
|
||||
|
||||
// they've been removed
|
||||
if (!hasKey && clientId != 1)
|
||||
if (!hasPrivilege || isTainted)
|
||||
{
|
||||
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
@ -102,5 +64,141 @@ namespace WebfrontCore.Middleware
|
||||
|
||||
await _nextRequest.Invoke(context);
|
||||
}
|
||||
|
||||
private void OnGameEvent(object sender, GameEvent gameEvent)
|
||||
{
|
||||
if (gameEvent.Extra?.GetType() == typeof(SetPasswordCommand))
|
||||
{
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
PrivilegedClientIds[gameEvent.Origin.ClientId] = (ClaimsState.Tainted, DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameEvent.Type != EventType.ChangePermission || gameEvent.Extra is not EFClient.Permission perm)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
switch (perm)
|
||||
{
|
||||
// we want to remove the claims when the client is demoted
|
||||
case < EFClient.Permission.Trusted when PrivilegedClientIds.ContainsKey(gameEvent.Target.ClientId):
|
||||
{
|
||||
PrivilegedClientIds.Remove(gameEvent.Target.ClientId, out _);
|
||||
break;
|
||||
}
|
||||
// and add if promoted
|
||||
case > EFClient.Permission.Trusted:
|
||||
{
|
||||
if (!PrivilegedClientIds.ContainsKey(gameEvent.Target.ClientId))
|
||||
{
|
||||
PrivilegedClientIds.TryAdd(gameEvent.Target.ClientId, (ClaimsState.Current, null));
|
||||
}
|
||||
else
|
||||
{
|
||||
// they've been intra-moted, so we need to taint their claims
|
||||
PrivilegedClientIds[gameEvent.Target.ClientId] =
|
||||
(ClaimsState.Tainted, DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Initialize()
|
||||
{
|
||||
// we want to load the initial list of privileged clients
|
||||
bool hasAny;
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
hasAny = PrivilegedClientIds.Any();
|
||||
}
|
||||
|
||||
if (!hasAny)
|
||||
{
|
||||
var ids = (await _manager.GetClientService().GetPrivilegedClients())
|
||||
.Select(client => client.ClientId);
|
||||
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
foreach (var id in ids)
|
||||
{
|
||||
PrivilegedClientIds.TryAdd(id, (ClaimsState.Current, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
|
||||
{
|
||||
if (context.Principal is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var claimsId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Sid)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(claimsId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var clientId = int.Parse(claimsId);
|
||||
|
||||
bool shouldSignOut;
|
||||
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
// we want to log them out if they aren't in the privileged clients list
|
||||
// or the token is tainted or the taint event occured after the token was generated
|
||||
shouldSignOut = PrivilegedClientIds.ContainsKey(clientId) &&
|
||||
(PrivilegedClientIds[clientId].Item1 == ClaimsState.Tainted ||
|
||||
PrivilegedClientIds[clientId].Item2 is not null &&
|
||||
PrivilegedClientIds[clientId].Item2.Value - context.Properties.IssuedUtc >
|
||||
TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
if (shouldSignOut)
|
||||
{
|
||||
context.RejectPrincipal();
|
||||
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task OnSignedIn(CookieSignedInContext context)
|
||||
{
|
||||
if (context.Principal is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var claimsId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Sid)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(claimsId))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var clientId = int.Parse(claimsId);
|
||||
|
||||
lock (PrivilegedClientIds)
|
||||
{
|
||||
if (PrivilegedClientIds.ContainsKey(clientId))
|
||||
{
|
||||
PrivilegedClientIds[clientId] = PrivilegedClientIds[clientId].Item1 == ClaimsState.Tainted
|
||||
? (ClaimsState.Current, DateTimeOffset.UtcNow)
|
||||
: (ClaimsState.Current, null);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ public enum WebfrontEntity
|
||||
RecentPlayersPage,
|
||||
ProfilePage,
|
||||
AdminMenu,
|
||||
ClientNote
|
||||
ClientNote,
|
||||
Interaction
|
||||
}
|
||||
|
||||
public enum WebfrontPermission
|
||||
|
@ -105,6 +105,8 @@ namespace WebfrontCore
|
||||
{
|
||||
options.AccessDeniedPath = "/";
|
||||
options.LoginPath = "/";
|
||||
options.Events.OnValidatePrincipal += ClaimsPermissionRemoval.ValidateAsync;
|
||||
options.Events.OnSignedIn += ClaimsPermissionRemoval.OnSignedIn;
|
||||
});
|
||||
|
||||
services.AddSingleton(Program.Manager);
|
||||
@ -138,6 +140,8 @@ namespace WebfrontCore
|
||||
services.AddSingleton(Program.ApplicationServiceProvider
|
||||
.GetRequiredService<StatsConfiguration>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IInteractionRegistration>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IRemoteCommandService>());
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Data.Models.Client;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using SharedLibraryCore;
|
||||
@ -30,9 +31,9 @@ public class HasPermission : TagHelper
|
||||
{
|
||||
output.TagName = null;
|
||||
var permissionLevel = _contextAccessor?.HttpContext?.User.Claims
|
||||
.FirstOrDefault(claim => claim.Type == ClaimTypes.Role)?.Value;
|
||||
.FirstOrDefault(claim => claim.Type == ClaimTypes.Role)?.Value ?? EFClient.Permission.User.ToString();
|
||||
|
||||
var hasPermission = permissionLevel != null && _permissionSets.ContainsKey(permissionLevel) &&
|
||||
var hasPermission = _permissionSets.ContainsKey(permissionLevel) &&
|
||||
_permissionSets[permissionLevel].HasPermission(Entity, Permission);
|
||||
if (!hasPermission)
|
||||
{
|
||||
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WebfrontCore.ViewModels
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ public class SideContextMenuItem
|
||||
public string Icon { get; set; }
|
||||
public string Tooltip { get; set; }
|
||||
public int? EntityId { get; set; }
|
||||
public string Meta { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@
|
||||
@foreach (var rule in rules)
|
||||
{
|
||||
<div class="rule">
|
||||
@if (!rule.StartsWith("#") && !Regex.IsMatch(rule, @"^\d+(.|\))"))
|
||||
@if (!rule.StartsWith("#") && !Regex.IsMatch(rule.StripColors(), @"^(Rule ?#?)?\d+(.|\))"))
|
||||
{
|
||||
<span>@start.</span>
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<h5 class="modal-title mb-10">@Model.Name.Titleize()</h5>
|
||||
<h5 class="modal-title mb-10">@Model.Name?.Titleize()</h5>
|
||||
@if (Model.Inputs.Any(input => input.Type != "hidden"))
|
||||
{
|
||||
<hr class="mb-10"/>
|
||||
|
@ -22,7 +22,7 @@
|
||||
EFPenalty.PenaltyType.Flag => "alert-secondary",
|
||||
EFPenalty.PenaltyType.TempBan => "alert-secondary",
|
||||
_ => "alert"
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
string ClassForProfileBackground()
|
||||
@ -391,6 +391,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var interaction in Model.Interactions.Where(i => (int)ViewBag.User.Level >= ((int?)i.MinimumPermission ?? 0)))
|
||||
{
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = interaction.Name,
|
||||
Tooltip = interaction.Description,
|
||||
EntityId = interaction.EntityId,
|
||||
Icon = interaction.DisplayMeta,
|
||||
Reference = interaction.ActionPath,
|
||||
Meta = System.Text.Json.JsonSerializer.Serialize(interaction.ActionMeta),
|
||||
IsButton = true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
|
||||
|
@ -5,54 +5,107 @@
|
||||
<div class="content mt-20">
|
||||
@foreach (var (pluginName, commandList) in Model)
|
||||
{
|
||||
<h2 class="content-title mb-lg-20 mt-20 ">@(pluginName == "Native" ? ViewBag.Localization["WEBFRONT_HELP_COMMANDS_NATIVE_TITLE"] : pluginName)</h2>
|
||||
|
||||
<table class="table rounded">
|
||||
<thead>
|
||||
<!-- desktop -->
|
||||
<tr class="bg-primary text-light d-none d-lg-table-row ">
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</th>
|
||||
<th class="text-right">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var command in commandList)
|
||||
{
|
||||
<div class="command-assembly-container">
|
||||
<h2 class="content-title mb-lg-20 mt-20 ">@(pluginName == "Native" ? ViewBag.Localization["WEBFRONT_HELP_COMMANDS_NATIVE_TITLE"] : pluginName)</h2>
|
||||
|
||||
<table class="table rounded">
|
||||
<thead>
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row bg-dark-dm bg-light-lm">
|
||||
<td>@command.Name</td>
|
||||
<td>@command.Alias</td>
|
||||
<td>@command.Description</td>
|
||||
<td>@command.RequiresTarget</td>
|
||||
<td>@ViewBag.CommandPrefix@command.Syntax.Split(ViewBag.CommandPrefix)[1]</td>
|
||||
<td class="text-right level-color-@((int)command.Permission)">@command.Permission.ToLocalizedLevelName()</td>
|
||||
<tr class="bg-primary text-light d-none d-lg-table-row ">
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_SUPPORTED_GAMES"]</th>
|
||||
<th class="text-right">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</th>
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none d-flex bg-dark-dm bg-light-lm">
|
||||
<td class="bg-primary text-right" style="min-width: 124px;">
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="mt-5 mb-5">@command.Name</div>
|
||||
<div class="mt-5 mb-5">@command.Alias</div>
|
||||
<div class="mt-5 mb-5">@command.Description</div>
|
||||
<div class="mt-5 mb-5">@command.RequiresTarget</div>
|
||||
<div class="mt-5 mb-5">@ViewBag.CommandPrefix@command.Syntax.Split(ViewBag.CommandPrefix)[1]</div>
|
||||
<div class="mt-5 mb-5 @($"level-color-{(int)command.Permission}")">@command.Permission.ToLocalizedLevelName()</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var command in commandList)
|
||||
{
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row bg-dark-dm bg-light-lm">
|
||||
<td class="font-weight-bold">@command.Name</td>
|
||||
<td>@command.Alias</td>
|
||||
<td class="text-muted">@command.Description</td>
|
||||
<td class="@(command.RequiresTarget ? "text-success" : "text-danger")">@command.RequiresTarget</td>
|
||||
<td class="text-muted">@ViewBag.CommandPrefix@command.Syntax.Split(ViewBag.CommandPrefix)[1]</td>
|
||||
<td>
|
||||
@if (command.SupportedGames is not null && command.SupportedGames.Any())
|
||||
{
|
||||
<div class="d-flex">
|
||||
@foreach (var game in command.SupportedGames)
|
||||
{
|
||||
<div class="mr-5" data-toggle="tooltip" data-title="@loc[$"GAME_{game.ToString().ToUpper()}"]">
|
||||
<div class="badge">@game.ToString()</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="badge">@loc["WEBFRONT_HELP_ALL_GAMES"]</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-right level-color-@((int)command.Permission)">@command.Permission.ToLocalizedLevelName()</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- mobile -->
|
||||
<table class="table d-lg-none rounded no-cell-divider bg-dark-dm bg-light-lm" style="border-collapse: collapse">
|
||||
@foreach (var command in commandList)
|
||||
{
|
||||
<tr>
|
||||
<th class="bg-primary text-right text-light first-row" style="width:40%">@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</th>
|
||||
<td class="first-row font-weight-bold">@command.Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="bg-primary text-right text-light">@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</th>
|
||||
<td>@command.Alias</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="bg-primary text-right text-light align-top">@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</th>
|
||||
<td class="text-muted">@command.Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="bg-primary text-right text-light">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</th>
|
||||
<td class="@(command.RequiresTarget ? "text-success" : "text-danger") align-top">@command.RequiresTarget</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="bg-primary text-right text-light align-top">@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</th>
|
||||
<td class="text-muted">@ViewBag.CommandPrefix@command.Syntax.Split(ViewBag.CommandPrefix)[1]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="bg-primary text-right text-light">@loc["WEBFRONT_HELP_SUPPORTED_GAMES"]</th>
|
||||
<td>
|
||||
@if (command.SupportedGames is not null && command.SupportedGames.Any())
|
||||
{
|
||||
<div class="d-flex">
|
||||
@foreach (var game in command.SupportedGames)
|
||||
{
|
||||
<div class="mr-5" data-toggle="tooltip" data-title="@loc[$"GAME_{game.ToString().ToUpper()}"]">
|
||||
<div class="badge">@game.ToString()</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="badge">@loc["WEBFRONT_HELP_ALL_GAMES"]</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-bottom">
|
||||
<th class="bg-primary text-right text-light last-row">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</th>
|
||||
<td class="@($"level-color-{(int)command.Permission}") last-row">@command.Permission.ToLocalizedLevelName()</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -3,7 +3,8 @@
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
|
||||
var canSeeLevel = (ViewBag.PermissionsSet as IEnumerable<string>).HasPermission(WebfrontEntity.ClientLevel, WebfrontPermission.Read) && Model.PunisherLevel != 0;
|
||||
var punisherLevelClass = canSeeLevel ? $"level-color-{(int)Model.PunisherLevel}" : "text-light-dm text-dark-lm";
|
||||
var punisherLevelClass = canSeeLevel ? $"level-color-{Model.PunisherLevel.ToString().ToLower()}" : "text-light-dm text-dark-lm";
|
||||
var offenderLevelClass = canSeeLevel ? $"level-color-{Model.OffenderLevel.ToString().ToLower()}" : "text-light-dm text-dark-lm";
|
||||
}
|
||||
|
||||
@using WebfrontCore.Permissions
|
||||
@ -12,7 +13,7 @@
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row">
|
||||
<td colspan="20%">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.OffenderId" class="link-inverse">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.OffenderId" class="@offenderLevelClass">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId">
|
||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId" data-action-meta="@item.Meta">
|
||||
<div class="@(item.IsButton ? "btn btn-block" : "")" data-title="@item.Tooltip" data-placement="left" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
||||
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
||||
<span class="@(item.IsActive ? "text-primary" : "") text-truncate">@item.Title</span>
|
||||
@ -28,7 +28,7 @@
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<div class="mt-15 mb-15">
|
||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action") no-decoration" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId">
|
||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action") no-decoration" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId" data-action-meta="@item.Meta">
|
||||
<div class="btn btn-block btn-lg @(item.IsActive ? "btn-primary" : "") text-truncate" data-title="@item.Tooltip" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
||||
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
||||
<span>@item.Title</span>
|
||||
|
@ -46,9 +46,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BuildWebCompiler" Version="1.12.405" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.161" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -452,3 +452,22 @@ img.social-icon {
|
||||
min-width: 1.2rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
|
||||
table.no-cell-divider tr {
|
||||
//border: none !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
table.no-cell-divider td, table.no-cell-divider th {
|
||||
padding: 0.25rem 1.5rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
table.no-cell-divider td.first-row, table.no-cell-divider th.first-row {
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
table.no-cell-divider td.last-row, table.no-cell-divider th.last-row {
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
@ -107,6 +107,10 @@
|
||||
color: rgb(140, 154, 132);
|
||||
}
|
||||
|
||||
.penalties-color-unmute {
|
||||
color: #989f94;
|
||||
}
|
||||
|
||||
.penalties-color-report {
|
||||
color: #b3ae8f;
|
||||
}
|
||||
@ -121,6 +125,10 @@
|
||||
color: rgba(254, 126, 76, 0.85);
|
||||
}
|
||||
|
||||
.penalties-color-tempmute {
|
||||
color: #df9476;
|
||||
}
|
||||
|
||||
.penalties-bgcolor-tempban {
|
||||
background-color: #fe7e4c;
|
||||
background-color: rgba(254, 126, 76, 0.85);
|
||||
@ -136,6 +144,10 @@
|
||||
color: rgba(255, 69, 69, 0.85);
|
||||
}
|
||||
|
||||
.penalties-color-mute {
|
||||
color: #e77171;
|
||||
}
|
||||
|
||||
#profile_aliases_btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -91,12 +91,18 @@ $(document).ready(function () {
|
||||
$(document).off('click', '.profile-action');
|
||||
$(document).on('click', '.profile-action', function (e) {
|
||||
e.preventDefault();
|
||||
const actionType = $(this).data('action');
|
||||
const action = $(this).data('action');
|
||||
const actionId = $(this).data('action-id');
|
||||
const actionMeta = $(this).data('action-meta');
|
||||
const responseDuration = $(this).data('response-duration') || 5000;
|
||||
const actionIdKey = actionId === undefined ? '' : `?id=${actionId}`;
|
||||
let actionKeys = actionId === undefined ? '' : `?id=${actionId}`;
|
||||
|
||||
if (actionMeta !== undefined) {
|
||||
actionKeys = actionKeys + '&meta=' + JSON.stringify(actionMeta);
|
||||
}
|
||||
showLoader();
|
||||
$.get(`/Action/${actionType}Form/${actionIdKey}`)
|
||||
|
||||
$.get(`/Action/${action}Form/${actionKeys}`)
|
||||
.done(function (response) {
|
||||
$('#actionModal .modal-message').fadeOut('fast')
|
||||
$('#actionModal').attr('data-response-duration', responseDuration);
|
||||
|
Reference in New Issue
Block a user