diff --git a/plugins/L4D2Tools.smx b/plugins/L4D2Tools.smx index b1487c6..ee8f410 100644 Binary files a/plugins/L4D2Tools.smx and b/plugins/L4D2Tools.smx differ diff --git a/plugins/jutils.smx b/plugins/jutils.smx index 7dc10b1..bba3408 100644 Binary files a/plugins/jutils.smx and b/plugins/jutils.smx differ diff --git a/plugins/l4d2_feedthetrolls.smx b/plugins/l4d2_feedthetrolls.smx index 638914b..d929fe9 100644 Binary files a/plugins/l4d2_feedthetrolls.smx and b/plugins/l4d2_feedthetrolls.smx differ diff --git a/scripting/L4D2Tools.sp b/scripting/L4D2Tools.sp index 2264d7f..ca200c4 100644 --- a/scripting/L4D2Tools.sp +++ b/scripting/L4D2Tools.sp @@ -47,7 +47,7 @@ enum L4DModelId { } static ArrayList LasersUsed; -static ConVar hLaserNotice, hFinaleTimer, hFFNotice, hMPGamemode, hPingDropThres, hForceSurvivorSet; +static ConVar hLaserNotice, hFinaleTimer, hFFNotice, hMPGamemode, hPingDropThres, hForceSurvivorSet, hPlayerLimit, hSVMaxPlayers; static int iFinaleStartTime, botDropMeleeWeapon[MAXPLAYERS+1], iHighPingCount[MAXPLAYERS+1]; ReserveMode reserveMode; static bool isHighPingIdle[MAXPLAYERS+1], isL4D1Survivors; @@ -99,6 +99,12 @@ public void OnPluginStart() { hPingDropThres = CreateConVar("sm_autoidle_ping_max", "0.0", "The highest ping a player can have until they will automatically go idle.\n0=OFF, Min is 30", FCVAR_NONE, true, 0.0, true, 1000.0); hForceSurvivorSet = FindConVar("l4d_force_survivorset"); + hSVMaxPlayers = FindConVar("sv_maxplayers"); + hPlayerLimit = CreateConVar("sm_player_limit", "0", "Overrides sv_maxplayers. 0 = off, > 0: limit", FCVAR_NONE, true, 0.0, false); + hPlayerLimit.AddChangeHook(Event_PlayerLimitChange); + hSVMaxPlayers.IntValue = hPlayerLimit.IntValue; + + hFFNotice.AddChangeHook(CVC_FFNotice); if(hFFNotice.IntValue > 0) { HookEvent("player_hurt", Event_PlayerHurt); @@ -156,6 +162,13 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[ cvar.GetString(gamemode, sizeof(gamemode)); } +public void Event_PlayerLimitChange(ConVar cvar, const char[] oldValue, const char[] newValue) { + if(cvar.IntValue > 0) { + hSVMaxPlayers.IntValue = cvar.IntValue; + } +} + + public void OnClientConnected(int client) { if(!IsFakeClient(client) && reserveMode == Reserve_Watch) { PrintChatToAdmins("%N is connecting", client); @@ -658,7 +671,14 @@ public void OnMapStart() { HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); + +} +public void OnConfigsExecuted() { isL4D1Survivors = L4D2_GetSurvivorSetMap() == 1; + + if(hPlayerLimit.IntValue > 0) { + hSVMaxPlayers.IntValue = hPlayerLimit.IntValue; + } } public void OnSceneStageChanged(int scene, SceneStages stage) { diff --git a/scripting/include/feedthetrolls/base.inc b/scripting/include/feedthetrolls/base.inc index e86aa12..62ee69a 100644 --- a/scripting/include/feedthetrolls/base.inc +++ b/scripting/include/feedthetrolls/base.inc @@ -4,7 +4,7 @@ //Allow MAX_TROLLS to be defined elsewhere #if defined MAX_TROLLS #else - #define MAX_TROLLS 37 + #define MAX_TROLLS 38 #endif enum trollModifier { @@ -358,36 +358,36 @@ void ApplyTroll(int victim, const char[] name, int activator, trollModifier modi bool isActive = IsTrollActive(victim, troll.name); if(!silent) { if(isActive) { - ShowActivityEx(activator, "[FTT] ", "deactivated troll \"%s\" on %N. ", troll.name, victim); - LogAction(activator, victim, "\"%L\" deactivated troll \"%s\" on \"%L\"", activator, troll.name, victim); + ShowActivityEx(activator, "[FTT] ", "deactivated \"%s\" 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]; - if(flags > 0 && flags & flags - 1 == 0) { + if(flags > 0 && flags & flags - 1 == 0 && flags & flags + 1 == 0) { // Get the flag name if there is only one flag set troll.GetFlagName(GetIndexFromPower(flags), flagName, sizeof(flagName)); } if(modifier & TrollMod_Constant) { if(flags > 0) { if(flagName[0] != '\0') { - ShowActivityEx(activator, "[FTT] ", "activated constant troll \"%s\" (%s) for %N. ", troll.name, flagName, victim); + ShowActivityEx(activator, "[FTT] ", "activated constant \"%s\" (%s) for %N. ", troll.name, flagName, victim); } else { - ShowActivityEx(activator, "[FTT] ", "activated constant troll \"%s\" (%d) for %N. ", troll.name, flags, victim); + ShowActivityEx(activator, "[FTT] ", "activated constant \"%s\" (%d) for %N. ", troll.name, flags, victim); } } else - ShowActivityEx(activator, "[FTT] ", "activated constant troll \"%s\" for %N. ", troll.name, victim); + ShowActivityEx(activator, "[FTT] ", "activated constant \"%s\" for %N. ", troll.name, victim); } else if(flags > 0) { if(flagName[0] != '\0') { - ShowActivityEx(activator, "[FTT] ", "activated troll \"%s\" (%s) for %N. ", troll.name, flagName, victim); + ShowActivityEx(activator, "[FTT] ", "activated \"%s\" (%s) for %N. ", troll.name, flagName, victim); } else { - ShowActivityEx(activator, "[FTT] ", "activated troll \"%s\" (%d) for %N. ", troll.name, flags, victim); + ShowActivityEx(activator, "[FTT] ", "activated \"%s\" (%d) for %N. ", troll.name, flags, victim); } } else - ShowActivityEx(activator, "[FTT] ", "activated troll \"%s\" for %N. ", troll.name, victim); + ShowActivityEx(activator, "[FTT] ", "activated \"%s\" for %N. ", troll.name, victim); - LogAction(activator, victim, "\"%L\" activated troll \"%s\" (%d) for \"%L\"", activator, troll.name, flags, victim); + LogAction(activator, victim, "\"%L\" activated \"%s\" (%d) for \"%L\"", activator, troll.name, flags, victim); } } else { - ReplyToCommand(activator, "ftt: Applied Troll \"%s\" on %N with flags=%d", troll.name, victim, flags); + ReplyToCommand(activator, "ftt: Applied \"%s\" on %N with flags=%d", troll.name, victim, flags); } // Toggle on flags for client, if it's not a single run. if(modifier & TrollMod_Constant) { diff --git a/scripting/include/feedthetrolls/events.inc b/scripting/include/feedthetrolls/events.inc index e76055f..b0f3371 100644 --- a/scripting/include/feedthetrolls/events.inc +++ b/scripting/include/feedthetrolls/events.inc @@ -16,6 +16,8 @@ public void OnMapStart() { PrecacheSound("weapons/ceda_jar/ceda_jar_explode.wav"); PrecacheSound("weapons/molotov/molotov_detonate_1.wav"); + PrecacheSound(MODEL_CAR); + g_spSpawnQueue.Clear(); spIsActive = false; //CreateTimer(30.0, Timer_AutoPunishCheck, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); @@ -44,6 +46,7 @@ public void Event_SecondaryHealthUsed(Event event, const char[] name, bool dontB L4D_StaggerPlayer(client, client, NULL_VECTOR); } } +static float SPIT_VEL[3] = { 0.0, 0.0, -1.0 }; public Action Timer_CheckSpecial(Handle h, int specialID) { int special = GetClientOfUserId(specialID); // Check if new player is the spawned special: @@ -60,9 +63,16 @@ public Action Timer_CheckSpecial(Handle h, int specialID) { TeleportEntity(special, spActiveRequest.position, spActiveRequest.angle, NULL_VECTOR); if(spActiveRequest.flags & view_as(SPI_KillOnSpawn)) { + if(type == Special_Spitter) { + float pos[3]; + GetClientEyePosition(special, pos); + L4D2_SpitterPrj(special, pos, SPIT_VEL); + } RequestFrame(Frame_Boom, special); } + g_iSpId++; + ProcessSpecialQueue(); } } @@ -96,21 +106,16 @@ public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroa shootAtTargetHP[client] = 0; } public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); + int userid = event.GetInt("userid"); + int client = GetClientOfUserId(userid); if(client > 0) { if(g_iAttackerTarget[client] > 0) { // If special died, clear & subtract one from counter - int target = GetClientOfUserId(g_iAttackerTarget[client]); - gInstaSpecialMagnet[target]--; - if(gInstaSpecialMagnet[target] == 0) { - PrintToServer("[FTT] gInstaSpecialMagnet dropped below 0"); - gInstaSpecialMagnet[target] = 0; - } g_iAttackerTarget[client] = 0; } else { // If player died, stop the targetting for(int i = 1; i <= MaxClients; i++) { - if(g_iAttackerTarget[i] == client) { + if(g_iAttackerTarget[i] == userid) { g_iAttackerTarget[i] = 0; break; } diff --git a/scripting/include/feedthetrolls/misc.inc b/scripting/include/feedthetrolls/misc.inc index e3656f2..7f28cdc 100644 --- a/scripting/include/feedthetrolls/misc.inc +++ b/scripting/include/feedthetrolls/misc.inc @@ -377,3 +377,51 @@ stock void ExplodeProjectile(int entity, bool smoke = true) { if(smoke) SetEntProp(entity, Prop_Data, "m_nNextThinkTick", 1); //for smoke } + +stock bool IsAreaClear(const float pos[3], const float ang[3]) { + float min[3] = { -30.0, -30.0, -2.0}; + float max[3] = { 30.0, 30.0, 50.0 }; + + // DebugTraceHull(pos, pos, min, max); + TR_TraceHullFilter(pos, pos, min, max, MASK_SOLID, Filter_Solid); + if(TR_DidHit()) { + return false; + } + return true; +} + +bool SpawnCarOnPlayer(int target) { + float pos[3]; + float ang[3]; + GetClientEyePosition(target, pos); + GetClientEyeAngles(target, ang); + if(IsAreaClear(pos, ang)) { + pos[2] += 40.0; + int id = CreateProp("prop_physics", MODEL_CAR, pos, ang); + CreateTimer(4.0, Timer_Delete, id); + return true; + } + return false; +} + +stock int CreateProp(const char[] entClass, const char[] model, const float pos[3], const float ang[3]) { + int entity = CreateEntityByName(entClass); + DispatchKeyValue(entity, "model", model); + DispatchKeyValue(entity, "solid", "6"); + DispatchKeyValue(entity, "targetname", "hsprop"); + DispatchKeyValue(entity, "disableshadows", "1"); + TeleportEntity(entity, pos, ang, NULL_VECTOR); + DispatchSpawn(entity); + #if defined DEBUG_LOG_MAPSTART + PrintToServer("spawn prop %.1f %.1f %.1f model %s", pos[0], pos[1], pos[2], model[7]); + #endif + return entity; +} + +public bool Filter_Solid(int entity, int contentsMask, any data) { + return entity <= 0; +} + +public Action Timer_Delete(Handle h, int id) { + AcceptEntityInput(id, "Kill"); +} \ No newline at end of file diff --git a/scripting/include/feedthetrolls/specials.inc b/scripting/include/feedthetrolls/specials.inc index 40c0a54..a20c7d9 100644 --- a/scripting/include/feedthetrolls/specials.inc +++ b/scripting/include/feedthetrolls/specials.inc @@ -35,10 +35,10 @@ stock bool SpawnSpecialForTarget(SpecialType specialType, int target, SpecialSpa GetClientAbsOrigin(target, pos); GetClientEyeAngles(target, ang); - if(specialType == Special_Boomer) + if(specialType == Special_Boomer || specialType == Special_Spitter) internalFlags |= view_as(SPI_KillOnSpawn); - if(specialType == Special_Jockey || specialType == Special_Boomer) { // Else spawn a little bit off, and above (above for jockeys) + if(specialType == Special_Jockey || specialType == Special_Boomer || specialType == Special_Spitter) { // Else spawn a little bit off, and above (above for jockeys) pos[2] += 25.0; pos[0] += 5.0; } else { //If not jockey/hunter find a suitable area that is at least 5 m away @@ -75,10 +75,15 @@ stock bool SpawnSpecialAtPosition(SpecialType special, const float destination[3 SpecialSpawnRequest spActiveRequest; +// On spawn, keep id +// timer checks if passed id still == kept id, if so, it failed. +int g_iSpId; + bool ProcessSpecialQueue() { if(g_spSpawnQueue.Length > 0) { if(g_spSpawnQueue.GetArray(0, spActiveRequest, sizeof(spActiveRequest))) { spIsActive = true; + CreateTimer(4.0, Timer_CheckSpecialSpawned, g_iSpId); } g_spSpawnQueue.Erase(0); diff --git a/scripting/include/feedthetrolls/timers.inc b/scripting/include/feedthetrolls/timers.inc index bd8468e..e1c0e27 100644 --- a/scripting/include/feedthetrolls/timers.inc +++ b/scripting/include/feedthetrolls/timers.inc @@ -140,4 +140,12 @@ public Action Timer_ShootReverse(Handle h, DataPack pack) { shootAtTargetHP[target] = 0; return Plugin_Stop; } +} + +public Action Timer_CheckSpecialSpawned(Handle h, int id) { + if(g_iSpId == id) { + PrintToServer("[FTT] Special did not spawn in time, continuing."); + g_iSpId++; + ProcessSpecialQueue(); + } } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/trolls.inc b/scripting/include/feedthetrolls/trolls.inc index 88de755..fcbd61e 100644 --- a/scripting/include/feedthetrolls/trolls.inc +++ b/scripting/include/feedthetrolls/trolls.inc @@ -130,6 +130,7 @@ void SetupTrolls() { 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("CameTooEarly", "When they shoot, random chance they empty whole clip", TrollMod_Constant); + index = SetupTroll("Car Splat", "Car. splats.", TrollMod_Instant); index = SetupTroll("Meta: Inverse", "Uhm you are not supposed to see this...", TrollMod_Instant); Trolls[index].hidden = true; Trolls[index].AddFlagPrompt(false); @@ -158,8 +159,8 @@ void AddMagnetFlags(int index) { bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier modifier, int flags) { bool isActive = IsTrollActiveByRawID(victim, troll.id); if(StrEqual(troll.name, "Reset User")) { - LogAction(activator, victim, "\"%L\" reset all troll effects for \"%L\"", activator, victim); - ShowActivityEx(activator, "[FTT] ", "reset troll effects for %N. ", victim); + 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; } @@ -244,6 +245,11 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod SpawnSpecialForTarget(view_as(i), victim, spawnFlag); } } + } else if(StrEqual(troll.name, "Car Splat")) { + if(!SpawnCarOnPlayer(victim)) { + ReplyToCommand(activator, "Could not find a suitable area to spawn a car"); + return false; + } } else if(modifier != TrollMod_Constant) { PrintToServer("[FTT] Warn: Possibly invalid troll, no apply action defined for \"%s\"", troll.name); #if defined DEBUG @@ -251,4 +257,4 @@ bool ApplyAffect(int victim, const Troll troll, int activator, trollModifier mod #endif } return true; -} \ No newline at end of file +} diff --git a/scripting/include/ftt.inc b/scripting/include/ftt.inc index 5b71762..6f89ddf 100644 --- a/scripting/include/ftt.inc +++ b/scripting/include/ftt.inc @@ -58,6 +58,7 @@ enum SpecialInternalFlags { SPI_KillOnSpawn = 1 } +#define MODEL_CAR "models/props_vehicles/cara_95sedan.mdl" #include #include diff --git a/scripting/include/jutils.inc b/scripting/include/jutils.inc index 1dd65b6..a1540ee 100644 --- a/scripting/include/jutils.inc +++ b/scripting/include/jutils.inc @@ -32,6 +32,26 @@ stock void GetHorizontalPositionFromClient(int client, float units, float finalP pos[1] += -150 * Sine(theta); finalPosition = pos; } + // Gets velocity of an entity (ent) toward new origin with speed (fSpeed) - thanks Ryan + stock bool GetVelocityToOrigin(int entity, const float destination[3], const float fSpeed, float outVelocity[3]) { + float srcOrigin[3]; + GetEntPropVector(entity, Prop_Data, "m_vecVelocity", srcOrigin); + // Velocity = Distance / Time + + float fDistance[3]; + fDistance[0] = destination[0] - srcOrigin[0]; + fDistance[1] = destination[1] - srcOrigin[1]; + fDistance[2] = destination[2] - srcOrigin[2]; + + float fTime = (GetVectorDistance(srcOrigin, destination) / fSpeed); + + outVelocity[0] = (destination[0] - srcOrigin[0]) / fTime; + outVelocity[1] = (destination[1] - srcOrigin[1]) / fTime; + outVelocity[2] = (destination[2] - srcOrigin[2]) / fTime; + + return (outVelocity[0] && outVelocity[1] && outVelocity[2]); + } + //Credits to Timocop for the stock :D /** * Runs a single line of vscript code. diff --git a/scripting/l4d2_feedthetrolls.sp b/scripting/l4d2_feedthetrolls.sp index 9a9f97d..f20afa1 100644 --- a/scripting/l4d2_feedthetrolls.sp +++ b/scripting/l4d2_feedthetrolls.sp @@ -20,17 +20,6 @@ #include -/* -TODO IDEAS: -1. [x] Instant pipebomb explosion -2. [x] Spicy gas (instant ignite), ignite propane/gas canisters -3. [x] Amazon Special Combo (flags, choose combinations) (NEED QUEUE) -4. Random weapons (on interval, possible second option of random capacity) -5. [x] Slippery shoes (periodic stagger, or on certain events) -6. (on hold) Sticky goo (slow user down in goo, or freezes) -7. Follow goo -*/ - public Plugin myinfo = { name = "L4D2 Feed The Trolls",