#pragma semicolon 1 #pragma newdecls required //#define DEBUG #define TURRET_MAX_RANGE_HUMANS 140.0 // Max range to find humans near. Does not activate if found #define TURRET_MAX_RANGE_SPECIALS 1700.0 // Max range of specials (including tanks, not witches) #define TURRET_MAX_RANGE_INFECTED 1500.0 // Max range of infected commons #define TURRET_ACTIVATION_TIME 5.0 // The time for a new turret to activate #define TURRET_MAX_RANGE_HUMANS_OPTIMIZED TURRET_MAX_RANGE_HUMANS * TURRET_MAX_RANGE_HUMANS #define TURRET_MAX_RANGE_SPECIALS_OPTIMIZED TURRET_MAX_RANGE_SPECIALS * TURRET_MAX_RANGE_SPECIALS #define TURRET_MAX_RANGE_INFECTED_OPTIMIZED TURRET_MAX_RANGE_INFECTED * TURRET_MAX_RANGE_INFECTED #define TURRET_NORMAL_PHASE_TICKS 15 // The number of ticks to be in normal operation #define TURRET_COMMON_PHASE_TICKS 5 // The number of ticks to clear out commons exclusively #define _TURRET_PHASE_TICKS TURRET_NORMAL_PHASE_TICKS + TURRET_COMMON_PHASE_TICKS #define PLUGIN_VERSION "1.0" #include #include #include #include // #include #define PARTICLE_ELMOS "st_elmos_fire_cp0" #define PARTICLE_TES1 "electrical_arc_01_system" #define ENT_PORTAL_NAME "turret" #define SOUND_LASER_FIRE "custom/xen_teleport.mp3" #define TEAM_SPECIALS 3 #define TEAM_SURVIVORS 2 // #define SOUND_LASER_FIRE "level/puck_impact.wav" #include int g_iLaserIndex; int g_iBeamSprite; int g_iHaloSprite; int manualTargetter; Handle thinkTimer; ConVar cv_autoBaseDamage; ConVar cv_manualBaseDamage; static int COLOR_RED[4] = { 255, 0, 0, 200 }; static int COLOR_RED_LIGHT[4] = { 150, 0, 0, 150 }; int manualTarget = -1; #define MANUAL_TARGETNAME "turret_target_manual" ArrayList turretIds; /* TODO: Entity_ChangeOverTime` acquire all turrets on plugin start, then go through list laser charge up - yellow to red - thick to thin - sound --- or possibly weak damage to higher? on death: kill info_target? keep ref of info_target, or just find via targetname? clear all perm props (info_target, env_laser?) on round end dont constantly wipe info_target, just teleport keep data if ent is being targetted? dont target last ent? incase stuck optimize by keeping turret pos? */ public Plugin myinfo = { name = "l4d2 turret", author = "jackzmc", description = "", version = PLUGIN_VERSION, url = "https://github.com/Jackzmc/sourcemod-plugins" }; public void OnPluginStart() { EngineVersion g_Game = GetEngineVersion(); if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { SetFailState("This plugin is for L4D/L4D2 only."); } turretIds = new ArrayList(); FindTurrets(); HookEvent("player_death", Event_PlayerDeath); HookEvent("tank_killed", Event_PlayerDeath); cv_autoBaseDamage = CreateConVar("turret_auto_damage", "50.0", "The base damage the automatic turret deals", FCVAR_NONE, true, 0.0); cv_manualBaseDamage = CreateConVar("turret_manual_damage", "70.0", "The base damage the manual turret deals", FCVAR_NONE, true, 0.0); RegAdminCmd("sm_turret", Command_SpawnTurret, ADMFLAG_CHEATS); RegAdminCmd("sm_rmturrets", Command_RemoveTurrets, ADMFLAG_CHEATS); RegAdminCmd("sm_rmturret", Command_RemoveTurret, ADMFLAG_CHEATS); RegAdminCmd("sm_manturret", Command_ManualTarget, ADMFLAG_CHEATS); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i)) { SDKHook(i, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); } } } public void OnPluginEnd() { ClearTurrets(false); } enum TurretState { Turret_Disabled = -1, Turret_Invalid = 0, Turret_Idle, Turret_Active } TurretState turretState[2048]; int turretActivatorParticle[2048]; int entityActiveTurret[2048]; // mapping the turret thats active on victim. [victim] = turret int turretActiveEntity[2048]; int turretPhaseOffset[2048]; // slight of offset so they dont all enter the same phase at same time bool turretIsActiveLaser[2048]; bool pendingDeletion[2048]; float turretDamage[2048]; int turretCount; void FindTurrets() { int entity = INVALID_ENT_REFERENCE; char targetname[32]; while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrEqual(targetname, "turret")) { SetupTurret(entity); PrintToServer("Found existing turret: %d", entity); } } } void SetupTurret(int turret, float time = 0.0) { float pos[3]; GetEntPropVector(turret, Prop_Send, "m_vecOrigin", pos); turretState[turret] = Turret_Disabled; turretActivatorParticle[turret] = INVALID_ENT_REFERENCE; char targetName[32]; Format(targetName, sizeof(targetName), "laser_target_%d", turret); CreateTimer(time, Timer_ActivateTurret, turret); turretCount++; if(thinkTimer == null) { PrintToServer("Created turret think timer"); thinkTimer = CreateTimer(0.1, Timer_Think, _, TIMER_REPEAT); } // Clamp to 0 -> _TURRET_PHASE_TICKS - 1 turretPhaseOffset[turret] = turretIds.Length % (_TURRET_PHASE_TICKS - 1); turretIds.Push(turret); } Action Timer_ActivateTurret(Handle h, int turret) { turretState[turret] = Turret_Idle; return Plugin_Handled; } void DeactivateTurret(int turret) { int particle = EntRefToEntIndex(turretActivatorParticle[turret]); if(IsValidEntity(particle)) AcceptEntityInput(particle, "Kill"); turretActivatorParticle[turret] = INVALID_ENT_REFERENCE; turretState[turret] = Turret_Idle; turretActiveEntity[turret] = 0; } int ClearTurrets(bool fullClear = true) { turretIds.Clear(); int entity = INVALID_ENT_REFERENCE; int count; char targetname[32]; if(fullClear) { while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(turretState[entity] != Turret_Invalid) { count++; AcceptEntityInput(entity, "Kill"); int particle = EntRefToEntIndex(turretActivatorParticle[entity]); if(IsValidEntity(particle)) AcceptEntityInput(particle, "Kill"); turretState[entity] = Turret_Invalid; turretActivatorParticle[entity] = 0; } else if(StrEqual(targetname, "turret_activate")) { AcceptEntityInput(entity, "Kill"); } } entity = INVALID_ENT_REFERENCE; } while ((entity = FindEntityByClassname(entity, "env_laser")) != INVALID_ENT_REFERENCE) { if(turretIsActiveLaser[entity]) { AcceptEntityInput(entity, "TurnOff"); AcceptEntityInput(entity, "Kill"); } } entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, "info_target")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrContains(targetname, "turret_target_") > -1 || StrEqual(targetname, MANUAL_TARGETNAME)) { AcceptEntityInput(entity, "Kill"); } } for(int i = 1; i < 2048; i++) { entityActiveTurret[i] = 0; pendingDeletion[i] = false; } turretCount = 0; if(IsValidHandle(thinkTimer)) { delete thinkTimer; } return count; } public void OnClientPutInServer(int client) { pendingDeletion[client] = false; SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); } public void OnClientDisconnect(int client) { if(manualTargetter == client) manualTargetter = 0; } public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { if(attacker > MaxClients && attacker < 2048 && turretIsActiveLaser[attacker] && GetClientTeam(victim) != 3) { int health = L4D_GetPlayerTempHealth(victim); L4D_SetPlayerTempHealth(victim, health); damage = 0.0; return Plugin_Stop; } return Plugin_Continue; } public void OnMapEnd() { manualTarget = -1; ClearTurrets(); } public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); int index = event.GetInt("entindex", 0); int turret = entityActiveTurret[client]; if(turret > 0) { pendingDeletion[client] = true; turretActiveEntity[turret] = 0; DeactivateTurret(turret); } entityActiveTurret[index] = 0; entityActiveTurret[client] = 0; } public void OnEntityDestroyed(int entity) { if(entity > 0 && entity <= 2048) { pendingDeletion[entity] = false; int turret = entityActiveTurret[entity]; if(turret > 0) { DeactivateTurret(turret); } entityActiveTurret[entity] = 0; } } public Action Command_SpawnTurret(int client, int args) { float pos[3]; GetClientEyePosition(client, pos); // pos[2] += 10.0; int base = CreateParticleNamed(ENT_PORTAL_NAME, PARTICLE_ELMOS, pos, NULL_VECTOR); SetupTurret(base, TURRET_ACTIVATION_TIME); ReplyToCommand(client, "New turret (%d) will activate in %.0f seconds", base, TURRET_ACTIVATION_TIME); return Plugin_Handled; } public Action Command_ManualTarget(int client, int args) { // Remove the activator particles int entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { if(view_as(turretState[entity]) > 0) { DeactivateTurret(entity); } } if(manualTargetter == client) { manualTargetter = 0; ReplyToCommand(client, "No longer manually targetting"); return Plugin_Handled; } else if(manualTargetter > 0) { ReplyToCommand(manualTargetter, "%N is now manually targetting", client); } if(turretCount == 0) { ReplyToCommand(client, "There are no turrets to manually target"); } else { manualTargetter = client; ReplyToCommand(client, "Now manually targetting"); } return Plugin_Handled; } public Action Command_RemoveTurrets(int client, int args) { int count = ClearTurrets(); /*int entity = INVALID_ENT_REFERENCE; char targetname[32]; int count; while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrEqual(targetname, ENT_PORTAL_NAME)) { AcceptEntityInput(entity, "Kill"); count++; } else if(StrEqual(targetname, "turret_activate")) { AcceptEntityInput(entity, "Kill"); } }*/ ReplyToCommand(client, "Removed %d turrets", count); return Plugin_Handled; } public Action Command_RemoveTurret(int client, int args) { if(turretIds.Length == 0) { ReplyToCommand(client, "No turrets to remove"); } else { int lastTurret = turretIds.Get(turretIds.Length - 1); ReplyToCommand(client, "Removed last turret %d", lastTurret); } return Plugin_Handled; } public Action Timer_Think(Handle h) { if( manualTargetter > 0) return Plugin_Continue; // Probably better to just store from CreateParticle static int entity; entity = INVALID_ENT_REFERENCE; // static char targetname[32]; static float pos[3]; static int count, target, tick; while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { // GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); // if(StrEqual(targetname, ENT_PORTAL_NAME)) { if(view_as(turretState[entity]) > 0) { GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); if(turretState[entity] == Turret_Active) { // 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)) { FireTurretAuto(pos, target, turretDamage[entity]); continue; } entityActiveTurret[target] = 0; } DeactivateTurret(entity); } // Skip activation if a survivor is too close if(FindNearestClient(TEAM_SURVIVORS, pos, TURRET_MAX_RANGE_HUMANS_OPTIMIZED) > 0) { continue; } bool inNormalPhase = ((tick + turretPhaseOffset[entity]) % _TURRET_PHASE_TICKS) <= TURRET_NORMAL_PHASE_TICKS; // Find a target, in this order: Tank Rock -> Specials -> Infected float damage = cv_autoBaseDamage.FloatValue; target = -1; if(inNormalPhase) { target = FindNearestVisibleEntity("tank_rock", pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED, entity); if(target > 0) { 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 == -1) target = FindNearestVisibleEntity("infected", pos, TURRET_MAX_RANGE_INFECTED_OPTIMIZED, entity); if(target > 0) { turretDamage[entity] = damage; entityActiveTurret[target] = entity; turretActiveEntity[entity] = EntIndexToEntRef(target); turretActivatorParticle[entity] = EntIndexToEntRef(CreateParticleNamed("turret_activate", PARTICLE_TES1, pos, NULL_VECTOR)); // AcceptEntityInput(turretActivatorParticle[entity], "Start"); FireTurretAuto(pos, target, turretDamage[entity]); turretState[entity] = Turret_Active; } // Optimization incase there's multiple info_particle_system if(++count > turretCount) { count = 0; break; } } } if(++tick >= _TURRET_PHASE_TICKS) { tick = 0; } return Plugin_Continue; } public Action Timer_KillRock(Handle h, int ref) { int rock = EntRefToEntIndex(ref); if(rock != INVALID_ENT_REFERENCE) { L4D_DetonateProjectile(rock); } return Plugin_Handled; } static float TURRET_LASER_COLOR[3] = { 0.0, 255.0, 255.0 }; void FireTurretAuto(const float origin[3], int targetEntity, float damage = 105.0) { int laser = CreateLaserAuto(origin, targetEntity, TURRET_LASER_COLOR, damage, 1.0, 0.2); EmitSoundToAll(SOUND_LASER_FIRE, laser, SNDCHAN_WEAPON, .flags = SND_CHANGEPITCH, .pitch = 150); turretIsActiveLaser[laser] = true; } void FireTurret(const float origin[3], const char[] targetName, float damage = 105.0, bool emitSound = true) { int laser = CreateLaser(origin, targetName, TURRET_LASER_COLOR, damage, 1.0, 0.1); if(emitSound) EmitSoundToAll(SOUND_LASER_FIRE, laser, SNDCHAN_WEAPON, .flags = SND_CHANGEPITCH, .pitch = 150); turretIsActiveLaser[laser] = true; } stock int CreateLaser(const float origin[3], const char[] targetName, float color[3], float damage, float width, float duration) { int laser = CreateEntityByName("env_laser"); if(laser > 0) { DispatchKeyValue(laser, "targetname", "sm_laser"); DispatchKeyValue(laser, "LaserTarget", targetName); DispatchKeyValue(laser, "spawnflags", "1"); // DispatchKeyValue(laser, "dissolvetype", "2"); DispatchKeyValue(laser, "NoiseAmplitude", "1"); DispatchKeyValueFloat(laser, "damage", damage); DispatchKeyValueFloat(laser, "life", duration); DispatchKeyValueVector(laser, "rendercolor", color); DispatchKeyValue(laser, "texture", "sprites/laserbeam.spr"); TeleportEntity(laser, origin); SetEntPropFloat(laser, Prop_Data, "m_fWidth", width); DispatchSpawn(laser); if(duration > 0) CreateTimer(duration, Timer_Kill, laser); } return laser; } // Creates a beam from beginTarget to endTarget. If target name starts with NUL, then will not be set. See env_beam on wiki stock int CreateBeam(const char[] beginTarget, const float center[3], const char[] endTarget, float color[3], float damage, float width, float duration) { int laser = CreateEntityByName("env_beam"); if(laser > 0) { DispatchKeyValue(laser, "targetname", "sm_laserbeam"); if(beginTarget[0] != '\0') DispatchKeyValue(laser, "LightningStart", beginTarget); if(endTarget[0] != '\0') DispatchKeyValue(laser, "LightningEnd", endTarget); DispatchKeyValue(laser, "spawnflags", "1"); // DispatchKeyValue(laser, "dissolvetype", "2"); DispatchKeyValue(laser, "NoiseAmplitude", "1"); DispatchKeyValueFloat(laser, "damage", damage); DispatchKeyValueFloat(laser, "life", duration); DispatchKeyValueVector(laser, "rendercolor", color); DispatchKeyValue(laser, "texture", "sprites/laserbeam.spr"); TeleportEntity(laser, center); DispatchKeyValueFloat(laser, "BoltWidth", width); DispatchSpawn(laser); if(duration > 0) CreateTimer(duration, Timer_Kill, laser); } return laser; } stock int CreateLaserAuto(const float origin[3], int targetEnt, float color[3], float damage = 0.0, float width, float duration = 5.0, bool createInfoTarget = true) { static char targetName[32]; Format(targetName, sizeof(targetName), "laser_target_%d", targetEnt); static float pos[3]; GetEntPropVector(targetEnt, Prop_Send, "m_vecOrigin", pos); pos[2] += 30.0; int target = CreateTarget(pos, targetName, duration); SetParent(target, targetEnt); return CreateLaser(origin, targetName, color, damage, width, duration); } int CreateTarget(const float origin[3], const char[] targetName, float duration = 0.0) { int target = CreateEntityByName("info_target"); DispatchKeyValue(target, "targetname", targetName); TeleportEntity(target, origin, NULL_VECTOR, NULL_VECTOR); DispatchSpawn(target); if(duration > 0.0) { CreateTimer(duration, Timer_Kill, target); } return target; } stock int FindNearestClient(int team, const float origin[3], float maxRange = 0.0) { int client = -1; float closestDist, pos[3]; for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == team && !pendingDeletion[i]) { GetClientAbsOrigin(i, pos); float distance = GetVectorDistance(origin, pos, true); if(maxRange > 0.0 && distance > maxRange) continue; if(client == -1 || distance <= closestDist) { client = i; closestDist = distance; } } } return client; } stock int FindNearestVisibleClient(int team, const float origin[3], float maxRange = 0.0) { int client = -1; float closestDist, pos[3]; for(int i = 1; i <= MaxClients; i++) { if(!pendingDeletion[i] && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == team) { GetClientAbsOrigin(i, pos); float distance = GetVectorDistance(origin, pos, true); if(maxRange > 0.0 && distance > maxRange) continue; if(distance <= closestDist || client == -1) { if(CanSeePoint(origin, pos)) { // Priority: Pinned survivors if(L4D_GetPinnedSurvivor(i)) { return i; } client = i; closestDist = distance; } } } } return client; } stock int FindNearestVisibleEntity(const char[] classname, const float origin[3], float maxRange = 0.0, int turretIndex) { int entity = INVALID_ENT_REFERENCE; static float pos[3]; while ((entity = FindEntityByClassname(entity, classname)) != INVALID_ENT_REFERENCE) { if(entityActiveTurret[entity] > 0) continue; bool ragdoll = GetEntProp(entity, Prop_Data, "m_bClientSideRagdoll") == 1; if(ragdoll) continue; // if(GetEntProp(entity, Prop_Send, "m_iHealth") <= 0) continue; GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); if(maxRange > 0.0 && GetVectorDistance(origin, pos, true) > maxRange) continue; pos[2] += 40.0; if(CanSeePoint(origin, pos)) { return entity; } } return -1; } stock bool CanSeePoint(const float origin[3], const float point[3]) { TR_TraceRay(origin, point, MASK_SOLID, RayType_EndPoint); return !TR_DidHit(); // Can see point if no collisions } 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); return TR_GetEntityIndex() == entity; // Can see point if no collisions } bool Filter_CanSeeEntity(int entity, int contentsMask, int data) { return entity != data; } public void OnMapStart() { PrecacheParticle(PARTICLE_ELMOS); PrecacheParticle(PARTICLE_TES1); g_iBeamSprite = PrecacheModel("sprites/laser.vmt", true); g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); PrecacheSound(SOUND_LASER_FIRE); if(g_iLaserIndex == 0) { LogError("g_iLaserIndex failed"); } } stock int CreateParticleNamed(const char[] targetname, const char[] sParticle, const float vPos[3], const float vAng[3], int parent = 0) { int entity = CreateEntityByName("info_particle_system"); if( entity != -1 ) { DispatchKeyValue(entity, "effect_name", sParticle); DispatchKeyValue(entity, "targetname", targetname); DispatchSpawn(entity); ActivateEntity(entity); AcceptEntityInput(entity, "start"); if(parent){ SetParent(entity, parent); } TeleportEntity(entity, vPos, vAng, NULL_VECTOR); // Refire float refire = 0.2; static char sTemp[64]; Format(sTemp, sizeof(sTemp), "OnUser1 !self:Stop::%f:-1", refire - 0.05); SetVariantString(sTemp); AcceptEntityInput(entity, "AddOutput"); Format(sTemp, sizeof(sTemp), "OnUser1 !self:FireUser2::%f:-1", refire); SetVariantString(sTemp); AcceptEntityInput(entity, "AddOutput"); AcceptEntityInput(entity, "FireUser1"); SetVariantString("OnUser2 !self:Start::0:-1"); AcceptEntityInput(entity, "AddOutput"); SetVariantString("OnUser2 !self:FireUser1::0:-1"); AcceptEntityInput(entity, "AddOutput"); return entity; } return -1; } stock void SetParent(int child, int parent) { SetVariantString("!activator"); AcceptEntityInput(child, "SetParent", parent); } /*#define MAX_IGNORE_TRACE 2 static char IGNORE_TRACE[MAX_IGNORE_TRACE][] = { "env_physics_blocker", "env_player_blocker" };*/ #define MAX_WHITELISTED_AUTO_AIM_TARGETS 3 static char WHITELISTED_AUTO_AIM_TARGETS[MAX_WHITELISTED_AUTO_AIM_TARGETS][] = { "tank_rock", "infected", "witch" }; bool Filter_ManualTarget(int entity, int contentsMask, int data) { if(entity == 0 || entity == data) return true; if(entity == manualTarget || entity == manualTargetter) return false; return true; /*static char classname[32]; GetEntityClassname(entity, classname, sizeof(classname)); for(int i = 0; i < MAX_IGNORE_TRACE; i++) { if(StrEqual(IGNORE_TRACE[i], classname)) { return false; } } return true;*/ } bool Filter_ManualTargetSights(int entity, int contentsMask, int data) { if(entity == 0 || entity == data) return true; if(entity == manualTarget || entity == manualTargetter) return false; if(entity <= MaxClients) return GetClientTeam(entity) == 3; static char classname[32]; GetEntityClassname(entity, classname, sizeof(classname)); for(int i = 0; i < MAX_WHITELISTED_AUTO_AIM_TARGETS; i++) { if(StrEqual(WHITELISTED_AUTO_AIM_TARGETS[i], classname)) { return false; } } return true; } 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(client == manualTargetter && turretCount > 0 && tickcount % 3 == 0) { static float pos[3], aimPos[3], orgPos[3]; GetClientEyePosition(client, orgPos); // Run a ray trace to find a suitable position // TODO: Possibly run per-turret for more accurate preview... but it's already lag fest TR_TraceRayFilter(orgPos, angles, MASK_SHOT, RayType_Infinite, Filter_ManualTarget); if(manualTarget <= 0 || !IsValidEntity(manualTarget)) manualTarget = CreateTarget(aimPos, MANUAL_TARGETNAME); // Disable aim snapping if player is holding WALK (which is apparently IN_SPEED) bool aimSnapping = ~buttons & IN_SPEED > 0; int targetEntity = TR_GetEntityIndex(); TR_GetEndPosition(aimPos); if(aimSnapping) ComputeAutoAim(targetEntity, aimPos); TeleportEntity(manualTarget, aimPos, NULL_VECTOR, NULL_VECTOR); if(buttons & IN_ATTACK) { PhysicsExplode(aimPos, 20, 20.0, true); TE_SetupExplodeForce(aimPos, 20.0, 20.0); } // Activate all turrets int entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { if(view_as(turretState[entity]) > 0) { GetEntPropVector(entity, Prop_Send, "m_vecOrigin", orgPos); if(buttons & IN_ATTACK) { FireTurret(orgPos, MANUAL_TARGETNAME, cv_manualBaseDamage.FloatValue, tickcount % 6 == 0); } else { TR_TraceRayFilter(orgPos, aimPos, MASK_SOLID, RayType_EndPoint, Filter_ManualTargetSights, targetEntity); pos = aimPos; if(TR_DidHit()) { TR_GetEndPosition(pos); TE_SetupBeamPoints(orgPos, pos, g_iLaserIndex, 0, 0, 1, 0.1, 0.1, 0.1, 0, 0.0, COLOR_RED_LIGHT, 1); } else { TE_SetupBeamPoints(orgPos, aimPos, g_iLaserIndex, 0, 0, 1, 0.1, 0.1, 0.1, 0, 0.0, COLOR_RED, 2); } // if(aimSnapping) ComputeAutoAim(TR_GetEntityIndex(), pos); TE_SendToAll(); } } } buttons &= ~IN_ATTACK; SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 1.0); return Plugin_Changed; } return Plugin_Continue; } bool ComputeAutoAim(int possibleTarget, float pos[3]) { static char classname[64]; if(possibleTarget > 0) { if(possibleTarget > MaxClients) { // Check if aimed non-player entity is an entity to be auto aimed at GetEntityClassname(possibleTarget, classname, sizeof(classname)); for(int i = 0; i < MAX_WHITELISTED_AUTO_AIM_TARGETS; i++) { if(StrEqual(WHITELISTED_AUTO_AIM_TARGETS[i], classname)) { GetEntPropVector(possibleTarget, Prop_Send, "m_vecOrigin", pos); pos[2] += 40.0; return true; } } } else if(GetClientTeam(possibleTarget) == 3) { // Target is an infected player, auto aim GetClientEyePosition(possibleTarget, pos); pos[2] -= 15.0; return true; } } return false; } public Action Timer_Kill(Handle h, int target) { if(IsValidEntity(target)) // TODO: See if necessary AcceptEntityInput(target, "Kill"); return Plugin_Handled; }