implement bus mode for game interface to allow files for bus data transfer

This commit is contained in:
RaidMax 2023-06-06 17:56:12 -05:00
parent ad89ecb39d
commit 871f8d75df
6 changed files with 190 additions and 22 deletions

View File

@ -67,7 +67,7 @@ public class ScriptPluginHelper
try try
{ {
await Task.Delay(delayMs, _manager.CancellationToken); await Task.Delay(delayMs, _manager.CancellationToken);
_scriptPlugin.ExecuteWithErrorHandling(_ => callback.DynamicInvoke(JsValue.Undefined)); _scriptPlugin.ExecuteWithErrorHandling(_ => callback.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined }));
} }
catch catch
{ {

View File

@ -21,13 +21,29 @@ Setup()
level.commonFunctions.setDvar = "SetDvarIfUninitialized"; level.commonFunctions.setDvar = "SetDvarIfUninitialized";
level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum"; level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum";
level.commonFunctions.waittillNotifyOrTimeout = "WaittillNotifyOrTimeout"; level.commonFunctions.waittillNotifyOrTimeout = "WaittillNotifyOrTimeout";
level.commonFunctions.getInboundData = "GetInboundData";
level.commonFunctions.getOutboundData = "GetOutboundData";
level.commonFunctions.setInboundData = "SetInboundData";
level.commonFunctions.setOutboundData = "SetOutboundData";
level.overrideMethods = []; level.overrideMethods = [];
level.overrideMethods[level.commonFunctions.setDvar] = scripts\_integration_base::NotImplementedFunction; level.overrideMethods[level.commonFunctions.setDvar] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getPlayerFromClientNum] = ::_GetPlayerFromClientNum; level.overrideMethods[level.commonFunctions.getPlayerFromClientNum] = ::_GetPlayerFromClientNum;
level.overrideMethods[level.commonFunctions.getInboundData] = ::_GetInboundData;
level.overrideMethods[level.commonFunctions.getOutboundData] = ::_GetOutboundData;
level.overrideMethods[level.commonFunctions.setInboundData] = ::_SetInboundData;
level.overrideMethods[level.commonFunctions.setOutboundData] = ::_SetOutboundData;
level.busMethods = [];
level.busMethods[level.commonFunctions.getInboundData] = ::_GetInboundData;
level.busMethods[level.commonFunctions.getOutboundData] = ::_GetOutboundData;
level.busMethods[level.commonFunctions.setInboundData] = ::_SetInboundData;
level.busMethods[level.commonFunctions.setOutboundData] = ::_SetOutboundData;
level.commonKeys = spawnstruct(); level.commonKeys = spawnstruct();
level.commonKeys.enabled = "sv_iw4madmin_integration_enabled"; level.commonKeys.enabled = "sv_iw4madmin_integration_enabled";
level.commonKeys.busMode = "sv_iw4madmin_integration_busmode";
level.commonKeys.busDir = "sv_iw4madmin_integration_busdir";
level.notifyTypes = spawnstruct(); level.notifyTypes = spawnstruct();
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized"; level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
@ -69,6 +85,8 @@ Setup()
_SetDvarIfUninitialized( level.eventBus.inVar, "" ); _SetDvarIfUninitialized( level.eventBus.inVar, "" );
_SetDvarIfUninitialized( level.eventBus.outVar, "" ); _SetDvarIfUninitialized( level.eventBus.outVar, "" );
_SetDvarIfUninitialized( level.commonKeys.enabled, 1 ); _SetDvarIfUninitialized( level.commonKeys.enabled, 1 );
_SetDvarIfUninitialized( level.commonKeys.busMode, "rcon" );
_SetDvarIfUninitialized( level.commonKeys.busdir, "" );
_SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 ); _SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
_SetDvarIfUninitialized( "GroupSeparatorChar", "" ); _SetDvarIfUninitialized( "GroupSeparatorChar", "" );
_SetDvarIfUninitialized( "RecordSeparatorChar", "" ); _SetDvarIfUninitialized( "RecordSeparatorChar", "" );
@ -157,6 +175,26 @@ _GetPlayerFromClientNum( clientNum )
return undefined; return undefined;
} }
_GetInboundData()
{
return GetDvar( level.eventBus.inVar );
}
_GetOutboundData()
{
return GetDvar( level.eventBus.outVar );
}
_SetInboundData( data )
{
return SetDvar( level.eventBus.inVar, data );
}
_SetOutboundData( data )
{
return SetDvar( level.eventBus.outVar, data );
}
// Not every game can output to console or even game log. // Not every game can output to console or even game log.
// Adds a very basic logging system that every // Adds a very basic logging system that every
// game specific script can extend.accumulate // game specific script can extend.accumulate
@ -322,29 +360,35 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
MonitorBus() MonitorBus()
{ {
level endon( level.eventTypes.gameEnd ); level endon( level.eventTypes.gameEnd );
[[level.overrideMethods[level.commonFunctions.SetInboundData]]]( "" );
[[level.overrideMethods[level.commonFunctions.SetOutboundData]]]( "" );
for( ;; ) for( ;; )
{ {
wait ( 0.1 ); wait ( 0.1 );
// check to see if IW4MAdmin is ready to receive more data // check to see if IW4MAdmin is ready to receive more data
if ( getDvar( level.eventBus.inVar ) == "" ) inVal = [[level.busMethods[level.commonFunctions.getInboundData]]]();
if ( !IsDefined( inVal ) || inVal == "" )
{ {
level notify( "bus_ready" ); level notify( "bus_ready" );
} }
eventString = getDvar( level.eventBus.outVar ); eventString = [[level.busMethods[level.commonFunctions.getOutboundData]]]();
if ( eventString == "" ) if ( !IsDefined( eventString ) || eventString == "" )
{ {
continue; continue;
} }
LogDebug( "-> " + eventString ); LogDebug( "-> " + eventString );
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 ); groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
NotifyEvent( strtok( eventString, groupSeparator ) ); NotifyEvent( strtok( eventString, groupSeparator ) );
SetDvar( level.eventBus.outVar, "" ); [[level.busMethods[level.commonFunctions.SetOutboundData]]]( "" );
} }
} }
@ -356,11 +400,11 @@ QueueEvent( request, eventType, notifyEntity )
maxWait = level.eventBus.timeout * 1000; // 30 seconds maxWait = level.eventBus.timeout * 1000; // 30 seconds
timedOut = ""; timedOut = "";
while ( GetDvar( level.eventBus.inVar ) != "" && ( GetTime() - start ) < maxWait ) while ( [[level.busMethods[level.commonFunctions.getInboundData]]]() != "" && ( GetTime() - start ) < maxWait )
{ {
level [[level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout]]]( "bus_ready", 1 ); level [[level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout]]]( "bus_ready", 1 );
if ( GetDvar( level.eventBus.inVar ) != "" ) if ( [[level.busMethods[level.commonFunctions.getInboundData]]]() != "" )
{ {
LogDebug( "A request is already in progress..." ); LogDebug( "A request is already in progress..." );
timedOut = "set"; timedOut = "set";
@ -379,14 +423,14 @@ QueueEvent( request, eventType, notifyEntity )
notifyEntity NotifyClientEventTimeout( eventType ); notifyEntity NotifyClientEventTimeout( eventType );
} }
SetDvar( level.eventBus.inVar, "" ); [[level.busMethods[level.commonFunctions.SetInboundData]]]( "" );
return; return;
} }
LogDebug( "<- " + request ); LogDebug( "<- " + request );
SetDvar( level.eventBus.inVar, request ); [[level.busMethods[level.commonFunctions.setInboundData]]]( request );
} }
ParseDataString( data ) ParseDataString( data )

