update plugins

This commit is contained in:
Jackzie 2022-01-07 07:52:36 -06:00
parent 42497b76f2
commit 509c6a1b21
No known key found for this signature in database
GPG key ID: 1E834FE36520537A
18 changed files with 1043 additions and 316 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
plugins/l4d2_detections.smx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -62,7 +62,7 @@ public void OnClientAuthorized(int client, const char[] auth) {
if(!StrEqual(auth, "BOT", true)) { if(!StrEqual(auth, "BOT", true)) {
static char query[256], ip[32]; static char query[256], ip[32];
GetClientIP(client, ip, sizeof(ip)); GetClientIP(client, ip, sizeof(ip));
Format(query, sizeof(query), "SELECT `reason`, `steamid`, `expired` FROM `bans` WHERE `steamid` = '%s' OR ip = '?'", auth, ip); Format(query, sizeof(query), "SELECT `reason`, `steamid`, `expired` FROM `bans` WHERE `steamid` = 'STEAM_%:%:%s' OR ip = '?'", auth[10], ip);
g_db.Query(DB_OnConnectCheck, query, GetClientUserId(client), DBPrio_High); g_db.Query(DB_OnConnectCheck, query, GetClientUserId(client), DBPrio_High);
} }
} }
@ -174,6 +174,7 @@ public void DB_OnConnectCheck(Database db, DBResultSet results, const char[] err
KickClient(client, "You have been banned from this server."); KickClient(client, "You have been banned from this server.");
} else { } else {
PrintChatToAdmins("%N was banned from this server for: \"%s\"", client, reason); PrintChatToAdmins("%N was banned from this server for: \"%s\"", client, reason);
return;
} }
static char query[128]; static char query[128];
g_db.Format(query, sizeof(query), "UPDATE bans SET times_tried=times_tried+1 WHERE steamid = '%s'", steamid); g_db.Format(query, sizeof(query), "UPDATE bans SET times_tried=times_tried+1 WHERE steamid = '%s'", steamid);

View file

