Skip to content

Instantly share code, notes, and snippets.

@so0k
Last active May 31, 2016 15:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save so0k/8201314 to your computer and use it in GitHub Desktop.
Save so0k/8201314 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;
}
}
}
@JYASKO
Copy link

JYASKO commented May 31, 2016

Which of your source codes for brush are most updated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment