Version bump (1.0.0 → 1.1.0)

Version bump (1.0.0 → 1.1.0)

/**
 * Copyright © 2014 awk
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma semicolon 1

#define PLUGIN_VERSION "1.1.0"

#include <sourcemod>
#include <sdktools>
#include <tf2>
#include <tf2_stocks>
#include <smlib>

public Plugin:myinfo = {

  name        = "Arena: Respawn",
  author      = "awk",
  description = "A gamemode that gives Arena second chances.",
  version     = PLUGIN_VERSION,
  url         = "http://steam.respawn.tf/"

}

new Handle:cvar_first_blood = INVALID_HANDLE;
new Handle:cvar_invuln_time = INVALID_HANDLE;
new Handle:cvar_cap_time = INVALID_HANDLE;
new Handle:cvar_first_blood_threshold = INVALID_HANDLE;
new Handle:cvar_lms_minicrits = INVALID_HANDLE;
new Handle:cvar_logging = INVALID_HANDLE;
new Handle:cvar_force_arena = INVALID_HANDLE;

new cap_owner = 0;
new num_revived_players = 0;
new Float:last_cap_time = 0.0;
new bool:client_on_point[MAXPLAYERS+1] = { false, ... };
new bool:client_is_marked[MAXPLAYERS+1] = { false, ... };
new Handle:client_mark_timer[MAXPLAYERS+1] = { INVALID_HANDLE, ... };

// Hide these cvar changes, as they're handled by the plugin itself.
new String:quiet_cvars[][] = { "tf_arena_first_blood" };

// Preserve these variables across players' lives, but only if they're playing the same class.
new String:preserved_int_names[][] = { "m_iKillStreak", "m_iDecapitations", "m_iRevengeCrits" };
new preserved_ints[MAXPLAYERS+1][sizeof(preserved_int_names)];
new String:preserved_float_names[][] = { "m_flRageMeter", "m_flHypeMeter" };
new Float:preserved_floats[MAXPLAYERS+1][sizeof(preserved_float_names)];

// Sound that plays to players whose team has respawned (and neutral spectators).
new String:sound_friendly_cap[] = "mvm/mvm_revive.wav";

// Sound that plays to players when the enemy team has respawned.
new String:sound_enemy_cap[] = "misc/ks_tier_03_kill_01.wav";

// Storage for the last played class of each player.
new TFClassType:client_last_class[MAXPLAYERS+1] = { TFClass_Unknown, ... };

// Load Respawn-specific functions
#include <respawn>

public OnPluginStart() {

  HookEntityOutput("tf_gamerules", "OnWonByTeam1", OnWin);
  HookEntityOutput("tf_gamerules", "OnWonByTeam2", OnWin);
  HookEntityOutput("trigger_capture_area", "OnCapTeam1", OnTeamCapture);
  HookEntityOutput("trigger_capture_area", "OnCapTeam2", OnTeamCapture);
  HookEntityOutput("trigger_capture_area", "OnStartTouch", OnPointTouch);
  HookEntityOutput("trigger_capture_area", "OnEndTouch", OnPointTouch);

  HookEvent("server_cvar", SilenceConVarChange, EventHookMode_Pre);
  HookEvent("teamplay_round_start", OnRoundStart);
  HookEvent("teamplay_broadcast_audio", OnTeamAudio, EventHookMode_Pre);
  HookEvent("teamplay_point_captured", OnPointCaptured);
  HookEvent("arena_round_start", OnArenaStart);
  HookEvent("player_death", OnPlayerDeath);

  cvar_first_blood = FindConVar("tf_arena_first_blood");

  cvar_logging = CreateConVar("ars_log_enabled", "1",
    "1 to enable status messages in the console, 0 to disable.");

  cvar_force_arena = CreateConVar("ars_force_arena", "0",
    "(Experimental) Set to 1 to attempt to force any map into Arena mode.");

  cvar_invuln_time = CreateConVar("ars_invuln_time", "3.0",
    "How long to flash Uber on a newly respawned player.");

  cvar_cap_time = CreateConVar("ars_cap_time", "1.0",
    "Approximate amount of time (in seconds) it takes to capture the central control point.");

  cvar_first_blood_threshold = CreateConVar("ars_first_blood_threshold", "14",
    "Number of active players required to activate first blood (-1 to disable).");

  cvar_lms_minicrits = CreateConVar("ars_lms_minicrits", "8.0",
    "How many seconds of minicrits the Last Man Standing gets when a player on the opposing team dies or is killed.");

}

// Fired on map load or on plugin load/reload.
public OnMapStart() {

  // Load sounds
  PrecacheSound(sound_friendly_cap);
  PrecacheSound(sound_enemy_cap);

}

// Handle cleanup on map end
public OnMapEnd() {

  for (new i = 1; i <= MaxClients; i++) {
    client_mark_timer[i] = INVALID_HANDLE;
  }

}

// Fired when a client connects to the server.
public OnClientPostAdminCheck(client) {

  CreateTimer(1.0, Timer_Credits, client);

}

// Fired on a client leaving the server.
public OnClientDisconnect_Post(client) {

  client_last_class[client] = TFClass_Unknown;
  client_on_point[client] = false;
  client_is_marked[client] = false;

}

// Fired on teamplay_round_start.
public OnRoundStart(Handle:event, const String:name[], bool:hide_broadcast) {

  if (GetConVarInt(cvar_force_arena) > 0) {
    Game_ForceArenaMode();
  }

}

// Fired after the gates open in Arena.
public OnArenaStart(Handle:event, const String:name[], bool:hide_broadcast) {

  Game_RegeneratePlayers();
  Game_SetupCapPoints();
  Game_ResetRoundState();

  if (Game_CountCapPoints() == 1) {
    Game_CreateCapTimer();
  }

  // Clear all MFD in case anything is lingering (if a round suddenly restarted, for example).
  for (new i = 1; i <= MaxClients; i++) {
    if (IsValidClient(i) && (GetClientTeam(i) == _:TFTeam_Red || GetClientTeam(i) == _:TFTeam_Blue)) {
      TF2_RemoveCondition(i, TFCond_MarkedForDeath);
    }
  }

  new num_players = Game_CountActivePlayers();

  Log("Round started with %d players.", num_players);

  new fb_threshold = GetConVarInt(cvar_first_blood_threshold);

  if (num_players < 4) {
    SetConVarInt(cvar_first_blood, 0);
    Client_PrintToChatAll(false, "{G}1v1 rules active - capture the point for overheal!");
  } else if (num_players < fb_threshold || fb_threshold == -1) {
    SetConVarInt(cvar_first_blood, 0);
  } else {
    SetConVarInt(cvar_first_blood, 1);
    Client_PrintToChatAll(false, "{G}First blood critboost enabled!");
  }

}

// Fired by a trigger_capture_area on team capture.
public OnTeamCapture(const String:output[], caller, activator, Float:delay) {

  // Set the last capture time.
  last_cap_time = GetGameTime();

  // Determine the team that triggered this capture.
  new team = 0;
  if (StrEqual(output, "OnCapTeam1")) {
    team = _:TFTeam_Red;
  } else if (StrEqual(output, "OnCapTeam2")) {
    team = _:TFTeam_Blue;
  }

  if (Game_CountCapPoints() > 1 || cap_owner != team) {
    Log("Point captured by team #%d", team);
    cap_owner = team;

    num_revived_players = 0;
    for (new i = 1; i <= MaxClients ; i++) {
      if (IsValidClient(i)) {
        if (IsClientObserver(i) && GetClientTeam(i) == team) {
          Player_RespawnWithEffects(i);
          num_revived_players++;
        }
      }
    }
  } else {
    Log("Point re-captured by team #%d", team);

    new cap_master = FindEntityByClassname(-1, "team_control_point_master");
    if (IsValidEntity(cap_master)) {
      SetVariantInt(team);
      AcceptEntityInput(cap_master, "SetWinner");
    } else {
      Log("cap_master is not a valid entity (%d).", cap_master);
    }
  }

  // Reset everyone's death marks.
  for (new i = 1; i <= MaxClients; i++) {
    if (IsValidClient(i) && client_is_marked[i]) {
      TF2_RemoveCondition(i, TFCond_MarkedForDeath);
      client_is_marked[i] = false;
    }
  }

}

public OnWin(const String:output[], caller, activator, Float:delay) {

  Game_ResetRoundState();

}

// Fired by a trigger_capture_area when a player starts or stops coming into contact with it.
public OnPointTouch(const String:output[], caller, activator, Float:delay) {

  // Ignore if the activator wasn't a client.
  if (!IsValidClient(activator)) {
    return;
  }

  // Ignore if the classname of what was touched is not a trigger_capture_area.
  if (!Entity_ClassNameMatches(caller, "trigger_capture_area")) {
    return;
  }

  // Set the state of the player to be touching or not touching the point.
  if (StrEqual(output, "OnEndTouch")) {
    client_on_point[activator] = false;
  } else {
    client_on_point[activator] = true;
  }

  // Don't bother marking anyone for death if there's more than one point.
  if (Game_CountCapPoints() > 1) {
    return;
  }

  new control_point = CapArea_GetControlPoint(caller);

  if (control_point > 0) {

    // Is the player a member of the team that owns this control point?
    if (GetClientTeam(activator) == Objective_GetPointOwner(GetEntProp(control_point, Prop_Data, "m_iPointIndex"))) {

      // Note: Always reset the timer state!
      if (client_mark_timer[activator] != INVALID_HANDLE) {
        KillTimer(client_mark_timer[activator]);
        client_mark_timer[activator] = INVALID_HANDLE;
      }

      // If so, and they've started touching the point, schedule a death mark.
      if (client_on_point[activator]) {
        new Float:time = (last_cap_time + 2.0) - GetGameTime();
        if (time < 0.0) {
          time = 0.0;
        }
        client_mark_timer[activator] = CreateTimer(time, Timer_Add_Mark, activator, TIMER_FLAG_NO_MAPCHANGE);

      // Otherwise, schedule a death mark removal if they're coming off of the point.
      } else {
        client_mark_timer[activator] = CreateTimer(2.0, Timer_Remove_Mark, activator, TIMER_FLAG_NO_MAPCHANGE);
      }

    }

  } else {

    // If this message appears, the map Respawn is running on could probably do with some special Stripper rules.
    Log("Client %d touched trigger_capture_area not associated with a control point.");

  }

}

// Fired as a netevent when a point has been successfully captured.
// Unlike the team_control_point output, this allows us access to the players who captured the point.
public OnPointCaptured(Handle:event, const String:name[], bool:hide_broadcast) {

  new String:cap_message[256];
  new String:player_name[64];
  new String:player_color[3];

  // Get the players who captured the point.
  new String:cappers[64];
  GetEventString(event, "cappers", cappers, sizeof(cappers));

  // Determine which color to use for highlighting player names in chat.
  new team = GetEventInt(event, "team");

  switch(team) {

    case (_:TFTeam_Red): {
      player_color = "{R}";
    }
    case (_:TFTeam_Blue): {
      player_color = "{B}";
    }
    default: {
      player_color = "{N}";
    }

  }

  for (new i = 0; i < strlen(cappers); i++) {

    new player = cappers[i];
    if (IsValidClient(player)) {

      if (Game_CountCapPoints() == 1) {
        // If this is the only point, mark the capturing players for death.
        if (client_mark_timer[player] != INVALID_HANDLE) {
          KillTimer(client_mark_timer[player]);
          client_mark_timer[player] = INVALID_HANDLE;
        }
        client_mark_timer[player] = CreateTimer(2.0, Timer_Add_Mark, player, TIMER_FLAG_NO_MAPCHANGE);
      }

      // If this is 1v1, overheal the capturing player.
      if (Game_CountActivePlayers() < 4) {
        new Float:health = float(Entity_GetMaxHealth(player));
        Entity_SetHealth(player, RoundToFloor(health * 1.5), true);
      }

      // Add the player name (team-colored) to the capture message.
      GetClientName(player, player_name, sizeof(player_name));
      StrCat(cap_message, sizeof(cap_message), player_color);
      StrCat(cap_message, sizeof(cap_message), player_name);
      if (i != strlen(cappers) - 1) {
        StrCat(cap_message, sizeof(cap_message), ", ");
      }

    }

  }

  // Announce the number of players respawned.
  new String:players_str[16] = "players!";
  if (num_revived_players == 1) {
    players_str = "player!";
  }

  new String:revived_message[64];
  Format(revived_message, sizeof(revived_message), "{N} revived %d %s", num_revived_players, players_str);

  StrCat(cap_message, sizeof(cap_message), revived_message);

  // Only announce and play sounds if at least one player was respawned.
  if (num_revived_players > 0) {
    Client_PrintToChatAll(false, cap_message);

    new enemy_team = Team_EnemyTeam(team);
    Team_EmitSound(_:TFTeam_Unassigned, sound_friendly_cap);
    Team_EmitSound(_:TFTeam_Spectator,  sound_friendly_cap);
    Team_EmitSound(team,                sound_friendly_cap);
    Team_EmitSound(enemy_team,          sound_enemy_cap);
  }

}

// Fired when a player dies or activates their Dead Ringer.
public OnPlayerDeath(Handle:event, const String:name[], bool:hide_broadcast) {

  new victim = GetClientOfUserId(GetEventInt(event, "userid"));
  new team = GetClientTeam(victim);
  new flags = GetEventInt(event, "death_flags");

  // Nobody cares about dead ringers.
  if(!IsValidClient(victim) || flags & 32) {
    return;
  }

  // Store information about the player's state for use upon respawn.
  client_last_class[victim] = TF2_GetPlayerClass(victim);
  for (new i = 0; i < sizeof(preserved_int_names); i++) {
    preserved_ints[victim][i] = GetEntProp(victim, Prop_Send, preserved_int_names[i]);
  }
  for (new i = 0; i < sizeof(preserved_float_names); i++) {
    preserved_floats[victim][i] = GetEntPropFloat(victim, Prop_Send, preserved_float_names[i]);
  }

  // If the opposing team has only one player remaining, grant that opposing player a five-second minicrit boost.
  new enemy_team = Team_EnemyTeam(team);
  new Float:minicrit_time = GetConVarFloat(cvar_lms_minicrits);
  if (minicrit_time > 0.0 && Team_CountAlivePlayers(enemy_team) == 1) {
    for (new i = 1; i <= MaxClients; i++) {
      if (IsValidClient(i) && IsPlayerAlive(i) && GetClientTeam(i) == enemy_team) {
        TF2_AddCondition(i, TFCond_CritCola, minicrit_time);
      }
    }
  }

  // If this team has only one player remaining, play a special voice response.
  if (Team_CountAlivePlayers(team) == 2) {

    Team_PlayVO(team, "Announcer.AM_LastManAlive0%d", GetRandomInt(1,4));

  }

}

public Action:OnTeamAudio(Handle:event, const String:name[], bool:hide_broadcast) {

  new String:vo[64];
  GetEventString(event, "sound", vo, sizeof(vo));

  new team = GetEventInt(event, "team");

  // Special (disappointed) response on a team that lost on a single-cap map with players left alive.
  if (StrEqual(vo, "Game.YourTeamLost") && Team_CountAlivePlayers(team) > 0 && Game_CountCapPoints() == 1) {

    Team_PlayVO(team, "Announcer.AM_LastManForfeit0%d", GetRandomInt(1,4));
    return Plugin_Handled;

  }

  return Plugin_Continue;

}

// Intercepts configuration variable changes and silences some of them.
public Action:SilenceConVarChange(Handle:event, String:name[], bool:hide_broadcast) {

  new String:cvar_name[64];
  GetEventString(event, "cvarname", cvar_name, sizeof(cvar_name));

  for (new i = 0; i < sizeof(quiet_cvars); i++) {
    if (StrEqual(quiet_cvars[i], cvar_name)) {
      SetEventBroadcast(event, true);
      return Plugin_Changed;
    }
  }

  return Plugin_Continue;

}

// Prints a prefixed log to the console.
public Log(const String:to_log[], any:...) {

  if (GetConVarInt(cvar_logging) != 1) {
    return;
  }

  new String:prefixed_string[512];
  new String:log_string[512];
  prefixed_string = "[Arena: Respawn] ";
  StrCat(prefixed_string, sizeof(prefixed_string), to_log);
  VFormat(log_string, sizeof(log_string), prefixed_string, 2);

  PrintToServer(log_string);

}

/**
 * Copyright © 2014 awk
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma semicolon 1

#define PLUGIN_VERSION "1.0.0"

#include <sourcemod>
#include <sdktools>
#include <tf2>
#include <tf2_stocks>
#include <smlib>

public Plugin:myinfo = {

  name        = "Arena: Respawn",
  author      = "awk",
  description = "A gamemode that gives Arena second chances.",
  version     = PLUGIN_VERSION,
  url         = "http://steam.respawn.tf/"

}

new Handle:cvar_first_blood = INVALID_HANDLE;
new Handle:cvar_invuln_time = INVALID_HANDLE;
new Handle:cvar_cap_time = INVALID_HANDLE;
new Handle:cvar_first_blood_threshold = INVALID_HANDLE;
new Handle:cvar_lms_minicrits = INVALID_HANDLE;
new Handle:cvar_logging = INVALID_HANDLE;
new Handle:cvar_force_arena = INVALID_HANDLE;

new cap_owner = 0;
new num_revived_players = 0;
new Float:last_cap_time = 0.0;
new bool:client_on_point[MAXPLAYERS+1] = { false, ... };
new bool:client_is_marked[MAXPLAYERS+1] = { false, ... };
new Handle:client_mark_timer[MAXPLAYERS+1] = { INVALID_HANDLE, ... };

// Hide these cvar changes, as they're handled by the plugin itself.
new String:quiet_cvars[][] = { "tf_arena_first_blood" };

// Preserve these variables across players' lives, but only if they're playing the same class.
new String:preserved_int_names[][] = { "m_iKillStreak", "m_iDecapitations", "m_iRevengeCrits" };
new preserved_ints[MAXPLAYERS+1][sizeof(preserved_int_names)];
new String:preserved_float_names[][] = { "m_flRageMeter", "m_flHypeMeter" };
new Float:preserved_floats[MAXPLAYERS+1][sizeof(preserved_float_names)];

// Sound that plays to players whose team has respawned (and neutral spectators).
new String:sound_friendly_cap[] = "mvm/mvm_revive.wav";

// Sound that plays to players when the enemy team has respawned.
new String:sound_enemy_cap[] = "misc/ks_tier_03_kill_01.wav";

// Storage for the last played class of each player.
new TFClassType:client_last_class[MAXPLAYERS+1] = { TFClass_Unknown, ... };

// Load Respawn-specific functions
#include <respawn>

public OnPluginStart() {

  HookEntityOutput("tf_gamerules", "OnWonByTeam1", OnWin);
  HookEntityOutput("tf_gamerules", "OnWonByTeam2", OnWin);
  HookEntityOutput("trigger_capture_area", "OnCapTeam1", OnTeamCapture);
  HookEntityOutput("trigger_capture_area", "OnCapTeam2", OnTeamCapture);
  HookEntityOutput("trigger_capture_area", "OnStartTouch", OnPointTouch);
  HookEntityOutput("trigger_capture_area", "OnEndTouch", OnPointTouch);

  HookEvent("server_cvar", SilenceConVarChange, EventHookMode_Pre);
  HookEvent("teamplay_round_start", OnRoundStart);
  HookEvent("teamplay_broadcast_audio", OnTeamAudio, EventHookMode_Pre);
  HookEvent("teamplay_point_captured", OnPointCaptured);
  HookEvent("arena_round_start", OnArenaStart);
  HookEvent("player_death", OnPlayerDeath);

  cvar_first_blood = FindConVar("tf_arena_first_blood");

  cvar_logging = CreateConVar("ars_log_enabled", "1",
    "1 to enable status messages in the console, 0 to disable.");

  cvar_force_arena = CreateConVar("ars_force_arena", "0",
    "(Experimental) Set to 1 to attempt to force any map into Arena mode.");

  cvar_invuln_time = CreateConVar("ars_invuln_time", "3.0",
    "How long to flash Uber on a newly respawned player.");

  cvar_cap_time = CreateConVar("ars_cap_time", "1.0",
    "Approximate amount of time (in seconds) it takes to capture the central control point.");

  cvar_first_blood_threshold = CreateConVar("ars_first_blood_threshold", "14",
    "Number of active players required to activate first blood (-1 to disable).");

  cvar_lms_minicrits = CreateConVar("ars_lms_minicrits", "8.0",
    "How many seconds of minicrits the Last Man Standing gets when a player on the opposing team dies or is killed.");

}

// Fired on map load or on plugin load/reload.
public OnMapStart() {

  // Load sounds
  PrecacheSound(sound_friendly_cap);
  PrecacheSound(sound_enemy_cap);

}

// Handle cleanup on map end
public OnMapEnd() {

  for (new i = 1; i <= MaxClients; i++) {
    client_mark_timer[i] = INVALID_HANDLE;
  }

}

// Fired when a client connects to the server.
public OnClientPostAdminCheck(client) {

  CreateTimer(1.0, Timer_Credits, client);

}

// Fired on a client leaving the server.
public OnClientDisconnect_Post(client) {

  client_last_class[client] = TFClass_Unknown;
  client_on_point[client] = false;
  client_is_marked[client] = false;

}

// Fired on teamplay_round_start.
public OnRoundStart(Handle:event, const String:name[], bool:hide_broadcast) {

  if (GetConVarInt(cvar_force_arena) > 0) {
    Game_ForceArenaMode();
  }

}

// Fired after the gates open in Arena.
public OnArenaStart(Handle:event, const String:name[], bool:hide_broadcast) {

  Game_RegeneratePlayers();
  Game_SetupCapPoints();
  Game_ResetRoundState();

  if (Game_CountCapPoints() == 1) {
    Game_CreateCapTimer();
  }

  // Clear all MFD in case anything is lingering (if a round suddenly restarted, for example).
  for (new i = 1; i <= MaxClients; i++) {
    if (IsValidClient(i) && (GetClientTeam(i) == _:TFTeam_Red || GetClientTeam(i) == _:TFTeam_Blue)) {
      TF2_RemoveCondition(i, TFCond_MarkedForDeath);
    }
  }

  new num_players = Game_CountActivePlayers();

  Log("Round started with %d players.", num_players);

  new fb_threshold = GetConVarInt(cvar_first_blood_threshold);

  if (num_players < 4) {
    SetConVarInt(cvar_first_blood, 0);
    Client_PrintToChatAll(false, "{G}1v1 rules active - capture the point for overheal!");
  } else if (num_players < fb_threshold || fb_threshold == -1) {
    SetConVarInt(cvar_first_blood, 0);
  } else {
    SetConVarInt(cvar_first_blood, 1);
    Client_PrintToChatAll(false, "{G}First blood critboost enabled!");
  }

}

// Fired by a trigger_capture_area on team capture.
public OnTeamCapture(const String:output[], caller, activator, Float:delay) {

  // Set the last capture time.
  last_cap_time = GetGameTime();

  // Determine the team that triggered this capture.
  new team = 0;
  if (StrEqual(output, "OnCapTeam1")) {
    team = _:TFTeam_Red;
  } else if (StrEqual(output, "OnCapTeam2")) {
    team = _:TFTeam_Blue;
  }

  if (Game_CountCapPoints() > 1 || cap_owner != team) {
    Log("Point captured by team #%d", team);
    cap_owner = team;

    num_revived_players = 0;
    for (new i = 1; i <= MaxClients ; i++) {
      if (IsValidClient(i)) {
        if (IsClientObserver(i) && GetClientTeam(i) == team) {
          Player_RespawnWithEffects(i);
          num_revived_players++;
        }
      }
    }
  } else {
    Log("Point re-captured by team #%d", team);

    new cap_master = FindEntityByClassname(-1, "team_control_point_master");
    if (IsValidEntity(cap_master)) {
      SetVariantInt(team);
      AcceptEntityInput(cap_master, "SetWinner");
    } else {
      Log("cap_master is not a valid entity (%d).", cap_master);
    }
  }

  // Reset everyone's death marks.
  for (new i = 1; i <= MaxClients; i++) {
    if (IsValidClient(i) && client_is_marked[i]) {
      TF2_RemoveCondition(i, TFCond_MarkedForDeath);
      client_is_marked[i] = false;
    }
  }

}

public OnWin(const String:output[], caller, activator, Float:delay) {

  Game_ResetRoundState();

}

// Fired by a trigger_capture_area when a player starts or stops coming into contact with it.
public OnPointTouch(const String:output[], caller, activator, Float:delay) {

  // Ignore if the activator wasn't a client.
  if (!IsValidClient(activator)) {
    return;
  }

  // Ignore if the classname of what was touched is not a trigger_capture_area.
  if (!Entity_ClassNameMatches(caller, "trigger_capture_area")) {
    return;
  }

  // Set the state of the player to be touching or not touching the point.
  if (StrEqual(output, "OnEndTouch")) {
    client_on_point[activator] = false;
  } else {
    client_on_point[activator] = true;
  }

  // Don't bother marking anyone for death if there's more than one point.
  if (Game_CountCapPoints() > 1) {
    return;
  }

  new control_point = CapArea_GetControlPoint(caller);

  if (control_point > 0) {

    // Is the player a member of the team that owns this control point?
    if (GetClientTeam(activator) == Objective_GetPointOwner(GetEntProp(control_point, Prop_Data, "m_iPointIndex"))) {

      // Note: Always reset the timer state!
      if (client_mark_timer[activator] != INVALID_HANDLE) {
        KillTimer(client_mark_timer[activator]);
        client_mark_timer[activator] = INVALID_HANDLE;
      }

      // If so, and they've started touching the point, schedule a death mark.
      if (client_on_point[activator]) {
        new Float:time = (last_cap_time + 2.0) - GetGameTime();
        if (time < 0.0) {
          time = 0.0;
        }
        client_mark_timer[activator] = CreateTimer(time, Timer_Add_Mark, activator, TIMER_FLAG_NO_MAPCHANGE);

      // Otherwise, schedule a death mark removal if they're coming off of the point.
      } else {
        client_mark_timer[activator] = CreateTimer(2.0, Timer_Remove_Mark, activator, TIMER_FLAG_NO_MAPCHANGE);
      }

    }

  } else {

    // If this message appears, the map Respawn is running on could probably do with some special Stripper rules.
    Log("Client %d touched trigger_capture_area not associated with a control point.");

  }

}

// Fired as a netevent when a point has been successfully captured.
// Unlike the team_control_point output, this allows us access to the players who captured the point.
public OnPointCaptured(Handle:event, const String:name[], bool:hide_broadcast) {

  new String:cap_message[256];
  new String:player_name[64];
  new String:player_color[3];

  // Get the players who captured the point.
  new String:cappers[64];
  GetEventString(event, "cappers", cappers, sizeof(cappers));

  // Determine which color to use for highlighting player names in chat.
  new team = GetEventInt(event, "team");

  switch(team) {

    case (_:TFTeam_Red): {
      player_color = "{R}";
    }
    case (_:TFTeam_Blue): {
      player_color = "{B}";
    }
    default: {
      player_color = "{N}";
    }

  }

  for (new i = 0; i < strlen(cappers); i++) {

    new player = cappers[i];
    if (IsValidClient(player)) {

      if (Game_CountCapPoints() == 1) {
        // If this is the only point, mark the capturing players for death.
        if (client_mark_timer[player] != INVALID_HANDLE) {
          KillTimer(client_mark_timer[player]);
          client_mark_timer[player] = INVALID_HANDLE;
        }
        client_mark_timer[player] = CreateTimer(2.0, Timer_Add_Mark, player, TIMER_FLAG_NO_MAPCHANGE);
      }

      // If this is 1v1, overheal the capturing player.
      if (Game_CountActivePlayers() < 4) {
        new Float:health = float(Entity_GetMaxHealth(player));
        Entity_SetHealth(player, RoundToFloor(health * 1.5), true);
      }

      // Add the player name (team-colored) to the capture message.
      GetClientName(player, player_name, sizeof(player_name));
      StrCat(cap_message, sizeof(cap_message), player_color);
      StrCat(cap_message, sizeof(cap_message), player_name);
      if (i != strlen(cappers) - 1) {
        StrCat(cap_message, sizeof(cap_message), ", ");
      }

    }

  }

  // Announce the number of players respawned.
  new String:players_str[16] = "players!";
  if (num_revived_players == 1) {
    players_str = "player!";
  }

  new String:revived_message[64];
  Format(revived_message, sizeof(revived_message), "{N} revived %d %s", num_revived_players, players_str);

  StrCat(cap_message, sizeof(cap_message), revived_message);

  // Only announce and play sounds if at least one player was respawned.
  if (num_revived_players > 0) {
    Client_PrintToChatAll(false, cap_message);

    new enemy_team = Team_EnemyTeam(team);
    Team_EmitSound(_:TFTeam_Unassigned, sound_friendly_cap);
    Team_EmitSound(_:TFTeam_Spectator,  sound_friendly_cap);
    Team_EmitSound(team,                sound_friendly_cap);
    Team_EmitSound(enemy_team,          sound_enemy_cap);
  }

}

// Fired when a player dies or activates their Dead Ringer.
public OnPlayerDeath(Handle:event, const String:name[], bool:hide_broadcast) {

  new victim = GetClientOfUserId(GetEventInt(event, "userid"));
  new team = GetClientTeam(victim);
  new flags = GetEventInt(event, "death_flags");

  // Nobody cares about dead ringers.
  if(!IsValidClient(victim) || flags & 32) {
    return;
  }

  // Store information about the player's state for use upon respawn.
  client_last_class[victim] = TF2_GetPlayerClass(victim);
  for (new i = 0; i < sizeof(preserved_int_names); i++) {
    preserved_ints[victim][i] = GetEntProp(victim, Prop_Send, preserved_int_names[i]);
  }
  for (new i = 0; i < sizeof(preserved_float_names); i++) {
    preserved_floats[victim][i] = GetEntPropFloat(victim, Prop_Send, preserved_float_names[i]);
  }

  // If the opposing team has only one player remaining, grant that opposing player a five-second minicrit boost.
  new enemy_team = Team_EnemyTeam(team);
  new Float:minicrit_time = GetConVarFloat(cvar_lms_minicrits);
  if (minicrit_time > 0.0 && Team_CountAlivePlayers(enemy_team) == 1) {
    for (new i = 1; i <= MaxClients; i++) {
      if (IsValidClient(i) && IsPlayerAlive(i) && GetClientTeam(i) == enemy_team) {
        TF2_AddCondition(i, TFCond_CritCola, minicrit_time);
      }
    }
  }

  // If this team has only one player remaining, play a special voice response.
  if (Team_CountAlivePlayers(team) == 2) {

    Team_PlayVO(team, "Announcer.AM_LastManAlive0%d", GetRandomInt(1,4));

  }

}

public Action:OnTeamAudio(Handle:event, const String:name[], bool:hide_broadcast) {

  new String:vo[64];
  GetEventString(event, "sound", vo, sizeof(vo));

  new team = GetEventInt(event, "team");

  // Special (disappointed) response on a team that lost on a single-cap map with players left alive.
  if (StrEqual(vo, "Game.YourTeamLost") && Team_CountAlivePlayers(team) > 0 && Game_CountCapPoints() == 1) {

    Team_PlayVO(team, "Announcer.AM_LastManForfeit0%d", GetRandomInt(1,4));
    return Plugin_Handled;

  }

  return Plugin_Continue;

}

// Intercepts configuration variable changes and silences some of them.
public Action:SilenceConVarChange(Handle:event, String:name[], bool:hide_broadcast) {

  new String:cvar_name[64];
  GetEventString(event, "cvarname", cvar_name, sizeof(cvar_name));

  for (new i = 0; i < sizeof(quiet_cvars); i++) {
    if (StrEqual(quiet_cvars[i], cvar_name)) {
      SetEventBroadcast(event, true);
      return Plugin_Changed;
    }
  }

  return Plugin_Continue;

}

// Prints a prefixed log to the console.
public Log(const String:to_log[], any:...) {

  if (GetConVarInt(cvar_logging) != 1) {
    return;
  }

  new String:prefixed_string[512];
  new String:log_string[512];
  prefixed_string = "[Arena: Respawn] ";
  StrCat(prefixed_string, sizeof(prefixed_string), to_log);
  VFormat(log_string, sizeof(log_string), prefixed_string, 2);

  PrintToServer(log_string);

}


Update FireSupport.sp

Update FireSupport.sp

//(C) 2020 rrrfffrrr <rrrfffrrr@naver.com>

#pragma semicolon 1
#pragma newdecls required

public Plugin myinfo = {
	name		= "[INS] Test",
	author		= "rrrfffrrr",
	description	= "Test",
	version		= "0.0.1",
	url			= ""
};

#include <sourcemod>
#include <entity>
#include <sdktools_functions>
#include <sdktools_entinput>
#include <sdktools_trace>
#include <sdktools>

#define UPGRADE_SCOPE 0
#define UPGRADE_PRIMARY_AMMO 1
#define UPGRADE_MAGAZINE 2
#define UPGRADE_BARREL 3
#define UPGRADE_SIDERAIL 5
#define UPGRADE_UNDERBARREL 6
#define UPGRADE_STOCK 0		// need to check value
#define UPGRADE_AESTHETIC 0	// need to check value
#define UPGRADE_SOMETHING 0	// need to check what it is

float UP_VECTOR[3] = {-90.0, 0.0, 0.0};
float DOWN_VECTOR[3] = {90.0, 0.0, 0.0};

int g_BeamSprite;

Handle c_gameconfig;
Handle f_CreateRocket;
Handle f_UpdateRocket;

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

	StartPrepSDKCall(SDKCall_Static);
	PrepSDKCall_SetFromConf(c_gameconfig, SDKConf_Signature, "CBaseRocketMissile::CreateRocketMissile");
	PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);
	PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
	PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
	PrepSDKCall_AddParameter(SDKType_QAngle, SDKPass_ByRef);
	PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_ByValue);
	f_CreateRocket = EndPrepSDKCall();
	if (f_CreateRocket == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CBaseRocketMissile::CreateRocketMissile");
	}
	StartPrepSDKCall(SDKCall_Entity);
	PrepSDKCall_SetFromConf(c_gameconfig, SDKConf_Signature, "CINSWeaponRocketBase::UpdateRocketBodygroup");
	f_UpdateRocket = EndPrepSDKCall();
	if (f_UpdateRocket == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CINSWeaponRocketBase::UpdateRocketBodygroup");
	}

	RegAdminCmd("sm_upgrade", fffu, 0);
	RegAdminCmd("sm_weapons", fff, 0);
	RegAdminCmd("sm_ammos", fffa, 0);
	RegAdminCmd("sm_gears", fffg, 0);
}

public void OnMapStart() {
	g_BeamSprite = PrecacheModel("sprites/laserbeam.vmt");
	PrecacheModel("models/weapons/w_rpg7_projectile.mdl");
}

Action fffu(int client, int args) {
	float ground[3];
	float sky[3];
	if (GetAimPos(client, ground)) {
		if (GetSkyPos(client, ground, sky)) {
			ground[2] += 20.0;
			sky[2] -= 20.0;
			TE_SetupBeamPoints(ground, sky, g_BeamSprite, 0, 0, 1, 30.0, 1.0, 0.0, 5, 0.0, {255, 0, 0, 255}, 10);
			TE_SendToAll();
			TE_SetupBeamRingPoint(ground, 500.0, 0.0, g_BeamSprite, 0, 0, 1, 30.0, 5.0, 0.0, {255, 0, 0, 255}, 10, 0);
			TE_SendToAll();
			SDKCall(f_CreateRocket, client, "rocket_rpg7", sky, DOWN_VECTOR);
		}
	}
	return Plugin_Handled;
}

//	PrintToChat(client, "%d", GetEntProp(client, Prop_Send, "m_nClassTemplateHandle"));
Action fff(int client, int args) {
	int ent = -1;
	int att = -1;
	char weaponname[64];
	char classname[64];
	int chamber = 0;
	for(int i = 0; i <= 47; ++i) {
		ent = GetEntPropEnt(client, Prop_Send, "m_hMyWeapons", i);
		if (ent != -1) {
			GetEdictClassname(ent, weaponname, sizeof(weaponname));
			GetEntityClassname(ent, classname, sizeof(classname));
			if (HasEntProp(ent, Prop_Send, "m_bChamberedRound")) {
				chamber = GetEntProp(ent, Prop_Send, "m_bChamberedRound");
			} else {
				chamber = 0;
			}
			PrintToChat(client, "%d: %d - %s(%s)\nClip1(Chamber, type): %d(%d, %d)", i, ent, weaponname, classname, GetEntProp(ent, Prop_Send, "m_iClip1"), chamber, GetEntProp(ent, Prop_Send, "m_iPrimaryAmmoType"));
			for(int j = 0; j <= 8; ++j) {
				att = GetEntProp(ent, Prop_Send, "m_upgradeSlots", 1, j);
				if (att != 255) {
					PrintToChat(client, "-- %d: %d, %d, %d", j, att, EntIndexToEntRef(att), EntRefToEntIndex(att));
				}
			}

			//int t = GetEntProp(ent, Prop_Send, "m_hWeaponDefinitionHandle");
			//	PrintToChat(client, "m_hWeaponDefinitionHandle: %d", t);
		}
	}

	return Plugin_Handled;
}

Action fffa(int client, int args) {
	int ent = -1;
	for(int i = 0; i <= 255; ++i) {
		ent = GetEntProp(client, Prop_Send, "m_iAmmo", 2, i);
		if (ent != 255) {
			PrintToChat(client, "%d: %d", i, ent);
		}
	}
	return Plugin_Handled;
}

Action fffg(int client, int args) {
	int ent = -1;
	for(int i = 0; i <= 6; ++i) {
		ent = GetEntProp(client, Prop_Send, "m_EquippedGear", 1, i);
		if (ent != 255) {
			PrintToChat(client, "%d: %d", i, ent);
		}
	}
	return Plugin_Handled;
}

bool GetAimPos(int client, float vec[3]) {
	float pos[3];
	float dir[3];
	GetClientEyePosition(client, pos);
	GetClientEyeAngles(client, dir);
	Handle ray = TR_TraceRayFilterEx(pos, dir, MASK_SOLID_BRUSHONLY, RayType_Infinite, TraceWorldOnly, client);

	if (TR_DidHit(ray)) {
		TR_GetEndPosition(vec, ray);
		CloseHandle(ray);
		return true;
	}

	CloseHandle(ray);
	return false;
}

bool GetSkyPos(int client, float pos[3], float vec[3]) {
	Handle ray = TR_TraceRayFilterEx(pos, UP_VECTOR, MASK_SOLID_BRUSHONLY, RayType_Infinite, TraceWorldOnly, client);

	if (TR_DidHit(ray)) {
		char surface[64];
		TR_GetSurfaceName(ray, surface, sizeof(surface));
		if (StrEqual(surface, "TOOLS/TOOLSSKYBOX", false)) {
			TR_GetEndPosition(vec, ray);
			CloseHandle(ray);
			return true;
		}
	}

	CloseHandle(ray);
	return false;
}

public bool TraceWorldOnly(int entity, int mask, any data) {
	if(entity == data || entity > 0)
		return false;
	return true;
}

//(C) 2020 rrrfffrrr <rrrfffrrr@naver.com>

#pragma semicolon 1
#pragma newdecls required

public Plugin myinfo = {
	name		= "[INS] Fire support",
	author		= "rrrfffrrr",
	description	= "Fire support",
	version		= "1.1.0",
	url			= ""
};

#include <sourcemod>
#include <datapack>
#include <float>
#include <sdktools>
#include <sdktools_trace>
#include <sdktools_functions>
#include <timers>

const int TEAM_SPECTATE = 1;
const int TEAM_SECURITY = 2;
const int TEAM_INSURGENT = 3;

const float MATH_PI = 3.14159265359;

float UP_VECTOR[3] = {-90.0, 0.0, 0.0};
float DOWN_VECTOR[3] = {90.0, 0.0, 0.0};

Handle cGameConfig;
Handle fCreateRocket;
// Need signature below
//"CBaseRocketMissile::CreateRocketMissile"
//{
//	"library"	"server"
//	"windows"	"\x55\x8B\xEC\x83\xEC\x28\x53\x8B\x5D\x08"
//    "linux"		"@_ZN18CBaseRocketMissile19CreateRocketMissileEP11CBasePlayerPKcRK6VectorRK6QAngle"
//}

int gBeamSprite;

ConVar gCvarMaxSpread;
ConVar gCvarRound;
ConVar gCvarDelay;
ConVar gCvarDelayNextSupport;
ConVar gCvarClass;
ConVar gCvarCountPerRound;
ConVar gCvarEnableCmd;
ConVar gCvarEnableWeapon;
ConVar gCvarWeapon;

bool IsEnabled[MAXPLAYERS + 1];
bool IsEnabledTeam[4];
int CountAvailableSupport[4];

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

	StartPrepSDKCall(SDKCall_Static);
	PrepSDKCall_SetFromConf(cGameConfig, SDKConf_Signature, "CBaseRocketMissile::CreateRocketMissile");
	PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);
	PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
	PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
	PrepSDKCall_AddParameter(SDKType_QAngle, SDKPass_ByRef);
	PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_ByValue);
	fCreateRocket = EndPrepSDKCall();
	if (fCreateRocket == INVALID_HANDLE) {
		SetFailState("Fatal Error: Unable to find CBaseRocketMissile::CreateRocketMissile");
	}

	gCvarMaxSpread = CreateConVar("sm_firesupport_spread", "800.0", "Max spread.", FCVAR_PROTECTED, true, 10.0);
	gCvarRound = CreateConVar("sm_firesupport_shell_num", "20.0", "Shells to fire.", FCVAR_PROTECTED, true, 1.0);
	gCvarDelay = CreateConVar("sm_firesupport_delay", "10.0", "Min delay to first shell.", FCVAR_PROTECTED, true, 1.0);
	gCvarDelayNextSupport = CreateConVar("sm_firesupport_delay_support", "60.0", "Min delay to next support.", FCVAR_PROTECTED, true, 1.0);
	gCvarClass = CreateConVar("sm_firesupport_class", "template_recon_security_coop", "Set fire support specialist class.", FCVAR_PROTECTED);
	gCvarCountPerRound = CreateConVar("sm_firesupport_count", "1", "Count of available support per rounds(0 = disable)", FCVAR_PROTECTED, true, 0.0);
	gCvarEnableCmd = CreateConVar("sm_firesupport_enable_cmd", "0", "Player can call fire support using sm_firesupport_call.", FCVAR_PROTECTED);
	gCvarEnableWeapon = CreateConVar("sm_firesupport_enable_weapon", "1", "Player can call fire support using weapon.", FCVAR_PROTECTED);
	gCvarWeapon = CreateConVar("sm_firesupport_weapon", "flare", "Weapon to call fire support.", FCVAR_PROTECTED);

	RegConsoleCmd("sm_firesupport_call", CmdCallFS, "Call fire support where you looking at.", 0);
	RegAdminCmd("sm_firesupport_ad_call", CmdCallAFS, 0);										// HINT: test command

	HookEvent("weapon_fire", Event_WeaponFire);
	HookEvent("round_start", Event_RoundStart);
	HookEvent("player_pick_squad", Event_PlayerPickSquad);

	InitSupportCount();
}

public void OnMapStart() {
	gBeamSprite = PrecacheModel("sprites/laserbeam.vmt");
}

public Action Event_WeaponFire(Event event, const char[] name, bool dontBroadcast) {
	int client = GetClientOfUserId(event.GetInt("userid"));
	if (!gCvarEnableWeapon.BoolValue) {
		return Plugin_Handled;
	}

	int team = GetClientTeam(client);
	char weapon[64];
	GetClientWeapon(client, weapon, sizeof(weapon));
	char indicator[64];
	gCvarWeapon.GetString(indicator, sizeof(indicator));
	if (IsEnabled[client] != true || (team != TEAM_SECURITY && team != TEAM_INSURGENT) || !IsPlayerAlive(client) || CountAvailableSupport[team] < 1 || !IsEnabledTeam[team] || (StrContains(weapon, indicator, false) == -1)) {
		return Plugin_Handled;
	}

	float ground[3];
	if (GetAimGround(client, ground)) {
		ground[2] += 20.0;

		if (CallFireSupport(client, ground)) {
			CountAvailableSupport[team]--;
			IsEnabledTeam[team] = false;
			CreateTimer(gCvarDelayNextSupport.FloatValue, Timer_EnableTeamSupport, team, TIMER_FLAG_NO_MAPCHANGE);
		}
	}
	return Plugin_Continue;
}

public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) {
	InitSupportCount();
	return Plugin_Continue;
}

public Action Event_PlayerPickSquad(Event event, const char[] name, bool dontBroadcast) {
	int client = GetClientOfUserId(event.GetInt("userid"));
	char template[64];
	event.GetString("class_template", template, sizeof(template), "");
	char class[64];
	gCvarClass.GetString(class, sizeof(class));

	IsEnabled[client] = (StrContains(template, class, false) > -1);

	return Plugin_Continue;
}

Action CmdCallFS(int client, int args) {													// HINT: Fire to where client looking
	if (!gCvarEnableCmd.BoolValue) {
		return Plugin_Handled;
	}

	int team = GetClientTeam(client);
	if (IsEnabled[client] != true || (team != TEAM_SECURITY && team != TEAM_INSURGENT) || !IsPlayerAlive(client) || CountAvailableSupport[team] < 1 || !IsEnabledTeam[team]) {
		return Plugin_Handled;
	}

	float ground[3];
	if (GetAimGround(client, ground)) {
		ground[2] += 20.0;

		if (CallFireSupport(client, ground)) {
			CountAvailableSupport[team]--;
			IsEnabledTeam[team] = false;
			CreateTimer(gCvarDelayNextSupport.FloatValue, Timer_EnableTeamSupport, team, TIMER_FLAG_NO_MAPCHANGE);
		}
	}
	return Plugin_Handled;
}

Action CmdCallAFS(int client, int args) {													// HINT: Fire to where client looking
	float ground[3];
	if (GetAimGround(client, ground)) {
		ground[2] += 20.0;
		if (CallFireSupport(client, ground)) {
		}
	}
	return Plugin_Handled;
}

/// FireSupport
public bool CallFireSupport(int client, float ground[3]) {									// HINT: Fire to target pos
	float sky[3];
	if (GetSkyPos(client, ground, sky)) {
		sky[2] -= 20.0;

		float time = gCvarDelay.FloatValue;
		DataPack pack = new DataPack();
		pack.WriteCell(client);
		pack.WriteFloat(sky[0]);
		pack.WriteFloat(sky[1]);
		pack.WriteFloat(sky[2]);

		ShowDelayEffect(ground, sky, time);

		for(int i = 0; i < gCvarRound.IntValue; ++i) {
			time = time + 0.05 + GetURandomFloat();
			CreateTimer(time, Timer_LaunchMissile, pack, TIMER_FLAG_NO_MAPCHANGE);
		}
		CreateTimer(time + 0.1, Timer_DataPackExpire, pack, TIMER_FLAG_NO_MAPCHANGE | TIMER_DATA_HNDL_CLOSE);
		return true;
	}

	return false;
}

void InitSupportCount() {
	IsEnabledTeam[TEAM_SECURITY] = true;
	IsEnabledTeam[TEAM_INSURGENT] = true;
	CountAvailableSupport[TEAM_SECURITY] = gCvarCountPerRound.IntValue;
	CountAvailableSupport[TEAM_INSURGENT] = gCvarCountPerRound.IntValue;
}

void ShowDelayEffect(float ground[3], float sky[3], float time) {	// WARNING: Tempent can't alive more than 25 second. must use env_beam entity
	TE_SetupBeamPoints(ground, sky, gBeamSprite, 0, 0, 1, time, 1.0, 0.0, 5, 0.0, {255, 0, 0, 255}, 10);
	TE_SendToAll();
	TE_SetupBeamRingPoint(ground, 500.0, 0.0, gBeamSprite, 0, 0, 1, time, 5.0, 0.0, {255, 0, 0, 255}, 10, 0);
	TE_SendToAll();
}

public Action Timer_LaunchMissile(Handle timer, DataPack pack) {
	float dir = GetURandomFloat() * MATH_PI * 8.0;	// not 2π for good result
	float length = GetURandomFloat() * gCvarMaxSpread.FloatValue;

	float pos[3];
	pack.Reset();
	int client = pack.ReadCell();
	pos[0] = pack.ReadFloat() + Cosine(dir) * length;
	pos[1] = pack.ReadFloat() + Sine(dir) * length;
	pos[2] = pack.ReadFloat();

	SDKCall(fCreateRocket, client, "rocket_rpg7", pos, DOWN_VECTOR);
	return Plugin_Handled;
}

public Action Timer_DataPackExpire(Handle timer, DataPack pack) {
	return Plugin_Handled;
}

public Action Timer_EnableTeamSupport(Handle timer, int team) {
	IsEnabledTeam[team] = true;
	return Plugin_Handled;
}

/// UTILS
bool GetAimGround(int client, float vec[3]) {
	float pos[3];
	float dir[3];
	GetClientEyePosition(client, pos);
	GetClientEyeAngles(client, dir);
	Handle ray = TR_TraceRayFilterEx(pos, dir, MASK_SOLID_BRUSHONLY, RayType_Infinite, TraceWorldOnly, client);

	if (TR_DidHit(ray)) {
		TR_GetEndPosition(pos, ray);
		CloseHandle(ray);

		ray = TR_TraceRayFilterEx(pos, DOWN_VECTOR, MASK_SOLID_BRUSHONLY, RayType_Infinite, TraceWorldOnly, client);
		if (TR_DidHit(ray)) {
			TR_GetEndPosition(vec, ray);
			CloseHandle(ray);
			return true;
		}
	}

	CloseHandle(ray);
	return false;
}

bool GetSkyPos(int client, float pos[3], float vec[3]) {
	Handle ray = TR_TraceRayFilterEx(pos, UP_VECTOR, MASK_SOLID_BRUSHONLY, RayType_Infinite, TraceWorldOnly, client);

	if (TR_DidHit(ray)) {
		char surface[64];
		TR_GetSurfaceName(ray, surface, sizeof(surface));
		if (StrEqual(surface, "TOOLS/TOOLSSKYBOX", false)) {
			TR_GetEndPosition(vec, ray);
			CloseHandle(ray);
			return true;
		}
	}

	CloseHandle(ray);
	return false;
}

public bool TraceWorldOnly(int entity, int mask, any data) {
	if(entity == data || entity > 0)
		return false;
	return true;
}


Reset CanIntroPlay onclientdisconnect

Reset CanIntroPlay onclientdisconnect

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio_donator_intro
 * Plays a user's theme song when they join the server if they are a donator.
 * A user's theme is set through the In Game Audio website.
 *
 * Copyright 2013 Crimsontautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <in_game_audio>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "1.1"

new bool:g_CanIntroPlay[MAXPLAYERS+1];
new bool:g_DonatorLibraryExists = false;

public Plugin:myinfo =
{
    name = "In Game Audio Donator Intro",
    author = "CrimsonTautology",
    description = "Play donator's theme song when they join the server",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

public OnPluginStart()
{
    LoadTranslations("in_game_audio.phrases");
    AddCommandListener(Event_JoinClass, "joinclass");
    g_DonatorLibraryExists = LibraryExists("donator.core");
}

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    MarkNativeAsOptional("IsPlayerDonator");
    return APLRes_Success;
}

public OnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = false;
    }
}

public OnLibraryAdded(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = true;
    }
}

public OnPostDonatorCheck(client)
{
    if (g_DonatorLibraryExists)
    {
        g_CanIntroPlay[client] = IsPlayerDonator(client);
    }
}

public OnClientConnected(client)
{
    if (!g_DonatorLibraryExists)
    {
        g_CanIntroPlay[client] = true;
    }
}

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

public Action:Event_JoinClass(client, const String:command[], args)
{
    if(g_CanIntroPlay[client])
    {
        UserTheme(client);
        g_CanIntroPlay[client] = false;
    }

    return Plugin_Continue;
}

/**
 * vim: set ts=4 :
 * =============================================================================
 * in_game_audio_donator_intro
 * Plays a user's theme song when they join the server if they are a donator.
 * A user's theme is set through the In Game Audio website.
 *
 * Copyright 2013 Crimsontautology
 * =============================================================================
 *
 */

#pragma semicolon 1

#include <sourcemod>
#include <in_game_audio>
#undef REQUIRE_PLUGIN
#include <donator>

#define PLUGIN_VERSION "1.1"

new bool:g_CanIntroPlay[MAXPLAYERS+1];
new bool:g_DonatorLibraryExists = false;

public Plugin:myinfo =
{
    name = "In Game Audio Donator Intro",
    author = "CrimsonTautology",
    description = "Play donator's theme song when they join the server",
    version = PLUGIN_VERSION,
    url = "https://github.com/CrimsonTautology/sm_in_game_audio"
};

public OnPluginStart()
{
    LoadTranslations("in_game_audio.phrases");
    AddCommandListener(Event_JoinClass, "joinclass");
    g_DonatorLibraryExists = LibraryExists("donator.core");
}

public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
{
    MarkNativeAsOptional("IsPlayerDonator");
    return APLRes_Success;
}

public OnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = false;
    }
}

public OnLibraryAdded(const String:name[])
{
    if (StrEqual(name, "donator.core"))
    {
        g_DonatorLibraryExists = true;
    }
}

public OnPostDonatorCheck(client)
{
    if (g_DonatorLibraryExists)
    {
        g_CanIntroPlay[client] = IsPlayerDonator(client);
    }
}

public OnClientConnected(client)
{
    if (!g_DonatorLibraryExists)
    {
        g_CanIntroPlay[client] = true;
    }
}

public Action:Event_JoinClass(client, const String:command[], args)
{
    if(g_CanIntroPlay[client])
    {
        UserTheme(client);
        g_CanIntroPlay[client] = false;
    }

    return Plugin_Continue;
}