mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-05 20:23:20 +00:00
292 lines
No EOL
8.9 KiB
SourcePawn
292 lines
No EOL
8.9 KiB
SourcePawn
#pragma semicolon 1
|
|
#pragma newdecls required
|
|
|
|
//#define DEBUG
|
|
|
|
#define PLUGIN_VERSION "1.0"
|
|
#define BILE_NO_HORDE_THRESHOLD 5
|
|
#define DOOR_CLOSE_THRESHOLD 5000.0
|
|
|
|
#include <sourcemod>
|
|
#include <sdktools>
|
|
#include <left4dhooks>
|
|
#include <jutils>
|
|
//#include <sdkhooks>
|
|
|
|
enum KitDetectionState {
|
|
KDS_None,
|
|
KDS_NoKitEnteringSaferoom,
|
|
KDS_Healed
|
|
}
|
|
|
|
enum struct PlayerDetections {
|
|
int kitPickupsSaferoom;
|
|
int saferoomLastOpen;
|
|
int saferoomOpenCount;
|
|
// Do not reset normally; need to keep track during level transitions
|
|
KitDetectionState saferoomKitState;
|
|
|
|
// Called on PlayerDisconnect, which includes map changes
|
|
void Reset() {
|
|
this.kitPickupsSaferoom = 0;
|
|
this.saferoomLastOpen = 0;
|
|
this.saferoomOpenCount = 0;
|
|
}
|
|
// Called on normal intentional disconnect, ignoring map changes
|
|
void FullReset() {
|
|
this.saferoomKitState = KDS_None;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
Bile Detections:
|
|
1. No commons around entowner or bile
|
|
2. Bile already exists
|
|
3. Player is currently vomitted on (check time?)
|
|
4. Bile on tank (common count near tank)
|
|
*/
|
|
|
|
stock bool IsPlayerBoomed(int client) {
|
|
return GetEntPropFloat(client, Prop_Send, "m_vomitStart") + 20.1 > GetGameTime();
|
|
}
|
|
stock bool IsAnyPlayerBoomed() {
|
|
for(int i = 1; i <= MaxClients; i++) {
|
|
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerBoomed(i)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
stock bool AnyRecentBileInPlay(int ignore) {
|
|
return false;
|
|
}
|
|
|
|
stock int GetEntityCountNear(const float srcPos[3], float radius = 50000.0) {
|
|
float pos[3];
|
|
int count;
|
|
int entity = -1;
|
|
while( (entity = FindEntityByClassname(entity, "infected")) != INVALID_ENT_REFERENCE ) {
|
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos);
|
|
if(GetEntProp(entity, Prop_Send, "m_clientLookatTarget") != -1 && GetVectorDistance(pos, srcPos) <= radius) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
stock int L4D_SpawnCommonInfected2(const float vPos[3], const float vAng[3] = { 0.0, 0.0, 0.0 })
|
|
{
|
|
int entity = CreateEntityByName("infected");
|
|
if( entity != -1 )
|
|
{
|
|
DispatchSpawn(entity);
|
|
TeleportEntity(entity, vPos, vAng, NULL_VECTOR);
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
PlayerDetections detections[MAXPLAYERS+1];
|
|
bool checkpointReached;
|
|
|
|
GlobalForward fwd_PlayerDoubleKit, fwd_NoHordeBileWaste, fwd_DoorFaceCloser, fwd_CheckpointDoorFaceCloser;
|
|
|
|
public Plugin myinfo =
|
|
{
|
|
name = "L4D2 Detections",
|
|
author = "jackzmc",
|
|
description = "",
|
|
version = PLUGIN_VERSION,
|
|
url = ""
|
|
};
|
|
|
|
public void OnPluginStart() {
|
|
EngineVersion g_Game = GetEngineVersion();
|
|
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
|
|
SetFailState("This plugin is for L4D/L4D2 only.");
|
|
}
|
|
|
|
fwd_PlayerDoubleKit = new GlobalForward("OnDoubleKit", ET_Hook, Param_Cell);
|
|
fwd_NoHordeBileWaste = new GlobalForward("OnNoHordeBileWaste", ET_Event, Param_Cell, Param_Cell);
|
|
fwd_DoorFaceCloser = new GlobalForward("OnDoorCloseInFace", ET_Hook, Param_Cell);
|
|
fwd_CheckpointDoorFaceCloser = new GlobalForward("OnDoorCloseInFaceSaferoom", ET_Hook, Param_Cell, Param_Cell);
|
|
|
|
HookEvent("item_pickup", Event_ItemPickup);
|
|
HookEvent("door_close", Event_DoorClose);
|
|
HookEvent("player_disconnect", Event_PlayerDisconnect);
|
|
HookEvent("heal_success", Event_HealSuccess);
|
|
}
|
|
|
|
public void OnClientPutInServer(int client) {
|
|
CreateTimer(20.0, Timer_ClearDoubleKitDetection, GetClientUserId(client));
|
|
}
|
|
|
|
// Called on map changes too, we want this:
|
|
public void OnClientDisconnect(int client) {
|
|
detections[client].Reset();
|
|
}
|
|
|
|
public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
|
|
int client = GetClientOfUserId(event.GetInt("userid"));
|
|
if(client) {
|
|
detections[client].FullReset();
|
|
}
|
|
}
|
|
|
|
public void OnMapStart() {
|
|
checkpointReached = false;
|
|
HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
|
|
HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
|
|
}
|
|
|
|
// TODO: Check when player enters saferoom, and has no kit and heals and pickup another
|
|
public void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
|
|
int client = GetClientOfUserId(event.GetInt("userid"));
|
|
if(client && L4D_IsInLastCheckpoint(client)) {
|
|
static char itmName[32];
|
|
event.GetString("item", itmName, sizeof(itmName));
|
|
if(StrEqual(itmName, "first_aid_kit")) {
|
|
if(detections[client].saferoomKitState == KDS_NoKitEnteringSaferoom) {
|
|
// Player had no kit entering saferoom and has healed
|
|
detections[client].saferoomKitState = KDS_Healed;
|
|
} else if(detections[client].saferoomKitState == KDS_Healed) {
|
|
// Player has healed. Double kit detected
|
|
InternalDebugLog("DOUBLE_KIT", client);
|
|
Call_StartForward(fwd_PlayerDoubleKit);
|
|
Call_PushCell(client);
|
|
Call_Finish();
|
|
}
|
|
if(++detections[client].kitPickupsSaferoom == 2) {
|
|
InternalDebugLog("DOUBLE_KIT_LEGACY", client);
|
|
Call_StartForward(fwd_PlayerDoubleKit);
|
|
Call_PushCell(client);
|
|
Call_Finish();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, int client, float time) {
|
|
if(!checkpointReached && client > 0 && client <= MaxClients && GetClientTeam(client) == 2) {
|
|
checkpointReached = true;
|
|
char itemName[32];
|
|
if(GetClientWeaponName(client, 3, itemName, sizeof(itemName))) {
|
|
if(StrEqual(itemName, "weapon_first_aid_kit")) {
|
|
detections[client].saferoomKitState = KDS_NoKitEnteringSaferoom;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Action Timer_ClearDoubleKitDetection(Handle h, int userid) {
|
|
int client = GetClientOfUserId(userid);
|
|
if(client) {
|
|
detections[client].saferoomKitState = KDS_None;
|
|
}
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public void Event_HealSuccess(Event event, const char[] name, bool dontBroadcast) {
|
|
int client = GetClientOfUserId(event.GetInt("userid"));
|
|
if(client) {
|
|
int target = GetClientOfUserId(event.GetInt("subject"));
|
|
int amount = event.GetInt("health_restored");
|
|
int orgHealth = GetClientHealth(target) - amount;
|
|
PrintToConsoleAll("[Debug] %N healed %N (+%d health, was perm. %d)", client, target, amount, orgHealth);
|
|
}
|
|
}
|
|
|
|
public void Event_DoorClose(Event event, const char[] name, bool dontBroadcast) {
|
|
int client = GetClientOfUserId(event.GetInt("userid"));
|
|
if(fwd_DoorFaceCloser.FunctionCount > 0 && client) {
|
|
bool isCheckpoint = event.GetBool("checkpoint");
|
|
DataPack pack = GetNearestClient(client);
|
|
pack.Reset();
|
|
int victim = pack.ReadCell();
|
|
float dist = pack.ReadFloat();
|
|
if(victim) {
|
|
if(dist < DOOR_CLOSE_THRESHOLD) {
|
|
if(isCheckpoint) {
|
|
if(detections[client].saferoomLastOpen > 0 && GetTime() - detections[client].saferoomLastOpen > 30000) {
|
|
detections[client].saferoomLastOpen = 0;
|
|
detections[client].saferoomOpenCount = 0;
|
|
}
|
|
Call_StartForward(fwd_CheckpointDoorFaceCloser);
|
|
Call_PushCell(client);
|
|
Call_PushCell(victim);
|
|
Call_PushCell(++detections[client].saferoomOpenCount);
|
|
Call_Finish();
|
|
detections[client].saferoomLastOpen = GetTime();
|
|
PrintToServer("[Detections] DOOR_SAFEROOM: %N victim -> %N %d times", client, victim, detections[client].saferoomOpenCount);
|
|
//TODO: Find way to reset, timer?
|
|
} else {
|
|
Call_StartForward(fwd_DoorFaceCloser);
|
|
Call_PushCell(client);
|
|
Call_PushCell(victim);
|
|
Call_Finish();
|
|
PrintToServer("[Detections] DOOR=: %N victim -> %N", client, victim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnEntityDestroyed(int entity) {
|
|
static char classname[16];
|
|
if(IsValidEntity(entity) && entity <= 4096) {
|
|
GetEntityClassname(entity, classname, sizeof(classname));
|
|
if(StrEqual(classname, "vomitjar_projec")) { //t cut off by classname size
|
|
int thrower = GetEntPropEnt(entity, Prop_Send, "m_hThrower");
|
|
if(thrower > 0 && thrower <= MaxClients && IsClientConnected(thrower) && IsClientInGame(thrower)) {
|
|
static float src[3];
|
|
float tmp[3];
|
|
GetClientAbsOrigin(thrower, tmp);
|
|
// TODO: Get source when lands
|
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", src);
|
|
|
|
int commons = GetEntityCountNear(src, 50000.0);
|
|
PrintToConsoleAll("[Debug] Bile Thrown By %N, Commons: %d", thrower, commons);
|
|
if(commons < BILE_NO_HORDE_THRESHOLD) {
|
|
InternalDebugLog("BILE_NO_HORDE", thrower);
|
|
Action result;
|
|
Call_StartForward(fwd_NoHordeBileWaste);
|
|
Call_PushCell(thrower);
|
|
Call_PushCell(commons);
|
|
Call_Finish(result);
|
|
|
|
if(result == Plugin_Stop) {
|
|
AcceptEntityInput(entity, "kill");
|
|
// GiveClientWeapon(thrower, "vomitjar");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Door close
|
|
|
|
void InternalDebugLog(const char[] name, int client) {
|
|
PrintToConsoleAll("[Detection] %s: Client %N", name, client);
|
|
}
|
|
|
|
stock DataPack GetNearestClient(int client) {
|
|
int victim;
|
|
float pos[3], pos2[3], distance;
|
|
GetClientAbsOrigin(client, pos);
|
|
for(int i = 1; i <= MaxClients; i++) {
|
|
if(i != client && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
|
|
GetClientAbsOrigin(i, pos2);
|
|
float dist = GetVectorDistance(pos, pos2, false);
|
|
if(victim == 0 || dist < distance) {
|
|
distance = dist;
|
|
victim = i;
|
|
}
|
|
}
|
|
}
|
|
DataPack pack = new DataPack();
|
|
pack.WriteCell(victim);
|
|
pack.WriteFloat(distance);
|
|
return pack;
|
|
} |