diff --git a/plugins/GrabEnt.smx b/plugins/GrabEnt.smx new file mode 100644 index 0000000..dccadec Binary files /dev/null and b/plugins/GrabEnt.smx differ diff --git a/plugins/L4D2Tools.smx b/plugins/L4D2Tools.smx index e300b3b..d2b568c 100644 Binary files a/plugins/L4D2Tools.smx and b/plugins/L4D2Tools.smx differ diff --git a/plugins/activitymonitor.smx b/plugins/activitymonitor.smx index 01f49a6..33dbb8f 100644 Binary files a/plugins/activitymonitor.smx and b/plugins/activitymonitor.smx differ diff --git a/plugins/globalbans.smx b/plugins/globalbans.smx index acebf99..c6cb1cb 100644 Binary files a/plugins/globalbans.smx and b/plugins/globalbans.smx differ diff --git a/plugins/l4d2_TKStopper.smx b/plugins/l4d2_TKStopper.smx index 2f461ce..f13042a 100644 Binary files a/plugins/l4d2_TKStopper.smx and b/plugins/l4d2_TKStopper.smx differ diff --git a/plugins/l4d2_autobotcrown.smx b/plugins/l4d2_autobotcrown.smx index 30fcc71..0d41327 100644 Binary files a/plugins/l4d2_autobotcrown.smx and b/plugins/l4d2_autobotcrown.smx differ diff --git a/plugins/l4d2_autorestart.smx b/plugins/l4d2_autorestart.smx index 72f8589..40fe024 100644 Binary files a/plugins/l4d2_autorestart.smx and b/plugins/l4d2_autorestart.smx differ diff --git a/plugins/l4d2_crescendo_control.smx b/plugins/l4d2_crescendo_control.smx index f095ad9..c01d57d 100644 Binary files a/plugins/l4d2_crescendo_control.smx and b/plugins/l4d2_crescendo_control.smx differ diff --git a/plugins/l4d2_detections.smx b/plugins/l4d2_detections.smx index 8864c78..29e8fae 100644 Binary files a/plugins/l4d2_detections.smx and b/plugins/l4d2_detections.smx differ diff --git a/plugins/l4d2_extraplayeritems.smx b/plugins/l4d2_extraplayeritems.smx index 17a7454..ccbff08 100644 Binary files a/plugins/l4d2_extraplayeritems.smx and b/plugins/l4d2_extraplayeritems.smx differ diff --git a/plugins/l4d2_feedthetrolls.smx b/plugins/l4d2_feedthetrolls.smx index 2c01d40..377e097 100644 Binary files a/plugins/l4d2_feedthetrolls.smx and b/plugins/l4d2_feedthetrolls.smx differ diff --git a/plugins/l4d2_hats.smx b/plugins/l4d2_hats.smx new file mode 100644 index 0000000..5d32b21 Binary files /dev/null and b/plugins/l4d2_hats.smx differ diff --git a/plugins/l4d2_population_control.smx b/plugins/l4d2_population_control.smx index f4c947b..d221b5b 100644 Binary files a/plugins/l4d2_population_control.smx and b/plugins/l4d2_population_control.smx differ diff --git a/plugins/l4d2_tank_priority.smx b/plugins/l4d2_tank_priority.smx index 01b8974..28a3b4d 100644 Binary files a/plugins/l4d2_tank_priority.smx and b/plugins/l4d2_tank_priority.smx differ diff --git a/plugins/l4d2_turret.smx b/plugins/l4d2_turret.smx index 57af84f..0d9f62a 100644 Binary files a/plugins/l4d2_turret.smx and b/plugins/l4d2_turret.smx differ diff --git a/plugins/left4dhooks.smx b/plugins/left4dhooks.smx index 6ea2b60..9d923b1 100644 Binary files a/plugins/left4dhooks.smx and b/plugins/left4dhooks.smx differ diff --git a/plugins/sm_namespamblock.smx b/plugins/sm_namespamblock.smx index 3f6bd55..bb0d236 100644 Binary files a/plugins/sm_namespamblock.smx and b/plugins/sm_namespamblock.smx differ diff --git a/plugins/sm_player_notes.smx b/plugins/sm_player_notes.smx index ec8098c..051d163 100644 Binary files a/plugins/sm_player_notes.smx and b/plugins/sm_player_notes.smx differ diff --git a/scripting/GrabEnt.sp b/scripting/GrabEnt.sp new file mode 100644 index 0000000..802091d --- /dev/null +++ b/scripting/GrabEnt.sp @@ -0,0 +1,533 @@ +#define DEBUG + +#define PLUGIN_AUTHOR "Stugger" +#define PLUGIN_VERSION "2.2" + +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/scripting/L4D2Tools.sp b/scripting/L4D2Tools.sp index 798c73d..22ee96f 100644 --- a/scripting/L4D2Tools.sp +++ b/scripting/L4D2Tools.sp @@ -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 [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; } } diff --git a/scripting/activitymonitor.sp b/scripting/activitymonitor.sp index ebe79ce..f44f618 100644 --- a/scripting/activitymonitor.sp +++ b/scripting/activitymonitor.sp @@ -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)); diff --git a/scripting/globalbans.sp b/scripting/globalbans.sp index 06038ba..e0c5a98 100644 --- a/scripting/globalbans.sp +++ b/scripting/globalbans.sp @@ -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); } diff --git a/scripting/include/feedthetrolls/base.inc b/scripting/include/feedthetrolls/base.inc index 87a7075..4e4dc25 100644 --- a/scripting/include/feedthetrolls/base.inc +++ b/scripting/include/feedthetrolls/base.inc @@ -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) diff --git a/scripting/include/feedthetrolls/combos.inc b/scripting/include/feedthetrolls/combos.inc index 18f0035..7b0b318 100644 --- a/scripting/include/feedthetrolls/combos.inc +++ b/scripting/include/feedthetrolls/combos.inc @@ -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"); diff --git a/scripting/include/feedthetrolls/commands.inc b/scripting/include/feedthetrolls/commands.inc index 3cc0e60..08dd361 100644 --- a/scripting/include/feedthetrolls/commands.inc +++ b/scripting/include/feedthetrolls/commands.inc @@ -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; } diff --git a/scripting/include/feedthetrolls/events.inc b/scripting/include/feedthetrolls/events.inc index 0e14b41..6979bea 100644 --- a/scripting/include/feedthetrolls/events.inc +++ b/scripting/include/feedthetrolls/events.inc @@ -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; diff --git a/scripting/include/feedthetrolls/menus.inc b/scripting/include/feedthetrolls/menus.inc index 3da38ba..b2f36c2 100644 --- a/scripting/include/feedthetrolls/menus.inc +++ b/scripting/include/feedthetrolls/menus.inc @@ -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); } } diff --git a/scripting/include/feedthetrolls/specials.inc b/scripting/include/feedthetrolls/specials.inc index cc87dde..1115c77 100644 --- a/scripting/include/feedthetrolls/specials.inc +++ b/scripting/include/feedthetrolls/specials.inc @@ -101,14 +101,21 @@ bool ProcessSpecialQueue() { CheatCommand(target, "z_spawn_old", SPECIAL_NAMES[view_as(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(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(i + 1); diff --git a/scripting/include/feedthetrolls/trolls.inc b/scripting/include/feedthetrolls/trolls.inc index e7bc0fa..2315896 100644 --- a/scripting/include/feedthetrolls/trolls.inc +++ b/scripting/include/feedthetrolls/trolls.inc @@ -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) diff --git a/scripting/include/ftt.inc b/scripting/include/ftt.inc index 89f0929..ed8bb6e 100644 --- a/scripting/include/ftt.inc +++ b/scripting/include/ftt.inc @@ -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" diff --git a/scripting/include/jutils.inc b/scripting/include/jutils.inc index 47d27ff..2e8f6d5 100644 --- a/scripting/include/jutils.inc +++ b/scripting/include/jutils.inc @@ -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(out)); + // Convert float to int: + out[0] = RoundToFloor(view_as(out[0])); + out[1] = RoundToFloor(view_as(out[1])); + out[2] = RoundToFloor(view_as(out[2])); +} \ No newline at end of file diff --git a/scripting/include/left4dhooks.inc b/scripting/include/left4dhooks.inc index 23d5140..91e7fca 100644 --- a/scripting/include/left4dhooks.inc +++ b/scripting/include/left4dhooks.inc @@ -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(); /** diff --git a/scripting/include/left4dhooks_anim.inc b/scripting/include/left4dhooks_anim.inc index 45505b1..776a509 100644 --- a/scripting/include/left4dhooks_anim.inc +++ b/scripting/include/left4dhooks_anim.inc @@ -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 diff --git a/scripting/include/left4dhooks_lux_library.inc b/scripting/include/left4dhooks_lux_library.inc index f71fdc9..3e11f04 100644 --- a/scripting/include/left4dhooks_lux_library.inc +++ b/scripting/include/left4dhooks_lux_library.inc @@ -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 diff --git a/scripting/include/left4dhooks_silver.inc b/scripting/include/left4dhooks_silver.inc index 8262e5d..8700355 100644 --- a/scripting/include/left4dhooks_silver.inc +++ b/scripting/include/left4dhooks_silver.inc @@ -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); } diff --git a/scripting/include/left4dhooks_stocks.inc b/scripting/include/left4dhooks_stocks.inc index cf3f7c1..f900b13 100644 --- a/scripting/include/left4dhooks_stocks.inc +++ b/scripting/include/left4dhooks_stocks.inc @@ -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 */ } diff --git a/scripting/l4d2_TKStopper.sp b/scripting/l4d2_TKStopper.sp index b86f2a3..0f4a794 100644 --- a/scripting/l4d2_TKStopper.sp +++ b/scripting/l4d2_TKStopper.sp @@ -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 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(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(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; diff --git a/scripting/l4d2_ai_tweaks.sp b/scripting/l4d2_ai_tweaks.sp index fe0f10f..938f3de 100644 --- a/scripting/l4d2_ai_tweaks.sp +++ b/scripting/l4d2_ai_tweaks.sp @@ -3,6 +3,7 @@ //#define DEBUG +#define ALLOW_HEALING_MIN_IDLE_TIME 180 #define PLUGIN_VERSION "1.0" #include @@ -10,6 +11,8 @@ #include //#include +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; } diff --git a/scripting/l4d2_autobotcrown.sp b/scripting/l4d2_autobotcrown.sp index a761e8f..f30ed5f 100644 --- a/scripting/l4d2_autobotcrown.sp +++ b/scripting/l4d2_autobotcrown.sp @@ -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); diff --git a/scripting/l4d2_autorestart.sp b/scripting/l4d2_autorestart.sp index 247ed09..078001b 100644 --- a/scripting/l4d2_autorestart.sp +++ b/scripting/l4d2_autorestart.sp @@ -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 #include //#include 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; diff --git a/scripting/l4d2_crescendo_control.sp b/scripting/l4d2_crescendo_control.sp index 001806e..2109b04 100644 --- a/scripting/l4d2_crescendo_control.sp +++ b/scripting/l4d2_crescendo_control.sp @@ -2,17 +2,29 @@ #pragma newdecls required #define DEBUG 0 - #define PLUGIN_VERSION "1.0" -#define PANIC_DETECT_THRESHOLD 50.0 - #include #include #include //#include -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 } \ No newline at end of file diff --git a/scripting/l4d2_detections.sp b/scripting/l4d2_detections.sp index 35aaef9..1f81b03 100644 --- a/scripting/l4d2_detections.sp +++ b/scripting/l4d2_detections.sp @@ -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); diff --git a/scripting/l4d2_extraplayeritems.sp b/scripting/l4d2_extraplayeritems.sp index c172418..e39bcca 100644 --- a/scripting/l4d2_extraplayeritems.sp +++ b/scripting/l4d2_extraplayeritems.sp @@ -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(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); diff --git a/scripting/l4d2_feedthetrolls.sp b/scripting/l4d2_feedthetrolls.sp index 95508d8..149affc 100644 --- a/scripting/l4d2_feedthetrolls.sp +++ b/scripting/l4d2_feedthetrolls.sp @@ -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."); diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp new file mode 100644 index 0000000..f59fee5 --- /dev/null +++ b/scripting/l4d2_hats.sp @@ -0,0 +1,1252 @@ +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0" +#define PLAYER_HAT_REQUEST_COOLDOWN 10 +static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; + +#include +#include +#include +#include +#include +#include + +enum hatFlags { + HAT_NONE = 0, + HAT_POCKET = 1, + HAT_REVERSED = 2, + HAT_COMMANDABLE = 4, + HAT_RAINBOW = 8 +} +enum struct HatData { + int entity; // The entity REFERENCE + int visibleEntity; // Thee visible entity REF + + // Original data for entity + float orgPos[3]; + float orgAng[3]; + float offset[3]; + float angles[3]; + int collisionGroup; + int solidType; + int moveType; + + float scale; + int flags; + float rainbowColor[3]; + int rainbowTicks; + bool rainbowReverse; +} +enum hatFeatures { + HatConfig_None = 0, + HatConfig_PlayerHats = 1, + HatConfig_RespectAdminImmunity = 2, + HatConfig_FakeHat = 4, + HatConfig_NoSaferoomHats = 8, + HatConfig_PlayerHatConsent = 16, + HatConfig_InfectedHats = 32, + HatConfig_ReversedHats = 64, + HatConfig_DeleteThrownHats = 128 +} + +HatData hatData[MAXPLAYERS+1]; +int lastHatRequestTime[MAXPLAYERS+1]; +bool tempGod[MAXPLAYERS+1]; + +Cookie noHatVictimCookie; +ConVar cvar_sm_hats_enabled; +ConVar cvar_sm_hats_flags; +ConVar cvar_sm_hat_rainbow_speed; +ConVar cvar_sm_hats_blacklist_enabled; + +#define MAX_FORBIDDEN_CLASSNAMES 12 +static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { + "prop_door_rotating_checkpoint", + "env_physics_blocker", + "env_player_blocker", + "func_brush", + "func_simpleladder", + "prop_door_rotating", + "func_button", + "func_elevator", + "func_button_timed", + // "func_movelinear", + // "infected", + "func_lod", + "func_door", + "prop_ragdoll" +}; + +#define MAX_REVERSE_CLASSNAMES 2 +static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { + "infected", + "func_movelinear" +}; + +public Plugin myinfo = +{ + name = "L4D2 Hats", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +ArrayList NavAreas; + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D/L4D2 only."); + } + + LoadTranslations("common.phrases"); + HookEvent("player_entered_checkpoint", OnEnterSaferoom); + HookEvent("player_bot_replace", Event_PlayerOutOfIdle ); + HookEvent("bot_player_replace", Event_PlayerToIdle); + + RegConsoleCmd("sm_hat", Command_DoAHat, "Hats"); + + cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); + cvar_sm_hats_enabled = CreateConVar("sm_hats_enabled", "1.0", "Enable hats.\n0=OFF, 1=Admins Only, 2=Any", FCVAR_NONE, true, 0.0, true, 2.0); + cvar_sm_hats_enabled.AddChangeHook(Event_HatsEnableChanged); + cvar_sm_hats_flags = CreateConVar("sm_hats_features", "217", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats", FCVAR_CHEAT, true, 0.0); + cvar_sm_hat_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0); + + noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public); + noHatVictimCookie.SetPrefabMenu(CookieMenu_OnOff_Int, "Disable player hats for self", OnLocalPlayerHatCookieSelect); +} + +//////////////////////////////////////////////////////////////// + +public Action Command_DoAHat(int client, int args) { + int hatter = GetHatter(client); + if(hatter > 0) { + ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); + PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); + return Plugin_Handled; + } + + AdminId adminId = GetUserAdmin(client); + if(cvar_sm_hats_enabled.IntValue == 1) { + if(adminId == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Hats are for admins only"); + return Plugin_Handled; + } + } else if(!adminId.HasFlag(Admin_Cheats)) { + PrintToChat(client, "[Hats] You do not have permission"); + return Plugin_Handled; + } + if(cvar_sm_hats_enabled.IntValue == 0) { + ReplyToCommand(client, "[Hats] Hats are disabled"); + return Plugin_Handled; + } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Hats are only available for survivors."); + return Plugin_Handled; + } + + int oldVisible = EntRefToEntIndex(hatData[client].visibleEntity); + if(oldVisible > 0) { + AcceptEntityInput(oldVisible, "Kill"); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + } + + int entity = GetHat(client); + if(entity > 0) { + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + // int orgEntity = entity; + if(HasFlag(client, HAT_REVERSED)) { + entity = client; + } + ClearParent(entity); + + if(arg[0] == 's') { + char sizeStr[4]; + GetCmdArg(2, sizeStr, sizeof(sizeStr)); + float size = StringToFloat(sizeStr); + if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); + else + PrintHintText(client, "Hat does not support scaling"); + int child = -1; + while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) + { + int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); + if(parent == entity && HasEntProp(child, Prop_Send, "m_flModelScale")) { + if(HasEntProp(child, Prop_Send, "m_flModelScale")) { + PrintToConsole(client, "found child %d for %d", child, entity); + SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); + } else { + PrintToConsole(client, "Child %d for %d cannot be scaled", child, entity); + } + + } + } + + EquipHat(client, entity); + return Plugin_Handled; + } else if(arg[0] == 'r' && arg[1] == 'a') { + SetFlag(client, HAT_RAINBOW); + hatData[client].rainbowTicks = 0; + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + hatData[client].rainbowColor[1] = 255.0; + hatData[client].rainbowColor[2] = 255.0; + EquipHat(client, entity); + ReplyToCommand(client, "Rainbow hats enabled"); + return Plugin_Handled; + } + + + AcceptEntityInput(entity, "EnableMotion"); + SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); + SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); + + + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + AcceptEntityInput(visibleEntity, "Kill"); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + } + tempGod[client] = true; + + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); + if(entity <= MaxClients) { + tempGod[entity] = true; + hatData[client].orgAng[2] = 0.0; + CreateTimer(2.5, Timer_RemoveGod, GetClientUserId(entity)); + SetEntityMoveType(entity, MOVETYPE_WALK); + } else { + SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); + } + + if(arg[0] == 'y') { + GetClientEyeAngles(client, hatData[client].orgAng); + GetClientAbsOrigin(client, hatData[client].orgPos); + hatData[client].orgPos[2] += 45.0; + float ang[3], vel[3]; + + GetClientEyeAngles(client, ang); + ang[2] = 0.0; + if(ang[0] > 0.0) ang[0] = -ang[0]; + // ang[0] = -45.0; + + vel[0] = Cosine(DegToRad(ang[1])) * 1500.0; + vel[1] = Sine(DegToRad(ang[1])) * 1500.0; + vel[2] = 700.0; + if(entity <= MaxClients) { + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + L4D2_CTerrorPlayer_Fling(entity, client, vel); + } /*else if(visibleEntity > 0) { + PrintToChat(client, "Yeeting fake car..."); + ClearParent(visibleEntity); + + SetEntProp(visibleEntity, Prop_Send, "movetype", 6); + + AcceptEntityInput(visibleEntity, "EnableMotion"); + + TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); + TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); + DataPack pack; + CreateDataTimer(4.0, Timer_PropYeetEnd, pack); + pack.WriteCell(hatData[client].entity); + pack.WriteCell(hatData[client].visibleEntity); + pack.WriteCell(hatData[client].collisionGroup); + pack.WriteCell(hatData[client].solidType); + pack.WriteCell(hatData[client].moveType); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + hatData[client].entity = INVALID_ENT_REFERENCE; + } */ else { + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); + CreateTimer(6.0, Timer_PropSleep, hatData[client].entity); + } + PrintToChat(client, "[Hats] Yeeted hat"); + hatData[client].entity = INVALID_ENT_REFERENCE; + return Plugin_Handled; + } else if(arg[0] == 'c') { + if(GetCursorLocation(client, hatData[client].orgPos)) { + GlowPoint(hatData[client].orgPos); + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat on cursor."); + } else { + PrintToChat(client, "[Hats] Could not find cursor position."); + } + } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { + if(!HasFlag(client, HAT_REVERSED)) { + + GetClientEyePosition(client, hatData[client].orgPos); + GetClientEyeAngles(client, hatData[client].orgAng); + GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 80.0, hatData[client].orgPos); + hatData[client].orgAng[0] = 0.0; + // GlowPoint(hatData[client].orgPos, 2.0); + float mins[3]; + GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); + // GlowPoint(hatData[client].orgPos, 3.0); + hatData[client].orgPos[2] += mins[2]; + FindGround(hatData[client].orgPos, hatData[client].orgPos); + // GlowPoint(hatData[client].orgPos); + } + + + /*GetGroundTopDown(client, hatData[client].orgPos, hatData[client].orgAng); + // GetGround(client, hatData[client].orgPos, hatData[client].orgAng); + float mins[3]; + GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); + hatData[client].orgPos[2] -= mins[2]; + GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 80.0, hatData[client].orgPos);*/ + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + // hatData[client].orgPos[2] = mins[2]; + PrintToChat(client, "[Hats] Placed hat in front of you."); + } else { + PrintToChat(client, "[Hats] Restored hat to its original position."); + } + + if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); + + AcceptEntityInput(entity, "Sleep"); + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + hatData[client].entity = INVALID_ENT_REFERENCE; + } else { + int flags = 0; + entity = GetLookingEntity(client, Filter_IgnorePlayer); //GetClientAimTarget(client, false); + if(entity <= 0) { + PrintCenterText(client, "[Hats] No entity found"); + } else { + if(args > 0) { + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + if(arg[0] == 'r') { + flags |= view_as(HAT_REVERSED); + } + } + int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); + if(parent > 0 && entity > MaxClients) { + PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); + entity = parent; + } else if(entity <= MaxClients) { + if(GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); + return Plugin_Handled; + } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { + PrintToChat(client, "[Hats] Player is either dead, hanging or in the process of dying."); + return Plugin_Handled; + } else if(EntRefToEntIndex(hatData[entity].entity) == client) { + PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); + return Plugin_Handled; + } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { + PrintToChat(client, "[Hats] Player hats are disabled"); + return Plugin_Handled; + } else if(!CanTarget(entity)) { + PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); + return Plugin_Handled; + } else if(!CanTarget(client)) { + PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); + return Plugin_Handled; + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { + AdminId targetAdmin = GetUserAdmin(entity); + AdminId clientAdmin = GetUserAdmin(client); + if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Cannot target an admin"); + return Plugin_Handled; + } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { + PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); + return Plugin_Handled; + } + } + if(!IsFakeClient(entity) && cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && ~flags & view_as(HAT_REVERSED)) { + int lastRequestDiff = GetTime() - lastHatRequestTime[client]; + if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { + PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", lastRequestDiff); + return Plugin_Handled; + } + + Menu menu = new Menu(HatConsentHandler); + menu.SetTitle("%N: Requests to hat you", client); + char id[8]; + Format(id, sizeof(id), "%d|1", GetClientUserId(client)); + menu.AddItem(id, "Accept"); + Format(id, sizeof(id), "%d|0", GetClientUserId(client)); + menu.AddItem(id, "Reject"); + menu.Display(entity, 12); + PrintHintText(client, "Sent hat request to %N", entity); + PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); + return Plugin_Handled; + } + } + + + char classname[64]; + GetEntityClassname(entity, classname, sizeof(classname)); + if(cvar_sm_hats_blacklist_enabled.BoolValue) { + for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { + if(StrEqual(FORBIDDEN_CLASSNAMES[i], classname)) { + PrintToChat(client, "[Hats] Entity (%s) is a blacklisted entity. Naughty.", classname); + return Plugin_Handled; + } + } + } + + if(~flags & view_as(HAT_REVERSED)) { + for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { + if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { + flags |= view_as(HAT_REVERSED); + break; + } + } + } + EquipHat(client, entity, classname, flags); + } + } + return Plugin_Handled; +} + +public int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { + if (action == MenuAction_Select) { + static char info[8]; + menu.GetItem(param2, info, sizeof(info)); + static char str[2][8]; + ExplodeString(info, "|", str, 2, 8, false); + int activator = GetClientOfUserId(StringToInt(str[0])); + int hatAction = StringToInt(str[1]); + if(activator == 0) { + ReplyToCommand(target, "Player has gone idle or left"); + return 0; + } else if(hatAction == 1) { + EquipHat(activator, target, "player", 0); + } else { + ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); + PrintHintText(activator, "%N refused your request", target); + lastHatRequestTime[activator] = GetTime(); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + for(int slot = 0; slot <= 5; slot++) { + int wpn = GetPlayerWeaponSlot(client, slot); + for(int i = 1; i <= MaxClients; i++) { + if(i != client && IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { + int hat = GetHat(i); + if(hat == wpn) { + + break; + } + } + } + } +} + +public void OnEnterSaferoom(Event event, const char[] name, bool dontBroadcast) { + int userid = event.GetInt("userid"); + int client = GetClientOfUserId(userid); + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats) && client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { + if(HasHat(client)) { + if(!IsHatAllowed(client)) { + PrintToChat(client, "[Hats] Hat is not allowed in the saferoom and has been returned"); + ClearHat(client, true); + } else { + CreateTimer(2.0, Timer_PlaceHat, userid); + // float maxflow = L4D2Direct_GetMapMaxFlowDistance() + // L4D_GetNavArea_SpawnAttributes + // L4D_GetNavAreaPos + } + } + } +} + +Action Timer_PlaceHat(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client > 0 && HasHat(client)) { + GetClientEyePosition(client, hatData[client].orgPos); + GetClientEyeAngles(client, hatData[client].orgAng); + GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 80.0, hatData[client].orgPos); + hatData[client].orgAng[0] = 0.0; + PrintToChat(client, "[Hats] Hat has been placed down"); + ClearHat(client, true); + } + return Plugin_Handled; +} +void GlowPoint(const float pos[3], float lifetime = 5.0) { + #if defined DEBUG_GLOW + PrecacheModel("models/props_fortifications/orange_cone001_reference.mdl"); + int entity = CreateEntityByName("prop_dynamic"); + DispatchKeyValue(entity, "disableshadows", "1"); + DispatchKeyValue(entity, "model", "models/props_fortifications/orange_cone001_reference.mdl"); + TeleportEntity(entity, pos, NULL_VECTOR, NULL_VECTOR); + DispatchSpawn(entity); + CreateTimer(lifetime, Timer_Kill, entity); + #endif +} + +Action Timer_Kill(Handle h, int entity) { + if(IsValidEntity(entity)) + AcceptEntityInput(entity, "Kill"); + return Plugin_Handled; +} + +stock bool GetCursorLocation(int client, float outPos[3]) { + float start[3], angle[3], ceilPos[3], wallPos[3], normal[3]; + GetClientEyePosition(client, start); + GetClientEyeAngles(client, angle); + TR_TraceRayFilter(start, angle, MASK_SOLID, RayType_Infinite, Filter_NoPlayers, client); + if(TR_DidHit()) { + TR_GetEndPosition(outPos); + TR_GetPlaneNormal(null, normal); + if(normal[2] < 0.1) { + + // Find a suitable position above + start[0] = outPos[0]; + start[1] = outPos[1]; + start[2] = outPos[2] += 100.0; + TR_TraceRayFilter(outPos, start, MASK_SOLID, RayType_EndPoint, TraceEntityFilterPlayer, client); + bool ceilCollided = TR_DidHit(); + bool ceilOK = !TR_AllSolid(); + TR_GetEndPosition(ceilPos); + float distCeil = GetVectorDistance(outPos, ceilPos, true); + // Find a suitable position backwards + angle[0] = 70.0; + angle[1] += 180.0; + TR_TraceRayFilter(outPos, angle, MASK_SOLID, RayType_Infinite, TraceEntityFilterPlayer, client); + bool wallCollided = TR_DidHit(); + TR_GetEndPosition(wallPos); + float distWall = GetVectorDistance(outPos, wallPos, true); + + if(ceilCollided && wallCollided) + + if(wallCollided && distWall < 62500) { + outPos = wallPos; + } else if(ceilOK) { + outPos = ceilPos; + } + + // if(TR_DidHit()) { + // PrintToChat(client, "Wall: Collided %f -> %f | %f", outPos[2], wallPos[2], distWall); + // if(ceilOk) { + // float distWall = GetVectorDistance(outPos, wallPos, true); + // if(distCeil < distWall) { + // outPos = ceilPos; + // } else { + // outPos = wallPos; + // } + // } else { + // outPos = wallPos; + // } + // } else if(ceilOk) { + // outPos = ceilPos; + // } + } + + return true; + } else { + return false; + } +} + +Action Timer_RemountHats(Handle h) { + float p1[3], p2[3]; + for(int i = 1; i <= MaxClients; i++) { + int entity = GetHat(i); + if(IsClientConnected(i) && IsClientInGame(i) && !HasFlag(i, HAT_POCKET)) { + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + if(entity > 0) { + GetClientAbsOrigin(i, p1); + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", p2); + if(GetVectorDistance(p1, p2) > 40000.0) { + ClearParent(entity); + if(visibleEntity > 0) { + ClearParent(visibleEntity); + } + RequestFrame(Frame_Remount, i); + } + } else if(visibleEntity > 0) { + AcceptEntityInput(visibleEntity, "Kill"); + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + } + } + } + return Plugin_Handled; +} + +void Frame_Remount(int i) { + int entity = GetHat(i); + if(entity == -1) return; + SetParent(entity, i); + SetParentAttachment(entity, "eyes", false); + SetParentAttachment(entity, "eyes", true); + + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + if(visibleEntity > 0) { + SetParent(visibleEntity, i); + SetParentAttachment(visibleEntity, "eyes", false); + SetParentAttachment(visibleEntity, "eyes", true); + } +} + +Action Timer_PropSleep(Handle h, int ref) { + if(IsValidEntity(ref)) { + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { + // Don't delete if someone has hatted it (including ourself): + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { + return Plugin_Handled; + } + } + char classname[64]; + GetEntityClassname(ref, classname, sizeof(classname)); + if(StrContains(classname, "prop_") > -1) { + AcceptEntityInput(ref, "Kill"); + return Plugin_Handled; + } + } + AcceptEntityInput(ref, "Sleep"); + } + return Plugin_Handled; +} + +Action Timer_PropYeetEnd(Handle h, DataPack pack) { + pack.Reset(); + int realEnt = EntRefToEntIndex(pack.ReadCell()); + int visibleEnt = EntRefToEntIndex(pack.ReadCell()); + // if(IsValidEntity(visibleEnt)) { + // float pos[3], ang[3]; + // GetEntPropVector(visibleEnt, Prop_Send, "m_vecOrigin", pos); + // GetEntPropVector(visibleEnt, Prop_Send, "m_angRotation", ang); + // AcceptEntityInput(visibleEnt, "kill"); + // if(IsValidEntity(realEnt)) { + // TeleportEntity(realEnt, pos, ang, NULL_VECTOR); + // } + // } + if(IsValidEntity(realEnt)) { + SetEntProp(realEnt, Prop_Send, "m_CollisionGroup", pack.ReadCell()); + SetEntProp(realEnt, Prop_Send, "m_nSolidType", pack.ReadCell()); + SetEntProp(realEnt, Prop_Send, "movetype", pack.ReadCell()); + AcceptEntityInput(realEnt, "Sleep"); + } + + return Plugin_Handled; +} + +Action Timer_RemoveGod(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client) { + tempGod[client] = false; + SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + } + return Plugin_Handled; +} + + +public void Event_PlayerOutOfIdle(Event event, const char[] name, bool dontBroadcast) { + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + if(GetClientTeam(client) != 2) return; + float pos[3]; + for(int i = 1; i <= MaxClients; i++) { + if(hatData[i].entity == bot) { + GetClientAbsOrigin(i, pos); + ClearHat(i); + hatData[i].entity = EntIndexToEntRef(client); + TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); + return; + } + } + PrintToServer("Fixing hatted player to bot: Bot %N to client %N", bot, client); + // Incase they removed hat right after, manually fix them + ClearParent(client); + ClearParent(bot); + SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(client, MOVETYPE_WALK); + RequestFrame(Frame_FixClient, client); + // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); +} + +void Frame_FixClient(int client) { + if(IsClientConnected(client) && GetClientTeam(client) == 2) { + ClearParent(client); + SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(client, MOVETYPE_WALK); + } + // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); +} +public void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) { + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + if(GetClientTeam(client) != 2) return; + float pos[3]; + for(int i = 1; i <= MaxClients; i++) { + if(hatData[i].entity == client) { + GetClientAbsOrigin(i, pos); + ClearHat(i); + hatData[i].entity = EntIndexToEntRef(bot); + TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); + return; + } + } + // Incase they removed hat right after, manually fix them + ClearParent(bot); + SetEntProp(bot, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(bot, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(bot, MOVETYPE_WALK); +} + +void OnLocalPlayerHatCookieSelect(int client, CookieMenuAction action, any info, char[] buffer, int maxlen) { + if(action != CookieMenuAction_SelectOption) return; + bool value = StringToInt(buffer) == 1; + if(value) { + for(int i = 1; i <= MaxClients; i++) { + int hat = GetHat(i); + if(hat == client) { + ClearHat(i, false); + PrintToChat(i, "%N has blocked player hats for themselves", client); + } + } + ClearHat(client, false); + } +} + +public void Event_HatsEnableChanged(ConVar convar, const char[] sOldValue, const char[] sNewValue) { + if(convar.IntValue == 0) { + ClearHats(); + } else if(convar.IntValue == 1) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && GetUserAdmin(i) == INVALID_ADMIN_ID && HasHat(i)) { + ClearHat(i, false); + } + } + } +} + +ArrayList GetSpawnLocations() { + ArrayList list = new ArrayList(); + ArrayList newList = new ArrayList(); + L4D_GetAllNavAreas(list); + for(int i = 0; i < list.Length; i++) { + Address nav = list.Get(i); + if(L4D_GetNavArea_SpawnAttributes(nav) & NAV_SPAWN_THREAT) { + newList.Push(nav); + } + } + delete list; + PrintToServer("[Hats] Got %d valid locations", newList.Length); + return newList; +} + +void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { + if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) { + int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1)); + L4D_FindRandomSpot(nav, pos); + } else { + int survivor = GetRandomClient(5, 1); + if(ignoreClient > 0 && survivor == ignoreClient) survivor = GetRandomClient(5, 1); + if(survivor > 0) { + GetClientAbsOrigin(survivor, pos); + } + } +} + +static float cmdThrottle[MAXPLAYERS+1]; +static bool onLadder[MAXPLAYERS+1]; +float lastAng[MAXPLAYERS+1][3]; +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]) { + float tick = GetGameTime(); + if(cvar_sm_hats_enabled.IntValue == 0 || (GetUserAdmin(client) == INVALID_ADMIN_ID && cvar_sm_hats_enabled.IntValue == 1)) return Plugin_Continue; + int entity = GetHat(client); + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + if(entity > 0) { + // try to tp hat to its own pos + if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { + onLadder[client] = true; + ClearParent(entity); + if(visibleEntity > 0) { + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + AcceptEntityInput(visibleEntity, "Kill"); + } + } else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { + onLadder[client] = false; + EquipHat(client, entity); + } + + if(HasFlag(client, HAT_RAINBOW)) { + // Decrement and flip, possibly when rainbowticks + if(hatData[client].rainbowReverse) { + hatData[client].rainbowColor[0] -= cvar_sm_hat_rainbow_speed.FloatValue; + } else { + hatData[client].rainbowColor[0] += cvar_sm_hat_rainbow_speed.FloatValue; + } + + if(hatData[client].rainbowColor[0] > 360.0) { + hatData[client].rainbowReverse = true; + hatData[client].rainbowColor[0] = 360.0; + } else if(hatData[client].rainbowColor[0] < 0.0) { + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + } + + static int rgb[3]; + HSVToRGBInt(hatData[client].rainbowColor, rgb); + SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); + hatData[client].rainbowTicks = -cvar_sm_hat_rainbow_speed.IntValue; + EquipHat(client, entity); + } + + if(entity <= MaxClients) { + if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { + onLadder[entity] = true; + ClearParent(entity); + } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { + onLadder[entity] = false; + EquipHat(client, entity); + } + } + if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { + float pos[3]; + ChooseRandomPosition(pos, client); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); + } + } + if(buttons & IN_USE && buttons & IN_RELOAD) { + if(entity > 0) { + if(buttons & IN_ZOOM) { + if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; + if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; + if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; + if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; + if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; + if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; + TeleportEntity(entity, hatData[client].offset, angles, vel); + return Plugin_Handled; + } else if(tick - cmdThrottle[client] > 0.25) { + if(buttons & IN_ATTACK) { + ClientCommand(client, "sm_hat %s", 'y'); + } else if(buttons & IN_SPEED) { + ClientCommand(client, "sm_hat %s", 'n'); + } else if(buttons & IN_DUCK) { + ClientCommand(client, "sm_hat %s", 'p'); + } + } + } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { + ClientCommand(client, "sm_hat"); + } + cmdThrottle[client] = tick; + lastAng[client] = angles; + hatData[client].angles = angles; + return Plugin_Handled; + } + return Plugin_Continue; +} + + +// Don't show real entity to hat wearer (Show for ALL but hat wearer) +Action OnRealTransmit(int entity, int client) { + #if defined DEBUG_HAT_SHOW_FAKE + return Plugin_Continue; + #endif + if(hatData[client].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].entity) == entity) + return Plugin_Handled; + return Plugin_Continue; +} + +// Only show to hat wearer (do not show to ALL) +Action OnVisibleTransmit(int entity, int client) { + #if defined DEBUG_HAT_SHOW_FAKE + return Plugin_Continue; + #endif + if(hatData[client].visibleEntity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].visibleEntity) != entity) + return Plugin_Handled; + return Plugin_Continue; +} + + +public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { + if(victim > MaxClients || victim <= 0) return Plugin_Continue; + if(damage > 0.0 && tempGod[victim]) { + damage = 0.0; + return Plugin_Handled; + } + if(attacker > MaxClients || attacker <= 0) return Plugin_Continue; + if(victim == EntRefToEntIndex(hatData[attacker].entity) || attacker == EntRefToEntIndex(hatData[victim].entity)) { + damage = 0.0; + return Plugin_Handled; + } + return Plugin_Continue; +} + +public void OnClientDisconnect(int client) { + tempGod[client] = false; +} + +public void OnEntityDestroyed(int entity) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { + if(EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + PrintHintText(i, "Hat entity has vanished"); + ClientCommand(i, "play ui/menu_back.wav"); + break; + } + } + } +} +public void OnMapStart() { + CreateTimer(30.0, Timer_RemountHats, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + for(int i = 1; i <= MaxClients; i++) { + cmdThrottle[i] = 0.0; + tempGod[i] = false; + } + NavAreas = GetSpawnLocations(); +} + +public void OnMapEnd() { + delete NavAreas; + ClearHats(); +} +public void OnPluginEnd() { + ClearHats(); +} + +public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { + if(EntRefToEntIndex(hatData[data].entity) == entity) { + return false; + } + return entity != data; +} + + +int GetLookingEntity(int client, TraceEntityFilter filter) { + static float pos[3], ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_TraceRayFilter(pos, ang, MASK_SHOT, RayType_Infinite, filter, client); + if(TR_DidHit()) { + return TR_GetEntityIndex(); + } + return -1; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool Filter_OnlyPlayers(int entity, int mask, int data) { + return entity > 0 && entity <= MaxClients && entity != data; +} + +bool Filter_NoPlayers(int entity, int mask, int data) { + return entity > MaxClients && entity != data; +} + +bool Filter_IgnorePlayer(int entity, int mask, int data) { + if(entity == data) return false; + if(entity <= MaxClients) { + int client = GetRealClient(data); + return CanTarget(client); // Don't target if player targetting off + } + if(cvar_sm_hats_blacklist_enabled.BoolValue) { + 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; +} + +//////////////////////////////// + +void ClearHats() { + for(int i = 1; i <= MaxClients; i++) { + if(HasHat(i)) { + ClearHat(i, false); + } + if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); + } +} +void ClearHat(int i, bool restore = false) { + + int entity = EntRefToEntIndex(hatData[i].entity); + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; + + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + AcceptEntityInput(visibleEntity, "Kill"); + } + if(modifyEntity > 0) { + SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); + ClearParent(modifyEntity); + } else { + return; + } + + // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); + SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); + SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); + + hatData[i].entity = INVALID_ENT_REFERENCE; + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + + if(HasFlag(i, HAT_REVERSED)) { + entity = i; + i = modifyEntity; + } + + if(entity <= MAXPLAYERS) { + AcceptEntityInput(entity, "EnableLedgeHang"); + } + if(restore) { + // If hat is a player, override original position to hat wearer's + if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { + GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); + } + // Restore to original position + if(HasFlag(i, HAT_REVERSED)) { + TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } else { + TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } + } +} + +bool HasHat(int client) { + return GetHat(client) > 0; +} + +int GetHat(int client) { + if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; + int index = EntRefToEntIndex(hatData[client].entity); + if(index <= 0) return -1; + if(!IsValidEntity(index)) return -1; + return index; +} + +int GetHatter(int client) { + int myRef = EntIndexToEntRef(client); + for(int i = 1; i <= MaxClients; i++) { + if(hatData[client].entity == myRef) { + return i; + } + } + return -1; +} + +bool CanTarget(int victim) { + static char buf[2]; + noHatVictimCookie.Get(victim, buf, sizeof(buf)); + return StringToInt(buf) == 0; +} + +bool IsHatAllowed(int client) { + char name[32]; + GetEntityClassname(hatData[client].entity, name, sizeof(name)); + // Don't allow non-weapons in saferoom + if(StrEqual(name, "prop_physics")) { + GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); + if(StrContains(name, "gnome") != -1) { + return true; + } + PrintToConsole(client, "Dropping hat: prop_physics (%s)", name); + return false; + } + else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { + return true; + } + PrintToConsole(client, "Dropping hat: %s", name); + return false; +} + + +void SetFlag(int client, hatFlags flag) { + hatData[client].flags |= view_as(flag); +} + +bool HasFlag(int client, hatFlags flag) { + return hatData[client].flags & view_as(flag) != 0; +} + +void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE) { + if(HasHat(client)) + ClearHat(client, true); + + // Player specific tweaks + int visibleEntity; + if(entity == 0) { + ThrowError("Attempted to equip world (client = %d)", client); + return; + } + + hatData[client].entity = EntIndexToEntRef(entity); + int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + + + if(modifyEntity <= MaxClients) { + SDKHook(modifyEntity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + AcceptEntityInput(modifyEntity, "DisableLedgeHang"); + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { + return; + // char model[64]; + // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + // visibleEntity = CreateEntityByName("prop_dynamic"); + // DispatchKeyValue(visibleEntity, "model", model); + // DispatchKeyValue(visibleEntity, "disableshadows", "1"); + // DispatchSpawn(visibleEntity); + // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); + // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); + // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); + } + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + // Temp remove the hat to be yoinked by another player + for(int i = 1; i <= MaxClients; i++) { + if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + } + } + + // Called on initial hat + if(classname[0] != '\0') { + if(entity <= MaxClients && !IsFakeClient(entity)) { + PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); + } + // Reset things: + hatData[client].flags = 0; + hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; + hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; + + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { + SetFlag(client, HAT_REVERSED); + if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { + SetFlag(client, HAT_COMMANDABLE); + } + PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); + if(entity <= MaxClients) { + LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); + PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); + } + } else { + if(entity <= MaxClients) + PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); + else + PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); + if(entity <= MaxClients) + LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); + else + LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); + } + hatData[client].scale = -1.0; + if(modifyEntity <= MaxClients) { + if(HasFlag(client, HAT_REVERSED)) { + hatData[client].offset[2] += 7.2; + } else { + hatData[client].offset[2] += 4.2; + } + } else { + float mins[3]; + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); + hatData[client].offset[2] += mins[2]; + } + PrintToConsole(client, "offset %f %f %f", hatData[client].offset[0],hatData[client].offset[1],hatData[client].offset[2]); + } + AcceptEntityInput(modifyEntity, "DisableMotion"); + + // Get the data (position, angle, movement shit) + + GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); + GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + + + if(StrEqual(classname, "witch", false)) { + TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); + SetFlag(client, HAT_POCKET); + } + + if(!HasFlag(client, HAT_POCKET)) { + // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); + if(HasFlag(client, HAT_REVERSED)) { + SetParent(client, entity); + if(StrEqual(classname, "infected")) { + SetParentAttachment(modifyEntity, "head", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "head", true); + } else { + SetParentAttachment(modifyEntity, "eyes", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "eyes", true); + } + + if(HasFlag(client, HAT_COMMANDABLE)) { + ChooseRandomPosition(hatData[client].offset); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); + } + } else { + SetParent(entity, client); + SetParentAttachment(modifyEntity, "eyes", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "eyes", true); + } + + if(visibleEntity > 0) { + SetParent(visibleEntity, client); + SetParentAttachment(visibleEntity, "eyes", true); + hatData[client].offset[2] += 10.0; + TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(visibleEntity, "eyes", true); + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); + #endif + } + + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); + #endif + + SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); + SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); + } +} + +stock bool FindGround(const float start[3], float end[3]) { + float angle[3]; + angle[0] = 90.0; + + Handle trace = TR_TraceRayEx(start, angle, MASK_SHOT, RayType_Infinite); + if(!TR_DidHit(trace)) { + delete trace; + return false; + } + TR_GetEndPosition(end, trace); + delete trace; + return true; +} + +stock bool L4D_IsPlayerCapped(int client) { + if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) + return true; + return false; +} \ No newline at end of file diff --git a/scripting/l4d2_population_control.sp b/scripting/l4d2_population_control.sp index 71c8d14..5a92dc5 100644 --- a/scripting/l4d2_population_control.sp +++ b/scripting/l4d2_population_control.sp @@ -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); diff --git a/scripting/l4d2_tank_priority.sp b/scripting/l4d2_tank_priority.sp index 61fa273..81e9044 100644 --- a/scripting/l4d2_tank_priority.sp +++ b/scripting/l4d2_tank_priority.sp @@ -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; diff --git a/scripting/l4d2_turret.sp b/scripting/l4d2_turret.sp index c6e5caa..f6cb857 100644 --- a/scripting/l4d2_turret.sp +++ b/scripting/l4d2_turret.sp @@ -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 } diff --git a/scripting/sm_namespamblock.sp b/scripting/sm_namespamblock.sp index cb27018..b9e7721 100644 --- a/scripting/sm_namespamblock.sp +++ b/scripting/sm_namespamblock.sp @@ -9,8 +9,8 @@ #include //#include -#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 = { diff --git a/scripting/sm_player_notes.sp b/scripting/sm_player_notes.sp index 19e8c13..58073af 100644 --- a/scripting/sm_player_notes.sp +++ b/scripting/sm_player_notes.sp @@ -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); } } }