From f1ad9f38655d2e554bb20c58fc9554faf0466b84 Mon Sep 17 00:00:00 2001 From: Jackz Date: Tue, 20 Apr 2021 21:44:43 -0500 Subject: [PATCH] Update updated part of README.md --- README.md | 2 +- plugins/BumpMineGiver.smx | Bin 0 -> 6438 bytes plugins/L4D2PreventBotMovement.smx | Bin 0 -> 4969 bytes plugins/l4d2_drop_secondary.smx | Bin 0 -> 5846 bytes plugins/l4d2_skill_detect.smx | Bin 0 -> 33717 bytes plugins/l4d2_target_test.smx | Bin 0 -> 5245 bytes plugins/l4d_tank_hp_sprite.smx | Bin 12554 -> 13863 bytes plugins/l4d_target_override.smx | Bin 0 -> 15009 bytes plugins/l4dunreservelobby.smx | Bin 0 -> 5715 bytes plugins/left4dhooks.smx | Bin 0 -> 2427 bytes plugins/sceneprocessor.smx | Bin 0 -> 21009 bytes scripting/AutoWarpBot.sp | 19 +- scripting/BumpMineGiver.sp | 114 + scripting/L4D2PreventBotMovement.sp | 57 + scripting/csgo-misc.sp | 60 + scripting/l4d2_drop_secondary.sp | 233 ++ scripting/l4d2_skill_detect.sp | 3125 +++++++++++++++++++++++++++ scripting/l4d2_target_test.sp | 136 ++ scripting/l4d_tank_hp_sprite.sp | 273 ++- scripting/l4dunreservelobby.sp | 149 ++ scripting/sceneprocessor.sp | 855 ++++++++ 21 files changed, 4934 insertions(+), 89 deletions(-) create mode 100644 plugins/BumpMineGiver.smx create mode 100644 plugins/L4D2PreventBotMovement.smx create mode 100644 plugins/l4d2_drop_secondary.smx create mode 100644 plugins/l4d2_skill_detect.smx create mode 100644 plugins/l4d2_target_test.smx create mode 100644 plugins/l4d_target_override.smx create mode 100644 plugins/l4dunreservelobby.smx create mode 100644 plugins/left4dhooks.smx create mode 100644 plugins/sceneprocessor.smx create mode 100644 scripting/BumpMineGiver.sp create mode 100644 scripting/L4D2PreventBotMovement.sp create mode 100644 scripting/csgo-misc.sp create mode 100644 scripting/l4d2_drop_secondary.sp create mode 100644 scripting/l4d2_skill_detect.sp create mode 100644 scripting/l4d2_target_test.sp create mode 100644 scripting/l4dunreservelobby.sp create mode 100644 scripting/sceneprocessor.sp diff --git a/README.md b/README.md index 3fb4971..b498d86 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ This really only affects wandering zombies, mobs and panic events, but it may wo ### l4d2_feedthetrolls This plugin allows you to enact certain troll modes on any player, some are subtle some are less so. Either way, it works great to deal with a rusher, an asshole or even your friends. -Troll Modes: (updated 1/2/2021) +Troll Modes: (updated 4/20/2021) 1. **SlowSpeed** (0.8 < 1.0 base) - Slows down a user 2. **HigherGravity** (1.3 > 1.0) - Adds more gravity to a user diff --git a/plugins/BumpMineGiver.smx b/plugins/BumpMineGiver.smx new file mode 100644 index 0000000000000000000000000000000000000000..04a48d71d46afaf12cc920a45fe5d63e1f787c49 GIT binary patch literal 6438 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 ghYhy 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 literal 0 HcmV?d00001 diff --git a/plugins/l4d2_skill_detect.smx b/plugins/l4d2_skill_detect.smx new file mode 100644 index 0000000000000000000000000000000000000000..c1eca08520c13427b657c041abedea52e88c8b90 GIT binary patch literal 33717 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 Nkfle^G8N6M{2%1kNKXI& literal 0 HcmV?d00001 diff --git a/plugins/l4d2_target_test.smx b/plugins/l4d2_target_test.smx new file mode 100644 index 0000000000000000000000000000000000000000..12792f7bf4ba9fbbde320d0a0bc975d825a29d3e GIT binary patch literal 5245 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 literal 0 HcmV?d00001 diff --git a/plugins/l4d_tank_hp_sprite.smx b/plugins/l4d_tank_hp_sprite.smx index 1766cc5f8f1e3b76840dc7cb75a4d444485957ac..54614412d40466142c1cdb763248de6ae1d001a8 100644 GIT binary patch literal 13863 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 literal 0 HcmV?d00001 diff --git a/plugins/l4dunreservelobby.smx b/plugins/l4dunreservelobby.smx new file mode 100644 index 0000000000000000000000000000000000000000..8e70f2355da2ac79ddaa6880d535d75238af5984 GIT binary patch literal 5715 zcmZ9IcQ~ADx5kH2A~9@<5-*aDUt#>`Iippz!LIMJPS^!{F1^@sx0001C0{rn`AD{vN zNbr3A5CBkk005BVS&9V!Fu^+wUWs^Cz&qXmWB5Z3@7#DjURdKo`%pA^^ZkLIA)L&!wUOKn=de4bO&>0Ki{(55UU-&(1{n znRZ_G4gev08-E*skavKsyNjLQe<0At7Z08`{w{$I{{fGG&ED2Y$kE09f6*_*!`93F zzqz}M=l|ya*Y<9j@RuojBVSwE1!;&R3+oR$9;7aR*E1 zo7;a(kq}{>$EQ=#Za+WglUFBJ?9O@9c<&pZKYRw$MzW4$&fPA!#jmFrZr^~y^^p0q z81Mup_031GDa<}YhceP@2E)!EU$zj{2Iw{TON&X z(s8}WkZ4P?goAaER;8;o6ZlVel&_w1B3WlJ{SwzF47aJ#;jfU4S{f(2 z;@59MsKS4XOQTumFw!8aSxl(JH329bf&>?@COTgfG2GIF!j+Kulb9Npi?mVr(IjS> zVPzVV!q8Fi?+WKG7winTF8_J|1g1)m0l&4ZIMfJ<`m#FA0Lzc=)IqY&V*16e$v}6^ zpl}srjr&a7xdb#ZI)56|5e;r2>a;4^-ZEk+B0f5!@&COyU+?eLsD7wRLv%KIV!Za& zJ<6M6^y}PMo))jBo>kHQ?!eZ!RMCve&}o?7Ol@o*G#2-j_$|lGO&Z^wOf@C{Ja*VE zp(e2}IFGU_{!H>~iyAh@_(S!!!dB1ZP1mh+)~T7FX7%3{lw)QIA@6a{Lkekq499d` zp{g(DzT}l`$=e9r7GB`&rX_b;2h; zcte+KhKP^b5DGEKN_N<*k(%+fJEOCa7Q}#8)fF*}9|nGWI7_3)-Dqe|74PC2y9KhfB#5qYhZm!TtI#xEPBA<4V+I?05I&iG&-PA$i&%mXXAbT9|a5B>cfqh~l=VKg35VZ+qm_ zUPd%qGANLJxwh7As;@dhhptbL zbgPQ)f_NdEQxOz@MER&4%MIN0j#mP`Q5;~Hne6Z=f zBwF~a=lI!T!cOd-?7FQai|;epMEj5KFV=0bEL&@mC=)09^Q>1S;-XsD1W`S;ef?qTkzFc?;@^N2Q3v5hWms>=!);$1nwtkY?p=gwF#65jiY$)t& zIxk+9b}uJ=le6k=!c@hj2^_A%Vkztn-2MktjFkLLv1l#)bwNSq-ymM3YT+;8g@U#B zhSiITR^*F$PNNGpC?d|^ayy^u`XHlc%iC9)TnwcuLgVZ|iLZK^6>6T-{h`|;eDT}7 z;Luk4l;WZ!MyjPIS~`!COEX7*P0Bm+tXG;72Bg#c@|@0O-8P{&G1%eh(e=~Ir+zMI z+CjQ39pHCJ$3RXxns%T}s-~}YVt&ll&&^p(g^>|0-d88}N!bu~9vo#MUm}KOT14C3 zOGs!`Cx2;7zf#=#+JD)?QZDz1&#y0zC#Xfj)QvL0NmeSbF-~${`QY?aPBhPA(c_YH z?pfUdOMm~23z1LkoaLroW4Qq?@5_FJ{%mz)MjJNkZS%-8S{QHN+09ORJfqW+Y5SVP zH{WJ^IzG>?;pd3X3I3MQYuk7x+eVZ2pPh)Uok}SWNmHt_px-Uz#stP8Sp(Us2J13B zruA~ZrIrpA2OoHr-W)-3p$k-sGZSd2EW_C1=QrY*IIOK;)2(k1TZOecH5>EW-3~Lw zTDdt|iNaKbBlKz*UJ3W|(@6MUAWwZ(TvFi0_1-1O$%KbKBD;I1a^uQ)juHGksd=%X8m6<+YsPRC(7P*?(PC!{!;r2_s%O&?j zR=|?i1T(Hsg}C%Nc@MkSCH?oQ15ZphXdC+~%O%i<6=#$EhMIR}t`?UxjR$WdXNv~z z?gdND>hnHcVItf)dGv6~3o@_}dH!Hp<{n$KPlxGnTS~`VgZff9NpRId|1WLa_kUs; zBj*Yee9Xm~cA5^l1OA%(yNRXuEX2IuCk!~J(FFUjyG3?+AmKi$HW%ENkR2Ue+SjK_ z8swgF+Jm?~$}Q@frrw;VCcR81-(;zyI-nn7ERYp>Q6on!^NixvBF ztMgcnFLP+D^0yB8Tr6Z;rX1NFV< zWc#=SjRw{KS|vAYw2-wHZQZvN<|tHiam+JP9cqbJNdg(vF{kwKCC*tF{5JBK#m2`H z+=mbRjtO%lZ#Ie?PMsHc(Nr+MqExbeYHU3z;ahN1Ef#6wQ?O2L;49l6&r`Aj1gwX} zmD&X-q8Pnl`b(|4PULTavqEOY5~ZJ9_#CBA`V^K2>r-B*4YfLdH{*MJaOx4P_?SY) zS>2i>Dk)Wn=(W`2_Ty{ouJmBBJ!?~Xb;r*FPK!*lp-XOd_ulQltLW`1&^33O`8IR` zol%jN9V6SmgFWZ6v{=XkM~G@_nq6A_f!B=^OY2sAn2$GsCCv+@7oEbg%hI?iqispz zr#{QNt@Ya23!)elwb-laNn5)T%dj+#{t%d=L#q$A*Lo68^LnVEpAzEZnSy zf_7c8YX!<8EK952H#sAYxzKuR^^>K#&q_6bT=aaHd8)_hN0@mC6*M*5c35-%2%foPd}n>v1VtYd$un}plSW5xsHA-2XUBW0d< zJ=AZJ^$tRMfidk)BSxDa+{Yzy<~OO=&M(xwbLbnflF#pu%!Bq z=z4Z#AK*@DqpjCfp;uMV9WmHM)wR|Z7xRVJ7cS)2+_)BKp1 znmQQGv*^1gD%SUYcab1h@Qp)|kz?WCgY;L@$4W>eiP!M<0iNvHPn{axk_|SJnZkKF}mU)jsZy8M@Ferfm(2WYFGRHKP7yRQqc;A zS0sl4+MgCEG}CZq$v{69F9*0i>u;8nnQz6af?55U;vtPO1uq#6 zNiomVH@hBvT6+T*5|yfxB2O!IGxEqw7H74l7mJ){!x%=X2sDu`3b%MOYJXK4jnb#6 zZi}-DB_^s|%l^>)@SQE(7wA{-VQ5h{r#h!IS9|4{$9$C>^vCd-dxl5oU~9QfcnZJ8 z6_(J3k$Abzp$}E}N2`5fD6dkc^^cd%_d;7PiXl%Cj`A#6R%ALPYCOeBBGv()v-RsAY*^_v&bMyL&_?O8>O$}!sA z@lB^^7P-Az{9TBcUKsjE%hcz(>b_k(hRn9J>Ye#zt8pz@FPT&6^23Wt!cC&P5=##3 z()&~RbqNa_cJh5H1cO3WxCcFcI`ue-ydij3Yv4~wb7!npB&#|Rt6^&hP!@Jb+mWHi zKF*W?Y}9>i6C!rUsTP~70m&XM4EPI|^#1*(sAr@ct1&@XJ#^>|n**ylIjj zUmYUSiHN*$RZ_yyfX?*BwON1uT*U&h*<_cgQv)zCTim-1(AlKVbA~gpo+$W>Jn>C! z-5vOu{TpQ?b^D31GU+xwwbS5&(H+ z)dvybT6Kh-n)#1X>WGTiJ?v3V5muZx&#uFjugcbg{}`d?AmM1g;&65MTLpFhuzMFl zJCI*4TG zHtN*t-Cl8c$_Ro5-I;v*BLhDn2(Qkh+aHJc`(e_pmZ*yV^d=5J0SviwL89ldKnT znd2kB$<9{^pd5E9og?+QUj}ZK!2&WLlQah=9+=7`HYAU|yZyn1mbVte$XmN9A>O4= z_|ieR({%`p%jXfv>1~fmzsyg`_B2VeK^_st*3DfnrOavdA`Pow3~WN5R9 zvPdtjf$vhLLsJs~IMCk!?V<4KBG8&5nBbBY9rYtLk}e4hv?PZz1e0ObeWh76^F2^W zpLL4YL5q=x_+PY(e^P6NLxILIp;y`4NS5t(B?Lelc+SzYJhik7_z65G6y|6pu+$8! z*OSl42Ux0OYt<1sfHPCQn)BE0>(}0Ad{9%+!m2Y= zm^s$Nd8fw{66aezda+?Prm8*>k*isu(#d@9#MdOE-ibk`EBFO(&8WEeO6>Hzgtff| z17+++uf*p{5h0aYqU~n3lFVUXv^I+$Pgj)y1I1MY%tAfjr}V&{=2rxlUfkPE%dr$* zGR`K=THQ9^(|a#|m#pvO3R4-@3ScrcK04|$*-r~~N@;)n_?14mOSOP{mKAUkV44(~ zQpZ(p8k(b4BRV5JBlgM)!>ys+VJ`eDr9nAOv5$w|gy!dy&v{8NN~}66z8L25W;hnh zBnB!6M^cyA`^^jtYg5fS+n6fy@_ey3X*)uAyH)$(JimH$5Szus1>IvrP@#HOHW?3Z z1b=m#H_*3CSEI9kag81#Ty3oMxOQk8|GpxzORq_*Xt0oo{fGnwu7r#Q&y<{VGB1Q) z&q#9f&zjROAJTz7m)Shh`OHP~4uE`@35oLTdj?0W|;rNKZXnuQ=Q4oA(E zPYS(%K|7#)dsFpeEBSh(3(7ml4b${(^a9>eUj(+rr~GwGh;HVkVE5gPpj*AYqK5_2tz4sJ z&AxQSA(5HG#h23t*cR~^LFxO8H7e~=CDPDmWs^)D>wm!9e|4<4iFb(6&4FN7fm=G6vm$&&3H;&JnF%O)*iRpKDSreAs zl-6P*$)W?2>fSDEF;Q&DWCz!?s}2*qf5mlE)+I8y*t4qZiHKeCQbABKDdyek16Gqm zguSURNwc5353zWzak^utw3y@_@`|(yZ7dILl(hJ7O&L}xSH|$d@ zmsRia;*C+#$RNV-n7uDiNaYjCNLE7m$!@Fre)+&#mgD}&pyV7usO!`<0fzlFk=<8x nsjWCI1HIW;1${RLwc^ju5#NgJq?7}+F`k7HUWDgM{jL2!8xI-1 literal 0 HcmV?d00001 diff --git a/plugins/left4dhooks.smx b/plugins/left4dhooks.smx new file mode 100644 index 0000000000000000000000000000000000000000..6ea2b60295d27bf3bc2a295db2ea17b63d4476ad GIT binary patch literal 2427 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 Q(L#cg7gwm18N6`&KPbjyf&c&j literal 0 HcmV?d00001 diff --git a/plugins/sceneprocessor.smx b/plugins/sceneprocessor.smx new file mode 100644 index 0000000000000000000000000000000000000000..7d50143903405b653700c951c9c068a52046efb6 GIT binary patch literal 21009 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 S{~ehIEHoS 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 literal 0 HcmV?d00001 diff --git a/scripting/AutoWarpBot.sp b/scripting/AutoWarpBot.sp index b428d1a..d79d17d 100644 --- a/scripting/AutoWarpBot.sp +++ b/scripting/AutoWarpBot.sp @@ -24,7 +24,7 @@ public Plugin myinfo = url = PLUGIN_URL }; -bool g_EnteredCheckpoint = false; +ConVar g_EnteredCheckpoint; public void OnPluginStart() { @@ -34,28 +34,31 @@ public void OnPluginStart() SetFailState("This plugin is for L4D/L4D2 only."); } HookEvent("player_entered_checkpoint", Event_PlayerEnteredCheckpoint); - HookEvent("round_start",Event_RoundStart); + HookEvent("player_left_start_area",Event_RoundStart); + g_EnteredCheckpoint = CreateConVar("awb_status", "0", "Get status of plugin", FCVAR_SPONLY | FCVAR_DONTRECORD, true, 0.0, true, 1.0); } public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { - g_EnteredCheckpoint = false; + if(g_EnteredCheckpoint.BoolValue) g_EnteredCheckpoint.BoolValue = false; } public void Event_PlayerEnteredCheckpoint(Event event, const char[] name, bool dontBroadcast) { - if(!g_EnteredCheckpoint) { + PrintToChatAll("boolvalue %d | client %d", g_EnteredCheckpoint.BoolValue, GetClientOfUserId(event.GetInt("userid"))); + if(!g_EnteredCheckpoint.BoolValue) { + int client = GetClientOfUserId(event.GetInt("userid")); bool playersLeft = false; for (int i = 1; i < MaxClients;i++) { - if (client == i)continue; - if (!IsClientConnected(i)) continue; + if (client == i) continue; + if (!IsClientInGame(i)) continue; if (GetClientTeam(i) != 2) continue; - + PrintToChatAll("playersLeft: %d . clientr %d", playersLeft, i); if(!IsFakeClient(i)) { playersLeft = true; break; } } if(!playersLeft) { - g_EnteredCheckpoint = true; + g_EnteredCheckpoint.BoolValue = true; CheatCommand(client,"warp_all_survivors_to_checkpoint","",""); } } diff --git a/scripting/BumpMineGiver.sp b/scripting/BumpMineGiver.sp new file mode 100644 index 0000000..3011921 --- /dev/null +++ b/scripting/BumpMineGiver.sp @@ -0,0 +1,114 @@ +#pragma semicolon 1 + +#define DEBUG + +#define PLUGIN_AUTHOR "" +#define PLUGIN_VERSION "0.00" + +#include +#include +#include +//#include + +#pragma newdecls required + +ConVar g_bmgTTeam, g_bmgEnabled, g_bmgCmdLimit; + +int g_bmgBumpsGiven[MAXPLAYERS+1]; + +public Plugin myinfo = +{ + name = "BumpMineGiver", + author = PLUGIN_AUTHOR, + description = "", + version = PLUGIN_VERSION, + url = "" +}; + +public void OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_CSGO && g_Game != Engine_CSS) + { + SetFailState("This plugin is for CSGO/CSS only."); + } + g_bmgEnabled = CreateConVar("bmg_enabled", "1", "Should BumpMineGiver be enabled?", FCVAR_NONE, true, 0.0, true, 1.0); + g_bmgTTeam = CreateConVar("bmg_restrict_team", "0", "Should BumpMineGiver be restricted to a team? 0 - All, 1 - Terrorists, 2 - CounterTerrorists", FCVAR_NONE, true, 0.0, true, 3.0); + g_bmgCmdLimit = CreateConVar("bmg_cmdlimit", "0", "Limit of amount of bumpmines to be given with !bmp. 0: Disabled, -1: Infinity", FCVAR_NONE); + HookEvent("round_start", Event_RoundStart); + HookEvent("player_spawn", Event_PlayerSpawn); + + RegConsoleCmd("sm_bmp", Command_GiveBMP, "Give yourself a bump mine"); + RegConsoleCmd("sm_bmg", Command_GiveBMP, "Give yourself a bump mine"); + RegAdminCmd("sm_givebmp", Command_GiveOthersBMP, ADMFLAG_CHEATS, "Give someone x amount of bump mines. Usage: sm_givebmp "); + AutoExecConfig(); +} + +public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { + for (int i = 0; i < sizeof(g_bmgBumpsGiven); i++) { + g_bmgBumpsGiven[i] = 0; + } +} + +public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { + if(g_bmgEnabled.BoolValue) { + int client = GetClientOfUserId(event.GetInt("userid")); + int wpn_id = GetPlayerWeaponSlot(client, 4); + int team = GetClientTeam(client); + if(g_bmgTTeam.IntValue > 0) { //1 or 2 + if (team != g_bmgTTeam.IntValue) return; + } + if(wpn_id == -1) { + GivePlayerItem(client, "weapon_bumpmine"); + } + } +} + +public Action Command_GiveBMP(int client, int args) { + if(g_bmgCmdLimit.IntValue == 0) { + ReplyToCommand(client, "You have hit the limit of bumpmines"); + return Plugin_Handled; + } + //limit is enabled, check + if(g_bmgCmdLimit.IntValue > 0) { + if(g_bmgBumpsGiven[client] > g_bmgCmdLimit.IntValue) { + ReplyToCommand(client, "You have hit the limit of bumpmines"); + return Plugin_Handled; + } + } + GivePlayerItem(client, "weapon_bumpmine"); + g_bmgBumpsGiven[client]++; + return Plugin_Handled; +} + +public Action Command_GiveOthersBMP(int client, int args) { + if(args < 1) { + ReplyToCommand(client, "Usage: sm_givebmp "); + }else{ + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg1, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, /* Only allow alive players */ + 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; + } + for (int i = 0; i < target_count; i++) + { + GivePlayerItem(target_list[i], "weapon_bumpmine"); + LogAction(client, target_list[i], "\"%L\" gave \"%L\" a bumpmine", client, target_list[i]); + } + } + return Plugin_Handled; +} \ No newline at end of file diff --git a/scripting/L4D2PreventBotMovement.sp b/scripting/L4D2PreventBotMovement.sp new file mode 100644 index 0000000..822071e --- /dev/null +++ b/scripting/L4D2PreventBotMovement.sp @@ -0,0 +1,57 @@ +#pragma semicolon 1 +#pragma newdecls required + +//#define DEBUG + +#define PLUGIN_NAME "L4D2 Prevent Bot Movement" +#define PLUGIN_DESCRIPTION "Prevents bots from moving in the beginning of the round for a set period of time." +#define PLUGIN_AUTHOR "jackzmc" +#define PLUGIN_VERSION "1.0" +#define PLUGIN_URL "" + +#include +#include +//#include + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_DESCRIPTION, + version = PLUGIN_VERSION, + url = PLUGIN_URL +}; + +static ConVar hSBStop, hStopTime; + +public void OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) + { + SetFailState("This plugin is for L4D/L4D2 only."); + } + HookEvent("round_start",Event_RoundStart); + + hStopTime = CreateConVar("sm_freeze_bot_time","20.0","How long should the bots be frozen for on beginning of round? 0 to disable",FCVAR_NONE,true,0.0); + hSBStop = FindConVar("sb_stop"); +} +public void OnMapStart() { + if(hStopTime.IntValue != 0) { + PrintToChatAll("round start"); + hSBStop.BoolValue = true; + CreateTimer(hStopTime.FloatValue, ResumeBots); + } +} + +public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { + if(hStopTime.IntValue != 0) { + PrintToChatAll("round start"); + hSBStop.BoolValue = true; + CreateTimer(hStopTime.FloatValue, ResumeBots); + } +} +public Action ResumeBots(Handle timer) { + PrintToChatAll("Resuming bots"); + hSBStop.BoolValue = false; +} diff --git a/scripting/csgo-misc.sp b/scripting/csgo-misc.sp new file mode 100644 index 0000000..f039622 --- /dev/null +++ b/scripting/csgo-misc.sp @@ -0,0 +1,60 @@ +#pragma semicolon 1 + +#define DEBUG + +#define PLUGIN_AUTHOR "" +#define PLUGIN_VERSION "0.00" + +#include +#include +#include +//#include + +#pragma newdecls required + +public Plugin myinfo = +{ + name = "", + author = PLUGIN_AUTHOR, + description = "", + version = PLUGIN_VERSION, + url = "" +}; + +public void OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_CSGO && g_Game != Engine_CSS) + { + SetFailState("This plugin is for CSGO/CSS only."); + } + RegConsoleCmd("sm_first", Command_FirstPov, "Go back to first person"); + RegConsoleCmd("sm_third", Command_ThirdPov, "Go to third person"); +} +public Action Command_FirstPov(int client, int args) { + if(client == 0) { + ReplyToCommand(client, "This command is for clients only"); + return Plugin_Handled; + } + CheatCommand(client, "firstperson", "", ""); + return Plugin_Handled; +} +public Action Command_ThirdPov(int client, int args) { + if(client == 0) { + ReplyToCommand(client, "This command is for clients only"); + return Plugin_Handled; + } + CheatCommand(client, "thirdperson", "", ""); + return Plugin_Handled; +} + +stock void CheatCommand(int client, char[] command, char[] argument1, char[] argument2) +{ + int userFlags = GetUserFlagBits(client); + SetUserFlagBits(client, ADMFLAG_ROOT); + int flags = GetCommandFlags(command); + SetCommandFlags(command, flags & ~FCVAR_CHEAT); + FakeClientCommand(client, "%s %s %s", command, argument1, argument2); + SetCommandFlags(command, flags); + SetUserFlagBits(client, userFlags); +} \ No newline at end of file diff --git a/scripting/l4d2_drop_secondary.sp b/scripting/l4d2_drop_secondary.sp new file mode 100644 index 0000000..7d56a65 --- /dev/null +++ b/scripting/l4d2_drop_secondary.sp @@ -0,0 +1,233 @@ +#pragma semicolon 1 +#pragma newdecls required //強制1.7以後的新語法 +#include +#include + +#define MODEL_V_FIREAXE "models/weapons/melee/v_fireaxe.mdl" +#define MODEL_V_FRYING_PAN "models/weapons/melee/v_frying_pan.mdl" +#define MODEL_V_MACHETE "models/weapons/melee/v_machete.mdl" +#define MODEL_V_BASEBALL_BAT "models/weapons/melee/v_bat.mdl" +#define MODEL_V_CROWBAR "models/weapons/melee/v_crowbar.mdl" +#define MODEL_V_CRICKET_BAT "models/weapons/melee/v_cricket_bat.mdl" +#define MODEL_V_TONFA "models/weapons/melee/v_tonfa.mdl" +#define MODEL_V_KATANA "models/weapons/melee/v_katana.mdl" +#define MODEL_V_ELECTRIC_GUITAR "models/weapons/melee/v_electric_guitar.mdl" +#define MODEL_V_GOLFCLUB "models/weapons/melee/v_golfclub.mdl" +#define MODEL_V_SHIELD "models/weapons/melee/v_riotshield.mdl" +#define MODEL_V_KNIFE "models/v_models/v_knife_t.mdl" +#define MODEL_V_SHOVEL "models/weapons/melee/v_shovel.mdl" +#define MODEL_V_PITCHFORK "models/weapons/melee/v_pitchfork.mdl" + +static g_PlayerSecondaryWeapons[MAXPLAYERS + 1]; + +public Plugin myinfo = +{ + name = "L4D2 Drop Secondary", + author = "Jahze, Visor, NoBody & HarryPotter", + version = "1.8", + description = "Survivor players will drop their secondary weapon when they die", + url = "https://github.com/Attano/Equilibrium" +}; + +public void OnPluginStart() +{ + HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy); + HookEvent("player_use", OnPlayerUse, EventHookMode_Post); + HookEvent("player_bot_replace", player_bot_replace); + HookEvent("bot_player_replace", bot_player_replace); + HookEvent("player_death", OnPlayerDeath, EventHookMode_Pre); + HookEvent("weapon_drop", OnWeaponDrop); + HookEvent("item_pickup", OnItemPickUp); +} + +public void OnRoundStart(Event event, const char[] name, bool dontBroadcast) +{ + for (int i = 0; i <= MAXPLAYERS; i++) + { + g_PlayerSecondaryWeapons[i] = -1; + } +} + +public Action OnPlayerUse(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + + if(client == 0 || !IsClientInGame(client) || GetClientTeam(client) != 2) + { + return; + } + + int weapon = GetPlayerWeaponSlot(client, 1); + + g_PlayerSecondaryWeapons[client] = (weapon == -1 ? weapon : EntIndexToEntRef(weapon)); +} + +public Action bot_player_replace(Event event, const char[] name, bool dontBroadcast) +{ + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + + g_PlayerSecondaryWeapons[client] = g_PlayerSecondaryWeapons[bot]; + g_PlayerSecondaryWeapons[bot] = -1; +} + +public Action player_bot_replace(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("player")); + int bot = GetClientOfUserId(event.GetInt("bot")); + + g_PlayerSecondaryWeapons[bot] = g_PlayerSecondaryWeapons[client]; + g_PlayerSecondaryWeapons[client] = -1; +} + +public Action OnItemPickUp(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + if(client == 0 || !IsClientInGame(client) || GetClientTeam(client) != 2) + { + return; + } + int weapon = GetPlayerWeaponSlot(client, 1); + + g_PlayerSecondaryWeapons[client] = (weapon == -1 ? weapon : EntIndexToEntRef(weapon)); +} + +public Action OnWeaponDrop(Event event, const char[] name, bool dontBroadcast) +{ + CreateTimer(0.1, ColdDown, event.GetInt("userid")); +} + +public Action ColdDown(Handle timer, int client) +{ + client = GetClientOfUserId(client); + if(client && IsClientInGame(client) && GetClientTeam(client) == 2) + { + int weapon = GetPlayerWeaponSlot(client, 1); + + g_PlayerSecondaryWeapons[client] = (weapon == -1 ? weapon : EntIndexToEntRef(weapon)); + } +} + +public Action OnPlayerDeath(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); + + if(client == 0 || !IsClientInGame(client) || GetClientTeam(client) != 2) + { + return; + } + + int weapon = EntRefToEntIndex(g_PlayerSecondaryWeapons[client]); + + if(weapon == INVALID_ENT_REFERENCE) + { + return; + } + + char sWeapon[32]; + int clip; + GetEdictClassname(weapon, sWeapon, 32); + + int index = CreateEntityByName(sWeapon); + float origin[3]; + float ang[3]; + if (strcmp(sWeapon, "weapon_melee") == 0) + { + char melee[150]; + GetEntPropString(weapon , Prop_Data, "m_ModelName", melee, sizeof(melee)); + if (strcmp(melee, MODEL_V_FIREAXE) == 0) + { + DispatchKeyValue(index, "melee_script_name", "fireaxe"); + } + else if (strcmp(melee, MODEL_V_FRYING_PAN) == 0) + { + DispatchKeyValue(index, "melee_script_name", "frying_pan"); + } + else if (strcmp(melee, MODEL_V_MACHETE) == 0) + { + DispatchKeyValue(index, "melee_script_name", "machete"); + } + else if (strcmp(melee, MODEL_V_BASEBALL_BAT) == 0) + { + DispatchKeyValue(index, "melee_script_name", "baseball_bat"); + } + else if (strcmp(melee, MODEL_V_CROWBAR) == 0) + { + DispatchKeyValue(index, "melee_script_name", "crowbar"); + } + else if (strcmp(melee, MODEL_V_CRICKET_BAT) == 0) + { + DispatchKeyValue(index, "melee_script_name", "cricket_bat"); + } + else if (strcmp(melee, MODEL_V_TONFA) == 0) + { + DispatchKeyValue(index, "melee_script_name", "tonfa"); + } + else if (strcmp(melee, MODEL_V_KATANA) == 0) + { + DispatchKeyValue(index, "melee_script_name", "katana"); + } + else if (strcmp(melee, MODEL_V_ELECTRIC_GUITAR) == 0) + { + DispatchKeyValue(index, "melee_script_name", "electric_guitar"); + } + else if (strcmp(melee, MODEL_V_GOLFCLUB) == 0) + { + DispatchKeyValue(index, "melee_script_name", "golfclub"); + } + else if (strcmp(melee, MODEL_V_SHIELD) == 0) + { + DispatchKeyValue(index, "melee_script_name", "riotshield"); + } + else if (strcmp(melee, MODEL_V_KNIFE) == 0) + { + DispatchKeyValue(index, "melee_script_name", "knife"); + } + else if (strcmp(melee, MODEL_V_SHOVEL) == 0) + { + DispatchKeyValue(index, "melee_script_name", "shovel"); + } + else if (strcmp(melee, MODEL_V_PITCHFORK) == 0) + { + DispatchKeyValue(index, "melee_script_name", "pitchfork"); + } + else return; + } + else if (strcmp(sWeapon, "weapon_chainsaw") == 0) + { + clip = GetEntProp(weapon, Prop_Send, "m_iClip1"); + } + else if (strcmp(sWeapon, "weapon_pistol") == 0 && (GetEntProp(weapon, Prop_Send, "m_isDualWielding") > 0)) + { + int indexC = CreateEntityByName(sWeapon); + GetClientEyePosition(client,origin); + GetClientEyeAngles(client, ang); + GetAngleVectors(ang, ang, NULL_VECTOR,NULL_VECTOR); + NormalizeVector(ang,ang); + ScaleVector(ang, 90.0); + + DispatchSpawn(indexC); + TeleportEntity(indexC, origin, NULL_VECTOR, ang); + } + else if (strcmp(sWeapon, "weapon_pistol_magnum") == 0) + { + clip = GetEntProp(weapon, Prop_Send, "m_iClip1"); + } + + RemovePlayerItem(client, weapon); + RemoveEntity(weapon); + + GetClientEyePosition(client,origin); + GetClientEyeAngles(client, ang); + GetAngleVectors(ang, ang, NULL_VECTOR,NULL_VECTOR); + NormalizeVector(ang,ang); + ScaleVector(ang, 90.0); + + DispatchSpawn(index); + TeleportEntity(index, origin, NULL_VECTOR, ang); + + if (strcmp(sWeapon, "weapon_chainsaw") == 0 || strcmp(sWeapon, "weapon_pistol") == 0 || strcmp(sWeapon, "weapon_pistol_magnum") == 0) + { + SetEntProp(index, Prop_Send, "m_iClip1", clip); + } +} diff --git a/scripting/l4d2_skill_detect.sp b/scripting/l4d2_skill_detect.sp new file mode 100644 index 0000000..4cc88d2 --- /dev/null +++ b/scripting/l4d2_skill_detect.sp @@ -0,0 +1,3125 @@ +/** + * L4D2_skill_detect + * + * Plugin to detect and forward reports about 'skill'-actions, + * such as skeets, crowns, levels, dp's. + * Works in campaign and versus modes. + * + * m_isAttemptingToPounce can only be trusted for + * AI hunters -- for human hunters this gets cleared + * instantly on taking killing damage + * + * Shotgun skeets and teamskeets are only counted if the + * added up damage to pounce_interrupt is done by shotguns + * only. 'Skeeting' chipped hunters shouldn't count, IMO. + * + * This performs global forward calls to: + * OnSkeet( survivor, hunter ) + * OnSkeetMelee( survivor, hunter ) + * OnSkeetGL( survivor, hunter ) + * OnSkeetSniper( survivor, hunter ) + * OnSkeetHurt( survivor, hunter, damage, isOverkill ) + * OnSkeetMeleeHurt( survivor, hunter, damage, isOverkill ) + * OnSkeetSniperHurt( survivor, hunter, damage, isOverkill ) + * OnHunterDeadstop( survivor, hunter ) + * OnBoomerPop( survivor, boomer, shoveCount, Float:timeAlive ) + * OnChargerLevel( survivor, charger ) + * OnChargerLevelHurt( survivor, charger, damage ) + * OnWitchCrown( survivor, damage ) + * OnWitchCrownHurt( survivor, damage, chipdamage ) + * OnTongueCut( survivor, smoker ) + * OnSmokerSelfClear( survivor, smoker, withShove ) + * OnTankRockSkeeted( survivor, tank ) + * OnTankRockEaten( tank, survivor ) + * OnHunterHighPounce( hunter, victim, actualDamage, Float:calculatedDamage, Float:height, bool:bReportedHigh, bool:bPlayerIncapped ) + * OnJockeyHighPounce( jockey, victim, Float:height, bool:bReportedHigh ) + * OnDeathCharge( charger, victim, Float: height, Float: distance, wasCarried ) + * OnSpecialShoved( survivor, infected, zombieClass ) + * OnSpecialClear( clearer, pinner, pinvictim, zombieClass, Float:timeA, Float:timeB, withShove ) + * OnBoomerVomitLanded( boomer, amount ) + * OnBunnyHopStreak( survivor, streak, Float:maxVelocity ) + * OnCarAlarmTriggered( survivor, infected, reason ) + * + * OnDeathChargeAssist( assister, charger, victim ) [ not done yet ] + * OnBHop( player, isInfected, speed, streak ) [ not done yet ] + * + * Where survivor == -2 if it was a team effort, -1 or 0 if unknown or invalid client. + * damage is the amount of damage done (that didn't add up to skeeting damage), + * and isOverkill indicates whether the shot would've been a skeet if the hunter + * had not been chipped. + * + * @author Tabun + * @libraryname skill_detect + */ + +#pragma semicolon 1 + +#include +#include +#include +#include + +#define PLUGIN_VERSION "1.0" + +#define IS_VALID_CLIENT(%1) (%1 > 0 && %1 <= MaxClients) +#define IS_SURVIVOR(%1) (GetClientTeam(%1) == 2) +#define IS_INFECTED(%1) (GetClientTeam(%1) == 3) +#define IS_VALID_INGAME(%1) (IS_VALID_CLIENT(%1) && IsClientInGame(%1)) +#define IS_VALID_SURVIVOR(%1) (IS_VALID_INGAME(%1) && IS_SURVIVOR(%1)) +#define IS_VALID_INFECTED(%1) (IS_VALID_INGAME(%1) && IS_INFECTED(%1)) +#define IS_SURVIVOR_ALIVE(%1) (IS_VALID_SURVIVOR(%1) && IsPlayerAlive(%1)) +#define IS_INFECTED_ALIVE(%1) (IS_VALID_INFECTED(%1) && IsPlayerAlive(%1)) +#define QUOTES(%1) (%1) + +#define SHOTGUN_BLAST_TIME 0.1 +#define POUNCE_CHECK_TIME 0.1 +#define HOP_CHECK_TIME 0.1 +#define HOPEND_CHECK_TIME 0.1 // after streak end (potentially) detected, to check for realz? +#define SHOVE_TIME 0.05 +#define MAX_CHARGE_TIME 12.0 // maximum time to pass before charge checking ends +#define CHARGE_CHECK_TIME 0.25 // check interval for survivors flying from impacts +#define CHARGE_END_CHECK 2.5 // after client hits ground after getting impact-charged: when to check whether it was a death +#define CHARGE_END_RECHECK 3.0 // safeguard wait to recheck on someone getting incapped out of bounds +#define VOMIT_DURATION_TIME 2.25 // how long the boomer vomit stream lasts -- when to check for boom count +#define ROCK_CHECK_TIME 0.34 // how long to wait after rock entity is destroyed before checking for skeet/eat (high to avoid lag issues) +#define CARALARM_MIN_TIME 0.11 // maximum time after touch/shot => alarm to connect the two events (test this for LAG) + +#define WITCH_CHECK_TIME 0.1 // time to wait before checking for witch crown after shoots fired +#define WITCH_DELETE_TIME 0.15 // time to wait before deleting entry from witch trie after entity is destroyed + +#define MIN_DC_TRIGGER_DMG 300 // minimum amount a 'trigger' / drown must do before counted as a death action +#define MIN_DC_FALL_DMG 175 // minimum amount of fall damage counts as death-falling for a deathcharge +#define WEIRD_FLOW_THRESH 900.0 // -9999 seems to be break flow.. but meh +#define MIN_FLOWDROPHEIGHT 350.0 // minimum height a survivor has to have dropped before a WEIRD_FLOW value is treated as a DC spot +#define MIN_DC_RECHECK_DMG 100 // minimum damage from map to have taken on first check, to warrant recheck + +#define HOP_ACCEL_THRESH 0.01 // bhop speed increase must be higher than this for it to count as part of a hop streak + +#define ZC_SMOKER 1 +#define ZC_BOOMER 2 +#define ZC_HUNTER 3 +#define ZC_JOCKEY 5 +#define ZC_CHARGER 6 +#define ZC_TANK 8 +#define HITGROUP_HEAD 1 + +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object. +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_BUCKSHOT (1 << 29) // not quite a bullet. Little, rounder, different. + +#define DMGARRAYEXT 7 // MAXPLAYERS+# -- extra indices in witch_dmg_array + 1 + +#define CUT_SHOVED 1 // smoker got shoved +#define CUT_SHOVEDSURV 2 // survivor got shoved +#define CUT_KILL 3 // reason for tongue break (release_type) +#define CUT_SLASH 4 // this is used for others shoving a survivor free too, don't trust .. it involves tongue damage? + +#define VICFLG_CARRIED (1 << 0) // was the one that the charger carried (not impacted) +#define VICFLG_FALL (1 << 1) // flags stored per charge victim, to check for deathchargeroony -- fallen +#define VICFLG_DROWN (1 << 2) // drowned +#define VICFLG_HURTLOTS (1 << 3) // whether the victim was hurt by 400 dmg+ at once +#define VICFLG_TRIGGER (1 << 4) // killed by trigger_hurt +#define VICFLG_AIRDEATH (1 << 5) // died before they hit the ground (impact check) +#define VICFLG_KILLEDBYOTHER (1 << 6) // if the survivor was killed by an SI other than the charger +#define VICFLG_WEIRDFLOW (1 << 7) // when survivors get out of the map and such +#define VICFLG_WEIRDFLOWDONE (1 << 8) // checked, don't recheck for this + +#define REP_SKEET (1 << 0) +#define REP_HURTSKEET (1 << 1) +#define REP_LEVEL (1 << 2) +#define REP_HURTLEVEL (1 << 3) +#define REP_CROWN (1 << 4) +#define REP_DRAWCROWN (1 << 5) +#define REP_TONGUECUT (1 << 6) +#define REP_SELFCLEAR (1 << 7) +#define REP_SELFCLEARSHOVE (1 << 8) +#define REP_ROCKSKEET (1 << 9) +#define REP_DEADSTOP (1 << 10) +#define REP_POP (1 << 11) +#define REP_SHOVE (1 << 12) +#define REP_HUNTERDP (1 << 13) +#define REP_JOCKEYDP (1 << 14) +#define REP_DEATHCHARGE (1 << 15) +#define REP_DC_ASSIST (1 << 16) +#define REP_INSTACLEAR (1 << 17) // 131072 +#define REP_BHOPSTREAK (1 << 18) // 262144 +#define REP_CARALARM (1 << 19) // 524288 + +#define REP_DEFAULT "581685" // (REP_SKEET | REP_LEVEL | REP_CROWN | REP_DRAWCROWN | REP_HUNTERDP | REP_JOCKEYDP | REP_DEATHCHARGE | REP_CARALARM) + // 1 4 16 32 8192 16384 32768 65536 (122933 with ASSIST, 57397 without); 131072 for instaclears + 524288 for car alarm + + +// trie values: weapon type +enum strWeaponType +{ + WPTYPE_SNIPER, + WPTYPE_MAGNUM, + WPTYPE_GL +}; + +// trie values: OnEntityCreated classname +enum strOEC +{ + OEC_WITCH, + OEC_TANKROCK, + OEC_TRIGGER, + OEC_CARALARM, + OEC_CARGLASS +}; + +// trie values: special abilities +enum strAbility +{ + ABL_HUNTERLUNGE, + ABL_ROCKTHROW +}; + +enum +{ + rckDamage, + rckTank, + rckSkeeter, + strRockData +}; + +// witch array entries (maxplayers+index) +enum +{ + WTCH_NONE, + WTCH_HEALTH, + WTCH_GOTSLASH, + WTCH_STARTLED, + WTCH_CROWNER, + WTCH_CROWNSHOT, + WTCH_CROWNTYPE, + strWitchArray +}; + +enum +{ + CALARM_UNKNOWN, + CALARM_HIT, + CALARM_TOUCHED, + CALARM_EXPLOSION, + CALARM_BOOMER, + enAlarmReasons +}; + +new const String: g_csSIClassName[][] = +{ + "", + "smoker", + "boomer", + "hunter", + "spitter", + "jockey", + "charger", + "witch", + "tank" +}; + +new bool: g_bLateLoad = false; + +new Handle: g_hForwardSkeet = INVALID_HANDLE; +new Handle: g_hForwardSkeetHurt = INVALID_HANDLE; +new Handle: g_hForwardSkeetMelee = INVALID_HANDLE; +new Handle: g_hForwardSkeetMeleeHurt = INVALID_HANDLE; +new Handle: g_hForwardSkeetSniper = INVALID_HANDLE; +new Handle: g_hForwardSkeetSniperHurt = INVALID_HANDLE; +new Handle: g_hForwardSkeetGL = INVALID_HANDLE; +new Handle: g_hForwardHunterDeadstop = INVALID_HANDLE; +new Handle: g_hForwardSIShove = INVALID_HANDLE; +new Handle: g_hForwardBoomerPop = INVALID_HANDLE; +new Handle: g_hForwardLevel = INVALID_HANDLE; +new Handle: g_hForwardLevelHurt = INVALID_HANDLE; +new Handle: g_hForwardCrown = INVALID_HANDLE; +new Handle: g_hForwardDrawCrown = INVALID_HANDLE; +new Handle: g_hForwardTongueCut = INVALID_HANDLE; +new Handle: g_hForwardSmokerSelfClear = INVALID_HANDLE; +new Handle: g_hForwardRockSkeeted = INVALID_HANDLE; +new Handle: g_hForwardRockEaten = INVALID_HANDLE; +new Handle: g_hForwardHunterDP = INVALID_HANDLE; +new Handle: g_hForwardJockeyDP = INVALID_HANDLE; +new Handle: g_hForwardDeathCharge = INVALID_HANDLE; +new Handle: g_hForwardClear = INVALID_HANDLE; +new Handle: g_hForwardVomitLanded = INVALID_HANDLE; +new Handle: g_hForwardBHopStreak = INVALID_HANDLE; +new Handle: g_hForwardAlarmTriggered = INVALID_HANDLE; + +new Handle: g_hTrieWeapons = INVALID_HANDLE; // weapon check +new Handle: g_hTrieEntityCreated = INVALID_HANDLE; // getting classname of entity created +new Handle: g_hTrieAbility = INVALID_HANDLE; // ability check +new Handle: g_hWitchTrie = INVALID_HANDLE; // witch tracking (Crox) +new Handle: g_hRockTrie = INVALID_HANDLE; // tank rock tracking +new Handle: g_hCarTrie = INVALID_HANDLE; // car alarm tracking + +// all SI / pinners +new Float: g_fSpawnTime [MAXPLAYERS + 1]; // time the SI spawned up +new Float: g_fPinTime [MAXPLAYERS + 1][2]; // time the SI pinned a target: 0 = start of pin (tongue pull, charger carry); 1 = carry end / tongue reigned in +new g_iSpecialVictim [MAXPLAYERS + 1]; // current victim (set in traceattack, so we can check on death) + +// hunters: skeets/pounces +new g_iHunterShotDmgTeam [MAXPLAYERS + 1]; // counting shotgun blast damage for hunter, counting entire survivor team's damage +new g_iHunterShotDmg [MAXPLAYERS + 1][MAXPLAYERS + 1]; // counting shotgun blast damage for hunter / skeeter combo +new Float: g_fHunterShotStart [MAXPLAYERS + 1][MAXPLAYERS + 1]; // when the last shotgun blast on hunter started (if at any time) by an attacker +new Float: g_fHunterTracePouncing [MAXPLAYERS + 1]; // time when the hunter was still pouncing (in traceattack) -- used to detect pouncing status +new Float: g_fHunterLastShot [MAXPLAYERS + 1]; // when the last shotgun damage was done (by anyone) on a hunter +new g_iHunterLastHealth [MAXPLAYERS + 1]; // last time hunter took any damage, how much health did it have left? +new g_iHunterOverkill [MAXPLAYERS + 1]; // how much more damage a hunter would've taken if it wasn't already dead +new bool: g_bHunterKilledPouncing [MAXPLAYERS + 1]; // whether the hunter was killed when actually pouncing +new g_iPounceDamage [MAXPLAYERS + 1]; // how much damage on last 'highpounce' done +new Float: g_fPouncePosition [MAXPLAYERS + 1][3]; // position that a hunter (jockey?) pounced from (or charger started his carry) + +// deadstops +new Float: g_fVictimLastShove [MAXPLAYERS + 1][MAXPLAYERS + 1]; // when was the player shoved last by attacker? (to prevent doubles) + +// levels / charges +new g_iChargerHealth [MAXPLAYERS + 1]; // how much health the charger had the last time it was seen taking damage +new Float: g_fChargeTime [MAXPLAYERS + 1]; // time the charger's charge last started, or if victim, when impact started +new g_iChargeVictim [MAXPLAYERS + 1]; // who got charged +new Float: g_fChargeVictimPos [MAXPLAYERS + 1][3]; // location of each survivor when it got hit by the charger +new g_iVictimCharger [MAXPLAYERS + 1]; // for a victim, by whom they got charge(impacted) +new g_iVictimFlags [MAXPLAYERS + 1]; // flags stored per charge victim: VICFLAGS_ +new g_iVictimMapDmg [MAXPLAYERS + 1]; // for a victim, how much the cumulative map damage is so far (trigger hurt / drowning) + +// pops +new bool: g_bBoomerHitSomebody [MAXPLAYERS + 1]; // false if boomer didn't puke/exploded on anybody +new g_iBoomerGotShoved [MAXPLAYERS + 1]; // count boomer was shoved at any point +new g_iBoomerVomitHits [MAXPLAYERS + 1]; // how many booms in one vomit so far + +// crowns +new Float: g_fWitchShotStart [MAXPLAYERS + 1]; // when the last shotgun blast from a survivor started (on any witch) + +// smoker clears +new bool: g_bSmokerClearCheck [MAXPLAYERS + 1]; // [smoker] smoker dies and this is set, it's a self-clear if g_iSmokerVictim is the killer +new g_iSmokerVictim [MAXPLAYERS + 1]; // [smoker] the one that's being pulled +new g_iSmokerVictimDamage [MAXPLAYERS + 1]; // [smoker] amount of damage done to a smoker by the one he pulled +new bool: g_bSmokerShoved [MAXPLAYERS + 1]; // [smoker] set if the victim of a pull manages to shove the smoker + +// rocks +new g_iTankRock [MAXPLAYERS + 1]; // rock entity per tank +new g_iRocksBeingThrown [10]; // 10 tanks max simultanously throwing rocks should be ok (this stores the tank client) +new g_iRocksBeingThrownCount = 0; // so we can do a push/pop type check for who is throwing a created rock + +// hops +new bool: g_bIsHopping [MAXPLAYERS + 1]; // currently in a hop streak +new bool: g_bHopCheck [MAXPLAYERS + 1]; // flag to check whether a hopstreak has ended (if on ground for too long.. ends) +new g_iHops [MAXPLAYERS + 1]; // amount of hops in streak +new Float: g_fLastHop [MAXPLAYERS + 1][3]; // velocity vector of last jump +new Float: g_fHopTopVelocity [MAXPLAYERS + 1]; // maximum velocity in hopping streak + +// alarms +new Float: g_fLastCarAlarm = 0.0; // time when last car alarm went off +new g_iLastCarAlarmReason [MAXPLAYERS + 1]; // what this survivor did to set the last alarm off +new g_iLastCarAlarmBoomer; // if a boomer triggered an alarm, remember it + +// cvars +new Handle: g_hCvarReport = INVALID_HANDLE; // cvar whether to report at all +new Handle: g_hCvarReportFlags = INVALID_HANDLE; // cvar what to report + +new Handle: g_hCvarAllowMelee = INVALID_HANDLE; // cvar whether to count melee skeets +new Handle: g_hCvarAllowSniper = INVALID_HANDLE; // cvar whether to count sniper headshot skeets +new Handle: g_hCvarAllowGLSkeet = INVALID_HANDLE; // cvar whether to count direct hit GL skeets +new Handle: g_hCvarDrawCrownThresh = INVALID_HANDLE; // cvar damage in final shot for drawcrown-req. +new Handle: g_hCvarSelfClearThresh = INVALID_HANDLE; // cvar damage while self-clearing from smokers +new Handle: g_hCvarHunterDPThresh = INVALID_HANDLE; // cvar damage for hunter highpounce +new Handle: g_hCvarJockeyDPThresh = INVALID_HANDLE; // cvar distance for jockey highpounce +new Handle: g_hCvarHideFakeDamage = INVALID_HANDLE; // cvar damage while self-clearing from smokers +new Handle: g_hCvarDeathChargeHeight = INVALID_HANDLE; // cvar how high a charger must have come in order for a DC to count +new Handle: g_hCvarInstaTime = INVALID_HANDLE; // cvar clear within this time or lower for instaclear +new Handle: g_hCvarBHopMinStreak = INVALID_HANDLE; // cvar this many hops in a row+ = streak +new Handle: g_hCvarBHopMinInitSpeed = INVALID_HANDLE; // cvar lower than this and the first jump won't be seen as the start of a streak +new Handle: g_hCvarBHopContSpeed = INVALID_HANDLE; // cvar + +new Handle: g_hCvarPounceInterrupt = INVALID_HANDLE; // z_pounce_damage_interrupt +new g_iPounceInterrupt = 150; +new Handle: g_hCvarChargerHealth = INVALID_HANDLE; // z_charger_health +new Handle: g_hCvarWitchHealth = INVALID_HANDLE; // z_witch_health +new Handle: g_hCvarMaxPounceDistance = INVALID_HANDLE; // z_pounce_damage_range_max +new Handle: g_hCvarMinPounceDistance = INVALID_HANDLE; // z_pounce_damage_range_min +new Handle: g_hCvarMaxPounceDamage = INVALID_HANDLE; // z_hunter_max_pounce_bonus_damage; + + +/* + Reports: + -------- + Damage shown is damage done in the last shot/slash. So for crowns, this means + that the 'damage' value is one shotgun blast + + + Quirks: + ------- + Does not report people cutting smoker tongues that target players other + than themselves. Could be done, but would require (too much) tracking. + + Actual damage done, on Hunter DPs, is low when the survivor gets incapped + by (a fraction of) the total pounce damage. + + + Fake Damage + ----------- + Hiding of fake damage has the following consequences: + - Drawcrowns are less likely to be registered: if a witch takes too + much chip before the crowning shot, the final shot will be considered + as doing too little damage for a crown (even if it would have been a crown + had the witch had more health). + - Charger levels are harder to get on chipped chargers. Any charger that + has taken (600 - 390 =) 210 damage or more cannot be leveled (even if + the melee swing would've killed the charger (1559 damage) if it'd have + had full health). + I strongly recommend leaving fakedamage visible: it will offer more feedback on + the survivor's action and reward survivors doing (what would be) full crowns and + levels on chipped targets. + + + To Do + ----- + + - fix: tank rock owner is not reliable for the RockEaten forward + - fix: tank rock skeets still unreliable detection (often triggers a 'skeet' when actually landed on someone) + + - fix: apparently some HR4 cars generate car alarm messages when shot, even when no alarm goes off + (combination with car equalize plugin?) + - see below: the single hook might also fix this.. -- if not, hook for sound + - do a hookoutput on prop_car_alarm's and use that to track the actual alarm + going off (might help in the case 2 alarms go off exactly at the same time?) + - fix: double prints on car alarms (sometimes? epi + m60) + + - fix: sometimes instaclear reports double for single clear (0.16s / 0.19s) epi saw this, was for hunter + - fix: deadstops and m2s don't always register .. no idea why.. + - fix: sometimes a (first?) round doesn't work for skeet detection.. no hurt/full skeets are reported or counted + + - make forwards fire for every potential action, + - include the relevant values, so other plugins can decide for themselves what to consider it + + - test chargers getting dislodged with boomer pops? + + - add commonhop check + - add deathcharge assist check + - smoker + - jockey + + - add deathcharge coordinates for some areas + - DT4 next to saferoom + - DA1 near the lower roof, on sidewalk next to fence (no hurttrigger there) + - DA2 next to crane roof to the right of window + DA2 charge down into start area, after everyone's jumped the fence + + - count rock hits even if they do no damage [epi request] + - sir + - make separate teamskeet forward, with (for now, up to) 4 skeeters + the damage each did + - xan + - add detection/display of unsuccesful witch crowns (witch death + info) + + detect... + - ? add jockey deadstops (and change forward to reflect type) + - ? speedcrown detection? + - ? spit-on-cap detection + + --- + done: + - applied sanity bounds to calculated damage for hunter dps + - removed tank's name from rock skeet print + - 300+ speed hops are considered hops even if no increase +*/ + +public Plugin:myinfo = +{ + name = "Skill Detection (skeets, crowns, levels)", + author = "Tabun", + description = "Detects and reports skeets, crowns, levels, highpounces, etc.", + version = PLUGIN_VERSION, + url = "https://github.com/Tabbernaut/L4D2-Plugins" +} + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + RegPluginLibrary("skill_detect"); + + g_hForwardSkeet = CreateGlobalForward("OnSkeet", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardSkeetHurt = CreateGlobalForward("OnSkeetHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardSkeetMelee = CreateGlobalForward("OnSkeetMelee", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardSkeetMeleeHurt = CreateGlobalForward("OnSkeetMeleeHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardSkeetSniper = CreateGlobalForward("OnSkeetSniper", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardSkeetSniperHurt = CreateGlobalForward("OnSkeetSniperHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardSkeetGL = CreateGlobalForward("OnSkeetGL", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardSIShove = CreateGlobalForward("OnSpecialShoved", ET_Ignore, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardHunterDeadstop = CreateGlobalForward("OnHunterDeadstop", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardBoomerPop = CreateGlobalForward("OnBoomerPop", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float ); + g_hForwardLevel = CreateGlobalForward("OnChargerLevel", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardLevelHurt = CreateGlobalForward("OnChargerLevelHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardCrown = CreateGlobalForward("OnWitchCrown", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardDrawCrown = CreateGlobalForward("OnWitchDrawCrown", ET_Ignore, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardTongueCut = CreateGlobalForward("OnTongueCut", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardSmokerSelfClear = CreateGlobalForward("OnSmokerSelfClear", ET_Ignore, Param_Cell, Param_Cell, Param_Cell ); + g_hForwardRockSkeeted = CreateGlobalForward("OnTankRockSkeeted", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardRockEaten = CreateGlobalForward("OnTankRockEaten", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardHunterDP = CreateGlobalForward("OnHunterHighPounce", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Float, Param_Cell, Param_Cell ); + g_hForwardJockeyDP = CreateGlobalForward("OnJockeyHighPounce", ET_Ignore, Param_Cell, Param_Cell, Param_Float, Param_Cell ); + g_hForwardDeathCharge = CreateGlobalForward("OnDeathCharge", ET_Ignore, Param_Cell, Param_Cell, Param_Float, Param_Float, Param_Cell ); + g_hForwardClear = CreateGlobalForward("OnSpecialClear", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Float, Param_Cell ); + g_hForwardVomitLanded = CreateGlobalForward("OnBoomerVomitLanded", ET_Ignore, Param_Cell, Param_Cell ); + g_hForwardBHopStreak = CreateGlobalForward("OnBunnyHopStreak", ET_Ignore, Param_Cell, Param_Cell, Param_Float ); + g_hForwardAlarmTriggered = CreateGlobalForward("OnCarAlarmTriggered", ET_Ignore, Param_Cell, Param_Cell, Param_Cell ); + g_bLateLoad = late; + + return APLRes_Success; +} + +public OnPluginStart() +{ + // hooks + HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy); + HookEvent("scavenge_round_start", Event_RoundStart, EventHookMode_PostNoCopy); + HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy); + + HookEvent("player_spawn", Event_PlayerSpawn, EventHookMode_Post); + HookEvent("player_hurt", Event_PlayerHurt, EventHookMode_Pre); + HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre); + HookEvent("ability_use", Event_AbilityUse, EventHookMode_Post); + HookEvent("lunge_pounce", Event_LungePounce, EventHookMode_Post); + HookEvent("player_shoved", Event_PlayerShoved, EventHookMode_Post); + HookEvent("player_jump", Event_PlayerJumped, EventHookMode_Post); + HookEvent("player_jump_apex", Event_PlayerJumpApex, EventHookMode_Post); + + HookEvent("player_now_it", Event_PlayerBoomed, EventHookMode_Post); + HookEvent("boomer_exploded", Event_BoomerExploded, EventHookMode_Post); + + //HookEvent("infected_hurt", Event_InfectedHurt, EventHookMode_Post); + HookEvent("witch_spawn", Event_WitchSpawned, EventHookMode_Post); + HookEvent("witch_killed", Event_WitchKilled, EventHookMode_Post); + HookEvent("witch_harasser_set", Event_WitchHarasserSet, EventHookMode_Post); + + HookEvent("tongue_grab", Event_TongueGrab, EventHookMode_Post); + HookEvent("tongue_pull_stopped", Event_TonguePullStopped, EventHookMode_Post); + HookEvent("choke_start", Event_ChokeStart, EventHookMode_Post); + HookEvent("choke_stopped", Event_ChokeStop, EventHookMode_Post); + HookEvent("jockey_ride", Event_JockeyRide, EventHookMode_Post); + HookEvent("charger_carry_start", Event_ChargeCarryStart, EventHookMode_Post); + HookEvent("charger_carry_end", Event_ChargeCarryEnd, EventHookMode_Post); + HookEvent("charger_impact", Event_ChargeImpact, EventHookMode_Post); + HookEvent("charger_pummel_start", Event_ChargePummelStart, EventHookMode_Post); + + HookEvent("player_incapacitated_start", Event_IncapStart, EventHookMode_Post); + HookEvent("triggered_car_alarm", Event_CarAlarmGoesOff, EventHookMode_Post); + + + // version cvar + CreateConVar( "sm_skill_detect_version", PLUGIN_VERSION, "Skill detect plugin version.", FCVAR_NONE|FCVAR_NOTIFY|FCVAR_REPLICATED|FCVAR_DONTRECORD ); + + // cvars: config + + g_hCvarReport = CreateConVar( "sm_skill_report_enable" , "0", "Whether to report in chat (see sm_skill_report_flags).", FCVAR_NONE, true, 0.0, true, 1.0 ); + g_hCvarReportFlags = CreateConVar( "sm_skill_report_flags" , REP_DEFAULT, "What to report skeets in chat (bitflags: 1,2:skeets/hurt; 4,8:level/chip; 16,32:crown/draw; 64,128:cut/selfclear, ... ).", FCVAR_NONE, true, 0.0 ); + + g_hCvarAllowMelee = CreateConVar( "sm_skill_skeet_allowmelee", "1", "Whether to count/forward melee skeets.", FCVAR_NONE, true, 0.0, true, 1.0 ); + g_hCvarAllowSniper = CreateConVar( "sm_skill_skeet_allowsniper", "1", "Whether to count/forward sniper/magnum headshots as skeets.", FCVAR_NONE, true, 0.0, true, 1.0 ); + g_hCvarAllowGLSkeet = CreateConVar( "sm_skill_skeet_allowgl", "1", "Whether to count/forward direct GL hits as skeets.", FCVAR_NONE, true, 0.0, true, 1.0 ); + g_hCvarDrawCrownThresh = CreateConVar( "sm_skill_drawcrown_damage", "500", "How much damage a survivor must at least do in the final shot for it to count as a drawcrown.", FCVAR_NONE, true, 0.0, false ); + g_hCvarSelfClearThresh = CreateConVar( "sm_skill_selfclear_damage", "200", "How much damage a survivor must at least do to a smoker for him to count as self-clearing.", FCVAR_NONE, true, 0.0, false ); + g_hCvarHunterDPThresh = CreateConVar( "sm_skill_hunterdp_height", "400", "Minimum height of hunter pounce for it to count as a DP.", FCVAR_NONE, true, 0.0, false ); + g_hCvarJockeyDPThresh = CreateConVar( "sm_skill_jockeydp_height", "300", "How much height distance a jockey must make for his 'DP' to count as a reportable highpounce.", FCVAR_NONE, true, 0.0, false ); + g_hCvarHideFakeDamage = CreateConVar( "sm_skill_hidefakedamage", "0", "If set, any damage done that exceeds the health of a victim is hidden in reports.", FCVAR_NONE, true, 0.0, true, 1.0 ); + g_hCvarDeathChargeHeight = CreateConVar("sm_skill_deathcharge_height","400", "How much height distance a charger must take its victim for a deathcharge to be reported.", FCVAR_NONE, true, 0.0, false ); + g_hCvarInstaTime = CreateConVar( "sm_skill_instaclear_time", "0.75", "A clear within this time (in seconds) counts as an insta-clear.", FCVAR_NONE, true, 0.0, false ); + g_hCvarBHopMinStreak = CreateConVar( "sm_skill_bhopstreak", "3", "The lowest bunnyhop streak that will be reported.", FCVAR_NONE, true, 0.0, false ); + g_hCvarBHopMinInitSpeed = CreateConVar( "sm_skill_bhopinitspeed", "150", "The minimal speed of the first jump of a bunnyhopstreak (0 to allow 'hops' from standstill).", FCVAR_NONE, true, 0.0, false ); + g_hCvarBHopContSpeed = CreateConVar( "sm_skill_bhopkeepspeed", "300", "The minimal speed at which hops are considered succesful even if not speed increase is made.", FCVAR_NONE, true, 0.0, false ); + + // cvars: built in + g_hCvarPounceInterrupt = FindConVar("z_pounce_damage_interrupt"); + HookConVarChange(g_hCvarPounceInterrupt, CvarChange_PounceInterrupt); + g_iPounceInterrupt = GetConVarInt(g_hCvarPounceInterrupt); + + g_hCvarChargerHealth = FindConVar("z_charger_health"); + g_hCvarWitchHealth = FindConVar("z_witch_health"); + + g_hCvarMaxPounceDistance = FindConVar("z_pounce_damage_range_max"); + g_hCvarMinPounceDistance = FindConVar("z_pounce_damage_range_min"); + g_hCvarMaxPounceDamage = FindConVar("z_hunter_max_pounce_bonus_damage"); + if ( g_hCvarMaxPounceDistance == INVALID_HANDLE ) { g_hCvarMaxPounceDistance = CreateConVar( "z_pounce_damage_range_max", "1000.0", "Not available on this server, added by l4d2_skill_detect.", FCVAR_NONE, true, 0.0, false ); } + if ( g_hCvarMinPounceDistance == INVALID_HANDLE ) { g_hCvarMinPounceDistance = CreateConVar( "z_pounce_damage_range_min", "300.0", "Not available on this server, added by l4d2_skill_detect.", FCVAR_NONE, true, 0.0, false ); } + if ( g_hCvarMaxPounceDamage == INVALID_HANDLE ) { g_hCvarMaxPounceDamage = CreateConVar( "z_hunter_max_pounce_bonus_damage", "49", "Not available on this server, added by l4d2_skill_detect.", FCVAR_NONE, true, 0.0, false ); } + + + // tries + g_hTrieWeapons = CreateTrie(); + SetTrieValue(g_hTrieWeapons, "hunting_rifle", WPTYPE_SNIPER); + SetTrieValue(g_hTrieWeapons, "sniper_military", WPTYPE_SNIPER); + SetTrieValue(g_hTrieWeapons, "sniper_awp", WPTYPE_SNIPER); + SetTrieValue(g_hTrieWeapons, "sniper_scout", WPTYPE_SNIPER); + SetTrieValue(g_hTrieWeapons, "pistol_magnum", WPTYPE_MAGNUM); + SetTrieValue(g_hTrieWeapons, "grenade_launcher_projectile", WPTYPE_GL); + + g_hTrieEntityCreated = CreateTrie(); + SetTrieValue(g_hTrieEntityCreated, "tank_rock", OEC_TANKROCK); + SetTrieValue(g_hTrieEntityCreated, "witch", OEC_WITCH); + SetTrieValue(g_hTrieEntityCreated, "trigger_hurt", OEC_TRIGGER); + SetTrieValue(g_hTrieEntityCreated, "prop_car_alarm", OEC_CARALARM); + SetTrieValue(g_hTrieEntityCreated, "prop_car_glass", OEC_CARGLASS); + + g_hTrieAbility = CreateTrie(); + SetTrieValue(g_hTrieAbility, "ability_lunge", ABL_HUNTERLUNGE); + SetTrieValue(g_hTrieAbility, "ability_throw", ABL_ROCKTHROW); + + g_hWitchTrie = CreateTrie(); + g_hRockTrie = CreateTrie(); + g_hCarTrie = CreateTrie(); + + if ( g_bLateLoad ) + { + for ( new client = 1; client <= MaxClients; client++ ) + { + if ( IS_VALID_INGAME(client) ) + { + SDKHook( client, SDKHook_OnTakeDamage, OnTakeDamageByWitch ); + } + } + } + + AutoExecConfig(true, "l4d2_skill_detect"); +} + +public CvarChange_PounceInterrupt( Handle:convar, const String:oldValue[], const String:newValue[] ) +{ + g_iPounceInterrupt = GetConVarInt(convar); +} + +public OnClientPostAdminCheck(client) +{ + SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamageByWitch); +} + +public OnClientDisconnect(client) +{ + SDKUnhook(client, SDKHook_OnTakeDamage, OnTakeDamageByWitch); +} + + + +/* + Tracking + -------- +*/ + +public Action: Event_RoundStart( Handle:event, const String:name[], bool:dontBroadcast ) +{ + g_iRocksBeingThrownCount = 0; + + for ( new i = 1; i <= MaxClients; i++ ) + { + g_bIsHopping[i] = false; + + for ( new j = 1; j <= MaxClients; j++ ) + { + g_fVictimLastShove[i][j] = 0.0; + } + } +} + +public Action: Event_RoundEnd( Handle:event, const String:name[], bool:dontBroadcast ) +{ + // clean trie, new cars will be created + ClearTrie(g_hCarTrie); +} + +public Action: Event_PlayerHurt( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new victim = GetClientOfUserId(GetEventInt(event, "userid")); + new attacker = GetClientOfUserId(GetEventInt(event, "attacker")); + new zClass; + + new damage = GetEventInt(event, "dmg_health"); + new damagetype = GetEventInt(event, "type"); + + if ( IS_VALID_INFECTED(victim) ) + { + zClass = GetEntProp(victim, Prop_Send, "m_zombieClass"); + new health = GetEventInt(event, "health"); + new hitgroup = GetEventInt(event, "hitgroup"); + + if ( damage < 1 ) { return Plugin_Continue; } + + switch ( zClass ) + { + case ZC_HUNTER: + { + // if it's not a survivor doing the work, only get the remaining health + if ( !IS_VALID_SURVIVOR(attacker) ) + { + g_iHunterLastHealth[victim] = health; + return Plugin_Continue; + } + + // if the damage done is greater than the health we know the hunter to have remaining, reduce the damage done + if ( g_iHunterLastHealth[victim] > 0 && damage > g_iHunterLastHealth[victim] ) + { + damage = g_iHunterLastHealth[victim]; + g_iHunterOverkill[victim] = g_iHunterLastHealth[victim] - damage; + g_iHunterLastHealth[victim] = 0; + } + + /* + handle old shotgun blast: too long ago? not the same blast + */ + if ( g_iHunterShotDmg[victim][attacker] > 0 && (GetGameTime() - g_fHunterShotStart[victim][attacker]) > SHOTGUN_BLAST_TIME ) + { + g_fHunterShotStart[victim][attacker] = 0.0; + } + + /* + m_isAttemptingToPounce is set to 0 here if the hunter is actually skeeted + so the g_fHunterTracePouncing[victim] value indicates when the hunter was last seen pouncing in traceattack + (should be DIRECTLY before this event for every shot). + */ + new bool: isPouncing = bool:( + GetEntProp(victim, Prop_Send, "m_isAttemptingToPounce") || + g_fHunterTracePouncing[victim] != 0.0 && ( GetGameTime() - g_fHunterTracePouncing[victim] ) < 0.001 + ); + + if ( isPouncing ) + { + if ( damagetype & DMG_BUCKSHOT ) + { + // first pellet hit? + if ( g_fHunterShotStart[victim][attacker] == 0.0 ) + { + // new shotgun blast + g_fHunterShotStart[victim][attacker] = GetGameTime(); + g_fHunterLastShot[victim] = g_fHunterShotStart[victim][attacker]; + } + g_iHunterShotDmg[victim][attacker] += damage; + g_iHunterShotDmgTeam[victim] += damage; + + if ( health == 0 ) { + g_bHunterKilledPouncing[victim] = true; + } + } + else if ( damagetype & (DMG_BLAST | DMG_PLASMA) && health == 0 ) + { + // direct GL hit? + /* + direct hit is DMG_BLAST | DMG_PLASMA + indirect hit is DMG_AIRBOAT + */ + + decl String: weaponB[32]; + new strWeaponType: weaponTypeB; + GetEventString(event, "weapon", weaponB, sizeof(weaponB)); + + if ( GetTrieValue(g_hTrieWeapons, weaponB, weaponTypeB) && weaponTypeB == WPTYPE_GL ) + { + if ( GetConVarBool(g_hCvarAllowGLSkeet) ) { + HandleSkeet( attacker, victim, false, false, true ); + } + } + } + else if ( damagetype & DMG_BULLET && + health == 0 && + hitgroup == HITGROUP_HEAD + ) { + // headshot with bullet based weapon (only single shots) -- only snipers + decl String: weaponA[32]; + new strWeaponType: weaponTypeA; + GetEventString(event, "weapon", weaponA, sizeof(weaponA)); + + if ( GetTrieValue(g_hTrieWeapons, weaponA, weaponTypeA) && + ( weaponTypeA == WPTYPE_SNIPER || + weaponTypeA == WPTYPE_MAGNUM ) + ) { + if ( damage >= g_iPounceInterrupt ) + { + g_iHunterShotDmgTeam[victim] = 0; + if ( GetConVarBool(g_hCvarAllowSniper) ) { + HandleSkeet( attacker, victim, false, true ); + } + ResetHunter(victim); + } + else + { + // hurt skeet + if ( GetConVarBool(g_hCvarAllowSniper) ) { + HandleNonSkeet( attacker, victim, damage, ( g_iHunterOverkill[victim] + g_iHunterShotDmgTeam[victim] > g_iPounceInterrupt ), false, true ); + } + ResetHunter(victim); + } + } + + // already handled hurt skeet above + //g_bHunterKilledPouncing[victim] = true; + } + else if ( damagetype & DMG_SLASH || damagetype & DMG_CLUB ) + { + // melee skeet + if ( damage >= g_iPounceInterrupt ) + { + g_iHunterShotDmgTeam[victim] = 0; + if ( GetConVarBool(g_hCvarAllowMelee) ) { + HandleSkeet( attacker, victim, true ); + } + ResetHunter(victim); + //g_bHunterKilledPouncing[victim] = true; + } + else if ( health == 0 ) + { + // hurt skeet (always overkill) + if ( GetConVarBool(g_hCvarAllowMelee) ) { + HandleNonSkeet( attacker, victim, damage, true, true, false ); + } + ResetHunter(victim); + } + } + } + else if ( health == 0 ) + { + // make sure we don't mistake non-pouncing hunters as 'not skeeted'-warnable + g_bHunterKilledPouncing[victim] = false; + } + + // store last health seen for next damage event + g_iHunterLastHealth[victim] = health; + } + + case ZC_CHARGER: + { + if ( IS_VALID_SURVIVOR(attacker) ) + { + // check for levels + if ( health == 0 && ( damagetype & DMG_CLUB || damagetype & DMG_SLASH ) ) + { + new iChargeHealth = GetConVarInt(g_hCvarChargerHealth); + new abilityEnt = GetEntPropEnt( victim, Prop_Send, "m_customAbility" ); + if ( IsValidEntity(abilityEnt) && GetEntProp(abilityEnt, Prop_Send, "m_isCharging") ) + { + // fix fake damage? + if ( GetConVarBool(g_hCvarHideFakeDamage) ) + { + damage = iChargeHealth - g_iChargerHealth[victim]; + } + + // charger was killed, was it a full level? + if ( damage > (iChargeHealth * 0.65) ) { + HandleLevel( attacker, victim ); + } + else { + HandleLevelHurt( attacker, victim, damage ); + } + } + } + } + + // store health for next damage it takes + if ( health > 0 ) + { + g_iChargerHealth[victim] = health; + } + } + + case ZC_SMOKER: + { + if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; } + + g_iSmokerVictimDamage[victim] += damage; + } + + } + } + else if ( IS_VALID_INFECTED(attacker) ) + { + zClass = GetEntProp(attacker, Prop_Send, "m_zombieClass"); + + switch ( zClass ) + { + case ZC_HUNTER: + { + // a hunter pounce landing is DMG_CRUSH + if ( damagetype & DMG_CRUSH ) { + g_iPounceDamage[attacker] = damage; + } + } + + case ZC_TANK: + { + new String: weapon[10]; + GetEventString(event, "weapon", weapon, sizeof(weapon)); + + if ( StrEqual(weapon, "tank_rock") ) + { + // find rock entity through tank + if ( g_iTankRock[attacker] ) + { + // remember that the rock wasn't shot + decl String:rock_key[10]; + FormatEx(rock_key, sizeof(rock_key), "%x", g_iTankRock[attacker]); + new rock_array[3]; + rock_array[rckDamage] = -1; + SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true); + } + + if ( IS_VALID_SURVIVOR(victim) ) + { + HandleRockEaten( attacker, victim ); + } + } + + return Plugin_Continue; + } + } + } + + // check for deathcharge flags + if ( IS_VALID_SURVIVOR(victim) ) + { + // debug + if ( damagetype & DMG_DROWN || damagetype & DMG_FALL ) { + g_iVictimMapDmg[victim] += damage; + } + + if ( damagetype & DMG_DROWN && damage >= MIN_DC_TRIGGER_DMG ) + { + g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_HURTLOTS; + } + else if ( damagetype & DMG_FALL && damage >= MIN_DC_FALL_DMG ) + { + g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_HURTLOTS; + } + } + + return Plugin_Continue; +} + +public Action: Event_PlayerSpawn( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId(GetEventInt(event, "userid")); + if ( !IS_VALID_INFECTED(client) ) { return Plugin_Continue; } + + new zClass = GetEntProp(client, Prop_Send, "m_zombieClass"); + + g_fSpawnTime[client] = GetGameTime(); + g_fPinTime[client][0] = 0.0; + g_fPinTime[client][1] = 0.0; + + switch ( zClass ) + { + case ZC_BOOMER: + { + g_bBoomerHitSomebody[client] = false; + g_iBoomerGotShoved[client] = 0; + } + case ZC_SMOKER: + { + g_bSmokerClearCheck[client] = false; + g_iSmokerVictim[client] = 0; + g_iSmokerVictimDamage[client] = 0; + } + case ZC_HUNTER: + { + SDKHook(client, SDKHook_TraceAttack, TraceAttack_Hunter); + + g_fPouncePosition[client][0] = 0.0; + g_fPouncePosition[client][1] = 0.0; + g_fPouncePosition[client][2] = 0.0; + } + case ZC_JOCKEY: + { + SDKHook(client, SDKHook_TraceAttack, TraceAttack_Jockey); + + g_fPouncePosition[client][0] = 0.0; + g_fPouncePosition[client][1] = 0.0; + g_fPouncePosition[client][2] = 0.0; + } + case ZC_CHARGER: + { + SDKHook(client, SDKHook_TraceAttack, TraceAttack_Charger); + + g_iChargerHealth[client] = GetConVarInt(g_hCvarChargerHealth); + } + } + + return Plugin_Continue; +} + +// player about to get incapped +public Action: Event_IncapStart( Handle:event, const String:name[], bool:dontBroadcast ) +{ + // test for deathcharges + + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + //new attacker = GetClientOfUserId( GetEventInt(event, "attacker") ); + new attackent = GetEventInt(event, "attackerentid"); + new dmgtype = GetEventInt(event, "type"); + + new String: classname[24]; + new strOEC: classnameOEC; + if ( IsValidEntity(attackent) ) { + GetEdictClassname(attackent, classname, sizeof(classname)); + if ( GetTrieValue(g_hTrieEntityCreated, classname, classnameOEC)) { + g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_TRIGGER; + } + } + + new Float: flow = GetSurvivorDistance(client); + + //PrintDebug( 3, "Incap Pre on [%N]: attk: %i / %i (%s) - dmgtype: %i - flow: %.1f", client, attacker, attackent, classname, dmgtype, flow ); + + // drown is damage type + if ( dmgtype & DMG_DROWN ) + { + g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_DROWN; + } + if ( flow < WEIRD_FLOW_THRESH ) + { + g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_WEIRDFLOW; + } +} + +// trace attacks on hunters +public Action: TraceAttack_Hunter (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup) +{ + // track pinning + g_iSpecialVictim[victim] = GetEntPropEnt(victim, Prop_Send, "m_pounceVictim"); + + if ( !IS_VALID_SURVIVOR(attacker) || !IsValidEdict(inflictor) ) { return; } + + // track flight + if ( GetEntProp(victim, Prop_Send, "m_isAttemptingToPounce") ) + { + g_fHunterTracePouncing[victim] = GetGameTime(); + } + else + { + g_fHunterTracePouncing[victim] = 0.0; + } +} +public Action: TraceAttack_Charger (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup) +{ + // track pinning + new victimA = GetEntPropEnt(victim, Prop_Send, "m_carryVictim"); + if ( victimA != -1 ) { + g_iSpecialVictim[victim] = victimA; + } else { + g_iSpecialVictim[victim] = GetEntPropEnt(victim, Prop_Send, "m_pummelVictim"); + } + +} +public Action: TraceAttack_Jockey (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup) +{ + // track pinning + g_iSpecialVictim[victim] = GetEntPropEnt(victim, Prop_Send, "m_jockeyVictim"); +} + +public Action: Event_PlayerDeath( Handle:hEvent, const String:name[], bool:dontBroadcast ) +{ + new victim = GetClientOfUserId( GetEventInt(hEvent, "userid") ); + new attacker = GetClientOfUserId( GetEventInt(hEvent, "attacker") ); + + if ( IS_VALID_INFECTED(victim) ) + { + new zClass = GetEntProp(victim, Prop_Send, "m_zombieClass"); + + switch ( zClass ) + { + case ZC_HUNTER: + { + if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; } + + if ( g_iHunterShotDmgTeam[victim] > 0 && g_bHunterKilledPouncing[victim] ) + { + // skeet? + if ( g_iHunterShotDmgTeam[victim] > g_iHunterShotDmg[victim][attacker] && + g_iHunterShotDmgTeam[victim] >= g_iPounceInterrupt + ) { + // team skeet + HandleSkeet( -2, victim ); + } + else if ( g_iHunterShotDmg[victim][attacker] >= g_iPounceInterrupt ) + { + // single player skeet + HandleSkeet( attacker, victim ); + } + else if ( g_iHunterOverkill[victim] > 0 ) + { + // overkill? might've been a skeet, if it wasn't on a hurt hunter (only for shotguns) + HandleNonSkeet( attacker, victim, g_iHunterShotDmgTeam[victim], ( g_iHunterOverkill[victim] + g_iHunterShotDmgTeam[victim] > g_iPounceInterrupt ) ); + } + else + { + // not a skeet at all + HandleNonSkeet( attacker, victim, g_iHunterShotDmg[victim][attacker] ); + } + } + else { + // check whether it was a clear + if ( g_iSpecialVictim[victim] > 0 ) + { + HandleClear( attacker, victim, g_iSpecialVictim[victim], + ZC_HUNTER, + ( GetGameTime() - g_fPinTime[victim][0]), + -1.0 + ); + } + } + + ResetHunter(victim); + } + + case ZC_SMOKER: + { + if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; } + + if ( g_bSmokerClearCheck[victim] && + g_iSmokerVictim[victim] == attacker && + g_iSmokerVictimDamage[victim] >= GetConVarInt(g_hCvarSelfClearThresh) + ) { + HandleSmokerSelfClear( attacker, victim ); + } + else + { + g_bSmokerClearCheck[victim] = false; + g_iSmokerVictim[victim] = 0; + } + } + + case ZC_JOCKEY: + { + // check whether it was a clear + if ( g_iSpecialVictim[victim] > 0 ) + { + HandleClear( attacker, victim, g_iSpecialVictim[victim], + ZC_JOCKEY, + ( GetGameTime() - g_fPinTime[victim][0]), + -1.0 + ); + } + } + + case ZC_CHARGER: + { + // is it someone carrying a survivor (that might be DC'd)? + // switch charge victim to 'impact' check (reset checktime) + if ( IS_VALID_INGAME(g_iChargeVictim[victim]) ) { + g_fChargeTime[ g_iChargeVictim[victim] ] = GetGameTime(); + } + + // check whether it was a clear + if ( g_iSpecialVictim[victim] > 0 ) + { + HandleClear( attacker, victim, g_iSpecialVictim[victim], + ZC_CHARGER, + (g_fPinTime[victim][1] > 0.0) ? ( GetGameTime() - g_fPinTime[victim][1]) : -1.0, + ( GetGameTime() - g_fPinTime[victim][0]) + ); + } + } + } + } + else if ( IS_VALID_SURVIVOR(victim) ) + { + // check for deathcharges + //new atkent = GetEventInt(hEvent, "attackerentid"); + new dmgtype = GetEventInt(hEvent, "type"); + + //PrintDebug( 3, "Died [%N]: attk: %i / %i - dmgtype: %i", victim, attacker, atkent, dmgtype ); + + if ( dmgtype & DMG_FALL) + { + g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_FALL; + } + else if ( IS_VALID_INFECTED(attacker) && attacker != g_iVictimCharger[victim] ) + { + // if something other than the charger killed them, remember (not a DC) + g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_KILLEDBYOTHER; + } + } + + return Plugin_Continue; +} + +public Action: Event_PlayerShoved( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new victim = GetClientOfUserId(GetEventInt(event, "userid")); + new attacker = GetClientOfUserId(GetEventInt(event, "attacker")); + + //PrintDebug(1, "Shove from %i on %i", attacker, victim); + + if ( !IS_VALID_SURVIVOR(attacker) || !IS_VALID_INFECTED(victim) ) { return Plugin_Continue; } + + new zClass = GetEntProp(victim, Prop_Send, "m_zombieClass"); + + //PrintDebug(1, " --> Shove from %N on %N (class: %i) -- (last shove time: %.2f / %.2f)", attacker, victim, zClass, g_fVictimLastShove[victim][attacker], ( GetGameTime() - g_fVictimLastShove[victim][attacker] ) ); + + // track on boomers + if ( zClass == ZC_BOOMER ) + { + g_iBoomerGotShoved[victim]++; + } + else { + // check for clears + switch ( zClass ) + { + case ZC_HUNTER: { + if ( GetEntPropEnt(victim, Prop_Send, "m_pounceVictim") > 0 ) + { + HandleClear( attacker, victim, GetEntPropEnt(victim, Prop_Send, "m_pounceVictim"), + ZC_HUNTER, + ( GetGameTime() - g_fPinTime[victim][0]), + -1.0, + true + ); + } + } + case ZC_JOCKEY: { + if ( GetEntPropEnt(victim, Prop_Send, "m_jockeyVictim") > 0 ) + { + HandleClear( attacker, victim, GetEntPropEnt(victim, Prop_Send, "m_jockeyVictim"), + ZC_JOCKEY, + ( GetGameTime() - g_fPinTime[victim][0]), + -1.0, + true + ); + } + } + } + } + + if ( g_fVictimLastShove[victim][attacker] == 0.0 || ( GetGameTime() - g_fVictimLastShove[victim][attacker] ) >= SHOVE_TIME ) + { + if ( GetEntProp(victim, Prop_Send, "m_isAttemptingToPounce") ) + { + HandleDeadstop( attacker, victim ); + } + + HandleShove( attacker, victim, zClass ); + + g_fVictimLastShove[victim][attacker] = GetGameTime(); + } + + // check for shove on smoker by pull victim + if ( g_iSmokerVictim[victim] == attacker ) + { + g_bSmokerShoved[victim] = true; + } + + //PrintDebug(0, "shove by %i on %i", attacker, victim ); + return Plugin_Continue; +} + +public Action: Event_LungePounce( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + + g_fPinTime[client][0] = GetGameTime(); + + // clear hunter-hit stats (not skeeted) + ResetHunter(client); + + // check if it was a DP + // ignore if no real pounce start pos + if ( g_fPouncePosition[client][0] == 0.0 + && g_fPouncePosition[client][1] == 0.0 + && g_fPouncePosition[client][2] == 0.0 + ) { + return Plugin_Continue; + } + + new Float: endPos[3]; + GetClientAbsOrigin( client, endPos ); + new Float: fHeight = g_fPouncePosition[client][2] - endPos[2]; + + // from pounceannounce: + // distance supplied isn't the actual 2d vector distance needed for damage calculation. See more about it at + // http://forums.alliedmods.net/showthread.php?t=93207 + + new Float: fMin = GetConVarFloat(g_hCvarMinPounceDistance); + new Float: fMax = GetConVarFloat(g_hCvarMaxPounceDistance); + new Float: fMaxDmg = GetConVarFloat(g_hCvarMaxPounceDamage); + + // calculate 2d distance between previous position and pounce position + new distance = RoundToNearest( GetVectorDistance(g_fPouncePosition[client], endPos) ); + + // get damage using hunter damage formula + // check if this is accurate, seems to differ from actual damage done! + new Float: fDamage = ( ( (float(distance) - fMin) / (fMax - fMin) ) * fMaxDmg ) + 1.0; + + // apply bounds + if (fDamage < 0.0) { + fDamage = 0.0; + } else if (fDamage > fMaxDmg + 1.0) { + fDamage = fMaxDmg + 1.0; + } + + new Handle: pack = CreateDataPack(); + WritePackCell( pack, client ); + WritePackCell( pack, victim ); + WritePackFloat( pack, fDamage ); + WritePackFloat( pack, fHeight ); + CreateTimer( 0.05, Timer_HunterDP, pack ); + + return Plugin_Continue; +} + +public Action: Timer_HunterDP( Handle:timer, Handle:pack ) +{ + ResetPack( pack ); + new client = ReadPackCell( pack ); + new victim = ReadPackCell( pack ); + new Float: fDamage = ReadPackFloat( pack ); + new Float: fHeight = ReadPackFloat( pack ); + CloseHandle( pack ); + + HandleHunterDP( client, victim, g_iPounceDamage[client], fDamage, fHeight ); +} + +public Action: Event_PlayerJumped( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + + if ( IS_VALID_INFECTED(client) ) + { + new zClass = GetEntProp(client, Prop_Send, "m_zombieClass"); + if ( zClass != ZC_JOCKEY ) { return Plugin_Continue; } + + // where did jockey jump from? + GetClientAbsOrigin( client, g_fPouncePosition[client] ); + } + else if ( IS_VALID_SURVIVOR(client) ) + { + // could be the start or part of a hopping streak + + new Float: fPos[3], Float: fVel[3]; + GetClientAbsOrigin( client, fPos ); + GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVel ); + fVel[2] = 0.0; // safeguard + + new Float: fLengthNew, Float: fLengthOld; + fLengthNew = GetVectorLength(fVel); + + + g_bHopCheck[client] = false; + + if ( !g_bIsHopping[client] ) + { + if ( fLengthNew >= GetConVarFloat(g_hCvarBHopMinInitSpeed) ) + { + // starting potential hop streak + g_fHopTopVelocity[client] = fLengthNew; + g_bIsHopping[client] = true; + g_iHops[client] = 0; + } + } + else + { + // check for hopping streak + fLengthOld = GetVectorLength(g_fLastHop[client]); + + // if they picked up speed, count it as a hop, otherwise, we're done hopping + if ( fLengthNew - fLengthOld > HOP_ACCEL_THRESH || fLengthNew >= GetConVarFloat(g_hCvarBHopContSpeed) ) + { + g_iHops[client]++; + + // this should always be the case... + if ( fLengthNew > g_fHopTopVelocity[client] ) + { + g_fHopTopVelocity[client] = fLengthNew; + } + + //PrintToChat( client, "bunnyhop %i: speed: %.1f / increase: %.1f", g_iHops[client], fLengthNew, fLengthNew - fLengthOld ); + } + else + { + g_bIsHopping[client] = false; + + if ( g_iHops[client] ) + { + HandleBHopStreak( client, g_iHops[client], g_fHopTopVelocity[client] ); + g_iHops[client] = 0; + } + } + } + + g_fLastHop[client][0] = fVel[0]; + g_fLastHop[client][1] = fVel[1]; + g_fLastHop[client][2] = fVel[2]; + + if ( g_iHops[client] != 0 ) + { + // check when the player returns to the ground + CreateTimer( HOP_CHECK_TIME, Timer_CheckHop, client, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE ); + } + } + + return Plugin_Continue; +} + +public Action: Timer_CheckHop (Handle:timer, any:client) +{ + // player back to ground = end of hop (streak)? + + if ( !IS_VALID_INGAME(client) || !IsPlayerAlive(client) ) + { + // streak stopped by dying / teamswitch / disconnect? + return Plugin_Stop; + } + else if ( GetEntityFlags(client) & FL_ONGROUND ) + { + new Float: fVel[3]; + GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVel ); + fVel[2] = 0.0; // safeguard + + //PrintToChatAll("grounded %i: vel length: %.1f", client, GetVectorLength(fVel) ); + + g_bHopCheck[client] = true; + + CreateTimer( HOPEND_CHECK_TIME, Timer_CheckHopStreak, client, TIMER_FLAG_NO_MAPCHANGE ); + + return Plugin_Stop; + } + + return Plugin_Continue; +} + +public Action: Timer_CheckHopStreak (Handle:timer, any:client) +{ + if ( !IS_VALID_INGAME(client) || !IsPlayerAlive(client) ) { return Plugin_Continue; } + + // check if we have any sort of hop streak, and report + if ( g_bHopCheck[client] && g_iHops[client] ) + { + HandleBHopStreak( client, g_iHops[client], g_fHopTopVelocity[client] ); + g_bIsHopping[client] = false; + g_iHops[client] = 0; + g_fHopTopVelocity[client] = 0.0; + } + + g_bHopCheck[client] = false; + + return Plugin_Continue; +} + + +public Action: Event_PlayerJumpApex( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + + if ( g_bIsHopping[client] ) + { + new Float: fVel[3]; + GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVel ); + fVel[2] = 0.0; + new Float: fLength = GetVectorLength(fVel); + + if ( fLength > g_fHopTopVelocity[client] ) + { + g_fHopTopVelocity[client] = fLength; + } + } +} + + +public Action: Event_JockeyRide( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + + if ( !IS_VALID_INFECTED(client) || !IS_VALID_SURVIVOR(victim) ) { return Plugin_Continue; } + + g_fPinTime[client][0] = GetGameTime(); + + // minimum distance travelled? + // ignore if no real pounce start pos + if ( g_fPouncePosition[client][0] == 0.0 && g_fPouncePosition[client][1] == 0.0 && g_fPouncePosition[client][2] == 0.0 ) { return Plugin_Continue; } + + new Float: endPos[3]; + GetClientAbsOrigin( client, endPos ); + new Float: fHeight = g_fPouncePosition[client][2] - endPos[2]; + + //PrintToChatAll("jockey height: %.3f", fHeight); + + // (high) pounce + HandleJockeyDP( client, victim, fHeight ); + + return Plugin_Continue; +} + +public Action: Event_AbilityUse( Handle:event, const String:name[], bool:dontBroadcast ) +{ + // track hunters pouncing + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + new String: abilityName[64]; + GetEventString( event, "ability", abilityName, sizeof(abilityName) ); + + if ( !IS_VALID_INGAME(client) ) { return Plugin_Continue; } + + new strAbility: ability; + if ( !GetTrieValue(g_hTrieAbility, abilityName, ability) ) { return Plugin_Continue; } + + switch ( ability ) + { + case ABL_HUNTERLUNGE: + { + // hunter started a pounce + ResetHunter(client); + GetClientAbsOrigin( client, g_fPouncePosition[client] ); + } + + case ABL_ROCKTHROW: + { + // tank throws rock + g_iRocksBeingThrown[g_iRocksBeingThrownCount] = client; + + // safeguard + if ( g_iRocksBeingThrownCount < 9 ) { g_iRocksBeingThrownCount++; } + } + } + + return Plugin_Continue; +} + +// charger carrying +public Action: Event_ChargeCarryStart( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + if ( !IS_VALID_INFECTED(client) ) { return; } + + PrintDebug(0, "Charge carry start: %i - %i -- time: %.2f", client, victim, GetGameTime() ); + + g_fChargeTime[client] = GetGameTime(); + g_fPinTime[client][0] = g_fChargeTime[client]; + g_fPinTime[client][1] = 0.0; + + if ( !IS_VALID_SURVIVOR(victim) ) { return; } + + g_iChargeVictim[client] = victim; // store who we're carrying (as long as this is set, it's not considered an impact charge flight) + g_iVictimCharger[victim] = client; // store who's charging whom + g_iVictimFlags[victim] = VICFLG_CARRIED; // reset flags for checking later - we know only this now + g_fChargeTime[victim] = g_fChargeTime[client]; + g_iVictimMapDmg[victim] = 0; + + GetClientAbsOrigin( victim, g_fChargeVictimPos[victim] ); + + //CreateTimer( CHARGE_CHECK_TIME, Timer_ChargeCheck, client, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE ); + CreateTimer( CHARGE_CHECK_TIME, Timer_ChargeCheck, victim, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE ); +} + +public Action: Event_ChargeImpact( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + if ( !IS_VALID_INFECTED(client) || !IS_VALID_SURVIVOR(victim) ) { return; } + + // remember how many people the charger bumped into, and who, and where they were + GetClientAbsOrigin( victim, g_fChargeVictimPos[victim] ); + + g_iVictimCharger[victim] = client; // store who we've bumped up + g_iVictimFlags[victim] = 0; // reset flags for checking later + g_fChargeTime[victim] = GetGameTime(); // store time per victim, for impacts + g_iVictimMapDmg[victim] = 0; + + CreateTimer( CHARGE_CHECK_TIME, Timer_ChargeCheck, victim, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE ); +} + +public Action: Event_ChargePummelStart( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + + if ( !IS_VALID_INFECTED(client) ) { return; } + + g_fPinTime[client][1] = GetGameTime(); +} + + +public Action: Event_ChargeCarryEnd( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + if ( client < 1 || client > MaxClients ) { return; } + + g_fPinTime[client][1] = GetGameTime(); + + // delay so we can check whether charger died 'mid carry' + CreateTimer( 0.1, Timer_ChargeCarryEnd, client, TIMER_FLAG_NO_MAPCHANGE ); +} + +public Action: Timer_ChargeCarryEnd( Handle:timer, any:client ) +{ + // set charge time to 0 to avoid deathcharge timer continuing + g_iChargeVictim[client] = 0; // unset this so the repeated timer knows to stop for an ongroundcheck +} + +public Action: Timer_ChargeCheck( Handle:timer, any:client ) +{ + // if something went wrong with the survivor or it was too long ago, forget about it + if ( !IS_VALID_SURVIVOR(client) || !g_iVictimCharger[client] || g_fChargeTime[client] == 0.0 || ( GetGameTime() - g_fChargeTime[client]) > MAX_CHARGE_TIME ) + { + return Plugin_Stop; + } + + // we're done checking if either the victim reached the ground, or died + if ( !IsPlayerAlive(client) ) + { + // player died (this was .. probably.. a death charge) + g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_AIRDEATH; + + // check conditions now + CreateTimer( 0.0, Timer_DeathChargeCheck, client, TIMER_FLAG_NO_MAPCHANGE ); + + return Plugin_Stop; + } + else if ( GetEntityFlags(client) & FL_ONGROUND && g_iChargeVictim[ g_iVictimCharger[client] ] != client ) + { + // survivor reached the ground and didn't die (yet) + // the client-check condition checks whether the survivor is still being carried by the charger + // (in which case it doesn't matter that they're on the ground) + + // check conditions with small delay (to see if they still die soon) + CreateTimer( CHARGE_END_CHECK, Timer_DeathChargeCheck, client, TIMER_FLAG_NO_MAPCHANGE ); + + return Plugin_Stop; + } + + return Plugin_Continue; +} + +public Action: Timer_DeathChargeCheck( Handle:timer, any:client ) +{ + if ( !IS_VALID_INGAME(client) ) { return; } + + // check conditions.. if flags match up, it's a DC + PrintDebug( 3, "Checking charge victim: %i - %i - flags: %i (alive? %i)", g_iVictimCharger[client], client, g_iVictimFlags[client], IsPlayerAlive(client) ); + + new flags = g_iVictimFlags[client]; + + if ( !IsPlayerAlive(client) ) + { + new Float: pos[3]; + GetClientAbsOrigin( client, pos ); + new Float: fHeight = g_fChargeVictimPos[client][2] - pos[2]; + + /* + it's a deathcharge when: + the survivor is dead AND + they drowned/fell AND took enough damage or died in mid-air + AND not killed by someone else + OR is in an unreachable spot AND dropped at least X height + OR took plenty of map damage + + old.. need? + fHeight > GetConVarFloat(g_hCvarDeathChargeHeight) + */ + if ( ( ( flags & VICFLG_DROWN || flags & VICFLG_FALL ) && + ( flags & VICFLG_HURTLOTS || flags & VICFLG_AIRDEATH ) || + ( flags & VICFLG_WEIRDFLOW && fHeight >= MIN_FLOWDROPHEIGHT ) || + g_iVictimMapDmg[client] >= MIN_DC_TRIGGER_DMG + ) && + !( flags & VICFLG_KILLEDBYOTHER ) + ) { + HandleDeathCharge( g_iVictimCharger[client], client, fHeight, GetVectorDistance(g_fChargeVictimPos[client], pos, false), bool:(flags & VICFLG_CARRIED) ); + } + } + else if ( ( flags & VICFLG_WEIRDFLOW || g_iVictimMapDmg[client] >= MIN_DC_RECHECK_DMG ) && + !(flags & VICFLG_WEIRDFLOWDONE) + ) { + // could be incapped and dying more slowly + // flag only gets set on preincap, so don't need to check for incap + g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_WEIRDFLOWDONE; + + CreateTimer( CHARGE_END_RECHECK, Timer_DeathChargeCheck, client, TIMER_FLAG_NO_MAPCHANGE ); + } +} + +stock ResetHunter(client) +{ + g_iHunterShotDmgTeam[client] = 0; + + for ( new i=1; i <= MaxClients; i++ ) + { + g_iHunterShotDmg[client][i] = 0; + g_fHunterShotStart[client][i] = 0.0; + } + g_iHunterOverkill[client] = 0; +} + + +// entity creation +public OnEntityCreated ( entity, const String:classname[] ) +{ + if ( entity < 1 || !IsValidEntity(entity) || !IsValidEdict(entity) ) { return; } + + // track infected / witches, so damage on them counts as hits + + new strOEC: classnameOEC; + if (!GetTrieValue(g_hTrieEntityCreated, classname, classnameOEC)) { return; } + + switch ( classnameOEC ) + { + case OEC_TANKROCK: + { + decl String:rock_key[10]; + FormatEx(rock_key, sizeof(rock_key), "%x", entity); + new rock_array[3]; + + // store which tank is throwing what rock + new tank = ShiftTankThrower(); + + if ( IS_VALID_INGAME(tank) ) + { + g_iTankRock[tank] = entity; + rock_array[rckTank] = tank; + } + SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true); + + SDKHook(entity, SDKHook_TraceAttack, TraceAttack_Rock); + SDKHook(entity, SDKHook_Touch, OnTouch_Rock); + } + + + case OEC_CARALARM: + { + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", entity); + + SDKHook(entity, SDKHook_OnTakeDamage, OnTakeDamage_Car); + SDKHook(entity, SDKHook_Touch, OnTouch_Car); + + SDKHook(entity, SDKHook_Spawn, OnEntitySpawned_CarAlarm); + } + + case OEC_CARGLASS: + { + SDKHook(entity, SDKHook_OnTakeDamage, OnTakeDamage_CarGlass); + SDKHook(entity, SDKHook_Touch, OnTouch_CarGlass); + + //SetTrieValue(g_hCarTrie, car_key, ); + SDKHook(entity, SDKHook_Spawn, OnEntitySpawned_CarAlarmGlass); + } + } +} + +public OnEntitySpawned_CarAlarm ( entity ) +{ + if ( !IsValidEntity(entity) ) { return; } + + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", entity); + + decl String:target[48]; + GetEntPropString(entity, Prop_Data, "m_iName", target, sizeof(target)); + + SetTrieValue( g_hCarTrie, target, entity ); + SetTrieValue( g_hCarTrie, car_key, 0 ); // who shot the car? + + HookSingleEntityOutput( entity, "OnCarAlarmStart", Hook_CarAlarmStart ); +} + +public OnEntitySpawned_CarAlarmGlass ( entity ) +{ + if ( !IsValidEntity(entity) ) { return; } + + // glass is parented to a car, link the two through the trie + // find parent and save both + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", entity); + + decl String:parent[48]; + GetEntPropString(entity, Prop_Data, "m_iParent", parent, sizeof(parent)); + new parentEntity; + + // find targetname in trie + if ( GetTrieValue(g_hCarTrie, parent, parentEntity ) ) + { + // if valid entity, save the parent entity + if ( IsValidEntity(parentEntity) ) + { + SetTrieValue( g_hCarTrie, car_key, parentEntity ); + + decl String:car_key_p[10]; + FormatEx(car_key_p, sizeof(car_key_p), "%x_A", parentEntity); + new testEntity; + + if ( GetTrieValue(g_hCarTrie, car_key_p, testEntity) ) + { + // second glass + FormatEx(car_key_p, sizeof(car_key_p), "%x_B", parentEntity); + } + + SetTrieValue( g_hCarTrie, car_key_p, entity ); + } + } +} + +// entity destruction +public OnEntityDestroyed ( entity ) +{ + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", entity); + + decl rock_array[3]; + if ( GetTrieArray(g_hRockTrie, witch_key, rock_array, sizeof(rock_array)) ) + { + // tank rock + CreateTimer( ROCK_CHECK_TIME, Timer_CheckRockSkeet, entity ); + SDKUnhook(entity, SDKHook_TraceAttack, TraceAttack_Rock); + return; + } + + decl witch_array[MAXPLAYERS+DMGARRAYEXT]; + if ( GetTrieArray(g_hWitchTrie, witch_key, witch_array, sizeof(witch_array)) ) + { + // witch + // delayed deletion, to avoid potential problems with crowns not detecting + CreateTimer( WITCH_DELETE_TIME, Timer_WitchKeyDelete, entity ); + SDKUnhook(entity, SDKHook_OnTakeDamagePost, OnTakeDamagePost_Witch); + return; + } +} + +public Action: Timer_WitchKeyDelete (Handle:timer, any:witch) +{ + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", witch); + RemoveFromTrie(g_hWitchTrie, witch_key); +} + + +public Action: Timer_CheckRockSkeet (Handle:timer, any:rock) +{ + decl rock_array[3]; + decl String: rock_key[10]; + FormatEx(rock_key, sizeof(rock_key), "%x", rock); + if (!GetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array)) ) { return Plugin_Continue; } + + RemoveFromTrie(g_hRockTrie, rock_key); + + // if rock didn't hit anyone / didn't touch anything, it was shot + if ( rock_array[rckDamage] > 0 ) + { + HandleRockSkeeted( rock_array[rckSkeeter], rock_array[rckTank] ); + } + + return Plugin_Continue; +} + +// boomer got somebody +public Action: Event_PlayerBoomed (Handle:event, const String:name[], bool:dontBroadcast) +{ + new attacker = GetClientOfUserId( GetEventInt(event, "attacker") ); + new bool: byBoom = GetEventBool(event, "by_boomer"); + + if ( byBoom && IS_VALID_INFECTED(attacker) ) + { + g_bBoomerHitSomebody[attacker] = true; + + // check if it was vomit spray + new bool: byExplosion = GetEventBool(event, "exploded"); + if ( !byExplosion ) + { + // count amount of booms + if ( !g_iBoomerVomitHits[attacker] ) { + // check for boom count later + CreateTimer( VOMIT_DURATION_TIME, Timer_BoomVomitCheck, attacker, TIMER_FLAG_NO_MAPCHANGE ); + } + g_iBoomerVomitHits[attacker]++; + } + } +} +// check how many booms landed +public Action: Timer_BoomVomitCheck ( Handle:timer, any:client ) +{ + HandleVomitLanded( client, g_iBoomerVomitHits[client] ); + g_iBoomerVomitHits[client] = 0; +} + +// boomers that didn't bile anyone +public Action: Event_BoomerExploded (Handle:event, const String:name[], bool:dontBroadcast) +{ + new client = GetClientOfUserId( GetEventInt(event, "userid") ); + new bool: biled = GetEventBool(event, "splashedbile"); + if ( !biled && !g_bBoomerHitSomebody[client] ) + { + new attacker = GetClientOfUserId( GetEventInt(event, "attacker") ); + if ( IS_VALID_SURVIVOR(attacker) ) + { + HandlePop( attacker, client, g_iBoomerGotShoved[client], (GetGameTime() - g_fSpawnTime[client]) ); + } + } +} + +// crown tracking +public Action: Event_WitchSpawned ( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new witch = GetEventInt(event, "witchid"); + + SDKHook(witch, SDKHook_OnTakeDamagePost, OnTakeDamagePost_Witch); + + new witch_dmg_array[MAXPLAYERS+DMGARRAYEXT]; + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", witch); + witch_dmg_array[MAXPLAYERS+WTCH_HEALTH] = GetConVarInt(g_hCvarWitchHealth); + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false); +} + +public Action: Event_WitchKilled ( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new witch = GetEventInt(event, "witchid"); + new attacker = GetClientOfUserId( GetEventInt(event, "userid") ); + SDKUnhook(witch, SDKHook_OnTakeDamagePost, OnTakeDamagePost_Witch); + + if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; } + + new bool: bOneShot = GetEventBool(event, "oneshot"); + + // is it a crown / drawcrown? + new Handle: pack = CreateDataPack(); + WritePackCell( pack, attacker ); + WritePackCell( pack, witch ); + WritePackCell( pack, (bOneShot) ? 1 : 0 ); + CreateTimer( WITCH_CHECK_TIME, Timer_CheckWitchCrown, pack ); + + return Plugin_Continue; +} +public Action: Event_WitchHarasserSet ( Handle:event, const String:name[], bool:dontBroadcast ) +{ + new witch = GetEventInt(event, "witchid"); + + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", witch); + decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT]; + + if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) ) + { + for ( new i = 0; i <= MAXPLAYERS; i++ ) + { + witch_dmg_array[i] = 0; + } + witch_dmg_array[MAXPLAYERS+WTCH_HEALTH] = GetConVarInt(g_hCvarWitchHealth); + witch_dmg_array[MAXPLAYERS+WTCH_STARTLED] = 1; // harasser set + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false); + } + else + { + witch_dmg_array[MAXPLAYERS+WTCH_STARTLED] = 1; // harasser set + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true); + } +} + +public Action: OnTakeDamageByWitch ( victim, &attacker, &inflictor, &Float:damage, &damagetype ) +{ + // if a survivor is hit by a witch, note it in the witch damage array (maxplayers+2 = 1) + if ( IS_VALID_SURVIVOR(victim) && damage > 0.0 ) + { + + // not a crown if witch hit anyone for > 0 damage + if ( IsWitch(attacker) ) + { + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", attacker); + decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT]; + + if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) ) + { + for ( new i = 0; i <= MAXPLAYERS; i++ ) + { + witch_dmg_array[i] = 0; + } + witch_dmg_array[MAXPLAYERS+WTCH_HEALTH] = GetConVarInt(g_hCvarWitchHealth); + witch_dmg_array[MAXPLAYERS+WTCH_GOTSLASH] = 1; // failed + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false); + } + else + { + witch_dmg_array[MAXPLAYERS+WTCH_GOTSLASH] = 1; // failed + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true); + } + } + } +} + +public OnTakeDamagePost_Witch ( victim, attacker, inflictor, Float:damage, damagetype ) +{ + // only called for witches, so no check required + + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", victim); + decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT]; + + if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) ) + { + for ( new i = 0; i <= MAXPLAYERS; i++ ) + { + witch_dmg_array[i] = 0; + } + witch_dmg_array[MAXPLAYERS+WTCH_HEALTH] = GetConVarInt(g_hCvarWitchHealth); + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false); + } + + // store damage done to witch + if ( IS_VALID_SURVIVOR(attacker) ) + { + witch_dmg_array[attacker] += RoundToFloor(damage); + witch_dmg_array[MAXPLAYERS+WTCH_HEALTH] -= RoundToFloor(damage); + + // remember last shot + if ( g_fWitchShotStart[attacker] == 0.0 || (GetGameTime() - g_fWitchShotStart[attacker]) > SHOTGUN_BLAST_TIME ) + { + // reset last shot damage count and attacker + g_fWitchShotStart[attacker] = GetGameTime(); + witch_dmg_array[MAXPLAYERS+WTCH_CROWNER] = attacker; + witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] = 0; + witch_dmg_array[MAXPLAYERS+WTCH_CROWNTYPE] = ( damagetype & DMG_BUCKSHOT ) ? 1 : 0; // only allow shotguns + } + + // continued blast, add up + witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] += RoundToFloor(damage); + + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true); + } + else + { + // store all chip from other sources than survivor in [0] + witch_dmg_array[0] += RoundToFloor(damage); + //witch_dmg_array[MAXPLAYERS+1] -= RoundToFloor(damage); + SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true); + } +} + +public Action: Timer_CheckWitchCrown(Handle:timer, Handle:pack) +{ + ResetPack( pack ); + new attacker = ReadPackCell( pack ); + new witch = ReadPackCell( pack ); + new bool:bOneShot = bool:ReadPackCell( pack ); + CloseHandle( pack ); + + CheckWitchCrown( witch, attacker, bOneShot ); +} + +stock CheckWitchCrown ( witch, attacker, bool: bOneShot = false ) +{ + decl String:witch_key[10]; + FormatEx(witch_key, sizeof(witch_key), "%x", witch); + decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT]; + if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) ) { + PrintDebug(0, "Witch Crown Check: Error: Trie entry missing (entity: %i, oneshot: %i)", witch, bOneShot); + return; + } + + new chipDamage = 0; + new iWitchHealth = GetConVarInt(g_hCvarWitchHealth); + + /* + the attacker is the last one that did damage to witch + if their damage is full damage on an unharrassed witch, it's a full crown + if their damage is full or > drawcrown_threshhold, it's a drawcrown + */ + + // not a crown at all if anyone was hit, or if the killing damage wasn't a shotgun blast + + // safeguard: if it was a 'oneshot' witch kill, must've been a shotgun + // this is not enough: sometimes a shotgun crown happens that is not even reported as a oneshot... + // seems like the cause is that the witch post ontakedamage is not called in time? + if ( bOneShot ) + { + witch_dmg_array[MAXPLAYERS+WTCH_CROWNTYPE] = 1; + } + + if ( witch_dmg_array[MAXPLAYERS+WTCH_GOTSLASH] || !witch_dmg_array[MAXPLAYERS+WTCH_CROWNTYPE] ) + { + PrintDebug(0, "Witch Crown Check: Failed: bungled: %i / crowntype: %i (entity: %i)", + witch_dmg_array[MAXPLAYERS+WTCH_GOTSLASH], + witch_dmg_array[MAXPLAYERS+WTCH_CROWNTYPE], + witch + ); + PrintDebug(1, "Witch Crown Check: Further details: attacker: %N, attacker dmg: %i, teamless dmg: %i", + attacker, + witch_dmg_array[attacker], + witch_dmg_array[0] + ); + return; + } + + PrintDebug(0, "Witch Crown Check: crown shot: %i, harrassed: %i (full health: %i / drawthresh: %i / oneshot %i)", + witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT], + witch_dmg_array[MAXPLAYERS+WTCH_STARTLED], + iWitchHealth, + GetConVarInt(g_hCvarDrawCrownThresh), + bOneShot + ); + + // full crown? unharrassed + if ( !witch_dmg_array[MAXPLAYERS+WTCH_STARTLED] && ( bOneShot || witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] >= iWitchHealth ) ) + { + // make sure that we don't count any type of chip + if ( GetConVarBool(g_hCvarHideFakeDamage) ) + { + chipDamage = 0; + for ( new i = 0; i <= MAXPLAYERS; i++ ) + { + if ( i == attacker ) { continue; } + chipDamage += witch_dmg_array[i]; + } + witch_dmg_array[attacker] = iWitchHealth - chipDamage; + } + HandleCrown( attacker, witch_dmg_array[attacker] ); + } + else if ( witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] >= GetConVarInt(g_hCvarDrawCrownThresh) ) + { + // draw crown: harassed + over X damage done by one survivor -- in ONE shot + + for ( new i = 0; i <= MAXPLAYERS; i++ ) + { + if ( i == attacker ) { + // count any damage done before final shot as chip + chipDamage += witch_dmg_array[i] - witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT]; + } else { + chipDamage += witch_dmg_array[i]; + } + } + + // make sure that we don't count any type of chip + if ( GetConVarBool(g_hCvarHideFakeDamage) ) + { + // unlikely to happen, but if the chip was A LOT + if ( chipDamage >= iWitchHealth ) { + chipDamage = iWitchHealth - 1; + witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] = 1; + } + else { + witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] = iWitchHealth - chipDamage; + } + // re-check whether it qualifies as a drawcrown: + if ( witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT] < GetConVarInt(g_hCvarDrawCrownThresh) ) { return; } + } + + // plus, set final shot as 'damage', and the rest as chip + HandleDrawCrown( attacker, witch_dmg_array[MAXPLAYERS+WTCH_CROWNSHOT], chipDamage ); + } + + // remove trie + +} + +// tank rock +public Action: TraceAttack_Rock (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup) +{ + if ( IS_VALID_SURVIVOR(attacker) ) + { + /* + can't really use this for precise detection, though it does + report the last shot -- the damage report is without distance falloff + */ + decl String:rock_key[10]; + decl rock_array[3]; + FormatEx(rock_key, sizeof(rock_key), "%x", victim); + GetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array)); + rock_array[rckDamage] += RoundToFloor(damage); + rock_array[rckSkeeter] = attacker; + SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true); + } +} + +public OnTouch_Rock ( entity ) +{ + // remember that the rock wasn't shot + decl String:rock_key[10]; + FormatEx(rock_key, sizeof(rock_key), "%x", entity); + new rock_array[3]; + rock_array[rckDamage] = -1; + SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true); + + SDKUnhook(entity, SDKHook_Touch, OnTouch_Rock); +} + +// smoker tongue cutting & self clears +public Action: Event_TonguePullStopped (Handle:event, const String:name[], bool:dontBroadcast) +{ + new attacker = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + new smoker = GetClientOfUserId( GetEventInt(event, "smoker") ); + new reason = GetEventInt(event, "release_type"); + + if ( !IS_VALID_SURVIVOR(attacker) || !IS_VALID_INFECTED(smoker) ) { return Plugin_Continue; } + + // clear check - if the smoker itself was not shoved, handle the clear + HandleClear( attacker, smoker, victim, + ZC_SMOKER, + (g_fPinTime[smoker][1] > 0.0) ? ( GetGameTime() - g_fPinTime[smoker][1]) : -1.0, + ( GetGameTime() - g_fPinTime[smoker][0]), + bool:( reason != CUT_SLASH && reason != CUT_KILL ) + ); + + if ( attacker != victim ) { return Plugin_Continue; } + + if ( reason == CUT_KILL ) + { + g_bSmokerClearCheck[smoker] = true; + } + else if ( g_bSmokerShoved[smoker] ) + { + HandleSmokerSelfClear( attacker, smoker, true ); + } + else if ( reason == CUT_SLASH ) // note: can't trust this to actually BE a slash.. + { + // check weapon + decl String:weapon[32]; + GetClientWeapon( attacker, weapon, 32 ); + + // this doesn't count the chainsaw, but that's no-skill anyway + if ( StrEqual(weapon, "weapon_melee", false) ) + { + HandleTongueCut( attacker, smoker ); + } + } + + return Plugin_Continue; +} + +public Action: Event_TongueGrab (Handle:event, const String:name[], bool:dontBroadcast) +{ + new attacker = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + + if ( IS_VALID_INFECTED(attacker) && IS_VALID_SURVIVOR(victim) ) + { + // new pull, clean damage + g_bSmokerClearCheck[attacker] = false; + g_bSmokerShoved[attacker] = false; + g_iSmokerVictim[attacker] = victim; + g_iSmokerVictimDamage[attacker] = 0; + g_fPinTime[attacker][0] = GetGameTime(); + g_fPinTime[attacker][1] = 0.0; + } + + return Plugin_Continue; +} + +public Action: Event_ChokeStart (Handle:event, const String:name[], bool:dontBroadcast) +{ + new attacker = GetClientOfUserId( GetEventInt(event, "userid") ); + + if ( g_fPinTime[attacker][0] == 0.0 ) { g_fPinTime[attacker][0] = GetGameTime(); } + g_fPinTime[attacker][1] = GetGameTime(); +} + +public Action: Event_ChokeStop (Handle:event, const String:name[], bool:dontBroadcast) +{ + new attacker = GetClientOfUserId( GetEventInt(event, "userid") ); + new victim = GetClientOfUserId( GetEventInt(event, "victim") ); + new smoker = GetClientOfUserId( GetEventInt(event, "smoker") ); + new reason = GetEventInt(event, "release_type"); + + if ( !IS_VALID_SURVIVOR(attacker) || !IS_VALID_INFECTED(smoker) ) { return; } + + // if the smoker itself was not shoved, handle the clear + HandleClear( attacker, smoker, victim, + ZC_SMOKER, + (g_fPinTime[smoker][1] > 0.0) ? ( GetGameTime() - g_fPinTime[smoker][1]) : -1.0, + ( GetGameTime() - g_fPinTime[smoker][0]), + bool:( reason != CUT_SLASH && reason != CUT_KILL ) + ); +} + +// car alarm handling +public Hook_CarAlarmStart ( const String:output[], caller, activator, Float:delay ) +{ + //decl String:car_key[10]; + //FormatEx(car_key, sizeof(car_key), "%x", entity); + + PrintDebug( 0, "calarm trigger: caller %i / activator %i / delay: %.2f", caller, activator, delay ); +} +public Action: Event_CarAlarmGoesOff( Handle:event, const String:name[], bool:dontBroadcast ) +{ + g_fLastCarAlarm = GetGameTime(); +} + +public Action: OnTakeDamage_Car ( victim, &attacker, &inflictor, &Float:damage, &damagetype ) +{ + if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; } + + /* + boomer popped on alarmed car = + DMG_BLAST_SURFACE| DMG_BLAST + and inflictor is the boomer + + melee slash/club = + DMG_SLOWBURN|DMG_PREVENT_PHYSICS_FORCE + DMG_CLUB or DMG_SLASH + shove is without DMG_SLOWBURN + */ + + CreateTimer( 0.01, Timer_CheckAlarm, victim, TIMER_FLAG_NO_MAPCHANGE ); + + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", victim); + SetTrieValue(g_hCarTrie, car_key, attacker); + + if ( damagetype & DMG_BLAST ) + { + if ( IS_VALID_INFECTED(inflictor) && GetEntProp(inflictor, Prop_Send, "m_zombieClass") == ZC_BOOMER ) { + g_iLastCarAlarmReason[attacker] = CALARM_BOOMER; + g_iLastCarAlarmBoomer = inflictor; + } else { + g_iLastCarAlarmReason[attacker] = CALARM_EXPLOSION; + } + } + else if ( damage == 0.0 && ( damagetype & DMG_CLUB || damagetype & DMG_SLASH ) && !( damagetype & DMG_SLOWBURN) ) + { + g_iLastCarAlarmReason[attacker] = CALARM_TOUCHED; + } + else + { + g_iLastCarAlarmReason[attacker] = CALARM_HIT; + } + + return Plugin_Continue; +} + +public OnTouch_Car ( entity, client ) +{ + if ( !IS_VALID_SURVIVOR(client) ) { return; } + + CreateTimer( 0.01, Timer_CheckAlarm, entity, TIMER_FLAG_NO_MAPCHANGE ); + + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", entity); + SetTrieValue(g_hCarTrie, car_key, client); + + g_iLastCarAlarmReason[client] = CALARM_TOUCHED; + + return; +} + +public Action: OnTakeDamage_CarGlass ( victim, &attacker, &inflictor, &Float:damage, &damagetype ) +{ + // check for either: boomer pop or survivor + if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; } + + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", victim); + new parentEntity; + + if ( GetTrieValue(g_hCarTrie, car_key, parentEntity) ) + { + CreateTimer( 0.01, Timer_CheckAlarm, parentEntity, TIMER_FLAG_NO_MAPCHANGE ); + + FormatEx(car_key, sizeof(car_key), "%x", parentEntity); + SetTrieValue(g_hCarTrie, car_key, attacker); + + if ( damagetype & DMG_BLAST ) + { + if ( IS_VALID_INFECTED(inflictor) && GetEntProp(inflictor, Prop_Send, "m_zombieClass") == ZC_BOOMER ) { + g_iLastCarAlarmReason[attacker] = CALARM_BOOMER; + g_iLastCarAlarmBoomer = inflictor; + } else { + g_iLastCarAlarmReason[attacker] = CALARM_EXPLOSION; + } + } + else if ( damage == 0.0 && ( damagetype & DMG_CLUB || damagetype & DMG_SLASH ) && !( damagetype & DMG_SLOWBURN) ) + { + g_iLastCarAlarmReason[attacker] = CALARM_TOUCHED; + } + else + { + g_iLastCarAlarmReason[attacker] = CALARM_HIT; + } + } + + return Plugin_Continue; +} + +public OnTouch_CarGlass ( entity, client ) +{ + if ( !IS_VALID_SURVIVOR(client) ) { return; } + + decl String:car_key[10]; + FormatEx(car_key, sizeof(car_key), "%x", entity); + new parentEntity; + + if ( GetTrieValue(g_hCarTrie, car_key, parentEntity) ) + { + CreateTimer( 0.01, Timer_CheckAlarm, parentEntity, TIMER_FLAG_NO_MAPCHANGE ); + + FormatEx(car_key, sizeof(car_key), "%x", parentEntity); + SetTrieValue(g_hCarTrie, car_key, client); + + g_iLastCarAlarmReason[client] = CALARM_TOUCHED; + } + + return; +} + +public Action: Timer_CheckAlarm (Handle:timer, any:entity) +{ + //PrintToChatAll( "checking alarm: time: %.3f", GetGameTime() - g_fLastCarAlarm ); + + if ( (GetGameTime() - g_fLastCarAlarm) < CARALARM_MIN_TIME ) + { + // got a match, drop stuff from trie and handle triggering + decl String:car_key[10]; + new testEntity; + new survivor = -1; + + // remove car glass + FormatEx(car_key, sizeof(car_key), "%x_A", entity); + if ( GetTrieValue(g_hCarTrie, car_key, testEntity) ) + { + RemoveFromTrie(g_hCarTrie, car_key); + SDKUnhook(testEntity, SDKHook_OnTakeDamage, OnTakeDamage_CarGlass); + SDKUnhook(testEntity, SDKHook_Touch, OnTouch_CarGlass); + } + FormatEx(car_key, sizeof(car_key), "%x_B", entity); + if ( GetTrieValue(g_hCarTrie, car_key, testEntity) ) + { + RemoveFromTrie(g_hCarTrie, car_key); + SDKUnhook(testEntity, SDKHook_OnTakeDamage, OnTakeDamage_CarGlass); + SDKUnhook(testEntity, SDKHook_Touch, OnTouch_CarGlass); + } + + // remove car + FormatEx(car_key, sizeof(car_key), "%x", entity); + if ( GetTrieValue(g_hCarTrie, car_key, survivor) ) + { + RemoveFromTrie(g_hCarTrie, car_key); + SDKUnhook(entity, SDKHook_OnTakeDamage, OnTakeDamage_Car); + SDKUnhook(entity, SDKHook_Touch, OnTouch_Car); + } + + // check for infected assistance + new infected = 0; + if ( IS_VALID_SURVIVOR(survivor) ) + { + if ( g_iLastCarAlarmReason[survivor] == CALARM_BOOMER ) + { + infected = g_iLastCarAlarmBoomer; + } + else if ( IS_VALID_INFECTED(GetEntPropEnt(survivor, Prop_Send, "m_carryAttacker")) ) + { + infected = GetEntPropEnt(survivor, Prop_Send, "m_carryAttacker"); + } + else if ( IS_VALID_INFECTED(GetEntPropEnt(survivor, Prop_Send, "m_jockeyAttacker")) ) + { + infected = GetEntPropEnt(survivor, Prop_Send, "m_jockeyAttacker"); + } + else if ( IS_VALID_INFECTED(GetEntPropEnt(survivor, Prop_Send, "m_tongueOwner")) ) + { + infected = GetEntPropEnt(survivor, Prop_Send, "m_tongueOwner"); + } + } + + HandleCarAlarmTriggered( + survivor, + infected, + (IS_VALID_INGAME(survivor)) ? g_iLastCarAlarmReason[survivor] : CALARM_UNKNOWN + ); + } +} + + +/* throwactivate .. for more reliable rock-tracking? +public Action: L4D_OnCThrowActivate ( ability ) +{ + // tank throws rock + if ( !IsValidEntity(ability) ) { return Plugin_Continue; } + + // find tank player + new tank = GetEntPropEnt(ability, Prop_Send, "m_owner"); + if ( !IS_VALID_INGAME(tank) ) { return Plugin_Continue; } + + ... +} +*/ + +/* + Reporting and forwards + ---------------------- +*/ +// boomer pop +stock HandlePop( attacker, victim, shoveCount, Float:timeAlive ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_POP ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 popped \x05%N\x01.", attacker, victim ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 popped a boomer.", attacker ); + } + } + + Call_StartForward(g_hForwardBoomerPop); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(shoveCount); + Call_PushFloat(timeAlive); + Call_Finish(); +} + +// charger level +stock HandleLevel( attacker, victim ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_LEVEL ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 leveled \x05%N\x01.", attacker, victim ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 leveled a charger.", attacker ); + } + else { + PrintToChatAll( "A charger was leveled." ); + } + } + + // call forward + Call_StartForward(g_hForwardLevel); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); +} +// charger level hurt +stock HandleLevelHurt( attacker, victim, damage ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_HURTLEVEL ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 chip-leveled \x05%N\x01 (\x03%i\x01 damage).", attacker, victim, damage ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 chip-leveled a charger. (\x03%i\x01 damage)", attacker, damage ); + } + else { + PrintToChatAll( "A charger was chip-leveled (\x03%i\x01 damage).", damage ); + } + } + + // call forward + Call_StartForward(g_hForwardLevelHurt); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(damage); + Call_Finish(); +} + +// deadstops +stock HandleDeadstop( attacker, victim ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_DEADSTOP ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 deadstopped \x05%N\x01.", attacker, victim ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 deadstopped a hunter.", attacker ); + } + } + + Call_StartForward(g_hForwardHunterDeadstop); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); +} + +stock HandleShove( attacker, victim, zombieClass ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_SHOVE ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 shoved \x05%N\x01.", attacker, victim ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 shoved an SI.", attacker ); + } + } + + Call_StartForward(g_hForwardSIShove); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(zombieClass); + Call_Finish(); +} + +// real skeet +stock HandleSkeet( attacker, victim, bool:bMelee = false, bool:bSniper = false, bool:bGL = false ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_SKEET ) + { + if ( attacker == -2 ) + { + // team skeet sets to -2 + if ( IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) { + PrintToChatAll( "\x05%N\x01 was team-skeeted.", victim ); + } else { + PrintToChatAll( "\x01A hunter was team-skeeted." ); + } + } + else if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 %sskeeted \x05%N\x01.", + attacker, + (bMelee) ? "melee-": ((bSniper) ? "headshot-" : ((bGL) ? "grenade-" : "") ), + victim + ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 %sskeeted a hunter.", + attacker, + (bMelee) ? "melee-": ((bSniper) ? "headshot-" : ((bGL) ? "grenade-" : "") ) + ); + } + } + + // call forward + if ( bSniper ) + { + Call_StartForward(g_hForwardSkeetSniper); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); + } + else if ( bGL ) + { + Call_StartForward(g_hForwardSkeetGL); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); + } + else if ( bMelee ) + { + Call_StartForward(g_hForwardSkeetMelee); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); + } + else + { + Call_StartForward(g_hForwardSkeet); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); + } +} + +// hurt skeet / non-skeet +// NOTE: bSniper not set yet, do this +stock HandleNonSkeet( attacker, victim, damage, bool:bOverKill = false, bool:bMelee = false, bool:bSniper = false ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_HURTSKEET ) + { + if ( IS_VALID_INGAME(victim) ) + { + PrintToChatAll( "\x05%N\x01 was \x04not\x01 skeeted (\x03%i\x01 damage).%s", victim, damage, (bOverKill) ? "(Would've skeeted if hunter were unchipped!)" : "" ); + } + else + { + PrintToChatAll( "\x01Hunter was \x04not\x01 skeeted (\x03%i\x01 damage).%s", damage, (bOverKill) ? "(Would've skeeted if hunter were unchipped!)" : "" ); + } + } + + // call forward + if ( bSniper ) + { + Call_StartForward(g_hForwardSkeetSniperHurt); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(damage); + Call_PushCell(bOverKill); + Call_Finish(); + } + else if ( bMelee ) + { + Call_StartForward(g_hForwardSkeetMeleeHurt); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(damage); + Call_PushCell(bOverKill); + Call_Finish(); + } + else + { + Call_StartForward(g_hForwardSkeetHurt); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(damage); + Call_PushCell(bOverKill); + Call_Finish(); + } +} + + +// crown +HandleCrown( attacker, damage ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_CROWN ) + { + if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 crowned a witch (\x03%i\x01 damage).", attacker, damage ); + } + else { + PrintToChatAll( "A witch was crowned." ); + } + } + + // call forward + Call_StartForward(g_hForwardCrown); + Call_PushCell(attacker); + Call_PushCell(damage); + Call_Finish(); +} +// drawcrown +HandleDrawCrown( attacker, damage, chipdamage ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_DRAWCROWN ) + { + if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 draw-crowned a witch (\x03%i\x01 damage, \x05%i\x01 chip).", attacker, damage, chipdamage ); + } + else { + PrintToChatAll( "A witch was draw-crowned (\x03%i\x01 damage, \x05%i\x01 chip).", damage, chipdamage ); + } + } + + // call forward + Call_StartForward(g_hForwardDrawCrown); + Call_PushCell(attacker); + Call_PushCell(damage); + Call_PushCell(chipdamage); + Call_Finish(); +} + +// smoker clears +HandleTongueCut( attacker, victim ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_TONGUECUT ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 cut \x05%N\x01's tongue.", attacker, victim ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 cut smoker tongue.", attacker ); + } + } + + // call forward + Call_StartForward(g_hForwardTongueCut); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); +} + +HandleSmokerSelfClear( attacker, victim, bool:withShove = false ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_SELFCLEAR && + (!withShove || GetConVarInt(g_hCvarReport) & REP_SELFCLEARSHOVE ) + ) { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 self-cleared from \x05%N\x01's tongue%s.", attacker, victim, (withShove) ? " by shoving" : "" ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + PrintToChatAll( "\x04%N\x01 self-cleared from a smoker tongue%s.", attacker, (withShove) ? " by shoving" : "" ); + } + } + + // call forward + Call_StartForward(g_hForwardSmokerSelfClear); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(withShove); + Call_Finish(); +} + +// rocks +HandleRockEaten( attacker, victim ) +{ + Call_StartForward(g_hForwardRockEaten); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); +} +HandleRockSkeeted( attacker, victim ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_ROCKSKEET ) + { + /* + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + PrintToChatAll( "\x04%N\x01 skeeted \x05%N\x01's rock.", attacker, victim ); + } + else if ( IS_VALID_INGAME(attacker) ) + { + } + */ + PrintToChatAll( "\x04%N\x01 skeeted a tank rock.", attacker ); + } + + Call_StartForward(g_hForwardRockSkeeted); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_Finish(); +} + +// highpounces +stock HandleHunterDP( attacker, victim, actualDamage, Float:calculatedDamage, Float:height, bool:playerIncapped = false ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) + && GetConVarInt(g_hCvarReportFlags) & REP_HUNTERDP + && height >= GetConVarFloat(g_hCvarHunterDPThresh) + && !playerIncapped + ) { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(attacker) ) + { + PrintToChatAll( "\x04%N\x01 high-pounced \x05%N\x01 (\x03%i\x01 damage, height: \x05%i\x01).", attacker, victim, RoundFloat(calculatedDamage), RoundFloat(height) ); + } + else if ( IS_VALID_INGAME(victim) ) + { + PrintToChatAll( "A hunter high-pounced \x05%N\x01 (\x03%i\x01 damage, height: \x05%i\x01).", victim, RoundFloat(calculatedDamage), RoundFloat(height) ); + } + } + + Call_StartForward(g_hForwardHunterDP); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(actualDamage); + Call_PushFloat(calculatedDamage); + Call_PushFloat(height); + Call_PushCell( (height >= GetConVarFloat(g_hCvarHunterDPThresh)) ? 1 : 0 ); + Call_PushCell( (playerIncapped) ? 1 : 0 ); + Call_Finish(); +} +stock HandleJockeyDP( attacker, victim, Float:height ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) + && GetConVarInt(g_hCvarReportFlags) & REP_JOCKEYDP + && height >= GetConVarFloat(g_hCvarJockeyDPThresh) + ) { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(attacker) ) + { + PrintToChatAll( "\x04%N\x01 jockey high-pounced \x05%N\x01 (height: \x05%i\x01).", attacker, victim, RoundFloat(height) ); + } + else if ( IS_VALID_INGAME(victim) ) + { + PrintToChatAll( "A jockey high-pounced \x05%N\x01 (height: \x05%i\x01).", victim, RoundFloat(height) ); + } + } + + Call_StartForward(g_hForwardJockeyDP); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushFloat(height); + Call_PushCell( (height >= GetConVarFloat(g_hCvarJockeyDPThresh)) ? 1 : 0 ); + Call_Finish(); +} + +// deathcharges +stock HandleDeathCharge( attacker, victim, Float:height, Float:distance, bool:bCarried = true ) +{ + // report? + if ( GetConVarBool(g_hCvarReport) && + GetConVarInt(g_hCvarReportFlags) & REP_DEATHCHARGE && + height >= GetConVarFloat(g_hCvarDeathChargeHeight) + ) { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(attacker) ) + { + PrintToChatAll( "\x04%N\x01 death-charged \x05%N\x01 %s(height: \x05%i\x01).", + attacker, + victim, + (bCarried) ? "" : "by bowling ", + RoundFloat(height) + ); + } + else if ( IS_VALID_INGAME(victim) ) + { + PrintToChatAll( "A charger death-charged \x05%N\x01 %s(height: \x05%i\x01).", + victim, + (bCarried) ? "" : "by bowling ", + RoundFloat(height) + ); + } + } + + Call_StartForward(g_hForwardDeathCharge); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushFloat(height); + Call_PushFloat(distance); + Call_PushCell( (bCarried) ? 1 : 0 ); + Call_Finish(); +} + +// SI clears (cleartimeA = pummel/pounce/ride/choke, cleartimeB = tongue drag, charger carry) +stock HandleClear( attacker, victim, pinVictim, zombieClass, Float:clearTimeA, Float:clearTimeB, bool:bWithShove = false ) +{ + // sanity check: + if ( clearTimeA < 0 && clearTimeA != -1.0 ) { clearTimeA = 0.0; } + if ( clearTimeB < 0 && clearTimeB != -1.0 ) { clearTimeB = 0.0; } + + PrintDebug(0, "Clear: %i freed %i from %i: time: %.2f / %.2f -- class: %s (with shove? %i)", attacker, pinVictim, victim, clearTimeA, clearTimeB, g_csSIClassName[zombieClass], bWithShove ); + + if ( attacker != pinVictim && GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_INSTACLEAR ) + { + new Float: fMinTime = GetConVarFloat(g_hCvarInstaTime); + new Float: fClearTime = clearTimeA; + if ( zombieClass == ZC_CHARGER || zombieClass == ZC_SMOKER ) { fClearTime = clearTimeB; } + + + if ( fClearTime != -1.0 && fClearTime <= fMinTime ) + { + if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) + { + if ( IS_VALID_INGAME(pinVictim) ) + { + PrintToChatAll( "\x04%N\x01 insta-cleared \x05%N\x01 from \x04%N\x01 (%s) (%.2f seconds).", + attacker, pinVictim, victim, + g_csSIClassName[zombieClass], + fClearTime + ); + } else { + PrintToChatAll( "\x04%N\x01 insta-cleared a teammate from \x04%N\x01 (%s) (%.2f seconds).", + attacker, victim, + g_csSIClassName[zombieClass], + fClearTime + ); + } + } + else if ( IS_VALID_INGAME(attacker) ) + { + if ( IS_VALID_INGAME(pinVictim) ) + { + PrintToChatAll( "\x04%N\x01 insta-cleared \x05%N\x01 from a %s (%.2f seconds).", + attacker, pinVictim, + g_csSIClassName[zombieClass], + fClearTime + ); + } else { + PrintToChatAll( "\x04%N\x01 insta-cleared a teammate from a %s (%.2f seconds).", + attacker, + g_csSIClassName[zombieClass], + fClearTime + ); + } + } + } + } + + Call_StartForward(g_hForwardClear); + Call_PushCell(attacker); + Call_PushCell(victim); + Call_PushCell(pinVictim); + Call_PushCell(zombieClass); + Call_PushFloat(clearTimeA); + Call_PushFloat(clearTimeB); + Call_PushCell( (bWithShove) ? 1 : 0 ); + Call_Finish(); +} + +// booms +stock HandleVomitLanded( attacker, boomCount ) +{ + Call_StartForward(g_hForwardVomitLanded); + Call_PushCell(attacker); + Call_PushCell(boomCount); + Call_Finish(); +} + +// bhaps +stock HandleBHopStreak( survivor, streak, Float: maxVelocity ) +{ + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_BHOPSTREAK && + IS_VALID_INGAME(survivor) && !IsFakeClient(survivor) && + streak >= GetConVarInt(g_hCvarBHopMinStreak) + ) { + PrintToChatAll( "\x04%N\x01 got \x05%i\x01 bunnyhop%s in a row (top speed: \x05%.1f\x01).", + survivor, + streak, + ( streak > 1 ) ? "s" : "", + maxVelocity + ); + } + + Call_StartForward(g_hForwardBHopStreak); + Call_PushCell(survivor); + Call_PushCell(streak); + Call_PushFloat(maxVelocity); + Call_Finish(); +} +// car alarms +stock HandleCarAlarmTriggered( survivor, infected, reason ) +{ + if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_CARALARM && + IS_VALID_INGAME(survivor) && !IsFakeClient(survivor) + ) { + if ( reason == CALARM_HIT ) { + PrintToChatAll( "\x05%N\x01 triggered an alarm with a hit.", survivor ); + } + else if ( reason == CALARM_TOUCHED ) + { + // if a survivor touches an alarmed car, it might be due to a special infected... + if ( IS_VALID_INFECTED(infected) ) + { + if ( !IsFakeClient(infected) ) + { + PrintToChatAll( "\x04%N\x01 made \x05%N\x01 trigger an alarm.", infected, survivor ); + } + else { + switch ( GetEntProp(infected, Prop_Send, "m_zombieClass") ) + { + case ZC_SMOKER: { PrintToChatAll( "\x01A hunter made \x05%N\x01 trigger an alarm.", survivor ); } + case ZC_JOCKEY: { PrintToChatAll( "\x01A jockey made \x05%N\x01 trigger an alarm.", survivor ); } + case ZC_CHARGER: { PrintToChatAll( "\x01A charger made \x05%N\x01 trigger an alarm.", survivor ); } + default: { PrintToChatAll( "\x01A bot infected made \x05%N\x01 trigger an alarm.", survivor ); } + } + } + } + else + { + PrintToChatAll( "\x05%N\x01 touched an alarmed car.", survivor ); + } + } + else if ( reason == CALARM_EXPLOSION ) { + PrintToChatAll( "\x05%N\x01 triggered an alarm with an explosion.", survivor ); + } + else if ( reason == CALARM_BOOMER ) + { + if ( IS_VALID_INFECTED(infected) && !IsFakeClient(infected) ) + { + PrintToChatAll( "\x05%N\x01 triggered an alarm by killing a boomer \x04%N\x01.", survivor, infected ); + } + else + { + PrintToChatAll( "\x05%N\x01 triggered an alarm by shooting a boomer.", survivor ); + } + } + else { + PrintToChatAll( "\x05%N\x01 triggered an alarm.", survivor ); + } + } + + Call_StartForward(g_hForwardAlarmTriggered); + Call_PushCell(survivor); + Call_PushCell(infected); + Call_PushCell(reason); + Call_Finish(); +} + + +// support +// ------- + +stock GetSurvivorPermanentHealth(client) +{ + return GetEntProp(client, Prop_Send, "m_iHealth"); +} + +stock GetSurvivorTempHealth(client) +{ + new temphp = RoundToCeil( + GetEntPropFloat(client, Prop_Send, "m_healthBuffer") + - ( (GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime") ) + * GetConVarFloat( FindConVar("pain_pills_decay_rate"))) + ) - 1; + return (temphp > 0 ? temphp : 0); +} + +stock Float: GetSurvivorDistance(client) +{ + return L4D2Direct_GetFlowDistance(client); +} +stock ShiftTankThrower() +{ + new tank = -1; + + if ( !g_iRocksBeingThrownCount ) { return -1; } + + tank = g_iRocksBeingThrown[0]; + + // shift the tank array downwards, if there are more than 1 throwers + if ( g_iRocksBeingThrownCount > 1 ) + { + for ( new x = 1; x <= g_iRocksBeingThrownCount; x++ ) + { + g_iRocksBeingThrown[x-1] = g_iRocksBeingThrown[x]; + } + } + + g_iRocksBeingThrownCount--; + + return tank; +} +/* Height check.. + not required now + maybe for some other 'skill'? +static Float: GetHeightAboveGround( Float:pos[3] ) +{ + // execute Trace straight down + new Handle:trace = TR_TraceRayFilterEx( pos, ANGLE_STRAIGHT_DOWN, MASK_SHOT, RayType_Infinite, ChargeTraceFilter ); + + if (!TR_DidHit(trace)) + { + LogError("Tracer Bug: Trace did not hit anything..."); + } + + decl Float:vEnd[3]; + TR_GetEndPosition(vEnd, trace); // retrieve our trace endpoint + CloseHandle(trace); + + return GetVectorDistance(pos, vEnd, false); +} + +public bool: ChargeTraceFilter (entity, contentsMask) +{ + if ( !entity || !IsValidEntity(entity) ) // dont let WORLD, or invalid entities be hit + { + return false; + } + return true; +} +*/ + +stock PrintDebug(debuglevel, const String:Message[], any:... ) +{ + decl String:DebugBuff[256]; + VFormat(DebugBuff, sizeof(DebugBuff), Message, 3); + LogMessage(DebugBuff); +} +stock bool: IsWitch(entity) +{ + if ( !IsValidEntity(entity) ) { return false; } + + decl String: classname[24]; + new strOEC: classnameOEC; + GetEdictClassname(entity, classname, sizeof(classname)); + if ( !GetTrieValue(g_hTrieEntityCreated, classname, classnameOEC) || classnameOEC != OEC_WITCH ) { return false; } + + return true; +} \ No newline at end of file diff --git a/scripting/l4d2_target_test.sp b/scripting/l4d2_target_test.sp new file mode 100644 index 0000000..f76755f --- /dev/null +++ b/scripting/l4d2_target_test.sp @@ -0,0 +1,136 @@ +#pragma semicolon 1 +#pragma newdecls required + +//#define DEBUG + +#define PLUGIN_VERSION "1.0" +#define GAMEDATA "l4d_target_override" + +#include +#include +#include + +enum L4D2Infected +{ + L4D2Infected_None = 0, + L4D2Infected_Smoker = 1, + L4D2Infected_Boomer = 2, + L4D2Infected_Hunter = 3, + L4D2Infected_Spitter = 4, + L4D2Infected_Jockey = 5, + L4D2Infected_Charger = 6, + L4D2Infected_Witch = 7, + L4D2Infected_Tank = 8 +} + +public Plugin myinfo = +{ + name = "L4D2 target poo", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "" +}; + +bool g_bIsVictim[MAXPLAYERS+1]; +Handle g_hDetour; + +public void OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead2) + { + SetFailState("This plugin is for L4D2 only."); + } + + + RegAdminCmd("sm_set_victim", Cmd_SetVictim, ADMFLAG_CHEATS); + + HookEvent("player_death", Event_PlayerDeath); +} + +public void OnPluginEnd() +{ +} + +public Action Cmd_SetVictim(int client, int args) { + if(args == 0) { + ReplyToCommand(client, "Please enter a player to target"); + }else{ + 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, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, /* Only allow alive players */ + 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; + } + for(int i = 0; i < target_count; i++) { + int victim = target_list[i]; + //g_iSITargets + g_bIsVictim[victim] = !g_bIsVictim[victim]; + ReplyToCommand(client, "Successfully toggled %N victim status to: %b", victim, g_bIsVictim[victim]); + ShowActivity(client, "toggled special infected victim status for %N to %b", victim, g_bIsVictim[victim]); + } + } + return Plugin_Handled; +} + +static int b_attackerTarget[MAXPLAYERS+1]; +public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { + // ========================= + // OVERRIDE VICTIM + // ========================= + L4D2Infected class = view_as(GetEntProp(attacker, Prop_Send, "m_zombieClass")); + if(class != L4D2Infected_Tank) { + int existingTarget = GetClientOfUserId(b_attackerTarget[attacker]); + if(existingTarget > 0) { + curTarget = existingTarget; + return Plugin_Changed; + } + + float closestDistance, survPos[3], spPos[3]; + GetClientAbsOrigin(attacker, spPos); + int closestClient = -1; + for(int i = 1; i <= MaxClients; i++) { + if(g_bIsVictim[i] && IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { + GetClientAbsOrigin(i, survPos); + float dist = GetVectorDistance(survPos, spPos, true); + if(closestClient == -1 || dist < closestDistance) { + closestDistance = dist; + closestClient = i; + } + } + } + + if(closestClient > 0) { + PrintToConsoleAll("Attacker %N new target: %N", attacker, closestClient); + b_attackerTarget[attacker] = GetClientUserId(closestClient); + curTarget = closestClient; + return Plugin_Changed; + } + } + return Plugin_Continue; +} + +public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + b_attackerTarget[client] = 0; +} + +public void OnClientDisconnect(int client) { + b_attackerTarget[client] = 0; +} \ No newline at end of file diff --git a/scripting/l4d_tank_hp_sprite.sp b/scripting/l4d_tank_hp_sprite.sp index 7e5fa09..c3035cf 100644 --- a/scripting/l4d_tank_hp_sprite.sp +++ b/scripting/l4d_tank_hp_sprite.sp @@ -2,11 +2,24 @@ // ==================================================================================================== Change Log: +1.0.6 (12-February-2021) + - Fixed custom model cvar typo. (thanks "weffer" for reporting) + +1.0.5 (11-February-2021) + - Fixed a bug not rendering custom sprites right after turning it on. + - Added one more custom sprite option with an alpha background filling the bar. + +1.0.4 (11-February-2021) + - Added custom sprite option. + +1.0.3 (08-February-2021) + - Fixed missing client in-game in visibility check. (thanks to "Krufftys Killers" and "Striker black") + 1.0.2 (08-February-2021) - Fixed wrong value on max health calculation. - Fixed sprite hiding behind tank rocks. - Fixed sprite hiding while tank throws rocks (ability use). - - Moved visibility logic to timer. + - Moved visibility logic to timer handle. 1.0.1 (30-January-2021) - Public release. @@ -23,7 +36,7 @@ Change Log: #define PLUGIN_NAME "[L4D1 & L4D2] Tank HP Sprite" #define PLUGIN_AUTHOR "Mart" #define PLUGIN_DESCRIPTION "Shows a sprite at the tank head that goes from green to red based on its HP" -#define PLUGIN_VERSION "1.0.2" +#define PLUGIN_VERSION "1.0.5" #define PLUGIN_URL "https://forums.alliedmods.net/showthread.php?t=330370" // ==================================================================================================== @@ -66,6 +79,7 @@ public Plugin myinfo = // Defines // ==================================================================================================== #define CLASSNAME_ENV_SPRITE "env_sprite" +#define CLASSNAME_ENV_TEXTURETOGGLE "env_texturetoggle" #define CLASSNAME_TANK_ROCK "tank_rock" #define TEAM_SPECTATOR 1 @@ -102,6 +116,9 @@ static ConVar g_hCvar_DeadAlpha; static ConVar g_hCvar_DeadScale; static ConVar g_hCvar_DeadColor; static ConVar g_hCvar_Team; +static ConVar g_hCvar_CustomModel; +static ConVar g_hCvar_CustomModelVMT; +static ConVar g_hCvar_CustomModelVTF; static ConVar g_hCvar_AllSpecials; // ==================================================================================================== @@ -115,6 +132,7 @@ static bool g_bCvar_Sight; static bool g_bCvar_AttackDelay; static bool g_bCvar_AliveShow; static bool g_bCvar_DeadShow; +static bool g_bCvar_CustomModel; // ==================================================================================================== // int - Plugin Variables @@ -147,11 +165,14 @@ static char g_sCvar_DeadAlpha[4]; static char g_sCvar_DeadScale[5]; static char g_sCvar_DeadColor[12]; static char g_sCvar_FadeDistance[5]; +static char g_sCvar_CustomModelVMT[100]; +static char g_sCvar_CustomModelVTF[100]; // ==================================================================================================== // client - Plugin Variables // ==================================================================================================== static int gc_iTankSpriteRef[MAXPLAYERS+1] = { INVALID_ENT_REFERENCE, ... }; +static int gc_iTankSpriteFrameRef[MAXPLAYERS+1] = { INVALID_ENT_REFERENCE, ... }; static bool gc_bVisible[MAXPLAYERS+1][MAXPLAYERS+1]; static float gc_fLastAttack[MAXPLAYERS+1][MAXPLAYERS+1]; @@ -185,21 +206,24 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max public void OnPluginStart() { CreateConVar("l4d_tank_hp_sprite_version", PLUGIN_VERSION, PLUGIN_DESCRIPTION, CVAR_FLAGS_PLUGIN_VERSION); - g_hCvar_Enabled = CreateConVar("l4d_tank_hp_sprite_enable", "1", "Enable/Disable the plugin.\n0 = Disable, 1 = Enable", CVAR_FLAGS, true, 0.0, true, 1.0); - g_hCvar_ZAxis = CreateConVar("l4d_tank_hp_sprite_z_axis", "92", "Additional Z distance based on the tank position.", CVAR_FLAGS, true, 0.0); - g_hCvar_FadeDistance = CreateConVar("l4d_tank_hp_sprite_fade_distance", "-1", "Minimum distance that a client must be from the tank to see the sprite (both alive and dead sprites).\n-1 = Always visible.", CVAR_FLAGS, true, -1.0, true, 9999.0); - g_hCvar_Sight = CreateConVar("l4d_tank_hp_sprite_sight", "1", "Show the sprite to the survivor only if the Tank is on sight.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); - g_hCvar_AttackDelay = CreateConVar("l4d_tank_hp_sprite_attack_delay", "0.0", "Show the sprite to the survivor attacker, by this amount of time in seconds, after hitting the Tank.\n0 = OFF.", CVAR_FLAGS, true, 0.0); - g_hCvar_AliveShow = CreateConVar("l4d_tank_hp_sprite_alive_show", "1", "Show the alive sprite while tank is alive.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); - g_hCvar_AliveModel = CreateConVar("l4d_tank_hp_sprite_alive_model", "materials/vgui/healthbar_white.vmt", "Model of alive tank sprite."); - g_hCvar_AliveAlpha = CreateConVar("l4d_tank_hp_sprite_alive_alpha", "200", "Alpha of alive tank sprite.\n0 = Invisible, 255 = Fully Visible", CVAR_FLAGS, true, 0.0, true, 255.0); - g_hCvar_AliveScale = CreateConVar("l4d_tank_hp_sprite_alive_scale", "0.25", "Scale of alive tank sprite (increases both height and width).\nNote: Some range values maintain the same size. (e.g. from 0.0 to 0.38 the size doesn't change).", CVAR_FLAGS, true, 0.0); - g_hCvar_DeadShow = CreateConVar("l4d_tank_hp_sprite_dead_show", "1", "Show the dead sprite when a tank dies.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); - g_hCvar_DeadModel = CreateConVar("l4d_tank_hp_sprite_dead_model", "materials/sprites/death_icon.vmt", "Model of dead tank sprite."); - g_hCvar_DeadAlpha = CreateConVar("l4d_tank_hp_sprite_dead_alpha", "200", "Alpha of dead tank sprite.\n0 = Invisible, 255 = Fully Visible", CVAR_FLAGS, true, 0.0, true, 255.0); - g_hCvar_DeadScale = CreateConVar("l4d_tank_hp_sprite_dead_scale", "0.25", "Scale of dead tank sprite (increases both height and width).\nSome range values maintain the size the same.", CVAR_FLAGS, true, 0.0); - g_hCvar_DeadColor = CreateConVar("l4d_tank_hp_sprite_dead_color", "225 0 0", "Color of dead tank sprite.\nUse three values between 0-255 separated by spaces (\"<0-255> <0-255> <0-255>\").", CVAR_FLAGS); - g_hCvar_Team = CreateConVar("l4d_tank_hp_sprite_team", "3", "Which teams should the sprite be visible.\n0 = NONE, 1 = SURVIVOR, 2 = INFECTED, 4 = SPECTATOR, 8 = HOLDOUT.\nAdd numbers greater than 0 for multiple options.\nExample: \"3\", enables for SURVIVOR and INFECTED.", CVAR_FLAGS, true, 0.0, true, 15.0); + g_hCvar_Enabled = CreateConVar("l4d_tank_hp_sprite_enable", "1", "Enable/Disable the plugin.\n0 = Disable, 1 = Enable.", CVAR_FLAGS, true, 0.0, true, 1.0); + g_hCvar_ZAxis = CreateConVar("l4d_tank_hp_sprite_z_axis", "92", "Additional Z distance based on the tank position.", CVAR_FLAGS, true, 0.0); + g_hCvar_FadeDistance = CreateConVar("l4d_tank_hp_sprite_fade_distance", "-1", "Minimum distance that a client must be from the tank to see the sprite (both alive and dead sprites).\n-1 = Always visible.", CVAR_FLAGS, true, -1.0, true, 9999.0); + g_hCvar_Sight = CreateConVar("l4d_tank_hp_sprite_sight", "1", "Show the sprite to the survivor only if the Tank is on sight.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); + g_hCvar_AttackDelay = CreateConVar("l4d_tank_hp_sprite_attack_delay", "0.0", "Show the sprite to the survivor attacker, by this amount of time in seconds, after hitting the Tank.\n0 = OFF.", CVAR_FLAGS, true, 0.0); + g_hCvar_AliveShow = CreateConVar("l4d_tank_hp_sprite_alive_show", "1", "Show the alive sprite while tank is alive.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); + g_hCvar_AliveModel = CreateConVar("l4d_tank_hp_sprite_alive_model", "materials/vgui/healthbar_white.vmt", "Model of alive tank sprite."); + g_hCvar_AliveAlpha = CreateConVar("l4d_tank_hp_sprite_alive_alpha", "200", "Alpha of alive tank sprite.\n0 = Invisible, 255 = Fully Visible", CVAR_FLAGS, true, 0.0, true, 255.0); + g_hCvar_AliveScale = CreateConVar("l4d_tank_hp_sprite_alive_scale", "0.25", "Scale of alive tank sprite (increases both height and width).\nNote: Some range values maintain the same size. (e.g. from 0.0 to 0.38 the size doesn't change).", CVAR_FLAGS, true, 0.0); + g_hCvar_DeadShow = CreateConVar("l4d_tank_hp_sprite_dead_show", "1", "Show the dead sprite when a tank dies.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); + g_hCvar_DeadModel = CreateConVar("l4d_tank_hp_sprite_dead_model", "materials/sprites/death_icon.vmt", "Model of dead tank sprite."); + g_hCvar_DeadAlpha = CreateConVar("l4d_tank_hp_sprite_dead_alpha", "200", "Alpha of dead tank sprite.\n0 = Invisible, 255 = Fully Visible", CVAR_FLAGS, true, 0.0, true, 255.0); + g_hCvar_DeadScale = CreateConVar("l4d_tank_hp_sprite_dead_scale", "0.25", "Scale of dead tank sprite (increases both height and width).\nSome range values maintain the size the same.", CVAR_FLAGS, true, 0.0); + g_hCvar_DeadColor = CreateConVar("l4d_tank_hp_sprite_dead_color", "225 0 0", "Color of dead tank sprite.\nUse three values between 0-255 separated by spaces (\"<0-255> <0-255> <0-255>\").", CVAR_FLAGS); + g_hCvar_Team = CreateConVar("l4d_tank_hp_sprite_team", "3", "Which teams should the sprite be visible.\n0 = NONE, 1 = SURVIVOR, 2 = INFECTED, 4 = SPECTATOR, 8 = HOLDOUT.\nAdd numbers greater than 0 for multiple options.\nExample: \"3\", enables for SURVIVOR and INFECTED.", CVAR_FLAGS, true, 0.0, true, 15.0); + g_hCvar_CustomModel = CreateConVar("l4d_tank_hp_sprite_custom_model", "0", "Use a custom sprite for the alive model\nNote: This requires the client downloading the custom model (.vmt and .vtf) to work.\nSearch for FastDL for more info.\n0 = OFF, 1 = ON.", CVAR_FLAGS, true, 0.0, true, 1.0); + g_hCvar_CustomModelVMT = CreateConVar("l4d_tank_hp_sprite_custom_model_vmt", "materials/mart/mart_custombar.vmt", "Custom sprite VMT path."); + g_hCvar_CustomModelVTF = CreateConVar("l4d_tank_hp_sprite_custom_model_vtf", "materials/mart/mart_custombar.vtf", "Custom sprite VTF path."); g_hCvar_AllSpecials = CreateConVar("l4d_tank_hp_sprite_all_specials", "1", "Should all specials have healthbar or only tanks\n0 = Tanks Only, 1 = All Specials", CVAR_FLAGS, true, 0.0, true, 1.0); g_hCvar_Enabled.AddChangeHook(Event_ConVarChanged); @@ -217,6 +241,9 @@ public void OnPluginStart() g_hCvar_DeadScale.AddChangeHook(Event_ConVarChanged); g_hCvar_DeadColor.AddChangeHook(Event_ConVarChanged); g_hCvar_Team.AddChangeHook(Event_ConVarChanged); + g_hCvar_CustomModel.AddChangeHook(Event_ConVarChanged); + g_hCvar_CustomModelVMT.AddChangeHook(Event_ConVarChanged); + g_hCvar_CustomModelVTF.AddChangeHook(Event_ConVarChanged); // Load plugin configs from .cfg AutoExecConfig(true, CONFIG_FILENAME); @@ -234,7 +261,7 @@ public void OnPluginStart() public void OnPluginEnd() { int entity; - char targetname[64]; + char targetname[19]; entity = INVALID_ENT_REFERENCE; while ((entity = FindEntityByClassname(entity, CLASSNAME_ENV_SPRITE)) != INVALID_ENT_REFERENCE) @@ -285,7 +312,6 @@ public void GetCvars() g_bCvar_AliveShow = g_hCvar_AliveShow.BoolValue; g_hCvar_AliveModel.GetString(g_sCvar_AliveModel, sizeof(g_sCvar_AliveModel)); TrimString(g_sCvar_AliveModel); - PrecacheModel(g_sCvar_AliveModel, true); g_iCvar_AliveAlpha = g_hCvar_AliveAlpha.IntValue; FormatEx(g_sCvar_AliveAlpha, sizeof(g_sCvar_AliveAlpha), "%i", g_iCvar_AliveAlpha); g_fCvar_AliveScale = g_hCvar_AliveScale.FloatValue; @@ -293,7 +319,6 @@ public void GetCvars() g_bCvar_DeadShow = g_hCvar_DeadShow.BoolValue; g_hCvar_DeadModel.GetString(g_sCvar_DeadModel, sizeof(g_sCvar_DeadModel)); TrimString(g_sCvar_DeadModel); - PrecacheModel(g_sCvar_DeadModel, true); g_iCvar_DeadAlpha = g_hCvar_DeadAlpha.IntValue; FormatEx(g_sCvar_DeadAlpha, sizeof(g_sCvar_DeadAlpha), "%i", g_iCvar_DeadAlpha); g_fCvar_DeadScale = g_hCvar_DeadScale.FloatValue; @@ -301,6 +326,28 @@ public void GetCvars() g_hCvar_DeadColor.GetString(g_sCvar_DeadColor, sizeof(g_sCvar_DeadColor)); TrimString(g_sCvar_DeadColor); g_iCvar_Team = g_hCvar_Team.IntValue; + g_bCvar_CustomModel = g_hCvar_CustomModel.BoolValue; + g_hCvar_CustomModelVMT.GetString(g_sCvar_CustomModelVMT, sizeof(g_sCvar_CustomModelVMT)); + TrimString(g_sCvar_CustomModelVMT); + g_hCvar_CustomModelVTF.GetString(g_sCvar_CustomModelVTF, sizeof(g_sCvar_CustomModelVTF)); + TrimString(g_sCvar_CustomModelVTF); + + if (g_bCvar_AliveShow) + { + if (g_bCvar_CustomModel) + { + AddFileToDownloadsTable(g_sCvar_CustomModelVMT); + AddFileToDownloadsTable(g_sCvar_CustomModelVTF); + PrecacheModel(g_sCvar_CustomModelVMT, true); + } + else + { + PrecacheModel(g_sCvar_AliveModel, true); + } + } + + if (g_bCvar_DeadShow) + PrecacheModel(g_sCvar_DeadModel, true); } /****************************************************************************************************/ @@ -309,8 +356,10 @@ public void LateLoad() { for (int client = 1; client <= MaxClients; client++) { - if (IsPlayerSpecialInfected(client)) - TankSprite(client); + if (!IsPlayerTank(client)) + continue; + + TankSprite(client); } } @@ -322,6 +371,7 @@ public void OnClientDisconnect(int client) return; gc_iTankSpriteRef[client] = INVALID_ENT_REFERENCE; + gc_iTankSpriteFrameRef[client] = INVALID_ENT_REFERENCE; for (int target = 1; target <= MaxClients; target++) { @@ -372,6 +422,7 @@ public void HookEvents(bool hook) { g_bEventsHooked = true; + HookEvent("tank_spawn", Event_TankSpawn); HookEvent("player_hurt", Event_PlayerHurt); return; @@ -381,6 +432,7 @@ public void HookEvents(bool hook) { g_bEventsHooked = false; + UnhookEvent("tank_spawn", Event_TankSpawn); UnhookEvent("player_hurt", Event_PlayerHurt); return; @@ -389,27 +441,37 @@ public void HookEvents(bool hook) /****************************************************************************************************/ +public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) +{ + int client = GetClientOfUserId(event.GetInt("userid")); -public void OnClientPutInServer(int client) { - if(GetClientTeam(client) == TEAM_INFECTED) { - //If all specials turned off and not tank; ignore. - if(!g_hCvar_AllSpecials.BoolValue && GetZombieClass(client) != g_iTankClass) return; - TankSprite(client); - } + if (!IsValidClient(client)) + return; + + TankSprite(client); } /****************************************************************************************************/ -public void Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast) { - int tank = GetClientOfUserId(event.GetInt("userid")); - if (IsPlayerSpecialInfected(tank)) { - TankSprite(tank); - if(g_bCvar_AttackDelay) { - int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (IsValidClient(attacker) && GetClientTeam(attacker) != TEAM_SURVIVOR) - gc_fLastAttack[tank][attacker] = GetGameTime(); - } - } +public void Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_bCvar_AttackDelay) + return; + + int target = GetClientOfUserId(event.GetInt("userid")); + + if (!IsPlayerTank(target)) + return; + + int attacker = GetClientOfUserId(event.GetInt("attacker")); + + if (!IsValidClient(attacker)) + return; + + if (GetClientTeam(attacker) != TEAM_SURVIVOR) + return; + + gc_fLastAttack[target][attacker] = GetGameTime(); } /****************************************************************************************************/ @@ -419,21 +481,26 @@ public Action TimerKill(Handle timer) if (!g_bConfigLoaded) return Plugin_Continue; - for (int client = 1; client <= MaxClients; client++) + for (int target = 1; target <= MaxClients; target++) { - if (gc_iTankSpriteRef[client] != INVALID_ENT_REFERENCE && !IsPlayerSpecialInfected(client)) { - int entity = EntRefToEntIndex(gc_iTankSpriteRef[client]); + if (gc_iTankSpriteRef[target] == INVALID_ENT_REFERENCE) + continue; - if (entity != INVALID_ENT_REFERENCE) - AcceptEntityInput(entity, "Kill"); + if (g_bCvar_Enabled && IsPlayerTank(target)) + continue; - gc_iTankSpriteRef[client] = INVALID_ENT_REFERENCE; + int entity = EntRefToEntIndex(gc_iTankSpriteRef[target]); - for (int client2 = 1; client2 <= MaxClients; client2++) - { - gc_bVisible[client][client2] = false; - gc_fLastAttack[client][client2] = 0.0; - } + if (entity != INVALID_ENT_REFERENCE) + AcceptEntityInput(entity, "Kill"); + + gc_iTankSpriteRef[target] = INVALID_ENT_REFERENCE; + gc_iTankSpriteFrameRef[target] = INVALID_ENT_REFERENCE; + + for (int client = 1; client <= MaxClients; client++) + { + gc_bVisible[target][client] = false; + gc_fLastAttack[target][client] = 0.0; } } @@ -455,38 +522,31 @@ public Action TimerVisible(Handle timer) if (gc_iTankSpriteRef[target] == INVALID_ENT_REFERENCE) continue; - if (!IsClientConnected(target)) + if (!IsClientInGame(target)) continue; for (int client = 1; client <= MaxClients; client++) { - if (!IsClientConnected(client)) + gc_bVisible[target][client] = false; + + if (!IsClientInGame(client)) continue; if (IsFakeClient(client)) continue; - if (!(GetClientTeamFlag(client) & g_iCvar_Team)) - { - gc_bVisible[target][client] = false; + if (!(GetTeamFlag(GetClientTeam(client)) & g_iCvar_Team)) continue; - } if (g_bCvar_AttackDelay || g_bCvar_Sight) { if (GetClientTeam(client) == TEAM_SURVIVOR || GetClientTeam(client) == TEAM_HOLDOUT) { if (g_bCvar_AttackDelay && (GetGameTime() - gc_fLastAttack[target][client] > g_fCvar_AttackDelay)) - { - gc_bVisible[target][client] = false; continue; - } if (g_bCvar_Sight && !IsVisibleTo(client, target)) - { - gc_bVisible[target][client] = false; continue; - } } } @@ -509,7 +569,7 @@ public Action TimerRender(Handle timer) for (int target = 1; target <= MaxClients; target++) { - if (!IsPlayerSpecialInfected(target)) + if (!IsPlayerTank(target)) continue; TankSprite(target); @@ -529,14 +589,43 @@ public void TankSprite(int client) if (entity == INVALID_ENT_REFERENCE) { + char targetname[22]; + FormatEx(targetname, sizeof(targetname), "%s-%i", "l4d_tank_hp_sprite", client); + entity = CreateEntityByName(CLASSNAME_ENV_SPRITE); - DispatchKeyValue(entity, "targetname", "l4d_tank_hp_sprite"); + ge_iOwner[entity] = client; + gc_iTankSpriteRef[client] = EntIndexToEntRef(entity); + DispatchKeyValue(entity, "targetname", targetname); DispatchKeyValue(entity, "spawnflags", "1"); DispatchKeyValue(entity, "fademindist", g_sCvar_FadeDistance); SetEntProp(entity, Prop_Data, "m_iHammerID", -1); SDKHook(entity, SDKHook_SetTransmit, OnSetTransmit); - ge_iOwner[entity] = client; - gc_iTankSpriteRef[client] = EntIndexToEntRef(entity); + } + + if (g_bCvar_CustomModel) + { + int entityFrame = INVALID_ENT_REFERENCE; + + if (gc_iTankSpriteFrameRef[client] != INVALID_ENT_REFERENCE) + entityFrame = EntRefToEntIndex(gc_iTankSpriteFrameRef[client]); + + if (entityFrame == INVALID_ENT_REFERENCE) + { + char targetname[22]; + FormatEx(targetname, sizeof(targetname), "%s-%i", "l4d_tank_hp_sprite", client); + + entityFrame = CreateEntityByName(CLASSNAME_ENV_TEXTURETOGGLE); + gc_iTankSpriteFrameRef[client] = EntIndexToEntRef(entityFrame); + DispatchKeyValue(entityFrame, "targetname", "l4d_tank_hp_sprite"); + DispatchKeyValue(entityFrame, "target", targetname); + + TeleportEntity(entityFrame, g_fVPos, NULL_VECTOR, NULL_VECTOR); + DispatchSpawn(entityFrame); + ActivateEntity(entityFrame); + + SetVariantString("!activator"); + AcceptEntityInput(entityFrame, "SetParent", entity); + } } if (IsPlayerIncapacitated(client)) @@ -547,12 +636,15 @@ public void TankSprite(int client) DispatchKeyValue(entity, "rendercolor", g_sCvar_DeadColor); DispatchKeyValue(entity, "renderamt", g_sCvar_DeadAlpha); // If renderamt goes before rendercolor, it doesn't render DispatchKeyValue(entity, "scale", g_sCvar_DeadScale); + + TeleportEntity(entity, g_fVPos, NULL_VECTOR, NULL_VECTOR); DispatchSpawn(entity); + ActivateEntity(entity); + SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", client); SetVariantString("!activator"); AcceptEntityInput(entity, "SetParent", client); AcceptEntityInput(entity, "ShowSprite"); - TeleportEntity(entity, g_fVPos, NULL_VECTOR, NULL_VECTOR); } return; @@ -568,9 +660,7 @@ public void TankSprite(int client) int currentHealth = GetClientHealth(client); float percentageHealth; - if (maxHealth == 0) - percentageHealth = 0.0; - else + if (maxHealth > 0) percentageHealth = (float(currentHealth) / float(maxHealth)); bool halfHealth = (percentageHealth <= 0.5); @@ -578,16 +668,35 @@ public void TankSprite(int client) char sRenderColor[12]; Format(sRenderColor, sizeof(sRenderColor), "%i %i 0", halfHealth ? 255 : RoundFloat(255.0 * ((1.0 - percentageHealth) * 2)), halfHealth ? RoundFloat(255.0 * (percentageHealth) * 2) : 255); - DispatchKeyValue(entity, "model", g_sCvar_AliveModel); + DispatchKeyValue(entity, "model", g_bCvar_CustomModel ? g_sCvar_CustomModelVMT : g_sCvar_AliveModel); DispatchKeyValue(entity, "rendercolor", sRenderColor); DispatchKeyValue(entity, "renderamt", g_sCvar_AliveAlpha); // If renderamt goes before rendercolor, it doesn't render DispatchKeyValue(entity, "scale", g_sCvar_AliveScale); + + TeleportEntity(entity, g_fVPos, NULL_VECTOR, NULL_VECTOR); DispatchSpawn(entity); + ActivateEntity(entity); + SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", client); SetVariantString("!activator"); AcceptEntityInput(entity, "SetParent", client); AcceptEntityInput(entity, "ShowSprite"); - TeleportEntity(entity, g_fVPos, NULL_VECTOR, NULL_VECTOR); + + if (!g_bCvar_CustomModel) + return; + + int entityFrame = EntRefToEntIndex(gc_iTankSpriteFrameRef[client]); + + if (entityFrame == INVALID_ENT_REFERENCE) + return; + + int frame = RoundFloat(percentageHealth * 100); + + char input[38]; + FormatEx(input, sizeof(input), "OnUser1 !self:SetTextureIndex:%i:0:1", frame); + SetVariantString(input); + AcceptEntityInput(entityFrame, "AddOutput"); + AcceptEntityInput(entityFrame, "FireUser1"); } /****************************************************************************************************/ @@ -612,14 +721,14 @@ bool IsVisibleTo(int client, int target) float vClientPos[3]; float vEntityPos[3]; float vLookAt[3]; - float vAngles[3]; + float vAng[3]; GetClientEyePosition(client, vClientPos); GetClientEyePosition(target, vEntityPos); MakeVectorFromPoints(vClientPos, vEntityPos, vLookAt); - GetVectorAngles(vLookAt, vAngles); + GetVectorAngles(vLookAt, vAng); - Handle trace = TR_TraceRayFilterEx(vClientPos, vAngles, MASK_PLAYERSOLID, RayType_Infinite, TraceFilter, target); + Handle trace = TR_TraceRayFilterEx(vClientPos, vAng, MASK_PLAYERSOLID, RayType_Infinite, TraceFilter, target); bool isVisible; @@ -682,6 +791,9 @@ public Action CmdPrintCvars(int client, int args) PrintToConsole(client, "l4d_tank_hp_sprite_dead_scale : %.2f", g_fCvar_DeadScale); PrintToConsole(client, "l4d_tank_hp_sprite_dead_color : \"%s\"", g_sCvar_DeadColor); PrintToConsole(client, "l4d_tank_hp_sprite_team : %i", g_iCvar_Team); + PrintToConsole(client, "l4d_tank_hp_sprite_custom_model : %b (%s)", g_bCvar_CustomModel, g_bCvar_CustomModel ? "true" : "false"); + PrintToConsole(client, "l4d_tank_hp_sprite_custom_model_vmt : \"%s\"", g_sCvar_CustomModelVMT); + PrintToConsole(client, "l4d_tank_hp_sprite_custom_model_vtf : \"%s\"", g_sCvar_CustomModelVTF); PrintToConsole(client, ""); PrintToConsole(client, "======================================================================"); PrintToConsole(client, ""); @@ -777,7 +889,8 @@ bool IsPlayerIncapacitated(int client) * @param client Client index. * @return True if client is a tank, false otherwise. */ -bool IsPlayerSpecialInfected(int client) { +bool IsPlayerTank(int client) +{ bool isValid = IsValidClient(client) && GetClientTeam(client) == TEAM_INFECTED && IsPlayerAlive(client) && !IsPlayerGhost(client); if(!g_hCvar_AllSpecials.BoolValue && GetZombieClass(client) != g_iTankClass) return false; @@ -788,14 +901,14 @@ bool IsPlayerSpecialInfected(int client) { /****************************************************************************************************/ /** - * Returns the team flag from a client. + * Returns the team flag from a team. * - * @param client Client index. - * @return Client team flag. + * @param team Team index. + * @return Team flag. */ -int GetClientTeamFlag(int client) +int GetTeamFlag(int team) { - switch (GetClientTeam(client)) + switch (team) { case TEAM_SURVIVOR: return FLAG_TEAM_SURVIVOR; diff --git a/scripting/l4dunreservelobby.sp b/scripting/l4dunreservelobby.sp new file mode 100644 index 0000000..1814300 --- /dev/null +++ b/scripting/l4dunreservelobby.sp @@ -0,0 +1,149 @@ +#include +#include +#include + +#define UNRESERVE_VERSION "1.1.1" + +#define UNRESERVE_DEBUG 0 +#define UNRESERVE_DEBUG_LOG 0 + +#define L4D_MAXCLIENTS MaxClients +#define L4D_MAXCLIENTS_PLUS1 (L4D_MAXCLIENTS + 1) + +#define L4D_MAXHUMANS_LOBBY_VERSUS 8 +#define L4D_MAXHUMANS_LOBBY_OTHER 4 + +new Handle:cvarGameMode = INVALID_HANDLE; + +public Plugin:myinfo = +{ + name = "L4D1/2 Remove Lobby Reservation", + author = "Downtown1", + description = "Removes lobby reservation when server is full", + version = UNRESERVE_VERSION, + url = "http://forums.alliedmods.net/showthread.php?t=87759" +} + +new Handle:cvarUnreserve = INVALID_HANDLE; + +public OnPluginStart() +{ + LoadTranslations("common.phrases"); + + RegAdminCmd("sm_unreserve", Command_Unreserve, ADMFLAG_BAN, "sm_unreserve - manually force removes the lobby reservation"); + + cvarUnreserve = CreateConVar("l4d_unreserve_full", "1", "Automatically unreserve server after a full lobby joins", FCVAR_SPONLY|FCVAR_NOTIFY); + CreateConVar("l4d_unreserve_version", UNRESERVE_VERSION, "Version of the Lobby Unreserve plugin.", FCVAR_SPONLY|FCVAR_NOTIFY); + + cvarGameMode = FindConVar("mp_gamemode"); +} + +bool:IsScavengeMode() +{ + decl String:sGameMode[32]; + GetConVarString(cvarGameMode, sGameMode, sizeof(sGameMode)); + if (StrContains(sGameMode, "scavenge") > -1) + { + return true; + } + else + { + return false; + } +} + +bool:IsVersusMode() +{ + decl String:sGameMode[32]; + GetConVarString(cvarGameMode, sGameMode, sizeof(sGameMode)); + if (StrContains(sGameMode, "versus") > -1) + { + return true; + } + else + { + return false; + } +} + +IsServerLobbyFull() +{ + new humans = GetHumanCount(); + + DebugPrintToAll("IsServerLobbyFull : humans = %d", humans); + + if(IsVersusMode() || IsScavengeMode()) + { + return humans >= L4D_MAXHUMANS_LOBBY_VERSUS; + } + return humans >= L4D_MAXHUMANS_LOBBY_OTHER; +} + +public OnClientPutInServer(client) +{ + DebugPrintToAll("Client put in server %N", client); + + if(GetConVarBool(cvarUnreserve) && /*L4D_LobbyIsReserved() &&*/ IsServerLobbyFull()) + { + //PrintToChatAll("[SM] A full lobby has connected, automatically unreserving the server."); + L4D_LobbyUnreserve(); + } +} + +public Action:Command_Unreserve(client, args) +{ + /*if(!L4D_LobbyIsReserved()) + { + ReplyToCommand(client, "[SM] Server is already unreserved."); + }*/ + + L4D_LobbyUnreserve(); + PrintToChatAll("[SM] Lobby reservation has been removed."); + + return Plugin_Handled; +} + + +//client is in-game and not a bot +stock bool:IsClientInGameHuman(client) +{ + return IsClientInGame(client) && !IsFakeClient(client); +} + +stock GetHumanCount() +{ + new humans = 0; + + new i; + for(i = 1; i < L4D_MAXCLIENTS_PLUS1; i++) + { + if(IsClientInGameHuman(i)) + { + humans++ + } + } + + return humans; +} + +DebugPrintToAll(const String:format[], any:...) +{ + #if UNRESERVE_DEBUG || UNRESERVE_DEBUG_LOG + decl String:buffer[192]; + + VFormat(buffer, sizeof(buffer), format, 2); + + #if UNRESERVE_DEBUG + PrintToChatAll("[UNRESERVE] %s", buffer); + PrintToConsole(0, "[UNRESERVE] %s", buffer); + #endif + + LogMessage("%s", buffer); + #else + //suppress "format" never used warning + if(format[0]) + return; + else + return; + #endif +} \ No newline at end of file diff --git a/scripting/sceneprocessor.sp b/scripting/sceneprocessor.sp new file mode 100644 index 0000000..dc2c2d1 --- /dev/null +++ b/scripting/sceneprocessor.sp @@ -0,0 +1,855 @@ +#define PLUGIN_VERSION "1.33.2" + +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include + +#include + +int iSkippedFrames; +char sVocalizeScene[MAXPLAYERS+1][MAX_VOCALIZE_LENGTH]; +bool bSceneHasInitiator[MAXPLAYERS+1], bScenesUnprocessed, bUnvocalizedCommands, bJailbreakVocalize, bIsL4D; + +float fStartTimeStamp, fVocalizePreDelay[MAXPLAYERS+1], fVocalizePitch[MAXPLAYERS+1]; +Handle hSceneStageForward, hVocalizeCommandForward; + +ArrayList alVocalize; +ArrayStack asScene; + +enum struct SceneData +{ + SceneStages ssDataBit; + bool bInFakePostSpawn; + float fTimeStampData; + int iActorData; + int iInitiatorData; + char sFileData[MAX_SCENEFILE_LENGTH]; + char sVocalizeData[MAX_VOCALIZE_LENGTH]; + float fPreDelayData; + float fPitchData; +} + +SceneData nSceneData[2048]; +int iScenePlaying[MAXPLAYERS+1], iVocalizeTick[MAXPLAYERS+1], iVocalizeInitiator[MAXPLAYERS+1]; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + EngineVersion evGame = GetEngineVersion(); + if (evGame == Engine_Left4Dead) + { + bIsL4D = true; + } + else if (evGame != Engine_Left4Dead2) + { + strcopy(error, err_max, "[SP] Plugin Supports L4D And L4D2 Only!"); + return APLRes_Failure; + } + + CreateNative("GetSceneStage", SP_GetSceneStage); + CreateNative("GetSceneStartTimeStamp", SP_GetSceneStartTimeStamp); + CreateNative("GetActorFromScene", SP_GetSceneActor); + CreateNative("GetSceneFromActor", SP_GetActorScene); + CreateNative("GetSceneInitiator", SP_GetSceneInitiator); + CreateNative("GetSceneFile", SP_GetSceneFile); + CreateNative("GetSceneVocalize", SP_GetSceneVocalize); + CreateNative("GetScenePreDelay", SP_GetScenePreDelay); + CreateNative("SetScenePreDelay", SP_SetScenePreDelay); + CreateNative("GetScenePitch", SP_GetScenePitch); + CreateNative("SetScenePitch", SP_SetScenePitch); + CreateNative("CancelScene", SP_CancelScene); + CreateNative("PerformScene", SP_PerformScene); + CreateNative("PerformSceneEx", SP_PerformSceneEx); + + RegPluginLibrary("sceneprocessor"); + return APLRes_Success; +} + +public any SP_GetSceneStage(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return SceneStage_Unknown; + } + + int scene = GetNativeCell(1); + if (scene < 1 || scene > 2048 || !IsValidEntity(scene)) + { + return SceneStage_Unknown; + } + + return nSceneData[scene].ssDataBit; +} + +public any SP_GetSceneStartTimeStamp(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return 0.0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0.0; + } + + return nSceneData[scene].fTimeStampData; +} + +public int SP_GetActorScene(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return INVALID_ENT_REFERENCE; + } + + int iActor = GetNativeCell(1); + if (iActor < 1 || iActor > MaxClients || !IsClientInGame(iActor) || GetClientTeam(iActor) != 2 || !IsPlayerAlive(iActor)) + { + return INVALID_ENT_REFERENCE; + } + + return iScenePlaying[iActor]; +} + +public int SP_GetSceneActor(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return 0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0; + } + + return nSceneData[scene].iActorData; +} + +public int SP_GetSceneInitiator(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return 0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0; + } + + return nSceneData[scene].iInitiatorData; +} + +public int SP_GetSceneFile(Handle plugin, int numParams) +{ + if (numParams != 3) + { + return 0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0; + } + + int len = GetNativeCell(3); + + int bytesWritten; + SetNativeString(2, nSceneData[scene].sFileData, len, _, bytesWritten); + return bytesWritten; +} + +public int SP_GetSceneVocalize(Handle plugin, int numParams) +{ + if (numParams != 3) + { + return 0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0; + } + + int len = GetNativeCell(3); + + int bytesWritten; + SetNativeString(2, nSceneData[scene].sVocalizeData, len, _, bytesWritten); + return bytesWritten; +} + +public any SP_GetScenePreDelay(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return 0.0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0.0; + } + + return nSceneData[scene].fPreDelayData; +} + +public int SP_SetScenePreDelay(Handle plugin, int numParams) +{ + if (numParams != 2) + { + return; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return; + } + + float fPreDelay = GetNativeCell(2); + + SetEntPropFloat(scene, Prop_Data, "m_flPreDelay", fPreDelay); + nSceneData[scene].fPreDelayData = fPreDelay; +} + +public any SP_GetScenePitch(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return 0.0; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return 0.0; + } + + return nSceneData[scene].fPitchData; +} + +public int SP_SetScenePitch(Handle plugin, int numParams) +{ + if (numParams != 2) + { + return; + } + + int scene = GetNativeCell(1); + if (!IsValidScene(scene)) + { + return; + } + + float fPitch = GetNativeCell(2); + + SetEntPropFloat(scene, Prop_Data, "m_fPitch", fPitch); + nSceneData[scene].fPitchData = fPitch; +} + +public int SP_CancelScene(Handle plugin, int numParams) +{ + if (numParams == 0) + { + return; + } + + int scene = GetNativeCell(1); + if (scene < 1 || scene > 2048 || !IsValidEntity(scene)) + { + return; + } + + SceneStages ssBit = nSceneData[scene].ssDataBit; + if (ssBit == SceneStage_Unknown) + { + return; + } + else if (ssBit == SceneStage_Started || (ssBit == SceneStage_SpawnedPost && nSceneData[scene].bInFakePostSpawn)) + { + AcceptEntityInput(scene, "Cancel"); + } + else if (ssBit != SceneStage_Cancelled && ssBit != SceneStage_Completion && ssBit != SceneStage_Killed) + { + AcceptEntityInput(scene, "Kill"); + } +} + +public int SP_PerformScene(Handle plugin, int numParams) +{ + if (numParams < 2) + { + return; + } + + int client = GetNativeCell(1); + + if (client < 1 || client > MaxClients || !IsClientInGame(client) || GetClientTeam(client) != 2 || !IsPlayerAlive(client)) + { + return; + } + + static char sVocalize[MAX_VOCALIZE_LENGTH], sFile[MAX_SCENEFILE_LENGTH]; + float fPreDelay = DEFAULT_SCENE_PREDELAY, fPitch = DEFAULT_SCENE_PITCH; + int iInitiator = SCENE_INITIATOR_PLUGIN; + + if (GetNativeString(2, sVocalize, MAX_VOCALIZE_LENGTH) != SP_ERROR_NONE) + { + ThrowNativeError(SP_ERROR_NATIVE, "Unknown Vocalize Parameter!"); + return; + } + + if (numParams >= 3) + { + if (GetNativeString(3, sFile, MAX_SCENEFILE_LENGTH) != SP_ERROR_NONE) + { + ThrowNativeError(SP_ERROR_NATIVE, "Unknown File Parameter!"); + return; + } + } + + if (numParams >= 4) + { + fPreDelay = GetNativeCell(4); + } + + if (numParams >= 5) + { + fPitch = GetNativeCell(5); + } + + if (numParams >= 6) + { + iInitiator = GetNativeCell(6); + } + + Scene_Perform(client, sVocalize, sFile, fPreDelay, fPitch, iInitiator); +} + +public int SP_PerformSceneEx(Handle plugin, int numParams) +{ + if (numParams < 2) + { + return; + } + + int client = GetNativeCell(1); + if (client < 1 || client > MaxClients || !IsClientInGame(client) || GetClientTeam(client) != 2 || !IsPlayerAlive(client)) + { + return; + } + + static char sVocalize[MAX_VOCALIZE_LENGTH], sFile[MAX_SCENEFILE_LENGTH]; + float fPreDelay = DEFAULT_SCENE_PREDELAY, fPitch = DEFAULT_SCENE_PITCH; + int iInitiator = SCENE_INITIATOR_PLUGIN; + + if (GetNativeString(2, sVocalize, MAX_VOCALIZE_LENGTH) != SP_ERROR_NONE) + { + ThrowNativeError(SP_ERROR_NATIVE, "Unknown Vocalize Parameter!"); + return; + } + + if (numParams >= 3) + { + if (GetNativeString(3, sFile, MAX_SCENEFILE_LENGTH) != SP_ERROR_NONE) + { + ThrowNativeError(SP_ERROR_NATIVE, "Unknown File Parameter!"); + return; + } + } + + if (numParams >= 4) + { + fPreDelay = GetNativeCell(4); + } + + if (numParams >= 5) + { + fPitch = GetNativeCell(5); + } + + if (numParams >= 6) + { + iInitiator = GetNativeCell(6); + } + + Scene_Perform(client, sVocalize, sFile, fPreDelay, fPitch, iInitiator, true); +} + +public Plugin myinfo = +{ + name = "Scene Processor", + author = "Buster \"Mr. Zero\" Nielsen (Fork by cravenge & Dragokas)", + description = "Provides Forwards and Natives For Scenes' Manipulation.", + version = PLUGIN_VERSION, + url = "https://forums.alliedmods.net/showthread.php?t=241585" +}; + +public void OnPluginStart() +{ + hSceneStageForward = CreateGlobalForward("OnSceneStageChanged", ET_Ignore, Param_Cell, Param_Cell); + hVocalizeCommandForward = CreateGlobalForward("OnVocalizeCommand", ET_Hook, Param_Cell, Param_String, Param_Cell); + + CreateConVar("sceneprocessor_version", PLUGIN_VERSION, "Scene Processor Version", FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_DONTRECORD); + if (!bIsL4D) + { + ConVar spJailbreakVocalize = CreateConVar("sceneprocessor_jailbreak_vocalize", "1", "Enable/Disable Jailbreak Vocalizations", FCVAR_SPONLY|FCVAR_NOTIFY); + spJailbreakVocalize.AddChangeHook(OnSPCVarChanged); + bJailbreakVocalize = spJailbreakVocalize.BoolValue; + } + + AddCommandListener(OnVocalizeCmd, "vocalize"); + + asScene = new ArrayStack(); + alVocalize = new ArrayList(MAX_VOCALIZE_LENGTH); + + for (int i = 1; i < 2049; i++) + { + if (IsValidEntity(i) && IsValidEdict(i)) + { + SceneData_SetStage(i, SceneStage_Unknown); + } + } + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + { + ResetClientVocalizeData(i); + } + } +} + +public void OnSPCVarChanged(ConVar cvar, const char[] sOldValue, const char[] sNewValue) +{ + bJailbreakVocalize = cvar.BoolValue; +} + +public Action OnVocalizeCmd(int client, const char[] command, int args) +{ + if (client == 0 || args == 0) + { + return Plugin_Continue; + } + + if (!IsClientInGame(client)) + { + return Plugin_Handled; + } + + static char sVocalize[128]; + GetCmdArg(1, sVocalize, sizeof(sVocalize)); + + if (!bIsL4D && args != 2) + { + if (bJailbreakVocalize) + { + JailbreakVocalize(client, sVocalize); + } + return Plugin_Handled; + } + + int iTick = GetGameTickCount(); + + if (!bSceneHasInitiator[client] || (iVocalizeTick[client] > 0 && iVocalizeTick[client] != iTick)) + { + iVocalizeInitiator[client] = client; + + if (!bIsL4D && args > 1 && StrEqual(sVocalize, "smartlook", false)) + { + static char sTime[32]; + GetCmdArg(2, sTime, sizeof(sTime)); + if (StrEqual(sTime, "auto", false)) + { + iVocalizeInitiator[client] = SCENE_INITIATOR_WORLD; + } + } + } + + strcopy(sVocalizeScene[client], MAX_VOCALIZE_LENGTH, sVocalize); + iVocalizeTick[client] = iTick; + + Action aResult = Plugin_Continue; + + Call_StartForward(hVocalizeCommandForward); + Call_PushCell(client); + Call_PushString(sVocalize); + Call_PushCell(iVocalizeInitiator[client]); + Call_Finish(aResult); + + return (aResult == Plugin_Stop) ? Plugin_Handled : Plugin_Continue; +} + +public void OnPluginEnd() +{ + RemoveCommandListener(OnVocalizeCmd, "vocalize"); +} + +public void OnMapStart() +{ + iSkippedFrames = 0; + fStartTimeStamp = GetGameTime(); +} + +public void OnEntityCreated(int entity, const char[] classname) +{ + if (entity < 1 || entity > 2048) + { + return; + } + + if (StrEqual(classname, "instanced_scripted_scene")) + { + SDKHook(entity, SDKHook_SpawnPost, OnSpawnPost); + SceneData_SetStage(entity, SceneStage_Created); + } +} + +public void OnSpawnPost(int entity) +{ + int iActor = GetEntPropEnt(entity, Prop_Data, "m_hOwner"); + nSceneData[entity].iActorData = iActor; + + static char sFile[MAX_SCENEFILE_LENGTH]; + GetEntPropString(entity, Prop_Data, "m_iszSceneFile", sFile, MAX_SCENEFILE_LENGTH); + + strcopy(nSceneData[entity].sFileData, MAX_SCENEFILE_LENGTH, sFile); + nSceneData[entity].fPitchData = GetEntPropFloat(entity, Prop_Data, "m_fPitch"); + + if (iActor > 0 && iActor <= MaxClients && IsClientInGame(iActor)) + { + if (iVocalizeTick[iActor] == GetGameTickCount()) + { + strcopy(nSceneData[entity].sVocalizeData, MAX_VOCALIZE_LENGTH, sVocalizeScene[iActor]); + + nSceneData[entity].iInitiatorData = iVocalizeInitiator[iActor]; + nSceneData[entity].fPreDelayData = fVocalizePreDelay[iActor]; + nSceneData[entity].fPitchData = fVocalizePitch[iActor]; + } + ResetClientVocalizeData(iActor); + } + + SetEntPropFloat(entity, Prop_Data, "m_fPitch", nSceneData[entity].fPitchData); + SetEntPropFloat(entity, Prop_Data, "m_flPreDelay", nSceneData[entity].fPreDelayData); + + asScene.Push(entity); + bScenesUnprocessed = true; + + HookSingleEntityOutput(entity, "OnStart", OnSceneStart_EntOutput); + HookSingleEntityOutput(entity, "OnCanceled", OnSceneCanceled_EntOutput); + + SceneData_SetStage(entity, SceneStage_Spawned); +} + +public void OnSceneStart_EntOutput(const char[] output, int caller, int activator, float delay) +{ + if (caller < 1 || caller > 2048 || !IsValidEntity(caller)) + { + return; + } + + static char sFile[MAX_SCENEFILE_LENGTH]; + strcopy(sFile, MAX_SCENEFILE_LENGTH, nSceneData[caller].sFileData); + if (!sFile[0]) + { + return; + } + + nSceneData[caller].fTimeStampData = GetEngineTime(); + + if (nSceneData[caller].ssDataBit == SceneStage_Spawned) + { + nSceneData[caller].bInFakePostSpawn = true; + SceneData_SetStage(caller, SceneStage_SpawnedPost); + } + + if (nSceneData[caller].ssDataBit == SceneStage_SpawnedPost) + { + int iActor = nSceneData[caller].iActorData; + if (iActor > 0 && iActor <= MaxClients && IsClientInGame(iActor)) + { + iScenePlaying[iActor] = caller; + } + SceneData_SetStage(caller, SceneStage_Started); + } +} + +public void OnSceneCanceled_EntOutput(const char[] output, int caller, int activator, float delay) +{ + if (caller < 1 || caller > 2048 || !IsValidEntity(caller)) + { + return; + } + + for (int i = 1; i <= MaxClients; i++) + { + if (iScenePlaying[i] == caller) + { + iScenePlaying[i] = INVALID_ENT_REFERENCE; + break; + } + } + + SceneData_SetStage(caller, SceneStage_Cancelled); +} + +public void OnEntityDestroyed(int entity) +{ + if (entity < 1 || entity > 2048 || !IsValidEdict(entity)) + { + return; + } + + static char sEntityClass[64]; + GetEdictClassname(entity, sEntityClass, sizeof(sEntityClass)); + if (!StrEqual(sEntityClass, "instanced_scripted_scene")) + { + return; + } + + SDKUnhook(entity, SDKHook_SpawnPost, OnSpawnPost); + + SceneStages ssBit = nSceneData[entity].ssDataBit; + if (ssBit != SceneStage_Unknown) + { + if (ssBit == SceneStage_Started) + { + SceneData_SetStage(entity, SceneStage_Completion); + } + SceneData_SetStage(entity, SceneStage_Killed); + + for (int i = 1; i <= MaxClients; i++) + { + if (!IsClientInGame(i) || iScenePlaying[i] != entity) + { + continue; + } + + iScenePlaying[i] = INVALID_ENT_REFERENCE; + break; + } + } + + SceneData_SetStage(entity, SceneStage_Unknown); +} + +public void OnClientDisconnect(int client) +{ + if (client == 0) + { + return; + } + + iScenePlaying[client] = INVALID_ENT_REFERENCE; +} + +public void OnGameFrame() +{ + iSkippedFrames += 1; + if (iSkippedFrames < 3) + { + return; + } + + iSkippedFrames = 1; + if (bScenesUnprocessed) + { + bScenesUnprocessed = false; + + int dScene; + while (!asScene.Empty) + { + asScene.Pop(dScene); + if (dScene < 1 || dScene > 2048 || !IsValidEntity(dScene)) + { + continue; + } + + if (nSceneData[dScene].ssDataBit != SceneStage_Spawned) + { + continue; + } + + nSceneData[dScene].fPreDelayData = GetEntPropFloat(dScene, Prop_Data, "m_flPreDelay"); + nSceneData[dScene].bInFakePostSpawn = false; + + SceneData_SetStage(dScene, SceneStage_SpawnedPost); + } + } + + if (bUnvocalizedCommands) + { + int iArraySize = alVocalize.Length, + iCurrentTick = GetGameTickCount(); + + static char sVocalize[MAX_VOCALIZE_LENGTH]; + float fPreDelay, fPitch; + int client, dInitiator, dTick; + + for (int i = 0; i < iArraySize; i += 6) + { + dTick = alVocalize.Get(i + 5); + if (iCurrentTick != dTick) + { + continue; + } + + client = alVocalize.Get(i + 0); + alVocalize.GetString(i + 1, sVocalize, MAX_VOCALIZE_LENGTH); + fPreDelay = view_as(alVocalize.Get(i + 2)); + fPitch = view_as(alVocalize.Get(i + 3)); + dInitiator = alVocalize.Get(i + 4); + + Scene_Perform(client, sVocalize, _, fPreDelay, fPitch, dInitiator, true); + + for (int j = 0; j < 6; j++) + { + alVocalize.Erase(i); + iArraySize -= 1; + } + } + if (iArraySize < 1) + { + alVocalize.Clear(); + bUnvocalizedCommands = false; + } + } +} + +public void OnMapEnd() +{ + iSkippedFrames = 0; + + bScenesUnprocessed = false; + bUnvocalizedCommands = false; + + while (!asScene.Empty) + { + PopStack(asScene); + } + alVocalize.Clear(); + + for (int i = 1; i < 2049; i++) + { + if (IsValidEntity(i) && IsValidEdict(i)) + { + SceneData_SetStage(i, SceneStage_Unknown); + } + } + + for (int i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i)) + { + iScenePlaying[i] = INVALID_ENT_REFERENCE; + } + } +} + +void ResetClientVocalizeData(int client) +{ + iVocalizeTick[client] = 0; + sVocalizeScene[client] = "\0"; + bSceneHasInitiator[client] = false; + iVocalizeInitiator[client] = SCENE_INITIATOR_WORLD; + fVocalizePreDelay[client] = DEFAULT_SCENE_PREDELAY; + fVocalizePitch[client] = DEFAULT_SCENE_PITCH; +} + +void SceneData_SetStage(int scene, SceneStages stage) +{ + nSceneData[scene].ssDataBit = stage; + + if (stage != SceneStage_Unknown) + { + Call_StartForward(hSceneStageForward); + Call_PushCell(scene); + Call_PushCell(stage); + Call_Finish(); + } + else + { + nSceneData[scene].bInFakePostSpawn = false; + nSceneData[scene].fTimeStampData = 0.0; + nSceneData[scene].iActorData = 0; + nSceneData[scene].iInitiatorData = 0; + strcopy(nSceneData[scene].sFileData, MAX_SCENEFILE_LENGTH, "\0"); + strcopy(nSceneData[scene].sVocalizeData, MAX_VOCALIZE_LENGTH, "\0"); + nSceneData[scene].fPreDelayData = DEFAULT_SCENE_PREDELAY; + nSceneData[scene].fPitchData = DEFAULT_SCENE_PITCH; + } +} + +void Scene_Perform(int client, const char[] sVocalizeParam, const char[] sFileParam = "", float fScenePreDelay = DEFAULT_SCENE_PREDELAY, float fScenePitch = DEFAULT_SCENE_PITCH, int iSceneInitiator = SCENE_INITIATOR_PLUGIN, bool bVocalizeNow = false) +{ + if (sFileParam[0] && FileExists(sFileParam, true)) + { + int iScene = CreateEntityByName("instanced_scripted_scene"); + DispatchKeyValue(iScene, "SceneFile", sFileParam); + + SetEntPropEnt(iScene, Prop_Data, "m_hOwner", client); + nSceneData[iScene].iActorData = client; + SetEntPropFloat(iScene, Prop_Data, "m_flPreDelay", fScenePreDelay); + nSceneData[iScene].fPreDelayData = fScenePreDelay; + SetEntPropFloat(iScene, Prop_Data, "m_fPitch", fScenePitch); + nSceneData[iScene].fPitchData = fScenePitch; + + nSceneData[iScene].iInitiatorData = iSceneInitiator; + strcopy(nSceneData[iScene].sVocalizeData, MAX_VOCALIZE_LENGTH, sVocalizeParam); + + DispatchSpawn(iScene); + ActivateEntity(iScene); + + AcceptEntityInput(iScene, "Start", client, client); + } + else if (sVocalizeParam[0]) + { + if (bVocalizeNow) + { + iVocalizeInitiator[client] = iSceneInitiator; + bSceneHasInitiator[client] = true; + fVocalizePreDelay[client] = fScenePreDelay; + fVocalizePitch[client] = fScenePitch; + + if (bIsL4D) + { + FakeClientCommandEx(client, "vocalize %s", sVocalizeParam); + } + else + { + JailbreakVocalize(client, sVocalizeParam); + } + } + else + { + alVocalize.Push(client); + alVocalize.PushString(sVocalizeParam); + alVocalize.Push(fScenePreDelay); + alVocalize.Push(fScenePitch); + alVocalize.Push(iSceneInitiator); + alVocalize.Push(GetGameTickCount() + 10 - 1); + + bUnvocalizedCommands = true; + } + } +} + +void JailbreakVocalize(int client, const char[] sVocalize) +{ + char sBuffer[2][32]; + FloatToString((GetGameTime() - fStartTimeStamp) + 2.0, sBuffer[0], 32); + ExplodeString(sBuffer[0], ".", sBuffer, 2, 32); + + Format(sBuffer[1], 2, "%s\0", sBuffer[1][0]); + FakeClientCommandEx(client, "vocalize %s #%s%s", sVocalize, sBuffer[0], sBuffer[1]); +}