View File

@ -28,7 +28,12 @@ Setup()
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = ::BackupRestoreClientKillStreakData; level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = ::BackupRestoreClientKillStreakData;
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout; level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper; level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
level.overrideMethods[level.commonFunctions.getInboundData] = ::GetInboundData;
level.overrideMethods[level.commonFunctions.getOutboundData] = ::GetOutboundData;
level.overrideMethods[level.commonFunctions.setInboundData] = ::SetInboundData;
level.overrideMethods[level.commonFunctions.setOutboundData] = ::SetOutboundData;
RegisterClientCommands(); RegisterClientCommands();
level notify( level.notifyTypes.gameFunctionsInitialized ); level notify( level.notifyTypes.gameFunctionsInitialized );
@ -97,6 +102,26 @@ WaitForClientEvents()
} }
} }
GetInboundData()
{
return FileRead( level.eventBus.inVar);
}
GetOutboundData()
{
return FileRead( level.eventBus.outVar );
}
SetInboundData( data )
{
FileWrite( level.eventBus.inVar, data, "write" );
}
SetOutboundData( data )
{
FileWrite(level.eventBus.outVar, data, "write" );
}
GetMaxClients() GetMaxClients()
{ {
return level.maxClients; return level.maxClients;

View File

@ -50,9 +50,11 @@ Setup()
level.eventTypes.urlRequestCompleted = "UrlRequestCompleted"; level.eventTypes.urlRequestCompleted = "UrlRequestCompleted";
level.eventTypes.registerCommandRequested = "RegisterCommandRequested"; level.eventTypes.registerCommandRequested = "RegisterCommandRequested";
level.eventTypes.getCommandsRequested = "GetCommandsRequested"; level.eventTypes.getCommandsRequested = "GetCommandsRequested";
level.eventTypes.getBusModeRequested = "GetBusModeRequested";
level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback; level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback;
level.eventCallbacks[level.eventTypes.getCommandsRequested] = ::OnCommandsRequestedCallback; level.eventCallbacks[level.eventTypes.getCommandsRequested] = ::OnCommandsRequestedCallback;
level.eventCallbacks[level.eventTypes.getBusModeRequested] = ::OnBusModeRequestedCallback;
level.iw4madminIntegrationDefaultPerformance = 200; level.iw4madminIntegrationDefaultPerformance = 200;
level.notifyEntities = []; level.notifyEntities = [];
@ -195,6 +197,28 @@ SaveTrackingMetrics()
scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId ); scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId );
} }
OnBusModeRequestedCallback( event )
{
data = [];
data["mode"] = GetDvar( level.commonKeys.busMode );
data["directory"] = GetDvar( level.commonKeys.busDir );
scripts\_integration_base::LogDebug( "Bus mode requested" );
busModeRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.getBusModeRequested, "", undefined, data );
scripts\_integration_base::QueueEvent( busModeRequest, level.eventTypes.getBusModeRequested, undefined );
scripts\_integration_base::LogDebug( "Bus mode updated" );
if ( GetDvar( level.commonKeys.busMode ) == "file" || GetDvar( level.commonKeys.busDir ) != "" )
{
level.busMethods[level.commonFunctions.getInboundData] = level.overrideMethods[level.commonFunctions.getInboundData];
level.busMethods[level.commonFunctions.getOutboundData] = level.overrideMethods[level.commonFunctions.getOutboundData];
level.busMethods[level.commonFunctions.setInboundData] = level.overrideMethods[level.commonFunctions.setInboundData];
level.busMethods[level.commonFunctions.setOutboundData] = level.overrideMethods[level.commonFunctions.setOutboundData];
}
}
// #region register script command // #region register script command
OnCommandsRequestedCallback( event ) OnCommandsRequestedCallback( event )

