implement dynamic command registration through game interface

This commit is contained in:
RaidMax 2023-06-03 22:46:15 -05:00
parent 2fcbab9a37
commit 3f0bdfe3a9
5 changed files with 166 additions and 29 deletions

View File

@ -76,6 +76,11 @@ public class ScriptPluginHelper
}); });
} }
public void RegisterDynamicCommand(JsValue command)
{
_scriptPlugin.RegisterDynamicCommand(command.ToObject());
}
private object RequestInternal(ScriptPluginWebRequest request) private object RequestInternal(ScriptPluginWebRequest request)
{ {
var entered = false; var entered = false;

View File

@ -47,6 +47,7 @@ public class ScriptPluginV2 : IPluginV2
private readonly List<string> _registeredCommandNames = new(); private readonly List<string> _registeredCommandNames = new();
private readonly List<string> _registeredInteractions = new(); private readonly List<string> _registeredInteractions = new();
private readonly Dictionary<MethodInfo, List<object>> _registeredEvents = new(); private readonly Dictionary<MethodInfo, List<object>> _registeredEvents = new();
private IManager _manager;
private bool _firstInitialization = true; private bool _firstInitialization = true;
private record ScriptPluginDetails(string Name, string Author, string Version, private record ScriptPluginDetails(string Name, string Author, string Version,
@ -112,8 +113,15 @@ public class ScriptPluginV2 : IPluginV2
}, _logger, _fileName, _onProcessingScript); }, _logger, _fileName, _onProcessingScript);
} }
public void RegisterDynamicCommand(object command)
{
var parsedCommand = ParseScriptCommandDetails(command);
RegisterCommand(_manager, parsedCommand.First());
}
private async Task OnLoad(IManager manager, CancellationToken token) private async Task OnLoad(IManager manager, CancellationToken token)
{ {
_manager = manager;
var entered = false; var entered = false;
try try
{ {
@ -253,9 +261,13 @@ public class ScriptPluginV2 : IPluginV2
command.Permission, command.TargetRequired, command.Permission, command.TargetRequired,
command.Arguments, Execute, command.SupportedGames); command.Arguments, Execute, command.SupportedGames);
manager.RemoveCommandByName(scriptCommand.Name);
manager.AddAdditionalCommand(scriptCommand); manager.AddAdditionalCommand(scriptCommand);
if (!_registeredCommandNames.Contains(scriptCommand.Name))
{
_registeredCommandNames.Add(scriptCommand.Name); _registeredCommandNames.Add(scriptCommand.Name);
} }
}
private void ResetEngineState() private void ResetEngineState()
{ {
@ -480,6 +492,33 @@ public class ScriptPluginV2 : IPluginV2
} }
private static ScriptPluginDetails AsScriptPluginInstance(dynamic source) private static ScriptPluginDetails AsScriptPluginInstance(dynamic source)
{
var commandDetails = ParseScriptCommandDetails(source);
var interactionDetails = Array.Empty<ScriptPluginInteractionDetails>();
if (HasProperty(source, "interactions") && source.interactions is dynamic[])
{
interactionDetails = ((dynamic[])source.interactions).Select(interaction =>
{
var name = HasProperty(interaction, "name") && interaction.name is string
? (string)interaction.name
: string.Empty;
var action = HasProperty(interaction, "action") && interaction.action is Delegate
? (Delegate)interaction.action
: null;
return new ScriptPluginInteractionDetails(name, action);
}).ToArray();
}
var name = HasProperty(source, "name") && source.name is string ? (string)source.name : string.Empty;
var author = HasProperty(source, "author") && source.author is string ? (string)source.author : string.Empty;
var version = HasProperty(source, "version") && source.version is string ? (string)source.author : string.Empty;
return new ScriptPluginDetails(name, author, version, commandDetails, interactionDetails);
}
private static ScriptPluginCommandDetails[] ParseScriptCommandDetails(dynamic source)
{ {
var commandDetails = Array.Empty<ScriptPluginCommandDetails>(); var commandDetails = Array.Empty<ScriptPluginCommandDetails>();
if (HasProperty(source, "commands") && source.commands is dynamic[]) if (HasProperty(source, "commands") && source.commands is dynamic[])
@ -513,7 +552,7 @@ public class ScriptPluginV2 : IPluginV2
(bool)command.targetRequired; (bool)command.targetRequired;
var supportedGames = var supportedGames =
HasProperty(command, "supportedGames") && command.supportedGames is IEnumerable<object> HasProperty(command, "supportedGames") && command.supportedGames is IEnumerable<object>
? ((IEnumerable<object>)command.supportedGames).Where(game => game?.ToString() is not null) ? ((IEnumerable<object>)command.supportedGames).Where(game => !string.IsNullOrEmpty(game?.ToString()))
.Select(game => .Select(game =>
Enum.Parse<Reference.Game>(game.ToString()!)) Enum.Parse<Reference.Game>(game.ToString()!))
: Array.Empty<Reference.Game>(); : Array.Empty<Reference.Game>();
@ -523,31 +562,10 @@ public class ScriptPluginV2 : IPluginV2
return new ScriptPluginCommandDetails(name, description, alias, permission, isTargetRequired, return new ScriptPluginCommandDetails(name, description, alias, permission, isTargetRequired,
commandArgs, supportedGames, execute); commandArgs, supportedGames, execute);
}).ToArray(); }).ToArray();
} }
var interactionDetails = Array.Empty<ScriptPluginInteractionDetails>(); return commandDetails;
if (HasProperty(source, "interactions") && source.interactions is dynamic[])
{
interactionDetails = ((dynamic[])source.interactions).Select(interaction =>
{
var name = HasProperty(interaction, "name") && interaction.name is string
? (string)interaction.name
: string.Empty;
var action = HasProperty(interaction, "action") && interaction.action is Delegate
? (Delegate)interaction.action
: null;
return new ScriptPluginInteractionDetails(name, action);
}).ToArray();
}
var name = HasProperty(source, "name") && source.name is string ? (string)source.name : string.Empty;
var author = HasProperty(source, "author") && source.author is string ? (string)source.author : string.Empty;
var version = HasProperty(source, "version") && source.version is string ? (string)source.author : string.Empty;
return new ScriptPluginDetails(name, author, version, commandDetails, interactionDetails);
} }
private static bool HasProperty(dynamic source, string name) private static bool HasProperty(dynamic source, string name)

