#pragma semicolon 1 #include #include #include #include #include new Handle:g_hSQL; new g_iReconnectCounter = 0; new g_iStrafeNumber[MAXPLAYERS+1]; new g_iDirection[MAXPLAYERS+1]; new bool:g_bJumped[MAXPLAYERS+1]; new bool:g_bReset[MAXPLAYERS+1]; new bool:g_bCheckSurf[MAXPLAYERS+1]; new bool:g_bLJStats[MAXPLAYERS+1]; new bool:g_bTurning[MAXPLAYERS+1]; new bool:g_bStraifingAW[MAXPLAYERS+1]; new bool:g_bStraifingSD[MAXPLAYERS+1]; new bool:g_bOnGround[MAXPLAYERS+1]; new bool:g_bPostThink[MAXPLAYERS+1]; new Handle:g_h_sv_gravity = INVALID_HANDLE; new Float:g_fDistanceCache[MAXPLAYERS+1]; enum RecordCache { String:Auth[32], Float:Distance, Float:Maxspeed, Float:Gain, Float:Prestrafe, Strafenumber, Direction, String:Name[32], Float:Sync } new String:g_sDirectionNames[][] = { {"Forwards"}, {"Sideways"}, {"Backwards"} }; new g_cache[100][RecordCache]; new g_cacheCount = 0; new bool:g_bCacheLoaded = false; public Plugin:myinfo = { name = "LJ Stats", // Sorry, Alon told me :[ //author = "0wn3r & Exolent, SchlumPF*, xPaw", author = "shavit/TimeBomb", description = "Longjump stats.", version = "1.5", // Sorry, Alon told me :[ //url = "http://playground.org.ua/" url = "not vgames" }; public OnPluginStart() { g_h_sv_gravity = FindConVar("sv_gravity"); RegConsoleCmd("sm_ljstats", Command_LJStats); RegConsoleCmd("sm_lj", Command_LJStats); RegConsoleCmd("sm_pre", Command_LJStats); RegConsoleCmd("sm_ljtop", Command_LJTop); RegAdminCmd("sm_removejump", Command_DeleteRecord_All, ADMFLAG_RCON, "sm_removejump STEAM_ID"); HookEvent("player_spawn", Event_PlayerSpawn); HookEntityOutput("trigger_teleport", "OnEndTouch", ResetJump); HookEntityOutput("trigger_push", "OnEndTouch", ResetJump); HookEntityOutput("trigger_wind", "OnEndTouch", ResetJump); ConnectSQL(); } public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) { return APLRes_Success; } public OnMapStart() { ConnectSQL(); } public Action:Command_LJStats(client, args) { g_bLJStats[client] = !g_bLJStats[client]; if (g_bLJStats[client]) { CPrintToChat(client, "{green}[{fullblue}LJStats{green}]{gold} LJ Stats is enabled."); } else { CPrintToChat(client, "{green}[{fullblue}LJStats{green}]{gold} LJ Stats is disabled."); } return Plugin_Handled; } public ResetJump(const String:output[], caller, activator, Float:delay) { if ((1 <= activator <= MaxClients) && IsClientConnected(activator)) { g_bReset[activator] = true; } } public Action:ResetJumpTimer(Handle:Timer, any:client) { g_bReset[client] = true; } public Action:Event_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast) { g_bReset[GetClientOfUserId(GetEventInt(event, "userid"))] = true; return Plugin_Continue; } public OnClientPutInServer(client) { SDKHook(client, SDKHook_PreThink, OnPreThink); SDKHook(client, SDKHook_PostThink, OnPostThink); SDKHook(client, SDKHook_Touch, OnTouch); g_bLJStats[client] = false; UpdateClientCache(client); } public OnTouch(client, other) { if ((1 <= client <= MaxClients) && !(GetEntityFlags(client) & FL_ONGROUND)) { g_bCheckSurf[client] = true; } if ((1 <= other <= MaxClients) && !(GetEntityFlags(other) & FL_ONGROUND)) { g_bCheckSurf[other] = true; } } public Action:OnPreThink(client) { if (!IsPlayerAlive(client)) { return Plugin_Continue; } static String:weapon[32]; GetClientWeapon(client, weapon, 32); if(StrEqual(weapon, "weapon_scout")) { return Plugin_Continue; } static nOldButtons; new bool:bOnGround = IsUserOnGround(client); if (g_bCheckSurf[client]) { g_bCheckSurf[client] = false; if (!bOnGround && !g_bOnGround[client]) { g_bReset[client] = true; nOldButtons = GetClientButtons(client); g_bOnGround[client] = bOnGround; return Plugin_Continue; } } static iFrames[MAXPLAYERS+1], iFramesGainedSpeed[MAXPLAYERS+1]; static bool:bFirstFrame[MAXPLAYERS+1]; static Float:fMaxspeed[MAXPLAYERS+1], Float:fPrestrafe[MAXPLAYERS+1]; static Float:vecFrameOrigin[MAXPLAYERS+1][2][3], Float:vecFrameVelocity[MAXPLAYERS+1][2][3]; static Float:vecJumpoffOrigin[MAXPLAYERS+1][3]; static iGroundFrames[MAXPLAYERS+1]; static Float:fLastMaxspeedChange[MAXPLAYERS+1]; static Float:flOldMaxspeed[MAXPLAYERS+1]; static Float:vecOldOrigin[MAXPLAYERS+1][3]; static Float:flMaxspeed; flMaxspeed = GetEntPropFloat(client, Prop_Data, "m_flMaxspeed"); static Float:vecOrigin[3]; GetEntPropVector(client, Prop_Data, "m_vecOrigin", vecOrigin); if (g_bReset[client] || flMaxspeed != flOldMaxspeed[client] || GetVectorDistance(vecOldOrigin[client], vecOrigin) > 20) { g_bReset[client] = false; g_bJumped[client] = false; bFirstFrame[client] = false; iFramesGainedSpeed[client] = 0; iFrames[client] = 0; iGroundFrames[client] = 0; g_iStrafeNumber[client] = 0; fLastMaxspeedChange[client] = GetGameTime(); nOldButtons = GetClientButtons(client); flOldMaxspeed[client] = flMaxspeed; vecOldOrigin[client] = vecOrigin; g_bOnGround[client] = bOnGround; return Plugin_Continue; } static Float:vecVelocity[3]; GetEntPropVector(client, Prop_Data, "m_vecVelocity", vecVelocity); static nButtons; nButtons = GetClientButtons(client); if (!bOnGround && g_bJumped[client]) { new Float:fSpeed = SquareRoot(Pow(vecVelocity[0], 2.0) + Pow(vecVelocity[1], 2.0)); static Float:fOldSpeed[MAXPLAYERS+1]; if (fSpeed > fOldSpeed[client]) { iFramesGainedSpeed[client]++; } iFrames[client]++; fOldSpeed[client] = fSpeed; if (fSpeed > fMaxspeed[client]) { fMaxspeed[client] = fSpeed; } if (bFirstFrame[client]) { bFirstFrame[client] = false; vecFrameOrigin[client][0] = vecOrigin; vecFrameVelocity[client][0] = vecVelocity; } else { vecFrameOrigin[client][1] = vecOrigin; vecFrameVelocity[client][1] = vecVelocity; } } if ((nButtons & IN_JUMP) && !(nOldButtons & IN_JUMP) && bOnGround && !g_bJumped[client]) { if (iGroundFrames[client] >= 50) { new Float:fGameTime = GetGameTime(); if (fLastMaxspeedChange[client] > (fGameTime + 0.7)) { g_bReset[client] = true; nOldButtons = GetClientButtons(client); flOldMaxspeed[client] = flMaxspeed; vecOldOrigin[client] = vecOrigin; g_bOnGround[client] = bOnGround; return Plugin_Continue; } vecJumpoffOrigin[client] = vecOrigin; g_iStrafeNumber[client] = 0; g_bTurning[client] = false; g_bStraifingAW[client] = false; g_bStraifingSD[client] = false; g_bJumped[client] = true; bFirstFrame[client] = true; new Float:fSpeed = SquareRoot(Pow(vecVelocity[0], 2.0) + Pow(vecVelocity[1], 2.0)); fPrestrafe[client] = fMaxspeed[client] = fSpeed; g_iDirection[client] = GetDirection(client); CreateTimer(0.8, ResetJumpTimer, client); } } else if (bOnGround && !g_bOnGround[client] && g_bJumped[client]) { static bool:bDucking; bDucking = IsDucking(client); static Float:fHeightDifference; fHeightDifference = vecJumpoffOrigin[client][2] - (bDucking ? vecOrigin[2] + 17.0 : vecOrigin[2]); if (fHeightDifference > 2.0 || fHeightDifference < -2.0) { g_bReset[client] = true; nOldButtons = GetClientButtons(client); flOldMaxspeed[client] = flMaxspeed; vecOldOrigin[client] = vecOrigin; g_bOnGround[client] = bOnGround; return Plugin_Continue; } static Float:fGravity; fGravity = GetConVarFloat(g_h_sv_gravity); static Float:vecLandOrigin[3]; CalculateLandOrigin(bDucking, fGravity, vecOrigin, vecFrameOrigin[client], vecFrameVelocity[client], vecLandOrigin); static Float:fDistance, Float:fDistance1, Float:fDistance2; fDistance1 = GetVectorDistance(vecJumpoffOrigin[client], vecOrigin); fDistance2 = GetVectorDistance(vecJumpoffOrigin[client], vecLandOrigin); if (fDistance1 > fDistance2) { fDistance = fDistance1; } else { fDistance = fDistance2; } fDistance += 32.0; if (fDistance < 210 || 280 < fDistance) { g_bReset[client] = true; nOldButtons = GetClientButtons(client); flOldMaxspeed[client] = flMaxspeed; vecOldOrigin[client] = vecOrigin; g_bOnGround[client] = bOnGround; return Plugin_Continue; } static Float:fSync, Float:fGain; fSync = FloatDiv(float(iFramesGainedSpeed[client] * 100), float(iFrames[client])); fGain = FloatSub(fMaxspeed[client], fPrestrafe[client]); if (g_bLJStats[client]) { decl String:display[512]; new Handle:menu = CreateMenu(MenuHandler_DoNothing, MENU_ACTIONS_ALL); SetMenuTitle(menu, "Jump Stats:"); FormatEx(display, 512, "Distance: %.02f", fDistance); AddMenuItem(menu, "distance", display); FormatEx(display, 512, "Maxspeed: %.02f (Gain %.02f)", fMaxspeed[client], fGain); AddMenuItem(menu, "maxspeed", display); FormatEx(display, 512, "Prestrafe: %.02f", fPrestrafe[client]); AddMenuItem(menu, "prestrafe", display); FormatEx(display, 512, "Strafes: %.d", g_iStrafeNumber[client]); AddMenuItem(menu, "strafes", display); FormatEx(display, 512, "Sync: %i%%", RoundToFloor(fSync)); AddMenuItem(menu, "sync", display); FormatEx(display, 512, "Direction: %s", g_sDirectionNames[g_iDirection[client]]); AddMenuItem(menu, "direction", display); SetMenuExitButton(menu, true); DisplayMenu(menu, client, MENU_TIME_FOREVER); } if(fDistance >= 260.00) { CPrintToChatAllEx(client, "{green}[{fullblue}LJStats{green}]{teamcolor} %N {gold}jumped for: {fullred}%.02f{gold} units! %s", client, fDistance, g_sDirectionNames[g_iDirection[client]]); } if(fDistance >= 240.00) { if(g_fDistanceCache[client] == 0.0) { RecordJump(client, fDistance, fMaxspeed[client], fGain, fPrestrafe[client], g_iStrafeNumber[client], g_iDirection[client], fSync); } else if (fDistance > g_fDistanceCache[client]) { UpdateJump(client, fDistance, fMaxspeed[client], fGain, fPrestrafe[client], g_iStrafeNumber[client], g_iDirection[client], fSync); } } g_bReset[client] = true; } nOldButtons = GetClientButtons(client); flOldMaxspeed[client] = flMaxspeed; vecOldOrigin[client] = vecOrigin; if (!g_bPostThink[client]) { if (bOnGround) { iGroundFrames[client]++; } else { iGroundFrames[client] = 0; } } else { g_bPostThink[client] = false; } g_bOnGround[client] = bOnGround; return Plugin_Continue; } public Action:OnPostThink(client) { if (!IsPlayerAlive(client)) { return Plugin_Continue; } new bool:bOnGround = IsUserOnGround(client); if (bOnGround && g_bJumped[client] && !g_bOnGround[client]) { g_bPostThink[client] = true; OnPreThink(client); } static nButtons; static Float:vecAngle[3], Float:vecOldAngle[3]; GetClientEyeAngles(client, vecAngle); g_bTurning[client] = false; if (vecOldAngle[1] != vecAngle[1]) { g_bTurning[client] = true; } vecOldAngle = vecAngle; nButtons = GetClientButtons(client); if (g_bJumped[client] && !bOnGround) { if (g_bTurning[client]) { if (!g_bStraifingAW[client] && ((nButtons & IN_MOVELEFT) || (nButtons & IN_FORWARD)) && !(nButtons & IN_MOVERIGHT) && !(nButtons & IN_BACK)) { g_bStraifingAW[client] = true; g_bStraifingSD[client] = false; g_iStrafeNumber[client]++; } else if (!g_bStraifingSD[client] && ((nButtons & IN_BACK) || (nButtons & IN_MOVERIGHT)) && !(nButtons & IN_MOVELEFT) && !(nButtons & IN_FORWARD)) { g_bStraifingAW[client] = false; g_bStraifingSD[client] = true; g_iStrafeNumber[client]++; } } } if ((1 <= GetEntPropEnt(client, Prop_Send, "m_hGroundEntity") <= MaxClients)) { g_bReset[client] = true; } return Plugin_Continue; } // SQL Stuff ConnectSQL() { if (g_hSQL != INVALID_HANDLE) { CloseHandle(g_hSQL); } g_hSQL = INVALID_HANDLE; if (SQL_CheckConfig("ljstats")) { SQL_TConnect(ConnectSQLCallback, "ljstats"); } else { LogError("PLUGIN STOPPED - Reason: no config entry found for 'ljstats' in databases.cfg - PLUGIN STOPPED"); } } public ConnectSQLCallback(Handle:owner, Handle:hndl, const String:error[], any:data) { if (g_iReconnectCounter >= 5) { LogError("PLUGIN STOPPED - Reason: reconnect counter reached max - PLUGIN STOPPED"); return; } if (hndl == INVALID_HANDLE) { LogError("Connection to SQL database has failed, Reason: %s", error); g_iReconnectCounter++; ConnectSQL(); return; } decl String:driver[16]; SQL_GetDriverIdent(owner, driver, sizeof(driver)); g_hSQL = CloneHandle(hndl); if (StrEqual(driver, "mysql", false)) { SQL_FastQuery(g_hSQL, "SET NAMES 'utf8'"); SQL_TQuery(g_hSQL, CreateSQLTableCallback, "CREATE TABLE IF NOT EXISTS `jumpstats` (`auth` varchar(32) NOT NULL, `distance` float NOT NULL, `maxspeed` float NOT NULL, `gain` float NOT NULL, `prestrafe` float NOT NULL, `strafenumber` int(11) NOT NULL, `direction` int(11) NOT NULL, `name` varchar(64) NOT NULL, `sync` float NOT NULL);"); } else if (StrEqual(driver, "sqlite", false)) { SQL_TQuery(g_hSQL, CreateSQLTableCallback, "CREATE TABLE IF NOT EXISTS `jumpstats` (`auth` varchar(32) NOT NULL, `distance` float NOT NULL, `maxspeed` float NOT NULL, `gain` float NOT NULL, `prestrafe` float NOT NULL, `strafenumber` INTEGER NOT NULL, `direction` INTEGER NOT NULL, `name` varchar(64) NOT NULL, `sync` float NOT NULL);"); } g_iReconnectCounter = 1; } public CreateSQLTableCallback(Handle:owner, Handle:hndl, const String:error[], any:data) { if (owner == INVALID_HANDLE) { LogError(error); g_iReconnectCounter++; ConnectSQL(); return; } if (hndl == INVALID_HANDLE) { LogError("SQL Error on CreateSQLTable: %s", error); return; } RefreshCache(); } RecordJump(client, Float:distance, Float:maxspeed, Float:gain, Float:prestrafe, strafenumber, jumpdirection, Float:sync) { if (!IsClientInGame(client)) return; if (!IsPlayerAlive(client)) return; decl String:auth[32]; GetClientAuthString(client, auth, sizeof(auth)); decl String:name[MAX_NAME_LENGTH]; GetClientName(client, name, sizeof(name)); decl String:safeName[2 * strlen(name) + 1]; SQL_EscapeString(g_hSQL, name, safeName, 2 * strlen(name) + 1); decl String:query[256]; Format(query, sizeof(query), "INSERT INTO jumpstats (auth, distance, maxspeed, gain, prestrafe, strafenumber, direction, name, sync) VALUES ('%s', %f, %f, %f, %f, %d, %d, '%s', %f);", auth, distance, maxspeed, gain, prestrafe, strafenumber, jumpdirection, safeName, sync); SQL_TQuery(g_hSQL, RecordJumpCallback, query, client, DBPrio_High); } public RecordJumpCallback(Handle:owner, Handle:hndl, const String:error[], any:client) { if (hndl == INVALID_HANDLE) { LogError("SQL Error on RecordJump: %s", error); return; } UpdateClientCache(client); RefreshCache(); } UpdateClientCache(client) { if (!IsClientInGame(client)) return; decl String:auth[32]; GetClientAuthString(client, auth, sizeof(auth)); decl String:query[255]; Format(query, sizeof(query), "SELECT distance FROM jumpstats WHERE auth = '%s'", auth); SQL_TQuery(g_hSQL, UpdateCacheCallback, query, client, DBPrio_High); } public UpdateCacheCallback(Handle:owner, Handle:hndl, const String:error[], any:client) { if (hndl == INVALID_HANDLE) { LogError("SQL Error on UpdateCache: %s", error); return; } if (SQL_FetchRow(hndl)) { g_fDistanceCache[client] = SQL_FetchFloat(hndl, 0); CloseHandle(hndl); } else { g_fDistanceCache[client] = 0.0; CloseHandle(hndl); } } UpdateJump(client, Float:distance, Float:maxspeed, Float:gain, Float:prestrafe, strafenumber, jumpdirection, Float:sync) { if (!IsClientInGame(client)) return; if (!IsPlayerAlive(client)) return; decl String:auth[32]; GetClientAuthString(client, auth, sizeof(auth)); decl String:name[MAX_NAME_LENGTH]; GetClientName(client, name, sizeof(name)); decl String:safeName[2 * strlen(name) + 1]; SQL_EscapeString(g_hSQL, name, safeName, 2 * strlen(name) + 1); decl String:query[256]; Format(query, sizeof(query), "UPDATE jumpstats SET distance = %f, maxspeed = %f, gain = %f, prestrafe = %f, strafenumber = %d, direction = %d, name = '%s', sync = %f WHERE auth = '%s';", distance, maxspeed, gain, prestrafe, strafenumber, jumpdirection, safeName, sync, auth); SQL_TQuery(g_hSQL, UpdateJumpCallback, query, client, DBPrio_High); } public UpdateJumpCallback(Handle:owner, Handle:hndl, const String:error[], any:client) { if (hndl == INVALID_HANDLE) { LogError("SQL Error on UpdateJump: %s", error); return; } UpdateClientCache(client); RefreshCache(); } RefreshCache() { g_bCacheLoaded = false; if (g_hSQL == INVALID_HANDLE) { ConnectSQL(); } else { decl String:query[384]; Format(query, sizeof(query), "SELECT * FROM jumpstats ORDER BY distance DESC LIMIT 100"); SQL_TQuery(g_hSQL, RefreshCacheCallback, query, _, DBPrio_High); } } public RefreshCacheCallback(Handle:owner, Handle:hndl, const String:error[], any:client) { if (hndl == INVALID_HANDLE) { LogError("SQL Error on RefreshCache: %s", error); return; } g_cacheCount = 0; while (SQL_FetchRow(hndl)) { SQL_FetchString(hndl, 0, g_cache[g_cacheCount][Auth], 32); g_cache[g_cacheCount][Distance] = SQL_FetchFloat(hndl, 1); g_cache[g_cacheCount][Maxspeed] = SQL_FetchFloat(hndl, 2); g_cache[g_cacheCount][Gain] = SQL_FetchFloat(hndl, 3); g_cache[g_cacheCount][Prestrafe] = SQL_FetchFloat(hndl, 4); g_cache[g_cacheCount][Strafenumber] = SQL_FetchInt(hndl, 5); g_cache[g_cacheCount][Direction] = SQL_FetchInt(hndl, 6); SQL_FetchString(hndl, 7, g_cache[g_cacheCount][Name], 32); g_cache[g_cacheCount][Sync] = SQL_FetchFloat(hndl, 8); g_cacheCount++; } g_bCacheLoaded = true; } public Action:Command_DeleteRecord_All(client, args) { if (args < 1) { ReplyToCommand(client, "Usage: sm_removejump "); return Plugin_Handled; } new String:auth[32]; GetCmdArgString(auth, sizeof(auth)); decl String:query[384]; Format(query, sizeof(query), "DELETE FROM jumpstats WHERE auth = '%s'", auth); SQL_TQuery(g_hSQL, DeleteRecordsCallback, query, _, DBPrio_High); return Plugin_Handled; } public DeleteRecordsCallback(Handle:owner, Handle:hndl, const String:error[], any:data) { if (hndl == INVALID_HANDLE) { LogError("SQL Error on RemoveJump: %s", error); return; } RefreshCache(); } // End of SQL Stuff // LJ Top Stuff public Action:Command_LJTop(client, args) { if (!g_bCacheLoaded) { PrintToChat(client, "Records can't be loaded"); return Plugin_Handled; } CreateTopMenu(client); return Plugin_Handled; } CreateTopMenu(client) { new Handle:menu = CreateMenu(MenuHandler_WR); SetMenuTitle(menu, "Best Jumpers"); new items = 0; for (new cache = 0; cache < g_cacheCount; cache++) { decl String:text[92]; FormatEx(text, sizeof(text), "#%d: %s - %.02f", (cache + 1), g_cache[cache][Name], g_cache[cache][Distance]); AddMenuItem(menu, g_cache[cache][Auth], text); items++; } if (items == 0) { CloseHandle(menu); PrintToChat(client, "No Records"); return; } DisplayMenu(menu, client, MENU_TIME_FOREVER); } public MenuHandler_WR(Handle:menu, MenuAction:action, param1, param2) { if (action == MenuAction_End) { CloseHandle(menu); } else if (action == MenuAction_Select) { decl String:info[32]; GetMenuItem(menu, param2, info, sizeof(info)); CreateJumpInfoMenu(param1, info); } } CreateJumpInfoMenu(client, const String:AuthString[]) { new Handle:menu = CreateMenu(MenuHandler_JumpInfo); SetMenuExitBackButton(menu, true); for (new cache = 0; cache < g_cacheCount; cache++) { if (!strcmp(g_cache[cache][Auth], AuthString)) { decl String:text[128]; SetMenuTitle(menu, "Jump Info", client); FormatEx(text, sizeof(text), "Player Name: %s (%s)", g_cache[cache][Name], g_cache[cache][Auth]); AddMenuItem(menu, "", text); FormatEx(text, sizeof(text), "Rank: #%d", cache + 1); AddMenuItem(menu, "", text); FormatEx(text, sizeof(text), "Distance: %f", g_cache[cache][Distance]); AddMenuItem(menu, "", text); FormatEx(text, sizeof(text), "Strafes: %d", g_cache[cache][Strafenumber]); AddMenuItem(menu, "", text); FormatEx(text, sizeof(text), "Prestrafe: %f", g_cache[cache][Prestrafe]); AddMenuItem(menu, "", text); FormatEx(text, sizeof(text), "Maxspeed: %f", g_cache[cache][Maxspeed]); AddMenuItem(menu, "", text); FormatEx(text, sizeof(text), "Direction: %s", g_sDirectionNames[g_cache[cache][Direction]]); AddMenuItem(menu, "", text); break; } } DisplayMenu(menu, client, MENU_TIME_FOREVER); } public MenuHandler_JumpInfo(Handle:menu, MenuAction:action, param1, param2) { if (action == MenuAction_End) { CloseHandle(menu); } else if (action == MenuAction_Cancel) { if (param2 == MenuCancel_ExitBack) { CreateTopMenu(param1); } } } public MenuHandler_DoNothing(Handle:menu, MenuAction:action, param1, param2) { if(action == MenuAction_End) { CloseHandle(menu); } }