Skip to content

Instantly share code, notes, and snippets.

@JYASKO
Forked from so0k/gist:8201314
Created May 31, 2016 15:43
Show Gist options
  • Save JYASKO/dac7244deaee342bfba144c2fa1d6fd9 to your computer and use it in GitHub Desktop.
Save JYASKO/dac7244deaee342bfba144c2fa1d6fd9 to your computer and use it in GitHub Desktop.
brush with queue
#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <cstrike>
#include <adminmenu>
#define _DEBUG 0 // Set to 1 for debug spew
#define SIZE_OF_INT 2147483647 // without 0
#define PLUGIN_VERSION "0.0.4.0"
#define UPDATE_URL "http://not.available.com"
#define SOUND_FILE "buttons/weapon_cant_buy.wav" // csgo\sound\buttons
//Parameters
new bool:g_bUseBotControl = true;
new String:g_sBombsite[] = "B";
// Plugin variables
//g_ChangeTeamImmune is used to flag Ts that need to be switched to CT and flag CTs that were switched to T
//CTs that were switched to T should not be switched back to CT
new g_ChangeTeamImmune[MAXPLAYERS+1] = false; // Flags the player as being Immune to Team Changes
new g_PluginTeamSwitch[MAXPLAYERS+1] = false; // Flags the player as being switched by the plugin
new bool:g_bSwitchingPlayers = false; // To capture player spawns during round end
//ScoringMethod: how did a Terrorist score a point
enum ScoringMethod {
ScoringMethod_CTKill = 0,
ScoringMethod_BombExplosion
};
new g_RoundPoints[MAXPLAYERS+1] = 0; // Holds the points earned by a T this round, either by killing a CT or detonating the bomb
new g_RoundMVPSerial = 0; // Holds the serial of the T who killed 2+ CTs or detonated the bomb
new g_ScoringPlayers = 0; // Holds the number of Ts that killed CTs or detonated the bomb
new g_iTeamSwitchCount = 0; // keeps track of the total number of players switched to CT
new g_iMenuTime; // Calculated time to show the menu to the player who killed 2+ CTs before the menu goes away and a random teammate is chosen;
new g_iBombEntity = INVALID_ENT_REFERENCE;
new g_iCTScore = 0;
new g_iCTStreak = 0;
new g_iMaxCTStreak = 0;
new bool:g_bResetMoney = false; //used to flag if money reset is needed
new g_iStartMoney;
new g_iPlayerMoney = -1;
//bot control
enum BOTState {
BOTState_NotDirected = 0,
BOTState_Directed
};
new BOTState:g_iBotDirectionState = BOTState_NotDirected;
new Handle:g_hBotMoveTo = INVALID_HANDLE;
new Handle:g_hBotControlTimer = INVALID_HANDLE;
new bool:g_ReachedSite[MAXPLAYERS+1];
new bool:g_IsPlayerFrozen[MAXPLAYERS+1] = {false, ...};
//bombsite info
new g_iBombsiteA = INVALID_ENT_REFERENCE; // Holder for Bomb site A entity index
new g_iBombsiteB = INVALID_ENT_REFERENCE; // Holder for Bomb site B entity index
new Float:g_vecBombsiteCenterA[3];
new Float:g_vecBombsiteCenterB[3];
//Queue
new g_QueueSize = MAXPLAYERS+1;
new g_ClientQueue[MAXPLAYERS+1]; //array containing the queue
new g_QueueHead = 0; //head of queue
new g_QueueTail = 0; //tail of queue
#if _DEBUG
#define MAX_MESSAGE_LENGTH 256
new String:dmsg[MAX_MESSAGE_LENGTH];
DebugMessage(const String:msg[], any:...)
{
LogMessage("%s", msg);
PrintToChatAll("[BRush DEBUG] %s", msg);
}
#endif
///TODO: weapon restrict
///TODO: Handle cvar changes through Event_ServerCvar (mp_startmoney & mp_freezetime & mp_round_restart_delay)
///TOOD: Calculate menu time based on freeze time and mp_round_restart_delay
///TODO: statistics
///TODO: add possibility for clients to DeQueue if they just want to spectate
///TODO: make sure to include csgo/addons/sourcemod/gamedata/plugin.brush.txt for CCSBotMoveTo definition
///TODO: review translations & named phrases
///TODO: Ensure ProcessQueue is called at the right time
///TODO: Updater??
///TODO: BUG - I was a CT and CT lost, but I stayed CT????
public Plugin:myinfo =
{
name = "CSGO B-Rush",
author = "TnTSCS aka ClarkKent & so0k (iGame.vn)",
description = "CSGO B-Rush",
version = "1.0",
url = "http://www.sourcemod.net/"
};
public OnPluginStart()
{
LoadTranslations("brush.phrases");
/** convar hooks **/
//Initialize pointer to the property storing Player's money
///TODO: There should be handler for mp_startmoney changes?
InitPlayerMoney();
new Handle:hRandom; //for some reason we don't like handles
//hook target bombsite variable
//AutoExecConfig_CreateConVar("sm_brush_bombsite", "B", "What bomb site should be used?", FCVAR_NONE)
hRandom = CreateConVar("sm_brush_bombsite", "B", "What bomb site should be used?", FCVAR_NOTIFY);
HookConVarChange(hRandom, OnBombsiteChanged);
GetConVarString(hRandom, g_sBombsite, sizeof(g_sBombsite));
//hook freezetime convar
hRandom = FindConVar("mp_freezetime");
HookConVarChange(hRandom, OnFreezeTimeChanged);
SetMenuTime(hRandom);
CloseHandle(hRandom);
/** command hooks **/
AddCommandListener(OnJoinTeamCommand, "jointeam");
/** event hooks **/
HookEvents();
//Initialize the CSSBotMoveTo function handler
InitCSSBotMoveTo();
}
stock HookEvents()
{
HookEvent("player_team", OnPlayerTeam, EventHookMode_Pre);
HookEvent("round_start", OnRoundStart, EventHookMode_Post);
HookEvent("player_spawn", OnPlayerSpawn);
HookEvent("bomb_pickup", OnBombPickup);
HookEvent("round_freeze_end", OnRoundFreezeEnd);
HookEvent("player_death", OnPlayerDeath);
HookEvent("bomb_exploded", OnBombExploded, EventHookMode_Pre);
HookEvent("round_end", OnRoundEnd, EventHookMode_Post);
//HookEvent("cs_match_end_restart", OnMatchRestart, EventHookMode_Pre);//CSGO only?
//HookEvent("teamchange_pending", OnTeamChangePending, EventHookMode_Pre); //function not implemented
//HookEvent("player_connect_full", OnFullConnect, EventHookMode_Pre);
//HookEvent("cs_match_end_restart", OnMatchRestart, EventHookMode_Pre);
//HookEvent("bomb_beginplant", OnBeginBombPlant);
}
stock UnhookEvents()
{
UnhookEvent("player_team", OnPlayerTeam, EventHookMode_Pre);
UnhookEvent("round_start", OnRoundStart, EventHookMode_Post);
UnhookEvent("player_spawn", OnPlayerSpawn);
UnhookEvent("bomb_pickup", OnBombPickup);
UnhookEvent("round_freeze_end", OnRoundFreezeEnd);
UnhookEvent("player_death", OnPlayerDeath);
UnhookEvent("bomb_exploded", OnBombExploded, EventHookMode_Pre);
UnhookEvent("round_end", OnRoundEnd, EventHookMode_Post);
//UnhookEvent("cs_match_end_restart", OnMatchRestart, EventHookMode_Pre);//CSGO only?
//UnhookEvent("teamchange_pending", OnTeamChangePending, EventHookMode_Pre); //function not implemented
//UnhookEvent("player_connect_full", OnFullConnect, EventHookMode_Pre);
//UnhookEvent("cs_match_end_restart", OnMatchRestart, EventHookMode_Pre);
//UnhookEvent("bomb_beginplant", OnBeginBombPlant);
}
public Action:OnJoinTeamCommand(client, const String:command[], argc)
{
//most code thanks to "TeamChange Unlimited" by Sheepdude
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[OnJoinTeamCommand] Entered");
DebugMessage(dmsg);
#endif
if(!IsValidClient(client) || argc < 1)
return Plugin_Handled;
decl String:arg[4];
GetCmdArg(1, arg, sizeof(arg));
new team_to = StringToInt(arg);
new team_from = GetClientTeam(client);
//if same team, teamswitch controlled by the plugin or a bot -> let CSGO handle it
if((team_from == team_to) || g_PluginTeamSwitch[client] || IsFakeClient(client))
{
return Plugin_Continue;
}else //let plugin handle it
{
//ignore switches between T/CT team
if( (team_from == CS_TEAM_CT && team_to == CS_TEAM_T )
|| (team_from == CS_TEAM_T && team_to == CS_TEAM_CT))
{
ReplyToCommand(client, "[B-Rush] Team switches have to be earned!");
return Plugin_Handled;
}else
{
//else move client to spectate & enqueue
ChangeClientTeam(client, CS_TEAM_SPECTATOR);
EnQueue(client); ///TODO: GIVE OPTION TO ENQUEUE OR NOT
//re-evaluate if client can be DeQueue-ed
CreateTimer(0.3, TeamChangeTimer, GetClientSerial(client));
return Plugin_Handled;
}
}
}
/**
* join_command timer event - re evaluate if client can be dequeue-ed
*/
public Action:TeamChangeTimer(Handle:timer, any:serial)
{
ProcessQueue();
return Plugin_Handled;
}
/**
* Process human player queue and ensure both teams are filled with humans or BOTs (3 CT / 5 T)
*
* @noreturn
*
*/
public ProcessQueue()
{
//Get count of different type of clients for both teams
new tHumanCount=0, ctHumanCount=0, tBotCount=0, ctBotCount=0;
GetTeamsClientCounts(tHumanCount, ctHumanCount, tBotCount, ctBotCount);
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[ProcessQueue] CT (Humans: %i - Bots: %i) - T (Humans: %i - Bots: %i)", ctHumanCount, ctBotCount, tHumanCount, tBotCount);
DebugMessage(dmsg);
#endif
FillTeam(CS_TEAM_T, tHumanCount, tBotCount, 5); //clients will always start T first from queue
FillTeam(CS_TEAM_CT, ctHumanCount, ctBotCount, 3);
//notify Queue-ing players
NotifyQueue();
}
/**
* Attempts to fill a team up to target count with Queue-ed clients first else with BOTs
*
* @param team team which needs to be filled (CS_TEAM_T or CS_TEAM_CT)
* @param humanCount current count of Humans in the team
* @param botCount current count of BOTs in the team
* @param targetCount desired target count
* @noreturn
*
*/
public FillTeam(team, humanCount, botCount, targetCount)
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[FillTeam] Team: %i", team);
DebugMessage(dmsg);
#endif
//Make sure team has target player count (preferrably human players from queue, else bots)
new client;
while(!IsQueueEmpty() && humanCount < targetCount)
{
//fetch player from queue
client = DeQueue();
if(IsValidClient(client))
{
if(humanCount+botCount+1 > targetCount)
{
//make place for human
KickBot(team);
botCount--;
}
ChangeClientTeam(client, team);
humanCount++;
}
}
//make absolutely sure bots aren't causing an unbalance, if it's humans causing imbalance... we should handle this as well..
if(humanCount+botCount > targetCount)
{
while(humanCount+botCount > targetCount && botCount < 0)
{
KickBot(team);
botCount--;
}
}else{
//fill remaining spots with bots
while(humanCount+botCount < targetCount)
{
AddBot(team);
botCount++;
}
}
}
/**
* Called when a player joins a team
* -> Silence team join events
*/
public Action:OnPlayerTeam(Handle:event, const String:name[], bool:dontBroadcast)
{
dontBroadcast = true;
return Plugin_Changed;
}
/**
* Called when a player spawns.
*
* @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking
* this event has set the hook mode EventHookMode_PostNoCopy.
* @param name String containing the name of the event.
* @param dontBroadcast True if event was not broadcast to clients, false otherwise.
* @noreturn
*
* @eventparam userid UserID of player spawning
*/
public OnPlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
{
new client = GetClientOfUserId(GetEventInt(event, "userid"));
if (!IsValidClient(client) || GetClientTeam(client) <= CS_TEAM_NONE)
{
return;
}
if(g_bResetMoney)
CreateTimer(0.1, ResetPlayerMoneyTimer, GetClientSerial(client), TIMER_FLAG_NO_MAPCHANGE);
//freeze players during player switch
if(g_bSwitchingPlayers)
CreateTimer(0.1, SetPlayerFreezeTimer, GetClientSerial(client), TIMER_FLAG_NO_MAPCHANGE);
ResetClientVariables(client);
}
public Action:ResetPlayerMoneyTimer(Handle:timer, any:serial) {
ResetPlayerMoney(GetClientFromSerial(serial));
return Plugin_Continue;
}
public Action:SetPlayerFreezeTimer(Handle:timer, any:serial) {
FreezePlayer(GetClientFromSerial(serial), g_bSwitchingPlayers);
return Plugin_Continue;
}
/**
* Called when mp_freezetime expires.
*
* @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking
* this event has set the hook mode EventHookMode_PostNoCopy.
* @param name String containing the name of the event.
* @param dontBroadcast True if event was not broadcast to clients, false otherwise.
* @noreturn
*/
public OnRoundFreezeEnd(Handle:event, const String:name[], bool:dontBroadcast)
{
//start directing BOTs
g_iBotDirectionState = BOTState_Directed;
if (g_iBombsiteA == INVALID_ENT_REFERENCE || g_iBombsiteB == INVALID_ENT_REFERENCE)
{
GetBombsiteInfo();
//Disable/Enable bombsites and setting target bombsite hooks
DoBombsiteControl();
}
//reset ReachedSite array
if (g_bUseBotControl)
{
for (new i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && IsFakeClient(i))
{
g_ReachedSite[i] = false;
}
}
}
//reset round variables
g_iTeamSwitchCount = 0;
g_ScoringPlayers = 0;
g_RoundMVPSerial = 0;
g_bResetMoney = false;
// Let the players know this round is LIVE
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "LiveRound");
PrintCenterTextAll("%t", "LiveRound");
}
/**
* @brief When an entity is created
*
* @param entity Entity index
* @param classname Class name
* @noreturn
*/
public OnEntityCreated(entity, const String:classname[])
{
// Get the entity index of the bomb so we can later on have the owner of the bomb drop it, if necessary
if (StrEqual(classname, "weapon_c4", false))
{
g_iBombEntity = entity;
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[OnEntityCreated] C4 index is %i", g_iBombEntity);
DebugMessage(dmsg);
#endif
}else if (StrEqual(classname, "planted_c4", false))
{
// If the bomb is planted, clear bomb entity index so we can bypass the bomb dropping function
g_iBombEntity = INVALID_ENT_REFERENCE;
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[OnEntityCreated] Bomb was planted, C4 index is cleared");
DebugMessage(dmsg);
#endif
}
}
/**
* Called When the bomb is picked up by a client.
*
* @eventparam userid player who picked up the bomb
*/
public OnBombPickup(Handle:event, const String:name[], bool:dontBroadcast)
{
//Display a message to ensure the player as aware of the bombsite to plant at
new client = GetClientOfUserId(GetEventInt(event, "userid"));
if (!client || IsFakeClient(client))
{
return;
}
if (StrEqual(g_sBombsite, "A", false))
{
PrintToChat(client, "\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "PlantA");
return;
}else if (StrEqual(g_sBombsite, "B", false))
{
PrintToChat(client, "\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "PlantB");
return;
}
LogError("ERROR WITH BOMB SITE CVAR - it's not set to A or B!!");
}
/**
* Called when a player dies.
*
* @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking
* this event has set the hook mode EventHookMode_PostNoCopy.
* @param name String containing the name of the event.
* @param dontBroadcast True if event was not broadcast to clients, false otherwise.
* @noreturn
*
* @eventparam userid UserID who died
* @eventparam attacker UserID who killed
* @eventparam weapon String containing name of weapon killer used
* @eventparam headshot Bool: Was this a headshot kill?
* @eventparam dominated Bool: Did killer dominate victim with this kill?
* @eventparam revenge Bool: Did killer get revenge on victim with this kill?
*/
public OnPlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
{
// if this is a T on CT kill, ensure the attacker gets RoundPoints
new victim = GetClientOfUserId(GetEventInt(event, "userid"));
new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
if(IsTonCTKill(attacker, victim))
{
// Let the CT know they might get switched if they lose.
PrintToChat(victim, "\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "CTKilled", attacker);
TrackPlayerRoundPoints(ScoringMethod_CTKill, attacker, victim);
}
}
/**
* Called when the bomb explodes.
*
* @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking
* this event has set the hook mode EventHookMode_PostNoCopy.
* @param name String containing the name of the event.
* @param dontBroadcast True if event was not broadcast to clients, false otherwise.
* @noreturn
*
* @eventparam userid UserID of player who planted the bomb
* @eventparam site Bombsite index
*/
public OnBombExploded(Handle:event, const String:name[], bool:dontBroadcast)
{
// ensure the bomber gets RoundPoints for this event and gets switched over to CT
new client = GetClientOfUserId(GetEventInt(event, "userid"));
if (IsValidClient(client))
{
//pass 0 for victim
TrackPlayerRoundPoints(ScoringMethod_BombExplosion, client, 0);
}
}
/**
* Keep track of all player scoring points and which player is the MVP
*/
TrackPlayerRoundPoints(ScoringMethod:method, client, victim)
{
g_RoundPoints[client]++; // Increase client points
if (g_RoundPoints[client] == 1)
{
g_ScoringPlayers++;
switch(method)
{
case(ScoringMethod_CTKill):
{
// Notify the scoring player they will be switched if Ts win (victim should be passed through..)
if(IsValidClient(victim))
PrintToChat(client, "\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "KilledCT", victim);
}
case(ScoringMethod_BombExplosion):
{
//if not enough scoring players, flag this player as MVP for successfully planting the bomb
if(g_ScoringPlayers == 1)
g_RoundMVPSerial = GetClientSerial(client);
}
}
g_ChangeTeamImmune[client] = true;
}
else if (g_RoundPoints[client] >= 2)
{
// This terrorist's has multiple points
g_RoundMVPSerial = GetClientSerial(client); // Mark client serial as the player who killed more than one CT
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[TrackPlayerRoundPoints] %N scored multiple points - serial:[%i]", client, g_RoundMVPSerial);
DebugMessage(dmsg);
#endif
}
}
/**
* Evaluate if this a T on CT kill
*/
bool:IsTonCTKill(attacker, victim)
{
if (!IsValidClient(attacker) || !IsValidClient(victim))
{
return false;
}
new ateam = GetClientTeam(attacker); // Get attacker's team
new vteam = GetClientTeam(victim); // Get the victim's team
// Ensure attacking team is Terrorist and it's not a team kill or self kill.
if (ateam != CS_TEAM_T || ateam == vteam || attacker == victim)
{
return false;
}
return true;
}
/**
* Called when the round starts.
*
* @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking
* this event has set the hook mode EventHookMode_PostNoCopy.
* @param name String containing the name of the event.
* @param dontBroadcast True if event was not broadcast to clients, false otherwise.
* @noreturn
*
* @eventparam timelimit Long: Winner team/user index
* @eventparam fraglimit Long: frag limit in seconds
* @eventparam objective String: round objective.
*/
public OnRoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
g_bSwitchingPlayers = false;
}
/**
* Called when the round ends.
*
* @param event Handle to event. This could be INVALID_HANDLE if every plugin hooking
* this event has set the hook mode EventHookMode_PostNoCopy.
* @param name String containing the name of the event.
* @param dontBroadcast True if event was not broadcast to clients, false otherwise.
* @noreturn
*
* @eventparam winner Byte: Winner team/user index
* @eventparam reason Byte: Reason why team won
* @eventparam message String: End round message.
*/
public OnRoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
{
CreateTimer(0.6, SetScoreTimer);
g_iBotDirectionState = BOTState_NotDirected;
//g_iTeamSwitchCount = 0; ///TODO: Is this needed??
new winner = GetEventInt(event, "winner");
if (winner == CS_TEAM_T)
{
g_bResetMoney = true;
CreateTimer(0.5, TerroristsWonTimer);
}
else
{
g_iCTScore++; // Increase the CT's score by 1
CreateTimer(0.3, CounterTerroritsWonTimer);
}
}
/**
* Function for timer that sets the scores
*
* @param timer Handle of timer
* @noreturn
*/
public Action:SetScoreTimer(Handle:timer)
{
// Set the team scores
SetTeamScore(CS_TEAM_CT, g_iCTScore);
SetTeamScore(CS_TEAM_T, 0);
return Plugin_Continue;
}
/**
* Timer event to handle Terrorist Win
*
* @param timer Handle of timer
* @noreturn
*/
public Action:TerroristsWonTimer(Handle:timer)
{
/* ===================================================================================================================================
ALL CTs get switched to the Terrorist's team.
The Terrorists who killed 1 or more CTs get switched to CT team.
If less than 3 Terrorists killed a CT, the Terrorist who killed 2+ CTs will get a menu to select the player(s) to be switched
=================================================================================================================================== */
g_bSwitchingPlayers = true;
ForceBombDrop();
// loop clients and switch CTs to T and Ts to CT
new team;
for (new client = 1; client <= MaxClients; client++)
{
if (IsClientInGame(client))
{
//transport live players back to their spawn
if(IsPlayerAlive(client))
CreateTimer(0.3, RespawnPlayerTimer, GetClientSerial(client), TIMER_FLAG_NO_MAPCHANGE);
team = GetClientTeam(client);
if (team == CS_TEAM_CT && !g_ChangeTeamImmune[client])
{
//Switch CTs to T and ensure they will not be switched back to CT by setting their Immune switch
CreateTimer(0.2, SwitchToTTimer, GetClientSerial(client));
}
else if (team == CS_TEAM_T && g_ChangeTeamImmune[client])
{
//these were flagged as Ts who scored points and should be CT
SwitchToCT(client);
}
}
}
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[TerroristWonTimer] numSwitched value is %i", g_iTeamSwitchCount);
DebugMessage(dmsg);
#endif
// If less than three Terrorists were switched to CT, we need to process further
if (g_iTeamSwitchCount != 3)
{
if (g_iTeamSwitchCount == 0) // CTs killed themselves or were slayed... something where Terrorists didn't kill them
{
SwitchRandomTerrorists();
}
else // Maybe there's an mvp
{
DisplayMenuToMVP();
}
}
// Since the Terrorists just dominated the CTs, reset the scores - it's only fun to see how many rounds the CTs can get against the Ts
g_iCTStreak = g_iCTScore; //keep track of the streak before the score reset
if(g_iCTStreak > g_iMaxCTStreak)
{
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "CTBrokeRecord", g_iMaxCTStreak);
g_iMaxCTStreak = g_iCTStreak;
}
g_iCTScore = 0;
// ===================================================================================================================================
// Let's get the number of players on the Terrorist's team who killed a player (or players) on the CT team, then announce this everyone
// ===================================================================================================================================
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TWon", g_ScoringPlayers);
if(g_iCTStreak > 1)
{
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "CTStreak", g_iCTStreak);
}
}
/**
* Timer event to handle Counter-Terrorist Win
*
* @param timer Handle of timer
* @noreturn
*/
public Action:CounterTerroritsWonTimer(Handle:timer)
{
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "CTWon");
//Taunts
if (g_iCTScore >= 2)
{
// ===================================================================================================================================
// Just fun taunting messages to the Terrorists if they keep loosing to the 3 CTs
// ===================================================================================================================================
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "CTWonAgain", g_iCTScore);
switch(g_iCTScore)
{
case 3: { PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TTaunt3"); }
case 4: { PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TTaunt4"); }
case 5: { PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TTaunt5"); }
case 6: { PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TTaunt6"); }
case 7: { PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TTaunt7"); }
case 8, 9, 10, 11, 12, 13, 14, 15:
{
// Switch up teams, and if CTs keep winning, keep switching up the teams
PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "Scrambling");
ScrambleTeams();
}
}
}
}
/**
* Function for timer to respawn live players (teleport back to their spawn)
*
* @param timer Handle of timer
* @param client client serial
* @noreturn
*/
public Action:RespawnPlayerTimer(Handle:timer, any:serial) {
new client = GetClientFromSerial(serial);
if (client != 0)
{
RespawnPlayer(client);
PrintCenterTextAll("%t", "TeamSwitch");
}
return Plugin_Continue;
}
/**
* Function for timer to switch a CT client to T side
*
* @param timer Handle of timer
* @param client client serial
* @noreturn
*/
public Action:SwitchToTTimer(Handle:timer, any:serial)
{
new client = GetClientFromSerial(serial);
if (client == 0 || GetClientTeam(client) <= CS_TEAM_SPECTATOR)
{
return Plugin_Continue;
}
g_ChangeTeamImmune[client] = true;
SwitchPlayerTeam(client, CS_TEAM_T);
return Plugin_Continue;
}
/**
* Function that handles the Terrorist's team swapping
*
* @param client client index
* @noreturn
*/
SwitchToCT(client)
{
g_iTeamSwitchCount++;
SwitchPlayerTeam(client, CS_TEAM_CT);
}
/**
* Function to make sure at least 3 Terrorists have been switched to CT
* @noreturn
* @note Uses the global variable g_iTeamSwitchCount to ensure enough players have been switched
*/
SwitchRandomTerrorists()
{
while (g_iTeamSwitchCount < 3)
{
// Since less than 3 Ts were switched to the CT team, randomly select 1 or 2 more to be switched
new client = GetRandomClientOfTeam(CS_TEAM_T);
// If player is a valid client index and is not one of the just switched CTs, switch to CT team.
if (client != -1)
{
g_ChangeTeamImmune[client] = true;
SwitchToCT(client);
}
else
{
LogError("ERROR with SwitchRandom");
break;
}
}
//re-evaluate & notify queue
//CreateTimer(0.2, ProcessQueueTimer);
}
/**
* Timer event to handle Player Cash Reset
*
* @param timer Handle of timer
* @noreturn
*/
public Action:ResetPlayerCashTimer(Handle:timer, any:serial) {
new client = GetClientFromSerial(serial);
if (!IsValidClient(client))
{
SetEntData(client, g_iPlayerMoney, g_iStartMoney, 4, true);
}
return Plugin_Continue;
}
/**
* Timer event to handle Bot Control (Direct To Bombsite until touched)
* @param timer event
*/
public Action:ProcessQueueTimer(Handle:timer)
{
ProcessQueue();
}
/**
* Called when a Bombsite Entity is touched by a client
*/
public OnTouchBombsite(trigger, client)
{
if (g_iBotDirectionState == BOTState_NotDirected || !IsValidClient(client) ||
!IsFakeClient(client) || g_ReachedSite[client])
{
return;
}
#if _DEBUG
Format(dmsg, sizeof(dmsg), "%N just touched the bombsite [%s - %i]", client, g_sBombsite, (StrEqual(g_sBombsite, "A", false) ? g_iBombsiteA : g_iBombsiteB));
DebugMessage(dmsg);
#endif
///TODO: check if Client is on wrong bombsite & inform them? (first if statement checks for fakeclient)
g_ReachedSite[client] = true;
}
/**
* Called when the map is loaded.
*
* @note This used to be OnServerLoad(), which is now deprecated.
* Plugins still using the old forward will work.
*/
public OnMapStart()
{
InitQueue();
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[OnMapStart] Entered");
DebugMessage(dmsg);
#endif
if (g_bUseBotControl && g_hBotMoveTo != INVALID_HANDLE)
{
ClearTimer(g_hBotControlTimer);
g_hBotControlTimer = CreateTimer(1.0, BotControlTimer, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
}
}
/**
* Called when the map has loaded, servercfgfile (server.cfg) has been
* executed, and all plugin configs are done executing. This is the best
* place to initialize plugin functions which are based on cvar data.
*
* @note This will always be called once and only once per map. It will be
* called after OnMapStart().
*
* @noreturn
*/
public OnConfigsExecuted()
{
if (!PrecacheSound(SOUND_FILE, true))
{
LogError("Unable to precache sound file %s", SOUND_FILE);
}
InitStartMoney();
}
/**
* Called right before a map ends.
*/
public OnMapEnd()
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[OnMapEnd] Entered");
DebugMessage(dmsg);
#endif
EnableBombsite(g_iBombsiteA);
EnableBombsite(g_iBombsiteB);
///TODO: Unhook bombsite touch?
//PrintToChatAll("\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "CTRecord", MaxCTStreak);
ClearTimer(g_hBotControlTimer);
g_iBombsiteA = INVALID_ENT_REFERENCE;
g_iBombsiteB = INVALID_ENT_REFERENCE;
}
/**
* Timer event to handle Bot Control (Direct To Bombsite until touched)
* @param timer event
*/
public Action:BotControlTimer(Handle:timer)
{
if (g_hBotMoveTo == INVALID_HANDLE)
{
g_hBotControlTimer = INVALID_HANDLE;
return Plugin_Stop; //wut? ...OK STAHP
}
if (g_iBotDirectionState == BOTState_NotDirected)
{
return Plugin_Continue;
}
for (new i = 1; i <= MaxClients; i++)
{
if (IsValidClient(i) && IsFakeClient(i) && !g_ReachedSite[i])
{
DirectBotToBombsite(i, g_sBombsite);
}
}
return Plugin_Continue;
}
/**
* Called once a client successfully connects. This callback is paired with OnClientDisconnect.
*
* @param client Client index.
* @noreturn
*/
public OnClientConnected(client)
{
//this is executed on freezetime end.. no need on client connect?
//if (g_iBombsiteA == INVALID_ENT_REFERENCE || g_iBombsiteB == INVALID_ENT_REFERENCE)
//{
// GetBombsiteInfo();
// DoBombsiteControl();
//}
ResetClientVariables(client);
//should also drop from queue just to make sure??
}
/**
* Called once a client is authorized and fully in-game, and
* after all post-connection authorizations have been performed.
*
* This callback is gauranteed to occur on all clients, and always
* after each OnClientPutInServer() call.
*
* @param client Client index.
* @noreturn
*/
public OnClientPostAdminCheck(client)
{
if (IsClientInGame(client) && !IsFakeClient(client))
{
PrintToConsole(client, "\n\n***************************");
PrintToConsole(client, "[CSGO B-Rush] for iGame.vn");
PrintToConsole(client, "***************************\n");
}
}
/**
* Called when a client is disconnecting from the server.
*
* @param client Client index.
* @noreturn
* @note Must use IsClientInGame(client) if you want to do client specific things
*/
public OnClientDisconnect(client)
{
//if client is in queue, drop from queue
if(!IsFakeClient(client))
DropFromQueue(client);
// ===================================================================================================================================
// Clean up client specific variables and open timers (if they exist)
// ===================================================================================================================================
if (IsClientInGame(client))
{
ResetClientVariables(client);
if (!IsFakeClient(client)) // Human player left...
{
CreateTimer(0.1, ClientDisconnectedTimer, _, TIMER_FLAG_NO_MAPCHANGE);
}
}
}
ResetClientVariables(client)
{
g_ChangeTeamImmune[client] = false;
g_RoundPoints[client] = 0;
g_PluginTeamSwitch[client] = false;
//FreezePlayer(client, false);
}
public Action:ClientDisconnectedTimer(Handle:timer)
{
ProcessQueue();
return Plugin_Continue;
}
/**
* Function to Scramble teams - moves all players to Terrorist's team, then calls SwitchRandomTerrorists to move 3 to CT
*
* @noreturn
*/
ScrambleTeams()
{
// Move everyone to Terrorist's team
for (new i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i))
{
continue;
}
if (GetClientTeam(i) == CS_TEAM_CT)
{
SwitchPlayerTeam(i, CS_TEAM_T);
if (IsPlayerAlive(i))
{
RespawnPlayer(i);
}
}
}
// Pull 3 random Ts and put them on CT team
SwitchRandomTerrorists();
}
SwitchPlayerTeam(client, team)
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[SwitchPlayerTeam] %N is being switched to team %i", client, team);
DebugMessage(dmsg);
#endif
g_PluginTeamSwitch[client] = true;
if (team > CS_TEAM_SPECTATOR)
{
CS_SwitchTeam(client, team);
CS_UpdateClientModel(client);
CS_RespawnPlayer(client);
}
else
{
ChangeClientTeam(client, team);
}
g_PluginTeamSwitch[client] = false;
}
/**
* Returns the Human & BOT counts of the T & CT Teams.
* Use this function for optimization if you have to get the counts of both teams,
*
* @param tHumanCount Pass an integer variable by reference
* @param ctHumanCount Pass an integer variable by reference
* @param tBotCount Pass an integer variable by reference
* @param ctBotCount Pass an integer variable by reference
* @noreturn
*/
GetTeamsClientCounts(&tHumanCount=0, &ctHumanCount=0, &tBotCount=0, &ctBotCount=0)
{
for (new client=1; client <= MaxClients; client++)
{
if (IsClientConnected(client) && IsClientInGame(client))
{
if (GetClientTeam(client) == CS_TEAM_T)
{
if(IsFakeClient(client))
tBotCount++;
else
tHumanCount++;
}
else if (GetClientTeam(client) == CS_TEAM_CT)
{
if(IsFakeClient(client))
ctBotCount++;
else
ctHumanCount++;
}
}
}
}
/**
* Gets a random client from the specified team.
*
* @param team team (CS_TEAM_CT or CS_TEAM_T).
* @return Client Index or -1 if no client was found
*/
GetRandomClientOfTeam(team)
{
decl clients[MaxClients];
new num = GetClientsOfTeam(clients, team);
if (num == 0)
{
return -1;
}
else if (num == 1)
{
return clients[0];
}
new random = Math_GetRandomInt(0, num-1);
return clients[random];
}
/**
* Gets all clients from the specified team.
*
* @param client Client Array, size should be MaxClients or MAXPLAYERS
* @param team team (CS_TEAM_CT or CS_TEAM_T).
* @return The number of clients stored in the array
*/
GetClientsOfTeam(clients[], team)
{
new count=0;
for (new client = 1; client <= MaxClients; client++)
{
if (IsValidClient(client) && GetClientTeam(client) == team)
{
clients[count++] = client;
}
}
return count;
}
/**
* Ensure the bomb is not carried by a player by forcefully dropping it
*
* @noreturn
*/
ForceBombDrop()
{
if (g_iBombEntity > MaxClients && IsValidEntity(g_iBombEntity))
{
new bombOwner = Weapon_GetOwner(g_iBombEntity);
if (bombOwner != INVALID_ENT_REFERENCE)
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[ForceBombDrop] The bomb owner is %L", bombOwner);
DebugMessage(dmsg);
#endif
SDKHooks_DropWeapon(bombOwner, g_iBombEntity, NULL_VECTOR, NULL_VECTOR);
}
else
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[ForceBombDrop] There is no bomb owner");
DebugMessage(dmsg);
#endif
}
}
else
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[ForceBombDrop] Bomb index is %i - doing nothing", g_iBombEntity);
DebugMessage(dmsg);
#endif
}
}
/**
* Reset PlayerMoney to mp_startmoney
*
* @param client of which money needs to be reset
*/
ResetPlayerMoney(client)
{
if(IsValidClient(client))
SetEntData(client, g_iPlayerMoney, g_iStartMoney, 4, true);
}
/**
* Initialize pointer to Player m_iAccount property of Player which stores PlayerMoney
*/
InitPlayerMoney()
{
g_iPlayerMoney = FindSendPropOffs("CCSPlayer", "m_iAccount");
if (g_iPlayerMoney == -1)
{
SetFailState("[InitPlayerMoney] Could not find m_iAccount");
}
}
/**
* Initialize g_iStartMoney
*/
InitStartMoney()
{
new Handle:cvhStartMoney = FindConVar("mp_startmoney");
if (cvhStartMoney == INVALID_HANDLE)
{
SetFailState("Unable to hook mp_startmoney");
}
g_iStartMoney = GetConVarInt(cvhStartMoney);
CloseHandle(cvhStartMoney);
}
/**
* Respawn a Player while ensuring join team command is ignored if triggered
*/
RespawnPlayer(client)
{
g_PluginTeamSwitch[client] = true;
CS_RespawnPlayer(client);
g_PluginTeamSwitch[client] = false;
}
/**
* Calculates & Sets the menu time based on the freezetime convar
*/
SetMenuTime(Handle:cvarFreezeTime)
{
g_iMenuTime = (3 + GetConVarInt(cvarFreezeTime)) / 2;
}
/******************
* Convar changes *
******************/
public OnFreezeTimeChanged(Handle:cvar, const String:oldVal[], const String:newVal[])
{
SetMenuTime(cvar);
}
public OnBombsiteChanged(Handle:cvar, const String:oldVal[], const String:newVal[])
{
g_sBombsite[0] = '\0';
GetConVarString(cvar, g_sBombsite, sizeof(g_sBombsite));
if (g_iBombsiteA == INVALID_ENT_REFERENCE || g_iBombsiteB == INVALID_ENT_REFERENCE)
{
GetBombsiteInfo();
}
if (g_iBombsiteA != INVALID_ENT_REFERENCE && g_iBombsiteB != INVALID_ENT_REFERENCE)
{
//Disable/Enable bombsites and setting target bombsite hooks
DoBombsiteControl();
}
}
/****************
* Menu Control *
****************/
/**
* Function called to display the Terrorist Selection Menu to the MVP
*/
public DisplayMenuToMVP()
{
new mvp = GetClientFromSerial(g_RoundMVPSerial);
if (IsValidClient(mvp) && !IsFakeClient(mvp))
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[DisplayMenuToMVP] %N is the MVP and is being shown the menu", mvp);
DebugMessage(dmsg);
#endif
// Display the terrorist selection menu to the player who killed more than 1 CT
DisplayTerroristSelectionMenu(mvp, "Menu1", MenuHandler:TerroristSelectionMenuHandler, false);
}
else
{
SwitchRandomTerrorists();
}
}
/**
* Timer Action called when more Terrorists need to be selected
*
* @param timer handle to the timer triggering the action
* @param serial serial of the client to which the first menu was displayed
*/
public Action:MoreTerroristsMenuTimer(Handle:timer, any:serial) {
new client = GetClientFromSerial(serial);
if (IsValidClient(client))
{
// Display the terrorist selection menu again
DisplayTerroristSelectionMenu(client, "Menu2", MenuHandler:TerroristSelectionMenuHandler, true);
}
return Plugin_Continue;
}
/**
* Display a menu with all Terrorists eligible for switching to CT
*
* @param client the client the menu needs to be displayed for
* @param titleNamedPhrase the named Phrase from the translations file for menu title
* @param terroristSelectionMenuHandler the function called to handle MenuActions
*
* @note uses the global g_iMenuTime variable for menu display time
**/
public DisplayTerroristSelectionMenu(client, String:titleNamedPhrase[], MenuHandler:terroristSelectionMenuHandler, bool:checkCTCount)
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[DisplayTerroristSelectionMenu] CT Count %i, check count = %s", GetTeamClientCount(CS_TEAM_CT), checkCTCount ? "true" : "false");
DebugMessage(dmsg);
#endif
if (!checkCTCount || GetTeamClientCount(CS_TEAM_CT) < 3)
{
new Handle:menu = CreateMenu(terroristSelectionMenuHandler);
SetMenuTitle(menu, "%t", titleNamedPhrase);
SetMenuExitButton(menu, true);
AddTerroristsToMenu(menu);
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[DisplayTerroristSelectionMenu] DisplayMenu('%N','%t',%i)", client, titleNamedPhrase, g_iMenuTime);
DebugMessage(dmsg);
#endif
DisplayMenu(menu, client, g_iMenuTime);
}
}
/**
* Function called to handle MenuActions raised by the Terrorist Selection Menu
* switch the selected T (selected item) to CT and start timer if more Ts need to be switched
* if menu was cancelled, switch random Ts
*
* @param menu The menu being acted upon.
* @param action The action of the menu.
* @param param1 First action parameter (usually the client).
* @param param2 Second action parameter (usually the item selected).
*/
public MenuHandler:TerroristSelectionMenuHandler(Handle:menu, MenuAction:action, param1, param2)
{
if (action == MenuAction_Select)
{
new String:info[32];
GetMenuItem(menu, param2, info, sizeof(info));
new UserID = StringToInt(info);
new client = GetClientOfUserId(UserID);
if (client > 0)
{
if (GetTeamClientCount(CS_TEAM_CT) < 3)
{
SwitchPlayerTeam(client, CS_TEAM_CT);
if (IsPlayerAlive(client))
{
RespawnPlayer(client);
}
g_iTeamSwitchCount++;
}
else
{
PrintToChat(client, "\x01 \x09[\x04%t\x09]\x01 %t", "Prefix", "TooLate");
}
}
//after first choice, see if more Ts need to be switched
CreateTimer(0.2, MoreTerroristsMenuTimer, GetClientSerial(param1));
}
else if (action == MenuAction_Cancel)
{
//reasons a menu can be cancelled: MenuCancel_Timeout, .. Exit, Disconnected, NoDisplay, ..
SwitchRandomTerrorists();
}
else if (action == MenuAction_End)
{
CloseHandle(menu);
}
}
/**
* Adds all iligible Terrorists to Menu
*
* @param menu Handle of the menu to add terrorists to.
* @noreturn
* @note displays Name & user_id, value is a string containing the user_id only
*/
public AddTerroristsToMenu(Handle:menu)
{
new String:user_id[12];
new String:name[MAX_NAME_LENGTH];
new String:display[MAX_NAME_LENGTH+15];
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[AddTerroristsToMenu] Entered");
DebugMessage(dmsg);
#endif
for (new client = 1; client <= MaxClients; client++)
{
if (IsValidClient(client) && GetClientTeam(client) == CS_TEAM_T && !g_ChangeTeamImmune[client])
{
// Retrieve and store the UserID of player index i as a string
IntToString(GetClientUserId(client), user_id, sizeof(user_id));
GetClientName(client, name, sizeof(name));
Format(display, sizeof(display), "%s (%s)", name, user_id);
AddMenuItem(menu, user_id, display);
}
}
}
/********************
* Bombsite Control *
********************/
/**
* populates bombsite A&B entity indexes and bombsite center vectors
*
* @noreturn
*/
stock GetBombsiteInfo()
{
// ===================================================================================================================================
// Thanks to exvel - http://forums.alliedmods.net/showthread.php?p=1287116
// Provided the GetBombsiteInfo() and stock bool:IsVecBetween
// ===================================================================================================================================
new index = -1;
index = FindEntityByClassname(index, "cs_player_manager");
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[GetBombsiteIndexes] cs_player_manager index = %i (shouldn't be -1)", index);
DebugMessage(dmsg);
#endif
if (index != -1)
{
GetEntPropVector(index, Prop_Send, "m_bombsiteCenterA", g_vecBombsiteCenterA);
GetEntPropVector(index, Prop_Send, "m_bombsiteCenterB", g_vecBombsiteCenterB);
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[GetBombsiteIndexes] m_bombsiteCenterA vec = %f %f %f m_bombsiteCenterB vec = %f %f %f", g_vecBombsiteCenterA[0], g_vecBombsiteCenterA[1], g_vecBombsiteCenterA[2], g_vecBombsiteCenterB[0], g_vecBombsiteCenterB[1], g_vecBombsiteCenterB[2]);
DebugMessage(dmsg);
#endif
}
//re-use index for func_bomb_target entity
index = -1;
//for each bombsite found, identify if A or B
while ((index = FindEntityByClassname(index, "func_bomb_target")) != -1)
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[GetBombsiteIndexes] func_bomb_target index = %i (shouldn't be -1)", index);
DebugMessage(dmsg);
#endif
new Float:vecBombsiteMin[3];
new Float:vecBombsiteMax[3];
GetEntPropVector(index, Prop_Send, "m_vecMins", vecBombsiteMin);
GetEntPropVector(index, Prop_Send, "m_vecMaxs", vecBombsiteMax);
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[GetBombsiteIndexes] vecBombsiteMin vec = %f %f %f vecBombsiteMax vec = %f %f %f", vecBombsiteMin[0], vecBombsiteMin[1], vecBombsiteMin[2], vecBombsiteMax[0], vecBombsiteMax[1], vecBombsiteMax[2]);
DebugMessage(dmsg);
#endif
if (IsVecBetween(g_vecBombsiteCenterA, vecBombsiteMin, vecBombsiteMax))
{
g_iBombsiteA = index;
}
if (IsVecBetween(g_vecBombsiteCenterB, vecBombsiteMin, vecBombsiteMax))
{
g_iBombsiteB = index;
}
}
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[GetBombsiteIndexes] Indexes are: a=%i, b=%i", g_iBombsiteA, g_iBombsiteB);
DebugMessage(dmsg);
#endif
}
/**
* Disable a bombsite depending on the target bombsite and Hook bombsite touch for target bombsite
*/
stock DoBombsiteControl()
{
//ensure target bombsite touch is hooked & other bombsite is disabled
if (StrEqual(g_sBombsite, "A", false))
{
EnableBombsite(g_iBombsiteA);
DisableBombsite(g_iBombsiteB);
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[DoBombsiteControl] Disabling bomsite B (index:%i)", g_iBombsiteB);
DebugMessage(dmsg);
#endif
if (g_bUseBotControl && g_hBotMoveTo != INVALID_HANDLE)
{
UnhookBombsiteTouch(g_iBombsiteB, OnTouchBombsite);
HookBombsiteTouch(g_iBombsiteA, OnTouchBombsite);
}
}
else if (StrEqual(g_sBombsite, "B", false))
{
EnableBombsite(g_iBombsiteB);
DisableBombsite(g_iBombsiteA);
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[DoBombsiteControl] Disabling bomsite A (index:%i)", g_iBombsiteA);
DebugMessage(dmsg);
#endif
if (g_bUseBotControl && g_hBotMoveTo != INVALID_HANDLE)
{
UnhookBombsiteTouch(g_iBombsiteA, OnTouchBombsite);
HookBombsiteTouch(g_iBombsiteB, OnTouchBombsite);
}
}
}
/**
* Attempts to disable the bombsite entity specified
*
* @param bombsite the index of the bombsite entity to disable
* @return 0 if bombsite index was valid, -1 if bombsite index was invalid
*
*/
stock DisableBombsite(bombsite)
{
if (IsValidEntity(bombsite))
{
AcceptEntityInput(bombsite, "Disable");
return 0;
}
else
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[DisableBombsite] bombsite [%i] is not valid", bombsite);
DebugMessage(dmsg);
#endif
return -1;
}
}
/**
* Attempts to enable the bombsite entity specified
*
* @param bombsite the index of the bombsite entity to enable
* @return 0 if bombsite index was valid, -1 if bombsite index was invalid
*
*/
stock EnableBombsite(bombsite)
{
if (IsValidEntity(bombsite))
{
AcceptEntityInput(bombsite, "Enable");
return 0;
}
else
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[EnableBombsite] bombsite [%i] is not valid", bombsite);
DebugMessage(dmsg);
#endif
return -1;
}
}
/**
* Hooks Entity Touch event for bombsite to callback function
*
* @param bombsite the index of the bombsite entity to hook
* @param callback the function to call when the bombsite is touched
* @return 0 if bombsite index was valid, -1 if bombsite index was invalid
*
*/
stock HookBombsiteTouch(bombsite, SDKHookCB:callback)
{
if (IsValidEntity(bombsite))
{
SDKHook(bombsite, SDKHook_Touch, callback);
return 0;
}
else
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[HookBombsiteTouch] bombsite [%i] is not valid", bombsite);
DebugMessage(dmsg);
#endif
return -1;
}
}
/**
* Unhooks Entity Touch event for bombsite from callback function
*
* @param bombsite the index of the bombsite entity to hook
* @param callback the function to call when the bombsite is touched
* @return 0 if bombsite index was valid, -1 if bombsite index was invalid
*
*/
stock UnhookBombsiteTouch(bombsite, SDKHookCB:callback)
{
if (IsValidEntity(bombsite))
{
SDKUnhook(bombsite, SDKHook_Touch, callback);
return 0;
}
else
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[UnHookBombsiteTouch] bombsite [%i] is not valid", bombsite);
DebugMessage(dmsg);
#endif
return -1;
}
}
/**************
* BOT Control *
***************/
/**
* Attempts to add a bot to the team specified (CS_TEAM_T or CS_TEAM_CT)
*
* @param team team to which a bot needs to be added (CS_TEAM_T or CS_TEAM_CT)
* @return 0 if team was valid, -1 if team was invalid
*
* @note this function does not feed back if a bot was actually added or not
*/
AddBot(team)
{
switch(team)
{
case(CS_TEAM_T):
{
ServerCommand("bot_add_t 3 100 *");
return 0;
}
case(CS_TEAM_CT):
{
ServerCommand("bot_add_ct 3 100 *");
return 0;
}
}
return -1;
}
/**
* Attempts to kick a bot from the team specified (CS_TEAM_T or CS_TEAM_CT)
*
* @param team team from which a bot needs to be kicked (CS_TEAM_T or CS_TEAM_CT)
* @return 0 if team was valid, -1 if team was invalid
*
* @note this function does not feed back if a bot was actually kicked or not
*/
KickBot(team)
{
switch(team)
{
case(CS_TEAM_T):
{
ServerCommand("bot_kick t");
return 0;
}
case(CS_TEAM_CT):
{
ServerCommand("bot_kick ct");
return 0;
}
}
return -1;
}
/**
* Attempts to direct a bot to the bombsite specified ("A" or "B")
*
* @param bot bot to direct
* @param bombsite string indicating the bombsite to direct to ("A" or "B")
* @return 0 if bombsite was valid, -1 if bombsite was invalid
*
* @note this function depends on bombsite center vectors populated by GetBombsiteInfo()
*/
DirectBotToBombsite(bot, String:bombsite[])
{
///TODO: on some bombsites, the center causes BOTs to get stuck (Inferno fountain)
if (StrEqual(bombsite, "A", false))
{
CCSBotMoveTo(bot, g_vecBombsiteCenterA);
return 0;
}
else if (StrEqual(bombsite, "B", false))
{
CCSBotMoveTo(bot, g_vecBombsiteCenterB);
return 0;
}
else
{
return -1;
}
}
/**
* Initialize Handle to CSSBotMoveTo function from gamedata signature
* @noreturn
*
* @note this function depends on function signatures in plugin.brush gamedata file
*/
InitCSSBotMoveTo()
{
#if _DEBUG
Format(dmsg, sizeof(dmsg), "[InitCSSBotMoveTo] Entered");
DebugMessage(dmsg);
#endif
new Handle:hGameConf = LoadGameConfigFile("plugin.brush");
StartPrepSDKCall(SDKCall_Player);
PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "CCSBotMoveTo");
PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
g_hBotMoveTo = EndPrepSDKCall();
CloseHandle(hGameConf);
if (g_hBotMoveTo == INVALID_HANDLE)
{
LogError("Unable to prepare SDKCall for CCSBotMoveTo, contact the author for a gamedata update");
}
}
/**
* Where to tell the bot to move to
* @param bot Index of bot
* @param origin Float position to have bot move to
* @noreturn
*/
CCSBotMoveTo(bot, Float:origin[3]) {
if (!IsValidClient(bot) || !IsFakeClient(bot))
{
return;
}
SDKCall(g_hBotMoveTo, bot, origin, 0);
}
/*********************************
* Circular Queue Implementation *
**********************************/
/**
* Peek at the next client in the Queue without removing from Queue
* @return Returns the next client in the queue or -1 if queue is empty.
*/
public PeekQueue()
{
if (g_QueueTail == g_QueueHead)
return -1;
return g_ClientQueue[g_QueueHead];
}
/**
* Initialize the Queue
* @noreturn
*/
public InitQueue()
{
//no need to initialize..
//for(new i = 1; i <= MaxClients; i++)
//{
// g_ClientQueue[i] = 0;
//}
}
/**
* Push a Client into the Queue (don't add a client if already in queue)
* @param client The client to push into the queue
* @return returns 0 if added or -1 if already in queue
*/
public EnQueue(client)
{
//if (g_QueueTail + 1) mod g_QueueSize equals head, then the queue is full. - not sure if possible, so ignoring
if(FindInQueue(client) != -1)
return -1;//ensure client is not in queue already
g_ClientQueue[g_QueueTail] = client;
g_QueueTail = (g_QueueTail + 1) % g_QueueSize;
return 0;
}
/**
* Finds a client in the Queue
* @param client The client to find in the queue
* @return index of client in internal array, -1 if not in queue
*/
public FindInQueue(client)
{
new i = g_QueueHead, bool:found = false;
while(i != g_QueueTail && !found)
{
if (client == g_ClientQueue[i])
{
//client found
found = true;
}else
{
//keep looking
i = (i + 1) % g_QueueSize;
}
}
return found ? i : -1;
}
/**
* Drops a client from the Queue
* @param client The client to drop from the queue
* @return 0 if success or -1 if not in queue
*/
public DropFromQueue(client)
{
//find client cur position in queue
new cur = FindInQueue(client);
if (cur == -1)
{
//client was not found in the queue
return -1;
}
else if (cur == g_QueueHead)
{
//dropping client from queue is same as deQueue, head moves forward on deletion
g_QueueHead = (cur + 1) % g_QueueSize;
}else
{
//shift all clients forward in queue
new next, prev = cur == 0 ? g_QueueSize : cur - 1;
while(cur != g_QueueTail)
{
next = (cur + 1) % g_QueueSize;
if(next != g_QueueTail)
{
//move next client forward to cur
g_ClientQueue[cur] = g_ClientQueue[next];
}
prev = cur;
cur = next;
}
//tail needs to update as well
g_QueueTail = prev;
}
return 0;
}
/**
* Notify each queue position & total queue size to each client in queue
* @noreturn
*/
public NotifyQueue()
{
new i = g_QueueHead, position = 0, count = GetQueueLength();
while(i != g_QueueTail)
{
if(IsValidClient(g_ClientQueue[i]))
{
PrintToChat(g_ClientQueue[i], "\x01 \x09[\x04Queue\x09]\x01 Position: %i", position+1);
PrintToChat(g_ClientQueue[i], "\x01 \x09[\x04Queue\x09]\x01 Total clients in Queue: %i", count);
}
position++;
i = (i + 1) % g_QueueSize;
}
}
/**
* Get queue length, does not validate clients in queue
* @return Queue length
*/
public GetQueueLength()
{
new i = g_QueueHead, count = 0;
while(i != g_QueueTail)
{
count++;
i = (i + 1) % g_QueueSize;
}
return count;
}
/**
* Test if queue is empty
* @return true if queue is empty, false if queue is not empty
*/
public IsQueueEmpty()
{
return g_QueueTail == g_QueueHead;
}
/**
* Fetch next client from queue
* @return Returns the next client from the queue or -1 if queue is empty.
*/
public DeQueue()
{
//check if queue is empty
if (g_QueueTail == g_QueueHead)
return -1;
//head advances on dequeue
new client = g_ClientQueue[g_QueueHead];
g_QueueHead = (g_QueueHead + 1) % g_QueueSize;
return client;
}
/***************************
* Stocks *
* & *
* SMLib Functions (berni) *
****************************/
/**
* Function to identify if a client is valid and in game
*
* @param client Vector to be evaluated
* @return true if valid client, false if not
*/
stock bool:IsValidClient(client)
{
if(client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
return true;
return false;
}
/**
* Gets the owner (usually a client) of the weapon
*
* @param weapon Weapon Entity.
* @return Owner of the weapon or INVALID_ENT_REFERENCE if the weapon has no owner.
*/
stock Weapon_GetOwner(weapon)
{
return GetEntPropEnt(weapon, Prop_Data, "m_hOwner");
}
/**
* Returns a random, uniform Integer number in the specified (inclusive) range.
* This is safe to use multiple times in a function.
* The seed is set automatically for each plugin.
* Rewritten by MatthiasVance, thanks.
*
* @param min Min value used as lower border
* @param max Max value used as upper border
* @return Random Integer number between min and max
*/
stock Math_GetRandomInt(min, max)
{
new random = GetURandomInt();
if (random == 0)
{
random++;
}
return RoundToCeil(float(random) / (float(SIZE_OF_INT) / float(max - min + 1))) + min - 1;
}
/**
* Function to evaluate if a vector is between a certain min and max
*
* @param vecVector Vector to be evaluated
* @param vecMin Vector of minimum
* @param vecMax Vector of maximum
* @return true if between min and max, else false
*/
stock bool:IsVecBetween(const Float:vecVector[3], const Float:vecMin[3], const Float:vecMax[3])
{
return ( (vecMin[0] <= vecVector[0] <= vecMax[0]) &&
(vecMin[1] <= vecVector[1] <= vecMax[1]) &&
(vecMin[2] <= vecVector[2] <= vecMax[2]) );
}
/**
* Function to clear/kill the timer and set to INVALID_HANDLE if it's still active
*
* @param timer Handle of the timer
* @noreturn
*/
stock ClearTimer(&Handle:timer)
{
if (timer != INVALID_HANDLE)
{
KillTimer(timer);
timer = INVALID_HANDLE;
}
}
/**
* Function to handle un/freezing of a player
*
* @param client ClientID of player to un/freeze
* @param freeze True to freeze, false to unfreeze
* @noreturn
*/
FreezePlayer(client, bool:freeze)
{
if (IsValidEntity(client))
{
if (freeze)
{
SetEntityMoveType(client, MOVETYPE_NONE);
//SetEntityRenderColor(client, 0, 128, 255, 192);
g_IsPlayerFrozen[client] = true;
}else
{
SetEntityMoveType(client, MOVETYPE_WALK);
//SetEntityRenderColor(client, 255, 255, 255, 255);
g_IsPlayerFrozen[client] = false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment