This commit is contained in:
Jackz 2023-04-29 10:25:49 -05:00
parent 078b7c4bf6
commit cb66da2ca2
No known key found for this signature in database
GPG key ID: E0BBD94CF657F603
50 changed files with 3176 additions and 383 deletions

BIN
plugins/GrabEnt.smx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
plugins/l4d2_hats.smx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

533
scripting/GrabEnt.sp Normal file
View file

@ -0,0 +1,533 @@
#define DEBUG
#define PLUGIN_AUTHOR "Stugger"
#define PLUGIN_VERSION "2.2"
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
public Plugin myinfo =
{
name = "GrabEnt",
author = PLUGIN_AUTHOR,
description = "Grab then Move, Push/Pull or Rotate the entity you're looking at until released",
version = PLUGIN_VERSION,
url = ""
};
int g_pGrabbedEnt[MAXPLAYERS + 1];
int g_eRotationAxis[MAXPLAYERS + 1] = { -1, ... };
int g_eOriginalColor[MAXPLAYERS + 1][4];
float g_pLastButtonPress[MAXPLAYERS + 1];
float g_fGrabOffset[MAXPLAYERS + 1][3];
float g_fGrabDistance[MAXPLAYERS + 1];
MoveType g_pLastMoveType[MAXPLAYERS + 1];
bool g_pInRotationMode[MAXPLAYERS + 1];
bool g_eReleaseFreeze[MAXPLAYERS + 1] = { true, ... };
Handle g_eGrabTimer[MAXPLAYERS+1];
int g_BeamSprite;
int g_HaloSprite;
#define MAX_FORBIDDEN_CLASSNAMES 10
static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
"env_physics_blocker",
"env_player_blocker",
"func_brush",
"func_simpleladder",
"func_button",
"func_elevator",
"func_button_timed",
// "func_movelinear",
// "infected",
"func_lod",
"func_door",
"prop_ragdoll"
};
public void OnPluginStart()
{
RegAdminCmd("sm_grabent_freeze", Cmd_ReleaseFreeze, ADMFLAG_CHEATS, "<0/1> - Toggle entity freeze/unfreeze on release.");
RegAdminCmd("sm_grab", Cmd_Grab, ADMFLAG_CHEATS, "Toggle Grab the entity in your crosshair.");
RegAdminCmd("+grabent", Cmd_Grab, ADMFLAG_CHEATS, "Grab the entity in your crosshair.");
RegAdminCmd("-grabent", Cmd_Release, ADMFLAG_CHEATS, "Release the grabbed entity.");
}
public void OnMapStart()
{
g_BeamSprite = PrecacheModel("materials/sprites/laser.vmt", true);
g_HaloSprite = PrecacheModel("materials/sprites/halo01.vmt", true);
for (int i = 0; i < MAXPLAYERS; i++) {
g_pGrabbedEnt[i] = -1;
g_eRotationAxis[i] = -1;
g_pLastButtonPress[i] = 0.0;
g_pInRotationMode[i] = false;
g_eReleaseFreeze[i] = true;
g_eGrabTimer[i] = null;
}
}
public void OnClientDisconnect(client)
{
if (g_pGrabbedEnt[client] != -1 && IsValidEntity(g_pGrabbedEnt[client]))
Cmd_Release(client, 0);
g_eRotationAxis[client] = -1;
g_pLastButtonPress[client] = 0.0;
g_pInRotationMode[client] = false;
g_eReleaseFreeze[client] = true;
}
//============================================================================
// FREEZE SETTING COMMAND //
//============================================================================
public Action Cmd_ReleaseFreeze(client, args)
{
if (args < 1) {
ReplyToCommand(client, "\x04[SM]\x01 \x05sm_grabent_freeze <0/1>\x01 -- \x050\x01: Entity unfreeze on release, \x051\x01: Entity freeze on release");
return Plugin_Handled;
}
char sArg[16];
GetCmdArg(1, sArg, sizeof(sArg)); TrimString(sArg);
if (!StrEqual(sArg, "0") && !StrEqual(sArg, "1")) {
ReplyToCommand(client, "\x04[SM]\x01 ERROR: Value can only be either 0 or 1");
return Plugin_Handled;
}
g_eReleaseFreeze[client] = StrEqual(sArg, "1") ? true : false;
PrintToChat(client, "\x04[SM]\x01 Entities will now be \x05%s\x01 on Release!", g_eReleaseFreeze[client] == true ? "Frozen" : "Unfrozen");
return Plugin_Handled;
}
//============================================================================
// GRAB ENTITY COMMAND //
//============================================================================
public Action Cmd_Grab(client, args) {
if (client < 1 || client > MaxClients || !IsClientInGame(client))
return Plugin_Handled;
if (g_pGrabbedEnt[client] > 0 && IsValidEntity(g_pGrabbedEnt[client])) {
Cmd_Release(client, 0);
return Plugin_Handled;
}
// int ent = GetClientAimTarget(client, false);
int ent = GetLookingEntity(client, Filter_IgnoreForbidden);
if (ent == -1 || !IsValidEntity(ent))
return Plugin_Handled; //<-- timer to allow search for entity??
float entOrigin[3], playerGrabOrigin[3];
GetEntPropVector(ent, Prop_Send, "m_vecOrigin", entOrigin);
GetClientEyePosition(client, playerGrabOrigin);
g_pGrabbedEnt[client] = ent;
// Get the point at which the ray first hit the entity
float initialRay[3];
initialRay[0] = GetInitialRayPosition(client, 'x');
initialRay[1] = GetInitialRayPosition(client, 'y');
initialRay[2] = GetInitialRayPosition(client, 'z');
// Calculate the offset between intitial ray hit and the entities origin
g_fGrabOffset[client][0] = entOrigin[0] - initialRay[0];
g_fGrabOffset[client][1] = entOrigin[1] - initialRay[1];
g_fGrabOffset[client][2] = entOrigin[2] - initialRay[2];
// Calculate the distance between ent and player
float xDis = Pow(initialRay[0]-(playerGrabOrigin[0]), 2.0);
float yDis = Pow(initialRay[1]-(playerGrabOrigin[1]), 2.0);
float zDis = Pow(initialRay[2]-(playerGrabOrigin[2]), 2.0);
g_fGrabDistance[client] = SquareRoot((xDis)+(yDis)+(zDis));
// Get and Store entities original color (useful if colored)
int entColor[4];
int colorOffset = GetEntSendPropOffs(ent, "m_clrRender");
if (colorOffset > 0)
{
entColor[0] = GetEntData(ent, colorOffset, 1);
entColor[1] = GetEntData(ent, colorOffset + 1, 1);
entColor[2] = GetEntData(ent, colorOffset + 2, 1);
entColor[3] = GetEntData(ent, colorOffset + 3, 1);
}
g_eOriginalColor[client][0] = entColor[0];
g_eOriginalColor[client][1] = entColor[1];
g_eOriginalColor[client][2] = entColor[2];
g_eOriginalColor[client][3] = entColor[3];
// Set entities color to grab color (green and semi-transparent)
SetEntityRenderMode(ent, RENDER_TRANSALPHA);
SetEntityRenderColor(ent, 0, 255, 0, 235);
// Freeze entity
char sClass[64];
GetEntityClassname(ent, sClass, sizeof(sClass)); TrimString(sClass);
if (StrEqual(sClass, "player", false)) {
g_pLastMoveType[ent] = GetEntityMoveType(ent);
SetEntityMoveType(ent, MOVETYPE_NONE);
} else
AcceptEntityInput(ent, "DisableMotion");
g_pLastMoveType[client] = GetEntityMoveType(client);
// Disable weapon prior to timer
SetWeaponDelay(client, 1.0);
// Make sure rotation mode can immediately be entered
g_pLastButtonPress[client] = GetGameTime() - 2.0;
g_pInRotationMode[client] = false;
DataPack pack;
g_eGrabTimer[client] = CreateDataTimer(0.1, Timer_UpdateGrab, pack, TIMER_REPEAT);
pack.WriteCell(client);
return Plugin_Handled;
}
//============================================================================
// TIMER FOR GRAB ENTITY //
//============================================================================
public Action Timer_UpdateGrab(Handle timer, DataPack pack) {
int client;
pack.Reset();
client = pack.ReadCell();
if (!IsValidEntity(client) || client < 1 || client > MaxClients || !IsClientInGame(client))
return Plugin_Stop;
if (g_pGrabbedEnt[client] == -1 || !IsValidEntity(g_pGrabbedEnt[client]))
return Plugin_Stop;
// Continuously delay use of weapon, as to not fire any bullets when pushing/pulling/rotating
SetWeaponDelay(client, 1.0);
// *** Enable/Disable Rotation Mode
if (GetClientButtons(client) & IN_RELOAD) {
// Avoid instant enable/disable of rotation mode by requiring a one second buffer
if (GetGameTime() - g_pLastButtonPress[client] >= 1.0) {
g_pLastButtonPress[client] = GetGameTime();
g_pInRotationMode[client] = g_pInRotationMode[client] == true ? false : true;
PrintToChat(client, "\x04[SM]\x01 Rotation Mode \x05%s\x01", g_pInRotationMode[client] == true ? "Enabled" : "Disabled");
// Restore the entities color and alpha if enabling
if(g_pInRotationMode[client]) {
SetEntityRenderColor(g_pGrabbedEnt[client], 255, 255, 255, 255);
PrintToChat(client, "\x05[A]\x01 RED \x05[S]\x01 GREEN \x05[D]\x01 BLUE \x05[W]\x01 SHOW RINGS");
}
// Change back to grabbed color if disabling
else
SetEntityRenderColor(g_pGrabbedEnt[client], 0, 255, 0, 235);
}
}
// ***In Rotation Mode
if (g_pInRotationMode[client]) {
SetEntityMoveType(client, MOVETYPE_NONE);
float ang[3], pos[3], mins[3], maxs[3];
GetEntPropVector(g_pGrabbedEnt[client], Prop_Send, "m_angRotation", ang);
GetEntPropVector(g_pGrabbedEnt[client], Prop_Send, "m_vecOrigin", pos);
GetEntPropVector(g_pGrabbedEnt[client], Prop_Send, "m_vecMins", mins);
GetEntPropVector(g_pGrabbedEnt[client], Prop_Send, "m_vecMaxs", maxs);
// If the entity is a child, it will have a null position, so we'll hesitantly use the parents position
int parent = GetEntPropEnt(g_pGrabbedEnt[client], Prop_Data, "m_hMoveParent");
if (parent > 0 && IsValidEntity(parent))
GetEntPropVector(parent, Prop_Send, "m_vecOrigin", pos);
// Get rotation axis from button press
int buttonPress = GetClientButtons(client);
switch(buttonPress) {
case IN_FORWARD: {
g_eRotationAxis[client] = -1; // [W] = Show Rings
PrintToChat(client, "\x04[SM]\x01 Show Rings \x05On\x01");
}
case IN_MOVELEFT: {
g_eRotationAxis[client] = 0; // [A] = x axis
PrintToChat(client, "\x04[SM]\x01 Rotation Axis \x05X\x01");
}
case IN_BACK: {
g_eRotationAxis[client] = 1; // [S] = y axis
PrintToChat(client, "\x04[SM]\x01 Rotation Axis \x05Y\x01");
}
case IN_MOVERIGHT: {
g_eRotationAxis[client] = 2; // [D] = z axis
PrintToChat(client, "\x04[SM]\x01 Rotation Axis \x05Z\x01");
}
}
// Reset angles when A+S+D is pressed
if((buttonPress & IN_MOVELEFT) && (buttonPress & IN_BACK) && (buttonPress & IN_MOVERIGHT)) {
ang[0] = 0.0; ang[1] = 0.0; ang[2] = 0.0;
g_eRotationAxis[client] = -1;
}
// Largest side should dictate the diameter of the rings
float diameter, sendAng[3];
diameter = (maxs[0] > maxs[1]) ? (maxs[0] + 10.0) : (maxs[1] + 10.0);
diameter = ((maxs[2] + 10.0) > diameter) ? (maxs[2] + 10.0) : diameter;
// Sending original ang will cause non-stop rotation issue
sendAng = ang;
// Draw rotation rings
switch(g_eRotationAxis[client]) {
case -1: CreateRing(client, sendAng, pos, diameter, 0, true); // all 3 rings
case 0: CreateRing(client, sendAng, pos, diameter, 0, false); // red (x)
case 1: CreateRing(client, sendAng, pos, diameter, 1, false); // green (y)
case 2: CreateRing(client, sendAng, pos, diameter, 2, false); // blue (z)
}
// Rotate with mouse if on a rotation axis (A,S,D)
if (g_eRotationAxis[client] != -1) {
// + Rotate
if (GetClientButtons(client) & IN_ATTACK)
ang[g_eRotationAxis[client]] += 10.0;
// - Rotate
else if (GetClientButtons(client) & IN_ATTACK2)
ang[g_eRotationAxis[client]] -= 10.0;
}
TeleportEntity(g_pGrabbedEnt[client], NULL_VECTOR, ang, NULL_VECTOR);
}
// ***Not in Rotation Mode
if (!g_pInRotationMode[client] || g_eRotationAxis[client] == -1) {
// Keep track of player noclip as to avoid forced enable/disable
if(!g_pInRotationMode[client]) {
SetEntityMoveType(client, g_pLastMoveType[client])
}
// Push entity (Allowed if we're in rotation mode, not on a rotation axis (-1))
if (GetClientButtons(client) & IN_ATTACK)
{
if (g_fGrabDistance[client] < 80)
g_fGrabDistance[client] += 10;
else
g_fGrabDistance[client] += g_fGrabDistance[client] / 25;
}
// Pull entity (Allowed if we're in rotation mode, not on a rotation axis (-1))
else if (GetClientButtons(client) & IN_ATTACK2 && g_fGrabDistance[client] > 25)
{
if (g_fGrabDistance[client] < 80)
g_fGrabDistance[client] -= 10;
else
g_fGrabDistance[client] -= g_fGrabDistance[client] / 25;
}
g_eRotationAxis[client] = -1;
}
// *** Runs whether in rotation mode or not
float entNewPos[3];
entNewPos[0] = GetEntNewPosition(client, 'x') + g_fGrabOffset[client][0];
entNewPos[1] = GetEntNewPosition(client, 'y') + g_fGrabOffset[client][1];
entNewPos[2] = GetEntNewPosition(client, 'z') + g_fGrabOffset[client][2];
float mins[3];
GetEntPropVector(g_pGrabbedEnt[client], Prop_Data, "m_vecMins", mins);
entNewPos[2] += mins[2];
TeleportEntity(g_pGrabbedEnt[client], entNewPos, NULL_VECTOR, NULL_VECTOR);
return Plugin_Handled;
}
//============================================================================
// RELEASE ENTITY COMMAND //
//============================================================================
public Action Cmd_Release(client, args) {
if (!IsValidEntity(client) || client < 1 || client > MaxClients || !IsClientInGame(client))
return Plugin_Handled;
if (g_pGrabbedEnt[client] == -1 || !IsValidEntity(g_pGrabbedEnt[client]))
return Plugin_Handled;
// Allow near-immediate use of weapon
SetWeaponDelay(client, 0.2);
SetEntityMoveType(client, g_pLastMoveType[client]);
// Unfreeze if target was a player and unfreeze if setting is set to 0
char sClass[64];
GetEntityClassname(g_pGrabbedEnt[client], sClass, sizeof(sClass)); TrimString(sClass);
if (StrEqual(sClass, "player", false))
SetEntityMoveType(g_pGrabbedEnt[client], g_pLastMoveType[g_pGrabbedEnt[client]]);
else if (g_eReleaseFreeze[client] == false)
AcceptEntityInput(g_pGrabbedEnt[client], "EnableMotion");
// Restore color and alpha to original prior to grab
SetEntityRenderColor(g_pGrabbedEnt[client], g_eOriginalColor[client][0], g_eOriginalColor[client][1], g_eOriginalColor[client][2], g_eOriginalColor[client][3]);
// Kill the grab timer and reset control values
if (g_eGrabTimer[client] != null) {
KillTimer(g_eGrabTimer[client]);
g_eGrabTimer[client] = null;
}
g_pGrabbedEnt[client] = -1;
g_eRotationAxis[client] = -1;
g_pInRotationMode[client] = false;
return Plugin_Handled;
}
//============================================================================
// *** UTILITIES *** //
//============================================================================
int GetLookingEntity(int client, TraceEntityFilter filter) {
static float pos[3], ang[3];
GetClientEyePosition(client, pos);
GetClientEyeAngles(client, ang);
TR_TraceRayFilter(pos, ang, MASK_ALL, RayType_Infinite, filter, client);
if(TR_DidHit()) {
return TR_GetEntityIndex();
}
return -1;
}
stock float GetEntNewPosition(int client, char axis)
{
if (client > 0 && client <= MaxClients && IsClientInGame(client)) {
float endPos[3], clientEye[3], clientAngle[3], direction[3];
GetClientEyePosition(client, clientEye);
GetClientEyeAngles(client, clientAngle);
GetAngleVectors(clientAngle, direction, NULL_VECTOR, NULL_VECTOR);
ScaleVector(direction, g_fGrabDistance[client]);
AddVectors(clientEye, direction, endPos);
TR_TraceRayFilter(clientEye, endPos, MASK_SOLID, RayType_EndPoint, TraceRayFilterEnt, client);
if (TR_DidHit(INVALID_HANDLE)) {
TR_GetEndPosition(endPos);
}
if (axis == 'x') return endPos[0];
else if (axis == 'y') return endPos[1];
else if (axis == 'z') return endPos[2];
}
return 0.0;
}
/////
stock float GetInitialRayPosition(int client, char axis)
{
if (client > 0 && client <= MaxClients && IsClientInGame(client)) {
float endPos[3], clientEye[3], clientAngle[3];
GetClientEyePosition(client, clientEye);
GetClientEyeAngles(client, clientAngle);
TR_TraceRayFilter(clientEye, clientAngle, MASK_SOLID, RayType_Infinite, TraceRayFilterActivator, client);
if (TR_DidHit(INVALID_HANDLE))
TR_GetEndPosition(endPos);
if (axis == 'x') return endPos[0];
else if (axis == 'y') return endPos[1];
else if (axis == 'z') return endPos[2];
}
return 0.0;
}
/////
stock void SetWeaponDelay(int client, float delay)
{
if (IsValidEntity(client) && client > 0 && client <= MaxClients && IsClientInGame(client)) {
int pWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
if (IsValidEntity(pWeapon) && pWeapon != -1) {
SetEntPropFloat(pWeapon, Prop_Send, "m_flNextPrimaryAttack", GetGameTime() + delay);
SetEntPropFloat(pWeapon, Prop_Send, "m_flNextSecondaryAttack", GetGameTime() + delay);
}
}
}
/////
stock void CreateRing(int client, float ang[3], float pos[3], float diameter, int axis, bool trio)
{
if (!IsValidEntity(client) || client < 1 || client > MaxClients || !IsClientInGame(client))
return;
float ringVecs[26][3];
int ringColor[3][4];
ringColor[0] = { 255, 0, 0, 255 };
ringColor[1] = { 0, 255, 0, 255 };
ringColor[2] = { 0, 0, 255, 255 };
int numSides = (!trio) ? 26 : 17;
float angIncrement = (!trio) ? 15.0 : 24.0;
for (int i = 1; i < numSides; i++) {
float direction[3], endPos[3];
switch(axis) {
case 0: GetAngleVectors(ang, direction, NULL_VECTOR, NULL_VECTOR);
case 1:
{
ang[2] = 0.0;
GetAngleVectors(ang, NULL_VECTOR, direction, NULL_VECTOR);
}
case 2: GetAngleVectors(ang, NULL_VECTOR, NULL_VECTOR, direction);
}
ScaleVector(direction, diameter);
AddVectors(pos, direction, endPos);
if (i == 1) ringVecs[0] = endPos;
ringVecs[i] = endPos;
ang[axis] += angIncrement;
TE_SetupBeamPoints(ringVecs[i-1], ringVecs[i], g_BeamSprite, g_HaloSprite, 0, 15, 0.2, 2.5, 2.5, 1, 0.0, ringColor[axis], 10);
TE_SendToClient(client, 0.0);
if(trio && i == numSides-1 && axis < 2) {
i = 0;
ang[axis] -= angIncrement * (numSides-1);
axis += 1;
}
}
}
//============================================================================
// *** FILTERS *** //
//============================================================================
public bool TraceRayFilterEnt(int entity, int mask, any:client)
{
if (entity == client || entity == g_pGrabbedEnt[client])
return false;
return true;
}
/////
public bool TraceRayFilterActivator(int entity, int mask, any:activator)
{
if (entity == activator)
return false;
return true;
}
bool Filter_IgnoreForbidden(int entity, int mask, int data) {
if(entity == data || entity == 0) return false;
if(entity <= MaxClients) return true;
static char classname[32];
GetEntityClassname(entity, classname, sizeof(classname));
for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) {
if(StrEqual(FORBIDDEN_CLASSNAMES[i], classname)) {
return false;
}
}
return true;
}

