2022-08-20 11:57:03 -04:00
#include common_scripts\utility;
Init()
{
2023-04-07 21:42:18 -04:00
thread Setup();
2022-08-20 11:57:03 -04:00
}
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;
2023-05-28 21:15:52 -04:00
level.commonFunctions = spawnstruct();
level.commonFunctions.setDvar = "SetDvarIfUninitialized";
level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum";
2023-06-01 21:45:05 -04:00
level.commonFunctions.waittillNotifyOrTimeout = "WaittillNotifyOrTimeout";
2023-06-06 18:56:12 -04:00
level.commonFunctions.getInboundData = "GetInboundData";
level.commonFunctions.getOutboundData = "GetOutboundData";
level.commonFunctions.setInboundData = "SetInboundData";
level.commonFunctions.setOutboundData = "SetOutboundData";
2023-06-01 21:45:05 -04:00
level.overrideMethods = [];
level.overrideMethods[level.commonFunctions.setDvar] = scripts\_integration_base::NotImplementedFunction;
level.overrideMethods[level.commonFunctions.getPlayerFromClientNum] = ::_GetPlayerFromClientNum;
2023-06-06 18:56:12 -04:00
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;
2023-04-07 21:42:18 -04:00
2022-10-23 14:29:01 -04:00
level.commonKeys = spawnstruct();
2023-06-01 21:45:05 -04:00
level.commonKeys.enabled = "sv_iw4madmin_integration_enabled";
2023-06-06 18:56:12 -04:00
level.commonKeys.busMode = "sv_iw4madmin_integration_busmode";
level.commonKeys.busDir = "sv_iw4madmin_integration_busdir";
2022-08-20 11:57:03 -04:00
level.notifyTypes = spawnstruct();
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
2023-05-28 21:15:52 -04:00
level.notifyTypes.sharedFunctionsInitialized = "SharedFunctionsInitialized";
2022-08-20 11:57:03 -04:00
level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized";
level.clientDataKey = "clientData";
level.eventTypes = spawnstruct();
2023-06-03 17:48:03 -04:00
level.eventTypes.eventAvailable = "EventAvailable";
2022-08-20 11:57:03 -04:00
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.iw4madminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
InitializeLogger();
2023-06-01 21:45:05 -04:00
wait ( 0.05 * 2 ); // needed to give script engine time to propagate notifies
2022-08-20 11:57:03 -04:00
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, "" );
2023-06-01 21:45:05 -04:00
_SetDvarIfUninitialized( level.commonKeys.enabled, 1 );
2023-06-06 18:56:12 -04:00
_SetDvarIfUninitialized( level.commonKeys.busMode, "rcon" );
_SetDvarIfUninitialized( level.commonKeys.busdir, "" );
2022-08-20 11:57:03 -04:00
_SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
2023-06-03 17:48:03 -04:00
_SetDvarIfUninitialized( "GroupSeparatorChar", " " );
_SetDvarIfUninitialized( "RecordSeparatorChar", " " );
_SetDvarIfUninitialized( "UnitSeparatorChar", " " );
2022-08-20 11:57:03 -04:00
2023-06-01 22:09:18 -04:00
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
2022-08-20 11:57:03 -04:00
{
return;
}
// start long running tasks
2023-06-03 17:48:03 -04:00
thread MonitorEvents();
2023-06-01 21:45:05 -04:00
thread MonitorBus();
2022-08-20 11:57:03 -04:00
}
2023-06-03 17:48:03 -04:00
MonitorEvents()
2022-08-20 11:57:03 -04:00
{
2023-06-01 21:45:05 -04:00
level endon( level.eventTypes.gameEnd );
2022-08-20 11:57:03 -04:00
for ( ;; )
{
2023-06-03 17:48:03 -04:00
level waittill( level.eventTypes.eventAvailable, event );
2022-08-20 11:57:03 -04:00
2023-06-03 17:48:03 -04:00
LogDebug( "Processing Event " + event.type + "-" + event.subtype );
2022-08-20 11:57:03 -04:00
2023-06-03 17:48:03 -04:00
eventHandler = level.eventCallbacks[event.type];
2022-08-20 11:57:03 -04:00
if ( IsDefined( eventHandler ) )
{
2023-06-03 17:48:03 -04:00
if ( IsDefined( event.entity ) )
{
event.entity [[eventHandler]]( event );
}
else
{
[[eventHandler]]( event );
}
}
if ( IsDefined( event.entity ) )
{
LogDebug( "Notify client for " + event.type );
event.entity notify( event.type, event );
}
else
{
LogDebug( "Notify level for " + event.type );
level notify( event.type, event );
2022-08-20 11:57:03 -04:00
}
}
}
//////////////////////////////////
// Helper Methods
//////////////////////////////////
2023-06-01 21:45:05 -04:00
NotImplementedFunction( a, b, c, d, e, f )
2022-08-20 11:57:03 -04:00
{
2023-06-01 21:45:05 -04:00
LogWarning( "Function not implemented" );
if ( IsDefined ( a ) )
{
LogWarning( a );
}
2022-08-20 11:57:03 -04:00
}
_SetDvarIfUninitialized( dvarName, dvarValue )
{
2022-10-23 14:29:01 -04:00
[[level.overrideMethods[level.commonFunctions.setDvar]]]( dvarName, dvarValue );
}
2023-06-01 21:45:05 -04:00
_GetPlayerFromClientNum( clientNum )
2022-10-23 14:29:01 -04:00
{
2023-06-01 21:45:05 -04:00
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;
2022-08-20 11:57:03 -04:00
}
2023-06-06 18:56:12 -04:00
_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 );
}
2022-08-20 11:57:03 -04:00
// 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++ )
{
2023-06-03 17:48:03 -04:00
[[level.logger._logger[i]]]( LogLevel, GetSubStr( message, 0, 1000 ) );
2022-08-20 11:57:03 -04:00
}
}
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 );
2023-06-01 21:45:05 -04:00
thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
2022-08-20 11:57:03 -04:00
}
RequestClientBasicData()
{
getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" );
2023-06-01 21:45:05 -04:00
thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
2022-08-20 11:57:03 -04:00
}
IncrementClientMeta( metaKey, incrementValue, clientId )
{
SetClientMeta( metaKey, incrementValue, clientId, "increment" );
}
DecrementClientMeta( metaKey, decrementValue, clientId )
{
SetClientMeta( metaKey, decrementValue, clientId, "decrement" );
}
SetClientMeta( metaKey, metaValue, clientId, direction )
{
2023-06-03 17:48:03 -04:00
data = [];
data["key"] = metaKey;
data["value"] = metaValue;
2022-08-20 11:57:03 -04:00
clientNumber = -1;
if ( IsDefined ( clientId ) )
{
2023-06-03 17:48:03 -04:00
data["clientId"] = clientId;
2022-08-20 11:57:03 -04:00
clientNumber = -1;
}
if ( IsDefined( direction ) )
{
2023-06-03 17:48:03 -04:00
data["direction"] = direction;
2022-08-20 11:57:03 -04:00
}
if ( IsPlayer( self ) )
{
clientNumber = self getEntityNumber();
}
setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data );
2023-06-01 21:45:05 -04:00
thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self );
2022-08-20 11:57:03 -04:00
}
BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
{
if ( !IsDefined( data ) )
{
data = "";
}
if ( !IsDefined( eventSubtype ) )
{
eventSubtype = "None";
}
2023-06-03 23:46:15 -04:00
if ( !IsDefined( entOrId ) )
{
entOrId = "-1";
}
2022-08-20 11:57:03 -04:00
if ( IsPlayer( entOrId ) )
{
entOrId = entOrId getEntityNumber();
}
request = "0";
if ( responseExpected )
{
request = "1";
}
2023-06-03 17:48:03 -04:00
data = BuildDataString( data );
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
request = request + groupSeparator + eventType + groupSeparator + eventSubtype + groupSeparator + entOrId + groupSeparator + data;
2023-06-03 23:46:15 -04:00
return request;
2022-08-20 11:57:03 -04:00
}
MonitorBus()
{
2023-06-01 21:45:05 -04:00
level endon( level.eventTypes.gameEnd );
2023-06-06 18:56:12 -04:00
[[level.overrideMethods[level.commonFunctions.SetInboundData]]]( "" );
[[level.overrideMethods[level.commonFunctions.SetOutboundData]]]( "" );
2022-08-20 11:57:03 -04:00
for( ;; )
{
wait ( 0.1 );
// check to see if IW4MAdmin is ready to receive more data
2023-06-06 18:56:12 -04:00
inVal = [[level.busMethods[level.commonFunctions.getInboundData]]]();
if ( !IsDefined( inVal ) || inVal == "" )
2022-08-20 11:57:03 -04:00
{
level notify( "bus_ready" );
}
2023-06-06 18:56:12 -04:00
eventString = [[level.busMethods[level.commonFunctions.getOutboundData]]]();
2022-08-20 11:57:03 -04:00
2023-06-06 18:56:12 -04:00
if ( !IsDefined( eventString ) || eventString == "" )
2022-08-20 11:57:03 -04:00
{
continue;
}
2023-06-06 18:56:12 -04:00
2022-08-20 11:57:03 -04:00
LogDebug( "-> " + eventString );
2023-06-03 17:48:03 -04:00
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
NotifyEvent( strtok( eventString, groupSeparator ) );
2022-08-20 11:57:03 -04:00
2023-06-06 18:56:12 -04:00
[[level.busMethods[level.commonFunctions.SetOutboundData]]]( "" );
2022-08-20 11:57:03 -04:00
}
}
QueueEvent( request, eventType, notifyEntity )
{
2023-06-01 21:45:05 -04:00
level endon( level.eventTypes.gameEnd );
2022-08-20 11:57:03 -04:00
start = GetTime();
maxWait = level.eventBus.timeout * 1000; // 30 seconds
timedOut = "";
2023-06-06 18:56:12 -04:00
while ( [[level.busMethods[level.commonFunctions.getInboundData]]]() != "" && ( GetTime() - start ) < maxWait )
2022-08-20 11:57:03 -04:00
{
2023-06-01 21:45:05 -04:00
level [[level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout]]]( "bus_ready", 1 );
2022-08-20 11:57:03 -04:00
2023-06-06 18:56:12 -04:00
if ( [[level.busMethods[level.commonFunctions.getInboundData]]]() != "" )
2022-08-20 11:57:03 -04:00
{
LogDebug( "A request is already in progress..." );
timedOut = "set";
continue;
}
timedOut = "unset";
}
2023-06-02 17:35:00 -04:00
if ( timedOut == "set" )
2022-08-20 11:57:03 -04:00
{
LogDebug( "Timed out waiting for response..." );
if ( IsDefined( notifyEntity ) )
{
notifyEntity NotifyClientEventTimeout( eventType );
}
2023-06-06 18:56:12 -04:00
[[level.busMethods[level.commonFunctions.SetInboundData]]]( "" );
2022-08-20 11:57:03 -04:00
return;
}
2023-06-03 17:48:03 -04:00
LogDebug( "<- " + request );
2022-08-20 11:57:03 -04:00
2023-06-06 18:56:12 -04:00
[[level.busMethods[level.commonFunctions.setInboundData]]]( request );
2022-08-20 11:57:03 -04:00
}
ParseDataString( data )
{
if ( !IsDefined( data ) )
{
LogDebug( "No data to parse" );
return [];
}
2023-06-03 17:48:03 -04:00
dataParts = strtok( data, GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 ) );
2022-08-20 11:57:03 -04:00
dict = [];
for ( i = 0; i < dataParts.size; i++ )
{
part = dataParts[i];
2023-06-03 17:48:03 -04:00
splitPart = strtok( part, GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 ) );
2022-08-20 11:57:03 -04:00
key = splitPart[0];
value = splitPart[1];
dict[key] = value;
dict[i] = key;
}
return dict;
}
2023-06-03 17:48:03 -04:00
BuildDataString( data )
{
if ( IsString( data ) )
{
return data;
}
dataString = "";
keys = GetArrayKeys( data );
unitSeparator = GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 );
recordSeparator = GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 );
for ( i = 0; i < keys.size; i++ )
{
dataString = dataString + keys[i] + unitSeparator + data[keys[i]] + recordSeparator;
}
return dataString;
}
2022-08-20 11:57:03 -04:00
NotifyClientEventTimeout( eventType )
{
// todo: make this actual eventing
if ( eventType == level.eventTypes.clientDataRequested )
{
self.pers["clientData"].state = level.eventBus.timeoutKey;
}
}
2023-06-03 17:48:03 -04:00
NotifyEvent( eventInfo )
2022-08-20 11:57:03 -04:00
{
2023-05-28 21:15:52 -04:00
origin = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[3] ) );
target = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[4] ) );
2022-08-20 11:57:03 -04:00
event = spawnstruct();
event.type = eventInfo[1];
event.subtype = eventInfo[2];
2023-06-03 17:48:03 -04:00
event.data = ParseDataString( eventInfo[5] );
2022-08-20 11:57:03 -04:00
event.origin = origin;
event.target = target;
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] ) );
}
2023-06-03 17:48:03 -04:00
client = event.origin;
if ( !IsDefined( client ) )
2022-08-20 11:57:03 -04:00
{
client = event.target;
}
2023-06-03 17:48:03 -04:00
event.entity = client;
level notify( level.eventTypes.eventAvailable, event );
2022-08-20 11:57:03 -04:00
}
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 )
{
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];
2022-09-11 12:46:13 -04:00
2023-06-01 21:45:05 -04:00
LogDebug( "Meta Key=" + CoerceUndefined( metaKey ) + ", Meta Value=" + CoerceUndefined( event.data[metaKey] ) );
2022-08-20 11:57:03 -04:00
return;
}
clientData.permissionLevel = event.data["level"];
clientData.clientId = event.data["clientId"];
clientData.lastConnection = event.data["lastConnection"];
2022-09-24 11:06:07 -04:00
clientData.tag = event.data["tag"];
2022-10-23 14:29:01 -04:00
clientData.performance = event.data["performance"];
2022-08-20 11:57:03 -04:00
clientData.state = "complete";
self.persistentClientId = event.data["clientId"];
}
OnExecuteCommand( event )
{
2023-06-03 17:48:03 -04:00
data = event.data;
2022-08-20 11:57:03 -04:00
response = "";
command = level.clientCommandCallbacks[event.subtype];
runAsTarget = level.clientCommandRusAsTarget[event.subtype];
executionContextEntity = event.origin;
if ( runAsTarget )
{
executionContextEntity = event.target;
}
if ( IsDefined( command ) )
{
2023-06-03 23:46:15 -04:00
if ( IsDefined( executionContextEntity ) )
{
2023-06-06 13:08:58 -04:00
response = executionContextEntity thread [[command]]( event, data );
2023-06-03 23:46:15 -04:00
}
else
{
2023-06-06 13:08:58 -04:00
thread [[command]]( event );
2023-06-03 23:46:15 -04:00
}
2022-08-20 11:57:03 -04:00
}
else
{
LogDebug( "Unknown Client command->" + event.subtype );
}
// send back the response to the origin, but only if they're not the target
2022-10-05 10:49:00 -04:00
if ( IsDefined( response ) && response != "" && IsPlayer( event.origin ) && event.origin != event.target )
2022-08-20 11:57:03 -04:00
{
event.origin IPrintLnBold( response );
}
}
OnSetClientDataCompleted( event )
{
2023-06-03 17:48:03 -04:00
LogDebug( "Set Client Data -> subtype = " + CoerceUndefined( event.subType ) + ", status = " + CoerceUndefined( event.data["status"] ) );
2023-06-01 21:45:05 -04:00
}
CoerceUndefined( object )
{
if ( !IsDefined( object ) )
{
return "undefined";
}
return object;
2022-08-20 11:57:03 -04:00
}