View File

@ -295,6 +295,11 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
eventSubtype = "None"; eventSubtype = "None";
} }
if ( !IsDefined( entOrId ) )
{
entOrId = "-1";
}
if ( IsPlayer( entOrId ) ) if ( IsPlayer( entOrId ) )
{ {
entOrId = entOrId getEntityNumber(); entOrId = entOrId getEntityNumber();
@ -311,7 +316,7 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 ); groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
request = request + groupSeparator + eventType + groupSeparator + eventSubtype + groupSeparator + entOrId + groupSeparator + data; request = request + groupSeparator + eventType + groupSeparator + eventSubtype + groupSeparator + entOrId + groupSeparator + data;
eturn request; return request;
} }
MonitorBus() MonitorBus()
@ -534,10 +539,17 @@ OnExecuteCommand( event )
} }
if ( IsDefined( command ) ) if ( IsDefined( command ) )
{
if ( IsDefined( executionContextEntity ) )
{ {
response = executionContextEntity [[command]]( event, data ); response = executionContextEntity [[command]]( event, data );
} }
else else
{
[[command]]( event );
}
}
else
{ {
LogDebug( "Unknown Client command->" + event.subtype ); LogDebug( "Unknown Client command->" + event.subtype );
} }

View File

@ -48,6 +48,7 @@ Setup()
level.eventTypes.urlRequested = "UrlRequested"; level.eventTypes.urlRequested = "UrlRequested";
level.eventTypes.urlRequestCompleted = "UrlRequestCompleted"; level.eventTypes.urlRequestCompleted = "UrlRequestCompleted";
level.eventTypes.registerCommandRequested = "RegisterCommandRequested";
level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback; level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback;
@ -191,6 +192,78 @@ SaveTrackingMetrics()
scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId ); scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId );
} }
// #region register script command
RegisterScriptCommandObject( command )
{
RegisterScriptCommand( command.eventKey, command.name, command.alias, command.description, command.minPermission, command.supportedGames, command.requiresTarget, command.handler );
}
RegisterScriptCommand( eventKey, name, alias, description, minPermission, supportedGames, requiresTarget, handler )
{
if ( !IsDefined( eventKey ) )
{
scripts\_integration_base::LogError( "eventKey must be provided for script command" );
return;
}
data = [];
data["eventKey"] = eventKey;
if ( IsDefined( name ) )
{
data["name"] = name;
}
else
{
scripts\_integration_base::LogError( "name must be provided for script command" );
return;
}
if ( IsDefined( alias ) )
{
data["alias"] = alias;
}
if ( IsDefined( description ) )
{
data["description"] = description;
}
if ( IsDefined( minPermission ) )
{
data["minPermission"] = minPermission;
}
if ( IsDefined( supportedGames ) )
{
data["supportedGames"] = supportedGames;
}
data["requiresTarget"] = false;
if ( IsDefined( requiresTarget ) )
{
data["requiresTarget"] = requiresTarget;
}
if ( IsDefined( handler ) )
{
level.clientCommandCallbacks[eventKey + "Execute"] = handler;
level.clientCommandRusAsTarget[eventKey + "Execute"] = data["requiresTarget"];
}
else
{
scripts\_integration_base::LogWarning( "handler not defined for script command " + name );
}
commandRegisterRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.registerCommandRequested, "", undefined, data );
thread scripts\_integration_base::QueueEvent( commandRegisterRequest, level.eventTypes.registerCommandRequested, undefined );
}
// #end region
// #region web requests // #region web requests
RequestUrlObject( request ) RequestUrlObject( request )
@ -262,7 +335,6 @@ WaitForUrlRequestComplete()
scripts\_integration_base::LogDebug( "Request to " + self.url + " completed" ); scripts\_integration_base::LogDebug( "Request to " + self.url + " completed" );
//self delete();
level.notifyEntities[self.index] = undefined; level.notifyEntities[self.index] = undefined;
} }
@ -315,6 +387,8 @@ GetNextNotifyEntity()
return i; return i;
} }
} }
return max;
} }