View File

@ -81,5 +81,8 @@ AffirmationCommandCallback( event, _ )
// horrible json parsing.. but it's just an example // horrible json parsing.. but it's just an example
parsedResponse = strtok( response, "\"" ); parsedResponse = strtok( response, "\"" );
self IPrintLnBold ( "^5" + parsedResponse[parsedResponse.size - 2] ); if ( IsPlayer( self ) )
{
self IPrintLnBold ( "^5" + parsedResponse[parsedResponse.size - 2] );
}
} }

View File

@ -7,6 +7,9 @@ const groupSeparatorChar = '\x1d';
const recordSeparatorChar = '\x1e'; const recordSeparatorChar = '\x1e';
const unitSeparatorChar = '\x1f'; const unitSeparatorChar = '\x1f';
let busMode = 'rcon';
let busDir = '';
const init = (registerNotify, serviceResolver, config, scriptHelper) => { const init = (registerNotify, serviceResolver, config, scriptHelper) => {
registerNotify('IManagementEventSubscriptions.ClientStateInitialized', (clientEvent, _) => plugin.onClientEnteredMatch(clientEvent)); registerNotify('IManagementEventSubscriptions.ClientStateInitialized', (clientEvent, _) => plugin.onClientEnteredMatch(clientEvent));
registerNotify('IGameServerEventSubscriptions.ServerValueReceived', (serverValueEvent, _) => plugin.onServerValueReceived(serverValueEvent)); registerNotify('IGameServerEventSubscriptions.ServerValueReceived', (serverValueEvent, _) => plugin.onServerValueReceived(serverValueEvent));
@ -70,6 +73,9 @@ const plugin = {
}, },
onServerValueSetCompleted: async function (serverValueEvent) { onServerValueSetCompleted: async function (serverValueEvent) {
this.logger.logDebug('Set {dvarName}={dvarValue} success={success} from {server}', serverValueEvent.valueName,
serverValueEvent.value, serverValueEvent.success, serverValueEvent.server.id);
if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) { if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) {
this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName); this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName);
return; return;
@ -132,7 +138,8 @@ const plugin = {
serverState.enabled = true; serverState.enabled = true;
serverState.running = true; serverState.running = true;
serverState.initializationInProgress = false; serverState.initializationInProgress = false;
this.sendEventMessage(responseEvent.server, true, 'GetBusModeRequested', null, null, null, {});
this.sendEventMessage(responseEvent.server, true, 'GetCommandsRequested', null, null, null, {}); this.sendEventMessage(responseEvent.server, true, 'GetCommandsRequested', null, null, null, {});
this.requestGetDvar(inDvar, responseEvent.server); this.requestGetDvar(inDvar, responseEvent.server);
}, },
@ -189,8 +196,8 @@ const plugin = {
let messageQueued = false; let messageQueued = false;
const event = parseEvent(input); const event = parseEvent(input);
this.logger.logDebug('Processing input... {eventType} {subType} {data} {clientNumber}', event.eventType, this.logger.logDebug('Processing input... {eventType} {subType} {@data} {clientNumber}', event.eventType,
event.subType, event.data.toString(), event.clientNumber); event.subType, event.data, event.clientNumber);
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2'); const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
const threading = importNamespace('System.Threading'); const threading = importNamespace('System.Threading');
@ -305,8 +312,7 @@ const plugin = {
this.scriptHelper.requestUrl(urlRequest, response => { this.scriptHelper.requestUrl(urlRequest, response => {
this.logger.logDebug('Got response for gamescript web request - {Response}', response); this.logger.logDebug('Got response for gamescript web request - {Response}', response);
if ( typeof response !== 'string' && !(response instanceof String) ) if (typeof response !== 'string' && !(response instanceof String)) {
{
response = JSON.stringify(response); response = JSON.stringify(response);
} }
@ -315,14 +321,12 @@ const plugin = {
let quoteReplace = '\\"'; let quoteReplace = '\\"';
// todo: may be more than just T6 // todo: may be more than just T6
if (server.gameCode === 'T6') if (server.gameCode === 'T6') {
{
quoteReplace = '\\\\"'; quoteReplace = '\\\\"';
} }
let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800); let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800);
if (chunks.length > max) if (chunks.length > max) {
{
this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`); this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`);
chunks = chunks.slice(0, max); chunks = chunks.slice(0, max);
} }
@ -338,6 +342,14 @@ const plugin = {
if (event.eventType === 'RegisterCommandRequested') { if (event.eventType === 'RegisterCommandRequested') {
this.registerDynamicCommand(event); this.registerDynamicCommand(event);
} }
if (event.eventType === 'GetBusModeRequested') {
if (event.data?.directory && event.data?.mode) {
busMode = event.data.mode;
busDir = event.data.directory;
this.logger.logDebug('Setting bus mode to {mode} {dir}', busMode, busDir);
}
}
tokenSource.dispose(); tokenSource.dispose();
return messageQueued; return messageQueued;
@ -363,6 +375,37 @@ const plugin = {
requestGetDvar: function (dvarName, server) { requestGetDvar: function (dvarName, server) {
const serverState = servers[server.id]; const serverState = servers[server.id];
if (dvarName !== integrationEnabledDvar && busMode === 'file') {
this.scriptHelper.requestNotifyAfterDelay(250, () => {
const io = importNamespace('System.IO');
serverState.outQueue.push({});
try {
const content = io.File.ReadAllText(`${busDir}/${dvarName}`);
plugin.onServerValueReceived({
server: server,
source: server,
success: true,
response: {
name: dvarName,
value: content
}
});
} catch (e) {
plugin.logger.logError('Could not get bus data {exception}', e.toString());
plugin.onServerValueReceived({
server: server,
success: false,
response: {
name: dvarName
}
});
}
});
return;
}
const serverEvents = importNamespace('SharedLibraryCore.Events.Server'); const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server); const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server);
requestEvent.delayMs = pollingRate; requestEvent.delayMs = pollingRate;
@ -393,6 +436,35 @@ const plugin = {
requestSetDvar: function (dvarName, dvarValue, server) { requestSetDvar: function (dvarName, dvarValue, server) {
const serverState = servers[server.id]; const serverState = servers[server.id];
if ( busMode === 'file' ) {
this.scriptHelper.requestNotifyAfterDelay(250, async () => {
const io = importNamespace('System.IO');
try {
const path = `${busDir}/${dvarName}`;
plugin.logger.logDebug('writing {value} to {file}', dvarValue, path);
io.File.WriteAllText(path, dvarValue);
serverState.outQueue.push({});
await plugin.onServerValueSetCompleted({
server: server,
source: server,
success: true,
value: dvarValue,
valueName: dvarName,
});
} catch (e) {
plugin.logger.logError('Could not set bus data {exception}', e.toString());
await plugin.onServerValueSetCompleted({
server: server,
success: false,
valueName: dvarName,
value: dvarValue
});
}
})
return;
}
const serverEvents = importNamespace('SharedLibraryCore.Events.Server'); const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
const requestEvent = new serverEvents.ServerValueSetRequestEvent(dvarName, dvarValue, server); const requestEvent = new serverEvents.ServerValueSetRequestEvent(dvarName, dvarValue, server);