/*
* shavit's Timer - Core
* by: shavit
*
* This file is part of shavit's Timer.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*
*/
#include
#include
#include
#include
#undef REQUIRE_PLUGIN
#include
#pragma semicolon 1
#pragma dynamic 131072 // let's make stuff faster
#pragma newdecls required // We're at 2015 :D
//#define DEBUG
// game type (CS:S/CS:GO)
ServerGame gSG_Type = Game_Unknown;
// database handle
Handle gH_SQL = null;
// forwards
Handle gH_Forwards_Start = null;
Handle gH_Forwards_Stop = null;
Handle gH_Forwards_Finish = null;
Handle gH_Forwards_OnRestart = null;
Handle gH_Forwards_OnPause = null;
Handle gH_Forwards_OnResume = null;
// timer variables
bool gB_TimerEnabled[MAXPLAYERS+1];
float gF_StartTime[MAXPLAYERS+1];
float gF_PauseStartTime[MAXPLAYERS+1];
float gF_PauseTotalTime[MAXPLAYERS+1];
bool gB_ClientPaused[MAXPLAYERS+1];
int gI_Jumps[MAXPLAYERS+1];
BhopStyle gBS_Style[MAXPLAYERS+1];
bool gB_Auto[MAXPLAYERS+1];
// late load
bool gB_Late;
// zones lateload support
bool gB_Zones;
// cvars
ConVar gCV_Autobhop = null;
ConVar gCV_Leftright = null;
ConVar gCV_Restart = null;
ConVar gCV_Pause = null;
public Plugin myinfo =
{
name = "[shavit] Core",
author = "shavit",
description = "The core for shavit's bhop timer.",
version = SHAVIT_VERSION,
url = "http://forums.alliedmods.net/member.php?u=163134"
}
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
// get game type
CreateNative("Shavit_GetGameType", Native_GetGameType);
// get database handle
CreateNative("Shavit_GetDB", Native_GetDB);
// timer natives
CreateNative("Shavit_StartTimer", Native_StartTimer);
CreateNative("Shavit_StopTimer", Native_StopTimer);
CreateNative("Shavit_FinishMap", Native_FinishMap);
CreateNative("Shavit_GetTimer", Native_GetTimer);
CreateNative("Shavit_PauseTimer", Native_PauseTimer);
CreateNative("Shavit_ResumeTimer", Native_ResumeTimer);
MarkNativeAsOptional("Shavit_GetGameType");
MarkNativeAsOptional("Shavit_GetDB");
MarkNativeAsOptional("Shavit_StartTimer");
MarkNativeAsOptional("Shavit_StopTimer");
MarkNativeAsOptional("Shavit_FinishMap");
MarkNativeAsOptional("Shavit_GetTimer");
// prevent errors from shavit-zones
MarkNativeAsOptional("Shavit_InsideZone");
// registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins
RegPluginLibrary("shavit");
gB_Late = late;
return APLRes_Success;
}
public void OnPluginStart()
{
// forwards
gH_Forwards_Start = CreateGlobalForward("Shavit_OnStart", ET_Event, Param_Cell);
gH_Forwards_Stop = CreateGlobalForward("Shavit_OnStop", ET_Event, Param_Cell);
gH_Forwards_Finish = CreateGlobalForward("Shavit_OnFinish", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
gH_Forwards_OnRestart = CreateGlobalForward("Shavit_OnRestart", ET_Event, Param_Cell);
gH_Forwards_OnPause = CreateGlobalForward("Shavit_OnPause", ET_Event, Param_Cell);
gH_Forwards_OnResume = CreateGlobalForward("Shavit_OnResume", ET_Event, Param_Cell);
// game types
EngineVersion evType = GetEngineVersion();
if(evType == Engine_CSS)
{
gSG_Type = Game_CSS;
}
else if(evType == Engine_CSGO)
{
gSG_Type = Game_CSGO;
}
else
{
SetFailState("This plugin was meant to be used in CS:S and CS:GO *only*.");
}
// database connections
SQL_DBConnect();
// hooks
HookEvent("player_jump", Player_Jump);
HookEvent("player_death", Player_Death);
HookEvent("player_team", Player_Death);
HookEvent("player_spawn", Player_Death);
// commands START
// style
RegConsoleCmd("sm_style", Command_Style, "Choose your bhop style.");
RegConsoleCmd("sm_styles", Command_Style, "Choose your bhop style.");
//RegConsoleCmd("sm_s", Command_Style, "Choose your bhop style.");
RegConsoleCmd("sm_diff", Command_Style, "Choose your bhop style.");
RegConsoleCmd("sm_difficulty", Command_Style, "Choose your bhop style.");
// forwards
RegConsoleCmd("sm_n", Command_Forwards, "Style shortcut: Forwards");
RegConsoleCmd("sm_forwards", Command_Forwards, "Style shortcut: Forwards");
RegConsoleCmd("sm_normal", Command_Forwards, "Style shortcut: Forwards");
// sideways
RegConsoleCmd("sm_sw", Command_Sideways, "Style shortcut: Sideways");
RegConsoleCmd("sm_sideways", Command_Sideways, "Style shortcut: Sideways");
// timer start
RegConsoleCmd("sm_s", Command_StartTimer, "Start your timer.");
RegConsoleCmd("sm_start", Command_StartTimer, "Start your timer.");
RegConsoleCmd("sm_r", Command_StartTimer, "Start your timer.");
RegConsoleCmd("sm_restart", Command_StartTimer, "Start your timer.");
// timer stop
RegConsoleCmd("sm_stop", Command_StopTimer, "Stop your timer.");
// timer pause / resume
RegConsoleCmd("sm_pause", Command_TogglePause, "Toggle pause.");
RegConsoleCmd("sm_unpause", Command_TogglePause, "Toggle pause.");
RegConsoleCmd("sm_resume", Command_TogglePause, "Toggle pause");
// autobhop toggle
RegConsoleCmd("sm_auto", Command_AutoBhop, "Toggle autobhop.");
RegConsoleCmd("sm_autobhop", Command_AutoBhop, "Toggle autobhop.");
// commands END
#if defined DEBUG
RegConsoleCmd("sm_finishtest", Command_FinishTest);
#endif
CreateConVar("shavit_version", SHAVIT_VERSION, "Plugin version.", FCVAR_PLUGIN|FCVAR_NOTIFY|FCVAR_DONTRECORD);
gCV_Autobhop = CreateConVar("shavit_core_autobhop", "1", "Enable autobhop?", FCVAR_PLUGIN|FCVAR_NOTIFY);
gCV_Leftright = CreateConVar("shavit_core_blockleftright", "1", "Block +left/right?", FCVAR_PLUGIN|FCVAR_NOTIFY);
gCV_Restart = CreateConVar("shavit_core_restart", "1", "Allow commands that restart the timer?", FCVAR_PLUGIN|FCVAR_NOTIFY);
gCV_Pause = CreateConVar("shavit_core_pause", "1", "Allow pausing?", FCVAR_PLUGIN|FCVAR_NOTIFY);
AutoExecConfig();
// late
if(gB_Late)
{
OnAdminMenuReady(null);
for(int i = 1; i <= MaxClients; i++)
{
OnClientPutInServer(i);
}
}
gB_Zones = LibraryExists("shavit-zones");
}
public void OnLibraryAdded(const char[] name)
{
if(StrEqual(name, "shavit-zones"))
{
gB_Zones = true;
}
}
public void OnLibraryRemoved(const char[] name)
{
if(StrEqual(name, "shavit-zones"))
{
gB_Zones = false;
}
}
public void OnAdminMenuReady(Handle topmenu)
{
Handle hTopMenu = INVALID_HANDLE;
if(LibraryExists("adminmenu") && ((hTopMenu = GetAdminTopMenu()) != INVALID_HANDLE))
{
AddToTopMenu(hTopMenu, "Timer Commands", TopMenuObject_Category, CategoryHandler, INVALID_TOPMENUOBJECT);
}
}
public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength)
{
if(action == TopMenuAction_DisplayTitle)
{
FormatEx(buffer, maxlength, "Timer Commands:");
}
else if (action == TopMenuAction_DisplayOption)
{
FormatEx(buffer, maxlength, "Timer Commands");
}
}
public void OnMapStart()
{
// cvar forcing
ConVar cvBhopping = FindConVar("sv_enablebunnyhopping");
SetConVarBool(cvBhopping, true);
ConVar cvAA = FindConVar("sv_airaccelerate");
SetConVarInt(cvAA, 2000);
}
public Action Command_StartTimer(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
if(!gCV_Restart.BoolValue)
{
if(args != -1)
{
char sCommand[16];
GetCmdArg(0, sCommand, 16);
ReplyToCommand(client, "%s The command (\x03%s\x01) is disabled.", PREFIX, sCommand);
}
return Plugin_Handled;
}
Call_StartForward(gH_Forwards_OnRestart);
Call_PushCell(client);
Call_Finish();
StartTimer(client);
return Plugin_Handled;
}
public Action Command_StopTimer(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
StopTimer(client);
return Plugin_Handled;
}
public Action Command_TogglePause(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
if(!gCV_Pause.BoolValue)
{
char sCommand[16];
GetCmdArg(0, sCommand, 16);
ReplyToCommand(client, "%s The command (\x03%s\x01) is disabled.", PREFIX, sCommand);
return Plugin_Handled;
}
if(!(GetEntityFlags(client) & FL_ONGROUND))
{
ReplyToCommand(client, "%s You are not allowed to pause when not on ground.", PREFIX);
return Plugin_Handled;
}
if(gB_ClientPaused[client])
{
ResumeTimer(client);
}
else
{
PauseTimer(client);
}
return Plugin_Handled;
}
#if defined DEBUG
public Action Command_FinishTest(int client, int args)
{
Shavit_FinishMap(client);
return Plugin_Handled;
}
#endif
public Action Command_AutoBhop(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
gB_Auto[client] = !gB_Auto[client];
ReplyToCommand(client, "%s Autobhop %s\x01.", PREFIX, gB_Auto[client]? "\x04enabled":"\x02disabled");
return Plugin_Handled;
}
public Action Command_Style(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
Handle menu = CreateMenu(StyleMenu_Handler);
SetMenuTitle(menu, "Choose a style:");
AddMenuItem(menu, "forwards", "Forwards");
AddMenuItem(menu, "sideways", "Sideways");
SetMenuExitButton(menu, true);
DisplayMenu(menu, client, 20);
return Plugin_Handled;
}
public int StyleMenu_Handler(Handle menu, MenuAction action, int param1, int param2)
{
if(action == MenuAction_Select)
{
char info[16];
GetMenuItem(menu, param2, info, 16);
if(StrEqual(info, "forwards"))
{
Command_Forwards(param1, 0);
}
else if(StrEqual(info, "sideways"))
{
Command_Sideways(param1, 0);
}
}
else if(action == MenuAction_End)
{
CloseHandle(menu);
}
}
public Action Command_Forwards(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
gBS_Style[client] = Style_Forwards;
ReplyToCommand(client, "%s You have selected to play \x03Forwards", PREFIX);
StopTimer(client);
Command_StartTimer(client, -1);
return Plugin_Handled;
}
public Action Command_Sideways(int client, int args)
{
if(!IsValidClient(client))
{
return Plugin_Handled;
}
gBS_Style[client] = Style_Sideways;
ReplyToCommand(client, "%s You have selected to play \x03Sideways", PREFIX);
StopTimer(client);
Command_StartTimer(client, -1);
return Plugin_Handled;
}
public void Player_Jump(Handle event, const char[] name, bool dontBroadcast)
{
int userid = GetEventInt(event, "userid");
int client = GetClientOfUserId(userid);
if(gB_TimerEnabled[client])
{
gI_Jumps[client]++;
}
/* I'm just gonna let server owners do it within the .cfg file.
SetEntPropFloat(client, Prop_Send, "m_flStamina", 0.0);
*/
}
public void Player_Death(Handle event, const char[] name, bool dontBroadcast)
{
int userid = GetEventInt(event, "userid");
int client = GetClientOfUserId(userid);
ResumeTimer(client);
StopTimer(client);
}
public int Native_GetGameType(Handle handler, int numParams)
{
return view_asgSG_Type;
}
public int Native_GetDB(Handle handler, int numParams)
{
SetNativeCellRef(1, gH_SQL);
}
// I can't return booleans :/
public int Native_GetTimer(Handle handler, int numParams)
{
// 1 - client
int client = GetNativeCell(1);
// 2 - time
float time = CalculateTime(client);
SetNativeCellRef(2, time);
// 3 - jumps
SetNativeCellRef(3, gI_Jumps[client]);
// 4 - style
SetNativeCellRef(4, gBS_Style[client]);
// 5 - enabled?
SetNativeCellRef(5, gB_TimerEnabled[client]);
}
public int Native_StartTimer(Handle handler, int numParams)
{
int client = GetNativeCell(1);
if(!IsFakeClient(client))
{
StartTimer(client);
Call_StartForward(gH_Forwards_Start);
Call_PushCell(client);
Call_Finish();
}
}
public int Native_StopTimer(Handle handler, int numParams)
{
int client = GetNativeCell(1);
StopTimer(client);
Call_StartForward(gH_Forwards_Stop);
Call_PushCell(client);
Call_Finish();
}
public int Native_FinishMap(Handle handler, int numParams)
{
int client = GetNativeCell(1);
Call_StartForward(gH_Forwards_Finish);
Call_PushCell(client);
Call_PushCell(view_asgBS_Style[client]);
Call_PushCell(CalculateTime(client));
Call_PushCell(gI_Jumps[client]);
Call_Finish();
StopTimer(client);
}
public int Native_PauseTimer(Handle handler, int numParams)
{
int client = GetNativeCell(1);
PauseTimer(client);
}
public int Native_ResumeTimer(Handle handler, int numParams)
{
int client = GetNativeCell(1);
ResumeTimer(client);
}
public void StartTimer(int client)
{
if(!IsValidClient(client) || IsFakeClient(client))
{
return;
}
gB_TimerEnabled[client] = true;
gI_Jumps[client] = 0;
gF_StartTime[client] = GetEngineTime();
gF_PauseTotalTime[client] = 0.0;
gB_ClientPaused[client] = false;
}
public void StopTimer(int client)
{
if(!IsValidClient(client))
{
return;
}
gB_TimerEnabled[client] = false;
gI_Jumps[client] = 0;
gF_StartTime[client] = 0.0;
gF_PauseTotalTime[client] = 0.0;
gB_ClientPaused[client] = false;
}
public void PauseTimer(int client)
{
if(!IsValidClient(client) || IsFakeClient(client))
{
return;
}
gF_PauseStartTime[client] = GetEngineTime();
gB_ClientPaused[client] = true;
Call_StartForward(gH_Forwards_OnPause);
Call_PushCell(client);
Call_Finish();
}
public void ResumeTimer(int client)
{
if(!IsValidClient(client) || IsFakeClient(client))
{
return;
}
gF_PauseTotalTime[client] += GetEngineTime() - gF_PauseStartTime[client];
gB_ClientPaused[client] = false;
Call_StartForward(gH_Forwards_OnResume);
Call_PushCell(client);
Call_Finish();
}
public float CalculateTime(int client)
{
if(!gB_ClientPaused[client])
{
return GetEngineTime() - gF_StartTime[client] - gF_PauseTotalTime[client];
}
else
{
return gF_PauseStartTime[client] - gF_StartTime[client] - gF_PauseTotalTime[client];
}
}
public void OnClientDisconnect(int client)
{
StopTimer(client);
}
public void OnClientPutInServer(int client)
{
gB_Auto[client] = true;
StopTimer(client);
gBS_Style[client] = Style_Forwards;
if(!IsValidClient(client) || IsFakeClient(client) || gH_SQL == null)
{
return;
}
// SteamID3 is cool, 2015 B O Y S
char sAuthID3[32];
GetClientAuthId(client, AuthId_Steam3, sAuthID3, 32);
char sName[MAX_NAME_LENGTH];
GetClientName(client, sName, MAX_NAME_LENGTH);
int iLength = ((strlen(sName) * 2) + 1);
char[] sEscapedName = new char[iLength]; // dynamic arrays! I love you, SourcePawn 1.7!
SQL_EscapeString(gH_SQL, sName, sEscapedName, iLength);
char sIP[32];
GetClientIP(client, sIP, 32);
char sCountry[45];
GeoipCountry(sIP, sCountry, 45);
if(StrEqual(sCountry, ""))
{
FormatEx(sCountry, 45, "Local Area Network");
}
// too lazy to calculate if it can go over 256 so let's not take risks and use 512, because #pragma dynamic <3
char sQuery[512];
FormatEx(sQuery, 512, "REPLACE INTO users (auth, name, country, ip) VALUES ('%s', '%s', '%s', '%s');", sAuthID3, sEscapedName, sCountry, sIP);
SQL_TQuery(gH_SQL, SQL_InsertUser_Callback, sQuery, GetClientSerial(client));
}
public void SQL_InsertUser_Callback(Handle owner, Handle hndl, const char[] error, any data)
{
if(hndl == null)
{
int client = GetClientFromSerial(data);
if(!client)
{
LogError("Timer error! Failed to insert a disconnected player's data to the table. Reason: %s", error);
}
else
{
LogError("Timer error! Failed to insert \"%N\"'s data to the table. Reason: %s", client, error);
}
return;
}
}
public void SQL_DBConnect()
{
if(gH_SQL != null)
{
CloseHandle(gH_SQL);
}
if(SQL_CheckConfig("shavit"))
{
char sError[255];
if(!(gH_SQL = SQL_Connect("shavit", true, sError, 255)))
{
SetFailState("Timer startup failed. Reason: %s", sError);
}
// let's not mess with shit and make it non-English characters work properly before we do any stupid crap rite?
SQL_LockDatabase(gH_SQL);
SQL_FastQuery(gH_SQL, "SET NAMES 'utf8';");
SQL_UnlockDatabase(gH_SQL);
// CREATE TABLE IF NOT EXISTS
SQL_TQuery(gH_SQL, SQL_CreateTable_Callback, "CREATE TABLE IF NOT EXISTS `users` (`auth` VARCHAR(32) NOT NULL, `name` VARCHAR(32), `country` VARCHAR(45), `ip` VARCHAR(32), PRIMARY KEY (`auth`));");
}
else
{
SetFailState("Timer startup failed. Reason: %s", "\"shavit\" is not a specified entry in databases.cfg.");
}
}
public void SQL_CreateTable_Callback(Handle owner, Handle hndl, const char[] error, any data)
{
if(hndl == null)
{
LogError("Timer error! Users' data table creation failed. Reason: %s", error);
return;
}
}
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3])
{
if(!IsValidClient(client, true))
{
return Plugin_Continue;
}
bool bOnLadder = (GetEntityMoveType(client) == MOVETYPE_LADDER);
if(gCV_Leftright.BoolValue && gB_Zones && gB_TimerEnabled[client] && !Shavit_InsideZone(client, Zone_Start) && (buttons & IN_LEFT || buttons & IN_RIGHT))
{
StopTimer(client);
PrintToChat(client, "%s I've stopped your timer for using +left/+right. No cheating!", PREFIX);
}
bool bEdit = false;
// SW cheat blocking
if(!Shavit_InsideZone(client, Zone_Freestyle) && gBS_Style[client] == Style_Sideways && !bOnLadder && (vel[1] != 0.0 || buttons & IN_MOVELEFT || buttons & IN_MOVERIGHT))
{
bEdit = true;
vel[1] = 0.0;
}
// autobhop
if(gCV_Autobhop.BoolValue && gB_Auto[client] && buttons & IN_JUMP && !(GetEntityFlags(client) & FL_ONGROUND) && !bOnLadder && GetEntProp(client, Prop_Send, "m_nWaterLevel") <= 1)
{
buttons &= ~IN_JUMP;
}
if(gB_ClientPaused[client])
{
bEdit = true;
vel = view_as{0.0, 0.0, 0.0};
}
return bEdit? Plugin_Changed:Plugin_Continue;
}