#include common_scripts\utility; Init() { 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.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum"; 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.commonFunctions.setDvar] = scripts\_integration_base::NotImplementedFunction; 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.enabled = "sv_iw4madmin_integration_enabled"; level.commonKeys.busMode = "sv_iw4madmin_integration_busmode"; level.commonKeys.busDir = "sv_iw4madmin_integration_busdir"; level.eventBus.inLocation = ""; level.eventBus.outLocation = ""; level.notifyTypes = spawnstruct(); level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized"; level.notifyTypes.sharedFunctionsInitialized = "SharedFunctionsInitialized"; level.notifyTypes.integrationBootstrapInitialized = "IntegrationBootstrapInitialized"; level.clientDataKey = "clientData"; level.eventTypes = spawnstruct(); level.eventTypes.eventAvailable = "EventAvailable"; 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(); wait ( 0.05 * 2 ); // 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( level.commonKeys.enabled, 1 ); _SetDvarIfUninitialized( level.commonKeys.busMode, "rcon" ); _SetDvarIfUninitialized( level.commonKeys.busdir, "" ); _SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 ); _SetDvarIfUninitialized( "GroupSeparatorChar", "" ); _SetDvarIfUninitialized( "RecordSeparatorChar", "" ); _SetDvarIfUninitialized( "UnitSeparatorChar", "" ); if ( GetDvarInt( level.commonKeys.enabled ) != 1 ) { return; } // start long running tasks thread MonitorEvents(); thread MonitorBus(); } MonitorEvents() { level endon( level.eventTypes.gameEnd ); for ( ;; ) { level waittill( level.eventTypes.eventAvailable, event ); LogDebug( "Processing Event " + event.type + "-" + event.subtype ); eventHandler = level.eventCallbacks[event.type]; if ( IsDefined( eventHandler ) ) { 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 ); } } } ////////////////////////////////// // Helper Methods ////////////////////////////////// NotImplementedFunction( a, b, c, d, e, f ) { LogWarning( "Function not implemented" ); if ( IsDefined ( a ) ) { LogWarning( a ); } } _SetDvarIfUninitialized( dvarName, dvarValue ) { [[level.overrideMethods[level.commonFunctions.setDvar]]]( dvarName, dvarValue ); } _GetPlayerFromClientNum( clientNum ) { assertEx( clientNum >= 0, "clientNum cannot be negative" ); 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; } _GetInboundData( location ) { return GetDvar( level.eventBus.inVar ); } _GetOutboundData( location ) { return GetDvar( level.eventBus.outVar ); } _SetInboundData( location, data ) { return SetDvar( level.eventBus.inVar, data ); } _SetOutboundData( location, data ) { return SetDvar( level.eventBus.outVar, data ); } // 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, GetSubStr( message, 0, 1000 ) ); } } 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 ); thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self ); } RequestClientBasicData() { getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" ); 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" ); } SetClientMeta( metaKey, metaValue, clientId, direction ) { data = []; data["key"] = metaKey; data["value"] = metaValue; clientNumber = -1; if ( IsDefined ( clientId ) ) { data["clientId"] = clientId; clientNumber = -1; } if ( IsDefined( direction ) ) { data["direction"] = direction; } if ( IsPlayer( self ) ) { clientNumber = self getEntityNumber(); } setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data ); thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self ); } BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) { if ( !IsDefined( data ) ) { data = ""; } if ( !IsDefined( eventSubtype ) ) { eventSubtype = "None"; } if ( !IsDefined( entOrId ) ) { entOrId = "-1"; } if ( IsPlayer( entOrId ) ) { entOrId = entOrId getEntityNumber(); } request = "0"; if ( responseExpected ) { request = "1"; } data = BuildDataString( data ); groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 ); request = request + groupSeparator + eventType + groupSeparator + eventSubtype + groupSeparator + entOrId + groupSeparator + data; return request; } MonitorBus() { level endon( level.eventTypes.gameEnd ); level.eventBus.inLocation = level.eventBus.inVar + "_" + GetDvar( "net_port" ); level.eventBus.outLocation = level.eventBus.outVar + "_" + GetDvar( "net_port" ); [[level.overrideMethods[level.commonFunctions.SetInboundData]]]( level.eventBus.inLocation, "" ); [[level.overrideMethods[level.commonFunctions.SetOutboundData]]]( level.eventBus.outLocation, "" ); for( ;; ) { wait ( 0.1 ); // check to see if IW4MAdmin is ready to receive more data inVal = [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ); if ( !IsDefined( inVal ) || inVal == "" ) { level notify( "bus_ready" ); } eventString = [[level.busMethods[level.commonFunctions.getOutboundData]]]( level.eventBus.outLocation ); if ( !IsDefined( eventString ) || eventString == "" ) { continue; } LogDebug( "-> " + eventString ); groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 ); NotifyEvent( strtok( eventString, groupSeparator ) ); [[level.busMethods[level.commonFunctions.SetOutboundData]]]( level.eventBus.outLocation, "" ); } } QueueEvent( request, eventType, notifyEntity ) { level endon( level.eventTypes.gameEnd ); start = GetTime(); maxWait = level.eventBus.timeout * 1000; // 30 seconds timedOut = ""; while ( [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ) != "" && ( GetTime() - start ) < maxWait ) { level [[level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout]]]( "bus_ready", 1 ); if ( [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ) != "" ) { 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 ); } [[level.busMethods[level.commonFunctions.SetInboundData]]]( level.eventBus.inLocation, "" ); return; } LogDebug( "<- " + request ); [[level.busMethods[level.commonFunctions.setInboundData]]]( level.eventBus.inLocation, request ); } ParseDataString( data ) { if ( !IsDefined( data ) ) { LogDebug( "No data to parse" ); return []; } dataParts = strtok( data, GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 ) ); dict = []; for ( i = 0; i < dataParts.size; i++ ) { part = dataParts[i]; splitPart = strtok( part, GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 ) ); key = splitPart[0]; value = splitPart[1]; dict[key] = value; dict[i] = key; } return dict; } 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; } NotifyClientEventTimeout( eventType ) { // todo: make this actual eventing if ( eventType == level.eventTypes.clientDataRequested ) { self.pers["clientData"].state = level.eventBus.timeoutKey; } } NotifyEvent( eventInfo ) { origin = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[3] ) ); target = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[4] ) ); event = spawnstruct(); event.type = eventInfo[1]; event.subtype = eventInfo[2]; event.data = ParseDataString( eventInfo[5] ); 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] ) ); } client = event.origin; if ( !IsDefined( client ) ) { client = event.target; } event.entity = client; level notify( level.eventTypes.eventAvailable, event ); } 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 ) { assertEx( isDefined( self ), "player entity is not defined"); 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=" + CoerceUndefined( metaKey ) + ", Meta Value=" + CoerceUndefined( 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.performance = event.data["performance"]; clientData.state = "complete"; self.persistentClientId = event.data["clientId"]; } OnExecuteCommand( event ) { data = event.data; response = ""; command = level.clientCommandCallbacks[event.subtype]; runAsTarget = level.clientCommandRusAsTarget[event.subtype]; executionContextEntity = event.origin; if ( runAsTarget ) { executionContextEntity = event.target; } if ( IsDefined( command ) ) { if ( IsDefined( executionContextEntity ) ) { response = executionContextEntity thread [[command]]( event, data ); } else { thread [[command]]( event ); } } 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 ) { LogDebug( "Set Client Data -> subtype = " + CoerceUndefined( event.subType ) + ", status = " + CoerceUndefined( event.data["status"] ) ); } CoerceUndefined( object ) { if ( !IsDefined( object ) ) { return "undefined"; } return object; }