View File

@ -321,6 +321,10 @@ const plugin = {
}); });
} }
if (event.eventType === 'RegisterCommandRequested') {
this.registerDynamicCommand(event);
}
tokenSource.dispose(); tokenSource.dispose();
return messageQueued; return messageQueued;
}, },
@ -434,6 +438,30 @@ const plugin = {
const script = importNamespace('IW4MAdmin.Application.Plugin.Script'); const script = importNamespace('IW4MAdmin.Application.Plugin.Script');
return new script.ScriptPluginWebRequest(url, body, method, contentType, headerDict); return new script.ScriptPluginWebRequest(url, body, method, contentType, headerDict);
},
registerDynamicCommand: function(event) {
const commandWrapper = {
commands: [{
name: event.data['name'] || 'DEFAULT',
description: event.data['description'] || 'DEFAULT',
alias: event.data['alias'] || 'DEFAULT',
permission: event.data['minPermission'] || 'DEFAULT',
targetRequired: (event.data['targetRequired'] || '0') === '1',
supportedGames: (event.data['supportedGames'] || '').split(','),
execute: (gameEvent) => {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
sendScriptCommand(gameEvent.owner, `${event.data['eventKey']}Execute`, gameEvent.origin, gameEvent.target, {
args: gameEvent.data
});
}
}]
}
this.scriptHelper.registerDynamicCommand(commandWrapper);
} }
}; };