add example module to game interface. convert gi command registration to a iw4madmin request

This commit is contained in:
RaidMax 2023-06-06 12:08:58 -05:00
parent 2340e30c2d
commit ad89ecb39d
7 changed files with 167 additions and 22 deletions

View File

@ -542,11 +542,11 @@ OnExecuteCommand( event )
{
if ( IsDefined( executionContextEntity ) )
{
response = executionContextEntity [[command]]( event, data );
response = executionContextEntity thread [[command]]( event, data );
}
else
{
[[command]]( event );
thread [[command]]( event );
}
}
else

View File

@ -49,11 +49,14 @@ Setup()
level.eventTypes.urlRequested = "UrlRequested";
level.eventTypes.urlRequestCompleted = "UrlRequestCompleted";
level.eventTypes.registerCommandRequested = "RegisterCommandRequested";
level.eventTypes.getCommandsRequested = "GetCommandsRequested";
level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback;
level.eventCallbacks[level.eventTypes.getCommandsRequested] = ::OnCommandsRequestedCallback;
level.iw4madminIntegrationDefaultPerformance = 200;
level.notifyEntities = [];
level.customCommands = [];
level notify( level.notifyTypes.sharedFunctionsInitialized );
level waittill( level.notifyTypes.gameFunctionsInitialized );
@ -194,6 +197,32 @@ SaveTrackingMetrics()
// #region register script command
OnCommandsRequestedCallback( event )
{
scripts\_integration_base::LogDebug( "Get commands requested" );
thread SendCommands( event.data["name"] );
}
SendCommands( commandName )
{
level endon( level.eventTypes.gameEnd );
for ( i = 0; i < level.customCommands.size; i++ )
{
data = level.customCommands[i];
if ( IsDefined( commandName ) && commandName != data["name"] )
{
continue;
}
scripts\_integration_base::LogDebug( "Sending custom command " + ( i + 1 ) + "/" + level.customCommands.size + ": " + data["name"] );
commandRegisterRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.registerCommandRequested, "", undefined, data );
// not threading here as there might be a lot of commands to register
scripts\_integration_base::QueueEvent( commandRegisterRequest, level.eventTypes.registerCommandRequested, undefined );
}
}
RegisterScriptCommandObject( command )
{
RegisterScriptCommand( command.eventKey, command.name, command.alias, command.description, command.minPermission, command.supportedGames, command.requiresTarget, command.handler );
@ -258,8 +287,7 @@ RegisterScriptCommand( eventKey, name, alias, description, minPermission, suppor
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 );
level.customCommands[level.customCommands.size] = data;
}
// #end region
@ -391,7 +419,6 @@ GetNextNotifyEntity()
return max;
}
// #end region
// #region team balance

View File

@ -0,0 +1,85 @@
Init()
{
// this gives the game interface time to setup
waittillframeend;
thread ModuleSetup();
}
ModuleSetup()
{
// waiting until the game specific functions are ready
level waittill( level.notifyTypes.gameFunctionsInitialized );
RegisterCustomCommands();
}
RegisterCustomCommands()
{
command = SpawnStruct();
// unique key for each command (how iw4madmin identifies the command)
command.eventKey = "PrintLineCommand";
// name of the command (cannot conflict with existing command names)
command.name = "println";
// short version of the command (cannot conflcit with existing command aliases)
command.alias = "pl";
// description of what the command does
command.description = "prints line to game";
// minimum permision required to execute
// valid values: User, Trusted, Moderator, Administrator, SeniorAdmin, Owner
command.minPermission = "Trusted";
// games the command is supported on
// separate with comma or don't define for all
// valid values: IW3, IW4, IW5, IW6, T4, T5, T6, T7, SHG1, CSGO, H1
command.supportedGames = "IW4,IW5,T5,T6";
// indicates if a target player must be provided to execvute on
command.requiresTarget = false;
// code to run when the command is executed
command.handler = ::PrintLnCommandCallback;
// register the command with integration to be send to iw4madmin
scripts\_integration_shared::RegisterScriptCommandObject( command );
// you can also register via parameters
scripts\_integration_shared::RegisterScriptCommand( "AffirmationCommand", "affirm", "af", "provide affirmations", "User", undefined, false, ::AffirmationCommandCallback );
}
PrintLnCommandCallback( event )
{
if ( IsDefined( event.data["args"] ) )
{
IPrintLnBold( event.data["args"] );
return;
}
scripts\_integration_base::LogDebug( "No data was provided for PrintLnCallback" );
}
AffirmationCommandCallback( event, _ )
{
level endon( level.eventTypes.gameEnd );
request = SpawnStruct();
request.url = "https://www.affirmations.dev";
request.method = "GET";
// If making a post request you can also provide more data
// request.body = "Body of the post message";
// request.headers = [];
// request.headers["Authorization"] = "api-key";
scripts\_integration_shared::RequestUrlObject( request );
request waittill( level.eventTypes.urlRequestCompleted, response );
// horrible json parsing.. but it's just an example
parsedResponse = strtok( response, "\"" );
self IPrintLnBold ( "^5" + parsedResponse[parsedResponse.size - 2] );
}

