From dc707f75b31ba7e20bcbcdc0a7a0ca841f1702f7 Mon Sep 17 00:00:00 2001 From: Edo Date: Mon, 17 Jul 2023 15:28:14 +0200 Subject: [PATCH] feature(iw5: gsc): use preprocessor to deduplicate code (#308) --- GameFiles/GameInterface/_integration_base.gsc | 85 ++++++++------- GameFiles/GameInterface/_integration_iw5.gsc | 103 ++++++++---------- .../GameInterface/_integration_utility.gsh | 40 +++++++ GameFiles/README.MD | 11 +- GameFiles/deploy.bat | 3 +- 5 files changed, 136 insertions(+), 106 deletions(-) create mode 100644 GameFiles/GameInterface/_integration_utility.gsh diff --git a/GameFiles/GameInterface/_integration_base.gsc b/GameFiles/GameInterface/_integration_base.gsc index dffb68c5b..5a6a6137a 100644 --- a/GameFiles/GameInterface/_integration_base.gsc +++ b/GameFiles/GameInterface/_integration_base.gsc @@ -8,7 +8,7 @@ Init() Setup() { level endon( "game_ended" ); - + // setup default vars level.eventBus = spawnstruct(); level.eventBus.inVar = "sv_iw4madmin_in"; @@ -16,7 +16,7 @@ Setup() level.eventBus.failKey = "fail"; level.eventBus.timeoutKey = "timeout"; level.eventBus.timeout = 30; - + level.commonFunctions = spawnstruct(); level.commonFunctions.setDvar = "SetDvarIfUninitialized"; level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum"; @@ -46,12 +46,12 @@ Setup() 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(); @@ -61,9 +61,9 @@ Setup() 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; @@ -76,14 +76,14 @@ Setup() 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 ); @@ -93,12 +93,12 @@ Setup() _SetDvarIfUninitialized( "GroupSeparatorChar", "" ); _SetDvarIfUninitialized( "RecordSeparatorChar", "" ); _SetDvarIfUninitialized( "UnitSeparatorChar", "" ); - + if ( GetDvarInt( level.commonKeys.enabled ) != 1 ) { return; } - + // start long running tasks thread MonitorEvents(); thread MonitorBus(); @@ -113,7 +113,7 @@ MonitorEvents() level waittill( level.eventTypes.eventAvailable, event ); LogDebug( "Processing Event " + event.type + "-" + event.subtype ); - + eventHandler = level.eventCallbacks[event.type]; if ( IsDefined( eventHandler ) ) @@ -161,11 +161,13 @@ _SetDvarIfUninitialized( 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 ) @@ -329,7 +331,7 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) { data = ""; } - + if ( !IsDefined( eventSubtype ) ) { eventSubtype = "None"; @@ -344,9 +346,9 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) { entOrId = entOrId getEntityNumber(); } - + request = "0"; - + if ( responseExpected ) { request = "1"; @@ -355,14 +357,14 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) 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" ); @@ -372,7 +374,7 @@ MonitorBus() for( ;; ) { wait ( 0.1 ); - + // check to see if IW4MAdmin is ready to receive more data inVal = [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ); @@ -380,19 +382,19 @@ MonitorBus() { 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, "" ); } } @@ -404,7 +406,7 @@ QueueEvent( request, eventType, notifyEntity ) 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 ); @@ -415,10 +417,10 @@ QueueEvent( request, eventType, notifyEntity ) timedOut = "set"; continue; } - + timedOut = "unset"; } - + if ( timedOut == "set" ) { LogDebug( "Timed out waiting for response..." ); @@ -432,9 +434,9 @@ QueueEvent( request, eventType, notifyEntity ) return; } - + LogDebug( "<- " + request ); - + [[level.busMethods[level.commonFunctions.setInboundData]]]( level.eventBus.inLocation, request ); } @@ -445,10 +447,10 @@ ParseDataString( 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]; @@ -458,7 +460,7 @@ ParseDataString( data ) dict[key] = value; dict[i] = key; } - + return dict; } @@ -495,14 +497,14 @@ 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] ) ); @@ -529,7 +531,7 @@ AddClientCommand( commandName, shouldRunAsTarget, callback, 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 } @@ -540,6 +542,7 @@ AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite ) OnClientDataReceived( event ) { + assertEx( isDefined( self ), "player entity is not defined"); clientData = self.pers[level.clientDataKey]; if ( event.subtype == "Fail" ) @@ -555,15 +558,15 @@ OnClientDataReceived( event ) { 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"]; @@ -577,7 +580,7 @@ OnExecuteCommand( event ) { data = event.data; response = ""; - + command = level.clientCommandCallbacks[event.subtype]; runAsTarget = level.clientCommandRusAsTarget[event.subtype]; executionContextEntity = event.origin; @@ -586,7 +589,7 @@ OnExecuteCommand( event ) { executionContextEntity = event.target; } - + if ( IsDefined( command ) ) { if ( IsDefined( executionContextEntity ) ) @@ -602,7 +605,7 @@ OnExecuteCommand( event ) { 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 ) { diff --git a/GameFiles/GameInterface/_integration_iw5.gsc b/GameFiles/GameInterface/_integration_iw5.gsc index 4619656cb..fd4d652a2 100644 --- a/GameFiles/GameInterface/_integration_iw5.gsc +++ b/GameFiles/GameInterface/_integration_iw5.gsc @@ -1,5 +1,7 @@ #include common_scripts\utility; +#inline scripts\_integration_utility; + Init() { thread Setup(); @@ -9,12 +11,12 @@ Setup() { level endon( "game_ended" ); waittillframeend; - + level waittill( level.notifyTypes.sharedFunctionsInitialized ); level.eventBus.gamename = "IW5"; - + scripts\_integration_base::RegisterLogger( ::Log2Console ); - + level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired; level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper; level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper; @@ -22,7 +24,7 @@ Setup() level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper; level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout; RegisterClientCommands(); - + level notify( level.notifyTypes.gameFunctionsInitialized ); } @@ -82,45 +84,36 @@ WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 ) GiveWeaponImpl( event, data ) { - if ( !IsAlive( self ) ) - { - return self.name + "^7 is not alive"; - } - + _IS_ALIVE( self ); + self IPrintLnBold( "You have been given a new weapon" ); self GiveWeapon( data["weaponName"] ); self SwitchToWeapon( data["weaponName"] ); - + return self.name + "^7 has been given ^5" + data["weaponName"]; } TakeWeaponsImpl() { - if ( !IsAlive( self ) ) - { - return self.name + "^7 is not alive"; - } - + _IS_ALIVE( self ); + self TakeAllWeapons(); self IPrintLnBold( "All your weapons have been taken" ); - + return "Took weapons from " + self.name; } TeamSwitchImpl() { - if ( !IsAlive( self ) ) - { - return self + "^7 is not alive"; - } - + _IS_ALIVE( self ); + team = level.allies; - + if ( self.team == "allies" ) { team = level.axis; } - + self IPrintLnBold( "You are being team switched" ); wait( 2 ); self [[team]](); @@ -130,10 +123,7 @@ TeamSwitchImpl() LockControlsImpl() { - if ( !IsAlive( self ) ) - { - return self.name + "^7 is not alive"; - } + _IS_ALIVE( self ); if ( !IsDefined ( self.isControlLocked ) ) { @@ -149,11 +139,11 @@ LockControlsImpl() info = []; info[ "alertType" ] = "Alert!"; info[ "message" ] = "You have been frozen!"; - + self AlertImpl( undefined, info ); self.isControlLocked = true; - + return self.name + "\'s controls are locked"; } else @@ -170,11 +160,13 @@ LockControlsImpl() NoClipImpl() { + _VERIFY_PLAYER_ENT( self ); + if ( !IsAlive( self ) ) { self IPrintLnBold( "You are not alive" ); } - + if ( !IsDefined ( self.isNoClipped ) ) { self.isNoClipped = false; @@ -184,29 +176,29 @@ NoClipImpl() { SetDvar( "sv_cheats", 1 ); self SetClientDvar( "cg_thirdperson", 1 ); - + self God(); self Noclip(); self Hide(); SetDvar( "sv_cheats", 0 ); - + self.isNoClipped = true; - + self IPrintLnBold( "NoClip enabled" ); } else { SetDvar( "sv_cheats", 1 ); self SetClientDvar( "cg_thirdperson", 0 ); - + self God(); self Noclip(); self Hide(); - + SetDvar( "sv_cheats", 0 ); - + self.isNoClipped = false; - + self IPrintLnBold( "NoClip disabled" ); } @@ -215,12 +207,13 @@ NoClipImpl() HideImpl() { + _VERIFY_PLAYER_ENT( self ); + if ( !IsAlive( self ) ) { self IPrintLnBold( "You are not alive" ); - return; } - + if ( !IsDefined ( self.isHidden ) ) { self.isHidden = false; @@ -230,26 +223,26 @@ HideImpl() { SetDvar( "sv_cheats", 1 ); self SetClientDvar( "cg_thirdperson", 1 ); - + self God(); self Hide(); SetDvar( "sv_cheats", 0 ); - + self.isHidden = true; - + self IPrintLnBold( "Hide enabled" ); } else { SetDvar( "sv_cheats", 1 ); self SetClientDvar( "cg_thirdperson", 0 ); - + self God(); self Show(); SetDvar( "sv_cheats", 0 ); - + self.isHidden = false; - + self IPrintLnBold( "Hide disabled" ); } } @@ -274,6 +267,8 @@ GotoImpl( event, data ) GotoCoordImpl( data ) { + _VERIFY_PLAYER_ENT( self ); + if ( !IsAlive( self ) ) { self IPrintLnBold( "You are not alive" ); @@ -287,6 +282,8 @@ GotoCoordImpl( data ) GotoPlayerImpl( target ) { + _VERIFY_PLAYER_ENT( self ); + if ( !IsAlive( target ) ) { self IPrintLnBold( target.name + " is not alive" ); @@ -299,10 +296,7 @@ GotoPlayerImpl( target ) PlayerToMeImpl( event ) { - if ( !IsAlive( self ) ) - { - return self.name + " is not alive"; - } + _IS_ALIVE( self ); self SetOrigin( event.origin GetOrigin() ); return "Moved here " + self.name; @@ -310,10 +304,7 @@ PlayerToMeImpl( event ) KillImpl() { - if ( !IsAlive( self ) ) - { - return self.name + " is not alive"; - } + _IS_ALIVE( self ); self Suicide(); self IPrintLnBold( "You were killed by " + self.name ); @@ -323,13 +314,15 @@ KillImpl() SetSpectatorImpl() { + _VERIFY_PLAYER_ENT( self ); + if ( self.pers["team"] == "spectator" ) { return self.name + " is already spectating"; } - + self [[level.spectator]](); self IPrintLnBold( "You have been moved to spectator" ); - + return self.name + " has been moved to spectator"; } diff --git a/GameFiles/GameInterface/_integration_utility.gsh b/GameFiles/GameInterface/_integration_utility.gsh new file mode 100644 index 000000000..df0e50eda --- /dev/null +++ b/GameFiles/GameInterface/_integration_utility.gsh @@ -0,0 +1,40 @@ +/* +* This file contains reusable preprocessor directives meant to be used on +* Plutonium & AlterWare clients that are up to date with the latest version. +* Older versions of Plutonium or other clients do not have support for loading +* or parsing "gsh" files. +*/ + +/* +* Turn off assertions by removing the following define +* gsc-tool will only emit assertions if developer_script dvar is set to 1 +* In short, you should not need to remove this define. Just turn them off +* by using the dvar +*/ + +#define _INTEGRATION_DEBUG + +#ifdef _INTEGRATION_DEBUG + +#define _VERIFY( cond, msg ) \ + assertEx( cond, msg ) + +#else + +// This works as an "empty" define here with gsc-tool +#define _VERIFY( cond, msg ) + +#endif + +// This function is meant to be used inside "client commands" +// If the client is not alive it shall return an error message +#define _IS_ALIVE( ent ) \ + _VERIFY( ent, "player entity is not defined" ); \ + if ( !IsAlive( ent ) ) \ + { \ + return ent.name + "^7 is not alive"; \ + } + +// This function should be used to verify if a player entity is defined +#define _VERIFY_PLAYER_ENT( ent ) \ + _VERIFY( ent, "player entity is not defined" ) diff --git a/GameFiles/README.MD b/GameFiles/README.MD index 3eafed850..09b5019d1 100644 --- a/GameFiles/README.MD +++ b/GameFiles/README.MD @@ -3,14 +3,7 @@ Allows integration of IW4M-Admin to GSC, mainly used for special commands that need to use GSC in order to work. But can also be used to read / write metadata from / to a profile and to get the player permission level. - -## Installation Plutonium IW5 +## Installation Guide -Move `_integration.gsc` to `%localappdata%\Plutonium\storage\iw5\scripts\` - - -## Installation IW4x - - -Move `_integration.gsc` to `IW4x/userraw/scripts`, `IW4x` being the root folder of your game server. \ No newline at end of file +The documentation can be found here: [GameInterface](https://github.com/RaidMax/IW4M-Admin/wiki/GameInterface) diff --git a/GameFiles/deploy.bat b/GameFiles/deploy.bat index 7f9cd833d..8b804506e 100644 --- a/GameFiles/deploy.bat +++ b/GameFiles/deploy.bat @@ -4,6 +4,7 @@ ECHO "Pluto IW5" xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" xcopy /y .\GameInterface\_integration_iw5.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" +xcopy /y .\GameInterface\_integration_utility.gsh "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts" xcopy /y .\AntiCheat\IW5\storage\iw5\scripts\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\iw5\scripts\mp" ECHO "Pluto T5" @@ -13,8 +14,8 @@ xcopy /y .\GameInterface\_integration_t5.gsc "%LOCALAPPDATA%\Plutonium\storage\t xcopy /y .\GameInterface\_integration_t5zm.gsc "%LOCALAPPDATA%\Plutonium\storage\t5\scripts\sp\zom" ECHO "Pluto T6" -xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp" xcopy /y .\GameInterface\_integration_base.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" xcopy /y .\GameInterface\_integration_shared.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" xcopy /y .\GameInterface\_integration_t6.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts" xcopy /y .\GameInterface\_integration_t6zm_helper.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\zm" +xcopy /y .\AntiCheat\PT6\storage\t6\scripts\mp\_customcallbacks.gsc "%LOCALAPPDATA%\Plutonium\storage\t6\scripts\mp"