Fix syntax error

Fix syntax error

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio
 * TODO - Add your project's description
 *
 * Copyright 2013 ???
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <clientprefs>
#include <steamtools>
#include <base64>
#include <smjansson>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "In Game Audio",
    author = "CrimsonTautology",
    description = "Interact with the In Game Audio web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

#define QUERY_SONG_ROUTE "/v1/api/query_song"
#define MAP_THEME_ROUTE "/v1/api/map_theme"
#define USER_THEME_ROUTE "/v1/api/user_theme"
#define AUTHORIZE_USER_ROUTE "/v1/api/authorize_user"
#define SONGS_ROUTE "/songs"
#define DIRECTORIES_ROUTE "/directories"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18
#define MAX_SONG_LENGTH 64

new Handle:g_Cvar_IGAApiKey = INVALID_HANDLE;
new Handle:g_Cvar_IGAUrl = INVALID_HANDLE;
new Handle:g_Cvar_IGADonatorsOnly = INVALID_HANDLE;
new Handle:g_Cvar_IGAEnabled = INVALID_HANDLE;
new Handle:g_Cvar_IGARequestCooldownTime = INVALID_HANDLE;

new Handle:g_Cookie_PallEnabled = INVALID_HANDLE;
new Handle:g_Cookie_Volume = INVALID_HANDLE;

new bool:g_IsInCooldown[MAXPLAYERS+1];
new bool:g_IsPallEnabled[MAXPLAYERS+1];
new bool:g_IsDonator[MAXPLAYERS+1];
new String:g_CurrentPallDescription[64];
new String:g_CurrentPallPath[64];
new String:g_CurrentPlastSongId[64];
new g_PNextFree[MAXPLAYERS+1];
new g_PallNextFree = 0;
new g_Volume[MAXPLAYERS+1];

new bool:g_DonatorLibrary = false;
public OnPluginStart()
{

    g_Cvar_IGAApiKey = CreateConVar("sm_iga_api_key", "", "API Key for your IGA webpage");
    g_Cvar_IGAUrl = CreateConVar("sm_iga_url", "", "URL to your IGA webpage");
    g_Cvar_IGADonatorsOnly = CreateConVar("sm_iga_donators_only", "1", "Whether or not only donators have access to pall");
    g_Cvar_IGAEnabled = CreateConVar("sm_iga_enabled", "1", "Whether or not pall is enabled");
    g_Cvar_IGARequestCooldownTime = CreateConVar("sm_iga_request_cooldown_time", "2.0", "How long in seconds before a client can send another http request");

    RegConsoleCmd("sm_p", Command_P, "Play a song for yourself");
    RegConsoleCmd("sm_pall", Command_Pall, "Play a song for everyone");
    RegConsoleCmd("sm_plist", Command_Plist, "Pop-up the song list");
    RegConsoleCmd("sm_stop", Command_Stop, "Stop the current song");
    RegAdminCmd("sm_fstop", Command_Fstop, ADMFLAG_VOTE, "[ADMIN] Stop the current pall for everyone");
    RegAdminCmd("sm_fpall", Command_Fpall, ADMFLAG_VOTE, "[ADMIN] Force everyone to listen to a song");
    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_yespall", Command_Yespall, "Turn on pall for yourself");
    RegConsoleCmd("sm_plast", Command_Plast, "Play the last played song for yourself");
    RegConsoleCmd("sm_authorize_iga", Command_AuthorizeIGA, "Declare that you want to upload songs to the website.  This will set you as an uploader.");

    g_Cookie_Volume = RegClientCookie("iga_volume", "Volume to play at [0-10]; 0 muted, 10 loudest", CookieAccess_Private);
    g_Cookie_PallEnabled = RegClientCookie("iga_pall_enabled", "Whether you want pall enabled or not. If yes, you will hear music when other players call !pall", CookieAccess_Private);

    g_DonatorLibrary = LibraryExists("donators");

    HookEvent("teamplay_game_over", Event_MapChange);
    //TODO hook pre-rtv map change
    //HookEvent("map_change", Event_MapChange);
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = false;
	}
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = true;
	}
}

public OnClientConnected(client)
{
    g_IsInCooldown[client] = false;
    g_PNextFree[client] = 0;
    g_Volume[client] = 10;
    g_IsPallEnabled[client] = true;

}
public OnClientCookiesCached(client)
{
    new String:buffer[11];

    GetClientCookie(client, g_Cookie_Volume, buffer, sizeof(buffer));
    if (strlen(buffer) > 0)
        g_Volume[client] = StringToInt(buffer);

    GetClientCookie(client, g_Cookie_PallEnabled, buffer, sizeof(buffer));
    if (strlen(buffer) > 0)
        g_IsPallEnabled[client] = bool:StringToInt(buffer);

    if (DonatorCheck(client))
        UserTheme(client);
}

public OnPostDonatorCheck(client)
{
    g_IsDonator[client] = true;
}

public OnClientDisconnect(client)
{
    g_IsDonator[client] = false;
}

public Action:Command_P(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, false);
    }

    return Plugin_Handled;
}

public Action:Command_Pall(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(!DonatorCheck(client))
    {
        ReplyToCommand(client, "[IGA] Only donators can use this command.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true);
    }

    return Plugin_Handled;
}

public Action:Command_Plist(client, args)
{
    if(client && IsClientAuthorized(client)){
        SongList(client);
    }

    return Plugin_Handled;
}

public Action:Command_Stop(client, args)
{
    StopSong(client);
    return Plugin_Handled;
}

public Action:Command_Fstop(client, args)
{
    StopSongAll();
    return Plugin_Handled;
}

public Action:Command_Fpall(client, args)
{
    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true, true);
    }

    return Plugin_Handled;
}

public Action:Command_Vol(client, args)
{
    if (client && args != 1)
    {
        ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".  Currently %d.", g_Volume[client]);
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client))
    {
        decl String:buffer[11];
        new volume;
        GetCmdArgString(buffer, sizeof(buffer));
        volume = StringToInt(buffer);
        if (volume >=0 && volume <= 10)
        {
            SetClientCookie(client, g_Cookie_Volume, buffer);
            g_Volume[client] = volume;
            ReplyToCommand(client, "[IGA] Set volume to %d.", volume);
        }else{
            ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".");
        }
    }

    return Plugin_Handled;
}

public Action:Command_Nopall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "0");
        g_IsPallEnabled[client] = false;
        ReplyToCommand(client, "[IGA] Disabled pall.  Type !yespall to renable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Yespall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "1");
        g_IsPallEnabled[client] = true;
        ReplyToCommand(client, "[IGA] Enabled pall.  Type !nopall to disable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Plast(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        PlaySong(client, g_CurrentPlastSongId);
    }
    return Plugin_Handled;
}

public Action:Command_AuthorizeIGA(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        AuthorizeUser(client);
    }
    return Plugin_Handled;
}

public Event_MapChange(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO get current map
    MapTheme("current_map");
    return Plugin_Continue;
}

public Steam_SetHTTPRequestGetOrPostParameterInt(&HTTPRequestHandle:request, const String:param[], value)
{
    new String:tmp[64];
    IntToString(value, tmp, sizeof(tmp));
    Steam_SetHTTPRequestGetOrPostParameter(request, param, tmp);
}

public SetAccessCode(&HTTPRequestHandle:request)
{
    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));
    Steam_SetHTTPRequestGetOrPostParameter(request, "access_token", api_key);
}

public HTTPRequestHandle:CreateIGARequest(const String:route[])
{
    decl String:base_url[256], String:url[512];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(trim_length < 0)
    {
        //IGA Url not set
        return INVALID_HTTP_HANDLE;
    }

    //check for forward slash after base_url;
    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, route);

    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url);
    SetAccessCode(request);

    return request;
}

public StartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

    g_IsInCooldown[client] = true;
    CreateTimer(GetConVarFloat(g_Cvar_IGARequestCooldownTime), RemoveCooldown, client);
}

public bool:IsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

public Action:RemoveCooldown(Handle:timer, any:client)
{
    g_IsInCooldown[client] = false;
}