View File

@ -86,6 +86,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterf
GameFiles\GameInterface\_integration_t5zm.gsc = GameFiles\GameInterface\_integration_t5zm.gsc
GameFiles\GameInterface\_integration_t6.gsc = GameFiles\GameInterface\_integration_t6.gsc
GameFiles\GameInterface\_integration_t6zm_helper.gsc = GameFiles\GameInterface\_integration_t6zm_helper.gsc
GameFiles\GameInterface\example_module.gsc = GameFiles\GameInterface\example_module.gsc
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntiCheat", "AntiCheat", "{AB83BAC0-C539-424A-BF00-78487C10753C}"

View File

@ -147,6 +147,18 @@ namespace Integrations.Cod
{
var convertedRConPassword = ConvertEncoding(RConPassword);
var convertedParameters = ConvertEncoding(parameters);
byte SafeConversion(char c)
{
try
{
return Convert.ToByte(c);
}
catch
{
return (byte)'.';
}
};
switch (type)
{
@ -154,30 +166,30 @@ namespace Integrations.Cod
waitForResponse = true;
payload = string
.Format(_config.CommandPrefixes.RConGetDvar, convertedRConPassword,
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
convertedParameters + '\0').Select(SafeConversion).ToArray();
break;
case StaticHelpers.QueryType.SET_DVAR:
payload = string
.Format(_config.CommandPrefixes.RConSetDvar, convertedRConPassword,
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
convertedParameters + '\0').Select(SafeConversion).ToArray();
break;
case StaticHelpers.QueryType.COMMAND:
payload = string
.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword,
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
convertedParameters + '\0').Select(SafeConversion).ToArray();
break;
case StaticHelpers.QueryType.GET_STATUS:
waitForResponse = true;
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray();
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(SafeConversion).ToArray();
break;
case StaticHelpers.QueryType.GET_INFO:
waitForResponse = true;
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray();
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(SafeConversion).ToArray();
break;
case StaticHelpers.QueryType.COMMAND_STATUS:
waitForResponse = true;
payload = string.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0")
.Select(Convert.ToByte).ToArray();
.Select(SafeConversion).ToArray();
break;
}
}

View File

@ -92,9 +92,7 @@ const plugin = {
const input = serverState.inQueue.shift();
// if we queued an event then the next loop will be at the value set complete
if (await this.processEventMessage(input, serverValueEvent.server)) {
// return;
}
await this.processEventMessage(input, serverValueEvent.server);
}
this.logger.logDebug('loop complete');
@ -135,6 +133,7 @@ const plugin = {
serverState.running = true;
serverState.initializationInProgress = false;
this.sendEventMessage(responseEvent.server, true, 'GetCommandsRequested', null, null, null, {});
this.requestGetDvar(inDvar, responseEvent.server);
},
@ -145,7 +144,9 @@ const plugin = {
const serverState = servers[responseEvent.server.id];
serverState.outQueue.shift();
if (responseEvent.server.connectedClients.count === 0) {
const utilities = importNamespace('SharedLibraryCore.Utilities');
if (responseEvent.server.connectedClients.count === 0 && !utilities.isDevelopment) {
// no clients connected so we don't need to query
serverState.running = false;
return;
@ -304,9 +305,22 @@ const plugin = {
this.scriptHelper.requestUrl(urlRequest, response => {
this.logger.logDebug('Got response for gamescript web request - {Response}', response);
if ( typeof response !== 'string' && !(response instanceof String) )
{
response = JSON.stringify(response);
}
const max = 10;
this.logger.logDebug(`response length ${response.length}`);
let chunks = chunkString(response.replace(/"/gm, '\\"').replace(/[\n|\t]/gm, ''), 800);
let quoteReplace = '\\"';
// todo: may be more than just T6
if (server.gameCode === 'T6')
{
quoteReplace = '\\\\"';
}
let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800);
if (chunks.length > max)
{
this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`);
@ -454,10 +468,16 @@ const plugin = {
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
return;
}
if (gameEvent.data === '--reload')
{
this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, { name: gameEvent.extra.name });
} else {
sendScriptCommand(gameEvent.owner, `${event.data['eventKey']}Execute`, gameEvent.origin, gameEvent.target, {
args: gameEvent.data
});
}
}
}]
}