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

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,48 +739,65 @@ 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;
}
/**
* @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);
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);
}
} 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(firstSaferoomDoorEntity > 0 && IsValidEntity(firstSaferoomDoorEntity)) {
UnlockDoor(firstSaferoomDoorEntity, 2);
}
// If 5 survivors, then set them up, TP them.
if(newCount > 4) {
CreateTimer(0.1, Timer_SetupNewClient, userid);
}
} 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);
}
}
}