public bool:IsInPall()
{
    return GetTime() < g_PallNextFree;
}
public bool:IsInP(client)
{
    return GetTime() < g_PNextFree[client];
}

//True if client can use a donator action. If donations are not enabled this
//will always be true, otherwise check if client is a donator.
public bool:DonatorCheck(client)
{
    if(!g_DonatorLibrary || !GetConVarBool(g_Cvar_IGADonatorsOnly))
        return true;
    else
        return g_IsDonator[client];
}

stock QuerySong(client, String:path[MAX_SONG_LENGTH], bool:pall = false, bool:force=false)
{
    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "path", path);

    Steam_SendHTTPRequest(request, ReceiveQuerySong, player);

    StartCooldown(client);
}

public ReceiveQuerySong(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedQuerySong (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new duration = json_object_get_int(json, "duration");
        new bool:pall = json_object_get_bool(json, "pall");
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:full_path[64], String:description[64], String:duration_formated[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "full_path", full_path, sizeof(full_path));
        json_object_get_string(json, "description", description, sizeof(description));
        json_object_get_string(json, "duration_formated", duration_formated, sizeof(duration_formated));

        if(pall)
        {
            if(!IsInPall())
            {
                g_PallNextFree = duration + GetTime();

                PrintToChatAll("[IGA] Started Playing \"%s\" to all.", description);
                PrintToChatAll("Duration %s.", duration_formated);
                PrintToChatAll("Type !stop to cancel or !nopall to mute.");

                strcopy(g_CurrentPallPath, 64, full_path);
                strcopy(g_CurrentPallDescription, 64, description);

                PlaySongAll(song_id, force);
            }else{
                PrintToChat(client, "[IGA] pall currently playing %s \"%s\".", g_CurrentPallPath, g_CurrentPallDescription);
            }
        }else if(client > 0){
            decl String:name[64];
            GetClientName(client, name, sizeof(name));

            g_PNextFree[client] = duration + GetTime();

            //PrintToChat(client, "[IGA] Started Playing \"%s\"", description);
            PrintToChatAll("[IGA] %s is currently playing \"%s\", type !p %s to play for yourself.", name, description, full_path);
            PrintToChat(client, "Duration %s.", duration_formated);
            PrintToChat(client, "Type !stop to cancel.");

            strcopy(g_CurrentPlastSongId, 64, song_id);

            PlaySong(client, song_id);
        }
    }else{
        PrintToChat(client, "[IGA] Could not find specified sound or directory.");
    }

    CloseHandle(json);
}

stock UserTheme(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    //Find the user's theme
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

stock MapTheme(String:map_theme[] ="")
{
    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameter(request, "map_theme", map_theme);
    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public ReceiveTheme(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedTheme (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));

        if(force || !IsInPall())
        {
            g_PallNextFree = 0;
            PlaySongAll(song_id, force);
        }
    }

    CloseHandle(json);
}

stock AuthorizeUser(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(AUTHORIZE_USER_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveAuthorizeUser, player);

    StartCooldown(client);
}

public ReceiveAuthorizeUser(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedAuthorizeUser (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    Steam_ReleaseHTTPRequest(request);
    if(client)
    {
        PrintToChat(client, "[IGA] You are now authorized to upload songs.");
    }
}

public PlaySongAll(String:song[], bool:force)
{
    for (new client=1; client <= MaxClients; client++)
    {
        if ( ClientHasPallEnabled(client) && (force || !IsInP(client)) )
        {
            PlaySong(client, song);
        }
    }
}

public PlaySong(client, String:song_id[])
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));

    Format(url, sizeof(url),
            "%s%s/%s/play?access_token=%s&volume=%f", base_url, SONGS_ROUTE, song_id, api_key, (g_Volume[client] / 10.0));

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
    return;
}

public StopSong(client)
{
    g_PNextFree[client] = 0;
    PlaySong(client, "stop");//TODO
}
public StopSongAll()
{
    g_PallNextFree = 0;
    PlaySongAll("stop", true);//TODO
}

public bool:ClientHasPallEnabled(client)
{
    return g_IsPallEnabled[client];
}

public SongList(client)
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, DIRECTORIES_ROUTE);

    ShowMOTDPanel(client, "Song List", url, MOTDPANEL_TYPE_URL);

}

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio
 * TODO - Add your project's description
 *
 * Copyright 2013 ???
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <clientprefs>
#include <steamtools>
#include <base64>
#include <smjansson>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "0.1"

public Plugin:myinfo =
{
    name = "In Game Audio",
    author = "CrimsonTautology",
    description = "Interact with the In Game Audio web api",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

#define QUERY_SONG_ROUTE "/v1/api/query_song"
#define MAP_THEME_ROUTE "/v1/api/map_theme"
#define USER_THEME_ROUTE "/v1/api/user_theme"
#define AUTHORIZE_USER_ROUTE "/v1/api/authorize_user"
#define SONGS_ROUTE "/songs"
#define DIRECTORIES_ROUTE "/directories"

#define MAX_STEAMID_LENGTH 21
#define MAX_COMMUNITYID_LENGTH 18
#define MAX_SONG_LENGTH 64

new Handle:g_Cvar_IGAApiKey = INVALID_HANDLE;
new Handle:g_Cvar_IGAUrl = INVALID_HANDLE;
new Handle:g_Cvar_IGADonatorsOnly = INVALID_HANDLE;
new Handle:g_Cvar_IGAEnabled = INVALID_HANDLE;
new Handle:g_Cvar_IGARequestCooldownTime = INVALID_HANDLE;

new Handle:g_Cookie_PallEnabled = INVALID_HANDLE;
new Handle:g_Cookie_Volume = INVALID_HANDLE;

new bool:g_IsInCooldown[MAXPLAYERS+1];
new bool:g_IsPallEnabled[MAXPLAYERS+1];
new bool:g_IsDonator[MAXPLAYERS+1];
new String:g_CurrentPallDescription[64];
new String:g_CurrentPallPath[64];
new String:g_CurrentPlastSongId[64];
new g_PNextFree[MAXPLAYERS+1];
new g_PallNextFree = 0;
new g_Volume[MAXPLAYERS+1];

new bool:g_DonatorLibrary = false;
public OnPluginStart()
{

    g_Cvar_IGAApiKey = CreateConVar("sm_iga_api_key", "", "API Key for your IGA webpage");
    g_Cvar_IGAUrl = CreateConVar("sm_iga_url", "", "URL to your IGA webpage");
    g_Cvar_IGADonatorsOnly = CreateConVar("sm_iga_donators_only", "1", "Whether or not only donators have access to pall");
    g_Cvar_IGAEnabled = CreateConVar("sm_iga_enabled", "1", "Whether or not pall is enabled");
    g_Cvar_IGARequestCooldownTime = CreateConVar("sm_iga_request_cooldown_time", "2.0", "How long in seconds before a client can send another http request");

    RegConsoleCmd("sm_p", Command_P, "Play a song for yourself");
    RegConsoleCmd("sm_pall", Command_Pall, "Play a song for everyone");
    RegConsoleCmd("sm_plist", Command_Plist, "Pop-up the song list");
    RegConsoleCmd("sm_stop", Command_Stop, "Stop the current song");
    RegAdminCmd("sm_fstop", Command_Fstop, ADMFLAG_VOTE, "[ADMIN] Stop the current pall for everyone");
    RegAdminCmd("sm_fpall", Command_Fpall, ADMFLAG_VOTE, "[ADMIN] Force everyone to listen to a song");
    RegConsoleCmd("sm_vol", Command_Vol, "Adjust your play volume");
    RegConsoleCmd("sm_nopall", Command_Nopall, "Turn off pall for yourself");
    RegConsoleCmd("sm_yespall", Command_Yespall, "Turn on pall for yourself");
    RegConsoleCmd("sm_plast", Command_Plast, "Play the last played song for yourself");
    RegConsoleCmd("sm_authorize_iga", Command_AuthorizeIGA, "Declare that you want to upload songs to the website.  This will set you as an uploader.");

    g_Cookie_Volume = RegClientCookie("iga_volume", "Volume to play at [0-10]; 0 muted, 10 loudest", CookieAccess_Private);
    g_Cookie_PallEnabled = RegClientCookie("iga_pall_enabled", "Whether you want pall enabled or not. If yes, you will hear music when other players call !pall", CookieAccess_Private);

    g_DonatorLibrary = LibraryExists("donators");

    HookEvent("teamplay_game_over", Event_MapChange);
    //TODO hook pre-rtv map change
    //HookEvent("map_change", Event_MapChange);
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = false;
	}
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual(name, "donators"))
	{
		g_DonatorLibrary = true;
	}
}

