#include <amxmodx>
#include <regex>
#include <sockets_hz>
#include <fakemeta>
#include <hamsandwich>
new const Plugin[] = "Steam Friends Highlighter"
new const Author[] = "joaquimandrade"
new const Version[] = "1.1"
new const VoogruMagicNumber[] = "76561197960265728"
new RequestStringFormatPre[] = "GET "
new RequestStringFormatPost[] = " HTTP/1.1^nHOST: steamcommunity.com^nConnection: close^n^n^n^n";
const BufferSize = 1000
const MaxSlots = 32
new Buffers[MaxSlots+1][BufferSize]
new BufferHelper[BufferSize]
new bool:isFriend[MaxSlots+1][MaxSlots+1]
new MaxPlayers
enum Regexes
{
RegexProfileToID,
RegexName,
RegexFriendData
}
new RegexesText[Regexes][] =
{
"Location: http://steamcommunity\.com(/id/([^^/]*)/friends)",
"(^" href=^".*?^">Return to ([^^']*)'s profile.*?<div id=^"memberList^">\s*)",
"(<div onClick=^"top\.location\.href='http://steamcommunity.com/(profiles||id)/([^^']*?)'^" class=^"([^^^"]*?)^">.*?</p>\s*</div>\s*(?:<br clear=^"all^" />|)\s*)"
}
new Regex:RegexesCompiled[Regexes]
new Sockets[MaxSlots+1]
new bool:SocketIsWriteable[MaxSlots+1]
new ProfileFormatString[] = "/profiles/%s/friends"
new ProfileString[sizeof ProfileFormatString + sizeof VoogruMagicNumber]
new Trie:SteamIDToProfile
new Trie:SteamIDToProfileStringListID
new Trie:ProfileToServerID
new Array:ProfileStringsList
new ProfileStringListIDs[MaxSlots+1]
new Profiles[MaxSlots+1][sizeof VoogruMagicNumber]
new Trie:IDToProfile
new ID[sizeof VoogruMagicNumber]
const PlayerSteamNameMaxLen = 32
new PlayerSteamNames[MaxSlots+1][PlayerSteamNameMaxLen]
enum PlayerCommunicationState
{
CheckingIfPageHasID,
StartPageParsing,
PageParsingSteamName,
PageParsingFriends
}
new PlayerCommunicationState:PlayersCommunicationState[MaxSlots+1]
new Float:TaskDelay = 1.0
new Array:ConnectionQueue
new Array:CommunicationQueue
new Array:RequestsQueue
new IsHighlightingFriends[MaxSlots+1]
const TransparencyAmountDefault = 255
new CurrentTransparencyAmount[MaxSlots+1]
new SpeedMultiplier[MaxSlots+1]
new ForwardAddToFullPack
new HighlightingN
new CvarSteamHighlightSpeed
new CvarSteamHighlightMinTrans
new Array:FriendProfiles[MaxSlots+1]
new Array:FriendIDs[MaxSlots+1]
public plugin_init()
{
register_plugin(Plugin,Version,Author)
register_clcmd("+steam","highlightFriendsStart")
register_clcmd("-steam","highlightFriendsStop")
register_clcmd("steam_toggle","steamToggle")
register_dictionary("steamFriendsHighlighter.txt")
CvarSteamHighlightSpeed = register_cvar("steamHighlight_speed","3")
CvarSteamHighlightMinTrans = register_cvar("steamHighlight_min_trans","100")
RegisterHam(Ham_Spawn,"player","playerSpawn",1)
register_cvar("steamFriendsHighlighter",Version,FCVAR_SERVER|FCVAR_SPONLY)
}
public plugin_cfg()
{
new regexErrorCode
new regexErrorString[100]
for(new Regexes:i=Regexes:0;i<Regexes;i++)
{
RegexesCompiled[i] = regex_compile(RegexesText[i],regexErrorCode,regexErrorString,charsmax(regexErrorString),"s");
}
SteamIDToProfileStringListID = TrieCreate()
IDToProfile = TrieCreate()
SteamIDToProfile = TrieCreate()
ProfileToServerID = TrieCreate()
ProfileStringsList = ArrayCreate(sizeof ProfileString)
MaxPlayers = get_maxplayers()
ConnectionQueue = ArrayCreate()
CommunicationQueue = ArrayCreate()
RequestsQueue = ArrayCreate()
for(new id=1;id<=MaxPlayers;id++)
{
FriendProfiles[id] = ArrayCreate(sizeof VoogruMagicNumber)
FriendIDs[id] = ArrayCreate(sizeof ID)
}
set_task(TaskDelay,"processCommunicationQueue",.flags="b")
}
public playerSpawn(id)
{
if(is_user_alive(id))
{
static profile[sizeof VoogruMagicNumber]
static idString[sizeof VoogruMagicNumber]
static friendID
for(new i=0;i<ArraySize(FriendProfiles[id]);i++)
{
ArrayGetString(FriendProfiles[id],i,profile,charsmax(profile))
if(TrieGetCell(ProfileToServerID,profile,friendID))
{
ArrayDeleteItem(FriendProfiles[id],i)
i--
isFriend[id][friendID] = true
isFriend[friendID][id] = true
}
}
for(new i=0;i<ArraySize(FriendIDs[id]);i++)
{
ArrayGetString(FriendIDs[id],i,idString,charsmax(idString))
if(TrieGetString(IDToProfile,idString,profile,charsmax(profile)))
{
if(TrieGetCell(ProfileToServerID,profile,friendID))
{
ArrayDeleteItem(FriendIDs[id],i)
i--
isFriend[id][friendID] = true
isFriend[friendID][id] = true
}
}
}
}
}
public processCommunicationQueue()
{
if(ArraySize(CommunicationQueue))
{
new id = ArrayGetCell(CommunicationQueue,0)
ArrayDeleteItem(CommunicationQueue,0)
checkPlayerSocket(id)
}
else if(ArraySize(RequestsQueue))
{
new id = ArrayGetCell(RequestsQueue,0)
ArrayDeleteItem(RequestsQueue,0)
makeRequest(id,Sockets[id])
}
else if(ArraySize(ConnectionQueue))
{
new id = ArrayGetCell(ConnectionQueue,0)
ArrayDeleteItem(ConnectionQueue,0)
initConnection(id)
}
}
steamIDToCommunityID(steamID[],communityID[sizeof VoogruMagicNumber])
{
const leftMaxLen = 8
const rightMaxLen = 20
new left[leftMaxLen];
new right[rightMaxLen];
strtok(steamID, left, leftMaxLen-1, right, rightMaxLen-1, ':')
strtok(right, left, leftMaxLen-1, right, rightMaxLen-1, ':')
new iServer = str_to_num(left);
new iAuthID = str_to_num(right);
const lastIndex = charsmax(VoogruMagicNumber) - 1
copy(communityID,charsmax(VoogruMagicNumber),VoogruMagicNumber)
new toAdd = iAuthID * 2 + iServer;
new toAddString[sizeof VoogruMagicNumber]
num_to_str(toAdd,toAddString,charsmax(VoogruMagicNumber));
new addLastIndex = strlen(toAddString) - 1;
for(new i=0;i<=addLastIndex;i++)
{
new num = toAddString[addLastIndex - i] - 48;
new j=lastIndex - i;
do
{
new num2 = communityID[j] - 48;
new sum = num + num2;
communityID[j] = (sum % 10) + 48;
num = sum / 10;
j--;
}
while(num);
}
}
initConnection(id)
{
static steamID[34]
get_user_authid(id,steamID,charsmax(steamID))
new socket = initRequest(id)
if(socket > 0)
{
new profileStringListID
static profile[sizeof VoogruMagicNumber]
if(TrieGetCell(SteamIDToProfileStringListID,steamID,profileStringListID))
{
ArrayGetString(ProfileStringsList,profileStringListID,ProfileString,charsmax(ProfileString))
TrieGetString(SteamIDToProfile,steamID,profile,charsmax(profile))
}
else
{
TrieSetCell(SteamIDToProfileStringListID,steamID,profileStringListID=ArraySize(ProfileStringsList))
steamIDToCommunityID(steamID,profile)
formatex(ProfileString,charsmax(ProfileString),ProfileFormatString,profile)
ArrayPushString(ProfileStringsList,ProfileString)
TrieSetString(SteamIDToProfile,steamID,profile)
}
copy(Profiles[id],charsmax(profile),profile)
TrieSetCell(ProfileToServerID,profile,id)
ProfileStringListIDs[id] = profileStringListID
ArrayPushCell(RequestsQueue,id)
}
}
public client_authorized(id)
{
ArrayClear(FriendProfiles[id])
ArrayClear(FriendIDs[id])
PlayersCommunicationState[id] = CheckingIfPageHasID
new buffer[BufferSize]
Buffers[id] = buffer
for(new i=1;i<=MaxPlayers;i++)
isFriend[id][i] = false
ArrayPushCell(ConnectionQueue,id)
}
cleanArrayElement(Array:array,element)
{
for(new i=0;i<ArraySize(array);i++)
{
if(ArrayGetCell(array,i) == element)
{
ArrayDeleteItem(array,i)
i--
}
}
}
public client_disconnect(id)
{
highlightFriendsStop(id)
cleanArrayElement(ConnectionQueue,id)
cleanArrayElement(CommunicationQueue,id)
cleanArrayElement(RequestsQueue,id)
if(TrieKeyExists(ProfileToServerID,Profiles[id]))
{
TrieDeleteKey(ProfileToServerID,Profiles[id])
}
if(Sockets[id])
{
closeUserSocket(id)
}
static name[32]
get_user_name(id,name,charsmax(name))
for(new i=1;i<=MaxPlayers;i++)
{
if(isFriend[i][id])
{
if(equal(PlayerSteamNames[id],name))
{
client_print(i,print_chat,"%L",i,"FRIEND_LEFT_SAME_NAME",name)
}
else
{
client_print(i,print_chat,"%L",i,"FRIEND_LEFT",PlayerSteamNames[id],name)
}
isFriend[i][id] = false
}
}
}
initRequest(id)
{
static error
new socket = socket_open_non_blocking("www.steamcommunity.com",80,SOCKET_TCP,error);
Sockets[id] = socket
SocketIsWriteable[id] = false
return socket
}
makeRequest(id,socket)
{
SocketIsWriteable[id] = socket_is_writable(socket)
if(SocketIsWriteable[id])
{
ArrayGetString(ProfileStringsList,ProfileStringListIDs[id],ProfileString,charsmax(ProfileString))
socket_send(socket,RequestStringFormatPre,charsmax(RequestStringFormatPre))
socket_send(socket,ProfileString,charsmax(ProfileString))
socket_send(socket,RequestStringFormatPost,charsmax(RequestStringFormatPost))
ArrayPushCell(CommunicationQueue,id)
}
else
{
//processCommunicationQueue()
ArrayPushCell(RequestsQueue,id)
}
}
closeUserSocket(id)
{
socket_close(Sockets[id])
Sockets[id] = 0
}
processData(id,socket,buffer[],len)
{
switch(PlayersCommunicationState[id])
{
case CheckingIfPageHasID:
{
static profileToIDRedirectStatusString[] = "HTTP/1.1 302 Found"
if(equal(buffer,profileToIDRedirectStatusString,charsmax(profileToIDRedirectStatusString)))
{
closeUserSocket(id)
new Regex:regexCompiled = Regex:RegexesCompiled[RegexProfileToID]
new results
regex_match_c(buffer,regexCompiled,results);
if(results)
{
regex_substr(regexCompiled,1,ProfileString,charsmax(ProfileString));
ArraySetString(ProfileStringsList,ProfileStringListIDs[id],ProfileString)
new socket = initRequest(id)
if(socket)
{
buffer[0] = 0
PlayersCommunicationState[id] = StartPageParsing
ArrayPushCell(RequestsQueue,id)
}
regex_substr(regexCompiled,2,ID,charsmax(ID))
static steamID[34]
get_user_authid(id,steamID,charsmax(steamID))
TrieSetString(IDToProfile,ID,Profiles[id])
}
}
else
{
PlayersCommunicationState[id] = StartPageParsing
processData(id,socket,buffer,len)
}
}
case StartPageParsing:
{
static startText[] = "linkStandard"
new position = contain(buffer,startText)
if(position != -1)
{
new storeFrom = position + charsmax(startText)
format(buffer,BufferSize,buffer[storeFrom])
PlayersCommunicationState[id] = PageParsingSteamName
}
else
{
buffer[0] = 0
}
checkSocketData(id,socket)
}
case PageParsingSteamName:
{
new Regex:regexCompiled = Regex:RegexesCompiled[RegexName]
new results
regex_match_c(buffer,regexCompiled,results);
if(results)
{
regex_substr(regexCompiled,1,BufferHelper,charsmax(BufferHelper));
regex_substr(regexCompiled,2,PlayerSteamNames[id],PlayerSteamNameMaxLen)
format(buffer,BufferSize,buffer[strlen(BufferHelper)])
PlayersCommunicationState[id] = PageParsingFriends
processData(id,socket,buffer,len)
}
}
case PageParsingFriends:
{
new Regex:regexCompiled = Regex:RegexesCompiled[RegexFriendData]
new results
regex_match_c(buffer,regexCompiled,results);
if(results)
{
regex_substr(regexCompiled,1,BufferHelper,charsmax(BufferHelper));
static idTypeString[] = "profiles"
regex_substr(regexCompiled,2,idTypeString,charsmax(idTypeString));
static idString[sizeof VoogruMagicNumber]
regex_substr(regexCompiled,3,idString,charsmax(idString));
static inGameClass[] = "friendBlock_in-game"
static class[sizeof inGameClass]
regex_substr(regexCompiled,4,class,charsmax(class));
if(equal(class,inGameClass))
{
static profile[sizeof VoogruMagicNumber]
new bool:hasProfile = true
if(equal(idTypeString,"id"))
{
hasProfile = TrieGetString(IDToProfile,idString,profile,charsmax(profile))
}
else
{
copy(profile,charsmax(profile),idString)
}
if(hasProfile)
{
static friendID
if(TrieGetCell(ProfileToServerID,profile,friendID))
{
isFriend[id][friendID] = true
isFriend[friendID][id] = true
static name[32]
get_user_name(id,name,charsmax(name))
if(equal(name,PlayerSteamNames[id]))
{
client_print(friendID,print_chat,"%L",friendID,"FRIEND_JOINED_SAME_NAME",name)
}
else
{
client_print(friendID,print_chat,"%L",friendID,"FRIEND_JOINED",PlayerSteamNames[id],name)
}
}
else
{
ArrayPushString(FriendProfiles[id],profile)
}
}
else
{
ArrayPushString(FriendIDs[id],idString)
}
format(buffer,BufferSize,buffer[strlen(BufferHelper)])
processData(id,socket,buffer,len)
}
}
else
{
checkSocketData(id,socket)
}
}
}
}
checkSocketData(id,socket)
{
new storeAt = strlen(Buffers[id])
new toReceive = BufferSize - 1 - storeAt
if(toReceive)
{
new received = socket_recv(socket,Buffers[id][storeAt],toReceive + 1)
if(received)
{
if(received == toReceive)
{
processData(id,socket,Buffers[id],storeAt + received)
}
else
{
ArrayPushCell(CommunicationQueue,id)
}
}
else
{
processData(id,socket,Buffers[id],strlen(Buffers[id]))
}
}
else
{
closeUserSocket(id)
}
}
public checkPlayerSocketFromTask(params[])
{
checkPlayerSocket(params[0])
}
checkPlayerSocket(id)
{
new socket = Sockets[id]
if(socket)
{
if(socket_change(socket))
{
checkSocketData(id,socket)
}
else
{
processCommunicationQueue()
ArrayPushCell(CommunicationQueue,id)
}
}
}
public highlightFriendsStart(id)
{
if(IsHighlightingFriends[id])
{
highlightFriendsStop(id)
}
CurrentTransparencyAmount[id] = TransparencyAmountDefault
SpeedMultiplier[id] = -1
if(!HighlightingN++)
{
ForwardAddToFullPack = register_forward(FM_AddToFullPack,"addToFullPackPost",1)
}
IsHighlightingFriends[id] = true
return PLUGIN_HANDLED
}
public highlightFriendsStop(id)
{
if(IsHighlightingFriends[id])
{
if(!--HighlightingN)
{
unregister_forward(FM_AddToFullPack,ForwardAddToFullPack,1)
}
IsHighlightingFriends[id] = false
}
return PLUGIN_HANDLED
}
public addToFullPackPost(es, e, ent, host, hostflags, player, pSet)
{
if(player && IsHighlightingFriends[host])
{
if(host == ent)
{
new speed = get_pcvar_num(CvarSteamHighlightSpeed)
new minTrans = clamp(get_pcvar_num(CvarSteamHighlightMinTrans),0,255)
new currentTrans = CurrentTransparencyAmount[host]
new speedMultiplier = SpeedMultiplier[host]
if(currentTrans < minTrans)
{
speedMultiplier = 0
currentTrans = minTrans
}
else if(currentTrans == minTrans)
{
speedMultiplier = 1
}
if(currentTrans > 255)
{
speedMultiplier = 0
currentTrans = 255
}
else if(currentTrans == 255)
{
speedMultiplier = -1
}
currentTrans += speed * speedMultiplier
SpeedMultiplier[host] = speedMultiplier
CurrentTransparencyAmount[host] = currentTrans
}
else if(isFriend[host][ent])
{
set_es(es,ES_RenderMode,kRenderTransAlpha)
set_es(es,ES_RenderAmt,CurrentTransparencyAmount[host])
}
}
}
public steamToggle(id)
{
static printTo[] = {print_chat,print_console}
if(IsHighlightingFriends[id])
{
for(new i=0;i<sizeof printTo;i++)
client_print(id,printTo[i],"%L",id,"HIGHLIGHT_STOP")
highlightFriendsStop(id)
}
else
{
for(new i=0;i<sizeof printTo;i++)
client_print(id,printTo[i],"%L",id,"HIGHLIGHT_START")
highlightFriendsStart(id)
}
return PLUGIN_HANDLED
}