View file

@ -185,7 +185,7 @@ int GetAllowedPlayerIndex(const char[] authid2) {
public void OnClientPostAdminCheck(int client) {
if(!IsFakeClient(client)) {
if(reserveMode == Reserve_AdminOnly && GetUserAdmin(client) == INVALID_ADMIN_ID) {
static char auth[32];
char auth[32];
GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth));
if(GetAllowedPlayerIndex(auth) == -1) {
KickClient(client, "Sorry, server is reserved");
@ -454,7 +454,7 @@ public Action Command_SetClientModel(int client, int args) {
if(args < 1) {
ReplyToCommand(client, "Usage: sm_model <model> [player] ['keep']");
} else {
static char arg1[2], arg2[16], arg3[8];
char arg1[2], arg2[16], arg3[8];
GetCmdArg(1, arg1, sizeof(arg1));
GetCmdArg(2, arg2, sizeof(arg2));
GetCmdArg(3, arg3, sizeof(arg3));
@ -644,6 +644,8 @@ public Action VGUIMenu(UserMsg msg_id, Handle bf, const int[] players, int playe
public void OnClientPutInServer(int client) {
if(!IsFakeClient(client))
SDKHook(client, SDKHook_WeaponEquip, Event_OnWeaponEquip);
else
SDKHook(client, SDKHook_OnTakeDamage, Event_OnTakeDamage);
}
public void OnClientDisconnect(int client) {
@ -656,14 +658,16 @@ public void OnClientDisconnect(int client) {
botDropMeleeWeapon[client] = -1;
}
}
public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client && !IsFakeClient(client)) {
static char auth[32];
char auth[32];
GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth));
SteamIDs.Remove(auth);
}
}
int disabledItem[2048];
//Can also probably prevent kit drop to pick them up
public void Event_WeaponDrop(Event event, const char[] name, bool dontBroadcast) {
@ -744,7 +748,7 @@ public void Event_BotPlayerSwap(Event event, const char[] name, bool dontBroadca
public Action Event_OnWeaponDrop(int client, int weapon) {
if(!IsValidEntity(weapon) || !IsFakeClient(client)) return Plugin_Continue;
if(GetEntProp(client, Prop_Send, "m_humanSpectatorUserID") > 0) {
static char wpn[32];
char wpn[32];
GetEdictClassname(weapon, wpn, sizeof(wpn));
if(StrEqual(wpn, "weapon_melee") || StrEqual(wpn, "weapon_pistol_magnum")) {
#if defined DEBUG
@ -757,7 +761,8 @@ public Action Event_OnWeaponDrop(int client, int weapon) {
return Plugin_Continue;
}
public void Frame_HideEntity(int entity) {
TeleportEntity(entity, OUT_OF_BOUNDS, NULL_VECTOR, NULL_VECTOR);
if(IsValidEntity(entity))
TeleportEntity(entity, OUT_OF_BOUNDS, NULL_VECTOR, NULL_VECTOR);
}
//STUCK BOTS WITH ZOMBIES FIX
public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) {
@ -768,11 +773,11 @@ public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, floa
return Plugin_Continue;
}
bool attackerVisible = IsEntityInSightRange(victim, attacker, 130.0, 100.0);
bool attackerVisible = IsEntityInSightRange(victim, attacker, 130.0, 10000.0);
if(!attackerVisible) {
//Zombie is behind the bot, reduce damage taken and slowly kill zombie (1/10 of default hp per hit)
damage /= 2.0;
SDKHooks_TakeDamage(attacker, victim, victim, 10.0);
SDKHooks_TakeDamage(attacker, victim, victim, 30.0);
return Plugin_Changed;
}
}

View file

@ -101,6 +101,10 @@ public void OnPluginEnd() {
TriggerTimer(pushTimer, true);
}
public void OnMapEnd() {
TriggerTimer(pushTimer, true);
}
public void OnMapStart() {
static char curMap[64];
GetCurrentMap(curMap, sizeof(curMap));

View file

@ -220,11 +220,11 @@ public void DB_OnConnectCheck(Database db, DBResultSet results, const char[] err
} else {
//No failure, check the data.
while(client > 0 && results.FetchRow()) { //Is there a ban found?
static char reason[128], steamid[64], public_message[255];
static char reason[255], steamid[64], public_message[255];
DBResult colResult;
results.FetchString(1, steamid, sizeof(steamid));
results.FetchString(0, reason, sizeof(reason), colResult);
results.FetchString(1, steamid, sizeof(steamid));
if(colResult == DBVal_Null) {
reason[0] = '\0';
}
@ -259,7 +259,7 @@ public void DB_OnConnectCheck(Database db, DBResultSet results, const char[] err
g_db.Format(query, sizeof(query), "UPDATE bans SET times_tried=times_tried+1 WHERE steamid = '%s'", steamid);
g_db.Query(DB_GenericCallback, query);
} else {
LogAction(-1, client, "%N (%s) was previously banned from server: \"%s\"", client, steamid, reason);
LogAction(-1, client, "\"%L\" was previously banned from server: \"%s\"", client, reason);
// User was previously banned
PrintChatToAdmins("%N (%s) has a previous suspended/expired ban of reason \"%s\"", client, steamid, reason);
}

View file

@ -240,6 +240,7 @@ void ResetClient(int victim, bool wipe = true) {
Trolls[i].activeFlagClients[victim] = -1;
}
}
noRushingUsSpeed[victim] = 1.0;
BaseComm_SetClientMute(victim, false);
SetEntityGravity(victim, 1.0);
SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0);
@ -368,8 +369,8 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi
// Log all actions, indicating if constant or single-fire, and if any flags
if(!silent) {
if(isActive) {
CShowActivityEx(activator, "[FTT] ", "deactivated {yellow}\"%s\"{default} on %N. ", troll.name, victim);
LogAction(activator, victim, "\"%L\" deactivated {yellow}\"%s\"{default} on \"%L\"", activator, troll.name, victim);
CShowActivityEx(activator, "[FTT] ", "deactivated {yellow}%s{default} on %N. ", troll.name, victim);
LogAction(activator, victim, "\"%L\" deactivated \"%s\" on \"%L\"", activator, troll.name, victim);
} else {
static char flagName[MAX_TROLL_FLAG_LENGTH];
// strcopy(flagName, sizeof(flagName), troll.name)

View file

@ -46,6 +46,19 @@ void SetupsTrollCombos() {
combo.AddTroll("Witch Magnet");
#endif
SetupCombo(combo, "Rush Stopper");
combo.AddTroll("Special Magnet");
combo.AddTroll("Tank Magnet");
#if defined _behavior_included
combo.AddTroll("Witch Magnet");
#endif
combo.AddTroll("No Button Touchie", TrollMod_Constant, 17);
combo.AddTroll("Slow Speed", TrollMod_Constant, 2);
combo.AddTroll("Instant Commons", TrollMod_Instant, 1);
// combo.AddTroll("Swarm", TrollMod_Instant);
combo.AddTroll("Vomit Player");
combo.AddTroll("Dull Melee", .flags=2);
SetupCombo(combo, "Tank Run Noob");
combo.AddTroll("Slow Speed");
combo.AddTroll("Tank Magnet");

View file

@ -57,7 +57,7 @@ public Action Command_InstaSpecial(int client, int args) {
}
}
if(successes > 0)
ShowActivityEx(client, "[FTT] ", "spawned Insta-%s™ near %s", arg2, target_name);
CShowActivityEx(client, "[FTT] ", "spawned {green}Insta-%s™{default} near {green}%s", arg2, target_name);
}
@ -123,7 +123,7 @@ public Action Command_InstaSpecialFace(int client, int args) {
}
}
if(successes > 0)
ShowActivityEx(client, "[FTT] ", "spawned Insta-%s™ on %s", arg2, target_name);
CShowActivityEx(client, "[FTT] ", "spawned {green}Insta-%s™{default} on {green}%s", arg2, target_name);
}
return Plugin_Handled;
}

View file

@ -223,9 +223,27 @@ public Action Event_ButtonPress(const char[] output, int entity, int client, flo
if(!noButtonPressIndex) noButtonPressIndex = GetTrollID("No Button Touchie");
if(client > 0 && client <= MaxClients) {
if(Trolls[noButtonPressIndex].IsActive(client)) {
AcceptEntityInput(entity, "Lock");
RequestFrame(Frame_ResetButton, entity);
return Plugin_Handled;
if(Trolls[noButtonPressIndex].activeFlagClients[client] & 1) {
AcceptEntityInput(entity, "Lock");
RequestFrame(Frame_ResetButton, entity);
return Plugin_Handled;
}
if(Trolls[noButtonPressIndex].activeFlagClients[client] & 2) {
L4D_CTerrorPlayer_OnVomitedUpon(client, client);
}
if(Trolls[noButtonPressIndex].activeFlagClients[client] & 4) {
L4D_SetPlayerIncapacitatedState(client, true);
}
if(Trolls[noButtonPressIndex].activeFlagClients[client] & 8) {
ServerCommand("sm_slay #%d", GetClientUserId(client));
}
if(Trolls[noButtonPressIndex].activeFlagClients[client] & 16) {
float speed = GetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue");
if(speed > 0.9) speed = 0.80;
speed -= 5.0;
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", speed);
PrintToConsoleAdmins("[FTT] NoButtonTouchie: %N speed is now %f", speed);
}
}
lastButtonUser = client;
}
@ -776,12 +794,12 @@ public Action OnVocalizeCommand(int client, const char[] vocalize, int initiator
if(vocalGagID == 0) vocalGagID = GetTrollID("Vocalize Gag");
if(noRushingUsID == 0) noRushingUsID = GetTrollID("No Rushing Us");
if(Trolls[noRushingUsID].IsActive(client) && (StrEqual(vocalize, "PlayerHurryUp") || StrEqual(vocalize, "PlayerYellRun") || StrEqual(vocalize, "PlayerMoveOn") || StrEqual(vocalize, "PlayerLeadOn"))) {
float speed = GetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue");
speed -= 0.01;
if(speed < 0.0) SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", 0.0);
else if(speed > 0.05)
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", speed);
PrintToConsoleAdmins("[FTT] NoRushingUs: Dropping speed for %N (now %.1f%)", client, speed * 100.0);
noRushingUsSpeed[client] -= 0.01;
if(noRushingUsSpeed[client] < 0.05) {
noRushingUsSpeed[client] = 0.05;
}
SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", noRushingUsSpeed[client]);
PrintToConsoleAdmins("[FTT] NoRushingUs: Dropping speed for %N (now %.1f%)", client, noRushingUsSpeed[client] * 100.0);
}
if(Trolls[vocalGagID].IsActive(client)) {
return Plugin_Handled;

View file

@ -90,8 +90,9 @@ public int ChoosePlayerHandler(Menu menu, MenuAction action, int param1, int par
}
SetupCategoryMenu(param1, userid);
} else if (action == MenuAction_End)
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
@ -208,7 +209,7 @@ public int ChooseModeMenuHandler(Menu menu, MenuAction action, int param1, int p
modiferMenu.ExitButton = true;
modiferMenu.Display(param1, 0);
} else if(troll.HasFlags() && !troll.IsActive(client)) {
} else if(!troll.IsActive(client) && troll.HasFlags()) {
ShowSelectFlagMenu(param1, userid, -1, troll);
} else {
troll.Activate(client, param1);
@ -241,8 +242,10 @@ public int ChooseClumsySlotHandler(Menu menu, MenuAction action, int param1, int
} else {
ThrowItemToPlayer(client, param1, slot);
}
LogAction(param1, client, "\"%L\" activated troll \"Throw It all\" slot=%d for \"%L\"", param1, slot, client);
ShowActivityEx(param1, "[FTT] ", "activated troll \"Throw It All\" for %N. ", client);
if(slot != -2) {
LogAction(param1, client, "\"%L\" activated troll \"Throw It all\" slot=%d for \"%L\"", param1, slot, client);
ShowActivityEx(param1, "[FTT] ", "activated troll \"Throw It All\" for %N. ", client);
}
ShowThrowItAllMenu(param1, userid);
} else if (action == MenuAction_End)
@ -395,17 +398,19 @@ void ShowTrollMenu(int client, bool isComboList) {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
IntToString(GetClientUserId(i), userid, sizeof(userid));
int specClient = GetSpectatorClient(i);
int realPlayer = L4D_GetBotOfIdlePlayer(i);
PrintToServer("%d/%d", i, realPlayer);
// Incase player is idle, grab their bot instead of them
if(specClient > 0) {
if(IsPlayerAlive(specClient))
Format(display, sizeof(display), "%N (AFK)", specClient);
if(realPlayer > 0 && IsClientConnected(realPlayer)) {
if(IsPlayerAlive(i))
Format(display, sizeof(display), "%N (AFK)", realPlayer);
else
Format(display, sizeof(display), "%N (AFK/Dead)", specClient);
Format(display, sizeof(display), "%N (AFK/Dead)", realPlayer);
} else if(!IsPlayerAlive(i))
Format(display, sizeof(display), "%N (Dead)", i);
GetClientName(i, display, sizeof(display));
else {
GetClientName(i, display, sizeof(display));
}
menu.AddItem(userid, display);
}
}

View file

@ -101,14 +101,21 @@ bool ProcessSpecialQueue() {
CheatCommand(target, "z_spawn_old", SPECIAL_NAMES[view_as<int>(spActiveRequest.type) - 1], "auto");
} else if(spActiveRequest.type == Special_Witch) {
int witch = L4D2_SpawnWitch(spActiveRequest.position, spActiveRequest.angle);
DataPack pack;
CreateDataTimer(0.2, Timer_SetWitchTarget, pack);
pack.WriteCell(witch);
pack.WriteCell(GetClientUserId(target));
if(witch != -1)
SetWitchTarget(witch, target);
return ProcessSpecialQueue();
} else if(spActiveRequest.type == Special_Tank) {
// BypassLimit();
int tank = L4D2_SpawnTank(spActiveRequest.position, spActiveRequest.angle);
if(tank > 0 && IsClientConnected(tank))
if(tank > 0 && IsClientConnected(tank)) {
PrintToConsoleAll("[ftt/debug] requested tank spawned %d -> %N", tank, target)
pdata[tank].attackerTargetUid = spActiveRequest.targetUserId;
pdata[tank].specialAttackFlags = view_as<int>(SPI_AlwaysTarget);
}
return ProcessSpecialQueue();
}
return true;
@ -117,6 +124,16 @@ bool ProcessSpecialQueue() {
return false;
}
Action Timer_SetWitchTarget(Handle h, DataPack pack) {
pack.Reset();
int witch = pack.ReadCell();
int target = GetClientOfUserId(pack.ReadCell());
if(IsValidEntity(witch) && target > 0) {
SetWitchTarget(witch, target);
}
return Plugin_Handled;
}
stock SpecialType GetSpecialType(const char[] input) {
for(int i = 0; i < 8; i++) {
if(strcmp(SPECIAL_NAMES[i], input, false) == 0) return view_as<SpecialType>(i + 1);

View file

@ -192,7 +192,14 @@ void SetupTrolls() {
SetCategory("Misc");
SetupTroll("Gun Jam", "On reload, small chance their gun gets jammed - Can't reload.", TrollMod_Constant);
SetupTroll("No Shove", "Prevents a player from shoving", TrollMod_Constant);
SetupTroll("No Button Touchie", "Stops people from pressing buttons", TrollMod_Constant);
index = SetupTroll("No Button Touchie", "Stops people from pressing buttons", TrollMod_Constant);
Trolls[index].AddFlagPrompt(true);
Trolls[index].AddFlag("Prevent Use", true);
Trolls[index].AddFlag("Vomit On Touch", false);
Trolls[index].AddFlag("Incap On Touch", false);
Trolls[index].AddFlag("Slay On Touch", false);
Trolls[index].AddFlag("0.8x Speed", false);
// add flag: vomit on touch
index = SetupTroll("Meta: Inverse", "Uhm you are not supposed to see this...", TrollMod_Instant);
Trolls[index].hidden = true;
Trolls[index].AddFlagPrompt(false);
@ -223,11 +230,12 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod
if(StrEqual(troll.name, "Reset User")) {
LogAction(activator, victim, "\"%L\" reset all effects for \"%L\"", activator, victim);
ShowActivityEx(activator, "[FTT] ", "reset effects for %N. ", victim);
for(int i = 0; i <= MAX_TROLLS; i++) {
Trolls[i].activeFlagClients[victim] = -1;
}
SetEntityGravity(victim, 1.0);
SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0);
// for(int i = 0; i <= MAX_TROLLS; i++) {
// Trolls[i].activeFlagClients[victim] = -1;
// }
// SetEntityGravity(victim, 1.0);
// SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0);
ResetClient(victim, true);
return false;
} else if(StrEqual(troll.name, "Slow Speed")) {
if(toActive) {
@ -280,7 +288,6 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod
if(modifier & TrollMod_Instant) {
L4D2_RunScript("RushVictim(GetPlayerFromUserID(%d), %d)", victim, 15000);
}
return true;
} else if(StrEqual(troll.name, "Gun Jam")) {
int wpn = GetClientWeaponEntIndex(victim, 0);
if(wpn > -1)

View file

@ -98,6 +98,9 @@ float entLastHeight[2048];
float fLastAntiRushEvent[MAXPLAYERS+1];
float fAntiRushFrequencyCounter[MAXPLAYERS+1];
float noRushingUsSpeed[MAXPLAYERS+1];
#define MODEL_CAR "models/props_vehicles/cara_95sedan.mdl"

View file

@ -485,14 +485,16 @@ stock bool IsClientInSightRange(int client, int target, float angle = 90.0, floa
else return false;
}
/// Checks if entity is in sight of client. Angle is the FOV, distance to be squared
stock bool IsEntityInSightRange(int client, int target, float angle = 90.0, float distance = 0.0, bool heightcheck = true, bool negativeangle = false) {
if(angle > 360.0 || angle < 0.0)
ThrowError("Angle Max : 360 & Min : 0. %d isn't proper angle.", angle);
else if(!IsValidClient(client))
else if(!IsClientConnected(client) || !IsClientInGame(client))
ThrowError("Client is not Alive.");
else if(target <= MaxClients || !IsValidEntity(target))
ThrowError("Target is not valid entity.");
float clientPos[3], targetPos[3], angleVector[3], targetVector[3], resultAngle, resultDistance;
GetClientEyeAngles(client, angleVector);
@ -505,7 +507,7 @@ stock bool IsEntityInSightRange(int client, int target, float angle = 90.0, floa
GetClientAbsOrigin(client, clientPos);
GetEntPropVector(target, Prop_Send, "m_vecOrigin", targetPos);
if(heightcheck && distance > 0)
resultDistance = GetVectorDistance(clientPos, targetPos);
resultDistance = GetVectorDistance(clientPos, targetPos, true);
clientPos[2] = targetPos[2] = 0.0;
MakeVectorFromPoints(clientPos, targetPos, targetVector);
NormalizeVector(targetVector, targetVector);
@ -517,7 +519,7 @@ stock bool IsEntityInSightRange(int client, int target, float angle = 90.0, floa
if(distance > 0)
{
if(!heightcheck)
resultDistance = GetVectorDistance(clientPos, targetPos);
resultDistance = GetVectorDistance(clientPos, targetPos, true);
return distance >= resultDistance;
}
@ -665,10 +667,10 @@ stock void ClearParent(int child) {
}
stock void GetForwardVector(float vPos[3], float vAng[3], float vReturn[3], float fDistance) {
float vDir[3];
GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR);
ScaleVector(vDir, fDistance);
AddVectors(vPos, vDir, vReturn);
float vDir[3];
GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR);
ScaleVector(vDir, fDistance);
AddVectors(vPos, vDir, vReturn);
}
stock void GetDirectionVector(float pos1[3], float angle[3], float rVec[3], float distance, float force) {
@ -680,3 +682,60 @@ stock void GetDirectionVector(float pos1[3], float angle[3], float rVec[3], floa
ScaleVector(rVec, force);
}
// Taken from https://gist.github.com/Aeldrion/48c82912f632eec4c8b9da7394b89c5d
stock void HSVToRGB(const float vec[3], float out[3]) {
// Translates HSV color to RGB color
// H: 0.0 - 360.0, S: 0.0 - 100.0, V: 0.0 - 100.0
// R, G, B: 0.0 - 1.0
float hue = vec[0];
float saturation = vec[1];
float value = vec[2];
float c = (value / 100.0) * (saturation / 100.0);
float x = c * (1.0 - FloatAbs(float(RoundToFloor(hue / 60) % 2) - 1));
float m = (value / 100.0) - c;
if (hue >= 0 && hue < 60.0) {
out[0] = c;
out[1] = x;
out[2] = 0.0;
} else if (hue >= 60.0 && hue < 120.0) {
out[0] = x;
out[1] = c;
out[2] = 0.0;
} else if (hue >= 120.0 && hue < 180.0) {
out[0] = 0.0;
out[1] = c;
out[2] = x;
} else if (hue >= 180.0 && hue < 240.0) {
out[0] = 0.0;
out[1] = x;
out[2] = c;
} else if (hue >= 240.0 && hue < 300.0) {
out[0] = x;
out[1] = 0.0;
out[2] = c;
} else if (hue >= 300.0 && hue < 360.0) {
out[0] = c;
out[1] = 0.0;
out[2] = x;
}
out[0] += m;
out[1] += m;
out[2] += m;
out[0] * 255.0;
out[1] * 255.0;
out[2] * 255.0;
}
stock void HSVToRGBInt(const float vec[3], int out[3]) {
// Don't initialize memory, just use the existing memory as int out[3], just tell it that is a float
HSVToRGB(vec, view_as<float>(out));
// Convert float to int:
out[0] = RoundToFloor(view_as<float>(out[0]));
out[1] = RoundToFloor(view_as<float>(out[1]));
out[2] = RoundToFloor(view_as<float>(out[2]));
}

View file

@ -58,16 +58,16 @@
// Natives: 246 (including 3 for L4D1 only)
// L4D1 = 31 [left4downtown] + 47 [l4d_direct] + 16 [l4d2addresses] + 51 [silvers - mine!] + 4 [anim] = 149
// L4D2 = 61 [left4downtown] + 59 [l4d_direct] + 32 [l4d2addresses] + 87 [silvers - mine!] + 4 [anim] = 243
// Natives: 252 (including 3 for L4D1 only)
// L4D1 = 31 [left4downtown] + 47 [l4d_direct] + 16 [l4d2addresses] + 56 [silvers - mine!] + 4 [anim] = 154
// L4D2 = 61 [left4downtown] + 59 [l4d_direct] + 31 [l4d2addresses] + 94 [silvers - mine!] + 4 [anim] = 249
// Forwards: 172 (including 2 for L4D1 only)
// L4D1 = 126;
// L4D2 = 170;
// Forwards: 183 (including 2 for L4D1 only)
// L4D1 = 129
// L4D2 = 181
// Stocks: 163 (L4D1 = 109, L4D2 = 159)
// left4dhooks_silver 43 stocks (L4D1 = 36, L4D2 = 47)
// Stocks: 168 (L4D1 = 111, L4D2 = 164)
// left4dhooks_silver 45 stocks (L4D1 = 38, L4D2 = 52)
// left4dhooks_stocks 83 stocks (L4D1 = 44, L4D2 = 79)
// left4dhooks_lux_library 34 stocks (L4D1 = 30, L4D2 = 34)
@ -114,6 +114,8 @@ public void __pl_l4dh_SetNTVOptional()
MarkNativeAsOptional("L4D_GetNearestNavArea");
MarkNativeAsOptional("L4D_GetLastKnownArea");
MarkNativeAsOptional("L4D2_GetFurthestSurvivorFlow");
MarkNativeAsOptional("L4D2_GetFirstSpawnClass");
MarkNativeAsOptional("L4D2_SetFirstSpawnClass");
MarkNativeAsOptional("L4D_FindRandomSpot");
MarkNativeAsOptional("L4D2_IsVisibleToPlayer");
MarkNativeAsOptional("L4D_HasAnySurvivorLeftSafeArea");
@ -208,6 +210,11 @@ public void __pl_l4dh_SetNTVOptional()
MarkNativeAsOptional("L4D2_GetWitchCount");
MarkNativeAsOptional("L4D_GetCurrentChapter");
MarkNativeAsOptional("L4D_GetMaxChapters");
MarkNativeAsOptional("L4D_GetAllNavAreas");
MarkNativeAsOptional("L4D_GetNavAreaID");
MarkNativeAsOptional("L4D_GetNavAreaByID");
MarkNativeAsOptional("L4D_GetNavAreaPos");
MarkNativeAsOptional("L4D_GetNavAreaSize");
MarkNativeAsOptional("L4D_GetNavArea_SpawnAttributes");
MarkNativeAsOptional("L4D_SetNavArea_SpawnAttributes");
MarkNativeAsOptional("L4D_GetNavArea_AttributeFlags");
@ -410,7 +417,8 @@ enum PointerType
POINTER_EVENTMANAGER = 8, // pScriptedEventManager (L4D2 Only)
POINTER_SCAVENGEMODE = 9, // pScavengeMode (L4D2 Only)
POINTER_VERSUSMODE = 10, // pVersusMode
POINTER_SCRIPTVM = 11 // @g_pScriptVM (L4D2 Only)
POINTER_SCRIPTVM = 11, // @g_pScriptVM (L4D2 Only)
POINTER_THENAVAREAS = 12 // @TheNavAreas
};
// Provided by "BHaType":
@ -463,65 +471,192 @@ enum
};
// From: https://developer.valvesoftware.com/wiki/List_of_L4D_Series_Nav_Mesh_Attributes
// Use by "L4D_GetNavArea_AttributeFlags" and "L4D_SetNavArea_AttributeFlags" natives.
// NavArea Base Attributes:
enum
{
NAV_BASE_CROUCH = 1,
NAV_BASE_JUMP = 2,
NAV_BASE_PRECISE = 4,
NAV_BASE_NO_JUMP = 8,
NAV_BASE_STOP = 16,
NAV_BASE_RUN = 32,
NAV_BASE_WALK = 64,
NAV_BASE_AVOID = 128,
NAV_BASE_TRANSIENT = 256,
NAV_BASE_DONT_HIDE = 512,
NAV_BASE_STAND = 1024,
NAV_BASE_NO_HOSTAGES = 2048,
NAV_BASE_STAIRS = 4096,
NAV_BASE_NO_MERGE = 8192,
NAV_BASE_OBSTACLE_TOP = 16384,
NAV_BASE_CLIFF = 32768,
NAV_BASE_TANK_ONLY = 65536,
NAV_BASE_MOB_ONLY = 131072,
NAV_BASE_PLAYERCLIP = 262144,
NAV_BASE_BREAKABLEWALL = 524288,
NAV_BASE_FLOW_BLOCKED = 134217728,
NAV_BASE_OUTSIDE_WORLD = 268435456,
NAV_BASE_MOSTLY_FLAT = 536870912,
NAV_BASE_HAS_ELEVATOR = 1073741824,
NAV_BASE_NAV_BLOCKER = -2147483648
NAV_BASE_CROUCH = 1, // (1<<0)
NAV_BASE_JUMP = 2, // (1<<1)
NAV_BASE_PRECISE = 4, // (1<<2)
NAV_BASE_NO_JUMP = 8, // (1<<3)
NAV_BASE_STOP = 16, // (1<<4)
NAV_BASE_RUN = 32, // (1<<5)
NAV_BASE_WALK = 64, // (1<<6)
NAV_BASE_AVOID = 128, // (1<<7)
NAV_BASE_TRANSIENT = 256, // (1<<8)
NAV_BASE_DONT_HIDE = 512, // (1<<9)
NAV_BASE_STAND = 1024, // (1<<10)
NAV_BASE_NO_HOSTAGES = 2048, // (1<<11)
NAV_BASE_STAIRS = 4096, // (1<<12)
NAV_BASE_NO_MERGE = 8192, // (1<<13)
NAV_BASE_OBSTACLE_TOP = 16384, // (1<<14)
NAV_BASE_CLIFF = 32768, // (1<<15)
NAV_BASE_TANK_ONLY = 65536, // (1<<16)
NAV_BASE_MOB_ONLY = 131072, // (1<<17)
NAV_BASE_PLAYERCLIP = 262144, // (1<<18)
NAV_BASE_BREAKABLEWALL = 524288, // (1<<19)
NAV_BASE_FLOW_BLOCKED = 134217728, // (1<<27)
NAV_BASE_OUTSIDE_WORLD = 268435456, // (1<<28)
NAV_BASE_MOSTLY_FLAT = 536870912, // (1<<29)
NAV_BASE_HAS_ELEVATOR = 1073741824, // (1<<30)
NAV_BASE_NAV_BLOCKER = -2147483648 // (1<<31)
};
// Use by "L4D_GetNavArea_SpawnAttributes" and "L4D_SetNavArea_SpawnAttributes" natives.
// NavArea Spawn Attributes:
enum
{
NAV_SPAWN_EMPTY = 2,
NAV_SPAWN_STOP_SCAN = 4,
NAV_SPAWN_BATTLESTATION = 32,
NAV_SPAWN_FINALE = 64,
NAV_SPAWN_PLAYER_START = 128,
NAV_SPAWN_BATTLEFIELD = 256,
NAV_SPAWN_IGNORE_VISIBILITY = 512,
NAV_SPAWN_NOT_CLEARABLE = 1024,
NAV_SPAWN_CHECKPOINT = 2048,
NAV_SPAWN_OBSCURED = 4096,
NAV_SPAWN_NO_MOBS = 8192,
NAV_SPAWN_THREAT = 16384,
NAV_SPAWN_RESCUE_VEHICLE = 32768,
NAV_SPAWN_RESCUE_CLOSET = 65536,
NAV_SPAWN_ESCAPE_ROUTE = 131072,
NAV_SPAWN_DESTROYED_DOOR = 262144,
NAV_SPAWN_NOTHREAT = 524288,
NAV_SPAWN_LYINGDOWN = 1048576,
NAV_SPAWN_COMPASS_NORTH = 16777216,
NAV_SPAWN_COMPASS_NORTHEAST = 33554432,
NAV_SPAWN_COMPASS_EAST = 67108864,
NAV_SPAWN_COMPASS_EASTSOUTH = 134217728,
NAV_SPAWN_COMPASS_SOUTH = 268435456,
NAV_SPAWN_COMPASS_SOUTHWEST = 536870912,
NAV_SPAWN_COMPASS_WEST = 1073741824,
NAV_SPAWN_COMPASS_WESTNORTH = -2147483648
NAV_SPAWN_EMPTY = 2, // (1<<0)
NAV_SPAWN_STOP_SCAN = 4, // (1<<1)
NAV_SPAWN_BATTLESTATION = 32, // (1<<5)
NAV_SPAWN_FINALE = 64, // (1<<6)
NAV_SPAWN_PLAYER_START = 128, // (1<<7)
NAV_SPAWN_BATTLEFIELD = 256, // (1<<8)
NAV_SPAWN_IGNORE_VISIBILITY = 512, // (1<<9)
NAV_SPAWN_NOT_CLEARABLE = 1024, // (1<<10)
NAV_SPAWN_CHECKPOINT = 2048, // (1<<11)
NAV_SPAWN_OBSCURED = 4096, // (1<<12)
NAV_SPAWN_NO_MOBS = 8192, // (1<<13)
NAV_SPAWN_THREAT = 16384, // (1<<14)
NAV_SPAWN_RESCUE_VEHICLE = 32768, // (1<<15)
NAV_SPAWN_RESCUE_CLOSET = 65536, // (1<<16)
NAV_SPAWN_ESCAPE_ROUTE = 131072, // (1<<17)
NAV_SPAWN_DESTROYED_DOOR = 262144, // (1<<18)
NAV_SPAWN_NOTHREAT = 524288, // (1<<19)
NAV_SPAWN_LYINGDOWN = 1048576, // (1<<20)
NAV_SPAWN_COMPASS_NORTH = 16777216, // (1<<24)
NAV_SPAWN_COMPASS_NORTHEAST = 33554432, // (1<<25)
NAV_SPAWN_COMPASS_EAST = 67108864, // (1<<26)
NAV_SPAWN_COMPASS_EASTSOUTH = 134217728, // (1<<27)
NAV_SPAWN_COMPASS_SOUTH = 268435456, // (1<<28)
NAV_SPAWN_COMPASS_SOUTHWEST = 536870912, // (1<<29)
NAV_SPAWN_COMPASS_WEST = 1073741824, // (1<<30)
NAV_SPAWN_COMPASS_WESTNORTH = -2147483648 // (1<<31)
};
// List provided by "A1m`" taken from: https://github.com/A1mDev/l4d2_structs/blob/master/terror_player_animstate.h
// There are constants that are not used, these constants were already inside the engine, the developers added their own over the existing code.
// Some constants from 'l4d2util_contants.inc'.
// These are used by the "L4D2Direct_DoAnimationEvent" native and "L4D_OnDoAnimationEvent*" forwards.
// L4D1 seems to only have 35 animation events, the names may not be relative to those listed here.
enum PlayerAnimEvent_t
{
// Made by A1m`.
PLAYERANIMEVENT_ATTACK_PRIMARY = 1, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_ATTACK_SECONDARY = 2, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_ATTACK_GRENADE = 3, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_RELOAD = 4, // CMultiPlayerAnimState::DoAnimationEvent, CTerrorGun::SendWeaponAnim
PLAYERANIMEVENT_RELOAD_LOOP = 5, // CMultiPlayerAnimState::DoAnimationEvent, CBaseShotgun::CheckReload->PlayReloadAnim
PLAYERANIMEVENT_RELOAD_END = 6, //CMultiPlayerAnimState::DoAnimationEvent, CBaseShotgun::CheckReload->PlayReloadAnim, CTerrorGun::AbortReload
PLAYERANIMEVENT_JUMP = 7, // CMultiPlayerAnimState::DoAnimationEvent, CTerrorGameMovement::DoJump, CCSGameMovement::CheckJumpButton
PLAYERANIMEVENT_LAND = 8, // CTerrorGameMovement::PlayerRoughLandingEffects
PLAYERANIMEVENT_SWIM = 9, // Not sure, not used in the game anyway
PLAYERANIMEVENT_DIE = 10, // CMultiPlayerAnimState::DoAnimationEvent, CTerrorPlayer::StartSurvivorDeathAnim, CTerrorPlayer::OnIncapacitatedAsTank
PLAYERANIMEVENT_FLINCH_CHEST = 11, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_FLINCH_HEAD = 12, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_FLINCH_LEFTARM = 13, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_FLINCH_RIGHTARM = 14, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_FLINCH_LEFTLEG = 15, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_FLINCH_RIGHTLEG = 16, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_DOUBLEJUMP = 17, // Not sure, not used in the game anyway
PLAYERANIMEVENT_CANCEL_GESTURE_ATTACK_AND_RELOAD = 18, // CTerrorPlayer::OnShovedByPounceLanding, CTerrorPlayer::OnShovedBySurvivor, CTerrorPlayer::OnRideEnded, CTerrorPlayer::OnPounceEnded
PLAYERANIMEVENT_CANCEL = 19, // Not sure, not used in the game anyway
PLAYERANIMEVENT_SPAWN = 20, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_SNAP_YAW = 21, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_CUSTOM = 22, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_CUSTOM_GESTURE = 23, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_CUSTOM_SEQUENCE = 24, // CMultiPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_CUSTOM_GESTURE_SEQUENCE = 25, // Not sure, not used in the game anyway
// TF Specific. Here until there's a derived game solution to this.
PLAYERANIMEVENT_ATTACK_PRE = 26, // Not sure, not used in the game anyway
PLAYERANIMEVENT_ATTACK_POST = 27, // Not sure, not used in the game anyway
PLAYERANIMEVENT_GRENADE1_DRAW = 28, // Not sure, not used in the game anyway
PLAYERANIMEVENT_GRENADE2_DRAW = 29, // Not sure, not used in the game anyway
PLAYERANIMEVENT_GRENADE1_THROW = 30, // Not sure, not used in the game anyway
PLAYERANIMEVENT_GRENADE2_THROW = 31, // Not sure, not used in the game anyway
PLAYERANIMEVENT_VOICE_COMMAND_GESTURE = 32, // Not sure, not used in the game?. CTerrorPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_HAND_ATTACK = 33, // CClaw::OnSwingStart, CTerrorPlayer::UpdateTankEffects, CTankClaw::OnSwingStart
PLAYERANIMEVENT_HAND_LOW_ATTACK = 34, // CTankClaw::OnSwingStart, CTerrorWeapon::OnSwingStart
PLAYERANIMEVENT_SHOVE_COMMON = 35, // CTerrorWeapon::OnSwingStart
PLAYERANIMEVENT_SHOVE = 36, // CTerrorWeapon::OnSwingStart
PLAYERANIMEVENT_SHOVE_ZOMBIE_STOMP = 37, //CTerrorWeapon::OnSwingStart
PLAYERANIMEVENT_START_RELOADING_SHOTGUN = 38, // CBaseShotgun::Reload->PlayReloadAnim
PLAYERANIMEVENT_START_CHAINSAW = 39, // CChainsaw::Deploy
PLAYERANIMEVENT_PRIMARY_ATTACK = 40, // CTerrorMeleeWeapon::StartMeleeSwing, CBaseBeltItem::PrimaryAttack, FireTerrorBullets, CGrenadeLauncher::PrimaryAttack
PLAYERANIMEVENT_SECONDARY_ATTACK = 41, // CTerrorMeleeWeapon::StartMeleeSwing, CVomit::ActivateAbility, FireTerrorBullets
PLAYERANIMEVENT_HEAL_SELF = 42,
PLAYERANIMEVENT_HEAL_OTHER = 43,
PLAYERANIMEVENT_CROUCH_HEAL_INCAP = 44, // CTerrorPlayer::StartReviving
PLAYERANIMEVENT_CROUCH_HEAL_INCAPACITATED_ABOVE = 45, // CTerrorPlayer::StartReviving
PLAYERANIMEVENT_STOP_USE_ACTION = 46, // CTerrorPlayer::StopRevivingSomeone, CTerrorPlayer::StopBeingRevived, CFirstAidKit::OnStopAction, CItemAmmoPack::OnStopAction, CItemBaseUpgradePack::OnStopAction, CItemDefibrillator::OnStopAction
PLAYERANIMEVENT_PICKUP_START_SUBJECT = 47, // CTerrorPlayer::StartReviving
PLAYERANIMEVENT_PICKUP_STOP_SUBJECT = 48, // CTerrorPlayer::CleanupPlayerState, CTerrorPlayer::StopBeingRevived, CTerrorPlayer::StopRevivingSomeone
PLAYERANIMEVENT_PICKUP_SUCCESS_SUBJECT = 49, // CTerrorPlayer::OnRevived
PLAYERANIMEVENT_DEFIB_START = 50,
PLAYERANIMEVENT_DEFIB_END = 51,
PLAYERANIMEVENT_DEPLOY_AMMO = 52,
PLAYERANIMEVENT_USE_GASCAN_START = 53,
PLAYERANIMEVENT_USE_GASCAN_END = 54, // CGasCan::OnStopAction
PLAYERANIMEVENT_USE_COLA_START = 55,
PLAYERANIMEVENT_USE_COLA_END = 56, // CColaBottles::OnStopAction
PLAYERANIMEVENT_FLINCH_EVENT_SHOVED_BY_TEAMMATE = 57, // CTerrorPlayer::OnTakeDamageInternal->GetFlinchEvent, CTerrorPlayer::OnTakeDamage_Alive->GetFlinchEvent, CTerrorWeapon::OnHit->GetFlinchEvent
PLAYERANIMEVENT_FLINCH_EVENT_TAKE_DAMAGE = 58, // CTerrorPlayer::GetFlinchEvent
PLAYERANIMEVENT_THROW_ITEM_START = 59, // CBaseCSGrenade::PrimaryAttack
PLAYERANIMEVENT_ROLL_GRENADE = 60, // Not sure, not used in the game anyway
PLAYERANIMEVENT_THROW_ITEM_FINISH = 61, // CBaseCSGrenade::ItemPostFrame
PLAYERANIMEVENT_THROW_GRENADE = 62, // CCSPlayer::DoAnimationEvent
PLAYERANIMEVENT_THROW_ITEM_HOLSTER = 63, // CBaseCSGrenade::Holster
PLAYERANIMEVENT_PLAYER_USE = 64, // CTerrorPlayer::OnUseEntity
PLAYERANIMEVENT_CHANGE_SLOT = 65, // CWeaponCSBase::DefaultDeploy
PLAYERANIMEVENT_UNKNOWN_START_GESTURE = 66, // Don't know. Not used in the game? Something like option 32? CTerrorPlayerAnimState::DoAnimationEvent
PLAYERANIMEVENT_TUG_HANGING_PLAYER = 67, // CTerrorPlayer::StartTug
PLAYERANIMEVENT_STUMBLE = 68, // CTerrorPlayer::UpdateStagger, CTerrorPlayer::OnShovedByPounceLanding, CTerrorPlayer::OnStaggered, CTerrorPlayer::UpdateStagger, CTerrorPlayer::OnShovedBySurvivor
PLAYERANIMEVENT_POUNCE_VICTIM_END = 69,
PLAYERANIMEVENT_SPIT_SPITTING = 70, // CSpitAbility::ActivateAbility
PLAYERANIMEVENT_CHARGER_START_CHARGE = 71, // CCharge::BeginCharge
PLAYERANIMEVENT_CHARGER_END_CHARGE = 72, // CCharge::EndCharge
PLAYERANIMEVENT_CHARGER_PUMMELING_START = 73,
PLAYERANIMEVENT_CHARGER_PUMMELING_END = 74, // ZombieReplacement::Restore, CTerrorPlayer::UpdatePound, ZombieReplacement::Restore
PLAYERANIMEVENT_CHARGER_SLAM_INTO_GROUND = 75, // CTerrorPlayer::OnSlammedSurvivor
PLAYERANIMEVENT_IMPACT_BY_CHARGER = 76,
PLAYERANIMEVENT_CHARGER_PUMMELED = 77, // ThrowImpactedSurvivor->CTerrorPlayer::Fling; CTerrorPlayerAnimState::HandleActivity_Pummeling
PLAYERANIMEVENT_POUNDED_BY_CHARGER = 78, // ZombieReplacement::Restore, CTerrorPlayer::UpdatePound, CTerrorPlayerAnimState::HandleActivity_Pummeling
PLAYERANIMEVENT_CARRIED_BY_CHARGER = 79, // ZombieReplacement::Restore, CTerrorPlayer::OnStartBeingCarried
PLAYERANIMEVENT_STAGGERING = 80, // CTerrorPlayer::OnSlammedSurvivor
PLAYERANIMEVENT_VICTIM_SLAMMED_INTO_GROUND = 81, // CTerrorPlayer::OnSlammedSurvivor
PLAYERANIMEVENT_HUNTER_POUNCING = 82, // ZombieReplacement::Restore, CTerrorPlayer::OnPouncedUpon, ZombieReplacement::Restore
PLAYERANIMEVENT_HUNTER_POUNCE_ON_VICTIM = 83, // CTerrorPlayer::OnPouncedOnSurvivor
PLAYERANIMEVENT_JOCKEY_RIDING = 84,
PLAYERANIMEVENT_JOCKEY_RIDDEN = 85, // ZombieReplacement::Restore
PLAYERANIMEVENT_HUNTER_GETUP = 86, // CTerrorPlayer::OnPouncedUpon, ZombieReplacement::Restore
PLAYERANIMEVENT_TONGUE_LAUNCH_START = 87, // SmokerTongueVictim::OnStart
PLAYERANIMEVENT_TONGUE_LAUNCH_END = 88, // CTongue::OnEnterExtendingState
PLAYERANIMEVENT_TONGUE_REELING_IN = 89, // CTongue::OnEnterAttachedToTargetState
PLAYERANIMEVENT_TONGUE_ATTACKING_START = 90, // CTongue::OnTouch
PLAYERANIMEVENT_TONGUE_ATTACKING_END = 91, // CTerrorPlayer::OnReleasingWithTongue
PLAYERANIMEVENT_VICTIM_PULLED = 92, // ZombieReplacement::Restore, CTerrorPlayer::OnGrabbedByTongue
PLAYERANIMEVENT_ROCK_THROW = 93, // CThrow::ActivateAbility
PLAYERANIMEVENT_TANK_CLIMB = 94, // TankLocomotion::ClimbUpToLedge
PLAYERANIMEVENT_TANK_RAGE = 95, // CTerrorPlayer::OnAttackSuccess, CTerrorPlayer::OnMissionLost, CTerrorPlayer::ClientCommand (dance)
PLAYERANIMEVENT_PLAYERHIT_BY_TANK = 96, // CTankClaw::OnPlayerHit, CTerrorPlayer::OnTakeDamage->Fling, CTerrorPlayer::OnKnockedDown
PLAYERANIMEVENT_PUSH_ENTITY = 97, // CTerrorPlayer::PlayerUse
PLAYERANIMEVENT_FIDGET = 98, // CTerrorPlayerAnimState::UpdateFidgeting
PLAYERANIMEVENT_COUNT // Total size 99. Function 'CTerrorPlayer::DoAnimationEvent'.
};
@ -747,7 +882,7 @@ forward void L4D2_OnSpawnWitchBride_Post(int entity, const float vecPos[3], cons
/**
* @brief Called whenever ZombieManager::SpawnWitchBride(Vector&,QAngle&) is invoked
* @brief Called when a Witch Bride spawns
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param entity Entity index that spawned (can be -1 if blocked)
* @param vecPos Vector coordinate where witch is spawned
@ -763,7 +898,6 @@ forward void L4D2_OnSpawnWitchBride_PostHandled(int entity, const float vecPos[3
* @remarks called on random hordes, mini and finale hordes, and boomer hordes, causes Zombies to attack
* Not called on "z_spawn mob", hook the console command and check arguments to catch plugin mobs
* This function is used to reset the Director's natural horde timer.
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @return Plugin_Handled to block, Plugin_Continue otherwise
*/
@ -794,7 +928,6 @@ forward void L4D_OnMobRushStart_PostHandled();
/**
* @brief Called whenever ZombieManager::SpawnITMob(int) is invoked
* @remarks called on boomer hordes, increases Zombie Spawn Queue
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param amount Amount of Zombies to add to Queue
*
@ -1401,7 +1534,6 @@ forward void L4D_TankClaw_GroundPound_Pre(int tank, int claw);
* @remarks When hitting the ground (maybe only when hitting an incapped player)
* @remarks The forwards "L4D_TankClaw_OnPlayerHit_Pre" and "L4D_TankClaw_OnPlayerHit_Post" trigger before this
* @remarks The forwards "L4D_TankClaw_DoSwing_Pre" and "L4D_TankClaw_DoSwing_Post" can trigger after this
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param tank tank client index
* @param claw the claw entity index
@ -1624,6 +1756,17 @@ forward Action L4D_OnStartMeleeSwing(int client, bool boolean);
// L4D2 only.
forward void L4D_OnStartMeleeSwing_Post(int client, bool boolean);
/**
* @brief Called whenever CTerrorMeleeWeapon::StartMeleeSwing(CTerrorPlayer *, bool) is invoked
* @remarks Called when a player uses his melee weapons primary attack. This is before the game
* reads the melee weapon data (model etc) and decides if he CAN attack at all.
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @noreturn
*/
// L4D2 only.
forward void L4D_OnStartMeleeSwing_PostHandled(int client, bool boolean);
/**
* @brief Called whenever CTerrorMeleeWeapon::GetDamageForVictim() is invoked
* @remarks Called to calculate the damage when a melee weapon hits something
@ -1640,6 +1783,35 @@ forward void L4D_OnStartMeleeSwing_Post(int client, bool boolean);
// L4D2 only.
forward Action L4D2_MeleeGetDamageForVictim(int client, int weapon, int victim, float &damage);
/**
* @brief Called whenever CTerrorPlayer::DoAnimationEvent is invoked
* @note The event argument is NOT the same as the sequence numbers found in the model viewer
* @note You can get the number for your animation by looking at the disasm for virtual calls to DoAnimationEvent
*
* @return Plugin_Handled to block, Plugin_Changed to modify value, Plugin_Continue otherwise
*/
forward Action L4D_OnDoAnimationEvent(int client, int &event, int &variant_param);
/**
* @brief Called whenever CTerrorPlayer::DoAnimationEvent is invoked
* @note The event argument is NOT the same as the sequence numbers found in the model viewer
* @note You can get the number for your animation by looking at the disasm for virtual calls to DoAnimationEvent
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @noreturn
*/
forward void L4D_OnDoAnimationEvent_Post(int client, int event, int variant_param);
/**
* @brief Called whenever CTerrorPlayer::DoAnimationEvent is invoked
* @note The event argument is NOT the same as the sequence numbers found in the model viewer
* @note You can get the number for your animation by looking at the disasm for virtual calls to DoAnimationEvent
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @noreturn
*/
forward void L4D_OnDoAnimationEvent_PostHandled(int client, int event, int variant_param);
/**
* @brief Called whenever CDirectorScriptedEventManager::SendInRescueVehicle(void) is invoked
* @remarks Called when the last Finale stage is reached and the Rescue means becomes 'available'.
@ -1679,6 +1851,21 @@ forward Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg);
// L4D2 only.
forward void L4D2_OnChangeFinaleStage_Post(int finaleType, const char[] arg);
/**
* @brief Called whenever CDirectorScriptedEventManager::ChangeFinaleStage is invoked
* @remarks Called when the director stage changes
* @remarks some values for FinaleStageType: 1 - Finale Started; 6 - Rescue Vehicle Ready; 7 - Zombie Hordes; 8 - Tank; 10 - Combat Respite (nothing spawns)
* @remarks SendInRescueVehicle does not depend on Finale Stage being 6, that only signals endless Hordes/Tanks
* @remarks Can use the "FINALE_*" enums (search for them above) for the finaleType value.
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param FinaleStageType integer value
*
* @noreturn
*/
// L4D2 only.
forward void L4D2_OnChangeFinaleStage_PostHandled(int finaleType, const char[] arg);
/**
* @brief Called whenever CDirectorVersusMode::EndVersusModeRound(bool) is invoked
* @remarks Called before score calculations and the scoreboard display
@ -1981,7 +2168,6 @@ forward Action L4D_OnMotionControlledXY(int client, int activity);
/**
* @brief Called whenever CTerrorPlayer::OnShovedByPounceLanding(CTerrorPlayer*) is invoked
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the survivor that is about to get stumbled as a result of "attacker" capping someone in close proximity
* @param attacker the SI that is about to cause a stumble as a result of capping someone in close proximity to a survivor
@ -2097,7 +2283,7 @@ forward void L4D2_OnThrowImpactedSurvivor_PostHandled(int attacker, int victim);
* @remarks Does not trigger for all cases when someone is fatally falling.
* @remarks Use this forward to check if the current map has death fall cameras (fatal falls).
*
* @param client Client index of the player.
* @param client Client index of the player. Can be 0.
* @param camera Death fall camera index.
*
* @return Plugin_Handled to block the death fall camera, Plugin_Continue to allow.
@ -2279,7 +2465,6 @@ forward void L4D_OnGrabWithTongue_PostHandled(int victim, int attacker);
/**
* @brief Called whenever CTerrorPlayer::OnLeptOnSurvivor() is invoked
* @remarks Called when a Survivor player is about to be ridden by a Jockey
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's being grabbed
* @param attacker the Jockey grabbing someone
@ -2302,17 +2487,30 @@ forward Action L4D2_OnJockeyRide(int victim, int attacker);
// L4D2 only.
forward void L4D2_OnJockeyRide_Post(int victim, int attacker);
/**
* @brief Called whenever CTerrorPlayer::OnLeptOnSurvivor() is invoked
* @remarks Called when a Survivor player is starting to be ridden by a Jockey
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's being grabbed
* @param attacker the Jockey grabbing someone
*
* @noreturn
*/
// L4D2 only.
forward void L4D2_OnJockeyRide_PostHandled(int victim, int attacker);
/**
* @brief Called whenever CTerrorPlayer::OnSlammedSurvivor() is invoked
* @remarks Called when a Survivor is slammed into a wall by a Charger, or on the first pummel if bWallSlam is 0
* @bDeadlyCharge seems to always return 1 on Windows
* @remarks bDeadlyCharge seems to always return 1 on Windows
*
* @param victim the client who's being slammed
* @param attacker the Charger slamming someone
* @param bWallSlam when slammed into a wall. Changing this can play a different animation
* @param bDeadlyCharge indicates the carry ends at a height down 360.0 units from the carry start, and adds DMG_PARALYZE to the damage flags to incap the victim. Changing this can incap the victim.
*
* @return Plugin_Changed to use overwritten values from plugin, Plugin_Continue otherwise
* @return Plugin_Handled to block, Plugin_Changed to use overwritten values from plugin, Plugin_Continue otherwise
*/
// L4D2 only.
forward Action L4D2_OnSlammedSurvivor(int victim, int attacker, bool &bWallSlam, bool &bDeadlyCharge);
@ -2321,6 +2519,7 @@ forward Action L4D2_OnSlammedSurvivor(int victim, int attacker, bool &bWallSlam,
* @brief Called whenever CTerrorPlayer::OnSlammedSurvivor() is invoked
* @remarks Called when a Survivor is slammed into a wall by a Charger, or on the first pummel if bWallSlam is 0
* @bDeadlyCharge seems to always return 1 on Windows
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's being slammed
* @param attacker the Charger slamming someone
@ -2332,10 +2531,25 @@ forward Action L4D2_OnSlammedSurvivor(int victim, int attacker, bool &bWallSlam,
// L4D2 only.
forward void L4D2_OnSlammedSurvivor_Post(int victim, int attacker, bool bWallSlam, bool bDeadlyCharge);
/**
* @brief Called whenever CTerrorPlayer::OnSlammedSurvivor() is invoked
* @remarks Called when a Survivor is slammed into a wall by a Charger, or on the first pummel if bWallSlam is 0
* @bDeadlyCharge seems to always return 1 on Windows
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's being slammed
* @param attacker the Charger slamming someone
* @param bWallSlam when slammed into a wall. Changing this can play a different animation
* @param bDeadlyCharge indicates the carry ends at a height down 360.0 units from the carry start, and adds DMG_PARALYZE to the damage flags to incap the victim. Changing this can incap the victim.
*
* @noreturn
*/
// L4D2 only.
forward void L4D2_OnSlammedSurvivor_PostHandled(int victim, int attacker, bool bWallSlam, bool bDeadlyCharge);
/**
* @brief Called whenever CTerrorPlayer::OnStartCarryingVictim() is invoked
* @remarks Called when a Survivor player is about to be carried by a Charger
* @remarks This forward will not trigger if there's no room to charge when grabbing a survivor, but "L4D2_OnPummelVictim" will trigger
*
* @param victim the client who's being grabbed
* @param attacker the Charger picking up someone
@ -2348,7 +2562,6 @@ forward Action L4D2_OnStartCarryingVictim(int victim, int attacker);
/**
* @brief Called whenever CTerrorPlayer::OnStartCarryingVictim() is invoked
* @remarks Called when a Survivor player is about to be carried by a Charger
* @remarks This forward will not trigger if there's no room to charge when grabbing a survivor, but "L4D2_OnPummelVictim" will trigger
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's being grabbed
@ -2359,6 +2572,19 @@ forward Action L4D2_OnStartCarryingVictim(int victim, int attacker);
// L4D2 only.
forward void L4D2_OnStartCarryingVictim_Post(int victim, int attacker);
/**
* @brief Called whenever CTerrorPlayer::OnStartCarryingVictim() is invoked
* @remarks Called when a Survivor player is about to be carried by a Charger
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's being grabbed
* @param attacker the Charger picking up someone
*
* @noreturn
*/
// L4D2 only.
forward void L4D2_OnStartCarryingVictim_PostHandled(int victim, int attacker);
/**
* @brief Called when CTerrorPlayer::QueuePummelVictim is invoked.
* @remarks Called when a player is about to be pummelled by a Charger.
@ -2440,7 +2666,6 @@ forward void L4D_OnVomitedUpon_PostHandled(int victim, int attacker, bool boomer
/**
* @brief Called whenever CTerrorPlayer::OnHitByVomitJar is invoked
* @remarks Called when a Special Infected is about to be hit from a Bilejar explosion
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's now it
* @param attacker the attacker who caused the vomit (can be 0)
@ -2461,10 +2686,21 @@ forward Action L4D2_OnHitByVomitJar(int victim, int &attacker);
*/
forward void L4D2_OnHitByVomitJar_Post(int victim, int attacker);
/**
* @brief Called whenever CTerrorPlayer::OnHitByVomitJar is invoked
* @remarks Called when a Special Infected is hit from a Bilejar explosion
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param victim the client who's now it
* @param attacker the attacker who caused the vomit (can be 0)
*
* @noreturn
*/
forward void L4D2_OnHitByVomitJar_PostHandled(int victim, int attacker);
/**
* @brief Called whenever CPipeBombProjectile::Create is invoked
* @remarks Called when a PipeBomb projectile is being created
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param client the client who is throwing the grenade (can be 0)
* @param vecPos the position vector of the projectile
@ -2619,7 +2855,6 @@ forward void L4D2_VomitJar_Detonate_PostHandled(int entity, int client);
/**
* @brief Called whenever CInsectSwarm::CanHarm() is invoked
* @remarks Called when Spitter Acid is determining if a client or entity can be damaged
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param acid the acid entity index causing the damage
* @param spitter the Spitter or client who created the acid (can be 0 or -1)
@ -2710,10 +2945,23 @@ forward Action L4D2_CGasCan_ShouldStartAction(int client, int gascan, int nozzle
// L4D2 only.
forward void L4D2_CGasCan_ShouldStartAction_Post(int client, int gascan, int nozzle);
/**
* @brief Called whenever CGasCan::ShouldStartAction() is invoked
* @remarks Called when someone has started to pour a gascan into a nozzle
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param client the client pouring
* @param gascan the gascan entity index that is being consumed
* @param nozzle the nozzle being poured into
*
* @noreturn
*/
// L4D2 only.
forward void L4D2_CGasCan_ShouldStartAction_PostHandled(int client, int gascan, int nozzle);
/**
* @brief Called whenever CGasCan::OnActionComplete() is invoked
* @remarks Called when someone is about to complete pouring a gascan into a nozzle
* @remarks This forward will not trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param client the client pouring
* @param gascan the gascan entity index that is being consumed
@ -2739,7 +2987,21 @@ forward Action L4D2_CGasCan_ActionComplete(int client, int gascan, int nozzle);
forward void L4D2_CGasCan_ActionComplete_Post(int client, int gascan, int nozzle);
/**
* @brief Returns the current game mode type when it changes. 0=Unknown or error. 1=Coop. 2=Survival. 4=Versus. 8=Scavenge (L4D2).
* @brief Called whenever CGasCan::OnActionComplete() is invoked
* @remarks Called when someone completes pouring a gascan into a nozzle
* @remarks This forward will ONLY trigger if the relative pre-hook forward has been blocked with Plugin_Handled
*
* @param client the client pouring
* @param gascan the gascan entity index that is being consumed
* @param nozzle the nozzle being poured into
*
* @noreturn
*/
// L4D2 only.
forward void L4D2_CGasCan_ActionComplete_PostHandled(int client, int gascan, int nozzle);
/**
* @brief Returns the current game mode type when it changes. 0=Unknown or error. 1=Coop. 2=Versus. 4=Survival. 8=Scavenge (L4D2).
* @remarks You can use the "GAMEMODE_*" enums provided above to match the mode.
* @remarks Only triggers when the server starts and after when the game mode changes.
*
@ -2922,7 +3184,7 @@ native bool L4D2_ExecVScriptCode(char[] code);
native bool L4D2_GetVScriptOutput(char[] code, char[] buffer, int maxlength);
/**
* @brief Returns the current game mode type. 0=Unknown or error. 1=Coop. 2=Survival. 4=Versus. 8=Scavenge (L4D2).
* @brief Returns the current game mode type. 0=Unknown or error. 1=Coop. 2=Versus. 4=Survival. 8=Scavenge (L4D2).
* @remarks You can use the "GAMEMODE_*" enums provided above to match the mode.
*
* @return Current game mode.
@ -3100,6 +3362,24 @@ native any L4D_GetNearestNavArea(const float vecPos[3], float maxDist = 300.0, b
*/
native any L4D_GetLastKnownArea(int client);
/**
* @brief Gets the first Special Infected type the Director will spawn. Value set on map start.
* @remarks zombieClass: 1=Smoker, 2=Boomer, 3=Hunter, 4=Spitter, 5=Jockey, 6=Charger
*
* @return zombieClass of the Special Infected first spawning.
*/
// L4D2 only.
native int L4D2_GetFirstSpawnClass();
/**
* @brief Sets the first Special Infected type the Director will spawn.
* @remarks zombieClass: 1=Smoker, 2=Boomer, 3=Hunter, 4=Spitter, 5=Jockey, 6=Charger
*
* @noreturn
*/
// L4D2 only.
native void L4D2_SetFirstSpawnClass(int zombieClass);
/**
* @brief Gets the maximum flow distance any survivor has achieved.
*
@ -3162,6 +3442,9 @@ native bool L4D_AreAllSurvivorsInFinaleArea();
/**
* @brief Returns true when the specified Survivor or Special Infected is in the starting checkpoint area.
* @remarks This might return true on certain maps, maybe in Survival/Scavenge start areas if they are close enough to the saferoom.
* @remarks You could use the "L4D_IsPositionInFirstCheckpoint" native instead to accurately determine if someone is in the starting area.
* @remarks This will always returns false when the "Unlock Finales" plugin by "Marttt" is installed: https://forums.alliedmods.net/showthread.php?t=333274
*
* @param client Client id to check their checkpoint.
*
@ -4045,8 +4328,56 @@ native int L4D_GetCurrentChapter();
*/
native int L4D_GetMaxChapters();
/**
* @brief Returns all TheNavAreas addresses
*
*param aList The ArrayList to store all nav area addresses.
*
* @noreturn
*/
native void L4D_GetAllNavAreas(ArrayList aList);
/**
* @brief Returns a given NavArea's ID from it's address
*
*param area The NavArea address
*
* @return NavArea ID
*/
native int L4D_GetNavAreaID(Address area);
/**
* @brief Returns a given NavArea address from it's ID
*
*param id The NavArea ID
*
* @return NavArea address or Address_Null if invalid ID
*/
native Address L4D_GetNavAreaByID(int id);
/**
* @brief Returns origin of a given NavArea
*
*param area The address of the NavArea to read.
*param vecPos The vector to store the position read.
*
* @noreturn
*/
native void L4D_GetNavAreaPos(Address area, float vecPos[3]);
/**
* @brief Returns size of a given NavArea
*
*param area The address of the NavArea to read.
*param vecPos The vector to store the size read.
*
* @noreturn
*/
native void L4D_GetNavAreaSize(Address area, float vecSize[3]);
/**
* @brief Returns the nav area attribute flags
* @remarks See the "NAV_BASE_*" near the top of the include file
*
*param pTerrorNavArea Pointer to a NavArea
*
@ -4056,6 +4387,7 @@ native int L4D_GetNavArea_AttributeFlags(Address pTerrorNavArea);
/**
* @brief Sets the nav area attribute flags
* @remarks See the "NAV_BASE_*" near the top of the include file
*
*param pTerrorNavArea Pointer to a NavArea
*param flags Attribute flags to set
@ -4066,6 +4398,7 @@ native void L4D_SetNavArea_AttributeFlags(Address pTerrorNavArea, int flags);
/**
* @brief Returns the terror nav area attribute flags
* @remarks See the "NAV_SPAWN_*" near the top of the include file
*
*param pTerrorNavArea Pointer to a TerrorNavArea
*
@ -4075,6 +4408,7 @@ native int L4D_GetNavArea_SpawnAttributes(Address pTerrorNavArea);
/**
* @brief Sets the terror nav area attribute flags
* @remarks See the "NAV_SPAWN_*" near the top of the include file
*
*param pTerrorNavArea Pointer to a TerrorNavArea
*param flags Attribute flags to set
@ -5068,7 +5402,7 @@ native float L4D2Direct_GetFlowDistance(int client);
*
* @noreturn
*/
native void L4D2Direct_DoAnimationEvent(int client, int event);
native void L4D2Direct_DoAnimationEvent(int client, int event, int variant_param = 0);
/**
* Get the clients health bonus.
@ -5555,6 +5889,7 @@ native void L4D2_SwapTeams();
* @return 0=Not flipped. 1=Flipped
*/
// L4D2 only.
#pragma deprecated Use this instead: GameRules_GetProp("m_bAreTeamsFlipped");
native bool L4D2_AreTeamsFlipped();
/**

View file

@ -1,6 +1,6 @@
/*
* Left 4 DHooks Direct
* Copyright (C) 2022 Silvers
* Copyright (C) 2023 Silvers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2022 LuxLuma
* Copyright (C) 2023 LuxLuma
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View file

@ -280,6 +280,32 @@ stock void StopUsingMinigun(int client)
}
}
/**
* @brief Returns if a player is on fire
*
* @param client Client index to check
*
* @return true on fire, false otherwise
*/
stock bool L4D_IsPlayerOnFire(int client)
{
if( GetEntProp(client, Prop_Data, "m_fFlags") & FL_ONFIRE ) return true;
else return false;
}
/**
* @brief Returns if a player is burning
*
* @param client Client index to check
*
* @return true on burning, false otherwise
*/
stock bool L4D_IsPlayerBurning(int client)
{
float fBurning = GetEntPropFloat(client, Prop_Send, "m_burnPercent");
return (fBurning > 0.0) ? true : false;
}
// ==================================================
@ -713,15 +739,19 @@ stock bool L4D_HasReachedSmoker(int client)
// ==================================================
// CHARGER STOCKS - Written by "Forgetest"
// ==================================================
#define QueuedPummel_Victim 0
#define QueuedPummel_StartTime 4
#define QueuedPummel_Attacker 8
/**
* @brief Internally used to get offset to the start of queued pummel field.
*
* @return Offset into CTerrorPlayer to the start of queued pummel props
*/
static int L4D2_OffsQueuedPummelInfo()
static stock int L4D2_OffsQueuedPummelInfo()
{
static int m_hQueuedPummelVictim = -1;
if ( m_hQueuedPummelVictim == -1 )
if( m_hQueuedPummelVictim == -1 )
m_hQueuedPummelVictim = FindSendPropInfo("CTerrorPlayer", "m_pummelAttacker") + 4;
return m_hQueuedPummelVictim;
@ -730,31 +760,44 @@ static int L4D2_OffsQueuedPummelInfo()
/**
* @brief Returns the timestamp when the queued pummel begins.
*
* @param client Client ID of the player to check
* @param client Client ID of the charger to check
*
* @return timestamp or -1.0 if no queued pummel
*/
stock float L4D2_GetQueuedPummelStartTime(int client)
stock float L4D2_GetQueuedPummelStartTime(int charger)
{
return GetEntDataFloat(client, L4D2_OffsQueuedPummelInfo() + 4);
return GetEntDataFloat(charger, L4D2_OffsQueuedPummelInfo() + QueuedPummel_StartTime);
}
/**
* @brief Sets the timestamp when the queued pummel begins.
*
* @param client Client ID of the charger to check
* @param timestamp Timestamp to set
*
* @noreturn
*/
stock void L4D2_SetQueuedPummelStartTime(int charger, float timestamp)
{
SetEntDataFloat(charger, L4D2_OffsQueuedPummelInfo() + QueuedPummel_StartTime, timestamp);
}
/**
* @brief Returns if a Charger is in a queued pummel.
*
* @param client Client ID of the player to check
* @param charger Client ID of the charger to check
*
* @return true if in queued pummel, false otherwise
*/
stock bool L4D2_IsInQueuedPummel(int client)
stock bool L4D2_IsInQueuedPummel(int charger)
{
float flTimestamp = L4D2_GetQueuedPummelStartTime(client);
float flTimestamp = L4D2_GetQueuedPummelStartTime(charger);
return flTimestamp != -1.0 && flTimestamp > GetGameTime();
}
/**
* @brief Returns the victim when a Charger is in a queued pummel.
* @brief Returns the victim of a Charger in a queued pummel.
*
* @param client Client ID of the player to check
*
@ -762,11 +805,24 @@ stock bool L4D2_IsInQueuedPummel(int client)
*/
stock int L4D2_GetQueuedPummelVictim(int client)
{
return GetEntDataEnt2(client, L4D2_OffsQueuedPummelInfo());
return GetEntDataEnt2(client, L4D2_OffsQueuedPummelInfo() + QueuedPummel_Victim);
}
/**
* @brief Returns the attacker when a Survivor is in a queued pummel.
* @brief Sets the victim of a Charger in a queued pummel.
*
* @param client Client ID of the player to set
* @param target Client ID of the target to set
*
* @noreturn
*/
stock void L4D2_SetQueuedPummelVictim(int client, int target)
{
SetEntDataEnt2(client, L4D2_OffsQueuedPummelInfo() + QueuedPummel_Victim, target);
}
/**
* @brief Returns the attacker of a Survivor in a queued pummel.
*
* @param client Client ID of the player to check
*
@ -774,7 +830,20 @@ stock int L4D2_GetQueuedPummelVictim(int client)
*/
stock int L4D2_GetQueuedPummelAttacker(int client)
{
return GetEntDataEnt2(client, L4D2_OffsQueuedPummelInfo() + 8);
return GetEntDataEnt2(client, L4D2_OffsQueuedPummelInfo() + QueuedPummel_Attacker);
}
/**
* @brief Sets the attacker of a Survivor in a queued pummel.
*
* @param client Client ID of the player to set
* @param target Client ID of the target to set
*
* @noreturn
*/
stock void L4D2_SetQueuedPummelAttacker(int client, int target)
{
SetEntDataEnt2(client, L4D2_OffsQueuedPummelInfo() + QueuedPummel_Attacker, target);
}

View file

@ -1,7 +1,7 @@
/**
* =============================================================================
* Left 4 Dead Stocks Library (C)2011-2012 Buster "Mr. Zero" Nielsen
* Syntax Update and merge into "Left 4 DHooks Direct" (C) 2022 "SilverShot"
* Syntax Update and merge into "Left 4 DHooks Direct" (C) 2023 "SilverShot"
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it
@ -134,13 +134,17 @@ enum L4D2ZombieClassType
enum L4D2UseAction
{
L4D2UseAction_None = 0, // No use action active
L4D2UseAction_Healing = 1, // Includes healing yourself or a teammate.
L4D2UseAction_Defibing = 4, // When defib'ing a dead body.
L4D2UseAction_GettingDefibed = 5, // When comming back to life from a dead body.
L4D2UseAction_PouringGas = 8, // Pouring gas into a generator
L4D2UseAction_Cola = 9, // For Dead Center map 2 cola event, when handing over the cola to whitalker.
L4D2UseAction_Button = 10 // Such as buttons, timed buttons, generators, etc.
L4D2UseAction_None = 0, // No use action active
L4D2UseAction_Healing = 1, // Includes healing yourself or a teammate.
L4D2UseAction_AmmoPack = 2, // When deploying the ammo pack that was never added into the game
L4D2UseAction_Defibing = 4, // When defib'ing a dead body.
L4D2UseAction_GettingDefibed = 5, // When comming back to life from a dead body.
L4D2UseAction_DeployIncendiary = 6, // When deploying Incendiary ammo
L4D2UseAction_DeployExplosive = 7, // When deploying Explosive ammo
L4D2UseAction_PouringGas = 8, // Pouring gas into a generator
L4D2UseAction_Cola = 9, // For Dead Center map 2 cola event, when handing over the cola to whitalker.
L4D2UseAction_Button = 10, // Such as buttons, timed buttons, generators, etc.
L4D2UseAction_UsePointScript = 11 // When using a "point_script_use_target" entity
/* List is not fully done, these are just the ones I have found so far */
}

View file

@ -46,6 +46,8 @@ enum struct PlayerData {
int immunityFlags;
bool pendingAction;
bool joined;
}
PlayerData pData[MAXPLAYERS+1];
@ -96,7 +98,7 @@ public void OnPluginStart() {
hFFAutoScaleAmount = CreateConVar("l4d2_tk_auto_ff_rate", "0.02", "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.05", "This amount times amount of minutes since last ff is removed from ff rate", FCVAR_NONE, true, 0.0);
hFFAutoScaleActivateTypes = CreateConVar("l4d2_tk_auto_ff_activate_types", "7", "The types of damages to ignore. Add bits together.\n0 = Just direct fire\n1 = Damage from admins\n2 = Blast damage (pipes, grenade launchers)\n4 = Molotov/gascan/firework damage\n8 = Killing black and white players", FCVAR_NONE, true, 0.0, true, 15.0);
hFFAutoScaleActivateTypes = CreateConVar("l4d2_tk_auto_ff_activate_types", "6", "The types of damages to ignore. Add bits together.\n0 = Just direct fire\n1 = Damage from admins\n2 = Blast damage (pipes, grenade launchers)\n4 = Molotov/gascan/firework damage\n8 = Killing black and white players", FCVAR_NONE, true, 0.0, true, 15.0);
ConVar hGamemode = FindConVar("mp_gamemode");
hGamemode.AddChangeHook(Event_GamemodeChange);
@ -238,6 +240,7 @@ public void Event_FinaleVehicleReady(Event event, const char[] name, bool dontBr
PrintChatToAdmins("Note: %N is still marked as troll and will be banned after this game. Use \"/ignore <player> tk\" to ignore them.", i);
}
}
PrintToServer("[TKStopper] Escape vehicle active, 2x rff in effect");
}
public void OnMapEnd() {
@ -250,7 +253,8 @@ public void OnClientPutInServer(int client) {
}
public void OnClientPostAdminCheck(int client) {
if(GetUserAdmin(client) != INVALID_ADMIN_ID) {
if(GetUserAdmin(client) != INVALID_ADMIN_ID && !pData[client].joined) {
pData[client].joined = true;
pData[client].immunityFlags = Immune_TK;
// If no admins can do ff and they
if(~hFFAutoScaleActivateTypes.IntValue & view_as<int>(RffActType_AdminDamage)) {
@ -298,6 +302,7 @@ public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroa
pData[client].ffCount = 0;
pData[client].immunityFlags = 0;
pData[client].totalFFCount = 0;
pData[client].joined = false;
}
}
@ -314,7 +319,7 @@ public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, flo
return Plugin_Changed;
}
// Otherwise if attacker was ignored or is a bot, stop here and let vanilla handle it
else if(pData[attacker].immunityFlags & Immune_RFF || IsFakeClient(attacker)) return Plugin_Continue;
else if(pData[attacker].immunityFlags & Immune_RFF || IsFakeClient(attacker) || IsFakeClient(victim)) return Plugin_Continue;
// If victim is black and white and rff damage isnt turned on for it, allow it:
else if(damagetype & DMG_DIRECT && GetEntProp(victim, Prop_Send, "m_isGoingToDie") && ~hFFAutoScaleActivateTypes.IntValue & view_as<int>(RffActType_BlackAndWhiteDamage)) {
return Plugin_Continue;
@ -462,7 +467,7 @@ public Action Event_OnTakeDamage(int victim, int& attacker, int& inflictor, flo
SDKHooks_TakeDamage(attacker, attacker, attacker, pData[attacker].autoRFFScaleFactor * damage);
if(pData[attacker].autoRFFScaleFactor > 1.0)
damage /= pData[attacker].autoRFFScaleFactor;
damage = 0.0;
else
damage /= 2.0;
return Plugin_Changed;

View file

@ -3,6 +3,7 @@
//#define DEBUG
#define ALLOW_HEALING_MIN_IDLE_TIME 180
#define PLUGIN_VERSION "1.0"
#include <sourcemod>
@ -10,6 +11,8 @@
#include <actions>
//#include <sdkhooks>
int idleTimeStart[MAXPLAYERS+1];
public Plugin myinfo =
{
name = "L4D2 AI Tweaks",
@ -24,8 +27,16 @@ public void OnPluginStart() {
if(g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D2 only.");
}
// HookEvent("player_bot_replace", Event_PlayerOutOfIdle );
HookEvent("bot_player_replace", Event_PlayerToIdle);
}
public Action Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) {
idleTimeStart[client] = GetTime();
}
}
public void OnActionCreated( BehaviorAction action, int actor, const char[] name ) {
/* Hooking friend healing action (when bot wants to heal someone) */
@ -37,7 +48,8 @@ public Action OnFriendAction( BehaviorAction action, int actor, BehaviorAction p
// Do not allow idle bots to heal another player, unless they are black and white.
// Do not let idle bots heal non-idle bots
int target = action.Get(0x34) & 0xFFF;
if(GetEntProp(actor, Prop_Send, "m_humanSpectatorUserID") > 0) { // If idle bot
int realPlayer = GetClientOfUserId(GetEntProp(actor, Prop_Send, "m_humanSpectatorUserID"));
if(realPlayer > 0) { // If idle bot
if(IsFakeClient(target)) {
// If target is a bot, not idle player, ignore
if(GetEntProp(target, Prop_Send, "m_humanSpectatorUserID") == 0) {
@ -46,7 +58,7 @@ public Action OnFriendAction( BehaviorAction action, int actor, BehaviorAction p
}
}
// If they are not black and white, also stop
if(!GetEntProp(target, Prop_Send, "m_bIsOnThirdStrike")) { //If real player and not black and white, stop
if(!GetEntProp(target, Prop_Send, "m_bIsOnThirdStrike") && idleTimeStart[realPlayer] < ALLOW_HEALING_MIN_IDLE_TIME) { //If real player and not black and white, stop
result.type = DONE;
return Plugin_Handled;
}

View file

@ -54,10 +54,12 @@ public void OnPluginStart()
if(IsValidEntity(i)) {
GetEntityClassname(i, classname, sizeof(classname));
if(StrEqual(classname, "witch", false)) {
WitchList.Push(i);
#if defined DEBUG
PrintToServer("Found pre-existing witch %d", i);
#endif
if(HasEntProp(i, Prop_Send, "m_rage")) {
WitchList.Push(EntIndexToEntRef(i));
#if defined DEBUG
PrintToServer("Found pre-existing witch %d", i);
#endif
}
}
}
@ -136,29 +138,28 @@ public void Change_Gamemode(ConVar convar, const char[] oldValue, const char[] n
}
public Action Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) {
public void Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) {
int witchID = event.GetInt("witchid");
WitchList.Push(witchID);
#if defined DEBUG
PrintToServer("Witch spawned: %d", witchID);
#endif
//If not currently scanning, begin scanning ONLY if not active
if(timer == INVALID_HANDLE && AutoCrownBot == -1) {
timer = CreateTimer(SCAN_INTERVAL, Timer_Scan, _, TIMER_REPEAT);
if(HasEntProp(witchID, Prop_Send, "m_rage")) {
WitchList.Push(EntIndexToEntRef(witchID));
#if defined DEBUG
PrintToServer("Witch spawned: %d", witchID);
#endif
//If not currently scanning, begin scanning ONLY if not active
if(timer == INVALID_HANDLE && AutoCrownBot == -1) {
timer = CreateTimer(SCAN_INTERVAL, Timer_Scan, _, TIMER_REPEAT);
}
}
}
public Action Event_WitchKilled(Event event, const char[] name, bool dontBroadcast) {
int witchID = event.GetInt("witchid");
int index = FindValueInArray(WitchList, witchID);
#if defined DEBUG
PrintToServer("Witched killed: %d", witchID);
#endif
public void Event_WitchKilled(Event event, const char[] name, bool dontBroadcast) {
int witchRef = EntIndexToEntRef(event.GetInt("witchid"));
int index = WitchList.FindValue(witchRef);
if(index > -1) {
RemoveFromArray(WitchList, index);
WitchList.Erase(index);
}
//If witch that was killed, terminate active loop
if(AutoCrownTarget == witchID) {
if(AutoCrownTarget == witchRef) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("AutoCrownTarget has died");
@ -174,65 +175,67 @@ public Action Timer_Active(Handle hdl) {
return Plugin_Stop;
}
//TODO: Also check if startled and cancel it immediately.
if(AutoCrownBot > -1) {
int client = GetClientOfUserId(AutoCrownBot);
if(!IsValidEntity(AutoCrownTarget) || IsPlayerIncapped(client)) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("Could not find valid AutoCrownTarget");
#endif
return Plugin_Stop;
}else if(client <= 0 || !IsClientConnected(client) || !IsClientInGame(client) || !IsPlayerAlive(client)) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("Could not find valid AutoCrownBot");
#endif
return Plugin_Stop;
}
char wpn[32];
if(!GetClientWeapon(client, wpn, sizeof(wpn)) || !StrEqual(wpn, "weapon_autoshotgun") && !StrEqual(wpn, "weapon_shotgun_spas")) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("AutoCrownBot does not have a valid weapon (%s)", wpn);
#endif
return Plugin_Stop;
}
GetEntPropVector(AutoCrownTarget, Prop_Send, "m_vecOrigin", witchPos);
GetClientAbsOrigin(client, botPosition);
float distance = GetVectorDistance(botPosition, witchPos);
if(distance <= 60) {
float botAngles[3];
GetClientAbsAngles(client, botAngles);
botAngles[0] = 60.0;
botAngles[1] = RadToDeg(ArcTangent2( botPosition[1] - witchPos[1], botPosition[0] - witchPos[0])) + 180.0;
//Is In Position
ClientCommand(client, "slot0");
TeleportEntity(client, NULL_VECTOR, botAngles, NULL_VECTOR);
AutoCrownInPosition = true;
}else{
L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", AutoCrownBot, witchPos[0], witchPos[1], witchPos[2]);
PathfindTries++;
}
if(PathfindTries > 30) {
ResetAutoCrown();
int index = FindValueInArray(WitchList, AutoCrownTarget);
if(index > -1)
RemoveFromArray(WitchList, index);
//remove witch
#if defined DEBUG
PrintToServer("Could not pathfind to witch in time.");
#endif
}
return Plugin_Continue;
}else{
if(AutoCrownBot == -1) {
timer = CreateTimer(SCAN_INTERVAL, Timer_Scan, _, TIMER_REPEAT);
return Plugin_Stop;
}
int client = GetClientOfUserId(AutoCrownBot);
int crownTarget = EntRefToEntIndex(AutoCrownTarget);
if(crownTarget == INVALID_ENT_REFERENCE) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("Could not find valid AutoCrownTarget");
#endif
return Plugin_Stop;
}else if(client <= 0 || !IsPlayerAlive(client)) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("Could not find valid AutoCrownBot");
#endif
return Plugin_Stop;
}
char wpn[32];
if(!GetClientWeapon(client, wpn, sizeof(wpn)) || !StrEqual(wpn, "weapon_autoshotgun") && !StrEqual(wpn, "weapon_shotgun_spas")) {
ResetAutoCrown();
#if defined DEBUG
PrintToServer("AutoCrownBot does not have a valid weapon (%s)", wpn);
#endif
return Plugin_Stop;
}
GetEntPropVector(crownTarget, Prop_Send, "m_vecOrigin", witchPos);
GetClientAbsOrigin(client, botPosition);
float distance = GetVectorDistance(botPosition, witchPos, true);
if(distance <= 3600) {
float botAngles[3];
GetClientAbsAngles(client, botAngles);
botAngles[0] = 60.0;
botAngles[1] = RadToDeg(ArcTangent2(botPosition[1] - witchPos[1], botPosition[0] - witchPos[0])) + 180.0;
//Is In Position
ClientCommand(client, "slot0");
TeleportEntity(client, NULL_VECTOR, botAngles, NULL_VECTOR);
AutoCrownInPosition = true;
} else {
L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", AutoCrownBot, witchPos[0], witchPos[1], witchPos[2]);
PathfindTries++;
}
if(PathfindTries > 40) {
ResetAutoCrown();
int index = WitchList.FindValue(AutoCrownTarget);
if(index > -1)
WitchList.Erase(index);
//remove witch
#if defined DEBUG
PrintToServer("Could not pathfind to witch in time.");
#endif
}
return Plugin_Continue;
}
public Action Timer_Scan(Handle hdl) {
float botPosition[3], witchPos[3];
@ -253,8 +256,13 @@ public Action Timer_Scan(Handle hdl) {
//Loop all witches, find any valid nearby witches:
for(int i = 0; i < WitchList.Length; i++) {
int witchID = WitchList.Get(i);
if(IsValidEntity(witchID) && HasEntProp(witchID, Prop_Send, "m_rage") && GetEntPropFloat(witchID, Prop_Send, "m_rage") <= 0.4) {
int witchRef = WitchList.Get(i);
int witchID = EntRefToEntIndex(witchRef);
if(witchID == INVALID_ENT_REFERENCE) {
WitchList.Erase(i);
continue;
}
if(GetEntPropFloat(witchID, Prop_Send, "m_rage") <= 0.4) {
GetEntPropVector(witchID, Prop_Send, "m_vecOrigin", witchPos);
if(GetVectorDistance(botPosition, witchPos) <= SCAN_RANGE) {
//GetEntPropVector(witchID, Prop_Send, "m_angRotation", witchAng);
@ -264,7 +272,7 @@ public Action Timer_Scan(Handle hdl) {
#endif
L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", GetClientUserId(bot), witchPos[0], witchPos[1], witchPos[2]);
AutoCrownTarget = witchID;
AutoCrownTarget = witchRef;
AutoCrownBot = GetClientUserId(bot);
AutoCrownInPosition = false;
CreateTimer(ACTIVE_INTERVAL, Timer_Active, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
@ -282,19 +290,19 @@ public Action Timer_Scan(Handle hdl) {
public Action Timer_StopFiring(Handle hdl) {
ResetAutoCrown();
return Plugin_Handled;
}
public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) {
if(AutoCrownInPosition && GetClientOfUserId(AutoCrownBot) == client && !(buttons & IN_ATTACK)) {
buttons |= IN_ATTACK;
//CreateTimer(0.4, Timer_StopFiring);
return Plugin_Changed;
}
return Plugin_Continue;
}
public void ResetAutoCrown() {
AutoCrownTarget = -1;
AutoCrownTarget = INVALID_ENT_REFERENCE;
AutoCrownInPosition = false;
if(AutoCrownBot > -1)
L4D2_RunScript("CommandABot({cmd=3,bot=GetPlayerFromUserID(%i)})", AutoCrownBot);

View file

@ -4,12 +4,14 @@
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#define MAX_TIME_ONLINE_MS 604800
#define MAX_TIME_ONLINE_SECONDS 172800
//604800
#include <sourcemod>
#include <sdktools>
//#include <sdkhooks>
int startupTime, triesBots, triesEmpty;
bool pendingRestart;
public Plugin myinfo = {
name = "L4D2 Autorestart",
@ -41,7 +43,7 @@ public Action Command_RequestRestart(int client, int args) {
ReplyToCommand(client, "Restarting...");
LogAction(client, -1, "requested to restart server if empty.");
ServerCommand("quit");
}else{
} else {
ReplyToCommand(client, "Players are online.");
}
return Plugin_Handled;
@ -55,8 +57,9 @@ public Action Timer_Check(Handle h) {
ServerCommand("quit");
}
return Plugin_Continue;
} else if(GetTime() - startupTime > MAX_TIME_ONLINE_MS) {
} else if(GetTime() - startupTime > MAX_TIME_ONLINE_SECONDS) {
LogAction(0, -1, "Server has passed max online time threshold, will restart if remains empty");
pendingRestart = true;
noHibernate.BoolValue = true;
if(IsServerEmpty()) {
if(++triesEmpty > 4) {
@ -65,12 +68,20 @@ public Action Timer_Check(Handle h) {
}
return Plugin_Continue;
}
// If server is occupied, falls down below and resets:
}
triesBots = 0;
triesEmpty = 0;
return Plugin_Continue;
}
public void OnConfigsExecuted() {
// Reset no hibernate setting when level changes:
if(pendingRestart) {
noHibernate.BoolValue = true;
}
}
// Returns true if server is empty, and there is only bots. No players
bool IsServerEmptyWithOnlyBots() {
bool hasBot;

View file

@ -2,17 +2,29 @@
#pragma newdecls required
#define DEBUG 0
#define PLUGIN_VERSION "1.0"
#define PANIC_DETECT_THRESHOLD 50.0
#include <sourcemod>
#include <sdktools>
#include <left4dhooks>
//#include <sdkhooks>
static ConVar hPercent, hRange, hEnabled;
#define PANIC_DETECT_THRESHOLD 50.0
#define MAX_GROUPS 4
enum struct Group {
float pos[3];
ArrayList members;
}
enum struct GroupResult {
int groupCount;
int ungroupedCount;
float ungroupedRatio;
}
static ConVar hPercent, hRange, hEnabled, hGroupTeamDist;
static char gamemode[32];
static bool panicStarted;
static float lastButtonPressTime;
@ -38,15 +50,27 @@ public void OnPluginStart()
hEnabled = CreateConVar("l4d2_crescendo_control", "1", "Should plugin be active?\n 1 = Enabled normally\n2 = Admins with bypass allowed only", FCVAR_NONE, true, 0.0, true, 1.0);
hPercent = CreateConVar("l4d2_crescendo_percent", "0.5", "The percent of players needed to be in range for crescendo to start", FCVAR_NONE);
hRange = CreateConVar("l4d2_crescendo_range", "250.0", "How many units away something range brain no work", FCVAR_NONE);
hGroupTeamDist = CreateConVar("l4d2_cc_team_maxdist", "320.0", "The maximum distance another player can be away from someone to form a group", FCVAR_NONE, true, 10.0);
ConVar hGamemode = FindConVar("mp_gamemode");
hGamemode.GetString(gamemode, sizeof(gamemode));
hGamemode.AddChangeHook(Event_GamemodeChange);
AddNormalSoundHook(SoundHook);
RegAdminCmd("sm_dgroup", Command_DebugGroups, ADMFLAG_GENERIC);
//dhook setup
}
Action Command_DebugGroups(int client, int args) {
PrintDebug("Running manual compute of groups");
float activatorFlow = L4D2Direct_GetFlowDistance(client);
Group groups[MAX_GROUPS];
GroupResult result;
ComputeGroups(groups, result, activatorFlow);
return Plugin_Handled;
}
public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[] newValue) {
cvar.GetString(gamemode, sizeof(gamemode));
}
@ -83,19 +107,37 @@ public Action Timer_GetFlows(Handle h) {
return Plugin_Continue;
}
public float GetFlowAtPosition(const float pos[3]) {
Address area = L4D_GetNearestNavArea(pos, 50.0, false, false, false, 2);
if(area == Address_Null) return -1.0;
return L4D2Direct_GetTerrorNavAreaFlow(area);
}
public Action Event_ButtonPress(const char[] output, int entity, int client, float delay) {
if(hEnabled.IntValue > 0 && client > 0 && client <= MaxClients) {
float activatorFlow = L4D2Direct_GetFlowDistance(client);
Group groups[MAX_GROUPS];
GroupResult result;
ComputeGroups(groups, result, activatorFlow);
AdminId admin = GetUserAdmin(client);
if(admin != INVALID_ADMIN_ID && admin.HasFlag(Admin_Custom1)) return Plugin_Continue;
if(admin != INVALID_ADMIN_ID && admin.HasFlag(Admin_Custom1)) {
lastButtonPressTime = GetGameTime();
return Plugin_Continue;
} else if(result.groupCount > 0 && result.ungroupedCount > 0) {
lastButtonPressTime = GetGameTime();
return Plugin_Continue;
}
if(panicStarted) {
panicStarted = false;
return Plugin_Continue;
}
static float pos[3];
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos);
float activatorFlow = L4D2Direct_GetFlowDistance(client);
PrintToConsoleAll("[CC] Button Press by %N", client);
if(hEnabled.IntValue == 2 || !IsActivationAllowed(activatorFlow, 1500.0)) {
@ -123,6 +165,134 @@ public Action SoundHook(int clients[MAXPLAYERS], int& numClients, char sample[PL
public void Frame_ResetButton(int entity) {
AcceptEntityInput(entity, "Unlock");
}
bool ComputeGroups(Group groups[MAX_GROUPS], GroupResult result, float activateFlow) {
float prevPos[3], pos[3];
// int prevMember = -1;
// ArrayList groupMembers = new ArrayList();
int groupIndex = 0;
// ArrayList groups = new ArrayList();
// Group group;
// // Create the first group
// group.pos = pos;
// group.members = new ArrayList();
// PrintToServer("[cc] Creating first group");
bool inGroup[MAXPLAYERS+1];
for(int i = 1; i <= MaxClients; i++) {
if(!inGroup[i] && IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) {
float prevFlow = L4D2Direct_GetFlowDistance(i);
GetClientAbsOrigin(i, prevPos);
ArrayList members = new ArrayList();
for(int j = 1; j <= MaxClients; j++) {
if(j != i && IsClientConnected(j) && IsClientInGame(j) && IsPlayerAlive(j) && GetClientTeam(j) == 2) {
// TODO: MERGE groups
GetClientAbsOrigin(j, pos);
float flow = L4D2Direct_GetFlowDistance(j);
float dist = FloatAbs(GetVectorDistance(prevPos, pos));
float flowDiff = FloatAbs(prevFlow - flow);
if(dist <= hGroupTeamDist.FloatValue) {
if(members.Length == 0) {
members.Push(GetClientUserId(i));
PrintDebug("add leader to group %d: %N", groupIndex + 1, i);
}
PrintDebug("add member to group %d: %N (dist = %.4f) (fldiff = %.1f)", groupIndex + 1, j, dist, flowDiff);
inGroup[j] = true;
members.Push(GetClientUserId(j));
} else {
PrintDebug("not adding member to group %d: %N (dist = %.4f) (fldiff = %.1f) (l:%N)", groupIndex + 1, j, dist, flowDiff, i);
}
}
}
if(members.Length > 1) {
groups[groupIndex].pos = prevPos;
groups[groupIndex].members = members;
groupIndex++;
PrintDebug("created group #%d with %d members", groupIndex, members.Length);
if(groupIndex == MAX_GROUPS) {
PrintDebug("maximum amount of groups reached (%d)", MAX_GROUPS);
}
} else {
delete members;
}
}
}
int totalGrouped = 0;
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) {
if(inGroup[i])
totalGrouped++;
else
result.ungroupedCount++;
}
}
result.ungroupedRatio = float(result.ungroupedCount) / float(totalGrouped);
PrintDebug("total grouped: %d | total ungrouped: %d | ratio: %f", totalGrouped, result.ungroupedCount, result.ungroupedRatio);
PrintDebug("total groups created: %d", groupIndex);
// for(int i = 1; i <= MaxClients; i++) {
// if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) {
// GetClientAbsOrigin(i, pos);
// // Skip the first member, as they will start the group
// if(prevMember == -1) {
// prevMember = i;
// continue;
// }
// // Check if player is in a radius of the group source
// float dist = GetVectorDistance(group.pos, pos);
// if(dist < TEAM_GROUP_DIST) {
// // TODO: not just join last group
// if(group.members.Length == 0) {
// PrintToServer("[cc] add leader to group %d: %N", groupIndex + 1, prevMember);
// // groupMembers.Push(GetClientUserId(prevMember));
// group.members.Push(GetClientUserId(prevMember));
// }
// // groupMembers.Push(GetClientUserId(i));
// group.members.Push(GetClientUserId(i));
// PrintToServer("[cc] add member to group %d: %N (dist = %.2f)", groupIndex + 1, i, dist);
// } else {
// // Player is not, create a new group.
// if(group.members.Length > 0) {
// groups.PushArray(group);
// }
// groupIndex++;
// group.pos = pos;
// group.members = new ArrayList();
// PrintToServer("[cc] Creating group %d", groupIndex + 1);
// }
// prevPos = pos;
// prevMember = i;
// }
// }
PrintDebug("===GROUP SUMMARY===");
for(int i = 0; i < MAX_GROUPS; i++) {
if(groups[i].members != null) {
PrintDebug("---Group %d---", i + 1);
PrintDebug("Origin: %.1f %.1f %.1f", groups[i].pos[0], groups[i].pos[1], groups[i].pos[2]);
float groupFlow = GetFlowAtPosition(groups[i].pos);
PrintDebug("Flow Diff: %.2f (g:%.1f) (a:%.1f) (gdt:%.f)", FloatAbs(activateFlow - groupFlow), activateFlow, groupFlow, hGroupTeamDist.FloatValue);
PrintDebug("Leader: %N (uid#%d)", GetClientOfUserId(groups[i].members.Get(0)), groups[i].members.Get(0));
for(int j = 1; j < groups[i].members.Length; j++) {
int userid = groups[i].members.Get(j);
PrintDebug("Member: %N (uid#%d)", GetClientOfUserId(userid), userid);
}
delete groups[i].members;
}
}
PrintDebug("===END GROUP SUMMARY===");
// delete groupMembers;
result.groupCount = groupIndex;
return groupIndex > 0;
}
// 5 far/8 total
@ -136,7 +306,7 @@ stock bool IsActivationAllowed(float flowmax, float threshold) {
int farSurvivors, totalSurvivors;
float totalFlow;
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && !IsFakeClient(i)) {
if(flowRate[i] < flowmax - threshold) {
PrintDebug("Adding %N with flow of %.2f to far survivors average", i, flowRate[i]);
farSurvivors++;
@ -145,7 +315,7 @@ stock bool IsActivationAllowed(float flowmax, float threshold) {
totalSurvivors++;
}
}
if(farSurvivors == 0) return true;
if(farSurvivors == 0 || totalSurvivors == 1) return true;
float average = totalFlow / farSurvivors;
float percentFar = float(farSurvivors) / float(totalSurvivors);
@ -156,7 +326,7 @@ stock bool IsActivationAllowed(float flowmax, float threshold) {
return true;
}
//If not, check the ratio of players
bool isAllowed = percentFar <= 0.30;
bool isAllowed = percentFar <= 0.40;
PrintDebug("Activation is %s", isAllowed ? "allowed" : "blocked");
return isAllowed;
}
@ -178,7 +348,8 @@ stock void PrintDebug(const char[] format, any ... ) {
#if defined DEBUG
char buffer[256];
VFormat(buffer, sizeof(buffer), format, 2);
PrintToServer("[Debug] %s", buffer);
PrintToConsoleAll("[Debug] %s", buffer);
// PrintToServer("[CrescendoControl:Debug] %s", buffer);
PrintToConsoleAll("[CrescendoControl:Debug] %s", buffer);
LogMessage("%s", buffer);
#endif
}

View file

@ -16,6 +16,7 @@
enum KitDetectionState {
KDS_None,
KDS_NoKitEnteringSaferoom,
KDS_PickedUpKit,
KDS_Healed
}
@ -23,6 +24,7 @@ enum struct PlayerDetections {
int kitPickupsSaferoom;
int saferoomLastOpen;
int saferoomOpenCount;
bool hadKitBeforeHeal;
// Do not reset normally; need to keep track during level transitions
KitDetectionState saferoomKitState;
@ -149,8 +151,8 @@ public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast)
if(StrEqual(itmName, "first_aid_kit")) {
if(detections[client].saferoomKitState == KDS_NoKitEnteringSaferoom) {
// Player had no kit entering saferoom and has healed
detections[client].saferoomKitState = KDS_Healed;
} else if(detections[client].saferoomKitState == KDS_Healed) {
detections[client].saferoomKitState = KDS_PickedUpKit;
} else if(detections[client].saferoomKitState == KDS_PickedUpKit) {
// Player has healed. Double kit detected
InternalDebugLog("DOUBLE_KIT", client);
Call_StartForward(fwd_PlayerDoubleKit);

View file

@ -24,7 +24,7 @@
#define DEBUG_ANY 3
//Set the debug level
#define DEBUG_LEVEL DEBUG_GENERIC
#define DEBUG_LEVEL DEBUG_ANY
#define EXTRA_PLAYER_HUD_UPDATE_INTERVAL 0.8
//Sets abmExtraCount to this value if set
// #define DEBUG_FORCE_PLAYERS 7
@ -103,11 +103,19 @@ enum State {
State_PendingEmpty,
State_Active
}
#if defined DEBUG_LEVEL
char StateNames[3][] = {
"Empty",
"PendingEmpty",
"Actve"
};
#endif
enum struct PlayerData {
bool itemGiven; //Is player being given an item (such that the next pickup event is ignored)
bool isUnderAttack; //Is the player under attack (by any special)
State state;
bool hasJoined;
}
enum struct PlayerInventory {
@ -187,7 +195,7 @@ public void OnPluginStart() {
HookEvent("tank_spawn", Event_TankSpawn);
//Special Event Tracking
HookEvent("player_team", Event_PlayerTeam);
HookEvent("player_disconnect", Event_PlayerDisconnect);
HookEvent("charger_carry_start", Event_ChargerCarry);
HookEvent("charger_carry_end", Event_ChargerCarry);
@ -212,7 +220,7 @@ public void OnPluginStart() {
hSaferoomDoorAutoOpen = CreateConVar("l4d2_extraitems_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0);
hEPIHudState = CreateConVar("l4d2_extraitems_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0);
hExtraFinaleTank = CreateConVar("l4d2_extraitems_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0);
hSplitTankChance = CreateConVar("l4d2_extraitems_splittank_chance", "0.5", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 1.0);
hSplitTankChance = CreateConVar("l4d2_extraitems_splittank_chance", "0.75", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0);
cvDropDisconnectTime = CreateConVar("l4d2_extraitems_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0);
cvFFDecreaseRate = CreateConVar("l4d2_extraitems_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0);
@ -264,6 +272,7 @@ public void OnPluginStart() {
RegAdminCmd("sm_epi_lock", Command_ToggleDoorLocks, ADMFLAG_CHEATS, "Toggle all toggle\'s lock state");
RegAdminCmd("sm_epi_kits", Command_GetKitAmount, ADMFLAG_CHEATS);
RegAdminCmd("sm_epi_items", Command_RunExtraItems, ADMFLAG_CHEATS);
RegConsoleCmd("sm_epi_status", Command_DebugStatus);
#endif
CreateTimer(10.0, Timer_ForceUpdateInventories, _, TIMER_REPEAT);
@ -333,7 +342,10 @@ public void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char
delete updateHudTimer;
}else {
int count = GetRealSurvivorsCount();
int threshold = hEPIHudState.IntValue == 1 ? 4 : 0;
int threshold = 0;
if(hEPIHudState.IntValue == 1) {
threshold = L4D2_GetSurvivorSetMap() == 2 ? 4 : 5;
}
if(convar.IntValue > 0 && count > threshold && updateHudTimer == null) {
PrintToServer("[EPI] Creating new hud timer");
updateHudTimer = CreateTimer(EXTRA_PLAYER_HUD_UPDATE_INTERVAL, Timer_UpdateHud, _, TIMER_REPEAT);
@ -414,7 +426,7 @@ public Action Command_SetKitAmount(int client, int args) {
extraKitsAmount = number;
extraKitsStarted = extraKitsAmount;
ReplyToCommand(client, "Set extra kits amount to %d", number);
}else{
} else {
ReplyToCommand(client, "Must be a number greater than 0. -1 to disable");
}
return Plugin_Handled;
@ -440,6 +452,15 @@ public Action Command_RunExtraItems(int client, int args) {
PopulateItems();
return Plugin_Handled;
}
public Action Command_DebugStatus(int client, int args) {
ReplyToCommand(client, "Player Statuses:");
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && !IsFakeClient(i)) {
ReplyToCommand(i, "\t%d. %N: %s", i, i, StateNames[view_as<int>(playerData[i].state)]);
}
}
return Plugin_Handled;
}
#endif
/////////////////////////////////////
/// EVENTS
@ -482,7 +503,8 @@ public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) {
int user = event.GetInt("userid");
int tank = GetClientOfUserId(user);
if(tank > 0 && IsFakeClient(tank) && abmExtraCount > 4 && hExtraFinaleTank.BoolValue) {
if(tank > 0 && IsFakeClient(tank) && abmExtraCount > 4 && hExtraFinaleTank.IntValue > 0) {
PrintToConsoleAll("[EPI] Split tank is enabled, checking new spawned tank");
if(finaleStage == Stage_FinaleTank2 && allowTankSplit && hExtraFinaleTank.IntValue & 2) {
PrintToConsoleAll("[EPI] Second tank spawned, setting health.");
// Sets health in half, sets finaleStage to health
@ -500,6 +522,8 @@ public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast)
extraTankHP = hp;
CreateTimer(0.2, Timer_SetHealth, user);
CreateTimer(GetRandomFloat(10.0, 18.0), Timer_SpawnSplitTank, user);
} else {
PrintToConsoleAll("[EPI] Random chance for split tank failed");
}
// Then, summon the next tank
} else if(finaleStage == Stage_TankSplit) {
@ -548,7 +572,7 @@ public void OnGetWeaponsInfo(int pThis, const char[] classname) {
///////////////////////////////////////////////////////
//Called on the first spawn in a mission.
public Action Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
public void Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
firstGiven = false;
extraKitsAmount = 0;
extraKitsStarted = 0;
@ -559,62 +583,71 @@ public Action Event_GameStart(Event event, const char[] name, bool dontBroadcast
for(int i = 1; i <= MaxClients; i++) {
playerData[i].state = State_Empty;
}
return Plugin_Continue;
}
public Action Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
public void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
int userid = event.GetInt("userid");
int client = GetClientOfUserId(userid);
if(GetClientTeam(client) == 2) {
CreateTimer(1.5, Timer_RemoveInvincibility, client);
if(GetClientTeam(client) != 2) return;
if(IsFakeClient(client)) {
// Make the real player's bot invincible, ONLY for the first time it appears
int player = L4D_GetIdlePlayerOfBot(client);
if(player > 0 && !playerData[client].hasJoined) {
playerData[client].hasJoined = true;
// TODO: Confirm this fix works
CreateTimer(1.5, Timer_RemoveInvincibility, userid);
SDKHook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken);
}
} else {
// Make the (real) player invincible:
CreateTimer(1.5, Timer_RemoveInvincibility, userid);
SDKHook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken);
if(!IsFakeClient(client)) {
playerData[client].state = State_Active;
if(L4D_IsFirstMapInScenario() && !firstGiven) {
//Check if all clients are ready, and survivor count is > 4.
if(AreAllClientsReady()) {
abmExtraCount = GetRealSurvivorsCount();
if(abmExtraCount > 4) {
firstGiven = true;
//Set the initial value ofhMinPlayers
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = abmExtraCount;
}
PopulateItems();
CreateTimer(1.0, Timer_GiveKits);
}
if(firstSaferoomDoorEntity > 0 && IsValidEntity(firstSaferoomDoorEntity)) {
UnlockDoor(firstSaferoomDoorEntity, 2);
}
}
} else {
// New client has connected, not on first map.
// TODO: Check if Timer_UpdateMinPlayers is needed, or if this works:
// Never decrease abmExtraCount
int newCount = GetRealSurvivorsCount();
if(newCount > abmExtraCount && abmExtraCount > 4) {
abmExtraCount = newCount;
hMinPlayers.IntValue = abmExtraCount;
ConVar friendlyFireFactor = GetActiveFriendlyFireFactor();
// TODO: Get previous default
friendlyFireFactor.FloatValue = friendlyFireFactor.FloatValue - ((newCount - 4) * cvFFDecreaseRate.FloatValue);
if(friendlyFireFactor.FloatValue < 0.0) {
friendlyFireFactor.FloatValue = 0.01;
playerData[client].state = State_Active;
if(L4D_IsFirstMapInScenario() && !firstGiven) {
//Check if all clients are ready, and survivor count is > 4.
if(AreAllClientsReady()) {
abmExtraCount = GetRealSurvivorsCount();
if(abmExtraCount > 4) {
PrintToServer("[EPI] First chapter kits given");
firstGiven = true;
//Set the initial value ofhMinPlayers
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = abmExtraCount;
}
PopulateItems();
CreateTimer(1.0, Timer_GiveKits);
}
// If 5 survivors, then set them up, TP them.
if(newCount > 4) {
CreateTimer(0.1, Timer_SetupNewClient, userid);
if(firstSaferoomDoorEntity > 0 && IsValidEntity(firstSaferoomDoorEntity)) {
UnlockDoor(firstSaferoomDoorEntity, 2);
}
}
} else {
// New client has connected, not on first map.
// TODO: Check if Timer_UpdateMinPlayers is needed, or if this works:
// Never decrease abmExtraCount
int newCount = GetRealSurvivorsCount();
if(newCount > abmExtraCount && abmExtraCount > 4) {
abmExtraCount = newCount;
hMinPlayers.IntValue = abmExtraCount;
ConVar friendlyFireFactor = GetActiveFriendlyFireFactor();
// TODO: Get previous default
friendlyFireFactor.FloatValue = friendlyFireFactor.FloatValue - ((newCount - 4) * cvFFDecreaseRate.FloatValue);
if(friendlyFireFactor.FloatValue < 0.0) {
friendlyFireFactor.FloatValue = 0.01;
}
}
// If 5 survivors, then set them up, TP them.
if(newCount > 4) {
CreateTimer(0.1, Timer_SetupNewClient, userid);
}
}
}
return Plugin_Continue;
}
public Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
if(StrEqual(gamemode, "hideandseek")) return Plugin_Continue;
public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
if(!StrEqual(gamemode, "coop") && !StrEqual(gamemode, "realism")) return;
int user = event.GetInt("userid");
int client = GetClientOfUserId(user);
if(GetClientTeam(client) == 2) {
@ -637,13 +670,15 @@ public Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadca
SDKHook(client, SDKHook_WeaponEquip, Event_Pickup);
}
int count = GetRealSurvivorsCount();
int threshold = hEPIHudState.IntValue == 1 ? 5 : 0;
int threshold = 0;
if(hEPIHudState.IntValue == 1) {
threshold = L4D2_GetSurvivorSetMap() == 2 ? 4 : 5;
}
if(hEPIHudState.IntValue > 0 && count > threshold && updateHudTimer == null) {
PrintToServer("[EPI] Creating new hud timer (player spawn)");
updateHudTimer = CreateTimer(EXTRA_PLAYER_HUD_UPDATE_INTERVAL, Timer_UpdateHud, _, TIMER_REPEAT);
}
UpdatePlayerInventory(client);
return Plugin_Continue;
}
@ -655,21 +690,19 @@ public Action Timer_CheckInventory(Handle h, int client) {
return Plugin_Handled;
}
public void Event_PlayerTeam(Event event, const char[] name, bool dontBroadcast) {
if(event.GetBool("disconnect")) {
int userid = event.GetInt("userid");
int client = GetClientOfUserId(userid);
int team = event.GetInt("team");
if(client > 0 && team == 2) { //TODO: re-add && !event.GetBool("isbot")
SaveInventory(client);
PrintToServer("debug: Player %N (index %d, uid %d) now pending empty", client, client, userid);
playerData[client].state = State_PendingEmpty;
/*DataPack pack;
CreateDataTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, pack);
pack.WriteCell(userid);
pack.WriteCell(client);*/
CreateTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, client);
}
public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
int userid = event.GetInt("userid");
int client = GetClientOfUserId(userid);
if(client > 0 && IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) == 2) { //TODO: re-add && !event.GetBool("isbot")
playerData[client].hasJoined = false;
SaveInventory(client);
PrintToServer("debug: Player %N (index %d, uid %d) now pending empty", client, client, userid);
playerData[client].state = State_PendingEmpty;
/*DataPack pack;
CreateDataTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, pack);
pack.WriteCell(userid);
pack.WriteCell(client);*/
CreateTimer(cvDropDisconnectTime.FloatValue, Timer_DropSurvivor, client);
}
}
@ -677,8 +710,8 @@ public Action Timer_DropSurvivor(Handle h, int client) {
if(playerData[client].state == State_PendingEmpty) {
playerData[client].state = State_Empty;
if(hMinPlayers != null) {
PrintToServer("[EPI] Dropping survivor %d. hMinPlayers-pre:%d", client, hMinPlayers.IntValue);
PrintToConsoleAll("[EPI] Dropping survivor %d. hMinPlayers-pre:%d", client, hMinPlayers.IntValue);
PrintToServer("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount);
PrintToConsoleAll("[EPI] Dropping survivor %d. hMinPlayers-pre:%d abmCount=%d", client, hMinPlayers.IntValue, abmExtraCount);
hMinPlayers.IntValue = --abmExtraCount;
if(hMinPlayers.IntValue < 4) {
hMinPlayers.IntValue = 4;
@ -712,12 +745,11 @@ public Action Timer_DropSurvivor(Handle h, int client) {
/////// Events
/////////////////////////////////////////
public Action Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) {
UpdatePlayerInventory(client);
}
return Plugin_Continue;
}
@ -765,19 +797,34 @@ public Action Timer_SetupNewClient(Handle h, int userid) {
char weaponName[64];
ArrayList tier2Weapons = new ArrayList(ByteCountToCells(32));
ArrayList tier1Weapons = new ArrayList(ByteCountToCells(32));
ArrayList secondaryWeapons = new ArrayList(ByteCountToCells(32));
for(int i = 1; i <= MaxClients; i++) {
if(i != client && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) {
int wpn = GetPlayerWeaponSlot(i, 0);
if(wpn > 0) {
GetEdictClassname(wpn, weaponName, sizeof(weaponName));
for(int j = 0; j < TIER2_WEAPON_COUNT; j++) {
if(StrEqual(TIER2_WEAPONS[j], weaponName)) {
tier2Weapons.PushString(weaponName);
break;
if(!StrEqual(weaponName, "weapon_grenade_launcher") && !StrEqual(weaponName, "weapon_rifle_m60")) {
for(int j = 0; j < TIER2_WEAPON_COUNT; j++) {
if(StrEqual(TIER2_WEAPONS[j], weaponName)) {
tier2Weapons.PushString(weaponName);
break;
}
}
tier1Weapons.PushString(weaponName);
// playerWeapons.PushString(weaponName);
}
}
wpn = GetPlayerWeaponSlot(i, 1);
if(wpn > 0) {
GetEdictClassname(wpn, weaponName, sizeof(weaponName));
if(StrEqual(weaponName, "weapon_melee")) {
// Get melee name, won't have weapon_ prefix
GetEntPropString(wpn, Prop_Data, "m_strMapSetScriptName", weaponName, sizeof(weaponName));
}
secondaryWeapons.PushString(weaponName);
}
float intensity = L4D_GetPlayerIntensity(i);
if(intensity < lowestIntensity || lowestClient == -1) {
@ -787,49 +834,70 @@ public Action Timer_SetupNewClient(Handle h, int userid) {
}
}
// Give player any random t2 weapon, if no one has one, fallback to t1, if no one has one, give them a magnum
if(tier2Weapons.Length > 0) {
tier2Weapons.GetString(GetRandomInt(0, tier2Weapons.Length - 1), weaponName, sizeof(weaponName));
// Format(weaponName, sizeof(weaponName), "weapon_%s", weaponName);
PrintToServer("[EPI/debug] Giving new client (%N) tier 2: %s", client, weaponName);
} else {
Format(weaponName, sizeof(weaponName), "weapon_%s", TIER1_WEAPONS[GetRandomInt(0, TIER1_WEAPON_COUNT - 1)]);
GiveWeapon(client, weaponName, 3.0, 0);
} else if(tier1Weapons.Length > 0) {
// Format(weaponName, sizeof(weaponName), "weapon_%s", TIER1_WEAPONS[GetRandomInt(0, TIER1_WEAPON_COUNT - 1)]);
tier1Weapons.GetString(GetRandomInt(0, tier1Weapons.Length - 1), weaponName, sizeof(weaponName));
PrintToServer("[EPI/debug] Giving new client (%N) tier 1: %s", client, weaponName);
GiveWeapon(client, weaponName, 3.0, 0);
}
PrintToServer("%N: Giving random secondary / %d", secondaryWeapons.Length, client);
PrintToConsoleAll("%N: Giving random secondary / %d", secondaryWeapons.Length, client);
if(secondaryWeapons.Length > 0) {
secondaryWeapons.GetString(GetRandomInt(0, secondaryWeapons.Length - 1), weaponName, sizeof(weaponName));
GiveWeapon(client, weaponName, 6.5, 1);
}
if(lowestClient > 0) {
float pos[3];
GetClientAbsOrigin(lowestClient, pos);
TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR);
}
delete tier2Weapons;
float pos[3];
if(L4D2_IsValidWeapon(weaponName)) {
int wpn = CreateEntityByName(weaponName);
DispatchSpawn(wpn);
SetEntProp(wpn, Prop_Send, "m_iClip1", L4D2_GetIntWeaponAttribute(weaponName, L4D2IWA_ClipSize));
L4D_SetReserveAmmo(client, wpn, L4D2_GetIntWeaponAttribute(weaponName, L4D2IWA_Bullets));
GetClientAbsOrigin(client, pos);
TeleportEntity(wpn, pos, NULL_VECTOR, NULL_VECTOR);
DataPack pack;
CreateDataTimer(0.2, Timer_GiveWeapon, pack);
pack.WriteCell(userid);
pack.WriteCell(wpn);
} else {
LogError("EPI: INVALID WEAPON: %s for %N", weaponName, client);
}
delete tier2Weapons;
delete tier1Weapons;
delete secondaryWeapons;
return Plugin_Handled;
}
void GiveWeapon(int client, const char[] weaponName, float delay = 0.3, int clearSlot = -1) {
if(clearSlot > 0) {
int oldWpn = GetPlayerWeaponSlot(client, clearSlot);
if(oldWpn != -1) {
AcceptEntityInput(oldWpn, "Kill");
}
}
PrintToServer("%N: Giving %s", client, weaponName);
PrintToConsoleAll("%N: Giving %s", client, weaponName);
DataPack pack;
CreateDataTimer(delay, Timer_GiveWeapon, pack);
pack.WriteCell(GetClientUserId(client));
pack.WriteString(weaponName);
}
public Action Timer_GiveWeapon(Handle h, DataPack pack) {
pack.Reset();
int userid = pack.ReadCell();
int wpn = pack.ReadCell();
int client = GetClientOfUserId(userid);
if(client > 0) {
EquipPlayerWeapon(client, wpn);
char wpnName[32];
pack.ReadString(wpnName, sizeof(wpnName));
CheatCommand(client, "give", wpnName, "");
}
return Plugin_Handled;
}
public Action Timer_RemoveInvincibility(Handle h, int client) {
SDKUnhook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken);
public Action Timer_RemoveInvincibility(Handle h, int userid) {
int client = GetClientOfUserId(userid);
if(client > 0) {
SetEntProp(client, Prop_Send, "m_iHealth", 100);
SDKUnhook(client, SDKHook_OnTakeDamage, OnInvincibleDamageTaken);
}
return Plugin_Handled;
}
public Action OnInvincibleDamageTaken(int victim, int& attacker, int& inflictor, float& damage, int& damagetype, int& weapon, float damageForce[3], float damagePosition[3]) {
@ -875,13 +943,13 @@ public void OnMapStart() {
GiveStartingKits();
}
isFailureRound = false;
}else if(!L4D_IsFirstMapInScenario()) {
} else if(!L4D_IsFirstMapInScenario()) {
//Re-set value incase it reset.
//hMinPlayers.IntValue = abmExtraCount;
currentChapter++;
}else if(L4D_IsMissionFinalMap()) {
} else if(L4D_IsMissionFinalMap()) {
//Add extra kits for finales
static char curMap[64];
char curMap[64];
GetCurrentMap(curMap, sizeof(curMap));
if(StrEqual(curMap, "c4m5_milltown_escape")) {
@ -932,6 +1000,12 @@ public void OnMapStart() {
L4D2_RunScript(HUD_SCRIPT_CLEAR);
}
public void OnConfigsExecuted() {
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = abmExtraCount;
}
}
public void OnMapEnd() {
for(int i = 0; i < ammoPacks.Length; i++) {
@ -947,7 +1021,6 @@ public void OnMapEnd() {
ammoPacks.Clear();
playersLoadedIn = 0;
abmExtraCount = 4;
PrintToServer("[EPI] Stopping timer for map ending");
delete updateHudTimer;
}
@ -968,7 +1041,7 @@ public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, i
int extraPlayers = abmExtraCount - 4;
float averageTeamHP = GetAverageHP();
if(averageTeamHP <= 30.0) extraPlayers += (extraPlayers / 2); //if perm. health < 30, give an extra 4 on top of the extra
else if(averageTeamHP <= 50.0) extraPlayers = (extraPlayers / 3); //if the team's average health is less than 50 (permament) then give another
else if(averageTeamHP <= 50.0) extraPlayers += (extraPlayers / 3); //if the team's average health is less than 50 (permament) then give another
//Chance to get an extra kit (might need to be nerfed or restricted to > 50 HP)
if(GetRandomFloat() < 0.3 && averageTeamHP <= 80.0) ++extraPlayers;
@ -1217,11 +1290,12 @@ public Action Timer_UpdateHud(Handle h) {
Format(players, sizeof(players), "%s%s %s\\n", players, prefix, data);
}
}
if(hEPIHudState.IntValue == 3) {
if(hEPIHudState.IntValue != 3) {
RunVScriptLong(HUD_SCRIPT_DATA, players);
} else {
PrintHintTextToAll("DEBUG HUD TIMER");
RunVScriptLong(HUD_SCRIPT_DEBUG, players);
} else
RunVScriptLong(HUD_SCRIPT_DATA, players);
}
return Plugin_Continue;
}
@ -1267,6 +1341,12 @@ public void PopulateItems() {
int spawner, count;
for(int i = 0; i < sizeof(cabinets); i++) {
if(cabinets[i].id == 0) break;
GetEntityClassname(cabinets[i].id, classname, sizeof(classname));
if(!StrEqual(classname, "prop_health_cabinet")) {
PrintToServer("Cabinet %d (ent %d) is not a valid entity, is %s. Skipping", i, cabinets[i].id, classname);
cabinets[i].id = 0;
continue;
}
int spawnCount = GetEntProp(cabinets[i].id, Prop_Data, "m_pillCount");
int extraAmount = RoundToCeil(float(abmExtraCount) * (float(spawnCount)/4.0) - spawnCount);
bool hasASpawner;
@ -1275,6 +1355,7 @@ public void PopulateItems() {
for(int block = 0; block < CABINET_ITEM_BLOCKS; block++) {
spawner = cabinets[i].items[block];
if(spawner > 0) {
if(!HasEntProp(spawner, Prop_Data, "m_itemCount")) continue;
hasASpawner = true;
count = GetEntProp(spawner, Prop_Data, "m_itemCount") + 1;
SetEntProp(spawner, Prop_Data, "m_itemCount", count);

View file

@ -84,7 +84,7 @@ public void OnPluginStart() {
hBotReverseFFDefend.AddChangeHook(Change_BotDefend);
RegAdminCmd("sm_ftl", Command_ListTheTrolls, ADMFLAG_GENERIC, "Lists all the trolls currently ingame.");
RegAdminCmd("sm_ftm", Command_ListModes, ADMFLAG_KICK, "Lists all the troll modes and their description");
RegAdminCmd("sm_ftm", Command_ListModes, ADMFLAG_GENERIC, "Lists all the troll modes and their description");
RegAdminCmd("sm_ftr", Command_ResetUser, ADMFLAG_GENERIC, "Resets user of any troll effects.");
RegAdminCmd("sm_fta", Command_ApplyUser, ADMFLAG_KICK, "Apply a troll mod to a player, or shows menu if no parameters.");
RegAdminCmd("sm_ftas", Command_ApplyUserSilent, ADMFLAG_ROOT, "Apply a troll mod to a player, or shows menu if no parameters.");

1252
scripting/l4d2_hats.sp Normal file

File diff suppressed because it is too large Load diff

View file

@ -67,6 +67,8 @@ public void OnPluginStart() {
SetFailState("This plugin is for L4D2 only.");
}
HookEvent("game_start", OnGameStart);
hPercentTotal = CreateConVar("l4d2_population_global_chance", "1.0", "The % chance that any the below chances occur.\n0.0 = NEVER, 1.0: ALWAYS");
hPercentClown = CreateConVar("l4d2_population_clowns", "0.0", "The % chance that a common spawns as a clown.\n0.0 = OFF, 1.0 = ALWAYS", FCVAR_NONE, true, 0.0, true, 1.0);
hPercentMud = CreateConVar("l4d2_population_mud", "0.0", "The % chance that a common spawns as a mud zombie.\n0.0 = OFF, 1.0 = ALWAYS", FCVAR_NONE, true, 0.0, true, 1.0);
@ -87,6 +89,18 @@ public void OnPluginStart() {
//AutoExecConfig(true, "l4d2_population_control");
}
public void OnGameStart(Event event, const char[] name, bool dontBroadcast) {
hPercentTotal.FloatValue = 1.0;
hPercentClown.FloatValue = 0.0;
hPercentMud.FloatValue = 0.0;
hPercentCeda.FloatValue = 0.0;
hPercentWorker.FloatValue = 0.0;
hPercentRiot.FloatValue = 0.0;
hPercentJimmy.FloatValue = 0.0;
hTotalZombies.FloatValue = 0.0;
}
public void OnMapStart() {
for(int i = 0; i < COMMON_MODELS_COUNT; i++) {
PrecacheModel(INFECTED_MODELS[i], true);

View file

@ -37,7 +37,7 @@ public void OnPluginStart() {
SetFailState("This plugin is for L4D/L4D2 only.");
}
clients = new ArrayList(3);
clients = new ArrayList(4);
HookEvent("player_hurt", Event_PlayerHurt);
HookEvent("tank_spawn", Event_TankSpawn);
@ -93,8 +93,8 @@ public Action L4D2_OnChooseVictim(int attacker, int &curTarget) {
clients.SortCustom(Sort_TankTargetter);
curTarget = clients.Get(0);
tankChosenVictim[attacker] = curTarget;
targettingTank[curTarget] = attacker;
// tankChosenVictim[attacker] = curTarget;
// targettingTank[curTarget] = attacker;
PrintToConsoleAll("[TankPriority] Player Selected to target: %N", curTarget);
//TODO: Possibly clear totalTankDamage
return Plugin_Changed;

View file

@ -160,7 +160,7 @@ void SetupTurret(int turret, float time = 0.0) {
thinkTimer = CreateTimer(0.1, Timer_Think, _, TIMER_REPEAT);
}
// Clamp to 0 -> _TURRET_PHASE_TICKS - 1
turretPhaseOffset[turret] = turretIds.Length % (_TURRET_PHASE_TICKS - 1);
turretPhaseOffset[turret] = (turretIds.Length + 1) % (_TURRET_PHASE_TICKS - 1);
turretIds.Push(turret);
}
Action Timer_ActivateTurret(Handle h, int turret) {
@ -356,12 +356,15 @@ public Action Timer_Think(Handle h) {
// Keep targetting if can view
target = EntRefToEntIndex(turretActiveEntity[entity]);
if(target > 0 && IsValidEntity(target)) {
bool ragdoll = GetEntProp(target, Prop_Data, "m_bClientSideRagdoll") == 1;
if(!ragdoll && CanSeeEntity(pos, target)) {
if(target <= MaxClients) {
if(IsPlayerAlive(target) && GetEntProp(target, Prop_Data, "m_bClientSideRagdoll") == 0 && CanSeeEntity(pos, target)) {
FireTurretAuto(pos, target, turretDamage[entity]);
continue;
}
} else if(CanSeeEntity(pos, target)) {
FireTurretAuto(pos, target, turretDamage[entity]);
continue;
}
entityActiveTurret[target] = 0;
}
DeactivateTurret(entity);
}
@ -381,9 +384,9 @@ public Action Timer_Think(Handle h) {
CreateTimer(1.2, Timer_KillRock, EntIndexToEntRef(target));
damage = 1000.0;
}
if(target == -1) target = FindNearestVisibleClient(TEAM_SPECIALS, pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED);
if(target <= 0) target = FindNearestVisibleClient(TEAM_SPECIALS, pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED);
}
if(target == -1) target = FindNearestVisibleEntity("infected", pos, TURRET_MAX_RANGE_INFECTED_OPTIMIZED, entity);
if(target <= 0) target = FindNearestVisibleEntity("infected", pos, TURRET_MAX_RANGE_INFECTED_OPTIMIZED, entity);
if(target > 0) {
turretDamage[entity] = damage;
entityActiveTurret[target] = entity;
@ -537,7 +540,7 @@ stock int FindNearestVisibleClient(int team, const float origin[3], float maxRan
if(distance <= closestDist || client == -1) {
if(CanSeePoint(origin, pos)) {
// Priority: Pinned survivors
if(L4D_GetPinnedSurvivor(i)) {
if(L4D_GetPinnedSurvivor(i) > 0) {
return i;
}
client = i;
@ -553,6 +556,7 @@ stock int FindNearestVisibleEntity(const char[] classname, const float origin[3]
int entity = INVALID_ENT_REFERENCE;
static float pos[3];
while ((entity = FindEntityByClassname(entity, classname)) != INVALID_ENT_REFERENCE) {
// Skip entity, it's already being targetted
if(entityActiveTurret[entity] > 0) continue;
bool ragdoll = GetEntProp(entity, Prop_Data, "m_bClientSideRagdoll") == 1;
if(ragdoll) continue;
@ -568,7 +572,7 @@ stock int FindNearestVisibleEntity(const char[] classname, const float origin[3]
}
stock bool CanSeePoint(const float origin[3], const float point[3]) {
TR_TraceRay(origin, point, MASK_SOLID, RayType_EndPoint);
TR_TraceRay(origin, point, MASK_SHOT, RayType_EndPoint);
return !TR_DidHit(); // Can see point if no collisions
}
@ -576,7 +580,7 @@ stock bool CanSeePoint(const float origin[3], const float point[3]) {
stock bool CanSeeEntity(const float origin[3], int entity) {
static float point[3];
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", point);
TR_TraceRayFilter(origin, point, MASK_ALL, RayType_EndPoint, Filter_CanSeeEntity, entity);
TR_TraceRayFilter(origin, point, MASK_SHOT, RayType_EndPoint, Filter_CanSeeEntity, entity);
return TR_GetEntityIndex() == entity; // Can see point if no collisions
}

View file

@ -9,8 +9,8 @@
#include <sdktools>
//#include <sdkhooks>
#define MIN_TIME_BETWEEN_NAME_CHANGES 10000
#define MAX_NAME_COUNT 3
#define MIN_TIME_BETWEEN_NAME_CHANGES 10000 // In seconds
#define MAX_NAME_COUNT 3 // How many changes max within a MIN_TIME_BETWEEN_NAME_CHANGES
public Plugin myinfo =
{

View file

@ -29,6 +29,7 @@ static char query[1024];
static char reason[256];
static int WaitingForNotePlayer;
static char menuNoteTarget[32];
static char menuNoteTargetName[32];
enum struct PlayerData {
char id[32];
@ -52,6 +53,7 @@ public void OnPluginStart() {
HookEvent("player_disconnect", Event_PlayerDisconnect);
HookEvent("player_first_spawn", Event_FirstSpawn);
RegConsoleCmd("sm_rep", Command_RepPlayer, "+rep or -rep a player");
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");
@ -69,6 +71,136 @@ public void OnPluginStart() {
// PrintToServer("");
}
void ShowRepMenu(int client, int targetUserid) {
Menu menu = new Menu(RepFinalHandler);
menu.SetTitle("Choose a rating");
char id[8];
Format(id, sizeof(id), "%d|1", targetUserid);
menu.AddItem(id, "+Rep");
Format(id, sizeof(id), "%d|1", targetUserid);
menu.AddItem(id, "-Rep");
menu.Display(client, 0);
}
public int RepPlayerHandler(Menu menu, MenuAction action, int param1, int param2) {
/* If an option was selected, tell the client about the item. */
if (action == MenuAction_Select) {
static char info[4];
menu.GetItem(param2, info, sizeof(info));
int targetUserid = StringToInt(info);
int target = GetClientOfUserId(targetUserid);
if(target == 0) {
ReplyToCommand(param1, "Could not acquire player");
return 0;
}
ShowRepMenu(param1, targetUserid);
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
public int RepFinalHandler(Menu menu, MenuAction action, int param1, int param2) {
/* If an option was selected, tell the client about the item. */
if (action == MenuAction_Select) {
char info[8];
menu.GetItem(param2, info, sizeof(info));
char str[2][8];
ExplodeString(info, "|", str, 2, 8, false);
int targetUserid = StringToInt(str[0]);
int target = GetClientOfUserId(targetUserid);
int rep = StringToInt(str[1]);
if(target == 0) {
ReplyToCommand(param1, "Could not acquire player");
return 0;
}
ApplyRep(param1, target, rep);
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
public Action Command_RepPlayer(int client, int args) {
if(client == 0) {
ReplyToCommand(client, "You must be a player to use this command.");
return Plugin_Handled;
}
if(args == 0) {
Menu menu = new Menu(RepPlayerHandler);
menu.SetTitle("Choose a player to rep");
char id[8], display[64];
// int clientTeam = GetClientTeam(client);
for(int i = 1; i <= MaxClients; i++) {
if(i != client && IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) {
Format(id, sizeof(id), "%d", GetClientUserId(i));
Format(display, sizeof(display), "%N", i);
menu.AddItem(id, display);
}
}
menu.Display(client, 0);
return Plugin_Handled;
} else if(args > 0) {
char arg1[32];
GetCmdArg(1, arg1, sizeof(arg1));
char target_name[MAX_TARGET_LENGTH];
int target_list[1], target_count;
bool tn_is_ml;
if ((target_count = ProcessTargetString(
arg1,
client,
target_list,
1,
COMMAND_FILTER_ALIVE,
target_name,
sizeof(target_name),
tn_is_ml)) <= 0)
{
/* This function replies to the admin with a failure message */
ReplyToTargetError(client, target_count);
return Plugin_Handled;
}
if (args == 1) {
ShowRepMenu(client, GetClientUserId(target_list[0]));
} else {
char arg2[2];
GetCmdArg(2, arg2, sizeof(arg2));
int rep;
if(arg2[0] == 'y' || arg2[0] == '+' || arg2[0] == 'p') {
rep = 1;
} else if(arg2[0] == 'n' || arg2[0] == '-' || arg2[0] == 's') {
rep = -1;
} else {
ReplyToCommand(client, "Invalid rep value: Use (y/+/p) for +rep or (n/-/s) for -rep");
return Plugin_Handled;
}
ApplyRep(client, target_list[0], rep);
}
}
return Plugin_Handled;
}
void ApplyRep(int client, int target, int rep) {
char[] msg = "+rep";
if(rep == -1) msg[0] = '-';
LogAction(client, target, "\"%L\" %srep \"%L\"", client, msg, target);
if(rep > 0)
CShowActivity(client, "{green}+rep %N", target);
else
CShowActivity(client, "{yellow}-rep %N", target);
char activatorId[32], targetId[32];
GetClientAuthId(client, AuthId_Steam2, activatorId, sizeof(activatorId));
GetClientAuthId(target, AuthId_Steam2, targetId, sizeof(targetId));
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", targetId, activatorId, msg);
DB.Query(DB_AddNote, query);
}
public Action Command_AddNoteDisconnected(int client, int args) {
if(lastPlayers.Length == 0) {
ReplyToCommand(client, "No disconnected players recorded.");
@ -87,8 +219,9 @@ public Action Command_AddNoteDisconnected(int client, int args) {
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 in the chat for %s: (or 'cancel' to cancel)", menuNoteTarget);
int style;
menu.GetItem(item, menuNoteTarget, sizeof(menuNoteTarget), style, menuNoteTargetName, sizeof(menuNoteTargetName));
CPrintToChat(client, "Enter a note in the chat for {yellow}%s {olive}(%s){default}: (or 'cancel' to cancel)", menuNoteTargetName, menuNoteTarget);
WaitingForNotePlayer = client;
} else if (action == MenuAction_End)
delete menu;
@ -101,7 +234,7 @@ public Action OnClientSayCommand(int client, const char[] command, const char[]
if(StrEqual(sArgs, "cancel", false)) {
PrintToChat(client, "Note cancelled.");
} else {
int size = strlen(sArgs);
int size = strlen(sArgs) + 1;
char[] sArgsTrimmed = new char[size];
strcopy(sArgsTrimmed, size, sArgs);
TrimString(sArgsTrimmed);
@ -109,9 +242,9 @@ public Action OnClientSayCommand(int client, const char[] command, const char[]
GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer));
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgsTrimmed);
DB.Query(DB_AddNote, query);
LogAction(client, -1, "\"%L\" added note for \"%s\": \"%s\"", client, menuNoteTarget, sArgsTrimmed);
LogAction(client, -1, "\"%L\" added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgsTrimmed);
Format(buffer, sizeof(buffer), "%N: ", client);
CShowActivity2(client, buffer, "added a note for {green}%s: {default}\"%s\"", menuNoteTarget, sArgsTrimmed);
CShowActivity2(client, buffer, "added a note for {green}%s: {default}\"%s\"", menuNoteTargetName, sArgsTrimmed);
}
return Plugin_Stop;
}
@ -126,6 +259,19 @@ public Action Command_AddNote(int client, int args) {
GetCmdArg(1, target_name, sizeof(target_name));
GetCmdArg(2, reason, sizeof(reason));
TrimString(reason);
if(args > 2) {
// Correct commands that don't wrap message in quotes
char buffer[64];
for(int i = 3; i <= args; i++) {
GetCmdArg(i, buffer, sizeof(buffer));
Format(reason, sizeof(reason), "%s %s", reason, buffer);
}
}
if(reason[0] == '\0') {
ReplyToCommand(client, "Can't create an empty note");
return Plugin_Handled;
}
int target_list[1], target_count;
bool tn_is_ml;
@ -139,7 +285,11 @@ public Action Command_AddNote(int client, int args) {
sizeof(target_name),
tn_is_ml)) <= 0
) {
ReplyToTargetError(client, target_count);
if(target_count == COMMAND_TARGET_NONE) {
ReplyToCommand(client, "Could not find any online user. If user has disconnected, use sm_notedisconnected");
} else {
ReplyToTargetError(client, target_count);
}
return Plugin_Handled;
}
if(args == 1) {
@ -261,19 +411,29 @@ public void DB_FindNotes(Database db, DBResultSet results, const char[] error, a
static char noteCreator[32];
CPrintChatToAdmins("{yellow}> Notes for %N", client);
int actions = 0;
int repP = 0, repN = 0;
while(results.FetchRow()) {
results.FetchString(0, reason, sizeof(reason));
results.FetchString(1, noteCreator, sizeof(noteCreator));
TrimString(reason);
if(ParseActions(data, reason)) {
actions++;
} else if((reason[0] == '+' || reason[0] == '-') && reason[1] == 'r' && reason[2] == 'e' && reason[3] == 'p') {
if(reason[0] == '+') {
repP++;
} else {
repN--;
}
} else {
CPrintChatToAdmins(" {olive}%s: {default}%s", noteCreator, reason);
}
}
if(actions > 0) {
CPrintChatToAdmins(" {olive}%d Auto Actions Applied", actions);
CPrintChatToAdmins(" > {olive}%d Auto Actions Applied", actions);
}
if(repP > 0 || repN > 0) {
CPrintChatToAdmins(" > {olive}%d +rep\t{yellow}-rep", repP, repN);
}
}
}