public OnClientConnected(client)
{
    g_IsInCooldown[client] = false;
    g_PNextFree[client] = 0;
    g_Volume[client] = 10;
    g_IsPallEnabled[client] = true;

}
public OnClientCookiesCached(client)
{
    new String:buffer[11];

    GetClientCookie(client, g_Cookie_Volume, buffer, sizeof(buffer));
    if (strlen(buffer) > 0)
        g_Volume[client] = StringToInt(buffer);

    GetClientCookie(client, g_Cookie_PallEnabled, buffer, sizeof(buffer));
    if (strlen(buffer) > 0)
        g_IsPallEnabled[client] = bool:StringToInt(buffer);

    if DonatorCheck(client)
        UserTheme(client);
}

public OnPostDonatorCheck(client)
{
    g_IsDonator[client] = true;
}

public OnClientDisconnect(client)
{
    g_IsDonator[client] = false;
}

public Action:Command_P(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, false);
    }

    return Plugin_Handled;
}

public Action:Command_Pall(client, args)
{
    if(IsClientInCooldown(client))
    {
        ReplyToCommand(client, "[IGA] User in cooldown.");
        return Plugin_Handled;
    }

    if(!GetConVarBool(g_Cvar_IGAEnabled))
    {
        ReplyToCommand(client, "[IGA] IGA not enabled.");
        return Plugin_Handled;
    }

    if(!DonatorCheck(client))
    {
        ReplyToCommand(client, "[IGA] Only donators can use this command.");
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true);
    }

    return Plugin_Handled;
}

public Action:Command_Plist(client, args)
{
    if(client && IsClientAuthorized(client)){
        SongList(client);
    }

    return Plugin_Handled;
}

public Action:Command_Stop(client, args)
{
    StopSong(client);
    return Plugin_Handled;
}

public Action:Command_Fstop(client, args)
{
    StopSongAll();
    return Plugin_Handled;
}

public Action:Command_Fpall(client, args)
{
    if(client && IsClientAuthorized(client)){
        decl String:path[MAX_SONG_LENGTH];
        GetCmdArgString(path, sizeof(path));
        QuerySong(client, path, true, true);
    }

    return Plugin_Handled;
}

public Action:Command_Vol(client, args)
{
    if (client && args != 1)
    {
        ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".  Currently %d.", g_Volume[client]);
        return Plugin_Handled;
    }

    if(client && IsClientAuthorized(client))
    {
        decl String:buffer[11];
        new volume;
        GetCmdArgString(buffer, sizeof(buffer));
        volume = StringToInt(buffer);
        if (volume >=0 && volume <= 10)
        {
            SetClientCookie(client, g_Cookie_Volume, buffer);
            g_Volume[client] = volume;
            ReplyToCommand(client, "[IGA] Set volume to %d.", volume);
        }else{
            ReplyToCommand(client, "[IGA] usage \"!vol [0-10]\".");
        }
    }

    return Plugin_Handled;
}

public Action:Command_Nopall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "0");
        g_IsPallEnabled[client] = false;
        ReplyToCommand(client, "[IGA] Disabled pall.  Type !yespall to renable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Yespall(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        SetClientCookie(client, g_Cookie_PallEnabled, "1");
        g_IsPallEnabled[client] = true;
        ReplyToCommand(client, "[IGA] Enabled pall.  Type !nopall to disable it.");
    }
    return Plugin_Handled;
}

public Action:Command_Plast(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        PlaySong(client, g_CurrentPlastSongId);
    }
    return Plugin_Handled;
}

public Action:Command_AuthorizeIGA(client, args)
{
    if (client && IsClientAuthorized(client))
    {
        AuthorizeUser(client);
    }
    return Plugin_Handled;
}

public Event_MapChange(Handle:event, const String:name[], bool:dontBroadcast)
{
    //TODO get current map
    MapTheme("current_map");
    return Plugin_Continue;
}

public Steam_SetHTTPRequestGetOrPostParameterInt(&HTTPRequestHandle:request, const String:param[], value)
{
    new String:tmp[64];
    IntToString(value, tmp, sizeof(tmp));
    Steam_SetHTTPRequestGetOrPostParameter(request, param, tmp);
}

public SetAccessCode(&HTTPRequestHandle:request)
{
    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));
    Steam_SetHTTPRequestGetOrPostParameter(request, "access_token", api_key);
}

public HTTPRequestHandle:CreateIGARequest(const String:route[])
{
    decl String:base_url[256], String:url[512];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));
    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(trim_length < 0)
    {
        //IGA Url not set
        return INVALID_HTTP_HANDLE;
    }

    //check for forward slash after base_url;
    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, route);

    new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url);
    SetAccessCode(request);

    return request;
}

public StartCooldown(client)
{
    //Ignore the server console
    if (client == 0)
        return;

    g_IsInCooldown[client] = true;
    CreateTimer(GetConVarFloat(g_Cvar_IGARequestCooldownTime), RemoveCooldown, client);
}

public bool:IsClientInCooldown(client)
{
    if(client == 0)
        return false;
    else
        return g_IsInCooldown[client];
}

public Action:RemoveCooldown(Handle:timer, any:client)
{
    g_IsInCooldown[client] = false;
}

public bool:IsInPall()
{
    return GetTime() < g_PallNextFree;
}
public bool:IsInP(client)
{
    return GetTime() < g_PNextFree[client];
}

//True if client can use a donator action. If donations are not enabled this
//will always be true, otherwise check if client is a donator.
public bool:DonatorCheck(client)
{
    if(!g_DonatorLibrary || !GetConVarBool(g_Cvar_IGADonatorsOnly))
        return true;
    else
        return g_IsDonator[client];
}

stock QuerySong(client, String:path[MAX_SONG_LENGTH], bool:pall = false, bool:force=false)
{
    new HTTPRequestHandle:request = CreateIGARequest(QUERY_SONG_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameterInt(request, "pall", pall);
    Steam_SetHTTPRequestGetOrPostParameterInt(request, "force", force);
    Steam_SetHTTPRequestGetOrPostParameter(request, "path", path);

    Steam_SendHTTPRequest(request, ReceiveQuerySong, player);

    StartCooldown(client);
}

public ReceiveQuerySong(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedQuerySong (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new duration = json_object_get_int(json, "duration");
        new bool:pall = json_object_get_bool(json, "pall");
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64], String:full_path[64], String:description[64], String:duration_formated[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));
        json_object_get_string(json, "full_path", full_path, sizeof(full_path));
        json_object_get_string(json, "description", description, sizeof(description));
        json_object_get_string(json, "duration_formated", duration_formated, sizeof(duration_formated));

        if(pall)
        {
            if(!IsInPall())
            {
                g_PallNextFree = duration + GetTime();

                PrintToChatAll("[IGA] Started Playing \"%s\" to all.", description);
                PrintToChatAll("Duration %s.", duration_formated);
                PrintToChatAll("Type !stop to cancel or !nopall to mute.");

                strcopy(g_CurrentPallPath, 64, full_path);
                strcopy(g_CurrentPallDescription, 64, description);

                PlaySongAll(song_id, force);
            }else{
                PrintToChat(client, "[IGA] pall currently playing %s \"%s\".", g_CurrentPallPath, g_CurrentPallDescription);
            }
        }else if(client > 0){
            decl String:name[64];
            GetClientName(client, name, sizeof(name));

            g_PNextFree[client] = duration + GetTime();

            //PrintToChat(client, "[IGA] Started Playing \"%s\"", description);
            PrintToChatAll("[IGA] %s is currently playing \"%s\", type !p %s to play for yourself.", name, description, full_path);
            PrintToChat(client, "Duration %s.", duration_formated);
            PrintToChat(client, "Type !stop to cancel.");

            strcopy(g_CurrentPlastSongId, 64, song_id);

            PlaySong(client, song_id);
        }
    }else{
        PrintToChat(client, "[IGA] Could not find specified sound or directory.");
    }

    CloseHandle(json);
}