@ -274,8 +274,15 @@ stock bool SpawnMinigun(const float vPos[3], const float vAng[3]) {
} }
} }
stock int GiveClientWeaponLasers(int client, const char[] wpnName) {
int entity = GiveClientWeapon(client, wpnName);
if(entity != -1) {
SetEntProp(entity, Prop_Send, "m_upgradeBitVec", 4);
}
return entity;
}
stock bool GiveClientWeapon(int client, const char[] wpnName, bool lasers) { stock int GiveClientWeapon(int client, const char[] wpnName) {
static char sTemp[64]; static char sTemp[64];
float pos[3]; float pos[3];
GetClientAbsOrigin(client, pos); GetClientAbsOrigin(client, pos);
@ -286,12 +293,10 @@ stock bool GiveClientWeapon(int client, const char[] wpnName, bool lasers) {
DispatchSpawn(entity); DispatchSpawn(entity);
TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR); TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR);
if(lasers) SetEntProp(entity, Prop_Send, "m_upgradeBitVec", 4);
EquipPlayerWeapon(client, entity); EquipPlayerWeapon(client, entity);
return true; return entity;
}else{ }else{
return false; return -1;
} }
} }
stock int GetNearestEntity(int client, char[] classname) stock int GetNearestEntity(int client, char[] classname)
@ -589,6 +594,12 @@ stock void StringToLower(char[] str) {
str[i] = CharToLower(str[i]); str[i] = CharToLower(str[i]);
} }
} }
stock void StringToUpper(char[] str) {
int len = strlen(str);
for(int i = 0; i < len; i++) {
str[i] = CharToUpper(str[i]);
}
}
stock int GetRealClient(int client) { stock int GetRealClient(int client) {
if(IsFakeClient(client)) { if(IsFakeClient(client)) {

View file

@ -19,7 +19,9 @@ int iJoinTime[MAXPLAYERS+1], iIdleStartTime[MAXPLAYERS+1], iJumpAttempts[MAXPLAY
float playerTotalDamageFF[MAXPLAYERS+1]; float playerTotalDamageFF[MAXPLAYERS+1];
int lastFF[MAXPLAYERS+1]; int lastFF[MAXPLAYERS+1];
ConVar hForgivenessTime, hBanTime, hThreshold, hJoinTime, hTKAction, hSuicideAction, hSuicideLimit; float autoFFScaleFactor[MAXPLAYERS+1];
ConVar hForgivenessTime, hBanTime, hThreshold, hJoinTime, hTKAction, hSuicideAction, hSuicideLimit, hFFAutoScaleAmount, hFFAutoScaleForgivenessAmount, hFFAutoScaleMaxRatio, hFFAutoScaleIgnoreAdmins;
public Plugin myinfo = public Plugin myinfo =
{ {
@ -52,6 +54,10 @@ public void OnPluginStart()
hSuicideAction = CreateConVar("l4d2_suicide_action", "3", "How should a suicider be punished?\n0 = No action (No message), 1 = Kick, 2 = Instant Ban, 3 = Ban on disconnect", FCVAR_NONE, true, 0.0, true, 3.0); hSuicideAction = CreateConVar("l4d2_suicide_action", "3", "How should a suicider be punished?\n0 = No action (No message), 1 = Kick, 2 = Instant Ban, 3 = Ban on disconnect", FCVAR_NONE, true, 0.0, true, 3.0);
hSuicideLimit = CreateConVar("l4d2_suicide_limit", "1", "How many attempts does a new joined player have until action is taken for suiciding?", FCVAR_NONE, true, 0.0); hSuicideLimit = CreateConVar("l4d2_suicide_limit", "1", "How many attempts does a new joined player have until action is taken for suiciding?", FCVAR_NONE, true, 0.0);
hFFAutoScaleAmount = CreateConVar("l4d2_tk_auto_ff_rate", "0.04", "The rate at which auto reverse-ff is scaled by.", FCVAR_NONE, true, 0.0);
hFFAutoScaleMaxRatio = CreateConVar("l4d2_tk_auto_ff_max_ratio", "5.0", "The maximum amount that the reverse ff can go. 0.0 for unlimited", FCVAR_NONE, true, 0.0);
hFFAutoScaleForgivenessAmount = CreateConVar("l4d2_tk_auto_ff_forgive_rate", "0.008", "This amount times amount of minutes since last ff is removed from ff rate", FCVAR_NONE, true, 0.0);
hFFAutoScaleIgnoreAdmins = CreateConVar("l4d2_tk_auto_ff_ignore_admins", "1", "Should automatic reverse ff ignore admins? 0 = Admins are subjected\n1 = Admins are excempt", FCVAR_NONE, true, 0.0, true, 1.0);
//AutoExecConfig(true, "l4d2_tkstopper"); //AutoExecConfig(true, "l4d2_tkstopper");
@ -78,6 +84,8 @@ public void OnPluginStart()
RegAdminCmd("sm_ignore", Command_IgnorePlayer, ADMFLAG_KICK, "Makes a player immune for any anti trolling detection for a session"); RegAdminCmd("sm_ignore", Command_IgnorePlayer, ADMFLAG_KICK, "Makes a player immune for any anti trolling detection for a session");
RegAdminCmd("sm_tkinfo", Command_TKInfo, ADMFLAG_KICK, "Debug info for TKSTopper");
if(lateLoaded) { if(lateLoaded) {
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
@ -85,8 +93,18 @@ public void OnPluginStart()
} }
} }
} }
// CreateTimer(60.0, Timer_Forgive, _, TIMER_REPEAT);
} }
/*public Action Timer_Forgive(Handle h) {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && ) {
}
}
return Plugin_Continue;
}*/
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Special Infected Events // Special Infected Events
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -173,6 +191,7 @@ public void OnMapEnd() {
public void OnClientPutInServer(int client) { public void OnClientPutInServer(int client) {
iJoinTime[client] = GetTime(); iJoinTime[client] = GetTime();
lastFF[client] = GetTime();
SDKHook(client, SDKHook_OnTakeDamage, Event_OnTakeDamage); SDKHook(client, SDKHook_OnTakeDamage, Event_OnTakeDamage);
} }
@ -191,21 +210,32 @@ public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroa
BanClient(client, hBanTime.IntValue, BANFLAG_AUTO | BANFLAG_AUTHID, "Excessive FF", "Excessive Friendly Fire", "TKStopper"); BanClient(client, hBanTime.IntValue, BANFLAG_AUTO | BANFLAG_AUTHID, "Excessive FF", "Excessive Friendly Fire", "TKStopper");
} }
isPlayerTroll[client] = false; isPlayerTroll[client] = false;
autoFFScaleFactor[client] = 0.0;
} }
public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3]) { public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3]) {
if(damage > 0.0 && victim <= MaxClients && attacker <= MaxClients && attacker > 0 && victim > 0) { if(damage > 0.0 && victim <= MaxClients && attacker <= MaxClients && attacker > 0 && victim > 0) {
if(GetUserAdmin(attacker) != INVALID_ADMIN_ID || isImmune[attacker] || IsFakeClient(attacker)) return Plugin_Continue; if(damagetype & DMG_BURN && IsFakeClient(attacker)) {
damage = 0.0;
return Plugin_Changed;
}
bool isAdmin = GetUserAdmin(attacker) != INVALID_ADMIN_ID;
bool ignore = hFFAutoScaleIgnoreAdmins.BoolValue && isAdmin;
if(isImmune[attacker] || IsFakeClient(attacker)) return Plugin_Continue;
if(GetClientTeam(victim) != 2 || GetClientTeam(attacker) != 2 || attacker == victim) return Plugin_Continue; if(GetClientTeam(victim) != 2 || GetClientTeam(attacker) != 2 || attacker == victim) return Plugin_Continue;
//Allow friendly firing BOTS that aren't idle players: //Allow friendly firing BOTS that aren't idle players:
//if(IsFakeClient(victim) && !HasEntProp(attacker, Prop_Send, "m_humanSpectatorUserID") || GetEntProp(attacker, Prop_Send, "m_humanSpectatorUserID") == 0) return Plugin_Continue; //if(IsFakeClient(victim) && !HasEntProp(attacker, Prop_Send, "m_humanSpectatorUserID") || GetEntProp(attacker, Prop_Send, "m_humanSpectatorUserID") == 0) return Plugin_Continue;
// Stop all damage early if already marked as troll
if(isPlayerTroll[attacker]) return Plugin_Stop; if(isPlayerTroll[attacker]) return Plugin_Stop;
// Allow vanilla-damage if being attacked by special (example, charger carry)
if(isUnderAttack[victim]) return Plugin_Continue; if(isUnderAttack[victim]) return Plugin_Continue;
// Is damage not caused by fire or pipebombs?
bool isDamageDirect = damagetype & (DMG_BLAST|DMG_BURN|DMG_BLAST_SURFACE) == 0; bool isDamageDirect = damagetype & (DMG_BLAST|DMG_BURN|DMG_BLAST_SURFACE) == 0;
int time = GetTime(); int time = GetTime();
// If is a fall within first 2 minutes, do appropiate action // If is a fall within first 2 minutes, do appropiate action
if(damagetype & DMG_FALL && attacker == victim && damage > 0.0 && time- iJoinTime[victim] <= hJoinTime.IntValue * 60000) { if(!isAdmin && damagetype & DMG_FALL && attacker == victim && damage > 0.0 && time - iJoinTime[victim] <= hJoinTime.IntValue * 60) {
iJumpAttempts[victim]++; iJumpAttempts[victim]++;
float pos[3]; float pos[3];
GetNearestPlayerPosition(victim, pos); GetNearestPlayerPosition(victim, pos);
@ -232,10 +262,28 @@ public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, flo
playerTotalDamageFF[attacker] = 0.0; playerTotalDamageFF[attacker] = 0.0;
} }
playerTotalDamageFF[attacker] += damage; playerTotalDamageFF[attacker] += damage;
lastFF[attacker] = time;
// Check for friendly fire damage // Auto reverse ff logic
if(playerTotalDamageFF[attacker] > hThreshold.IntValue && !IsFinaleEnding && isDamageDirect) { lastFF[attacker] = time;
if(isDamageDirect && !ignore) {
// Decrement any recovered FF
float minutesSinceLastFF = (time - lastFF[attacker]) / 60.0;
autoFFScaleFactor[attacker] -= minutesSinceLastFF * hFFAutoScaleForgivenessAmount.FloatValue;
if(autoFFScaleFactor[attacker] < 0.0) {
autoFFScaleFactor[attacker] = 0.0;
}
// Then increment
autoFFScaleFactor[attacker] += hFFAutoScaleAmount.FloatValue * damage;
if(hFFAutoScaleMaxRatio.FloatValue > 0.0 && autoFFScaleFactor[attacker] > hFFAutoScaleMaxRatio.FloatValue) {
autoFFScaleFactor[attacker] = hFFAutoScaleMaxRatio.FloatValue;
}
if(minutesSinceLastFF > 3.0) {
PrintToConsoleAdmins("%N new reverse ratio: %f", attacker, autoFFScaleFactor[attacker]);
}
}
// Check for excessive friendly fire damage in short timespan
if(!isAdmin && playerTotalDamageFF[attacker] > hThreshold.IntValue && !IsFinaleEnding && isDamageDirect) {
LogAction(-1, attacker, "Excessive FF (%.2f HP)", playerTotalDamageFF[attacker]); LogAction(-1, attacker, "Excessive FF (%.2f HP)", playerTotalDamageFF[attacker]);
if(hTKAction.IntValue == 1) { if(hTKAction.IntValue == 1) {
LogMessage("[NOTICE] Kicking %N for excessive FF (%.2f HP) for %d minutes.", attacker, playerTotalDamageFF[attacker], hBanTime.IntValue); LogMessage("[NOTICE] Kicking %N for excessive FF (%.2f HP) for %d minutes.", attacker, playerTotalDamageFF[attacker], hBanTime.IntValue);
@ -255,19 +303,19 @@ public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, flo
} }
// Modify damages based on criteria // Modify damages based on criteria
if(iJumpAttempts[victim] > 0 || L4D_IsInFirstCheckpoint(victim) || L4D_IsInLastCheckpoint(victim) || time - iJoinTime[attacker] <= hJoinTime.IntValue * 60000) { if(iJumpAttempts[victim] > 0 || L4D_IsInFirstCheckpoint(victim) || L4D_IsInLastCheckpoint(victim) || time - iJoinTime[attacker] <= hJoinTime.IntValue * 60) {
// If the amount of MS is <= join time threshold * 60000 ms then cancel // If the amount of MS is <= join time threshold * 60000 ms then cancel
// Or if the player is in a saferoom // Or if the player is in a saferoom
// Or if the player tried to suicide jump // Or if the player tried to suicide jump
damage = 0.0; damage = 0.0;
return Plugin_Handled; return Plugin_Handled;
}else if(IsFinaleEnding) { }else if(IsFinaleEnding) {
if(isAdmin) return Plugin_Continue;
SDKHooks_TakeDamage(attacker, attacker, attacker, damage * 2.0); SDKHooks_TakeDamage(attacker, attacker, attacker, damage * 2.0);
damage = 0.0; damage = 0.0;
return Plugin_Changed; return Plugin_Changed;
}else if(!isDamageDirect) { // Ignore fire and propane damage, mistakes can happen }else if(!isDamageDirect) { // Ignore fire and propane damage, mistakes can happen
// Make the victim take slightly less, attacker more, to in event of 1-1 victim wins SDKHooks_TakeDamage(attacker, attacker, attacker, float(RoundToCeil(autoFFScaleFactor[attacker] * damage)));
SDKHooks_TakeDamage(attacker, attacker, attacker, damage / 1.9);
damage /= 2.1; damage /= 2.1;
return Plugin_Changed; return Plugin_Changed;
} }
@ -275,6 +323,20 @@ public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, flo
return Plugin_Continue; return Plugin_Continue;
} }
public Action Command_TKInfo(int client, int args) {
int time = GetTime();
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) {
float minutesSinceLastFF = (time - lastFF[i]) / 60.0;
float activeRate = autoFFScaleFactor[i] - (minutesSinceLastFF * hFFAutoScaleForgivenessAmount.FloatValue);
if(activeRate < 0.0) {
activeRate = 0.0;
}
ReplyToCommand(client, "%N: %f TK-FF buffer | %f (active: %f), reverse FF rate | %f ff min ago | %d suicide jumps", i, playerTotalDamageFF[i], autoFFScaleFactor[i], activeRate, minutesSinceLastFF, iJumpAttempts[i]);
}
}
return Plugin_Handled;
}
public Action Command_IgnorePlayer(int client, int args) { public Action Command_IgnorePlayer(int client, int args) {
char arg1[32]; char arg1[32];
GetCmdArg(1, arg1, sizeof(arg1)); GetCmdArg(1, arg1, sizeof(arg1));
@ -284,15 +346,15 @@ public Action Command_IgnorePlayer(int client, int args) {
bool tn_is_ml; bool tn_is_ml;
if ((target_count = ProcessTargetString( if ((target_count = ProcessTargetString(
arg1, arg1,
client, client,
target_list, target_list,
MaxClients, MaxClients,
COMMAND_FILTER_ALIVE, COMMAND_FILTER_ALIVE,
target_name, target_name,
sizeof(target_name), sizeof(target_name),
tn_is_ml)) <= 0) tn_is_ml)) <= 0
{ ) {
ReplyToTargetError(client, target_count); ReplyToTargetError(client, target_count);
return Plugin_Handled; return Plugin_Handled;
} }
@ -338,7 +400,7 @@ stock bool GetNearestPlayerPosition(int client, float pos[3]) {
stock void PrintChatToAdmins(const char[] format, any ...) { stock void PrintChatToAdmins(const char[] format, any ...) {
char buffer[254]; char buffer[254];
VFormat(buffer, sizeof(buffer), format, 2); VFormat(buffer, sizeof(buffer), format, 2);
for(int i = 1; i < MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) { if(IsClientConnected(i) && IsClientInGame(i)) {
AdminId admin = GetUserAdmin(i); AdminId admin = GetUserAdmin(i);
if(admin != INVALID_ADMIN_ID) { if(admin != INVALID_ADMIN_ID) {
@ -352,7 +414,7 @@ stock void PrintChatToAdmins(const char[] format, any ...) {
stock void PrintToConsoleAdmins(const char[] format, any ...) { stock void PrintToConsoleAdmins(const char[] format, any ...) {
char buffer[254]; char buffer[254];
VFormat(buffer, sizeof(buffer), format, 2); VFormat(buffer, sizeof(buffer), format, 2);
for(int i = 1; i < MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) { if(IsClientConnected(i) && IsClientInGame(i)) {
AdminId admin = GetUserAdmin(i); AdminId admin = GetUserAdmin(i);
if(admin != INVALID_ADMIN_ID) { if(admin != INVALID_ADMIN_ID) {

View file

@ -54,7 +54,7 @@ public Action Timer_Check(Handle h) {
} else if(GetTime() - startupTime > MAX_TIME_ONLINE_MS) { } else if(GetTime() - startupTime > MAX_TIME_ONLINE_MS) {
LogAction(0, -1, "Server has passed max online time threshold, will restart if remains empty"); LogAction(0, -1, "Server has passed max online time threshold, will restart if remains empty");
if(IsServerEmpty()) { if(IsServerEmpty()) {
if(++triesEmpty > 3) { if(++triesEmpty > 4) {
LogAction(0, -1, "Server has passed max online time threshold and is empty after %d tries, restarting now", triesEmpty); LogAction(0, -1, "Server has passed max online time threshold and is empty after %d tries, restarting now", triesEmpty);
ServerCommand("quit"); ServerCommand("quit");
} }
@ -86,10 +86,8 @@ bool IsServerEmptyWithOnlyBots() {
//Returns true if there is a bot connected and there is no real players //Returns true if there is a bot connected and there is no real players
bool IsServerEmpty() { bool IsServerEmpty() {
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) { if(IsClientConnected(i) && !IsFakeClient(i)) {
if(!IsFakeClient(i)) return false;
return false;
} }
} }
return true; return true;

View file

@ -0,0 +1,218 @@
#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#define BILE_NO_HORDE_THRESHOLD 20
#define DOOR_CLOSE_THRESHOLD 5000.0
#include <sourcemod>
#include <sdktools>
#include <left4dhooks>
#include <jutils>
//#include <sdkhooks>
enum struct PlayerDetections {
int kitPickupsSaferoom;
int saferoomLastOpen;
int saferoomOpenCount;
void Reset() {
this.kitPickupsSaferoom = 0;
this.saferoomLastOpen = 0;
this.saferoomOpenCount = 0;
}
}
/*
Bile Detections:
1. No commons around entowner or bile
2. Bile already exists
3. Player is currently vomitted on (check time?)
4. Bile on tank (common count near tank)
*/
stock bool IsPlayerBoomed(int client) {
return GetEntPropFloat(%0, Prop_Send, "m_vomitStart") + 20.1 > GetGameTime();
}
stock bool IsAnyPlayerBoomed() {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerBoomed(i)) {
return true;
}
}
return false;
}
stock bool AnyRecentBileInPlay(int ignore) {
return false;
}
stock int GetEntityCountNear(const float[3] srcPos, float radius = 50000.0) {
float pos[3];
int count;
int entity = -1;
while( (entity = FindEntityByClassname(entity, "infected")) != INVALID_ENT_REFERENCE ) {
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos);
if(GetEntProp(entity, Prop_Send, "m_clientLookatTarget") != -1 && GetVectorDistance(pos, srcPos) <= radius) {
count++;
}
}
return count;
}
stock int L4D_SpawnCommonInfected2(const float vPos[3], const float vAng[3] = { 0.0, 0.0, 0.0 })
{
int entity = CreateEntityByName("infected");
if( entity != -1 )
{
DispatchSpawn(entity);
TeleportEntity(entity, vPos, vAng, NULL_VECTOR);
}
return entity;
}
PlayerDetections[MAXPLAYERS+1] detections;
GlobalForward fwd_PlayerDoubleKit, fwd_NoHordeBileWaste, fwd_DoorFaceCloser, fwd_CheckpointDoorFaceCloser;
public Plugin myinfo =
{
name = "L4D2 Detections",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = ""
};
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D/L4D2 only.");
}
fwd_PlayerDoubleKit = new GlobalForward("OnDoubleKit", ET_Hook, Param_Cell);
fwd_NoHordeBileWaste = new GlobalForward("OnNoHordeBileWaste", ET_Event, Param_Cell, Param_Cell);
fwd_DoorFaceCloser = new GlobalForward("OnDoorCloseInFace", ET_Hook, Param_Cell);
fwd_CheckpointDoorFaceCloser = new GlobalForward("OnDoorCloseInFaceSaferoom", ET_Hook, Param_Cell, Param_Cell);
HookEvent("item_pickup", Event_ItemPickup);
HookEvent("door_close", Event_DoorClose);
}
// Called on map changes too, we want this:
public void OnClientDisconnect(int client) {
detections[client].Reset();
}
public Action Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client && L4D_IsInLastCheckpoint(client)) {
static char itmName[32];
event.GetString("item", itmName, sizeof(itmName));
if(StrEqual(itmName, "first_aid_kit")) {
if(++detections[client].kitPickupsSaferoom == 2) {
InternalDebugLog("DOUBLE_KIT", client);
Call_StartForward(fwd_PlayerDoubleKit);
Call_PushCell(client);
Call_Finish();
}
}
}
}
public Action Event_DoorClose(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(fwd_DoorFaceCloser.FunctionCount > 0 && client) {
bool isCheckpoint = event.GetBool("checkpoint");
DataPack pack = GetNearestClient(client);
pack.Reset();
int victim = pack.ReadCell();
float dist = pack.ReadFloat();
if(victim) {
if(dist < DOOR_CLOSE_THRESHOLD) {
if(isCheckpoint) {
if(detections[client].saferoomLastOpen > 0 && GetTime() - detections[client].saferoomLastOpen > 30000) {
detections[client].saferoomLastOpen = 0;
detections[client].saferoomOpenCount = 0;
}
Call_StartForward(fwd_CheckpointDoorFaceCloser);
Call_PushCell(client);
Call_PushCell(victim);
Call_PushCell(++detections[client].saferoomOpenCount);
Call_Finish();
detections[client].saferoomLastOpen = GetTime();
PrintToServer("[Detections] DOOR_SAFEROOM: %N victim -> %N %d times", client, victim, detections[client].saferoomOpenCount);
//TODO: Find way to reset, timer?
} else {
Call_StartForward(fwd_DoorFaceCloser);
Call_PushCell(client);
Call_PushCell(victim);
Call_Finish();
PrintToServer("[Detections] DOOR=: %N victim -> %N", client, victim);
}
}
}
}
}
public void OnEntityDestroyed(int entity) {
static char classname[16];
if(IsValidEntity(entity) && entity <= 4096) {
GetEntityClassname(entity, classname, sizeof(classname));
if(StrEqual(classname, "vomitjar_projec")) {
int thrower = GetEntPropEnt(entity, Prop_Send, "m_hThrower");
if(thrower > 0 && thrower <= MaxClients && IsClientConnected(thrower) && IsClientInGame(thrower)) {
static float src[3];
float tmp[3];
GetClientAbsOrigin(thrower, tmp);
// TODO: Get source when lands
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", src);
int commons = GetEntityCountNear(src, 50000.0);
PrintToConsoleAll("[Debug] Bile Thrown By %N, Commons: %d", thrower, commons);
if(commons < BILE_NO_HORDE_THRESHOLD) {
InternalDebugLog("BILE_NO_HORDE", thrower);
Action result;
Call_StartForward(fwd_NoHordeBileWaste);
Call_PushCell(thrower);
Call_PushCell(commons);
Call_Finish(result);
if(result == Plugin_Stop) {
AcceptEntityInput(entity, "kill");
GiveClientWeapon(thrower, "vomitjar");
}
}
}
}
}
}
// TODO: Door close
void InternalDebugLog(const char[] name, int client) {
PrintToConsoleAll("[Detection] %s: Client %N", name, client);
}
stock DataPack GetNearestClient(int client) {
int victim;
float pos[3], pos2[3], distance;
GetClientAbsOrigin(client, pos);
for(int i = 1; i <= MaxClients; i++) {
if(i != client && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
GetClientAbsOrigin(i, pos2);
float dist = GetVectorDistance(pos, pos2, false);
if(victim == 0 || dist < distance) {
distance = dist;
victim = i;
}
}
}
DataPack pack = new DataPack();
pack.WriteCell(victim);
pack.WriteFloat(distance);
return pack;
}

View file

@ -27,7 +27,7 @@
#define DEBUG_LEVEL DEBUG_GENERIC #define DEBUG_LEVEL DEBUG_GENERIC
#define EXTRA_PLAYER_HUD_UPDATE_INTERVAL 0.8 #define EXTRA_PLAYER_HUD_UPDATE_INTERVAL 0.8
//Sets abmExtraCount to this value if set //Sets abmExtraCount to this value if set
//#define DEBUG_FORCE_PLAYERS 5 // #define DEBUG_FORCE_PLAYERS 5
#define PLUGIN_VERSION "1.0" #define PLUGIN_VERSION "1.0"
@ -269,19 +269,25 @@ public Action Command_RunExtraItems(int client, int args) {
/// EVENTS /// EVENTS
//////////////////////////////////// ////////////////////////////////////
// 0 = inactive | 1 = started | 2 = first tank round started | 3 = waiting for tank spawn | # > 3: Health for next tank #define FINALE_TANK 8
#define FINALE_STARTED 1
#define FINALE_RESCUE_READY 6
#define FINALE_HORDE 7
#define FINALE_WAIT 10
public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
if(finaleType == 1 && abmExtraCount > 4 && hExtraFinaleTank.BoolValue) { if(finaleType == FINALE_STARTED && abmExtraCount > 4 && hExtraFinaleTank.BoolValue) {
finaleStage = 1; finaleStage = 1;
PrintToConsoleAll("[EPI] Finale started and over threshold"); PrintToConsoleAll("[EPI] Finale started and over threshold");
} else if(finaleType == 8) { } else if(finaleType == FINALE_TANK) {
if(finaleStage == 1) { if(finaleStage == 1) {
finaleStage = 2; finaleStage = 2;
PrintToConsoleAll("[EPI] First tank has spawned"); PrintToConsoleAll("[EPI] First tank stage has started");
} else { } else if(finaleStage == 2) {
finaleStage = 3; finaleStage = 3;
PrintToConsoleAll("[EPI] Waiting for second tank to spawn"); PrintToConsoleAll("[EPI] Second stage started, waiting for tank");
} else {
PrintToConsoleAll("invalid");
} }
} }
return Plugin_Continue; return Plugin_Continue;
@ -291,23 +297,25 @@ public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast)
int user = GetEventInt(event, "userid"); int user = GetEventInt(event, "userid");
int tank = GetClientOfUserId(user); int tank = GetClientOfUserId(user);
if(finaleStage == 3) { if(finaleStage == 3) {
PrintToConsoleAll("[EPI] Third tank spawned, setting health."); PrintToConsoleAll("[EPI] Second tank spawned, setting health.");
if(tank > 0 && IsFakeClient(tank)) { if(tank > 0 && IsFakeClient(tank)) {
RequestFrame(Frame_ExtraTankWait, user); // Sets health in half, sets finaleStage to health
CreateTimer(5.0, Timer_SplitTank, user);
} }
finaleStage = 0; //Only set for a frame
} else if(finaleStage > 3) { } else if(finaleStage > 3) {
PrintToConsoleAll("[EPI] Third & final tank spawned, setting health.");
RequestFrame(Frame_SetExtraTankHealth, user); RequestFrame(Frame_SetExtraTankHealth, user);
} }
} }
public Action Timer_SplitTank(Handle t, int user) {
public void Frame_ExtraTankWait(int user) {
int tank = GetClientOfUserId(user); int tank = GetClientOfUserId(user);
if(tank > 0) { if(tank > 0) {
// Half their HP, assign half to self and for next tank // Half their HP, assign half to self and for next tank
int hp = GetEntProp(tank, Prop_Send, "m_iHealth") / 2; int hp = GetEntProp(tank, Prop_Send, "m_iHealth") / 2;
SetEntProp(tank, Prop_Send, "m_iHealth", hp); SetEntProp(tank, Prop_Send, "m_iHealth", hp);
finaleStage = hp; finaleStage = hp;
// Then, summon the next tank
ServerCommand("sm_forcespecial tank");
} }
} }
@ -362,7 +370,13 @@ public Action Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBr
} else { } else {
// New client has connected, not on first map. // New client has connected, not on first map.
// TODO: Check if Timer_UpdateMinPlayers is needed, or if this works: // TODO: Check if Timer_UpdateMinPlayers is needed, or if this works:
if(++abmExtraCount > 4) { // Never decrease abmExtraCount
int newCount = GetRealSurvivorsCount();
if(newCount > abmExtraCount) {
abmExtraCount = newCount;
}
// If 5 survivors, then set them up, TP them.
if(abmExtraCount > 4) {
RequestFrame(Frame_SetupNewClient, client); RequestFrame(Frame_SetupNewClient, client);
} }
} }
@ -427,8 +441,9 @@ public void Frame_SetupNewClient(int client) {
EquipPlayerWeapon(client, item); EquipPlayerWeapon(client, item);
} }
static float spawnPos[3]; static float spawnPos[3];
GetCenterPositionInSurvivorFlow(client, spawnPos); // TODO: Fix null
TeleportEntity(client, spawnPos, NULL_VECTOR, NULL_VECTOR); if(GetCenterPositionInSurvivorFlow(client, spawnPos))
TeleportEntity(client, spawnPos, NULL_VECTOR, NULL_VECTOR);
} }
public Action Timer_GiveClientKit(Handle hdl, int user) { public Action Timer_GiveClientKit(Handle hdl, int user) {
int client = GetClientOfUserId(user); int client = GetClientOfUserId(user);
@ -479,6 +494,7 @@ public void OnMapStart() {
extraKitsStarted = extraKitsAmount; extraKitsStarted = extraKitsAmount;
} }
} }
if(!isLateLoaded) { if(!isLateLoaded) {
isLateLoaded = false; isLateLoaded = false;
} }
@ -505,6 +521,7 @@ public void OnMapStart() {
HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
playersLoadedIn = 0; playersLoadedIn = 0;
finaleStage = 0;
} }
@ -521,7 +538,6 @@ public void OnMapEnd() {
} }
ammoPacks.Clear(); ammoPacks.Clear();
playersLoadedIn = 0; playersLoadedIn = 0;
L4D2_RunScript("ExtraPlayerHUD <- { Fields = { } }; HUDSetLayout(ExtraPlayerHud); HUDPlace( g_ModeScript.HUD_RIGHT_BOT, 0.72, 0.79, 0.25, 0.2 ); g_ModeScript");
} }
public void Event_RoundFreezeEnd(Event event, const char[] name, bool dontBroadcast) { public void Event_RoundFreezeEnd(Event event, const char[] name, bool dontBroadcast) {
@ -577,7 +593,7 @@ public Action Event_MapTransition(Event event, const char[] name, bool dontBroad
} }
//TODO: Possibly hacky logic of on third different ent id picked up, in short timespan, detect as set of 4 (pills, kits) & give extra //TODO: Possibly hacky logic of on third different ent id picked up, in short timespan, detect as set of 4 (pills, kits) & give extra
public Action Event_Pickup(int client, int weapon) { public Action Event_Pickup(int client, int weapon) {
char name[32]; static char name[32];
GetEntityClassname(weapon, name, sizeof(name)); GetEntityClassname(weapon, name, sizeof(name));
if(StrEqual(name, "weapon_first_aid_kit", true)) { if(StrEqual(name, "weapon_first_aid_kit", true)) {
if(isBeingGivenKit[client]) return Plugin_Continue; if(isBeingGivenKit[client]) return Plugin_Continue;
@ -1108,25 +1124,45 @@ stock void RunVScriptLong(const char[] sCode, any ...) {
} }
// Gets a position (from a nav area) // Gets a position (from a nav area)
stock void GetCenterPositionInSurvivorFlow(int target, float pos[3]) { stock bool GetCenterPositionInSurvivorFlow(int target, float pos[3]) {
static float ang[3];
int client = GetHighestFlowSurvivor(target); int client = GetHighestFlowSurvivor(target);
GetClientAbsOrigin(client, pos); if(client > 0) {
int nav = L4D_GetNearestNavArea(pos); GetClientAbsOrigin(client, pos);
L4D_FindRandomSpot(nav, pos); GetClientAbsAngles(client, ang);
pos[2] = -pos[2];
TR_TraceRayFilter(pos, ang, MASK_SHOT, RayType_Infinite, Filter_GroundOnly);
if(TR_DidHit()) {
TR_GetEndPosition(pos);
return true;
} else {
return false;
}
}
return false;
}
bool Filter_GroundOnly(int entity, int mask) {
return entity == 0;
} }
stock int GetLowestFlowSurvivor(int ignoreTarget = 0) { stock int GetLowestFlowSurvivor(int ignoreTarget = 0) {
int client = L4D_GetHighestFlowSurvivor(); int client = L4D_GetHighestFlowSurvivor();
float lowestFlow = L4D2Direct_GetFlowDistance(client); if(client != ignoreTarget) {
for(int i = 1; i <= MaxClients; i++) { return client;
if(ignoreTarget != i && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { } else {
if(L4D2Direct_GetFlowDistance(i) < lowestFlow) { client = -1;
client = i; float lowestFlow = L4D2Direct_GetFlowDistance(client);
lowestFlow = L4D2Direct_GetFlowDistance(i); for(int i = 1; i <= MaxClients; i++) {
if(ignoreTarget != i && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) {
if(L4D2Direct_GetFlowDistance(i) < lowestFlow) {
client = i;
lowestFlow = L4D2Direct_GetFlowDistance(i);
}
} }
} }
return client;
} }
return client;
} }
stock int GetHighestFlowSurvivor(int ignoreTarget = 0) { stock int GetHighestFlowSurvivor(int ignoreTarget = 0) {
@ -1134,6 +1170,7 @@ stock int GetHighestFlowSurvivor(int ignoreTarget = 0) {
if(client != ignoreTarget) { if(client != ignoreTarget) {
return client; return client;
} else { } else {
client = -1;
float highestFlow = L4D2Direct_GetFlowDistance(client); float highestFlow = L4D2Direct_GetFlowDistance(client);
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(ignoreTarget != i && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { if(ignoreTarget != i && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,107 @@
#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#include <sourcemod>
#include <sdktools>
//#include <sdkhooks>
public Plugin myinfo =
{
name = "L4D2 Tank Priority",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = ""
};
#define TANK_CLASS_ID 8
static int tankChooseVictimTicks[MAXPLAYERS+1]; //Per tank
static int totalTankDamage[MAXPLAYERS+1]; //Per survivor
static ArrayList clients;
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D/L4D2 only.");
}
clients = new ArrayList(2);
HookEvent("player_hurt", Event_PlayerHurt);
HookEvent("tank_spawn", Event_TankSpawn);
}
public Action L4D2_OnChooseVictim(int attacker, int &curTarget) {
int class = GetEntProp(attacker, Prop_Send, "m_zombieClass");
if(class != TANK_CLASS_ID) return Plugin_Continue;
//Find a new victim
if(++tankChooseVictimTicks[attacker] >= 200) {
tankChooseVictimTicks[attacker] = 0;
clients.Clear();
float tankPos[3], clientPos[3];
GetClientAbsOrigin(attacker, tankPos);
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) {
//If a player does less than 50 damage, and has green health add them to list
if(totalTankDamage[i] < 100 && GetClientHealth(i) > 40) {
GetClientAbsOrigin(i, clientPos);
float dist = GetVectorDistance(clientPos, tankPos);
// Only add targets who are far enough away from tank
if(dist > 5000.0) {
PrintToConsoleAll("Adding player %N to possible victim list. Dist=%f, Dmg=%d", i, dist, totalTankDamage[i]);
int index = clients.Push(i);
clients.Set(index, dist, 1);
}
}
}
}
if(clients.Length == 0) return Plugin_Continue;
clients.SortCustom(Sort_TankTargetter);
/*curTarget = clients.Get(0);*/
PrintToConsoleAll("[TankPriority] Player Selected to target: %N", curTarget);
//TODO: Possibly clear totalTankDamage
//return Plugin_Changed;
}
return Plugin_Continue;
}
int Sort_TankTargetter(int index1, int index2, Handle array, Handle hndl) {
int client1 = GetArrayCell(array, index1);
int client2 = GetArrayCell(array, index2);
float distance1 = GetArrayCell(array, index2, 0);
float distance2 = GetArrayCell(array, index2, 1);
/*500 units away, 0 damage vs 600 units away, 0 damage
-> target closest 500
500 units away, 10 damage, vs 600 units away 0 damage
500 - 10 = 450 vs 600
*/
return (totalTankDamage[client1] + RoundFloat(distance1)) - (totalTankDamage[client2] + RoundFloat(distance2));
}
public void Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast) {
int victim = GetClientOfUserId(event.GetInt("userid"));
int attacker = GetClientOfUserId(event.GetInt("attacker"));
int dmg = event.GetInt("dmg_health");
if(dmg > 0 && attacker > 0 && victim > 0 && IsFakeClient(victim) && GetEntProp(victim, Prop_Send, "m_zombieClass") == TANK_CLASS_ID) {
if(GetClientTeam(victim) == 3 && GetClientTeam(attacker) == 2) {
totalTankDamage[victim] += dmg;
}
}
}
public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) {
int tank = GetClientOfUserId(GetEventInt(event, "userid"));
if(tank > 0 && IsFakeClient(tank)) {
tankChooseVictimTicks[tank] = -20;
}
}

View file

@ -0,0 +1,291 @@
#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#define MAX_PLAYER_HISTORY 25
#include <sourcemod>
#include <sdktools>
//#include <sdkhooks>
public Plugin myinfo =
{
name = "Noob DB",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = ""
};
static Database DB;
static char query[1024];
static char reason[256];
static int WaitingForNotePlayer;
static char menuNoteTarget[32];
enum struct PlayerData {
char id[32];
char name[32];
}
static ArrayList lastPlayers;
public void OnPluginStart() {
if(!SQL_CheckConfig("stats")) {
SetFailState("No database entry for 'stats'; no database to connect to.");
}
if(!ConnectDB()) {
SetFailState("Failed to connect to database.");
}
lastPlayers = new ArrayList(sizeof(PlayerData));
HookEvent("player_disconnect", Event_PlayerDisconnect);
HookEvent("player_first_spawn", Event_FirstSpawn);
RegAdminCmd("sm_note", Command_AddNote, ADMFLAG_KICK, "Add a note to a player");
RegAdminCmd("sm_notes", Command_ListNotes, ADMFLAG_KICK, "List notes for a player");
RegAdminCmd("sm_notedisconnected", Command_AddNoteDisconnected, ADMFLAG_KICK, "Add a note to any disconnected players");
}
public Action Command_AddNoteDisconnected(int client, int args) {
if(lastPlayers.Length == 0) {
ReplyToCommand(client, "No disconnected players recorded.");
return Plugin_Handled;
}
Menu menu = new Menu(Menu_Disconnected);
menu.SetTitle("Add Note For Disconnected");
for(int i = lastPlayers.Length + 1; i >= 0; i--) {
PlayerData data;
lastPlayers.GetArray(i, data, sizeof(data));
menu.AddItem(data.id, data.name);
}
menu.Display(client, 0);
return Plugin_Handled;
}
public int Menu_Disconnected(Menu menu, MenuAction action, int client, int item) {
if (action == MenuAction_Select) {
menu.GetItem(item, menuNoteTarget, sizeof(menuNoteTarget));
PrintToChat(client, "Enter a note for %s:", menuNoteTarget);
WaitingForNotePlayer = client;
} else if (action == MenuAction_End)
delete menu;
}
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) {
if(WaitingForNotePlayer == client) {
WaitingForNotePlayer = 0;
static char buffer[32];
GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer));
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgs);
DB.Query(DB_AddNote, query);
LogAction(client, -1, "\"%L\" added note for \"%s\": \"%s\"", client, menuNoteTarget, sArgs);
Format(buffer, sizeof(buffer), "%N: ", client);
ShowActivity2(client, buffer, "added a note for %s: \"%s\"", menuNoteTarget, sArgs);
return Plugin_Stop;
}
return Plugin_Continue;
}
public Action Command_AddNote(int client, int args) {
if(args < 2) {
ReplyToCommand(client, "Syntax: sm_note <player> <note>");
} else {
static char target_name[MAX_TARGET_LENGTH];
GetCmdArg(1, target_name, sizeof(target_name));
GetCmdArg(2, reason, sizeof(reason));
int target_list[MAXPLAYERS], target_count;
bool tn_is_ml;
if ((target_count = ProcessTargetString(
target_name,
client,
target_list,
1,
COMMAND_FILTER_NO_MULTI,
target_name,
sizeof(target_name),
tn_is_ml)) <= 0
) {
ReplyToTargetError(client, target_count);
return Plugin_Handled;
}
static char auth[32];
GetClientAuthId(target_list[0], AuthId_Steam2, auth, sizeof(auth));
static char authMarker[32];
if(client > 0)
GetClientAuthId(client, AuthId_Steam2, authMarker, sizeof(authMarker));
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", auth, authMarker, reason);
DB.Query(DB_AddNote, query);
LogAction(client, target_list[0], "\"%L\" added note for \"%L\": \"%s\"", client, target_list[0], reason);
ShowActivity(client, "added a note for \"%N\": \"%s\"", target_list[0], reason);
}
return Plugin_Handled;
}
public Action Command_ListNotes(int client, int args) {
if(args < 1) {
ReplyToCommand(client, "Syntax: sm_notes <player>");
} else {
static char target_name[MAX_TARGET_LENGTH];
GetCmdArg(1, target_name, sizeof(target_name));
GetCmdArg(2, reason, sizeof(reason));
int target_list[MAXPLAYERS], target_count;
bool tn_is_ml;
if ((target_count = ProcessTargetString(
target_name,
client,
target_list,
1,
COMMAND_FILTER_NO_MULTI,
target_name,
sizeof(target_name),
tn_is_ml)) <= 0
) {
ReplyToTargetError(client, target_count);
return Plugin_Handled;
}
static char auth[32];
GetClientAuthId(target_list[0], AuthId_Steam2, auth, sizeof(auth));
DB.Format(query, sizeof(query), "SELECT notes.content, stats_users.last_alias FROM `notes` JOIN stats_users ON markedBy = stats_users.steamid WHERE notes.`steamid` = '%s'", auth);
ReplyToCommand(client, "Fetching notes...");
DataPack pack = new DataPack();
pack.WriteCell(GetClientUserId(client));
pack.WriteCell(GetClientUserId(target_list[0]));
pack.WriteString(auth);
DB.Query(DB_ListNotesForPlayer, query, pack);
}
return Plugin_Handled;
}
bool ConnectDB() {
static char error[255];
DB = SQL_Connect("stats", true, error, sizeof(error));
if (DB== null) {
LogError("Database error %s", error);
delete DB;
return false;
} else {
PrintToServer("Connected to database stats");
SQL_LockDatabase(DB);
SQL_FastQuery(DB, "SET NAMES \"UTF8mb4\"");
SQL_UnlockDatabase(DB);
DB.SetCharset("utf8mb4");
return true;
}
}
public Action Event_FirstSpawn(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientUserId(event.GetInt("userid"));
if(client > 0 && client <= MaxClients && !IsFakeClient(client)) {
static char auth[32];
GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth));
DB.Format(query, sizeof(query), "SELECT notes.content, stats_users.last_alias FROM `notes` JOIN stats_users ON markedBy = stats_users.steamid WHERE notes.`steamid` = '%s'", auth);
DB.Query(DB_FindNotes, query, GetClientUserId(client));
}
}
public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
if(!event.GetBool("bot")) {
PlayerData data;
event.GetString("networkid", data.id, sizeof(data.id));
if(!StrEqual(data.id, "BOT")) {
if(!IsPlayerInHistory(data.id)) {
event.GetString("name", data.name, sizeof(data.name));
lastPlayers.PushArray(data);
if(lastPlayers.Length > MAX_PLAYER_HISTORY) {
lastPlayers.Erase(0);
}
}
}
}
}
bool IsPlayerInHistory(const char[] id) {
static PlayerData data;
for(int i = 0; i < lastPlayers.Length; i++) {
lastPlayers.GetArray(i, data, sizeof(data));
if(StrEqual(data.id, id))
return true;
}
return false;
}
public void DB_FindNotes(Database db, DBResultSet results, const char[] error, any data) {
if(db == null || results == null) {
LogError("DB_FindNotes returned error: %s", error);
return;
}
//initialize variables
int client = GetClientOfUserId(data);
if(client && results.RowCount > 0) {
static char noteCreator[32];
PrintChatToAdmins("Notes for %s", client);
while(results.FetchRow()) {
results.FetchString(0, reason, sizeof(reason));
results.FetchString(1, noteCreator, sizeof(noteCreator));
PrintChatToAdmins("%s: %s", noteCreator, reason);
}
}
}
public void DB_ListNotesForPlayer(Database db, DBResultSet results, const char[] error, DataPack pack) {
if(db == null || results == null) {
LogError("DB_ListNotesForPlayer returned error: %s", error);
return;
}
//initialize variables
static char auth[32];
pack.Reset();
int client = GetClientOfUserId(pack.ReadCell());
int target = GetClientOfUserId(pack.ReadCell());
pack.ReadString(auth, sizeof(auth));
delete pack;
if(client > 0) {
if(results.RowCount > 0) {
if(target > 0) {
PrintToChat(client, "Notes for %N:", target);
} else {
PrintToChat(client, "Notes for %s:", auth);
}
char noteCreator[32];
while(results.FetchRow()) {
results.FetchString(0, reason, sizeof(reason));
results.FetchString(1, noteCreator, sizeof(noteCreator));
PrintToChat(client, "%s: %s", noteCreator, reason);
}
} else {
if(target > 0)
PrintToChat(client, "No notes found for %N", target);
else
PrintToChat(client, "No notes found for %s", auth);
}
}
}
public void DB_AddNote(Database db, DBResultSet results, const char[] error, any data) {
if(db == null || results == null) {
LogError("DB_AddNote returned error: %s", error);
return;
}
}
stock void PrintChatToAdmins(const char[] format, any ...) {
char buffer[254];
VFormat(buffer, sizeof(buffer), format, 2);
for(int i = 1; i < MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) {
AdminId admin = GetUserAdmin(i);
if(admin != INVALID_ADMIN_ID) {
PrintToChat(i, "%s", buffer);
}
}
}
PrintToServer("%s", buffer);
}