# HG changeset patch # User Adam Kaminski # Date 1609043430 18000 # Sat Dec 26 23:30:30 2020 -0500 # Node ID 711eaafe096c18fef8e9bcfdc159b54840a26c86 # Parent a20c5397be072a3fde7d31c30a5ae9005b8fbe05 Added ACS functions: SendNetworkString and NamedSendNetworkString, allowing strings to be sent from the server to the client(s) and vice versa, which are passed as the first argument of script to also be executed. Note that sending strings from client to server works just like puking scripts and there's no guarantee they are sent to the server successfully. diff -r a20c5397be07 -r 711eaafe096c docs/zandronum-history.txt --- a/docs/zandronum-history.txt Sat Dec 26 12:36:10 2020 -0500 +++ b/docs/zandronum-history.txt Sat Dec 26 23:30:30 2020 -0500 @@ -30,6 +30,7 @@ + - Added an ACS special to check whether the game is in demo or not [DoomJoshuaBoy/geNia] + - Added new SBARINFO commands: IfSpectator [not] [dead] and IfSpying [not] that execute a sub block if the local player is (not) a (dead) spectator, or (not) spying on another player respectively. [Kaminsky] + - Added ACS functions: ExecuteClientScript and NamedExecuteClientScript which let the server execute clientside scripts for only one client as opposed to everyone. This allows net traffic to be greatly optimized, such that the server only sends out commands to the client(s) that matter. [Kaminsky] ++ - Added ACS functions: SendNetworkString and NamedSendNetworkString, allowing strings to be sent from the server to the client(s) and vice versa, which are passed as the first argument of script to also be executed. Note that sending strings from client to server works just like puking scripts and there's no guarantee they are sent to the server successfully. [Kaminsky] - - Fixed: Bots tries to jump to reach item when sv_nojump is true. [sleep] - - Fixed: ACS function SetSkyScrollSpeed didn't work online. [Edward-san] - - Fixed: color codes in callvote reasons weren't terminated properly. [Dusk] diff -r a20c5397be07 -r 711eaafe096c protocolspec/spec.misc.txt --- a/protocolspec/spec.misc.txt Sat Dec 26 12:36:10 2020 -0500 +++ b/protocolspec/spec.misc.txt Sat Dec 26 23:30:30 2020 -0500 @@ -11,6 +11,13 @@ Bool always EndCommand +Command ACSSendString + ExtendedCommand + Short netid + Actor activator with NullAllowed + String string +EndCommand + Struct JoinSlot Byte player Byte team diff -r a20c5397be07 -r 711eaafe096c src/cl_commands.cpp --- a/src/cl_commands.cpp Sat Dec 26 12:36:10 2020 -0500 +++ b/src/cl_commands.cpp Sat Dec 26 23:30:30 2020 -0500 @@ -672,6 +672,22 @@ //***************************************************************************** // +void CLIENTCOMMANDS_ACSSendString ( int scriptNum, const char *pszString ) +{ + const int scriptNetID = NETWORK_ACSScriptToNetID( scriptNum ); + + CLIENT_GetLocalBuffer( )->ByteStream.WriteByte( CLC_ACSSENDSTRING ); + CLIENT_GetLocalBuffer( )->ByteStream.WriteLong( scriptNetID ); + + // [AK] If we don't have a netID on file for this script, we send the name as a string. + if ( scriptNetID == NO_SCRIPT_NETID ) + CLIENT_GetLocalBuffer( )->ByteStream.WriteString( FName( ENamedName( -scriptNum ))); + + CLIENT_GetLocalBuffer( )->ByteStream.WriteString( pszString ); +} + +//***************************************************************************** +// void CLIENTCOMMANDS_MorphCheat ( const char *pszMorphClass ) { if ( pszMorphClass == NULL ) diff -r a20c5397be07 -r 711eaafe096c src/cl_commands.h --- a/src/cl_commands.h Sat Dec 26 12:36:10 2020 -0500 +++ b/src/cl_commands.h Sat Dec 26 23:30:30 2020 -0500 @@ -107,6 +107,7 @@ void CLIENTCOMMANDS_EnterConsole( void ); void CLIENTCOMMANDS_ExitConsole( void ); void CLIENTCOMMANDS_Puke ( int script, int args[4], bool always ); +void CLIENTCOMMANDS_ACSSendString( int script, const char* pszString ); void CLIENTCOMMANDS_MorphCheat ( const char *pszMorphClass ); void CLIENTCOMMANDS_FullUpdateReceived ( void ); void CLIENTCOMMANDS_InfoCheat( AActor* mobj, bool extended ); diff -r a20c5397be07 -r 711eaafe096c src/cl_main.cpp --- a/src/cl_main.cpp Sat Dec 26 12:36:10 2020 -0500 +++ b/src/cl_main.cpp Sat Dec 26 23:30:30 2020 -0500 @@ -6706,6 +6706,19 @@ //***************************************************************************** // +void ServerCommands::ACSSendString::Execute() +{ + // [AK] Resolve the script netid into a script number + int scriptNum = NETWORK_ACSScriptFromNetID( netid ); + + // [AK] Add the string to the ACS string table. + int args[4] = { GlobalACSStrings.AddString( string ), 0, 0, 0 }; + + P_StartScript( activator, NULL, scriptNum, NULL, args, 4, ACS_ALWAYS ); +} + +//***************************************************************************** +// void ServerCommands::Sound::Execute() { if ( volume > 127 ) diff -r a20c5397be07 -r 711eaafe096c src/network_enums.h --- a/src/network_enums.h Sat Dec 26 12:36:10 2020 -0500 +++ b/src/network_enums.h Sat Dec 26 23:30:30 2020 -0500 @@ -372,6 +372,7 @@ ENUM_ELEMENT ( SVC2_SETLOCALPLAYERJUMPTICS ), ENUM_ELEMENT ( SVC2_SETPLAYERDEATHS ), ENUM_ELEMENT ( SVC2_STOPSOUND ), + ENUM_ELEMENT ( SVC2_ACSSENDSTRING ), // [BB] Commands necessary for the account system. ENUM_ELEMENT ( SVC2_SRP_USER_START_AUTHENTICATION ), ENUM_ELEMENT ( SVC2_SRP_USER_PROCESS_CHALLENGE ), @@ -435,6 +436,7 @@ ENUM_ELEMENT( CLC_EXITCONSOLE ), ENUM_ELEMENT( CLC_IGNORE ), ENUM_ELEMENT( CLC_PUKE ), + ENUM_ELEMENT( CLC_ACSSENDSTRING ), ENUM_ELEMENT( CLC_MORPHEX ), ENUM_ELEMENT( CLC_FULLUPDATE ), ENUM_ELEMENT( CLC_INFOCHEAT ), diff -r a20c5397be07 -r 711eaafe096c src/p_acs.cpp --- a/src/p_acs.cpp Sat Dec 26 12:36:10 2020 -0500 +++ b/src/p_acs.cpp Sat Dec 26 23:30:30 2020 -0500 @@ -1789,6 +1789,84 @@ return 0; } +// ================================================================================================ +// +// [AK] SendNetworkString +// +// Sends a string across the network then executes a script with the string's id as an argument. +// +// ================================================================================================ + +static int SendNetworkString ( FBehavior* module, AActor* activator, int script, int index, int client ) +{ + const ScriptPtr* scriptdata = FBehavior::StaticFindScript( script, module ); + FString rep = FBehavior::RepresentScript( script ); + + // [AK] Don't execute during demo playback. + if ( CLIENTDEMO_IsPlaying() ) + return 1; + + // [AK] If we're in singleplayer, execute the script like normal. + if (( NETWORK_GetState() == NETSTATE_SINGLE ) || ( NETWORK_GetState() == NETSTATE_SINGLE_MULTIPLAYER )) + { + int scriptArgs[4] = { index, 0, 0, 0 }; + + P_StartScript( activator, NULL, script, NULL, scriptArgs, 4, ACS_ALWAYS ); + return 1; + } + + // [AK] Don't execute any network commands if the script doesn't exist. + if ( ACS_ExistsScript( script ) == false ) + { + Printf( "SendNetworkString: Script %s doesn't exist.\n", rep.GetChars() ); + return 0; + } + + const char *string = FBehavior::StaticLookupString( index ); + + // [AK] Don't send empty strings across the network. + if (( string == NULL ) || ( strlen( string ) < 1 )) + return 0; + + if ( NETWORK_GetState() == NETSTATE_CLIENT ) + { + // [AK] Don't have the client send the string if the script is non-pukable. + if (( scriptdata->Flags & SCRIPTF_Net ) == 0 ) + { + Printf( "SendNetworkString: Script %s must be NET but isn't.\n", rep.GetChars() ); + return 0; + } + + CLIENTCOMMANDS_ACSSendString( script, string ); + return 1; + } + + if ( NETWORK_GetState() == NETSTATE_SERVER ) + { + // [AK] Don't send the string to a client that doesn't exist. + if (( client >= 0 ) && ( PLAYER_IsValidPlayer( client ) == false )) + return 0; + + // [AK] Don't send the string to the client(s) if the script isn't clientside. + if ( ACS_IsScriptClientSide( script ) == false ) + { + Printf( "SendNetworkString: Script %s must be CLIENTSIDE but isn't.\n", rep.GetChars() ); + return 0; + } + + // [AK] We want to send the string to all the clients. + if ( client < 0 ) + SERVERCOMMANDS_ACSSendString( script, activator, string ); + // [AK] Otherwise, we're sending the string to only this client. + else + SERVERCOMMANDS_ACSSendString( script, activator, string, client, SVCF_ONLYTHISCLIENT ); + + return 1; + } + + return 0; +} + //---- Plane watchers ----// class DPlaneWatcher : public DThinker @@ -5271,6 +5349,8 @@ ACSF_InDemoMode, ACSF_ExecuteClientScript, ACSF_NamedExecuteClientScript, + ACSF_SendNetworkString, + ACSF_NamedSendNetworkString, // ZDaemon ACSF_GetTeamScore = 19620, // (int team) @@ -7521,6 +7601,20 @@ return ExecuteClientScript( activator, -scriptName, args[1], &args[2], argCount - 2 ); } + case ACSF_SendNetworkString: + { + const int client = argCount > 2 ? args[2] : -1; + return SendNetworkString( activeBehavior, activator, args[0], args[1], client ); + } + + case ACSF_NamedSendNetworkString: + { + FName scriptName = FBehavior::StaticLookupString( args[0] ); + const int client = argCount > 2 ? args[2] : -1; + + return SendNetworkString( activeBehavior, activator, -scriptName, args[1], client ); + } + case ACSF_GetActorFloorTexture: { auto a = SingleActorFromTID(args[0], activator); diff -r a20c5397be07 -r 711eaafe096c src/sv_commands.cpp --- a/src/sv_commands.cpp Sat Dec 26 12:36:10 2020 -0500 +++ b/src/sv_commands.cpp Sat Dec 26 23:30:30 2020 -0500 @@ -3093,6 +3093,30 @@ } //***************************************************************************** +// +void SERVERCOMMANDS_ACSSendString( int ScriptNum, AActor *pActivator, const char *pszString, ULONG ulPlayerExtra, ServerCommandFlags flags ) +{ + int netid = NETWORK_ACSScriptToNetID( ScriptNum ); + + if ( netid == NO_SCRIPT_NETID ) + { + // [AK] This shouldn't happen. + if ( sv_showwarnings ) + { + Printf( "SERVERCOMMANDS_ACSSendString: Failed to find a netid for script %s!\n", + FName( ENamedName( -ScriptNum )).GetChars() ); + } + return; + } + + ServerCommands::ACSSendString command; + command.SetNetid( netid ); + command.SetActivator( pActivator ); + command.SetString( pszString ); + command.sendCommandToClients( ulPlayerExtra, flags ); +} + +//***************************************************************************** //***************************************************************************** // void SERVERCOMMANDS_SetSideFlags( ULONG ulSide, ULONG ulPlayerExtra, ServerCommandFlags flags ) diff -r a20c5397be07 -r 711eaafe096c src/sv_commands.h --- a/src/sv_commands.h Sat Dec 26 12:36:10 2020 -0500 +++ b/src/sv_commands.h Sat Dec 26 23:30:30 2020 -0500 @@ -305,6 +305,7 @@ // ACS commands. These have something to do with ACS scripts. void SERVERCOMMANDS_ACSScriptExecute( int ScriptNum, AActor *pActivator, LONG lLineIdx, int levelnum, bool bBackSide, const int *args, int argcount, bool bAlways, ULONG ulPlayerExtra = MAXPLAYERS, ServerCommandFlags flags = 0 ); +void SERVERCOMMANDS_ACSSendString( int ScriptNum, AActor *pActivator, const char *pszString, ULONG ulPlayerExtra = MAXPLAYERS, ServerCommandFlags flags = 0 ); // Sound commands. These play a sound. void SERVERCOMMANDS_Sound( LONG lChannel, const char *pszSound, float fVolume, float fAttenuation, ULONG ulPlayerExtra = MAXPLAYERS, ServerCommandFlags flags = 0 ); diff -r a20c5397be07 -r 711eaafe096c src/sv_main.cpp --- a/src/sv_main.cpp Sat Dec 26 12:36:10 2020 -0500 +++ b/src/sv_main.cpp Sat Dec 26 23:30:30 2020 -0500 @@ -170,6 +170,7 @@ static bool server_InventoryUse( BYTESTREAM_s *pByteStream ); static bool server_InventoryDrop( BYTESTREAM_s *pByteStream ); static bool server_Puke( BYTESTREAM_s *pByteStream ); +static bool server_ReceiveACSString( BYTESTREAM_s *pByteStream ); static bool server_MorphCheat( BYTESTREAM_s *pByteStream ); static bool server_CheckForClientCommandFlood( ULONG ulClient ); static bool server_CheckForClientMinorCommandFlood( ULONG ulClient ); @@ -4638,6 +4639,10 @@ // [BB] Client wishes to puke a scipt. return ( server_Puke( pByteStream )); + case CLC_ACSSENDSTRING: + + // [AK] Client sent us a string through an ACS script. + return ( server_ReceiveACSString( pByteStream )); case CLC_MORPHEX: // [BB] Client wishes to morph to a certain class. @@ -6593,6 +6598,36 @@ //***************************************************************************** // +static bool server_ReceiveACSString( BYTESTREAM_s *pByteStream ) +{ + const int scriptNetID = pByteStream->ReadLong(); + + // [AK] Resolve the script netid into a script number. + const int scriptNum = ( scriptNetID != NO_SCRIPT_NETID ) + ? NETWORK_ACSScriptFromNetID ( scriptNetID ) + : -FName( pByteStream->ReadString() ); + + const char *string = pByteStream->ReadString(); + + // [AK] A normal client checks if the script is pukeable and only requests to puke pukeable scripts. + // Thus if the requested script is not pukeable, the client was tampered with. + if ( ACS_IsScriptPukeable ( scriptNum ) == false ) + { + // [AK] Trying to puke a non-pukeable script is treated as possible command flooding. + if ( server_CheckForClientCommandFlood ( g_lCurrentClient ) == true ) + return ( true ); + + return ( false ); + } + + int arg[4] = { GlobalACSStrings.AddString ( string ), 0, 0, 0 }; + P_StartScript( players[g_lCurrentClient].mo, NULL, scriptNum, NULL, arg, 4, ACS_ALWAYS | ACS_NET ); + + return ( false ); +} + +//***************************************************************************** +// static bool server_MorphCheat( BYTESTREAM_s *pByteStream ) { const char *pszMorphClass = pByteStream->ReadString();