stock UserTheme(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    //Find the user's theme
    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

stock MapTheme(String:map_theme[] ="")
{
    new HTTPRequestHandle:request = CreateIGARequest(USER_THEME_ROUTE);

    if(request == INVALID_HTTP_HANDLE)
    {
        PrintToConsole(0, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    Steam_SetHTTPRequestGetOrPostParameter(request, "map_theme", map_theme);
    Steam_SendHTTPRequest(request, ReceiveTheme, 0);
}

public ReceiveTheme(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedTheme (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    decl String:data[4096];
    Steam_GetHTTPResponseBodyData(request, data, sizeof(data));
    Steam_ReleaseHTTPRequest(request);

    new Handle:json = json_load(data);
    new bool:found = json_object_get_bool(json, "found");

    if(found)
    {
        new bool:force = json_object_get_bool(json, "force");
        new String:song_id[64];
        json_object_get_string(json, "song_id", song_id, sizeof(song_id));

        if(force || !IsInPall())
        {
            g_PallNextFree = 0;
            PlaySongAll(song_id, force);
        }
    }

    CloseHandle(json);
}

stock AuthorizeUser(client)
{
    new HTTPRequestHandle:request = CreateIGARequest(AUTHORIZE_USER_ROUTE);
    new player = client > 0 ? GetClientUserId(client) : 0;

    if(request == INVALID_HTTP_HANDLE)
    {
        ReplyToCommand(client, "[IGA] sm_iga_url invalid; cannot create HTTP request");
        return;
    }

    decl String:uid[MAX_COMMUNITYID_LENGTH];
    Steam_GetCSteamIDForClient(client, uid, sizeof(uid));
    Steam_SetHTTPRequestGetOrPostParameter(request, "uid", uid);

    Steam_SendHTTPRequest(request, ReceiveAuthorizeUser, player);

    StartCooldown(client);
}

public ReceiveAuthorizeUser(HTTPRequestHandle:request, bool:successful, HTTPStatusCode:code, any:userid)
{
    new client = GetClientOfUserId(userid);
    if(!successful || code != HTTPStatusCode_OK)
    {
        LogError("[IGA] Error at RecivedAuthorizeUser (HTTP Code %d; success %d)", code, successful);
        Steam_ReleaseHTTPRequest(request);
        return;
    }

    Steam_ReleaseHTTPRequest(request);
    if(client)
    {
        PrintToChat(client, "[IGA] You are now authorized to upload songs.");
    }
}

public PlaySongAll(String:song[], bool:force)
{
    for (new client=1; client <= MaxClients; client++)
    {
        if ( ClientHasPallEnabled(client) && (force || !IsInP(client)) )
        {
            PlaySong(client, song);
        }
    }
}

public PlaySong(client, String:song_id[])
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    decl String:api_key[128];
    GetConVarString(g_Cvar_IGAApiKey, api_key, sizeof(api_key));

    Format(url, sizeof(url),
            "%s%s/%s/play?access_token=%s&volume=%f", base_url, SONGS_ROUTE, song_id, api_key, (g_Volume[client] / 10.0));

    new Handle:panel = CreateKeyValues("data");
    KvSetString(panel, "title", "In Game Audio");
    KvSetNum(panel, "type", MOTDPANEL_TYPE_URL);
    KvSetString(panel, "msg", url);

    ShowVGUIPanel(client, "info", panel, false);
    CloseHandle(panel);
    return;
}

public StopSong(client)
{
    g_PNextFree[client] = 0;
    PlaySong(client, "stop");//TODO
}
public StopSongAll()
{
    g_PallNextFree = 0;
    PlaySongAll("stop", true);//TODO
}

public bool:ClientHasPallEnabled(client)
{
    return g_IsPallEnabled[client];
}

public SongList(client)
{
    decl String:url[256], String:base_url[128];
    GetConVarString(g_Cvar_IGAUrl, base_url, sizeof(base_url));

    TrimString(base_url);
    new trim_length = strlen(base_url) - 1;

    if(base_url[trim_length] == '/')
    {
        strcopy(base_url, trim_length + 1, base_url);
    }

    Format(url, sizeof(url),
            "%s%s", base_url, DIRECTORIES_ROUTE);

    ShowMOTDPanel(client, "Song List", url, MOTDPANEL_TYPE_URL);

}


Update Medic.sp

Update Medic.sp

Fix wrong target !IsPlayerAlive(i) to !IsPlayerAlive(j) in 173 line.
Thanks to Bot Chris

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <timers>

#define PLUGIN_DESCRIPTION "Medic"
#define PLUGIN_NAME "Medic"
#define PLUGIN_VERSION "0.8.1"
#define PLUGIN_WORKING "1"
#define PLUGIN_LOG_PREFIX "Medic"
#define PLUGIN_AUTHOR "rrrfffrrr"
#define PLUGIN_URL ""

#define MAXWEAPONNAME 64
#define MAXUSERNAME 64
#define OFFSETCOUNT 7

/*
* 죽은 위치 표시, 렉 제거
*/

public Plugin:myinfo = {
        name            = PLUGIN_NAME,
        author          = PLUGIN_AUTHOR,
        description     = PLUGIN_DESCRIPTION,
        version         = PLUGIN_VERSION,
        url             = PLUGIN_URL
};

new Handle:cvarEnable = INVALID_HANDLE;
new Handle:cvarMedic = INVALID_HANDLE;
new Handle:cvarDelay = INVALID_HANDLE;
new Handle:cvarMaxRevive = INVALID_HANDLE;
new Handle:cvarMaxDistance = INVALID_HANDLE;
new Handle:cvarMedicItem = INVALID_HANDLE;
new Handle:cvarReviveScore = INVALID_HANDLE;

new bool:iEnabled = false;

new String:sType[64];
new String:sClass[MAXPLAYERS+1][64];
new String:sItem[MAXWEAPONNAME];

new bool:bMedicEnabled[MAXPLAYERS+1];
new bool:bPlayerJoined[MAXPLAYERS+1];
int iSoulDetected[MAXPLAYERS+1];
new Handle:RespawnTimer[MAXPLAYERS+1];
int RespawnTarget[MAXPLAYERS+1];
new Float:DeadPosition[MAXPLAYERS+1][3];
int RespawnCount[MAXPLAYERS+1];

int iRevivePoint[MAXPLAYERS+1];
int iMaxRevivePoint;
int iReviveScore;

float fMaxDistance;
float fReviveDelay;

new Handle:f_respawn = INVALID_HANDLE;
new Handle:c_gameconfig;

new Handle:HGameTimer = INVALID_HANDLE;

public void OnPluginStart() {
	cvarEnable = CreateConVar("sm_medic_enabled", "1", "Enable Medic", FCVAR_NOTIFY);
	cvarMedic = CreateConVar("sm_medic_class", "breacher", "Class name of Medic", FCVAR_NOTIFY);
	cvarDelay = CreateConVar("sm_medic_delay", "10", "Delay for revive", FCVAR_NOTIFY);
	cvarMaxRevive = CreateConVar("sm_medic_rp", "8", "max revive point for revive", FCVAR_NOTIFY);
	cvarMaxDistance = CreateConVar("sm_medic_distance", "100", "Distance for revive", FCVAR_NOTIFY);
	cvarMedicItem = CreateConVar("sm_medic_item", "kabar", "Item for revive", FCVAR_NOTIFY);
	cvarReviveScore = CreateConVar("sm_medic_score", "50", "Get score when revive", FCVAR_NOTIFY);

	HookConVarChange(cvarEnable,cvarUpdate);
	HookConVarChange(cvarMedic,cvarUpdate);
	HookConVarChange(cvarDelay,cvarUpdate);
	HookConVarChange(cvarMaxRevive,cvarUpdate);
	HookConVarChange(cvarMaxDistance,cvarUpdate);
	HookConVarChange(cvarMedicItem,cvarUpdate);
	HookConVarChange(cvarReviveScore,cvarUpdate);

	c_gameconfig = LoadGameConfigFile("insurgency.games");
	if (c_gameconfig == INVALID_HANDLE) {
		SetFailState("Fatal Error: Missing File \"insurgency.games\"!");
	}

	StartPrepSDKCall(SDKCall_Player);
	decl String:game[40];
	GetGameFolderName(game, sizeof(game));
	if (StrEqual(game, "insurgency")) {
		PrepSDKCall_SetFromConf(c_gameconfig, SDKConf_Signature, "ForceRespawn");
	}
	if (StrEqual(game, "doi")) {
		PrepSDKCall_SetFromConf(c_gameconfig, SDKConf_Virtual, "ForceRespawn");
	}
	f_respawn = EndPrepSDKCall();
	if (f_respawn == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find ForceRespawn");
	}

	HookEvent("player_pick_squad", Event_PlayerPickSquad);
	HookEvent("weapon_fire", Event_WeaponFired);
	HookEvent("player_death", Event_PlayerDeath);
	HookEvent("controlpoint_captured", Event_Captured);
	HookEvent("object_destroyed", Event_Captured);
	HookEvent("round_start", Event_Captured);
	HookEvent("round_begin", Event_Captured);

	UpdateCvar();
}

public OnMapStart() {
	SDKHook(GetPlayerResourceEntity(), SDKHook_ThinkPost, HookThinkPost);
}

public cvarUpdate(Handle:cvar, const String:oldvalue[], const String:newvalue[]) {
	UpdateCvar();
}

public UpdateCvar() {
	iEnabled = GetConVarBool(cvarEnable);
	iMaxRevivePoint = GetConVarInt(cvarMaxRevive);
	iReviveScore = GetConVarInt(cvarReviveScore);
	fMaxDistance = GetConVarFloat(cvarMaxDistance);
	fReviveDelay = GetConVarFloat(cvarDelay);
	GetConVarString(cvarMedic, sType, sizeof(sType));
	GetConVarString(cvarMedicItem, sItem, MAXWEAPONNAME);
}

public OnClientPutInServer(client) {
	RespawnTarget[client] = 0;
	RespawnTimer[client] = INVALID_HANDLE;
	bMedicEnabled[client] = false;
	iSoulDetected[client] = 0;
	bPlayerJoined[client] = false;
	if (HGameTimer == INVALID_HANDLE) {
		HGameTimer = CreateTimer(1.0, FTimer,_, TIMER_REPEAT);
	}
	RespawnCount[client] = 0;
}

// function that using in game
public OneGameFrame() {
	decl String:weaponName[MAXWEAPONNAME];
	int i;
	int j;
	int s;
	for(i = 1; i < MaxClients + 1; i++) {
		if (bMedicEnabled[i] && IsValidPlayer(i) && !IsFakeClient(i)) {
			decl Float:vecFrom[3];
			GetClientEyePosition(i, vecFrom);
			GetClientWeapon(i, weaponName, MAXWEAPONNAME);
			if (RespawnTimer[i] != INVALID_HANDLE) {

				if (IsValidPlayer(RespawnTarget[i]) && !IsPlayerAlive(RespawnTarget[i]) && StrContains(weaponName, sItem, false) != -1 && GetVectorDistance(vecFrom, DeadPosition[RespawnTarget[i]]) <= fMaxDistance) {
					PrintCenterText(i, "Reviving...");
				} else {
					PrintCenterText(i, "Cancel reviving");
					RespawnTarget[i] = 0;
					new Handle:temp = RespawnTimer[i];
					RespawnTimer[i] = INVALID_HANDLE;
					KillTimer(temp);
				}
			} else if (IsPlayerAlive(i)) {
				// detect soul
				s = iSoulDetected[i];
				if (IsValidPlayer(s) && !IsPlayerAlive(s)) {
					if (!(GetVectorDistance(vecFrom, DeadPosition[s]) <= fMaxDistance)) {
						iSoulDetected[i] = 0;
					}
				} else {
					iSoulDetected[i] = 0;
					for(j = 1; j < MaxClients + 1; j++) {
						if (IsValidPlayer(j) && bPlayerJoined[j] == true && !IsPlayerAlive(j) && !IsFakeClient(j) && GetVectorDistance(vecFrom, DeadPosition[j]) <= fMaxDistance && i != j) {
							iSoulDetected[i] = j;
							PrintCenterText(i, (iRevivePoint[i] > 0) ? "A soul detected..." : "A soul is detected but you can't do any thing...");
							break;
						}
					}
				}
			}
		}
	}
}

// hook
public HookThinkPost(iEnt) {
	if (iEnabled) {
		int offset = FindSendPropInfo("CINSPlayerResource", "m_iPlayerScore");
		int i;

		decl iTotalScore[MAXPLAYERS+1];
		GetEntDataArray(iEnt, offset, iTotalScore, MaxClients + 1);

		for(i = 0; i < MaxClients + 1; ++i) {
			if (IsValidPlayer(i) && RespawnCount[i] > 0) {
				iTotalScore[i] += RespawnCount[i] * iReviveScore;
			}
		}

		SetEntDataArray(iEnt, offset, iTotalScore, MaxClients + 1);
	}
}

// events
public Action:Event_PlayerPickSquad(Handle:event, const String:name[], bool:dontBroadcast) {
	new client = GetClientOfUserId( GetEventInt( event, "userid" ) );
	bPlayerJoined[client] = true;
	bMedicEnabled[client] = false;
	decl String:class_template[64];
	GetEventString(event, "class_template",class_template,sizeof(class_template));
	if(IsValidPlayer(client) && strlen(class_template) > 1) {
		strcopy(sClass[client], 64, class_template);
		if (StrContains(sClass[client], sType, false) != -1) {
			bMedicEnabled[client] = true;
			if (RespawnTimer[client] != INVALID_HANDLE) {
				KillTimer(RespawnTimer[client]);
			}
			RespawnTimer[client] = INVALID_HANDLE;
			decl String:user_name[MAXUSERNAME];
			GetClientName(client, user_name, MAXUSERNAME);
			PrintCenterTextAll("Now %s is Medic.", user_name);
		}
	}
	return Plugin_Continue;
}

public Action:Event_WeaponFired(Handle:event, const String:name[], bool:dontBroadcast) {
	new client = GetClientOfUserId( GetEventInt( event, "userid" ) );
	decl String:weapon_name[MAXWEAPONNAME];
	GetClientWeapon(client, weapon_name, MAXWEAPONNAME);

	if (!iEnabled || !IsValidPlayer(client)) {
		return Plugin_Continue;
	}

	if (bMedicEnabled[client] && RespawnTimer[client] == INVALID_HANDLE && iRevivePoint[client] > 0) {
		int target = iSoulDetected[client];
		if (IsValidPlayer(target) && !IsFakeClient(target) && !IsPlayerAlive(target) && StrContains(weapon_name, sItem) != -1) {
			RespawnTimer[client] = CreateTimer(fReviveDelay, FRevive, client);
			RespawnTarget[client] = target;
		}
	}

	return Plugin_Continue;
}

public Action:Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) {
	new client = GetClientOfUserId( GetEventInt( event, "userid" ) );
	if (!IsFakeClient(client)) {
		GetClientAbsOrigin(client, DeadPosition[client]);
	}

	if (bMedicEnabled[client] && RespawnTimer[client] != INVALID_HANDLE) {
		PrintCenterText(client, "Cancel reviving");
		RespawnTarget[client] = 0;
		if (RespawnTimer[client] != INVALID_HANDLE) {
			KillTimer(RespawnTimer[client]);
		}
		RespawnTimer[client] = INVALID_HANDLE;
	}
	return Plugin_Continue;
}

public Action:Event_Captured(Handle:event, const String:name[], bool:dontBroadcast) {
	for(new i = 0; i < MaxClients + 1; i++) {
		iRevivePoint[i] = iMaxRevivePoint;
	}
	return Plugin_Continue;
}

// timer function
public Action:FRevive(Handle:timer, any client) {
	int s = RespawnTarget[client];
	if (!IsValidPlayer(s) || IsPlayerAlive(s) || !IsPlayerAlive(client)) {
		PrintCenterText(client, "A soul has disappeared...");
		RespawnTarget[client] = 0;
		RespawnTimer[client] = INVALID_HANDLE;
		return Plugin_Handled;
	}
	SDKCall(f_respawn, RespawnTarget[client]);
	TeleportEntity(s, DeadPosition[s], NULL_VECTOR, NULL_VECTOR);
	RespawnTarget[client] = 0;
	RespawnTimer[client] = INVALID_HANDLE;
	iRevivePoint[client]--;
	PrintCenterText(client, "Some one is revived...");
	RespawnCount[client]++;
	return Plugin_Handled;
}

public Action:FTimer(Handle:timer) {
	OneGameFrame();
	return Plugin_Continue;
}

// etc
public IsValidPlayer(client)
{
	if (client == 0)
		return false;

	if (!IsClientConnected(client))
		return false;

	if (!IsClientInGame(client))
		return false;

	return true;
}

stock GetRealClientCount(bool:inGameOnly = true)
{
	new clients = 0;
	for( new i = 1; i <= MaxClients + 1; i++) {
		if ( ( ( inGameOnly ) ? IsClientInGame( i ) : IsClientConnected( i ) ) && !IsFakeClient( i ) ) {
			clients++;
		}
	}
	return clients;
}

#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <timers>

#define PLUGIN_DESCRIPTION "Medic"
#define PLUGIN_NAME "Medic"
#define PLUGIN_VERSION "0.8.1"
#define PLUGIN_WORKING "1"
#define PLUGIN_LOG_PREFIX "Medic"
#define PLUGIN_AUTHOR "rrrfffrrr"
#define PLUGIN_URL ""

#define MAXWEAPONNAME 64
#define MAXUSERNAME 64
#define OFFSETCOUNT 7

/*
* 죽은 위치 표시, 렉 제거
*/

public Plugin:myinfo = {
        name            = PLUGIN_NAME,
        author          = PLUGIN_AUTHOR,
        description     = PLUGIN_DESCRIPTION,
        version         = PLUGIN_VERSION,
        url             = PLUGIN_URL
};

new Handle:cvarEnable = INVALID_HANDLE;
new Handle:cvarMedic = INVALID_HANDLE;
new Handle:cvarDelay = INVALID_HANDLE;
new Handle:cvarMaxRevive = INVALID_HANDLE;
new Handle:cvarMaxDistance = INVALID_HANDLE;
new Handle:cvarMedicItem = INVALID_HANDLE;
new Handle:cvarReviveScore = INVALID_HANDLE;

new bool:iEnabled = false;

new String:sType[64];
new String:sClass[MAXPLAYERS+1][64];
new String:sItem[MAXWEAPONNAME];

new bool:bMedicEnabled[MAXPLAYERS+1];
new bool:bPlayerJoined[MAXPLAYERS+1];
int iSoulDetected[MAXPLAYERS+1];
new Handle:RespawnTimer[MAXPLAYERS+1];
int RespawnTarget[MAXPLAYERS+1];
new Float:DeadPosition[MAXPLAYERS+1][3];
int RespawnCount[MAXPLAYERS+1];

int iRevivePoint[MAXPLAYERS+1];
int iMaxRevivePoint;
int iReviveScore;

float fMaxDistance;
float fReviveDelay;

new Handle:f_respawn = INVALID_HANDLE;
new Handle:c_gameconfig;

new Handle:HGameTimer = INVALID_HANDLE;

public void OnPluginStart() {
	cvarEnable = CreateConVar("sm_medic_enabled", "1", "Enable Medic", FCVAR_NOTIFY);
	cvarMedic = CreateConVar("sm_medic_class", "breacher", "Class name of Medic", FCVAR_NOTIFY);
	cvarDelay = CreateConVar("sm_medic_delay", "10", "Delay for revive", FCVAR_NOTIFY);
	cvarMaxRevive = CreateConVar("sm_medic_rp", "8", "max revive point for revive", FCVAR_NOTIFY);
	cvarMaxDistance = CreateConVar("sm_medic_distance", "100", "Distance for revive", FCVAR_NOTIFY);
	cvarMedicItem = CreateConVar("sm_medic_item", "kabar", "Item for revive", FCVAR_NOTIFY);
	cvarReviveScore = CreateConVar("sm_medic_score", "50", "Get score when revive", FCVAR_NOTIFY);

	HookConVarChange(cvarEnable,cvarUpdate);
	HookConVarChange(cvarMedic,cvarUpdate);
	HookConVarChange(cvarDelay,cvarUpdate);
	HookConVarChange(cvarMaxRevive,cvarUpdate);
	HookConVarChange(cvarMaxDistance,cvarUpdate);
	HookConVarChange(cvarMedicItem,cvarUpdate);
	HookConVarChange(cvarReviveScore,cvarUpdate);

	c_gameconfig = LoadGameConfigFile("insurgency.games");
	if (c_gameconfig == INVALID_HANDLE) {
		SetFailState("Fatal Error: Missing File \"insurgency.games\"!");
	}

	StartPrepSDKCall(SDKCall_Player);
	decl String:game[40];
	GetGameFolderName(game, sizeof(game));
	if (StrEqual(game, "insurgency")) {
		PrepSDKCall_SetFromConf(c_gameconfig, SDKConf_Signature, "ForceRespawn");
	}
	if (StrEqual(game, "doi")) {
		PrepSDKCall_SetFromConf(c_gameconfig, SDKConf_Virtual, "ForceRespawn");
	}
	f_respawn = EndPrepSDKCall();
	if (f_respawn == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find ForceRespawn");
	}

	HookEvent("player_pick_squad", Event_PlayerPickSquad);
	HookEvent("weapon_fire", Event_WeaponFired);
	HookEvent("player_death", Event_PlayerDeath);
	HookEvent("controlpoint_captured", Event_Captured);
	HookEvent("object_destroyed", Event_Captured);
	HookEvent("round_start", Event_Captured);
	HookEvent("round_begin", Event_Captured);

	UpdateCvar();
}

public OnMapStart() {
	SDKHook(GetPlayerResourceEntity(), SDKHook_ThinkPost, HookThinkPost);
}

public cvarUpdate(Handle:cvar, const String:oldvalue[], const String:newvalue[]) {
	UpdateCvar();
}

public UpdateCvar() {
	iEnabled = GetConVarBool(cvarEnable);
	iMaxRevivePoint = GetConVarInt(cvarMaxRevive);
	iReviveScore = GetConVarInt(cvarReviveScore);
	fMaxDistance = GetConVarFloat(cvarMaxDistance);
	fReviveDelay = GetConVarFloat(cvarDelay);
	GetConVarString(cvarMedic, sType, sizeof(sType));
	GetConVarString(cvarMedicItem, sItem, MAXWEAPONNAME);
}

public OnClientPutInServer(client) {
	RespawnTarget[client] = 0;
	RespawnTimer[client] = INVALID_HANDLE;
	bMedicEnabled[client] = false;
	iSoulDetected[client] = 0;
	bPlayerJoined[client] = false;
	if (HGameTimer == INVALID_HANDLE) {
		HGameTimer = CreateTimer(1.0, FTimer,_, TIMER_REPEAT);
	}
	RespawnCount[client] = 0;
}

// function that using in game
public OneGameFrame() {
	decl String:weaponName[MAXWEAPONNAME];
	int i;
	int j;
	int s;
	for(i = 1; i < MaxClients + 1; i++) {
		if (bMedicEnabled[i] && IsValidPlayer(i) && !IsFakeClient(i)) {
			decl Float:vecFrom[3];
			GetClientEyePosition(i, vecFrom);
			GetClientWeapon(i, weaponName, MAXWEAPONNAME);
			if (RespawnTimer[i] != INVALID_HANDLE) {

				if (IsValidPlayer(RespawnTarget[i]) && !IsPlayerAlive(RespawnTarget[i]) && StrContains(weaponName, sItem, false) != -1 && GetVectorDistance(vecFrom, DeadPosition[RespawnTarget[i]]) <= fMaxDistance) {
					PrintCenterText(i, "Reviving...");
				} else {
					PrintCenterText(i, "Cancel reviving");
					RespawnTarget[i] = 0;
					new Handle:temp = RespawnTimer[i];
					RespawnTimer[i] = INVALID_HANDLE;
					KillTimer(temp);
				}
			} else if (IsPlayerAlive(i)) {
				// detect soul
				s = iSoulDetected[i];
				if (IsValidPlayer(s) && !IsPlayerAlive(s)) {
					if (!(GetVectorDistance(vecFrom, DeadPosition[s]) <= fMaxDistance)) {
						iSoulDetected[i] = 0;
					}
				} else {
					iSoulDetected[i] = 0;
					for(j = 1; j < MaxClients + 1; j++) {
						if (IsValidPlayer(j) && bPlayerJoined[j] == true && !IsPlayerAlive(j) && !IsFakeClient(i) && GetVectorDistance(vecFrom, DeadPosition[j]) <= fMaxDistance && i != j) {
							iSoulDetected[i] = j;
							PrintCenterText(i, (iRevivePoint[i] > 0) ? "A soul detected..." : "A soul is detected but you can't do any thing...");
							break;
						}
					}
				}
			}
		}
	}
}

// hook
public HookThinkPost(iEnt) {
	if (iEnabled) {
		int offset = FindSendPropInfo("CINSPlayerResource", "m_iPlayerScore");
		int i;

		decl iTotalScore[MAXPLAYERS+1];
		GetEntDataArray(iEnt, offset, iTotalScore, MaxClients + 1);

		for(i = 0; i < MaxClients + 1; ++i) {
			if (IsValidPlayer(i) && RespawnCount[i] > 0) {
				iTotalScore[i] += RespawnCount[i] * iReviveScore;
			}
		}

		SetEntDataArray(iEnt, offset, iTotalScore, MaxClients + 1);
	}
}

// events
public Action:Event_PlayerPickSquad(Handle:event, const String:name[], bool:dontBroadcast) {
	new client = GetClientOfUserId( GetEventInt( event, "userid" ) );
	bPlayerJoined[client] = true;
	bMedicEnabled[client] = false;
	decl String:class_template[64];
	GetEventString(event, "class_template",class_template,sizeof(class_template));
	if(IsValidPlayer(client) && strlen(class_template) > 1) {
		strcopy(sClass[client], 64, class_template);
		if (StrContains(sClass[client], sType, false) != -1) {
			bMedicEnabled[client] = true;
			if (RespawnTimer[client] != INVALID_HANDLE) {
				KillTimer(RespawnTimer[client]);
			}
			RespawnTimer[client] = INVALID_HANDLE;
			decl String:user_name[MAXUSERNAME];
			GetClientName(client, user_name, MAXUSERNAME);
			PrintCenterTextAll("Now %s is Medic.", user_name);
		}
	}
	return Plugin_Continue;
}

public Action:Event_WeaponFired(Handle:event, const String:name[], bool:dontBroadcast) {
	new client = GetClientOfUserId( GetEventInt( event, "userid" ) );
	decl String:weapon_name[MAXWEAPONNAME];
	GetClientWeapon(client, weapon_name, MAXWEAPONNAME);

	if (!iEnabled || !IsValidPlayer(client)) {
		return Plugin_Continue;
	}

	if (bMedicEnabled[client] && RespawnTimer[client] == INVALID_HANDLE && iRevivePoint[client] > 0) {
		int target = iSoulDetected[client];
		if (IsValidPlayer(target) && !IsFakeClient(target) && !IsPlayerAlive(target) && StrContains(weapon_name, sItem) != -1) {
			RespawnTimer[client] = CreateTimer(fReviveDelay, FRevive, client);
			RespawnTarget[client] = target;
		}
	}

	return Plugin_Continue;
}

public Action:Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) {
	new client = GetClientOfUserId( GetEventInt( event, "userid" ) );
	if (!IsFakeClient(client)) {
		GetClientAbsOrigin(client, DeadPosition[client]);
	}

	if (bMedicEnabled[client] && RespawnTimer[client] != INVALID_HANDLE) {
		PrintCenterText(client, "Cancel reviving");
		RespawnTarget[client] = 0;
		if (RespawnTimer[client] != INVALID_HANDLE) {
			KillTimer(RespawnTimer[client]);
		}
		RespawnTimer[client] = INVALID_HANDLE;
	}
	return Plugin_Continue;
}

public Action:Event_Captured(Handle:event, const String:name[], bool:dontBroadcast) {
	for(new i = 0; i < MaxClients + 1; i++) {
		iRevivePoint[i] = iMaxRevivePoint;
	}
	return Plugin_Continue;
}

// timer function
public Action:FRevive(Handle:timer, any client) {
	int s = RespawnTarget[client];
	if (!IsValidPlayer(s) || IsPlayerAlive(s) || !IsPlayerAlive(client)) {
		PrintCenterText(client, "A soul has disappeared...");
		RespawnTarget[client] = 0;
		RespawnTimer[client] = INVALID_HANDLE;
		return Plugin_Handled;
	}
	SDKCall(f_respawn, RespawnTarget[client]);
	TeleportEntity(s, DeadPosition[s], NULL_VECTOR, NULL_VECTOR);
	RespawnTarget[client] = 0;
	RespawnTimer[client] = INVALID_HANDLE;
	iRevivePoint[client]--;
	PrintCenterText(client, "Some one is revived...");
	RespawnCount[client]++;
	return Plugin_Handled;
}

public Action:FTimer(Handle:timer) {
	OneGameFrame();
	return Plugin_Continue;
}

// etc
public IsValidPlayer(client)
{
	if (client == 0)
		return false;

	if (!IsClientConnected(client))
		return false;

	if (!IsClientInGame(client))
		return false;

	return true;
}

stock GetRealClientCount(bool:inGameOnly = true)
{
	new clients = 0;
	for( new i = 1; i <= MaxClients + 1; i++) {
		if ( ( ( inGameOnly ) ? IsClientInGame( i ) : IsClientConnected( i ) ) && !IsFakeClient( i ) ) {
			clients++;
		}
	}
	return clients;
}


cli: Fix --debug

cli: Fix --debug

class HTTP-headers is List[Pair] {
    method content-type~ { $self<Content-Type> }
    method content-length~ { $self<Content-Length> }
    method content-encoding~ { $self<Content-Encoding> }
    method location~ { $self<Location> }
    method host~     { $self<Host> }
}

class HTTP-response is List[Pair] {

    method headers -->HTTP-headers { $self<headers>-->File.slurp-->HTTP-headers }
    method req-method~ { $self<req-method> }
    method req-headers -->HTTP-headers { $self<req-headers>-->File.slurp-->HTTP-headers }
    method body~ { .slurp if $self<body>-->File  }
    method remote-url -->HTTP   { $self<remote-url>   }
    method body-size+ { $self<body-size>-->Int }
    method json -->JSON { $self.body-->JSON }
    method code+ { $self<code>-->Int }

    method charset~ {
        $self.headers.content-type
        .${ sed -rn 's/.*charset\s*=\s*"?([^"\s;]+)"?/\1/pi' }.uc;
    }
    method message~ { $self<message> }
    method http-version~ { $self<http-version> }

    method is-success? { $self.code.starts-with(2) }
    method is-redirect? { $self.code.starts-with(3) }
    method is-client-error? { $self.code.starts-with(4) }
    method is-server-error? { $self.code.starts-with(5) }
    method is-error? { $self.code.matches(/^[45]/) }

    method ok^ {
        if $self.is-success {
            $self;
        } else {
            die "{$self.req-method} {$self.remote-url} {$self.code} {$self.message}\n" ~
              ($self.body-size > 100 ?? $self.body.substr(0,100) ~ '...' !! $self.body)
        }
    }
}

augment HTTP {
    method get~ {
        ${ $:curl -fSsL $self }
    }

    method add($path) -->HTTP {
        "$self/$path";
    }

    method get-file(:$to)-->File {
        ${
            $:curl -fSsL -w '%{filename_effective}'
            ($to ?? "-o$to" !! '-O') $self
        }
    }

    method redirect-url-->HTTP {
        ${ $:curl -Isw '%{redirect_url}' -o '/dev/null' $self }
    }

    method is-https? {
        $self.starts-with('https');
    }

    method query(Pair *@query) -->HTTP {
        my $query = (.key ~ '=' ~ .value for @query).join('&');
        if $self.contains('?') {
            $self ~ '&' ~ $query;
        } else {
            $self ~ '?' ~ $query;
        }
    }

    method request($method,
                   :@headers,
                   :$to,
                   :$max-redirects,
                   :$proxy,
                   Pair :@form,
                   JSON :$json) -->HTTP-response {
        my $headers = File.tmp;
        my $req-headers = File.tmp;
        debug "HTTP ==> $method $self", "\c[GLOBE WITH MERIDIANS]";
        my $response = ${
            # ✨ save all stderr output to $req-headers to be parsed later
            $:curl -svL !> $req-headers
            -X $method
            --max-redir ($max-redirects || 0)
            -D $headers
            -o ($to || File.tmp)
            ("-H$_" for ('Content-Type: application/json' if ~$json), @headers)
            ('--data-binary', $_ if ~$json)
            ("-x$_" if $proxy)
            ('--form-string', .key ~ '=' ~ .value for @form)
            -w (
                "headers\\t$headers\\n" ~
                "req-method\\t$method\\n" ~
                'local-ip\t%{local_ip}\n' ~
                'local-port\t%{local_port}\n' ~
                'remote-ip\t%{remote_ip}\n' ~
                'remote-port\t%{remote_port}\n' ~
                'remote-url\t%{url_effective}\n' ~
                'code\t%{http_code}\n' ~
                'content-type\t%{content_type}\n' ~
                'body-size\t%{size_download}\n' ~
                'body\t%{filename_effective}\n' ~
                'download-speed\t%{speed_download}\n' ~
                'total-time\t%{time_total}\n'
            )
            $self
        }-->HTTP-response;

        $headers.line-subst(/\r/,'',:g);
        $response.push: $headers.shift.${
            sed -r 's§HTTP/([^ ]+) [0-9]+ (.*)§http-version\t\1\nmessage\t\2\n§'
        };
        $req-headers.line-subst(/\r/,'',:g);
        # Keep only lines beggining with > (request header data)
        # and remove the >
        $req-headers.filter(/^> (.+)/,'\1');
        # Get rid of the GET request line
        $req-headers.shift;
        # Change the : to tabs to make it a List[Pair]
        $req-headers.line-subst(/: /,'\t',:g);
        $headers.line-subst(/: /,'\t');
        # Put the req-headers as an attribute of the response
        $response.push((:$req-headers));

        debug "HTTP <== {$response.code} {$response.message} $self", "\c[GLOBE WITH MERIDIANS]";
        $response;
    }
}

class HTTP-headers is List[Pair] {
    method content-type~ { $self<Content-Type> }
    method content-length~ { $self<Content-Length> }
    method content-encoding~ { $self<Content-Encoding> }
    method location~ { $self<Location> }
    method host~     { $self<Host> }
}

class HTTP-response is List[Pair] {

    method headers -->HTTP-headers { $self<headers>-->File.slurp-->HTTP-headers }
    method req-method~ { $self<req-method> }
    method req-headers -->HTTP-headers { $self<req-headers>-->File.slurp-->HTTP-headers }
    method body~ { .slurp if $self<body>-->File  }
    method remote-url -->HTTP   { $self<remote-url>   }
    method body-size+ { $self<body-size>-->Int }
    method json -->JSON { $self.body-->JSON }
    method code+ { $self<code>-->Int }

    method charset~ {
        $self.headers.content-type
        .${ sed -rn 's/.*charset\s*=\s*"?([^"\s;]+)"?/\1/pi' }.uc;
    }
    method message~ { $self<message> }
    method http-version~ { $self<http-version> }

    method is-success? { $self.code.starts-with(2) }
    method is-redirect? { $self.code.starts-with(3) }
    method is-client-error? { $self.code.starts-with(4) }
    method is-server-error? { $self.code.starts-with(5) }
    method is-error? { $self.code.matches(/^[45]/) }

    method ok^ {
        if $self.is-success {
            $self;
        } else {
            die "{$self.req-method} {$self.remote-url} {$self.code} {$self.message}\n" ~
              ($self.body-size > 100 ?? $self.body.substr(0,100) ~ '...' !! $self.body)
        }
    }
}

augment HTTP {
    method get~ {
        ${ $:curl -fSsL $self }
    }

    method add($path) -->HTTP {
        "$self/$path";
    }

    method get-file(:$to)-->File {
        ${
            $:curl -fSsL -w '%{filename_effective}'
            ($to ?? "-o$to" !! '-O') $self
        }
    }

    method redirect-url-->HTTP {
        ${ $:curl -Isw '%{redirect_url}' -o '/dev/null' $self }
    }

    method is-https? {
        $self.starts-with('https');
    }

    method query(Pair *@query) -->HTTP {
        my $query = (.key ~ '=' ~ .value for @query).join('&');
        if $self.contains('?') {
            $self ~ '&' ~ $query;
        } else {
            $self ~ '?' ~ $query;
        }
    }

    method request($method,
                   :@headers,
                   :$to,
                   :$max-redirects,
                   :$proxy,
                   Pair :@form,
                   JSON :$json) -->HTTP-response {
        my $headers = File.tmp;
        my $req-headers = File.tmp;
        debug "HTTP⟹$method $self";
        my $response = ${
            # ✨ save all stderr output to $req-headers to be parsed later
            $:curl -svL !> $req-headers
            -X $method
            --max-redir ($max-redirects || 0)
            -D $headers
            -o ($to || File.tmp)
            ("-H$_" for ('Content-Type: application/json' if ~$json), @headers)
            ('--data-binary', $_ if ~$json)
            ("-x$_" if $proxy)
            ('--form-string', .key ~ '=' ~ .value for @form)
            -w (
                "headers\\t$headers\\n" ~
                "req-method\\t$method\\n" ~
                'local-ip\t%{local_ip}\n' ~
                'local-port\t%{local_port}\n' ~
                'remote-ip\t%{remote_ip}\n' ~
                'remote-port\t%{remote_port}\n' ~
                'remote-url\t%{url_effective}\n' ~
                'code\t%{http_code}\n' ~
                'content-type\t%{content_type}\n' ~
                'body-size\t%{size_download}\n' ~
                'body\t%{filename_effective}\n' ~
                'download-speed\t%{speed_download}\n' ~
                'total-time\t%{time_total}\n'
            )
            $self
        }-->HTTP-response;

        $headers.line-subst(/\r/,'',:g);
        $response.push: $headers.shift.${
            sed -r 's§HTTP/([^ ]+) [0-9]+ (.*)§http-version\t\1\nmessage\t\2\n§'
        };
        $req-headers.line-subst(/\r/,'',:g);
        # Keep only lines beggining with > (request header data)
        # and remove the >
        $req-headers.filter(/^> (.+)/,'\1');
        # Get rid of the GET request line
        $req-headers.shift;
        # Change the : to tabs to make it a List[Pair]
        $req-headers.line-subst(/: /,'\t',:g);
        $headers.line-subst(/: /,'\t');
        # Put the req-headers as an attribute of the response
        $response.push((:$req-headers));

        debug "HTTP⟸{$response.code} {$response.message} $self ";
        $response;
    }
}