/* ** ** Server Hop (c) 2009, 2010 [GRAVE] rig0r ** www.gravedigger-company.nl ** */ #include #include #define PLUGIN_VERSION "0.8.1" #define MAX_SERVERS 32 #define REFRESH_TIME 60.0 #define SERVER_TIMEOUT 10.0 #define MAX_STR_LEN 160 #define MAX_INFO_LEN 200 //#define DEBUG new serverCount = 0; new advertCount = 0; new advertInterval = 1; new String:serverName[MAX_SERVERS][MAX_STR_LEN]; new String:serverAddress[MAX_SERVERS][MAX_STR_LEN]; new serverPort[MAX_SERVERS]; new String:serverInfo[MAX_SERVERS][MAX_INFO_LEN]; new Handle:socket[MAX_SERVERS]; new bool:socketError[MAX_SERVERS]; new Handle:cv_hoptrigger = INVALID_HANDLE new Handle:cv_serverformat = INVALID_HANDLE new Handle:cv_broadcasthops = INVALID_HANDLE new Handle:cv_advert = INVALID_HANDLE new Handle:cv_advert_interval = INVALID_HANDLE public Plugin:myinfo = { name = "Server Hop", author = "[GRAVE] rig0r", description = "Provides live server info with join option", version = PLUGIN_VERSION, url = "http://www.gravedigger-company.nl" }; public OnPluginStart() { LoadTranslations( "serverhop.phrases" ); // convar setup cv_hoptrigger = CreateConVar( "sm_hop_trigger", "!servers", "What players have to type in chat to activate the plugin (besides !hop)" ); cv_serverformat = CreateConVar( "sm_hop_serverformat", "%name - %map (%numplayers/%maxplayers)", "Defines how the server info should be presented" ); cv_broadcasthops = CreateConVar( "sm_hop_broadcasthops", "1", "Set to 1 if you want a broadcast message when a player hops to another server" ); cv_advert = CreateConVar( "sm_hop_advertise", "1", "Set to 1 to enable server advertisements" ); cv_advert_interval = CreateConVar( "sm_hop_advertisement_interval", "1", "Advertisement interval: advertise a server every x minute(s)" ); AutoExecConfig( true, "plugin.serverhop" ); new Handle:timer = CreateTimer( REFRESH_TIME, RefreshServerInfo, _, TIMER_REPEAT ); RegConsoleCmd( "say", Command_Say ); RegConsoleCmd( "say_team", Command_Say ); new String:path[MAX_STR_LEN]; new Handle:kv; BuildPath( Path_SM, path, sizeof( path ), "configs/serverhop.cfg" ); kv = CreateKeyValues( "Servers" ); if ( !FileToKeyValues( kv, path ) ) LogToGame( "Error loading server list" ); new i; KvRewind( kv ); KvGotoFirstSubKey( kv ); do { KvGetSectionName( kv, serverName[i], MAX_STR_LEN ); KvGetString( kv, "address", serverAddress[i], MAX_STR_LEN ); serverPort[i] = KvGetNum( kv, "port", 27015 ); i++; } while ( KvGotoNextKey( kv ) ); serverCount = i; TriggerTimer( timer ); } public Action:Command_Say( client, args ) { new String:text[MAX_STR_LEN]; new startidx = 0; if ( !GetCmdArgString( text, sizeof( text ) ) ) { return Plugin_Continue; } if ( text[strlen( text) - 1] == '"' ) { text[strlen( text )-1] = '\0'; startidx = 1; } new String:trigger[MAX_STR_LEN]; GetConVarString( cv_hoptrigger, trigger, sizeof( trigger ) ); if ( strcmp( text[startidx], trigger, false ) == 0 || strcmp( text[startidx], "!hop", false ) == 0 ) { ServerMenu( client ); } return Plugin_Continue; } public Action:ServerMenu( client ) { new Handle:menu = CreateMenu( MenuHandler ); new String:serverNumStr[MAX_STR_LEN]; new String:menuTitle[MAX_STR_LEN]; Format( menuTitle, sizeof( menuTitle ), "%T", "SelectServer", client ); SetMenuTitle( menu, menuTitle ); for ( new i = 0; i < serverCount; i++ ) { if ( strlen( serverInfo[i] ) > 0 ) { #if defined DEBUG then PrintToConsole( client, serverInfo[i] ); #endif IntToString( i, serverNumStr, sizeof( serverNumStr ) ); AddMenuItem( menu, serverNumStr, serverInfo[i] ); } } DisplayMenu( menu, client, 20 ); } public MenuHandler( Handle:menu, MenuAction:action, param1, param2 ) { if ( action == MenuAction_Select ) { new String:infobuf[MAX_STR_LEN]; new String:address[MAX_STR_LEN]; GetMenuItem( menu, param2, infobuf, sizeof( infobuf ) ); new serverNum = StringToInt( infobuf ); // header new Handle:kvheader = CreateKeyValues( "header" ); new String:menuTitle[MAX_STR_LEN]; Format( menuTitle, sizeof( menuTitle ), "%T", "AboutToJoinServer", param1 ); KvSetString( kvheader, "title", menuTitle ); KvSetNum( kvheader, "level", 1 ); KvSetString( kvheader, "time", "10" ); CreateDialog( param1, kvheader, DialogType_Msg ); CloseHandle( kvheader ); // join confirmation dialog new Handle:kv = CreateKeyValues( "menu" ); KvSetString( kv, "time", "10" ); Format( address, MAX_STR_LEN, "%s:%i", serverAddress[serverNum], serverPort[serverNum] ); KvSetString( kv, "title", address ); CreateDialog( param1, kv, DialogType_AskConnect ); CloseHandle( kv ); // broadcast to all if ( GetConVarBool( cv_broadcasthops ) ) { new String:clientName[MAX_NAME_LENGTH]; GetClientName( param1, clientName, sizeof( clientName ) ); PrintToChatAll( "\x04[\x03hop\x04]\x01 %t", "HopNotification", clientName, serverInfo[serverNum] ); } } } public Action:RefreshServerInfo( Handle:timer ) { for ( new i = 0; i < serverCount; i++ ) { serverInfo[i] = ""; socketError[i] = false; socket[i] = SocketCreate( SOCKET_UDP, OnSocketError ); SocketSetArg( socket[i], i ); SocketConnect( socket[i], OnSocketConnected, OnSocketReceive, OnSocketDisconnected, serverAddress[i], serverPort[i] ); } CreateTimer( SERVER_TIMEOUT, CleanUp ); } public Action:CleanUp( Handle:timer ) { for ( new i = 0; i < serverCount; i++ ) { if ( strlen( serverInfo[i] ) == 0 && !socketError[i] ) { LogError( "Server %s:%i is down: no timely reply received", serverAddress[i], serverPort[i] ); CloseHandle( socket[i] ); } } // all server info is up to date: advertise if ( GetConVarBool( cv_advert ) ) { if ( advertInterval == GetConVarFloat( cv_advert_interval ) ) { Advertise(); } advertInterval++; if ( advertInterval > GetConVarFloat( cv_advert_interval ) ) { advertInterval = 1; } } } public Action:Advertise() { new String:trigger[MAX_STR_LEN]; GetConVarString( cv_hoptrigger, trigger, sizeof( trigger ) ); // skip servers being marked as down while ( strlen( serverInfo[advertCount] ) == 0 ) { #if defined DEBUG then LogError( "Not advertising down server %i", advertCount ); #endif advertCount++; if ( advertCount >= serverCount ) { advertCount = 0; break; } } if ( strlen( serverInfo[advertCount] ) > 0 ) { PrintToChatAll( "\x04[\x03hop\x04]\x01 %t", "Advert", serverInfo[advertCount], trigger ); #if defined DEBUG then LogError( "Advertising server %i (%s)", advertCount, serverInfo[advertCount] ); #endif advertCount++; if ( advertCount >= serverCount ) { advertCount = 0; } } } public OnSocketConnected( Handle:sock, any:i ) { decl String:requestStr[ 25 ]; Format( requestStr, sizeof( requestStr ), "%s", "\xFF\xFF\xFF\xFF\x54Source Engine Query" ); SocketSend( sock, requestStr, 25 ); } GetByte( String:receiveData[], offset ) { return receiveData[offset]; } String:GetString( String:receiveData[], dataSize, offset ) { decl String:serverStr[MAX_STR_LEN] = ""; new j = 0; for ( new i = offset; i < dataSize; i++ ) { serverStr[j] = receiveData[i]; j++; if ( receiveData[i] == '\x0' ) { break; } } return serverStr; } public OnSocketReceive( Handle:sock, String:receiveData[], const dataSize, any:i ) { new String:srvName[MAX_STR_LEN]; new String:mapName[MAX_STR_LEN]; new String:gameDir[MAX_STR_LEN]; new String:gameDesc[MAX_STR_LEN]; new String:numPlayers[MAX_STR_LEN]; new String:maxPlayers[MAX_STR_LEN]; // parse server info new offset = 2; srvName = GetString( receiveData, dataSize, offset ); offset += strlen( srvName ) + 1; mapName = GetString( receiveData, dataSize, offset ); offset += strlen( mapName ) + 1; gameDir = GetString( receiveData, dataSize, offset ); offset += strlen( gameDir ) + 1; gameDesc = GetString( receiveData, dataSize, offset ); offset += strlen( gameDesc ) + 1; offset += 2; IntToString( GetByte( receiveData, offset ), numPlayers, sizeof( numPlayers ) ); offset++; IntToString( GetByte( receiveData, offset ), maxPlayers, sizeof( maxPlayers ) ); new String:format[MAX_STR_LEN]; GetConVarString( cv_serverformat, format, sizeof( format ) ); ReplaceString( format, strlen( format ), "%name", serverName[i], false ); ReplaceString( format, strlen( format ), "%map", mapName, false ); ReplaceString( format, strlen( format ), "%numplayers", numPlayers, false ); ReplaceString( format, strlen( format ), "%maxplayers", maxPlayers, false ); serverInfo[i] = format; #if defined DEBUG then LogError( serverInfo[i] ); #endif CloseHandle( sock ); } public OnSocketDisconnected( Handle:sock, any:i ) { CloseHandle( sock ); } public OnSocketError( Handle:sock, const errorType, const errorNum, any:i ) { LogError( "Server %s:%i is down: socket error %d (errno %d)", serverAddress[i], serverPort[i], errorType, errorNum ); socketError[i] = true; CloseHandle( sock ); }