From 14e26036d158192fb6f2163c3ceb2dd0c4e6c51e Mon Sep 17 00:00:00 2001 From: A1m` <33463136+A1mDev@users.noreply.github.com> Date: Sun, 2 Oct 2022 19:51:57 +0700 Subject: [PATCH] Update plugin confoglcompmod. (#538) * Moved plugin confoglcompmod to archive * Add updated plugin 'confoglcompmod' * Extra module disabled * Build plugin confoglcompmod --- addons/sourcemod/plugins/confoglcompmod.smx | Bin 59012 -> 59140 bytes .../scripting/archive/confoglcompmod.sp | 157 ++ .../scripting/archive/include/confogl.inc | 169 +++ .../{ => archive}/includes/configs.sp | 0 .../{ => archive}/includes/constants.sp | 0 .../{ => archive}/includes/customtags.inc | 0 .../scripting/{ => archive}/includes/debug.sp | 0 .../{ => archive}/includes/functions.sp | 0 .../{ => archive}/includes/survivorindex.sp | 0 .../{ => archive}/modules/BossSpawning.sp | 0 .../{ => archive}/modules/BotKick.sp | 0 .../{ => archive}/modules/CheckVersion.sp | 0 .../{ => archive}/modules/ClientSettings.sp | 0 .../{ => archive}/modules/CvarSettings.sp | 0 .../{ => archive}/modules/EntityRemover.sp | 0 .../{ => archive}/modules/FinaleSpawn.sp | 0 .../{ => archive}/modules/GhostTank.sp | 0 .../{ => archive}/modules/GhostWarp.sp | 0 .../{ => archive}/modules/ItemTracking.sp | 0 .../{ => archive}/modules/MapInfo.sp | 0 .../{ => archive}/modules/PasswordSystem.sp | 0 .../{ => archive}/modules/ReqMatch.sp | 0 .../{ => archive}/modules/ScoreMod.sp | 0 .../{ => archive}/modules/SpectatorHud.sp | 0 .../{ => archive}/modules/UnprohibitBosses.sp | 0 .../{ => archive}/modules/UnreserveLobby.sp | 0 .../{ => archive}/modules/WaterSlowdown.sp | 0 .../modules/WeaponCustomization.sp | 0 .../modules/WeaponInformation.sp | 0 .../{ => archive}/modules/l4dt_forwards.sp | 0 addons/sourcemod/scripting/confoglcompmod.sp | 567 ++++++-- .../scripting/confoglcompmod/BossSpawning.sp | 232 +++ .../scripting/confoglcompmod/BotKick.sp | 115 ++ .../confoglcompmod/ClientSettings.sp | 457 ++++++ .../scripting/confoglcompmod/CvarSettings.sp | 402 +++++ .../scripting/confoglcompmod/EntityRemover.sp | 403 +++++ .../scripting/confoglcompmod/FinaleSpawn.sp | 100 ++ .../scripting/confoglcompmod/GhostTank.sp | 345 +++++ .../scripting/confoglcompmod/GhostWarp.sp | 176 +++ .../scripting/confoglcompmod/ItemTracking.sp | 612 ++++++++ .../scripting/confoglcompmod/MapInfo.sp | 511 +++++++ .../confoglcompmod/PasswordSystem.sp | 161 ++ .../scripting/confoglcompmod/ReqMatch.sp | 391 +++++ .../scripting/confoglcompmod/ScoreMod.sp | 515 +++++++ .../confoglcompmod/UnprohibitBosses.sp | 58 + .../confoglcompmod/UnreserveLobby.sp | 36 + .../scripting/confoglcompmod/WaterSlowdown.sp | 125 ++ .../confoglcompmod/WeaponCustomization.sp | 132 ++ .../confoglcompmod/WeaponInformation.sp | 1292 +++++++++++++++++ .../confoglcompmod/includes/configs.sp | 176 +++ .../confoglcompmod/includes/constants.sp | 115 ++ .../confoglcompmod/includes/customtags.sp | 105 ++ .../confoglcompmod/includes/debug.sp | 77 + .../confoglcompmod/includes/functions.sp | 201 +++ .../confoglcompmod/includes/survivorindex.sp | 98 ++ .../sourcemod/scripting/include/confogl.inc | 49 +- 56 files changed, 7645 insertions(+), 132 deletions(-) create mode 100644 addons/sourcemod/scripting/archive/confoglcompmod.sp create mode 100644 addons/sourcemod/scripting/archive/include/confogl.inc rename addons/sourcemod/scripting/{ => archive}/includes/configs.sp (100%) rename addons/sourcemod/scripting/{ => archive}/includes/constants.sp (100%) rename addons/sourcemod/scripting/{ => archive}/includes/customtags.inc (100%) rename addons/sourcemod/scripting/{ => archive}/includes/debug.sp (100%) rename addons/sourcemod/scripting/{ => archive}/includes/functions.sp (100%) rename addons/sourcemod/scripting/{ => archive}/includes/survivorindex.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/BossSpawning.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/BotKick.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/CheckVersion.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/ClientSettings.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/CvarSettings.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/EntityRemover.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/FinaleSpawn.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/GhostTank.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/GhostWarp.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/ItemTracking.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/MapInfo.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/PasswordSystem.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/ReqMatch.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/ScoreMod.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/SpectatorHud.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/UnprohibitBosses.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/UnreserveLobby.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/WaterSlowdown.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/WeaponCustomization.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/WeaponInformation.sp (100%) rename addons/sourcemod/scripting/{ => archive}/modules/l4dt_forwards.sp (100%) create mode 100644 addons/sourcemod/scripting/confoglcompmod/BossSpawning.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/BotKick.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/ClientSettings.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/CvarSettings.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/EntityRemover.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/FinaleSpawn.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/GhostTank.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/GhostWarp.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/ItemTracking.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/MapInfo.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/PasswordSystem.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/ReqMatch.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/ScoreMod.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/UnprohibitBosses.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/UnreserveLobby.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/WaterSlowdown.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/WeaponCustomization.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/WeaponInformation.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/includes/configs.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/includes/constants.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/includes/customtags.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/includes/debug.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/includes/functions.sp create mode 100644 addons/sourcemod/scripting/confoglcompmod/includes/survivorindex.sp diff --git a/addons/sourcemod/plugins/confoglcompmod.smx b/addons/sourcemod/plugins/confoglcompmod.smx index 19b14ed3170798136106fc1d60d82de92254a8a3..30c28bb92f156292107ac7c8bf72e2157ffdbd28 100644 GIT binary patch literal 59140 zcmYhCcRZWX-}kAOwpLZ`Rn?Z7wQE-uRYg_N)<|1>?-3+vX^pB)ZK++<-Z7$9tcbmd z9T5aU#E8f5_uTjEdGg9PpZ7V}=bZCBf8(1>GeF@Lr*ay-q>Fa>+LZuTX?vD(_{)Uvk@J8^KF{ zBYcIzOpAg-{E|!EuTTVBcJkzs1^up2h%!-7XkM~W_!Wwu*Dk%6jE=fO@%M6Ui%X7- zxk7ROQk^cN{E}rZ6?hpdmmGAd{}~Sud+EQ_#LJ+KyF&5!Qu8lE>5>O7wfQn=;;&FF zUMk`;FqiztmV#pBl4D3$C~jTq++{#7Ip{Js>?LziP*E^4QcxUSvM?hRg_rDQ{#I_* zHWcF4pS(X&h`alIc6PAx`Y-r?^1KAsPu>o`Hva{eOY!vfcKE;E|B{Q1x4oP7|5X34 z^}knaTzy>rr`{XrZewj@`~Q`^Y`p)cWaa$H%j^GLv~{p?{@?EY+k>~KkJbNr|6c{* ze|+NBpY6nL9i0D@-+$2A!S#R9!PVC7|5pBQj{kMoIlF!S>}!|I8MMG3YG< zxEOeMw*9(*vva?e2XFNCo4;%*+v8k5(cY@7xb{nZJw{CXgM;aeY{`6-@3r<{%=y`r z^7r!}d8|Ee5sI1G`NwsPZyd~ zr|U&B@<{L1LWxI6d1-VI0V)5Vmw_#}DU;!Qv-cpn{Bv}l+CD?ZZ&`*_+dDIvR5|{I1=%{DyD&b=}eiZ!Ev0_lV{%>%KMlG2BVA(}#Uo zDzqUav?2GuEgJZDzVYwm@w?94lD7(PvJY=^3>QhWv-i`;unI4c?386+{y^_xs4@4O zf9DUs>%c8}hwyZ{&P@4E3&~Cv)FdK_YuS;`Loc*}LBsrV)S+8`mmb^jbeYafxlSbe zvN*@`-+Q3BHyY;o{ACNbCO?NqaxPQTdm!jME;G1HIFEm)hJWWT|4uXiP8I(kOoz|Z zHoW9<=Q#WFCjEapHqkH-;}>@g=aT8T4L|B>9ro6v?28s-W7Wyo8}lAZKi%h6oRgZ9e|dw!7@3mV5X?d3VZ z>7X2^oW@?|tb$-f((eY6GFTjVXa0+_D~u92=iH;wCic`prLXqchRp^nX4&i_o_ zzIJ_E!GH4>LT7gk_oLaa901pMH#0a$c_0op&Ty5zdG;qr)MQ0?-^p-MVwtgxnV0XO z`|Pc<`h6*n=>}JkxS-({iNupn9EB=HfR%0W)_Tu|uwH+kruu~kZ(8~d!3|MmFMQq? zY{-*FKJu5J-j*7qczPqGX@L^ulXsRcZh4cKFJ1 z%{{kx#Mr2FvaAc_2+RWu_@^(* z|J*J4@TKqV;yuEb-;#-<$u{#IJ>#YiEKhzv0+nm20};>pryXNRj?nhEy6P z&hR|fO#rET3(P@}#IdawC=^K)C;-)ZPv>+NFrlmspi-Gd_R%jVlRA1AuuO=+VXFEe z$1{KQS8sbsvP?EvvsI-Bt8;SVnFVP@-^en0v2b(d(-~v7EX#;^LbJuu-PZKgeew;d zm=>DpmxMo~LD9N5CZjspSLblsY>nPqI`s4S)k8rm_05Gvklv8BK{WW`ru3%`udAq% z=5ijh(Dm7RYP?f*I9|jJbb2yF(O!OJ;sZ#hADS!^?-4_<24JmcEP{3(j2b6tZTK0l ziSB%^R3m$Q?hx@qjNJZa;2oFjhjH-6HpLA-DeiT8k-)}d)j0QP+0)KCPQ)+k>Hf72 zR)BOz-;-b2t%3w%&pqNfAkJLq+1WjBcXb@;<`V^#e};K*?UxW0?X;xkpX1LN{hEW! zy^-UP??3;*Q29+gAu}p$MWhW&a#T-n#l2D?ZT-kS&cCa$v{F32_=sn;X8PrVmQ?wN z7j+W~;yj{bs=+ij#0KHBd~muwp@>4cquXvA&mr?|r{9MF*P4q5q;#1TFzO|0G?JoGFADu#TpS3AEj2aJgnEkL`r-GV2Qg)m5Sg7lY&(hy* zW*lBp&e2ear}>suP0KN#xn{`|GS-BpRBP!CY}#&xYE`XUaHhWMy9ER!cYDF=>Uv^C z3NGG0TB+Sz0|PZ&<Z$MJrx05$*sjl|13P$I0iD%8hnbw5s>N@QMDB-x8T$?Ld9O_ejT?5uZ{ z$s&V3`Sr<1F%pM^rytx1gDQ-Bji<^H zWh*7`=FWpeg_aJx_o{ZpP*gefMVNTJ~ z{^cU`LXq_r<43A?jaU;Z^9rQ1Hu zt&1#vi#G74Xt(mgozq@)v)o&$q*G*Ys?ZAm=?)-Pv zxXXZrcx2K98`V(S#!j{Dg170FI>YfaC|NFzuOe!lWlHPn-Rq?WWtu*gTbuSJLr*~A zD|ZjZvTM~8ydh2Yij5QbMaR5%v@Oy_>8BnZ){=&;VnX@;luJ5}3TK&QA$b2HGf`2l zn0g~u{uFtD9WnYWTohKs0 z8Z4DN50@vp%2c=PCnDEKA90_FEmSRx{3-!2J|z5;8<0{JoEvmWwGk@2jl3p2)RepD z^Jw(ACDG9x7>lBO(HPRhE8T+`qoEoRE6z@TRU(vQ@X+8xuSZU1iGKhK9J9KuSPRPG z>^;XSLX<6&Q_BcPh~IVLzhLd_t|5_!Uh>+Ke$_w#Q-j*K_11U#F`_1@Q|l&=u(dwU z%jJE6s~amtGtWsKlKp9-PNyWkx83B74WJvtf&44Ciq~YY7_?q1Tv5$E$zV_K+DUMF zP`20Fd*`0&Co#%i<9w%b>p8Rh`d0lPx!9B=J(!QG$8W+ZdCVVFWtVHH#y$iGzS|FZ zgB5;!TZMo29Z~4)$zFX)evgEG#HnR%XRS1wSF;kYt>?i@>|Ebp&9$GuiG~`f$+{NP zp!~7?{?oWtpBqc#{gZVz$BMl%T1RN5%IDkguuoy0ZGA^O%zvVV0cydM0`Y(jokZ}< z1YVp#%u8(8>j2tP0^q|IfXn|?RT30snDuBo>A}L8jWRBdZ>5vECd@4YYEU-2sXQS7 zh|K;{^%NnZ2x-!t%X(8A_ixb;_ONak2NxxFkvQQ`ow80X1DYb!w{T-$MiRw+mUxr5 zPVO<%3C8e<>3StYMD|Z>fsh2Omim^CA_cx7GL47 z&2wXK2Wr|EUJYMlI#)DM@j$s{NI($keQx`Biv_9Zw`WB5f3J<)p$+ImDlJ$~6h`hG;6@whvJE0tUX*jK z=L=5*S%$4QAGs-*8qQ{#EQ;2zj8km?$yw35f8m&RQ-Y@6Lp&=&4SV=VltD<)LkYxn zh(I-y)>m>=(>(`HD@(;&IWkXg-|~(5r26pmJ>TrIo7a||3QSX_!25+&<}3U)PR04J z-D*{CnnQVpoThj!_e0JG^BS&zZZp66gy{Y*f_b_n2Pb6cG)}4oY;L%|S;RVNBvw|) zklYyH7?M}`;RT|q0$K3NrTYDS(!@=8v3)hH%+oR*XldN+%xu7z3$x;>TOOQK-1;WI zZP7S-ll@E_4>8`6UWM0Zt7PZ7WGIE;{?JzZo&Mntn-wYPyAKk%jSYvD8B<*GI)~<`*h0$I6|nQ z#-kPMyu=olXqiM}Xn*rhpzb=ZFc0j+bp*X>?y9L6N5}|(i`2bC zryt7)ccij{;L77UI{=R77ykmALEf$TlMyN)vVzY-t`OcsZ$UL6TNV7!AfU)SePmHC z#9axRyP$3@dh}g%VW%+NQB(|Os`jhz%*P`P#+w3v0cHuUd~zMqyPEu&H4LZ8Vp}N|foRV2u1?{qAC)F|akpwd# z5}iwu|C^r?`M|n_bLJuax`NEd+*u*7)G+0KWwrmPyLjV%Db0J<@4=W=TCZ;NiCPpK zk%TN+ow=)k;CnH4K!qyk(AdtLgPG@FheWZ_a-%;J0?w9<<7Wn#{+PXlNx6ln^>u4~ zwFZR93Rj+_siocUQn@g}M5c~5aH2~kNWAJNUS|p3pG?}zCnVc6PMTQyqc6E15OI9vAS*n&mO2sglP%28iY~%7g$O-4kZGipPhTl*m6~kszu?=H?e97ZeACuF!gi0 z%eG9IFDVoEbrkZV41%7jn8vMvOG71W$gW8nVh6#baE1o)JUzh~FDO8|2y1|Hnt<)G z&%tKXwS(cbr~W!kld20T9jdL|(_S%80-EqrSLJQLG|~Li<6K;bS{!6wT=>2y&bher z-2HikaHlv(Zf!E8?^hFkt$)0(5tZXdlhiW0pT&`6kOlv_;3Ny|t6Fyw(j)9I;(n%3 zFR6L~lzz^9OwL2ZtD;};AV*g*z-DHWiA0l!>VQvXBj||=PP%9gC++QG(>UW=ZI-6A z9k<%e+Yf+9b@Sr;)S+eVSmw^Kag0Q>`L=}f{xz_9HT2Cy?PGNFm-SONfsN)B&((zH zx$#f#Gi*!}2@P_I4|8=!ALe79I)q-y`*&2v4>lR9Tnh-CtXy;C>K&3jH=i7v?DJ|IsA&4x5@Nf* z?OC6WO3Yp$h(bV&FJT=vmh}xOUVC~=nm5(lworVN1Cz6|ielqRcw(Vxrnc@$6brq+ z$ml}y%qjAKfB(SoZ4};}_n23L0x-^-QK`Hq1~LF|1Bw@^-j~r+$2!@4-mv_!KiB-X zy;7rH(q6Se<<-7dwl#wJun^<_fPjnK=eWUkw2ofbw=5QRMcq5p*&|OL@S4)p%c$yI zQDxKHo62HzF$$m5P)6R?Cp;tB_?L?yDz@xF20MEZn-3{TXIgdgM`%7gvb=?O)gC9U zmhS?pFrA;kT&X*Za_%7gp(p6@)zC|j6YD-1y_pX+T^|aaTF~mV*C8FE)XN~OrW=O_ zw+lzqXfVGa4AetvSeX6y_YByBfsjzr!@Bp-mqmMBb`|359_3EplJ#f%0luZ7t?!{k z#^OmKGqrGUkuiotmH=qB+||ji3mrmwf`u$)v=(NikINCoGarr|I=r`*8w{aGs@_lm zVa&VlXyUu$x{dX5f2881Vv(b(;v32OMr@WcCO?%?#8b#l3^k-bMLM|^}vG8tpV&C4t=V0k*TQ6m%LgxRc!^YEWGg{R}b59X++ z!~FJJ%Px4~tvpV6?BR61Ov1shbd0EWq&eu6#a0aSB6bNzr9sqhL8^gL* zyqfwap%0_vv;tTn{_F7H8Qf+&A3b?9Ew`(XYD=0c~@tJd_SXxZ+Q%cFZQb{^HYeUTrQk@!%T< zBJQ^j@NG^yG|dNy<6exv-E3e>JzvspONEH&1qg5-&xC~CiL0cg^YDk(-YA%&7=7a! z-EXX=8!hE5UTBQ&^)HBTd8TE_6nIBQ>NbJxh@R8z(@|yDx!KgK@7$nhLSKY>Iq%)> zCe0Bt^wtMro3D@L5%p64@$d9sS!&Yu`jG0xti@u2OFZrg<9H=W){V#P%7i5MEs1#e z4T(_>dPTlZnc~LwC*k~{vcM5f?B8N#ugnmuKDFp$xk=UHH+sD9;1Z`5GXUxi;)jLm zRS)xfh(@Yl)3@kZ8MIO#5|pEBUL*q!o^ zi-|(WS_y=z2u|FWt=K;oVUr~f={S@_zcAbUnGpztcROuX4Q5xlrx)Jwx;Edqcwsqf z)LkpYXuhU0>eWT8qGoIa3^Hy|0j`&M!n=mqpex_86XfOb>a?OrQVCwrCUgoYYb!rAoW=~Dk|_MIo2eYm8z1}J zP8~kekr=E(uS@kFg>5%cLET*jjD?^pB?F|ltA<%Y;G|a=NCj=b!IqgMY5f}3Rd4(Ss|gNuMPaXPuFqLxjawTpNN1xzSUKSERv)Wjf3c@=B2~El&h~*YMkqkd zR*105-OsJLc4x8y|3LOc$^cn8aPzG`bAX#%A}}D@#`m`;Lk_VcRH_j3ZvDEj-OU!L zOd`-W+9peL?QNOXX|@TjU-|S4z_fKg65MD_X#G1Y5w`S@s$K8eHzkf+J8z#hj8ZNY zP_<{?xb9~{RakBFIbelwuPakms#NmTwcyl|wzv0E5;#+YuX??tG79;q@AT1;X`qI> zfZ<=paNC$bN;PLnD9u3NYftQLF}FKqx)~=mCAwqHd%5EeUMW7EEj-7Nd(Y=(E(VtC zKY+p-T^To~<%6M-LdoNQAL-U=p%tA~xhhnwhc}6HCcqO`itPOD6&9ilD*{W77Y6yG1ZZKSikvV)_E0UdCWD!H( zWXICuw;&JcZkl*6TH1dTJ-JK);u)cV2W@8hxh5>EAWfpFk$Ux7bWW{1Bk!Pl8c|rQw12v@pESC?&pK#b1B0x z+9PZ%;#M3ZuZ9ol`tc+SS?WCmjtI#tmu>#A9kaE^(YXFywlvaWwZe+?-K(Y%6mAk* zDW-hFVPbcMFL8qQe(OC45Ls-}+>E?8{_ot(otMp!*QkU*h?A^M-y$lu%pfr#8FIR! zcph5e%)3IRRlX(MKJV@w5+X|o_!w^xMZU;;=B=&rbTK%z@h1Q(BVS}(!{QpK0QykH zFQ(a$RBAYS->L{jCE#)X>tgOT57c0;C{_Y0@v8Ouf*_Fl;%v@3aYLgP&|{#*bUazw z-e(znR5mBH;5)I3m^F5~xw8Esv24v{b^EuB*qec+Xz)KJa?W2C<2jwQ4C#B>m^ zJ}48tKiL0+cggNI+(sO~D=hCe?dR%j{22NaCk7%T=twb`IQSf-JQ|2KAH+7Zj&tC8 z*vce%I7XdK;_y>u;x0T4Y~aYH+L+Pgg=?p*lc`Sz!mrq_im60b|H%-}LSXCJ#jlzM zOk9!3rCy3$v6CgS^&#BCoUdaZalN_L7fPA9Gc|jc>ncdEF1m5ESayXJMRk?;w146I zfz<8fa^4a`GJQUdy@!i6)^%L}r>V>|3bO}md4rzfywD<}oT^#nLot>2<2(k5z zM;z=nXNPlArZsG>Uhdw;k1y7K0Eqp~{UhM%2Xm0{J}AI6S)R3`nO_s=xd1z1lX}>* z7AaLRknc%`iC`kcl9x{&m~?A?!W_rrbic0{pNDr|+txl-)t*k~!2p+aPh?037biC+ zQP@uhr&mragm&Ih-#QK{nQId7e%**W#y{)-2Ri=S5aS#LRu$b+L_FE*m33^2rh#Ad z5PjXjci}c<z||5)8Q!#^we z#Gp*Cm3PeyW>N}I>-#NtE3wSiW$8pDV%DwEj+hLZjw#$RCsJ-PdedV+PMY&jf z5N{j|0UYgf)drrK?06d?LQsJZ!6L`ag3xLSCrCf!XyvoGwhzwo&}m!|)u*u2D`79T_DTL0~4Jr9)YQL@ZsC;f<| z;^lg1*zyLj7ZaI~R@J4N9l>Ox9S47;=ZjpMG(Va2QVAP+VHr{aa8eDitm9Yu;cJLh z$kBUj9t^j`OkZ@(`V}8rMm{^i@8$t^0+05}kRYv+o#y>{r;t*t{-d3$*L!I<_19&U z4cLMndIt-@w=*#^4ioQeR8z-yP0FfkBza=Wd>s7RJn;eHWrrQnP9ImCEX>g%gLbNo za>t~B?rSW*U>G!}~b2@ywx8G?Ua#v`op;-}hU8EZEVn(XqVN z$11K?e_P1K9jtMS{?1TOl*g`4tEe*K`W4KdDu@Z;`47NjuLt95*9lDsUK1$#ylLb7 zpo5foLzVe1YFe2f8UR;Bm9I#5pv%?lnzzq411;YCL3N_>P)0^L^dJ3SAH<{m=(V|n z3!yF5A@#R~B^#il^OK{*z1VX$>@nFG|K808U<^H+*fp--dEgViqwxhLOc5Bq7#)#>|J)6wA?wBef@%rEHLI6Cy-7KXx+(iCMF2kwRSz{ydv}{ z*!Dik2tXj1rH7*3EnF6$#>#~`q=wLSIgHyTn@67+dKdo(7r5$Yo`Em~8Q5>8m+-<` zt(L<&IKjVtF2w7(K*{6h=9y-8!q#{8+4|qp%)V&F&2Z218WN_F8LF6M|MM?Oyn72~ zVR$*7q0Xg1U0Zm_Q3`~HL=Syd2gL_`V%P-+JKM$M*_n4gGdx}B;$9rXpJ4J2<_xsA zCkvf&L!1tY5{w)>s?2A8ONnV0FI#thfex5z{MMO3kXfY7+wI9i)gL)?b>$`fD&c*Z z&)2d7?i1Oo0!wGmBNGGAt6Sf-&$i|L`4sCziSuLlN;F5*--mnm3^0bEtm4)aHJ9eF z>TGp8+8nP|w9)9!ePIYO*FGrVE!x)bZL%pIZ705fuCL(&A&l%e!3=dP9B= zC|oO*Jy951ztJgi4$+0IBlZBChjThnog`=q_j$4cZMoR z=9+?BG*mrWIO4dAA?b8upb0UQu`3BN3$M-)^c=v_@EXM!Ur83=G}pgt9(~VTi?<)i z_RLLT?;RMH6ZEShXnivRapu?CSEblI;YKw|`dc!=l8c;^u=)UkZWkfWf+E)>vauw$ zVh7mU1Uns-w9&B={J8Nv5YV^w{#}9_sD;CHUOBYyy8|AmT&1iaTUxgAAkmd>9OvHx z%;*9$KQmR_7jtXbHLqJUv(MyRJ5T$*6QljO`f%N6l(v8D(0K1~=a95li*-#?pyU$Ax3Ojq$JPsC{dwRuw}*TJkz<?r6W(B?-W!S^<2!9wecmI6F{(c;uzb0Pe_z=F3rAl*}ejUEHH07ayLe zJG?3kPPq`PA0vn>*DVMBu$vTFw~uMUtNa->j%fl0^}K;17Q#ogUe5?njzs6w@FyPK zd3pLtZ>Cyk+cL3kBVx{>Kg%6$CFD|a&!w!%RuUBhMu@`8%P^qjm@@|)3gx0lJmxDp z6%;2*1xKhZCHEH}2y^eHfPGpT0YAV(gIO$2PR4d|F4`fHFS8P%@Su63g96N{35!#ZplAxJHHtoAir0*bH3uK%)x1JUIZ0n)IDi@A#%YAOxisc znT;JZlU!-uFj}h2gU)_BE4saycP)D9zd@JL?@*#}mKGyTxy&E#lj$XC+rIHxpe> z>(q!db#E(RUfOnE2l+R2J!o<*QhuhL5$2?dHy{EWwM*2!f9Qcdf9SvW{;4-xNU?r7 ztx)|c4j+cJqI4Di4zDD zBPj1`PqXqsfL1f@&(YmnPti*ROTJ@snLA!HmGHNxZSwX<<%`wE;o|R!kY)#OjANj4 zJ69ZhNXt)DysDW)NeyC}nRtMJV?Bd&G_nY;Q@E++5E)o{B9kbw#Ko7w7^+8Emh`s3o zQH7Lll#g$FQ`RO_KYFJWhmo1lH4iy)r{C<5hODVg%7vpUn)OXZ{wusS=L5T{(YDr!ro8;UCf)$G*+0ZoZ%&NKI`FOjnys}J`maRw zr)V3w-=1$aRIG%m9inY`wbnAU@S~wpT!yI_`4W&bj%mP{{&Lax2=s>DDB|O$%wyy0 zw)NBn)c@%0|Fh~dq<>|Rd!ip1^V=t4PcUTOVNda;*S(KvF*!1VR0Rs(Mlx=stQ#k; zo43i7XUnib>3rEKY?D$aDdTHsth z$GgYNS{DJUae?Q4ytn=5VY=IDznjM6L`4E`W>po)uSM}4jUhd^H zwSCY9h{Fp)KA-nvWQidfa<2UgDQ#PR3mjQ#t;Z z49w<6BlCHdX;}}M<-RJ;?jUg%_4telnH4dEv;Dx#y)pd}P3xPreQXo}@;`fYs7!$? zU^Xb)z7DN8^9!*-EEIa8ZN!{Y_AYR19n{FI!32Y~g;8L5A(M;g&zZ93VKdiZVvEgn z_BfQ_gV~wSyii?1w7{Mozl+i?h(Mkg1&%xu47(26eUCPUyB&=u`OHIW&;52kUfg|E zeV6>~Ei}z)e$|%^bhO1%-OQ|P^kYQM!8($FB756J{j;$=gC6cj*IW##;XG{4WM*Pj z{9f~47;lU9|CzWbd_uJxc2Y;K*j5p+T%#?3LGY9g+_#C~73pr{we9+Izmui?Vo^L0 z_Ao0AC_Dvr590eIIZ0cDM>8I5e+4@4(rgjm94KAC+#^*4KRF4RJC7gaq1{p{ZsPyW z%hIyR)8rmRgNSMC+4h#_Wn|f7h+(UHz#l?go`#A<0MDA&T9lX(JmUi>iS^&ZRKG5G z`$T>yDR?OTn$JwV{Eg_jeo<=f@@eKF|5#MQp;nEhp;?nt~itf?Fg@r z9vSqRJ^8^%8F4&p)d~T^{(UFHa^v z3ViP6VD*^KMUqC6w3Kk>HugcoiCIyQswGAqyS*f)x9%lc@LIL&`Z~U{rA7xLr^R0< zkEnAv|ByCj<-yX~f2RErY&ZkGn=IDx`V?#gR&^+V3EBKu%Yu3Y(ZUS&9iHP054WW& zySlQM9nOJ#D`w_d_wZSPz3bE0GKq#^?%bMqE9xMf>4S`aq`Cz^GWHA=&5q7FqBSD@ zSoCH#J8R`vB&m^P6cO(d7*<})`bDPOevTvs(FX7?vcfZ-z=n=UM;G#DnQocwj1g@R zmP15QVMw{=6u00=IRoqT*CXL2#Tc%Q5|PNA3wSTXs?`VIgi>xN=P3AqTi{{wp*qp9 z_vEeYh(-(*gQs#98oK(V&yJy!}CV6E8_FC4@h8!NOStQ;P9#g05_XBkE7D)RU?!*c7%NRiZ99 zSRvI{d|xgX_Su=IJP0K{J_0C&t?S$e#gZNm;=~gtA8jeUd1}cequO$aT2(V}3+n!U zT4Q^0 z(3m6LG;jA2=&N(BS|SA2BbVhM);|z}ntPJ^X!jPT>GMo@wSFY)v?|nX+V{42C1}ro z;APU(G~v0#uHSSyFMe3@m<|SA<2WQeY;BR|R0bf7Ok){lwrAJicP-GvI>evQWx zL90mn=x+;xY4ok{k@obp({A|RUOl#K<#utrVM{of)1lr|uPK891vLwCHhq})eaoM4 z>$#LxB!i5pfII9Qe{hDGb>41dDQ-1|G$99j-u=}VbY~&oe+FQu^q<3{Ed1al&K~dV z<~X0)CGP!J4MI99Wa^^Y0&ZB)x*V0!7rb;S-T4y8e5>4aL&P4E-SL# z+Bj&-IHOg3np9i|18@A2F%hddBcAv8T>O+Vd9OT={1YWE06jY90mHI@-L!#UaIf}( zevx^H2J_R?NWZCHeHi5u>h+_3SxTGht*@QuNkh?`=UWr$05{3RKzAoH=0=#|0^883riMaT z{a{qVMD+^^Z9ET()A^&9s24?&7j+AYy)PrhDmB?q!9^^5UF<|E230wr{&w(AfSQ94 zp@+LaLu>7hN|^I33fr&zqtAKvhcm!zoc!gOeCtXKo~sgQc1_Rp=LipD?2bWPt3r)& zxf_{3Sjqm&snu1#DGDOoMb^T)0bVig9lO-(RDu)hP(T%d=3}zuO2*sh1X3Q-4_pt% z4z>NfmqJNDFlAy2;W4Zc`0AkbIM-}EAc!iMW3)}?UWyfGisDu2zh8^A4^O1Wcm+a_ z+T3#~m%8iL>)iM>K!*lH4T@*xJJnEc2Lzo%NJe$i`%fq=cfLsf*+~`BCZl<0yw4}I z*i%X5G@N`p41IN{+MJCUyH4uf9Zk2ssJPd!Qc`kluOl`mC;)ynm>)kjS)Vb@Wn|Wf z_HNux=c5y?2VwgbY%t1HEQB?Y5zgkG!UI(k@^Go+h5GFnyi13_am(h6-Hkci5C1q! z_!>O0bg22J3)d%_{ugkXJaotiz5pV)lC00x%7PU8b&r?L&T-KvWT44qWg6Uox|vnr zN6})2s;6ZEJq>f3HIXfYXJ~_L($L|}KQJlD;yl?pxQX_W?FGj=o%284I-{*uQz7DL zd|y=v=h+9t=`h*HO>VpF7Js4%ZOc<|8(}tSp{k{CUc9WS`7q_Eu2qywJhsF*^$;22q%zo~#Rq{kp$dk$iHMEb zss$y!Lx=R3{Kk(jRKK-qyHE>Ex_HOYHYsU?8}zRI6qZn>aT>yI!&;u?&}%cDrwu)s z>6|TbO)4Fyw^mMX5NiS_m5!0pV=Wq7n#iL5qsqmrUq2H5+#cjB4pg|F;mSz(LtTEO z6O$K>;qyk+Q|gQCBi9L8VN7)E!~gvJYd-Rp#vYvyc_B;oF(@zNpK5zxnN&3i&SNj8 z8Rq9G>1jiD36wJX&tyAZ(VxjiN(0U2+^=or-_mf^-{Af=j2sVu>P%aH6u$8HsgSfP zKG80xl2N-uS6>(Ayz+ifef5i|o9*kY6hA?~t4;EhxMXA5y7+oKs#Q%CWGv6k`4COI zi=S^Y+}HcX;)zBCFt=P(bem+A3^&Z=JBj2t^_yG-Jhn)8)C84{;}X0Ro+Sfd6ZtHI zc7J8xHQks#3tsDUf3tv55Zf?!iZZpGv!9M}C{0jfyD~-tJ7Lcl)XacQxc>=X^Jh(IekbT5~vkqoQ zd!6%d((b|W8pd$_kzQ)l4=K&}pPpkRBLtsws`u|}x15ClsWE}HoYH$7W`S{AA8Z;i z8cXLG&uLx%4Ch%+O>hu%3{0_gQD9Sv{AUL4duK0DCm>^oA`L3O0u+z656Ak- zPcl_Ft582?%ylCT(GtV1LfFW`hza2NwMxc3(Wa~0(GyMn^a2+F=J?zn0hd2s{g`C& zsc2jc-*ILSPAD2g@Ku9|?><^`uKURLK9XF(I3mJHZ-`&#@gYIDmmp#Jme7-DUqDZ*`abP+DaDVN$W)J`^O3QktP$pi!FS>`Udcl#g z&%G)-k{X)j?Eo^79ISQzbdaj)^$LB?ls>N#%_9VV-!+$(1^NAjP3BD|MMTD$(n!JOmkEu?c!SfSh0a@oG$1Y zDg8i!?xNoLKd4+{Nz>uVSj2qUnPR1x7$9cLb8qHr_ai#DnZQPxUUa<5zq4@5PO?~F zaFkI~1w^+^24!`@s*y&WSO83>CHj8>X$MDZ`vDPMpRmLpaR|Vq)Zn(cVqL@86Ieg3 z7X^t=TqEe-X+kt6m4P^Z6@nVHOLN|y=O>MbUYFP$iSys2J@>AVY*=<6uuoP9PNjmp z=cAHUPHF(+pB0o28c3p(>90vSQO<*Eopu(kf|3Rn1}K&a71D0iS#WtYEGwJN*iiRdUJS;zAl7b(sbCwhk*>qk+#ceQf&)pwBMkcaZ@uun5dD<6p@ZYV~RQ>ZCzV#U^P0ijzhuh|`#y@783O@6jH-(t-MLqHshLqT{A7MJ4q>fHEOZjQ$N2Ol&tJ$u*9J>S;=kPJSjNp+jE5yP`!e>Wi^qq{VyfE5+Xs%2d257}kAqMv|t)J>YH*+*N%? z^f6Lrb?@QH=iR;aMZ&`Q$LA6cYm^Ah5K+}SUC6^_-K8$j{$wR_bAfI}(6#w2jQen&3Zuew^RvdmSEmB#cWn)sd+mdk7_M>^f(&I)GCXVea zkU{nPnPznJ;>e~mh0?Fz=RK~n+mT29gaikOiTY`E534VAZHMvm(`7l??KMwUp_#^a z@gbj8u&fR$STS7?@mbkv`ek!I!gIC08LOE)WT)~X{9SlnC4pxd^uESITDZQIzPCoM z*8CR=UKGE+S-i)&B0$uUq_!fz+9hrU9E;DI7GSrUZ-|RLZ1~IW=w;KH`NHJmbbw&H z1J?|j%Fuck&ol2dHO9m3x;f0%jAssPxiy!&$zie%$*rrc63-p{;E75u)cF?QM)ZY8 zO!lfNUU{{DmAkN5@=`4PjhtBc?pKFg8?OGt%Vs%35Sot}wta*e=(0&{vF4goeX9zr z?h$l5c~FbwgEGnO#h11;VLtJ?r7>E@j`Y`c_a$#Vt-CU3TO3%R3FlxV)I5ZBpG!C4OznHde z)!kI;AiZlMy;3ngE6+Y2_B}koyx|4RE=5P?|7o@P15J?Y&q_U{Jmg68Oq=H$f}#b> zDI*Woew-go33d+ePNc;4axTP^Y+iYo#zX*~r(FxS<#$6f?HyZgUo)vV<<#bv$Y^EH zFSN7NKCnqz71Eucd0|mGA4&#XT#_-u|f&$ECq_5^i2F(1zUmDFJH6F}&Ydr|Qj{`@Va z8Ry3!Mt~QBSoLmaPbEk!rP(BEg;oJ9oW+yuZ!)NR5N98=Et2~}I5%4Z1o$xaTIxIAE1^aFyBAwZ=9_fd%=TdNU#6QF4UMpZbPih|BHa2XP zU>V&_3n+WEGyAVObrJuJ%^P-(J9^eiKkGO<6Qo1DgOkvZB+fbh3!GNrmT0Q!@%AUG zPyZrY?ye^v5*;(N&ZpzNi}`gy65X+@fNv-hwWyhm&td4JrxO9IO;WpL(=}ePsDXbK z8VVmnwi@G4Zk1j}|b^m~UUJT79!o-d}OC`HT zO|L&}l~9J9mM1VN*S>1k`)JBUJTmQM{J!TTeqCWgB4n$BL#CVYH&Ok7R~!E*uZf=j zv>TZrk^5*C5rdF9VXee|oIPN9ON7P30Gmf^qqV-)W~FW_Yb2ha>VOS9WYGE!PQTH8J^HL6u8XJej>MmF1O{9D^h zN<Sje{Z31U|yhF&wbW3x1R+~ z>3_&Sa+9O#&f$uuE{L(~YWq2N`}sqvop)tDiY%=mKYDU{Rm(-+kTQCnSUh`T?41gF z>A3z8^>Pb)#9BV(zNlI{?e%5aHz!1Em9!ZwEOJz!R@+ zGYbbooALgzZ+$oX1o+}yL8*sK=iJzj_2B6HTPlL(q)%LTr1T~t3uEaqi>Z`UNe(RWY~9m>(6UL(JMi6VDZnm5W9w z{OwRGK%|Ayol`?7bE^|y742a9P}R1^LNjKsd2+8|l4||^*G(aOks~rU>Kk8g(5gh@ z<^R|aVKnEM=O0g!_mw+2?j@5r7$3yDy)eE%rD87^EdS5=`N3)5i#c%zNEux&46)73 z3o$65y|zcJQD{xn4n3=*vimj&~qqpDwgM1 zyzQ@nK?O`T0h81X)SM)WRZFR`ZzZmT3ZRKtSQ581j&vjjN#D+k= zobXma&6!_MuR{zb)dZ|2i#WjCd=Y~OcO90BhAi$ns=o*#Q zfnw>g$=smTrfg%F&TPqC*oED>l~^3kU-`k~egBU$N?-R<*lxw_#}w_g5gvXuAR(N$ z+3#GH;K}R-Ta1E|mqP0P4*;k@SHJI$Oq0Lv1?ilKcvL;VQJoWUIGTIUz%++FrhyHz zo~W98k46ihdtdmto%g)BMBXNM|D*j9QP_3KGgw<%P&ThJW( zf&^bt08X;+2tEU2M$vClzho<6rxmaj!!F-XUGww(%rKKLCnZ{V_KE$R?K!JM-n%fP z*t>ww^bmBxwuMCqpM3bGSmKvch+ob|)xLW83`V-15_91`h}Hv7p}EZ|K7SzTgpE4> zbFSPT`MCb9n)%W?Wqr)Quly7m8TES;> z`m{7J!EybT|86%7gZXgcPH!Jc@b|2IKJ3a@lYObv>koQegP!-KKh za`;B;Ag559O(7lR@TY9wd4sYJQuhg$m!k@Jl@4-Rp@W=8I*7vyLpINWPuog3)mc|_!D$C?7t((+zi4OE zi2gN$`q%8Iy#1>pz}_~M&qfIvfA3bn7c1c9@m-a!M3b&qkuDd`rFr7Kv_PE8ZUD|D zmUlmmYCoFnnb)%a98X>M+ZtrM@vrkVbnn4uygD|)x6$(7|CRN{c{$jf2lP(&&)B~7CtePIXF}14 zZ9I?n^vl7{iEUF(AOrgJOaE_j0f1}?{`{`|RM zXE+i#lK;%hOm@zE&VOz|koixM!gqs=Bcs3K%K0k=o*6XsE9NtUhJHnTZpN1kemb&` z%9T+jQ_jEa_ovz*>CO*JWs3Yrh9bW+pLgYa&^%A)kw%_x$#xQ(^Kf<#bKHE!>I9ej zYf;^T;=V4=G%CLp&snlaW}gg*H|DPdf6vZaKIfgc6kBgk-=_CI#OHqA=lKHj$fvz3 z##p~U$NN3`E&C~*d)drW7Zq)DRl`$LWBK=oDTL!Xl*b+oelqV^y}|Iy#S=LHvfjem zSB0~DC;RcY%(CwITeh*zPH(XOlFMIB)Q_~o>|0*C&%CVOLxc4Pe6IM+%T^#?sIy!A z-_HHE|J!uC|J%_!T>d)O>*DbnIrfoxe6b$(e>?uT|6ASD{%;32DRAHA|8}NBk>4Tz zx72_2e~X{-e;cv=w)!jA{cuM{{BVc=)(=;)NRi(i{%=>G@P8Y9(*JGx1%>b5KDVtd z^!jCO>+WFfRq{s)U-u$KJ(u~vHGR(i?R2LrM=vSx8htS!ABX(kCcf(bHmSfX-sJyQ z_G&O+!dXSRvkKm-U8pGIbHU1}{i9%Z9Za`l-_Acx_%?PoP4w6GcY~YoY&@yxdzSe= zoVJDL_)8mt(P$tbEn6?S`@XVgIebZd|94KF`+I+0;|1n5xz*0Qy;}dlUw-98@bd9l zKseu$%l>?a^2j&H9O&wVtvx6A3utcnt?B9Y`S?v=>Gj%`Qon)rC-WAyr zzTc{O2()9QCNGMFJBi>1M_&co*e`nQU@3boX zomPdvv#M}-Q4Rh(TMho3RfCtq@g?tB^xfYaeUvWE@`2Wc;t9s-=16zOtzWg>W_aB>P-A_Iphu z`3?NLJ(2=B6bU~Wi^L(1{sbv&M5Bzu5NNS zo;SVpn^ej3O>`{$jaSL@O>imx#;fG{CORAbCOFssjaJG1&RI$N?Zw$;G0!(2&MwP& zzWH!=SG~5b$;H~aC z+5p>>Ozsu1QSvTe;{Z2+VB;_z)P^Ytey>jV4p_qgH^yNv!*dowF(|vG5F7}V5NaUQ zL1=)`3}H8f4hXM?Bi*6?c!r!Y^lCWNx3@16rtdv{kx1e->(zLyCz|0u$afLn3494J zuO{QMeGv-ROMg>%2J~vGzb_S!uyl~c0Sez2O7yUABT)V*gfR%?5GEiThj0SIB!npl zCn21IaC#xD-#p|;C&EqKC-4^jTlk*fKO2R2{&N4>(lgiJ^?-i@eoJCsC>{$tUGZ2X zk#S;)aAd$)5wV+p5+b!W>4!9`?O5c$Q#L3=tC+Ml3(H{C;Aq%Fr@ zNV1`P@@gg$>g^&z{58+JCB;3T2GQ?i_9q?k(V<_{1hHfy;`B11D$otZT2;dV-w5MY6c)C3t=?Wca z?+ej+O;<9}o$QIz6RKCFS?j63<@$pLP51l8jwpTI*x%hvCd1y}t+8G@PIdM7^#R4V zNA|_Y1iGK;Xz%>Ff35v}grA+pNN0bK)12(-Av_N}4h#(=Ep04kYckx==efjo5+<<3 zKaI@goOrS)?PQWpSARN_>~$i2eaSxZ(dmxGBTi+F^Ptn8jyUOLe_t2j7YU-uS)Q2i` z0FPh%cQ}>?=1sul0eu6VKZ;;FlbQCu2>j>**++ix@7Zsl^2Iv)Ku(8X3epZ;E}TtE zWIo8tbblX6%48ogw}iSfAXvM)d!Wa>)1L4Az4J%@nxA^MePe%&*h&)Jv7Q#v1wC*LuAc?#xr@diH)G_n?rDOwnx%1Nq*2_ zx@;qx^l{y5MHiDzJQ*v>3CH@Vjp6`EE+?FfF#R?%5K9yMM0tiZDN-&>fME9TO#v>d&Tb7~>KJ)b#qD;dYmJm4ys;b_R1%>>j-ep;X5Dr5)24NJ!1cXTl zry!hxa1O#n2v>+B@O_q50-+2-1%w(1^$?mMv_j~B5Qi`T;UI*g5Jn)3LpTB9B!p=Q zGY~F7xC~(yLh-vTYbgW=LKTEM2#pY0AhbbfY1zKH-s>R6of(N9X@#H@0~xBj515m8g?Jj(oCRhZeK8nu=!~a5m5c%M>>a=|;(KHu(--1jEUPz^ia`n0nSl(9 zKTb51b~+=GgcAyfBVl54fZ08PBdZbQXC_Ll5ey9{*-d}fQ@_41^X4Z4jamG7yF!9Dy(lVGP1?2vZPFLpTfJJcLUSu0kkU z33?2KatM_WY9Ta0Xoj#GLKs2{!r)48+4ImlKG&xR#odI~R&GWx`0R#a^u7SW-->+D z7NRtFqP3WNQNAnC_vL6^=YEUr?$71`8#NK-B=O)tT)2k^Gx2L`Tm6(-|CNn+#2`ki}Ns{;W-r973in`iaeX;I%q`i0b!=W`+IuV1fFccAL3Py9si!ZJ2!%n_2=ynrqPSfc z-FO>4T%%eGi^AxU4(+FNFes+IGucbd>F!O&lbPf`YhSWAmU)@F-dP@@WJz{YqRXUu z`T&nqsB14s*;G84j_r#`-(w(K6X6)C23j8jnuJLu;;}@eaM}6eSj)BpgrCGeot>Qy zHNb2=&~)vwOoTX2ZfAyaXVZ=y4jZ4+&i)v6AR+F1PIGPJ8WCafbt!IKZ|_Q{voXw_ zhpwP;mURTnqI3kNoyU992v(J0vn-5Yw?16sP_|EdVQD8oa6o`etShUSR*IK&e^=B2 z)rKfw(A6C1aw)1SgYFmVixA}t7#Fl{&@!0v2FlC(yk)VxNp_Z}Oi#Cw@S|~-7e3pS z9Nv)}(v#AjbP(g|ayY%)<)jRx^tfwg+dLBkeKXs-L?@*@!GngO2qnD zqSX#@;ocXDM~EVZd4fc*2~b9`ysOAEZFdZWN5oQ;)1HmWmbam~SuWF5fU|k$=IwiS zHErI$S(QJKn9LJnASdNqVN6QL!V$M3>1u}~0h1sB4VCTM%Q0YO_orx;K-%)GoD?up ze~Q;HPRim`RF(_f;RY8gm)kC8C5WzR-fW@|(X6?6_t1@kW%~tB%bH1<7^h`mEHUEo zI84g=oHX&Z5=`#lZyHad<-P@z0f)#r57j8qoJ#hcX#8my4V|@BPAz5pbycAJC;HO@ z+xL0}_XXwlrFn_{w3jc4ggn-5;I{^tOi*J=>ERVSg}{U)>v~ ztC_@cC7yn0f4g{LECnB2TlYF@_h3#Vh34VKv#>mZ%z~E+70jBQoY|XWYlf z*E!k1o=t2GWxAqUp=2_15Fe$UJWNiT4BTpx;5S=!>wP z%*Qn*7_u>zTp?L$jo-M>36QExD-+5I;Z(8R}-{n^30O zwH;QI6-`zaS)+nq?aaiOXIa&336-{BG657NZoqui3z`W=cZyAKB$QxwA=cz+ijR|s zM8ecxsWaj*-k~NcA-_Jd>@09dcM|1+`lnErPSJ08*Uhm`4mhOxfVH48 z8D`yVubi_t5=lW5CR5N!tDI=LA1CaNg2@T%GbBNj#OW<*LnOciZD<~Z`4EaA+yvof2)95ehOhv_tq>MMD1opD z!fg;1Lnwu?1j6kQ?triq!krNAf^av4G6?rTxEI1Q2;~so1>rune)t&Wu`GrJxAuMz z++BM;-}|X7=X$1w2D~SU-mZWHfscqvxj597p|b_%yZ&O<^_{ynvd=GNKl7q!Qr6hZ zgQwljMgxXHYPqf+p3F5cbO@kM6u{Q34polSmmJ#BNFS-Ga<-(K`g=o(^s|W_Y!F$| zS><#-$@=DsPJcOU^z>4TFrZZst@;yLYFWm5V?dcN7*{CLdkIUiWc@e}-9ApeA%fy8 zy39(xiD5-L8@IArRrU_IWKs!`c^)5(pyhJE996NdQ$`iYXTP^cPMUHF%t!s@){7ho zBE0BIPMRwTAxS}~4vxPsCteYjp$q5cY#}^036i;WU4DaUw>&6~7zZxL`X*UK;y%wi zLw#wk`$ZBwQRe~S4egHAUHbr#iJP>WN8&&>52|oPL;E6Z>4@=bIJ-^=lkiv~mWhSp zu~*>pKCxVc^AE@9&`08O_YJ|Ba(sHVpXTE~r$)+7||9+e5|D&%HtqGe{ z#h{$Z73uep!I+Q4(0PagKj4s*57AER#3PBGOcWqVbNG0VsC*Q&Ib`bN=d8aU^)I9- zr#-p*(`?9OBE2c9Ezoun;)GC;%^hR7Ptwg#1da#?IWI(C27=qkS-B#;(kt%h7buuM zmoHzQ&$y{9fp1jw3Bf`H;P$LEIasR}$gofQM3`*+6pb z55+RvQo(bh_KD79I^9mdTw5Ud#pOS>&whT4H;ch{S~ktL zoo?Z4r`uEsZp@>ZAs0_V*XcU|BV(L-MzyojEbEo_-ee~;qLBq*$Q}Tp|F*L=BX4|Y zz#I?h)1V6Y@`ZHbJ6_MCsB!z9W=}}8AK-|Aww;@}`FBc%P**GyVneRQnz)@aK)Yyt z>nXyht&E454>ch!X@aKEfnH$QKEfa?=rK1w*c}A7t;tRz!m6Am+QOVs$Uq%-$UeQk zMqn61Q~1*DYz2*|x4)OZ1J8!wXQGGvLj8*`*RlF{#p$B1@Gp~}8NN$h{0{f_WE)@D zt5x~*w#U+2BqHg)(RxeoRxaBkP&q`lxpjgHN7NEv3g)*)?N@xJ!F4pUfNo)%<6g*t zCha2%BeL-b%SX~7ApY!$ZN`(>mE%l8g#Br%e-hVqfwshm8cL`416vUubd9G~#OO&z zx&tl%mo?#P7)2sNXI@FHh14v}_bHwNR+Uc+$8pDjH0|NSp5tle72B%Z-%Qs>b*1|p$hV8nSk`3-SD&%yAzZ}} zN}GwjA-R_g6;Gq&D89;BEN5)d|7-N_<)ior2)CHgeoVA_& z&nNG6&P?s-!Z(9yD4;sAEUCLI=!jcGDMvVKRy%9ft#H<_cUITbFh^Hntsfu*ro&3( ziIaruY5_>3BsMdvhT$a&LYQgm26pqL6j4tg$S*RaC(xBR?!gESE+&l zRIjOCTl4Umb);O9yq6G)>tHq3jV_m*%Ruuc<*t7Jx|$kQxvOt@x#WllR$-^IGfrk! zL|FyhH$(TY11>c+YFyUnaRE((2uqLy9!`TfB5+WnXjk|x_!XL%X06GOe;||0ELTwJ zlU?+yVfnDVY5<3Qg!iSheAC{^I~07G%5<=*T!*LRK(u00h2)r2gdqcb8HMce5l z7x}}9@16Wa2jQFP>gw<=im}2;(9OHz$#lf8`(i&YtS?JCE+}C8=;ntTnO9dd(zTa6 zWs5N{@Y(^%-&yTYf%T}ct}>fMU^^*mWx^(cM?5+GC~;wVtof;_&j!#f$&oLtKf0Mz z*R1LG+C*eX20{m96Uz^(S zUj{tbM1>k(sI37RSg1lERIe%`=eg%81t(&hTPK7aCfuKO6{8?5I)}^>?kdvUm71jJ zjGa(8qWSit^ffm9o!VttXLhl(>MugLvTJT-F5PWe;t`^_W)qRB_!BGBZllWx z#2=P6F}FEqoKRO+B$c6dU2aBJr{9u(lBA|%EFH?6?W?o2bGEO^zC$}b+`GEH4hGd~0`yBvjApS!Zlyzcri1$S4V`5H0HWRts4%olOEFS|cW zkspO)nbP%RL!xziLHc!I9ysmc^4nH9}cjCY2J#f$j~ zIF&29-&ak{<+P6x%Q=%!Tt=96vaHtC>}qGkyG_&p3X3xN6G}Ls-9&mo31Q`uIOXCS z%epfAWkomp@;++|&wz90&TKyXToI^C^_uSYS)|`Wg&gvWojF3~F8Zx&8-93XGxI9Q zhfw}?Ow@n8%j{U6bYY0Nfr_yFyB)Tlb|Cw{p^FI*(v<_^fcu^-3=jcfEdp<#yd#Q-0eLmmx`TA90>16V& z(DYsIht)?9Jf^*vbauM-*T->TZr5TVL==%kz6oN9TX>wXbgagS$bK$!^p#NJ=387B zIniU3e9keL4glaT-IEO=$^Hzr(@;T?@Y7tEmZYgCNeI)!8*si#w&da8;Bi8b^YQx= z1obAcG1t467Pz<_88H7p*H4y3{G(l*E&_Q%V z2tyE#Kp2KF2H`k_DF~+^oPjU{;XH&(5UxTfBK9FZ*CP`@v2O=Y0?E68!4pCEo$Rrr zCVNjj*^|n*r25_+@+0ltw~ArJ9#;NLE53R5w|0^>d7h{Iy}dggO7Hzq%vosfB1^g1 ze%VWp8D(}zW_)kQ@zyiM;hTIBYbn{ae`x>CB(Nz}U$f_w;>7fj5P5ea$f0rwuIx*jbaX+h7%qnYd7Agn6TkiD{+Es zdFSn7<6ApTW{G`vtcLC!Wd$`tM?%D*F_fxy9$ft>brB}8k2#O8uX&W0OO9hDU543M zA6#AF9$Dct!Lix`xHG)`@2y$w$@SOep4dI@u^oJ?7@Y`EMQ=0ID$>VfI92>e8432A zH4|H(^4VGVTr`mV#S81P%ZT)%OG*2na90!Gcvo`wm3hBse~T`a1(d!Q)Mxbjup%EM zr`(e5TFQ116BDY*CAi(6Zij$=M0GEi-R_U1V9cjp8l2n6S2A;?mPqu!m1BS1YP4sr zkUwP|p5(JqT?GJbx{s1EKeZhw^LYjsCCgxDdxDylOebb@&iBCZqoYus^%HpYK~G5b z#$jd_O{OCWQ9DwUithY3b?*@$aa(kEcR%sYx%XzfStuNiktPQve}9NPP6hr^vFsJ48?B z!ZMioAheUQ2)|RQ+zSJAbEu~$5+)}q^@7gCZVJ(T-xq_SsTcb-<(EEJI@gnCQ?fS| z$;8Mu6^AWyh~vXsBca|ZXT$2=4$v9tkpcpa90yJ&f@H;#tedZ`Cd7$mGO6^V4?WZa zeY(H18WKDN01uK2(E1ax%mMOi&4Z}C2cPT%Er5*LRvpwe3ZWIkK?p~Bvo-?XQ~b@< zhi_n|W&JTZPv?kjl|Y9Z|CDW=vDx`Fi|2tZ`i5;i2hS;ZeiWXmU$U()!n0!7w*C;F zGw}QYJP*HVTWjW9mNQ~oAD++dKK-@%?2gMnoX_t5{N8+aPvhN1>~7~x@N9Yj+NX%! z1N&KccEI!Z;5qz$%UXMrWi`|RJ>XgNh-JNY6T3g~bMU+Z&p(4_~ybw^-I?cs@w>jXiEzFWkcJn*A_5XE#9J@T}hm`QF0r zmaQnZtoUY_6Bn~P(|@j*-Ix2Hi`jj&e?<14Jp*)F0PWUnS*zfA=2?)7@SJ|mvVM61 zv+~31Jca7X%`o&w>eWKI1vU^eg9-i?OyA$OsJRgE* z7>3}T@I2pdS)U-kU$Lw|C;Q&uc}oe*1>ji=&(rV>!SezO{rxagZDOgPK*G5z;g=1bqLX419~h0zJPEF!qjog`YU)2e%Z3_y&dQS z@9S@8cNu@_c6PV#$=lhTuzzo)_+B z_YE&BWB04pma#jDV`c0v-cP}E8lGQ)=aGAX7G>=I+2VWHeXA?sc@&;)_prN%NA6*F zzkVH_^|#yBzuv>{cCNmc-9x+yo)?w@4en+4`i|bq?$P}^JO`HB)pjMtU3`&~a!&dylIA@r$snZ^1K~vaSDn zAG`nf?sv0$eRq=cV25n$3-GM?h;97=JUifdoty)E(6*|`Ij7oJr>ucGD9FG_8z2A-o^Y-=w(&p&Tl$KY86&o98Ue4B0kCOl^#{xm#` z0sp^%XUjUE89b~0m>p`C`5Vi64?G9{9Qq?X&;E&JwZpR(o*8(ifZq!u3elL_0pAD-vo z_eOZe;rSdqC*c`|XC1_U9G<6PmiU|SECL<#zr*tgwErb|*1+@56+oBY0KS1|&9B?m zet6D)(YA)+IS9YM1kbbZ`>XK03cvplo+TO|X>CM9L4tO4RY%2rLlkof$JWC$3tuK?a_1;`2Jmz z9)AA-o|oZy2RRcCzCTn+&iu2jXW*HthJFCg;*~ZXd+RH0>lfg8`9a_#cs4`-{t9`1 z+Ooa@&&&zS`crad96bLHo)z%C>wVC_C!w9;dEq}mzlG-nJa@qJ%CCTYAm^fu+1Ag( z^CbL!gPf@b{fC^LRu=*K!n3N~wl2W44BEMJ6_N9{^(Z{Y6SlPto^{Z#Lh!7Q+14wo z*m-|%kl)V(?curfV=x}U^TJNc`XNa-WLbB>7=IMTiCXgfsAW9^&yyd4aUY&3`2Gew zTY=vH37)eH`8k4%*uA?Q@O(Ep8*vfSX~_k)$ z-^4bC?HIPdVe>-KJr&zKuze8Q25gsN(@fY$Mp7$M!0=e_%TmI`(_9t;co|w#%?JU~9wnacuWsdlcJu zv5jNGe+bFgPY%gP*U9EzXfGru@1=udc zwgX!Owtd(h!S)okm$3a6+kdc~2$_C1wp46uuoYq3zJ}xb9?$jI`mqgQ8^rcQY|mi( zCAQyVI{|v{`?0OVR)FnNY*%Bu7TYb@9>n(luzerfIJRTh{*CPv=*g+nm9Z6IE628z z`$hCRQPRf6`Z!Eb*4jcB9#wAr$F`+#aE+{B40}8!T-wZ2urpPlX2LS=Gou@{RB z%a~K!-oPueS7j|-VQU6Q1bNnKWb3Ag`$UF$UUtsXd3O1x<#$`V)}3{k*^)N4w(OS0 z7X|sP;+yK+r8(J|yp++d3h*3TQu!^SRUF(Bp6;^{%X!>v%^JK_Nt@Om3rz5j4D-CT ztFkgK+eXh&?vNWO^el_OnQOD~W;#&gE80`%3Ek)M)!}zqIwcr%XRXcDS)1^{&ideq z2sSix`3#)9ZvDEoJl8FN>y~{F3)Jd0aONtO%>o>0_n92=gooPp@%Ec}_Y73VtelLE zd)gviad`|*U7v*;w{nSCeeqiaxfBs}J03R#+-9YCcTI;!W6d6pLTVV=Q&-=%v$e_R z%%nEdFH6uKJy&+t>Z}Zkk>I_<0yrT%GczO7Rf%U=1WsJDel1Siq1FF_J4^$*_~IGs z)@9)gx#w9b9^v8IKteC2Ij+oEz1CF&lTu~%t@w2mJX>J!zZ}BF>sN(cTwcH1xBR-0 z*e18{^E4EPtWg;F>@hqRw_lUBKJ4~xN~#twTsan(tX{{Y3LNuT0DL>9&2)tcU&gGj)d&;;2QVb+tq-S?D21P(@|H5 z%2>5F6L+HB+hjn>yXB*#9fXH= z23K8ca_&rmhGblvvu0b{ft%XGeZuCKjF%;I~#V{ zA`!TFHDpf7o+dw+mO_;b?%~iHNJOsZ?PgkY0j#h9XKgjt@8R7fC$i9DWo1OJq!k%% z+t_633}K|H2p;=B1b0}EmL{<77JegS*InoCkdc*@jq*@X9{IA75jc^J2$%PEPc1P- zoo$7vJ@q$ec_dq4Q~~_KZB}QaXi2AtylmxIDj6AB*=x|uUsB?-$$-dpIW98%QI|zz zWTBl5oLJ)vJu496DLG50upJBC$KfnUiI6)low3yLW#z0x6NT7|cT^R)?`1;waX3dm zfhgy!EM&KhxkwR*)7EDNPuuQRQwS_Tuu@!GL3=A}V#eysRS17 zeyEji70($eobpfS;hYcccY%!6t5&1p4BKrP~Ja;KFV|Dgg=rfl^3YBFMI5CIHafsLm z-RI#o?l1&6^vEiwyLuwSJS`{V1D+3fT6}cu%)hG;%ffYm=K}1lg&_Q(=Y!mXZ($~!~8(=JFq+CL|2<<#mJU+ zzpowrXmmDxexz46`tk0k%(1lY?O7I4ThbO%)f$2`92#ui19h4DX45@ux5;a-vwPq- zoMz*1ExN9~rr8tR*;&(8zpZ{xn;UxdwE2;V__NBLzh-U7Gr2P6i41G1y!~QBu;986 zP2JOObf1=NfV@^1g)8_1dL3R9h|*1$$9pWDvsdGp@azSU00t@|T8Mk(M-_XOVES(!RThN`l5e@URT_7>5&C>uI2jyf-V(geaoO`F?ymo?Szq&PHYtjT1TIe4DD zRC*%AwadDU=IRnR$jU^EfD5EbKqK6;#}g66OcyGt>s@EKewTY&Q}nm&srN*N z^?h)gXLp+^vTM-dhNVm4lKYF5V{r+~WWkO|^TdiV5HhWM-HvlrroEQtIL6r3a(vGrJgQyHZ(T} zXALilXxgSHS(lxetw+(<8DJ6saqd{gn)OJ;;5%7| zKQi3X(16_Wl(n|_Ha68a*SWWKu8DRcxqOKB)a+JYe$DzUWZtI8?#Z%ZEHfVtj*5R&zIG-x79lbQ(=LOwQE-C zb+*>GweJp|5fZ{FPT_Zv^?pxq2L~)EO4@j4$sQWMJ&|Fa2Bl$pX+$n8i@=HNvhBp> z0`V=PanX9zm9og{zbqnS?b@|Sin8#U91+Ax>mdFRTfmPa0$z1I%OWz?u2Ww@Pn6&8 z_qWJu93GLeHiz}6EW}b8y3fPec6j-_y+t%HTJPQjbtUjbffL(2Zx!#mA<+F_>kJ+F z7e}PfvWSeeuHka=x=Vq{;T58q3d~TyE;Gwnp6zp(68k3Gvpl8^#Z}thx=b3;wyEtk zAaboDBg{j~n&5S8MhaY$1ToqOH6<+2wyz*)e%cq%Le^!i$qBnTvm+vilh&f-ZwqV9 z!$TSCvN@fg6je=k7*E^Ht=gYGsfnuVvDdbvg$62$jHgc4xG5We!N9AF5)18StSE(3LUcJ zIqUK2jEbR3Q~9|BWI1X z@ofujippBr{J~b@zZ}XyHxBiCV^hZ!!QMewfF||e7-Qh^d9PsLvYJ+3c}p-D^k$Qh zYqZfroNKcNf_L%Alu%pP0(GJlPrI|G4F$u+x>mDMCqNQ} z{Y=2ex2GlWBRicDi6_cF5R^F1>i}Ci><49ur-{4Pn&ZG_^g3Zj+7K@&9E{o)8li!zu%B&|jg8GlSp3GY9)Z?r_SAaq@wvqR3TN`k&$d7!nv z#HQV=Ku|O0(4#{3c>p6(M$6=5)=jpchVBZ3J3HWOHB2iH-q1Zwu%E+2tTXj>;i0PF z`Hb7LhtKArfU_pQZuc(NrLq&0+YQ?SE_>bZU_;~!d8oUfRd23^2&5QVdKUd$X?EfA z9#Ks#*C^d<2c7eaU|Uq*;A~LNex>7q-ce}K^-8XDGgKm@tJo0Inz@1I<@%in+9@k^ zl$@IhO`x!`nB8^TxHx-Q2#uhpA=q@fJw|CQYd0}ya}BL|%iShIjF#23)<7zm&jmYi zx!=Vt8@H8isB$J4ysO^ErCSt&(H&GHJ3-?7b2B%4gGmq>#sp*ciS${3O1FM zbfx9?i_rNQ!R|{)yR&6aK<%vy?gx&P*X(LjrCQ#TvXWUAQTc*lNWXf|rS0{tdkaw7 zngSMS7sKk%?02RG6Li>fLZsD>=o0pb+I>Dmwk^H)C2ejjQuWwetC~mEl0utxGJ#>f? z=ywNoc$2pcriOZ|0?h^Mo-lq$>wFVD5!QfL2)aE7;WO8z?sWOCmS!m-qUt5l0R??g{SVti4}p^ zWrM3th4l?PLY^UF#CC;f8wF{5>k9+jRmsJMh!a;Bb`Cibz7FIiHLcgG;o8s^Os*Yh zNE;xLTkCh&B-sY{D(;0)Z?#j{biH<<2_o$`mTuWlUR7FDRi#)Y9Y?=*qJsjkow7dp zHV)1N-VrAtrNagjf>!Q~pt*Y~rrsYICH6RxVe?C6R;Cr%nUb9qc~ijDyqRV@)>Nme zXbzK`6;Z#Zj%T}3DcylT5*#EC+_9uh<*mdul00)$b4zVaGm7G#wsLw)^LuvK%Y|B| zO4>HoTw8CdHty$|Sg7swYQ?BusRFpa&QuiKbMyGDt6*uxF*YMMeVk!}()LL0U8 zpH1UiF%(FY?a)jusa;fLxQz1wSv1{FeoITU_UtjrYLVZzpWkp9ClGU(Bd{MB(A!fN z$j+r%j_$GDp1W)Kvy{~7NzibIPQeVOz9~H%f^i4joUh>A1Iw#m-Z8D67HcQF1Fqa; zFuR-x;-E}ZbI2?_U2VrU*Vk|XQ=6J(GwF6 zlTD?w!r4951n93~RR2wXrN$q4BWf@Z0)?8)${!2lshzM?D)bEQ(e)JkCYjnT9$YsL z8?%jwT?%uUj5RFm=mt6+q(LDgxaV16&!#{{TvFEF)@YHzvH*=WH0Kn-j_%TBMXfb$ z^&w$BxVvg8a;XSzt>3kw4t+z{Ep2fKPG>_9* zo{xJTckkET z7ChT>RW)VYx5l?=H*MJMZ=U+f!XH#FE)1)9_st*#vX^WA-ml`$YZcYM@xb2o{n*K& z#~xKY~s3!v5Cqt%bhtD1ntARaW2MnMq%A+(+LMc^v4YnD`LL{map_@qKIhN#<`5 z^7UU!LCu$b;8Zan2{wuy z6X!%-ucwS$KKbk1lB$WOZ%A47-)7}m7{5k{NxN(FMdfLpjI_dnGc&ZBFT@uO)k|hp zMch_5o~+pAojx_zl)CTe@hPCn|FadpbXC%eoSQ|H?2-Y0nfz3@KNW6Mk{y{S{=E%h_@^W3w}1b}yKmiquaC5wC_#>LnxDYH zA_a1Fw&NpA^P`tQ6L8MOsYG-7USnM_&UJXG=kd#uGXZ(>ZHxlpPfYjOQ8{2reQiG_xZR|& zD)FT8&s)55ua6l#-P%0+c+AUASNhH^!v#LmlA*5wgr|uY_#B9)?leH_JjC6(^IzWd>EnN@tD7}7Z{IP2H!JJm z%$NLTes2Vp7+m=xo&Wevu)A5Q`n`##f4xPftW(GXfkDL{BK4aub@#5D3EfS!ia!|B zRmz?%2cLGwT8kaZ{`7w~?*IEfEnexz=X+;JXLNUaHBX!}J5_VamcL~ZYczAA75_o& zzkf~>55@2JbQ-2a_~zvXnGAh)IP>7Gb>1tdsyH8&y6HIP>f~M~%|njiW+BTD;;2Li9W)<`E!+e=!g0H2G`$ft36xdR_F@wjXwS9Zm3%EhX+YF;>UVfcuZc<=jaMEpH}t7JgA4mn{e6Lj_)%>TmeOcd*y zvd7}nn;{Q@cF_`h%0XpxfOv86sq40>;-|0icua78J#{96e+Tj8^Noqa%l!6({ugl- zHoR_;X;^pyMF5iK@gK%%is1YP5J7n&uX4h!?4{YON40)N(MLRPzv6kaeP7hh%Fp2r zplEvNam9nAwg~nz0~S?+)WviJ(oX1K*cIoF_}c!-YVW8-MN^A*OtaPp zJ}XbY8KaAzcgDYlY(`BP&`+GTsEOZS*iLTn5Ia5-)o}vi_;If_?8A@w*MSS0XmoV! zNY})~BY$}##sIcSQVNO)Xj=RtgbG~I zjoEyW-(7@Et=w*fuH1_gz-~2zwtAqgl3Do6PKiR~s=tQ>J6DFYz<(NZF%C7DS7~V{ zF8iI0_|TAm%{>=spO=?}IHVKE5BL5s0taolQ3hyF#X!BeZox~XiS1UgUljCyr98w~ zJ~Nqmhuuzp-NLtpzVtoC=%-SfC-s<$QEPYNXj!kp#Qi;JUcw^1-zh27zBm`@%A*C4zhg+q(aF0LIi^tF6#g)) zDdSp6vDe;ZtE(=XBb)K}Up|#SS2LpCv-U`our7R9u8`+tt@ez2_uMDVrBb71BlWRT zy4i{P!|8Y3(xDOQj#Y@(5WKlVvC*#q$f+UAA;_$(^VK91-&0D_cg1(K_xw+WDQ`c&b+g z^qDh7?!kJC8}(-6Pl4z`T?*oumX4y+9x*-Uyna29zd^K4vIyq!-9i?>kcZg(hr6oWW#(`g8U2AH?GHe z%=B+cWyo_)e{#wo*2xr$;2vL;Osb4t{+TbB>Ux@04AQ9JaeevVL#T?RU~jNGPe{Ps zUUc9sI{(U2zF_+KxbJXD+{h*=^s1LdhP-mDM-yBcx2Xk*3aposB4n1x9IOd zRuw$%FDr)%u1P9}JlZtx^+pQjlICkC;YzsrYeu z2cOf&z>=yOhv!4S-{l(;fc3)Fac5{or&&oL_ljE{x}FZ2#G%k3huhL0dwpS;??pq8 zH6*p1zZ118heIb~6={QTAnwAI`91lSp9O+&#vx4I6BD|0mzdEOG*x-a6XEQODJD&1 zFYpcZYe?$T7%)E!g7mMTm;KZNNuo~XRthy)_F@H`y)537j_muW+R@()90`>ertS^D-u{pT zbH|jCj0>MoIIdzEsqL0oA>O+0JR%c81r>?jFY7csaiN;S)F&aj=E=D63;KbWN>b#0 z^o#lPbVqJPUiB<_Dp+!-vX1 z;#jCd+H~ZGru^Sf7pMmA2mCSaZJ5Yf9q4Pttu?(f;B~ikT>TF5w2J=YgV1MC$oJwE za$$NcLmyCxTRX|m9)dd`Ts;g7NigS|xLX&|>39(OV))rx(65S8-CZvK6+g&7j>AUap12#kXLMDxw_rA`2$b|iB~V+2K|o2423!UrRU`(yWcy>jkv1~;O*{2B|7#q|Iwdo`c~OfwZV zL~6l)_pV%X3OzKBs{gS|`gfpnJ9Zczk2|A-g?L!>kWRy&V9!rg5;#{(S;?5+UqdH` zx7W2j7edcsXeeKcA0##S`g{inP)T_9az3PDmvsFw^hHFWMv(3cu=oO7t8y_^0doMw z!c%bdIzLdF@&u!+hpHjk7Dj}cK#IjESTdbPw+;bY^!^l>_p#_BJ%=xFh1>xLEFcRX z1nataM6+k%*_>x_6nNuO8C-C{5vuOF9QqJL3)HM!4z=Bw(hG6gkO+D(S0EIk`_CgL zWNI?l!@_7qfQV48TnY8U(CRda{|l+gyH`UU;-sYh6|InC`_fTf7TBoF1;dayi(aAt zG9(@T9>=9S=o}BaRhj7da_=boL*?~%>X0Fju4JjF%wZPDq%tvdp#6m#XU{az@1?5A zJ6x#l{t9IsNF;?04R5#2!#_TdANbBhXnL-OmSOfFB+1|^i{#agxG}vYhvM|X^R?>o zYoWH7XBi;-%F?ho7zSR8NfR zf8pJ@5xtDPLXAk>SmLH?!GlP&`r)GxL21_o^^pIt`Yv?Y5Miu7o;Fl~i>xF>Eo&k$ z1d>eGh!Sx#M>gmDArnxb5jF7MqYmDW`=n10-7N}Xl!h}USKJVLZmn4O@Po=lX4ex7 z;^P%jRtX5{DS`AHh5wGQr$?-$BGQSov@UxKl42qqTv-$nt@{CmQ6g^Z=LwRcSVlvS z4X{rli$I^TO>5#qQP8$x3Q&GG)D}ziUl~GNOC`>`TC7J|O0nO8T0A9w)D>pO{5}6} z997sD^A6->>A4pgZOONH7a37Ni_xw1)R~9laSH~^l=2k;WI8qQdg};$kp`bW%_;-+ z5)C%C20-BACEN`#jTp0X6{#!sqj5zOc@5uq7q!<5 zk9D^$Dr5z#jd-F<^R%}#qTNL{4z)qTj=r=h%7zgq=9T2xp}SZx*N?H4ZDbJV3@(OE zq9d^f=k1TW^n7=2`E^8rz&Nm+`f$YI`14+vdkW8&Q%~rWX5((SB=I8G4EF1Rqf5#$ zi7(V&&5=h$1n@LR|CHlYV6{J&v=recw*YKP_#8fg+d^t`k-Gyu#%J&L+`l!($=>g| z?qwK7r1{?w5eYm4)$)t46@LVmm;$&rPMh8YOgugMXH!(=i{Xh0;xe;&a`9-YYiqx- zBA%O9pG|1CmjD z>sE7X&x<7B^hG`g{PUeQw6NaCMk?5T`#Di9HbXh?x+G%x;FWShcM`x>+PUf1=?Qrk zy1D(?cqsT~n*z6p5&q_JHr6-g(#!`PDb$3p8Qws2&Ob#?Eqx)zZ}X_r59+wz8Q#?| z0yyjMM7^h+VgdcUr#_lJvr42ye*Mz3*I_sFT)(H!P!K`&DdBn9Hqn#OVMoP3t`jzh zoU2X(IBofC^6YZ89{HYXlOn_M#KI|$>ZAT5UqSiRX_0cnGYZ+QYTRKLRMMZ>

)a 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

    vMayDt9B4(DHd2p*ONsn)I!_lT{dtLd_e){>| z8&jRoJmWy=*4xnsYJEWiXEJv;cnOQLqtgW7pj2kV$#nsR^?HwS&K1*GeMrYXwV5u+1~Od3<;B# z&$*8d77dmqb9AeLGR{h!M4p4@sKTAj4*LC_J?I^F`6xEzcV9@HDZ!dVWTF%8Ig?-i zYy`r4Xig4lP~s5HIi|2uEy`RTojw>SZ^7o71!hdEbQ(x4b>x<o0p_nXaN zlqnS^vpbwbflimBEdBCW;7-T=gQ0NJI{$QeOD`pJ4)Ntybj+3@X>C%ktddC1L?HNr zolHrBB$7K?kYkPJrywV`dTLK z@!`2qjyl1oR5sr0pkmO~I}y{i%}~#z*)7(dCiFPBQBpfG^Dc_U2Mjgi7{WE6RB81xe2IgI+%+;LghpL<&+vbxqr?+n#AV=Etcd3Wut`f?2}qtSau*pDOVGmGs=< za6ZrLrye4SeohI3C=o5{h3K6G(TUzpJ-r7BLQbzoI5oQH93fhQ1j*@62syo;&hNgT z-yi$F^S-;!>^}4C%+Ait?y%Rq2-94yyfAhaB9mE~Mj4&B4oG(xod{Oyo>VsKo>Z`` zh1N9(pHTU3y|6m$HCa4GZF1nRHBpqNo{U&X@E**bDGIlRi%po4A71v4lbtXOu}&-I zOO8LOKxDUn+ZwcHfmI@A@QayD^W*k!7s<(+e2y_}uo2sh$Y3sY6r3GAWX-GeKBV+X z&|;gKRoaEq8=XyrKTD_5xcMdk>*PHwy-<2H@0WJrb$|~1>vh0#GA~ePfU21b?3E_> z$&XlLIoTM5TgF1C6Gs_7G@`CV__sdn8V?KgS0Xe%(h2lR!6!?MvI zB|xJ}7v!FJHM+X}`E^VGAU7DL$R_?i<`^vrn3QrCej0HF6r(LgwNR!mb4XrJ*BwO_ zeCM-o)w}LeRyf&EO)611nO}#D>tXuqQJJpV#4;kqhfhz?EOkZ^?%$=u3oh(6{)PG! zK69yW+)l!^q1Ml-=C)?K^QtmZte>j-$_&ijw%nhxRZC63&sc zYJ&Ror7qlzad?kARLtU0tJrCmIcA1I22UeHt5-eGZ-Dk280>Cyju+85V5Ig5CCC z+H5Qqst?F+6|fw{m#;Otk)f}w4>%ZbiEqI} z8)?Zdm&g?3y{xP4C6V)4d$z~G7`9gH1(Ehwurze?eEEV<*^-bv^3V~>#|^&bnphO- z=Gnbp$6Bt}^ZY_%^wFf=W3Lb4hJlI4DSymMVaCf`-6dQ1v5efU>L}^p#N&4(=J>IQ zjW5|*NYH+vcr&*4s!>z8;Ojf+igwb7EM%s zxkNcour2taNRL(kO2z(F z)?C^j;n{0V{BDZa($$G9DIPMO!cHPHuJ8+qbNo51q3;eGO}+;nTIp1%#W~GMA9;UP z9%-#+*KD`M^{>zW>hW6=mbFFixat9yYjJ-PyeFA_d~HnCJ3z;_h6{dN}b5;`ILtk@5Dt(GrS6u^7H;OgX~$SG_0rr{6|-q}rO7u(bQ zERH9Utb?9=j}dNQMfTUO_VU9JyGg!ZyT=-dp~2pEe_hvwDj&W#|==}FId`LS-K)7lXGB|VWaI@xUZX?(+YS2uxltQ;mp>d zweu_eV8*ghZE(2;CrG$?KRkO2PHP02;BQn`VL5k~Fx#6m~?ta7v`91RCy*)lWYb!e71zeQkYX#Oc zMZvPjl4(8#!H~j;$)a8xxl7{sQxxa&WKF7i$ zkIg&wWoAiN_dZyPu7GAqeeu8#+MA3$|CH?*{B%xzAAJ=bz6+YAm`bfUR(wTsC>yM( zjw)eCWEJewsYjF;X@`b8E%0^Pvfw|k81LOqhq@#2IpWEQe@yC%B}U+DUl8%~#9j*I z0-FygK7?&w?)mY60{Y!Ru$Szy{y%vhwDzioYtFY#{-j>-*QOL<26|L)4&(w^+egH}t;INk`CCrw-C~^=kh7C{NVr13Y z%j#~~3TItLS>t+3PQQ7w9z`;pMsCNn(+=x~tP$p^9Yk(N*@IE$+2KmKH*Wy1^-M#1 z2X|0BozFJ1Uf;4xybsd|=p7Lm8BYyX4~WB0UC!hU7#lncdYrl)ofiMvDf?+@(7h4b z(ec!qqHXFi)#VtL365a>qnJUC^e|hZQmuUZK`g+*DLX_T*Y_155so-a-Hw^1bph#* zD>qWzSLEsLJmkdA_QaPe!sL0v&N0f3BGd{z-9i62lUq~dd9H&tImggBA?mm{+3^n6 z>cOS~Z~Ri1bN0Ys zmLvrcS8AoSymDOKA}91%m<)IWlBvMeU2{Q?b-7V{4fq$Ra`8J#ZMtr6BRJO`2Krzn zfsY%l8c~~8LNU`pSlvCUI4(|C`$anuZoFD4lTwASbomr+e4)>c{#UB7 zU-4UWJJ|RdyQGH1XDO&U_|c$-Z2gW0Ccdg6%jzLj1P!*W!nRCeoyXs;&zrv+jK zIgC2@LbR_9ZtY!%E|acPDixpZXAe@ua~MVVu8Exp@_~#{J{Nev31p=B7+aqmYI;4= z+`p-~4*D%^wPc&TdY*LC&oZ$k_vxT>soz!Y66luiF(6Gj>2*e=j*>d~mo&MoL!=%d z=`!e-8t$Qq`V=s8>X1A|6Lak5OI?bBj;=HQ$*Q9`asS7n)6&2!*Z2D*D6auxD zFSmx%ucOA+uWWHcs58zUdffC)60_6NVi28)^Ir|xYYEXW>X?n=)|FducRd>SQe_=Z^U`PCVa|y+NAs+s(-J-t|F#FxE8V^Ljb?SH=e?xnysWty2R*6Z zOeisr1Vp!(j)u)yJlv~mb=s_Vnsb^^F&jQ;%lmuh*T}Q?w2Ph->hnZHZ+9D=klQ8K zIgKeh#2etou%btiJN?SIZR^iYigptU{ODv)YTNP}hk*c02u^po74~u4t?_f>nPaV&&fvY7SDU zS_wnf>c@dvP70i1D{L8c&Zbj;X)fP?XC2j_GvgPR+3oM)n&;fuN($Qe1*1>TXl{Z& zB46ip;N20zM_`M%n6HHj)+_H@V1k_3Etq`bkC!#Oj6*UW3HKjk<{GX z)6l*4Q@M@yU5=a&DI54S2-p6*VG|7pl^}e<&72vBc!X93up%}M@I{OjK`E*5kJ24h zKX2pt)gre2-WLD5?gArc8L=OQFYmUDG=pu}*PMU#$!=sJG)t07Lr~u6_bb#S?EN`_ zr*Tcx+fTCb(V9_uVpqG&Jp=W^tk!mfmv>@-Tws=M>3vQz%Yg+-%h)~98Gq5%XM8Jy z)B)`I9yaK{9J0NBXl7sVdnwxbBg{6@;#af3_N)l1j>IO4ZTSR$0(Mm);+ZhIPJ#fA z0ij4;?lRy~3MU!0AFCKe(R>0V@-p&IYD89)IJFRuun85!n`pX7(x&J5eOvJKOzY+Q&?2PukOJlM?Oq=*o&%p;UnCw)5j z^T;iEX~>@yV*Cb3eL=*K2N*Mu1>xBvQiBN};~q^d;Yx~80<>TP>8@za`ii1OFM$E! zPtN{Gz2JR18x$a(U>S!2W7I%w1m7(|@z;@5@-qO_-9D>cf6ynAtDfjS!9+tr0@k}K z@x}xSYY>t=3d9NcqfdMcNQm^R0H8&H+P4w2v3kySpRjNj65B-~asak_7mqgncmHo* zk<5yt-W_`XFRG-)k_C}9panqNa*Ri$$X2v^RxV-0Y9DWJ%H7zxX<|E*hwvv$YVRn< z4fZ)%$j6$Ksi#0}ALShbTcSA_+>3QQ0Z^-{@6KX-_o z7q*M6FUbB)hvQkO1oKo2h}Fzu3dy%!;;1pz|0CghfFOb5fsKsK(pPU&GSjs_!`{Cj zA;PY{n6HuZ@(K8a$5S9}V$i+~C#F7bpOx$kK>CyOVXuf>Hi=3gBaJV?F99o1r+frsVY?N!>@|(S;Y;q`r{IEF(6O z_I&{GdX9NHr4?DupAdYXK==eBTG08$kQ9Z#6Abh;^bNXpO{t!o$n6!0=~ka`ZzmpA z+kDI=!D(EQ z_hh|^@}{(HeKG!#twB&hn;3u_p}CwX%~@M?c7+tdt_j`I)ze;LkOkFQ8~{zwv)U)n zt0+KDQ%Q35B}S0IspefjW$%=nDKM}WAihJhE-2 z(M~EF;EfcwK}3rvr@6>7f{gbU#!8zby&f_NjbxjGSLkU22*N*ttEPd0h7S02EcXNJ z??H0_<^)+GP#O&Ep31Ou>r6e;!8r_Q=^7GNYiG1rdWXp`H#7|p`^}aj3NB6Yn2=1)7lKvdg=-qnK zCBQ(vL``F`?@)DhRzt#ZE(s`9{o!)gj~a>q47J$!@iPf&kd{P>JWLQsCb$`mi6>k( zpewQeC+Rjxm>vB7W=LWd!aFfH1AR^W#C%N7t;<(5@G-;MnwelxuL(T#s(LzgQ%2(3 z9N51wo}jvWjr^{=kPXl(h|1&4@l{g}{HlntUaxP`%@87vu@^JFYfP-L3Q;kXC6W$@g7De(qs7mAbep8tD8% zgbVHnO6fon=x`68=!i=`%gDsG!|{Qd^n`(usp5P3?@RqqYC3dN38iMCJkW6-DE59b z@jN}6qHmy5ars&P2lxiN1WVdy&AucFUnk2=2sZ^Wsc_|`J4Dr|6EB^RfaiD6)8?na zaNr=1w?Tnte9LP!IPl|^${mwm@nISOP8W=fS*Y3Ku``z;nbHEa-gT2<+^gtoo)4ZJ`=8pzQ+z#FGGMPa)3wk+wLej6H)OI1STEG14NahEkgfdrdC6qBM^g)1v7@|!g zk2^oJ|8>un^AGYfiiNqGd)Hy#3f%}+SU>-MwtWC)%s)X!6aS7xka|@Bd22Eh-PW7i zcxuTS9Yf%4dcz(1e{&kL=*-IT$u6`K<(zYaS^&sHazBc#y80H$c?QUp!dHGvmGz zd70aqKVTrjo-T*3SRA>7*QE~A8pMBRjLZ0$5@Q9X1o``*aiAFec-{V*6w3fwiYl)auFww_C z$KeI3m>3G6Wo4XpVzjBS&Y}FPLt^s_6vOvxR+dQTJ~`mawkJqh}(n;satAm&wde zOP3=w_dJf1{FuiH&+$9VHDk+l8Dn42Hafp_@@7L8qgJt8t8i{py>&o-7a25_hmV!X z&iyx0k3wSq^#FL9RV=~ckfPO<8>+hwl6dVol33JSF&?9O_*3qEN|TcA=J&;0Ud8hMy02 zPR^KZ$h(&62D56gJI2WK&2yW(Bn%&{=G0jighydQdMA^-q=4o%>8Ovhg{3iKBa^fH z7k@OKGNpau`>~_-lpj7px6S<{xbZ@DLNK9F$~8^hhwZ~?z~|cmu4ITuRSEPWOsGuKCh4Foe+ex(UOQt zPPh%Tz+=l<1Ca)=PNCG9peG}z!TZA$)XEoX^J&c)LAm%K_kHxSbxex*6H9<}?pi7b zFjoVkerIo8s8plZ7`w^?V5n>e(oAGa`mC;;7HXvsy1Cy^tE1_L7?=arI z*XL|>{g4v>e;wSG23Xas8*YTM7r`it)HBhn8&$mXFG+yOJf*qxlNy(Xc>gqoY%~2- z@j9tQ$V6d(6bFkHhSq7b-5aRrfYq)1%=B$QC7eV@Uh2cN80na|+1j*;>&U!zJ>}Cy#9wj1*x;Y6AZ$)3T3CLcnAUo)>@ZToSL2-x4l>V) z;%iIBOcTbP^HmcjDtoLz#+wXU29(pkx>4)IU&X?+K(G1d(S_~X1z}&^G}9uavMk@D z*$v?wuKzL@?+Q;FLI+~Ao-^-0__PNcy&GGQ5yw06EW@G$2#2;=N`Hr!J)c0%;H_*C z4BE468%B*Z$7ug|hH)rl1X~Gex31F6n=gu!%61)5l>BN&r6}vhXZ>LOR@x>pdXh6l zNSj_eYj)E6z3&f;S0k!_PMeF8#N44796?zDFYPUYjF80X%@dFUw(&5x1vLhXjq$015axNiSNV~2n{x3`PgZm$k-!az@(+d@D*z}p6Q(q>trF;3Y zD(uLeO&e0id6gDdZuk(r;b?%Fnk9Z<+jaAOU%dvKutzkM47JlSdqa}~7vO z4Nb)#(VKiNK8Mc#Ju%FB^AB0c{PNyB`TK&wv3$QVKhc0Ce4&o@>drKK>pV>GCCbXv zitE)fheAg84)VzOucAzbxDR3e&BCF~^eLhMMMzfH#qqjxw)1++IEGY` zMa1S?Xd8n)({=|;h;`)j&bG1sd-v7p+&I$BVX{kjOhZl|#N`rO-VjmUfqYw$RV$ZV zppjAF$)4MG|3oMD!Ov!0M>T;2wWjL1?wl>EjyGMQoVWWK zwg)q9Lj1{ZF*Dt+&bRU!nHZSmJi~gRLmZBnWa|DqUd6W5h}g{{7X#ToF#(>>i$-zH z(U0RXQtu=>f3Xo!1yS_HEmna|hpF!7``kaYAidO`83TpyNu6=Kg~{9QES)ugt#Y(P zBz_F@H|PhuF!nXWpWw~`!E}3W^=`>y-?fR`~XNlo_OW#pLm^+$lnL>VZl#b5p(aYO=tm{6Z(hY zEQoSC=qZ0tk52vBq)PpskBFYM!e;lvH>0BQDahKxQG$pk54OyhU*Gk*=B1hT63A2X z5mR&4U|*k#jXENK$-p0`#Y_op*RLvi=-g)qBq8RP;&w=s8GW{Bka_oo3V&Q#3B=-_ z#N8KuKe|NZZBIwOmNfgIzOh<1mGmT03i$olsc3k(6UG+7U z+*^i{*HWCG@#}-E6U%;YDJ1t}t!po4)eP8=7a-rmA6{G(lvIN>Q=X|%QnGYlkM(I5n8o`C zT^o9+(_2PJ#OzKyd;!8EW!;p|b85gji>UIB4d!ZA=_|LAC*2Hwnh=a@7w}&Rx=e4G zFoK*==1!vNwy*MSKlzb}s_O(Hsiu*#Oj{2IX!y?)J3J+i_y{{<1nFzS^eMj_a42%RV_$~UkAEc3&B4WXyX00CzX(W! zO+86M_^;!X>Lzh*_qZdz1UB09eIW zcTRVDL5A+X{zG?ChY~7{HW?4x1NQVBTqntvMmsNJ)o6|91QK{|c=Lj<==_8dqXSC9 zX#qQeu<=^wYArMq0-uBQA?;Vs#6OoRT?Mob#GgTS2bwm=-Zlt&#+FM-@U1!rt*Op& zxf}aFu2s7fN*|**RMAg*m)5l!88gYX-A!M}Rj|6#Es@^c8gmFA*cRqTMPqTF>pN*w z_>ymu5XDg zuCMBkm@oB}OrIkFb{$0{LR{|4Mw2O&@Vo5}mAw&CN&dxCy#B)L^-+0sZ0VRPAuFLl zj&xIwU8T@2(><)Jk`Y(xUDz2?pi^3O0gXJ=08e%78T^?ts2e2QDHL1LKZskMspgpExEumj@H>i5i7~Cs8?HTNe>@|u5IM6K47d!lV!>imedb_{&neq3vHL#?q{|g+ zb(0%GGbf)`*wI9N@ZuL_8BVmq68U3+qkMx2F^a|0s6cLAWoB>SU^!K^z2ZSR6=@0f%d_^PuEY!9 zuW3&VU;++pr`iEOSBsu7K-;E_o=0`$07s>tL7?BJj*LX~dixN;{|R>{R(7bE-xY;O zyHdeI-wX?YcDRx?u-|AkNf(-EeiK&f^azt@e14!qT}*Aces3&i+ceAeD0?BP*^H@k z&a~m-=+uJU>qjMNSe>^vIy8KHej{$;#sxb03!x=x8a^>GyR46ROg&d3Lt3I}ctXKh z9*2^tz(L7WmQk|FzM21uA5 zdx|apw`0qLKLM`-scRR$gm@kCQqp;b@_9Nq>xKrF%y&!B`z;kF;Nkl2!7rsDsrxC% zGI6bokPP&kYE}l7=X++c^rJ!g0ql`=4`$kx`m;5 zO-MvYUccY_|DX5GIrrSpbDx`g?>9LoPwsP-l-_Gzp`;AjprFX6x=L}6nv#NI9h z`j5WnOi6L$l5sAS6t~?eC}=PF#ug>TKWYk!`gr%gYxI#g}bICm? zloXR6DJTRkIZfpXh2&)r$x9wrze175NI{`^$&O#HP}p9lpty9Sm^Z&dv2&Tv@RI*o zUZHq$sckN0=aNM)wb!LQvbsXyd#S@NCGC<+E_M8+s90a2IJnfXOOgC`$?6moMVI`n z@e0N5OI`CH`z1$S*3fau-S8_EXY`k~UveAb3dQ$l zE&jiii@EFntW0f9TwMO&riHb+?PWLtv#*u{7S^`^$@?F)wYL9Xw6?c!_`k;g&HY~w zOIwGpCbs|U?qK>K_MF{mrS$yN>-Nl-*-U@B>P7F9e@bcsI^&NMgWInRJeceJR~Hr> z8*BT>|L@!8Uy~&@HoN^{K{;z&|^;U287raJGb9Oj!|Dq;Fv_?yB z_Suiw;n&s)<>#d&P%C}_2e#Z8M5Hu|cz9g59>tf$47AF)SZoZYh*%=hq(oNfUbp;W z<>EMhaoml%-DWd`y#?~9=IO~u5)zQ_3}suUVqaEfU(RP=wq##sW?N2TTaJr9uvV5l zuLw?(>GV_fOy_DDVO-pzM(t7~r8^lUI~im;W28GZBs(=^Ixj7PlAVH5ojmNzBGCuG zyrOZ=LB!|-X=TrBu9gMH#cgWTJ~eoU8oWmha0@ba47z;ZWwKoC?sCF^-Kx2K>$!m2 z)Zis*@XG(V!90SDor5%_I$Q7foXRVE)^hn?Rx$Cv(JtLKsKJ>31ibW&xlGQstj)H( z=!QGErRjraGm>e`F1lxMj0WKjGzr_m~&9l!qU&K|JEDF<(tX{yiD#M z1aJz1IR?QlAImk!*gXg-**Wl*eYK6NW${1BQJDXHUdn9C`E1L~FO~&YCC=?a>%@#odH`5nl05S zFCNNvrT4P+7`Nuki}T`t1RprS%PK8ONFL~0jepqS?HX*c_N+9I*Xw`g7XLx-3yb(e z_8#GZn=$cUNdPmAP;NE4NI3`ih4^~$^?wfaCT#7k0jQsNg-qDO^HGOh;MK?i!qHe=uT4%QNGov+3*p^(lHZ%r=(90R?FXpnY41AaVJ5zmQZMC#W*1o%8 z;sL!|6Y_4XL~KL9b1T9;2T`?{z+{vzzE$^t*m>on#tbSVYd}qf!788S#`~vBi*10O zq`7PA4qw>Tu<;{#7h!X(DqN@b6zmvrMH#?0%dyYF+i1i;RyM;IL^ z9(-Iwm)UxL^eEayl(wrB-tNwKO+p`fm80o89RF3OO}_JqgFw|W?^16P{{?OXcyX;$ z_G4YBxk5t6wJ=tM)y=9>zMk?4Av4lRy1p!_(&j>bz>y%P9CP-y-1oz#2STCy9FH zbNF)kpLhJ6RU2A^Twgcu?<*}l1`Iu9qW*2@W6rn=?x%y6o#q!aPQO?lg zaRytH_c#_?nu6-?b@TX>DU*kp_lG^>hZ)+EzP7!boZ^4%UUTIzEoPmkA|?vmI|!OhCMu?Jvt$TKQh){)R8nsR{0h=wBUUGisTR7 zE~1L~S&fl8r)O^R%`?|KNKl@mP#YtYt&c+<1(H&rjfFz1Cu8;VSyK_w=`_sS z2ca*K!XFu)`D_Suj|tUQH2pR`AI+sfltzERT8HDdHyV726Wj zIkp|8&}uK+&vet}Zilr+WdZYAdTo5VMvM@lW8Cx!pk^`aXks(MZ$7wwT(O?3{3@cE zdD|OVg0@e+IVyg4S{zh}^kGEa?1uk3<|)AcQ2cJ+2OBn*UNXYQNgMi?r9PIgb4h)P z8-cwvyTA6v`b@kZ#Jg?k@ht6Gsggb8t|;!VH*6i(B-8CFkM6EVATgO*sKkpmTlG2vgNs!#&Q5!sePo}nXYD-t#TqENbEmHKeS-~?!?Q^Gl|XDvTrWYi9X zyV*}=Nv$TxYS%_#t*6*Sze%iC)glXH{b;auI%nC1+n8K1$PPKxwm0nNF@yR>>m?bD zizvFye%rHPOCXm#rQ36o*BnMix!zwQRHiYnXnQTu>EhVXJ1xdZ^`-zFwQ}$NNb~rq zA4VXU$Uj*cG#&MDZ`9iH?osrBdzZOztj$YM)IwHIw%h(F`uc6KQ|J<9LDwBN=712Y z;@8t3*J7^~%5;xNvlo4IlWu(n;K>E``!VkgTe~wC&e>2icp)A-YK0)4V-wu-f62I= znqTAC(&<uQhZ_|`-=xN1>Y`^4!`N1G5MR^I%@)<+YgXqzrhv z>k8#OrR!%tZ>#&fn8WA|L_K8FtP3q^P8Elxk>HQvB&et2FhVS~7co)s7>*E&^llsP z$StEkbHIF!J$$)k*wt;}lfEoL0lBL`9HJTE#3c#(%xU*rXz%tRJd>~(fqnPS6r7U3 zr_9C>G~UtO>Reed60)q`m7BIikeYp8H|1(b!bw*MOBxq*S6R-zm5rV?;fzLDx|e;z zrd;sdhbl#i+4`rKjG?Ven%oijLt{kWp|8z51}1|qmdZ^k%i`cMgA%#v74}-UEm1nf z*(QINW_7NWy|}(44Y}XY-;$YrPW+@JHQQ0l%tyTAn~K4BkJbI{IFXw2zH6wliZfQ7 zm0syO5$=D?03C3h`ROVE=Cihx-n0tUIwfY@HWt@z6XeoR9!78583d+_Oa!gDjSkb3___M;p{cA+h-xeI z+VJLzW9yPV8&9qp%_u5{(0^R#${4vSj=Vvs01rUgJiLc$?$HS&(^S!a)?+cCa_JD@ zzcML!rLK&6I`8q9xd{K=7gyY&9Mk2D(_J@8iczAEVy-x7muRhqD4(?_XgxQoYlMG> zp(o30=elIGW0JQW`fA_q|5j?Xc{qi7>v&=3S~?nMV)y$j0pU?fRX*YNv}jvRvOlM3 zvC*w*0T|%rB)cS6Yr(UeXv(qC5DR%96So?2%xwMY!JDZpDz_$%$L3L`bkTOod=t}8 z2Y#Beo>XJ%PsdcCESU0-mFj;-4kgrATz26IE3%s$(AsAxs3z$RoGNV5*NUFdk282Z z`T^ztQ5ou)Z~z+LEmwh_4d#O{UM|&%$3!N$bkZ;kvRky7#AVsNJo~v9@p_J7#f+Hd zr{Vys4E=r)H(E*SmyBN3Gkvh=78CYz+f!RQ28&kA3p4(LoWWyO*#S1s-2e)ys4rrj zDcI6N}t!D$UW6trXRf?VldJoMZR4Vs}3iN%+RT z!}&OsBJWM-c@X=J{t!uBao@3vTff0lTQ@Wq$&gH93ce9fok9fo!$Mf!%&Tr$ zk4EO#()sY&2DVR#+Og3LalVBhIj8gMCB21j98ylB*ITuOCpFtCwo~-8~xiJ*`wr`YN5N5G*I6 zqG`_CwMJ7dFkg3EpdJz7k{R3x2ssM1Rp{s0rsIeS8pN2*xFY)xZurIccek4TSiobX zziZF@T537?ki)*VP}4C4r(^w2`b?Ab43vRxJmLI}tl);ZCHmH!3C*5t6v9eNcvW?+ zvrE3NDY(V8GI*MSOzWnydXIs|aToSKDE<%u2D^$TRz9_$jWVK{rX%355LNm>WSd!B zy#@4_BU%Va_nKtK<0I7RDK{FjBFXf9)h0}vFj|~%IIwEpQpCZqqE$H_#S9oodM-T} z*VM)h+sJK-yg64bkG?I)s4D~Ts{XO`wI9AZtpt-;OK9|Hf5(Gf5mC~OF=_IN@)`n0 zLV>UM<{YKo;o{U-w?&Bnw^hEAOoe2;=tvYr49`CZRT!Dy2b#FE%ol=)CBKqZmfy7P zq>RdwDDZAP_VJf(`1G%=D$28G=;K01!?Ipeit6VrnmWJ*(%{Pg_N(&*B>|Cwll*I66D#gJlcGJ#?9sBz>$FXExi zdkNDA?8vQ*d%KR#yj$;2iQ^wT#_mvMo*d>Dak%bod1s?W&T4NHnp7K<}^&n zSr~Cfz0ThovAOQ)hAZ-8AJc8A@)BI99op@MeODQ{Vz~9=5xC~qM-Us@b9zCoCRD(c zv;ANupv&_d@)g=$E@u5tfX4ZiJ+9lZc{3E3{i;K1WWF!cT+W=mHHSv5Q~6rxAaQmv zSy+Kr+ipjV9B2U+$Fxn=-EVa%F{ihS`ul| ztPNBz$(i_&P-6ZsX=#e8iIlX;tZOtYofluyTY0Yp5t+ zSZRL!tUZIC`j-&)mmYVMx#*L9ZVtYo8kD`Zk#==M;EIS*ItrvXhdFj}fAEOlwU+wg zb$KAj#l+fCLv59*Sx}J$CshGwo0YfysY-+OD@1*iL~8BdnzGBaO)i^6*W3NO>pWC0 z`#L6HxUE)=A?YN@QTj%0eWB3i7kwwuXWjce_4%{IA{mvZkg-bRy8UW1bsbXVuWV-B zbK6=3jeg(h)#W%$X;5uz%aDVZsM@O7b?kdU!SJeG$TOy zgos`3Fh3*GWcxMd(peeL14fzo>I%L69i1n-YVnTw9X7!moYvc0czBR7pZpf}F=inJ zR*r$dMs|bi@19JRa9SeXrz*}$`h3mjB)3Qnb4FdbGp{(Zj^-u1v(1*dzU!-IUffR% zJZ~AfO={9RQ$^(l%iPK)8sea|G zSD;y7*e7Q6Iu0qvJPr6mbKrLaKTS+!BO96)NihRs+`}dfI~pIofM#WBDm19}#<~84 z=%C%N+u?)_X735^jM{~3HvgM;({S9qKY1Tfbs(}M6?KR-@pDi-H`7mRRyOvjs11(H zcH4VId=&#$Ml`f2mr}iE^KP|M<3=^f2{lz}``a7Tr>4og=Q>`s4n%pd`e;znBknva zcIxW$dJ{}}m-`rIP}KD~xLuv4f{wF7o0@;n<~QxM`W2bhPcsGr{94#lw~?j!_Xt2v zX&ZOZ6xVmu#~Ytn+Wr`JYu~eiI@dq<33uQi8M9{Y3^ebqis(bWXIL9BOE?;6U!N;JPH|SbC?cd5{xD3*tc$8^|jGh zb=k7Hu(+cgt9${Zy4!O=Wy&)tZQ8lK8@=yR9AeBtE}2RldiX3ijNQ|@?}!<+Opng9 zRh_6Kv7esDcT%l{2(3Z8?{-}=6HP;kqn$uhyKQ-jfLI14^y;}zyQRsgiGE6N|BmI) z-?+@l9ZRg=1vlR7gsPQTQ9ZN&Oc{*-OmtVdaD0+CH%8igGAOt%*?$p4DUd$ zxbvITwTB5BVv`9Yhm5HVz%{A83c2rW`nT^WU*NjFyjA$plN8ds&SwhLU=hp)?MNLz z`I%cMij*0BzhKL-v0T+_tn+P(DvwT|sQTcB--(&puwcQ|G=S&A{;`$Ijm5_4>>k}r zqH1@(8*?Qe7namcDwhVr|)lr!a-<7D~eY~;}rUd$J>T4sKF_yIy9)XMFw~reT0?TJ%ZK_>F>~Sui>h6Co01I=M#n0jv?1|?w zqg3Wi6o7k4A(sL-kp_$SVt0VGg>j=J(B*t{9oi(5CPsyb}V5pc-i&Zz!gcWZI+J$ zFpa0T@T<*VYgz|l_N%)gb$;|mW{c2A7wGjY=#v;=%2UyVBQwcZ6pYa|)RZqcde+}F zcy}UAhZ`3OGtS*NZ0wa(mEUNj$BGWyZEanP!{`0Ghd>td68Ig})GGu7}jaeoXnrZ0znRx991ZD7- zyW>juULtiUy*e|_xOQd$zj$hIYMCuS*c1MZReaDdbE6O#&!|i;vFxqtx6n+~vhLTu zKE{t5Z~R#X9IEzz*K`hz?ZoOROnOmDNTV6D(TYmFG&@q7fO)KV%X6r=BseobmbCTd zEU@^yTVao{8uabmjm+y$X=Cn|oUiUk*>4EC^bbF*fD_!*|+G9v`>i^JF_;}#KS1-$% z>bZ|S1=*1!j|U`&jzE7i7*$y;+0D~xxNZy!=4=G^B?BFIWOQQ(f$D2`~cVY+ll-X_VqP}us{wJg8L4(7_ORGXd+=|}miWrYuDTcKzqyGddCdE0km%phMne9M z55*e6R{(Zxmf4%uxB?fl;ut9Y&BXe*3ab~V-SN#xfP0#op4iUfSpPm>L96s{cIrM@ z8Rb96IoXu+cat%g3lJ@!$z5?hb5J4LQZz;;2k@`=xHjS}Cbn4}>XWZOl8!r==?|gGWsR@<*+R0I1$X>GG>#Bo5ZNa?w3_a-=Fo@VjDRREPFjj756}c#V1w{_-9N(BjsA2TCu* zcyG1n={gM0eSPhaear^^D{KM(vqb-B{>SC!cC%JP9r+^j7ZrE;lHRQiA@e>{v5+xf z*#j>XulKlc0fr)DrzyX*@#SZU03XG1kq~d|o%^lxi`t(Wv%`o^?|dS245Q%Iv#Uz8 z^917?h*kPD^enzC*Wd{7PcTUj{U~Wm_xEqA4S}R5>2)$2RNC449ArnJc#9#>_wjI0&&k|c#Q7C z^D6-9(4j@;M`^fNnsQz#sl(sD;^N9N&UgB=F>gVIXP*ancK!HL@*PspQF8|imC>P4$|)60e$%dd{&WBkuMNYY&KNq;0cR-0+j|G$wjYC?!=!g%~q?rT2(`N7Y2p zx2X5j!34{q)R||L&_^{Ro&k2EIDg5x6Rl@L+c~c zbeDVxQ@K^j^G!X?2mHZzVYg;kA}Ry0UcHRw3VX6&QGm})bq&IqL-{&B#*jx1y{K_9 z^HU??4O+kOC6gs3f?giGFplZXX$6+^RBBI5lH~Wi0KX7)rj{TS!m}!8e9J%EXbSPbFZ zj7+Vbo!Pvl?LZ!qfMIL~;9!Qm83A+jZF zvTv$%VoJr%<343e9=Rp!80O+IeUdm?>+SQtRdH0WZC%IM7NzGo?w64ge{*hf7WwfH zWZcAZrD4`6(X?~M^Gn!4`)veNfPDf@L)E%O;0wLIIEfREin7QesYi0;PrKOK4Wq2B z>`2F24B^dB`#EUyP{yyk8?+zyWuzw_Q{7Sl{~cAUvK;(H`a+3Gof68h)HrGSa>jK% z2R=BOZiZx3J&V0E3iui~e6fS^dzMVvoL!o6X686@wj8*)$ufli-h(_{h~!#G0Ajvz ze8!L*q%FW~@2LFvi50V{9K{Oo(b~5g?GnZe#B##E=@n?Uq^`tk|C zC|e({Q#NAqmd*U6-yj5ZJ!rt`vDf9zG(AAm&EE36_cB*Ub)KAtO8Eq`BolfM?PqjQ zqEH6&6ELuZ+#i^WT8IAQLa&xhn1t}Z44rzu;KDspO%MJ>HUJf9@9TL;8eb*YzJIFP z6a$@9_n=O4g`exD@8!a{&^#~MG;}uYN0DwuO98bV&AL8dp0>C!=sx>&tf!%_GEgFnJ4|Boe_1B@1;#=p= zvZE^prO)=;ay|FoWlN(nW_5emmYw%1ut|MhWvEW9!3^tBh*#I9U@`U)MLFRCoZ&<< z^`Zkvl@4n>80e5uAm}Q#K>$S&IOSVAs$;CKpl{^w=`oKCjz$oIo4%sw*Z(T#ZtDW9 zYw!#|eda%(UvE|K5+|VBP1BZ~X4rrKaB2OYwQ^;kgBAnWDSMI~+N$LMbKj z8INO}*CV83)Sw>@bui^kClPM&Z~2Tt=nXQwXsiIQeINFOg;W}IgAf$0EmKiG;?zBf zbpHbVg~HT!_mZD`s;@{NTU9`3%r-`(`UeBy7Wv#ks(KtGu|P{`)aEx@_=s|ZjK{b0 zktNB5RdO6X3H;`ioD;1v_QrXNcED9yV{StxG-|Z;Yak>kGQMl3_UvNO#Dh5n|JSLR zSib%8JZ+gS9B!qrDH}Ulgqhl{`R@3pIMH~@+MEwh!6#8CH)0FP@S+4$?M|IkQxAcp zm)q^WFNs?(ea5jCv1JTukAWn1QKaYSa?pH5UX%G++)$1 zsbY)g-`(7YVg$~WB>h@YHDgqr+8Ezk%k3;Y2VJBVL4InB&4#~QSP?EKT`5j4NOD?a^Iq@P9jjsAqa02$xP zxT^QHBwvm+3Y8*q-&eZWv@+N`&lcqM)qws;tDVX5{B~^UKGThc!S8nm%muSa{%c4h zW0Q{EGYRZ%A}ZB{0+0DG!;y#&2G**#R0h!3V|Y_bN}AQ^)yT$jQliJn`t*I0&qMnm zs(+C{CJ6G`__l`F!dbt!W_{gZm@3pxlD%zD$^30w^BGf|+x`GNH-`N{W{M0QI0%qt zA14kFXZfy#P6D z=nM1NJ-PXd=3q=^7riWQ@NP#leW)y6$A<~sTGpIt;34|H!DMxZ{TBXaB-o}TSBvEI z-ky$m2GOkCXV4{YT;28*yn)LW4b6YydT}&b&}z^ZN});c=|&exk<;nxe9~XFHSD>q zI64$LRQq8X--Yt8&&_%qt4RRf8*i}QJRg{*$|={ysa4sN@-Fu9zXc;)K5=p!?QkwT zvlWu7K+!c&sej6$8`m*UPbk~D?Ut(U<3r;g(T)?xllp#x0kscm$mMMMtPmLd_HLF@It?KIg0mbL3ClBfS_ zM~aVa7qsS2w(i2c!mh7czD`e(32G7fwp_cNv)`T+Xi42X%k9%cZz{8G+tYy>$H@~m z4q8^uhy)P=(Pswmlhk$yZiAC6&V+XJUkb950}mi7cyKa?68;-}50eRvH%aYxoaNaf zy+8S}rakZXn8>wr_n7~RHmYLR2Zc*mIUCQ4!hyKgqvJI%R2a%a$pPsko;NePgXB&n z{OQkf)J{9-mDi`T{0@nd{DeW8GDEF2ZX0cfmD%ysuY%#Fg7bmyv3=EYM#+js$v2m; z7xa#)%%uv^*-gQOYz2-qIzOV`>z|#hrA1d&cVNw*s8juGy!wh6EfsNn)Cmt5ii+lj z0wtuSt?%+iitj?r<_VI5WZx`IOkh*P@ChQ1Qyh}~rx!`mc{wb(TeqvP)!czLReVG$MKeT+8!AiXQ*Cjs|t$1ho~#AD6tIfAI}t)UDS z)w7u^qYkFlb2DQNyvdNWzRYq`=nsf+74LjG^yh5qQTrZOMLxFOf_i5LSmAdKC(ki` zuhwHdU^fetn0mCY>+gD3gUJ$JK-O$2KJHtZz1e-}_gVo>b^VSM_^P>~A>ai8?_lb~N)@L`o0-lCC*VnR{Bdhm4P=T#pCcH(+R&RfFz)TsjTRTOraSzH7N@mYn_y^1&F1O{v8=9>WQ*UY%ap)U{k_4k{2+c9 z=ZQ4o(?PV4f7~H6|6#Anc|12s@`vCITO|1KlVh0=8&=9{Q~2IS&~>Ffg~kp|Vj!i_ zY$Cnk@Tx_a-_4aAKg_@DtBd(Gd)Ni5=2rPBIr-6KAF_4oST*%J!T+O&^jSPYq0UFw zh?}@h853trx8YLa@7u&rP&rE@6{*7_jB8P`UlEK0N5^W@+frCF5=8n8{cX>+?{SPf zsqy*hf>}(lDuUy}#Sqcy@H{*EqVd;*@R?LdYtF$>eFf!kL|7JhAR|HhEYtJs+#?}wU5jwY*l z_J+oa=23^gP3XN)uaC%m`zm_LAEIa1vPS4>8OCU0{?NLW5_Z}>wdWIf(32bq&{!zo z)9S{`etgWMp&Z+5&5O%$MMno!XKwKsgy3s#L7FVp^iQk)`vK)g9t6}G zI7c#sMx9UqR2WkFAivE(;M{|h&Q4AZ zZwHW=>X<^w^unRBXSq_$_%=q{{kzOszkl!~lsOaL)bHtrsq^~%DkxX1w_SB8@XYKo zE1YH!T+KO`Ceax}q99zpL+eb^45W|?+GLej@#)0;6gCr$t}!`Q3oRScF_mtU2`=Xa zrda>J(bSNp$M;ul;;HpD42!REvl&q7`9#mF0qg@;oBi9>frh8LydM`ih|%oyM;+N- z!659=!6DrWrv_Z>A`vKbrk2hl;5$~zI#R8$sTYfXQTtgm3M4w#LwLG-HyoZ9<;0Eq z=>t6_Ib8Oo0g}>i4iZzPoccAlWLWida)J8m{gU*H#7!!((lcHD3WOI`K-Rhs>T}z1 zmG9-r^au+-xNIFFifQ*!GpG<74)*=0Tc7`HxK0Ymqu0coU%4%FFn6$vose<>vovR(HkpnxmHyW=lt8v)S zcko8$54z9DhSudueweA-m9d|>;Z##51JB;_K9%V>I|(_?dMOiKhdz-B4}sV?vSpg` zwklA6i7PIu$Np*dubI?iw0$o2R$$ z$H1ASl%JFeK!uFeiME-ik6_5wzH3^egd4BbJC?W{`DT9KU1`(i8ZzlzeVK;QCDXz@MrkrsrJGl;$hO?#{8!ulwv zpGF#>26nTD6Zd2;@LWdy6pz12j;^0Makq9kzZHA+?yj66o0Z4{2k@%(pQ!1uSw{vm z9t#Utt5zy9)4+&yNWTJ|m#pnGyW<95>kmsL%zol6$?5VIM&)1ZEQx$Ky35p{UAa>z zo}O1@00P@p@0&{z1eb}qT**vUI%gVkDJqBYlF6PS>jOyQX})9QmBU~445T>3oHDu3 z9vt?wQJ|fiVXxu+3`G+CCRoJ1u0^iqP{xnTrFB>D!Q6nUWZjd1JE7pm=`oh%8M~_gR}v? zjb~5VkPmR1+|%@{)AT$UH|v!Ze&fIVc1hm&L}@*lN}Q1&eJ`6DV~M0r)`+dmzv!7A z>lU#$Od|%Hv;e6H%xq}ecMl!mR_$-34g0S0Sc}}fqD%~o1PD!4&3!lB4*d0GqS|`~rX-odQ!7X7vuqiE)t=!F%lwh;d+^OxD~btRaE_6KP3;TNr>22GAd)lP8`CG62`)r-_~z={i{rdg#y`2G&#}i(}-tF z=q9xYhb1+F%sY)BPhPOP8mmhtX6bjxE+{J%=tf2VcUy!>w$1)>RI`w7fZy*nE>bz| zdAyt&5#;dV4n#lTVD`d;Ty|P;feOUeC9?PkWtHB|Sx_P^?4f~Q<6Yw8UDi>%cms;_ ziyW9Z%Y`VdL>sF2=UqVk&I?|?(LK$&s1sh?pgmpmRdoIHw6-?~A&xF=r`L~V?`&sv zF(_~TW*dN~$ezr7H#c}>?wcO()S(;F%E86Ie>s|R;9Y&pyXv6A!klhS3%(;X>5&zA zLupMK_sRBh2oxNDh|h`SQ%rOUU-$bid)Y0$ixX~B=uZ}?iriwT5{i(`1<)o6DfFKz zf9#3OrS+F;6VuV&5^VYBpG%B+wnrF^_m(rTweIv>lPumB!m(A3``BKd5Sc(^RZY?h zjG&#?MU9E_Fw`%tyIyC7N9EjJ_1ydp*zCvU+$i+Lp=ORDTy6HGD%+5E(2Vk>rv7fz z^66w|na7vn@XO&6;K^Z9RNaPUZQSV_)iZ@Bkp4SR&tgz?^xo|gg8m;3*nAc&OY`5^ z!rm{H5&VP5%Wqt+r*2E!39^AneG6xqd448=f)g#?YwzuTK3fHTHEsBzkcN8Ycd80> z;~1gNS|F6lHJ=xjek%!bfIOW}^|YM)+;SDK=4Z?1rx_Jx%4A^475ge<{qpqrkOWjf zGUZy2Ib^{XCYOHGbdOZ&H&6I6GnfMZ$`TVh{g&tC=JlHcEja+2bzL7=HFS(z>_i|i z1_DT-4f$)|MIQ=WueTw2aBaP(soXn?ya{P5X09x2R4|S|x!$&Pam~c8#pB+x0q9fw z=X*&Kw+aHI+_mGBF>Kh14s5HwencR+ZbG)6Y|^*wTlaH+gec@5L{z%~U&HxwOhdDY zudwYT_%&SBRdG94Orr}sH_#4j zFr;y$H1E|}LQ=>W!wUDs^S5qi6Y)1N%yO+^D0AoRhx^L)H}Gl6=*2gFE61HGdn8P3 z=^4i~AuKx*ycLFAsK3j29N=5vKqyT8#L+9?|Ie*i| zCh;6HwD_QMYqn12wi0dHrXR!6dDd3ygzq}-s9AghqnxY{9cglhd<~F7de%AT`EyYS zPcJy@SJW7zt-IVZt@Cu&e}miWuq2q-SYC1|;>Tl8w-Wv|ZBCqdbc^KgNLxb=_8l+YKTHY^k zL%IyEj&sr4AKfMV#Hp{%RCuec6;53%oXYu7KBGT>_KMT_`;l~lcAx%43(F~VL7>&B zo-zIBR(t--^c~jsi|WdH1xQ7j^8*35kUe0w`p3LJ7m1DMa|Jsu%_$cYp5rxl1Y>me zjr<*F#HspE>R4WAi(WHO`geA3&CNCmk`(=B0O!Fw`%iS0bN7+?Ar)N;!T86b`-(U> zxJ;=oRkS8uNMyS`AwvQED^{dFct9m=3PEmE>-(4JasMe7VII@y$VQm<^{HTY2QjaW z$ZzhWNC0Ir=ha^M&5SnLw7uKqGhhd8SFnrEvMU(V{=&ALXHED#b<8_xy|ray<7ni-#dNJ|2}I;|6U0$;F=T=NK-Uk)j3))=<#d=}by z{CLoldhmxgrOS_D+qM9_(2AyK%4SKD6D(uLCOhI2R4@1lEqUf=P&Y60!fu`!rdc5l z)=t^?EpfO{2sv6-$9*1EZcnU>Jia+gwRgDVG-2+}ZaC_}leMZIXvFQ3GZh@(IASBr z(2CP8Ak1@)%k~`lQui_7V~@<+s=ba{UmeNr{L{{OL+KjlR27wpSohepdovfa!HMK6 zevI-_0YKpS2QxTBo-m=4)`*JshuI zes+$!$0Lw=gN12?#|hsI+IBS@2o8h=_~sh{q{LeSULNdj)qezlHj?W#7M}D=zdc=f z7QIjjbV;X)=t4{17CgaJ{%r!uHcEaRXCJtnR+*Fe_o-0~w~Hes_wQHk-hDQnUlg0r z94)KGE(eYcr?1VJk~`;lG`|7n?5e%^$u8FnN!#n)q&D{XQ^Do+CHr`OGWy(-_La;h z_Xa;TPJ}rPb#b|k+A-HP_*_&Y!Ls#0M2FKj6TgR_wPQ(m0Ji;6zaYi4e!WBkR+rW=Af26Fw4!Iq4`$*1*eN}5UMyN^CSDH zCv^49(p#^`hH~w-zj1%U>-p!`HBMPNglcG`KIxxWoaVr;&C}Z%GGv>^MRo3d zL3%-{kk1}Bkcaus2dA>nQO={tmMd$qGx_chiQi78Jof9S?svMNQLV=1+oa<5A7@?& zF~2^ooFrG&42wb6A3Mvqj)_`tR!x>2DYJurt^IYMHP$MAcw`BSyi@NNSDTuiyVq%+ zNw?hrAbP0VqBdRE!v zYw6FX*wTrJhwV|r0_WN+Y0@GF>ezHm98{^3?7VD)7Zb-MN%N8B?=$jEs@S;&%2y1s z|ESikyD`nRcS@VHCcm0%4>DnW)~ZoYM^VmC*mD3km#RkPuC{iF!ET0Lv>T)@+5B9E^?~}HW=0BD$ zQkm)WBf7DYS(rwxw@Oj2*Uj%OgxXZsetHP%Ke>@nBWNLe;p_7a##;;wpuW22-phBEL@ccr|t@g{`1HWsiG9&rnJ)(8(aO|Z?z%||!$w!7KM`PI=zlrP z$JDcf4jwsI+KUOowazzqz#kT4F1DQas6br$vKJXh9fc>3ZNjpuIc=O@WmV1SdaUs% zu>vO#%RnDQnz_@wQT6GNX_rDP~qDLmkUoiNB?wM{}c!+dzBt81=~Bi%DA24T-RXC zX?MfvV@!(ot0~i#TDiG!;8-NFrc|rxlhn#*PBi1;Qp!6~$WspBoLw6lWUP`QqKtn% zi_p!8+5SFG{(KZQQn&K|-1R3FVk^A~(`IL}BA~)KL+5YB=vKO`?cTo7F#j4ozxc)p!zXU~pxu z-;sAfuw=^I>*zzU={~mKVcvbuT0PWH^w$)4$NXFtNt5x{bHnQb;_iK;L<$;**Pq?1 zLgK~uc^I(z*`9)rnnq&j!v7BdZa|U0K9>um&;AYJ^Z`A@Mjr?Vxak7uQ-iu=ou!^< z$z5A!J>{7m5{{nfLC;~e*-(!xo@q+?^?K<)`b5OZ1LnFe%pv(5cLeXsJwg5rvpxu-CBGV(;wf3PAMT$45DXyquw#-0%4LIwFP z$Aj@F6m9wWuFb)+b|-atLeJEcCjZR6Z*6Ad4jn{C2857TCs zu=#1%eIUpWO?`HLFl`rgSl5<~x_%?=Hj-}^kvYs1nFA~(wgwwaDS@eQz?2b~N(W3i zf$<3pt#hM;W-k*x%+-I>PYN1-8uPD2d>0a%i7aQNPxb2?NS?HYDg5`OfJ%>|4A6KYT zzVw$F%mjPRThccakiIU>aAi+<+H#h6DmV{p-K6^c8SDe_miiI!+0myWxjVEb`$YQ! zjPxTY#E0-iTGvy|`BR~JhxrShPAi+i*|+Rvw5RN4RE5x0=Ev#wg05>(8qxXR-=lKpGKhLrZ&#dLcNUrD=9Q7sT~UERwgZi(7-~k{^tO+RFit69{km zPs{oB;TQDvqW`2zQM8PM$p{0zx$k%I1j zt|304w!N)kXRLT3FnrzngZbwG&Cf-Oo;Q^_=1oTi)V%4OW8P$&XHeaMu1lV`^vTg3 zs!pSCi9QMM^QaRwJQuVhT}C}|6ziT32WnsX1g5`a@Oo1sqQze@;wKZT@1J=~@B5?A zi@tx>u%FQ}v3i$UXTD_dv%O^a*?tPcppKBQZ0ADTwA8`dqXR*I1i6M=o)K{a#QuNI zxbIG$%K|v@_5O^q*PX+D`EKGzAbE$zXFUC$*?JaGlhN}SH0Fr)*ftli?I>*4&@*ND zix!>huyCb6Mu$Mll_xXJDNZS8xecv@!owM|di_fWkaTdoac%Q?u>Ud1? z<5|mCb<~J2cC3===~#8vF+Q~H*5$i7HjH)@HYPRgw#)s7j9yPF_hp)EA@>_ILAg)q zFfR`sVp01zXVT;1?&8M7ol!eodPC?U(_9O>q(kUp&f6)U9YVQy=54oJEbmlu_YC@( zTnn5>GWK&7&)}I3Q=RVhxNRivVkS6$3GDX{X@|?5*RCC=EbVXv+u;_n$K@=UzteLf z#^m0atdfOiL-uXIgkv_B^981%bX*uyW8Ro$%p0@p?-_erAJbj-GpSeD&H}8<>U&b? zn%5_gPGd$Np6yY52=V8Q_+!06J!GOlTwgN$WT4&7J*ST?>%79p7IUnajj6F>9C={e zu+LrYQRBh5qo2CQ14uvKqx`;ToyYUEkG%KsVUkA;vGEm}&sL;6wO`A&IkF~r-k!;e zL*1%!Du}|*I2IY*&Bg@H|H2Lal`tw5i1tgx1@pX)zrYt&`wWp&p3DRlz zANHuZ+L4~1yqYU*4rhbwuf@>fDPqHy{bpNS*to95^#0>}?z=h4jeW554gcds7T8h) z*7TXSr75$dDK*mM!?C;w92d?3$BL`Kk=Cy~PkCh)nwLAud&PoJm4&u*#j$)2I4+z6 zjui#qNbT=sWY6^-A@u>7(7s3MjJJBqXW<|TU*rhC#Swn9Bm52{T)uxAWk|~&VFev~ z9{oqz8~SO<9^q3{$=*<>dXgu|CfoitI=;P%{)3}g-3O+}@QIUpCcstWdMUvDD%odO znT$AXiF6Nl!=T8A`jdc+%qAl_nC1`|x!$rrsjs(;_vvFDJ&$eYpzh;B&ttRQVF2xa ztS_SNwKDgO%Jv!~aPta;n_mFj_5$;YD?01Z2D7!N*_DZoPVuo>R2%B z(!;^9Nzx|MgQBneT8Iy$wjTD|!LW(%2gCNyTIl*sC6i_cm9D(MC4af?-^>ql@#$dL z@MkSNWWnwH(qMa3wB}zko3@m>dn6dP^Iw8tXC?}0)88(95BRa)%Aa4==YnAeF1zy_ z{uh;J$%|}^t`Y0!D{EKQ)z;OnS-q;ktGj>2%GE1Y)va3Pd8_Ky-dDeNHJBE;Eb=hV z3X8ALhSUITU+}u{-2^g<#6J81``8u4EMbCvDk)GUthF8 z){@9%Rcbr+%;R?@7i9{Du@{tbRWm?-D`k_fx5XzG%`j z^2qStTjJ3eua5kN>fkr84t|U3h;LpM{N`1`Z&4M5m({@ExoY5VQ4K5y@rCak`sz2L zPts*XKCE?F@qlrrCElG?^{Z8_WQOtT>0zRpdKk8z7AC5#hl%RzVc4R2eu5lY7?MN_ z!`9XDlk^~AicEoU+1kN;Gf2bQT%*(9f{$~*k(YD72|wq;wyJik%`pS{O?X@7qxd@) zMtD3IM)+Leq6!@k}P#6VK5`Leg-Sr~2u&4y3ZF&cW_l)l7k~HT6cAtQ!rJb);dkt~5;6 znTE-_(=bVcV3?qV47WGAipQomenXYCFxjza7*})hla&9n;S4o8< zD;d9aoE3{{VF8>K%V}W&oD~adVF8?VmZa_DVokjbUAz}$Rjo`JybRZ2=6CM%v%QIo z-TdmZYPt%827z?|&e zCUs8s-5%}V)1K(s6VD2IH!^a1qqwG1wo(t@mWX~*nO6N0On?2R+X<$z?d$zHsmpw!5Imn(7_MBkP zD0|Mc=K^~!vS*Avm)JATo(c9`X3r#huCQm8J$vKPbgI9jKh+XV=oY#H{_bT$l{Hf!oaNn-NhIU_UGZ2hOyt$k)jN>l zyiD`#h{YMVXLGRqiFACRqc4$6WTON7JTs3>I+~GT-HCxrwj-K|b?ixG2@ZWslx0&d zsyebPJ33Q+odhNwO+ZAFGdG6yL^EB{egZR)N=N(Sz(cvP)X=`3c)tkBl^0LODGj=M zS>~DO-dw%|@&0Hm-jR$B_ILHNS&pQ0UtfwL(zS=Ajm7{tndm2Uip9GVodXQlDC#*F z{$i>xk$pZopwlzDr~ZCYr+z{59B!#(l+hxa1uad{F}^o7u%~Mvnx(LV>7D_GH(=P2 zU=7+IOYkPK#m9%z$y6rsB26#&j`h!;exbt?9_dT3HuxaZvwHO^&)|dT-n3_iWx7&> zSv_svnmUp{v`z;To_h^`WobpIYNvr78Hiz>DMl{?F4LFV69`KuvRNbT^C>21_UUka z(XL+Bn7k`_yum;|rjvY}PBYb{i%~4e-Y~+Y2a|lP?&@U)fZ z@jKYeqfg58%lMR_Tw90o>;QXCu;(0mCfQSlGOL|ES@s-Z&uR8tV$T(jYfO&R;nTvN zEIuHU&Z5jIDPb~}h3B?=^hjZ^d5L$7AtiW`&># zdrM~PtnG^>T`<6SgC9z85No^;GR}A@7(4pv+0Yvw2rCO2bLE=oDtCEO*!3q?)?;Qa zyrUuUM2LsN2~jIh3#F4Tg!9ph3+1r<;x8~68Dl~^1UG;Ya$5j7U_h3GD0kunFiiY% z(VoKzBzdOe`N94@D}<1*t6PbLqH!qjSv=UI1{jbA16HkcACvwo)F>c9kS`GpJ|Zw= zJ&L0c&c%Ti$NHBLit5yuY-s6k9f2vPyY}Dn3D&|fR?ZCvjNVHKOOMG@L|IrWt(gI zmyCC3>tkGRtJ4koi7 z;os4LJp=I=|7IN`-v1Hr!^uQXZx(+*xQy>)b4d2_;Z%~fDu?Uizd5`B`fz%1Af1eh zbS#T~9DX3m23a0fSL7r2mXzR{p?t1Gf8QJMw_M++e-ZM$^OyfGV!ZSHy#?dQ5$ zrUzVBnZ9)sfO#Lz#-n{*U`+W4=L!0oe)Fwt88(ITvwKrMMEE==0|m3mo8Ko491@*q zGMU=T=2UIbeeG;oy$aV}o?-Z)DMZ*VSS30Vu_2~I3cGM$rl$i<2wplm5bZ-dNg>}| zne0G#N`+KB4g4la{`RAj9-QBQ&rT=bE0Oi$WlMOLl(S02_(dz5y8XRC_d3H;j z;U7m!rTB}*I|qA^PVIvOz!QEGg!wJ0o*v*E#wUy^{0*Wa{-#uH5Z7(YzX6tUtRus8 zxh#IOF%;UGH4^1z{2fcM5vQN&L0J+8_7(!erjQ*2arOh|FYp7yb77kD(J`2T7C{)` z4_5aKwi+yhL|Ky!cwOB+tS0Zsz<2)M`QtKbkj-iPSg*j}O`D%;_uDr=v!U5ZK*gw5Zd*xd2xU;@@b`nwZ7tK>&S%I=~1%%bsu3-Nd4(!(s0iD_9Z{V2u;1N$J$7;OtzxdyHr5X&{o#8%<_gG&`M+0td1 zr8NQp0P2Rp#?ITB7dx`i9zhEzti}k3znYqNWWd$@mEsyl1=<6v;mj}gG~jwiGp>1T zW6y5(^s*<-o+0)O;~L0eTn9OZHpEl79fL@wgG$OPIJudrtZCrqWdMpD5ZpFP!R zcWl7*l4e{p*~XsT?CHg|lObF`Imn*F>=|LtG4`CmwUklzoMq2>_FP1}vtz_{u-`CNaWt{!2!9&Z)-;&p8D__=Z$^AB$)k?94E5fghV`3?$f? z!)H4k=~RLhD!v)YSsBh|qyte5Ve_zPI>BmNn;pus@yPFuX8g{0oK3G{v3QK>5^M|v zS4|H*1mI`3H||T4_^EFGTNt=u(CuX@s3i2l*`&etDkefy$A{5#55DiMEKDiCU!n0zEMp!e7EJwX-3)?-_%mllV< zju+!yn+Fm-iGJ<-YHX!ogBrs9G;rS9VNaale9RMPAf1B!Gq9Hz{K>%|9sKXXKOTJU zVGjZE4=F@{qJQ(qH&r`INZfVAws}2)xq<3!65Q;Y)~J zbt&$R0=(|Ry*F~tB=rfGX^Wvhg)ijW)_!u0V2HHgQEbDL*oJ4Y{bU=$Ge(|?0w3OI z#JaF2onFj!UT#BE>$#q`Zj0&RYHn>EHkYQ;Go4yAXBKIrBhDM(&X9AGkT_E3ha&p} zeSoI>jR$Ca1nEorhxwc*bRNh4GL7>LxU-|m=nvG9DCa0d?UK|1NY&9vr+_1kK{w4A z#ME`9Qx4F9I0Q*&p`0@f`!gMjOa~m7ae^abY2CNWNSBNAk@O0Oxt0Z;k83fg?8UhD=X3Rsa|5G7Vn8HX>4Ou;i_v5p6d|n)rlPQZ zN$MD0R$*n-z_Lv)W4*j6clM%Xt#4^D%L~igf9%=Ewrt+sS3o-L`Yv=vC0Ldum+EkW-V&y&tAUAREH`YQ6! z@ts>9wz|tO^X;OEi>}4O7lXHJ9hhgu6t5oelR}45>0injxI=Ka#0BB6S~uv6X1jVj z2I2xE!T)V>=w!WdCiBpMk{o38W&Muz2n3bmnh**+7ufj_XOnH1RkJ?P#YRBZGX|Kj zWJzG{S(L@SC|*oug$*fCo-l4@{H|!f&!FNfFazGrR5=QF&~RZMF4pOr*}zP&5x5Sf zeSqqN@thUE4+`sNl1`N6qZLiqbhzn4ET||i6j!-o$wkrtyV0Ta&AiXv(|v^ zHn^6>ScYjM9Q(d_v|qG37RK@S`{VIg2B_K@=Y);NYP}%+y1R+}l>I7>hOA41wK3Dz z!TFfSu>2l}AE@_5`?&dQFca6A1Jd*r4m$uVwnqL05zrsG<4Je7zvZzfwybYzd5kIA zK$K{5Z**S%BF8 zZ)z{_7?ly(=$<%qUIv@$W&>xf-;@GN8B@~x!IKJBk(XusR+K2Lw@SQ(iB(m;26+O+ zi3kkubpXtZSOFOv<&v1Gwz85-6uw20^wY9ZI z*(?qJ*>LEwm%});lS@dUBdJ{@V~#F z&4q99yxpaqw{wB#ZM)I)+Hdl_)-qP-o0|3%=dqKJ}kxvNBc|F>d#rX||6~3lBa;~cAY9)eq`MK~-DkIU- zOA1fBMGR>IpY#${4?R13+y4Lt3GV2?I)bP}jc>HR&$~JFs=69~W2SkqFWR5k+}|dK znPr_de&^$&2QTZ)&qsonb?R~Eyh^$a_N%HIG*D3*ZU%oOajy(ZtCLi89WAYp2T3MA zlHAduP<&-4Lf>Qb{qWmVX@E47io}V++HSmFC^vD8Hrx0|{Z}YWa6Ko3>PJ)(2VH}5 za9GXSVEZaMh?dsdIkMwAEIP8we>4RX!g#a~7U^J?1peVXDH;H{{Ue@DKu-|O0d&>! zXlLU^3Bw@5bI56kp_$aGRDFX?9gKE$#nV|a7c5yJnmvMROG|z{*Lb^Lj1FXk;Nx>m zIC4e$k3P$yP%wlb)q|93sZ91Oi)#CcEP(20yP!k7Ud`-7j<-w&R z=E7arGKvP6MXm2|i1vdv&X565RzEfftI!P39E*AF1JSNM5D1HY(d0lp8rvsI4PbUwW)>09ZyI;dI6@Vww%gc3C#Ae)c6| zu_V`lqEapMP~L9p77WEo7T5)XkMb%=P^lMf>Oq>ZfA2|CXQpvDJ%h5%U#-<;+X`S z0{EOzB4~r~p;kd8eLJl1#)WGnFQyf0CPO~txPo_BKN;`uVKT3pHX$)E}zh&`be^eafsw2U2_a>~Q@?9|{DLGAXn-n#iJ626L14&v>RQnvNrK z#P$gRD7U$#Nv?M=Vb{gjPSJ5Mp@X zgo&ar4xfqh{U97KwBI@QSM(0%Y;?jp24=YqG*b?Mh^$IL{Fz=s*H>Na}by zo@i&f6Ce#}HAnaLvF02AqAlY`x$*cmz-Lpc6S`lG-^@va1cMO}QfnO}o4|VjVIz=U6p`0O;Wj!LA?C8%O8>Ri_gV*AtL*el( zeTJzSfqYpy84iDR?>s#;K>CoGZ|P?)7%Q2A69D!xEhH(t&pJfwD)=m(CKK!H!CgVh z7H=!BuOpG!Xf&!VjHl&nCJdS#d~2hCeF8dffiUmvNwoj%Wpp0MFrvj$e8B=_(_`FP zngRVW0gcVX2M6;Boc!$Qj&kqCy4nZ0D+0GM_cH0&2dlRt8Mi<}0v}TNax)ZDHdofx z)%g#x0kj{MV@M{7ibWVUs7|DlY5j!q<}9XK|j5I=whr1MHbvA;bnMt%E5a zAcEQE{0#V5IoO{bNcAQ<6Is&kK19JnBFGUOaA5M(7fmzW0gRPg><{+v#oB%7fRNk= z;|UD4a`&bfgWyMdFHw>Rxk4(ddOeNhDz?$3@cYdpp5zgdrS;s+OKCoG9^fCzzrsXeSomg zqC5CFT~~Ww-I`UaU3Fz+b)pXr+=I12$Uv5r;05J|aK_CkF*@}|`TU3%k@0Nht`z02 zynl6F-L)+j&Jf^bSj%07<<`|*>vG{Z15shWx-$v0Y?u_WIaW5lT<`^J0**=uUowps zgc|GH8iz%PfNPp$Te9DfpV<1y+MoKV*E#dX05Cq}xPnWU+=BXxnH=wS@RCJXHWBY+ zSnbJ$DL+vlOh-D-R1R=ZA^ZIW#X7iwgZO^R@J>86fa@=EiXis?r&4gbSSk|_&Jn5a z2GtFX`5#lFUgVAr88(?^kJxyBjq~p6jd$(AjRkT%((LV9AQOM3|0ob`tzWV%rpIG* zK-JZEd0JU|iy2AeOH%|Sq7_pb_~DtL1U)^zO-R-@DOow7-^#L+M`uV<6ihQ!H@$F(B(}~13J%yP zZH026Jm)^z&UDn=t(Ry$O!^kb26OIzT(uArWsptlW6#5C4_92dA&N&4V-p{^w;s`Sp#R0Rk;4j zhr`y+_-2sYM~~ zp`>zo?3JkZCx8UAHuxZxdgL~ox{UjYwm_f;oG#q|!bReBgsq!&E?w*rgNNUI5 zwX=_U-m%8OnlL}}O05d>HC;RiE4PG1SA@HAOR%{-_yUQ@h_2s?Og5-RTwF*&AN^pK z`g&|GKfF&Q=Mycn!Q;C)NG(~O1s6-TK+6q9!r?8qW(*w&^M^(UC5EpZ`|>A2ksruG zO3D>4&)XKWea@3zx$%o9P^(UD*;G1Ba>`Lwt}W8B&q$lw>^Tt64kY4?2zSIXd)+G3FZO%K7ZF7B06sqhKo# zi}rr?vhL-zVEgAC4VD3-L-<*M-w3+Sw5Sy`?uaLA!3ygYLM7VoGtn0BVWX)i7vgdY zX}-v?I+x3`ZV>`=jE$HMx$xOsa`dBSRj6Inz1)L-&g$XAFX5L`!_^M;JB&v1#JCo* zk`FRJNXT|sWE-|QqWT3xWQaz?$ zi!mhR{99NzVY#!9KKOqJ{~aBI-|+vAby1$>!@G9r@>Kf2h$VX>j=5jNGySk!&(#Ay z{uG+7SpOM1q7~>r6Bq~3O>ltUY{5qa7;^ZcFt-5+oP-yeT(poyrHC(zbWxC=MIxQ3 zXMcPzj#ci;xN!`4NXYE?iCm`FQ(oD)L3mdX2MutUOAThZ^@R^f{ob7Nf%NMjIY?Ue z#l&rBK81U_xP4sm(P~4Xz9jBEbDXsSY0ifyIe_4o=z2JBmh&(l=X5adqUZ0z*}MF# zPTW&}fIWwBzc=iuKaP9qPm_J$=PYL<$aytPvgQ6aI^oV8y6x$EoYgn%Rpm;)%vhHo#^Qo#|`GdYW$TfoG;n)$&zw(kN6K3RB-VHzJ0BArhjAkTjBTeON; z3kuk&aGqa~{Yr%(d-6MO@{=n(<*uVf2>JTJG zQ?#!0%{Yuh!}lr1U+7pSj_l`owMi2iOay{CfqZ6?HlAi9Joi{aZh=s+E^xak`9I%2 zRJpelY&pf&saSXp_c&TPT$N*0;{N4=v4zbQ(mjbeV=aoNe~6t(&|OTwgk9t=HsaW+ zso^rfxIWdFWV89+R3_dpYXHf;IneV*?*C)(3I66TcruXkj`CL6Kz0sn;&*Y$j?EM^j zPqFvQ>|M@y`4oHCv-jV!cPo3(uy-$eFE3>@X78ui`y_ksWAAh9{RQ~V^yo?U9%t`6 z7kFM-f~8#m{ym=eN%kIQ@4sR18TP*UMpn-Atgh@`&E8wt`*fP&#@?rHV)SP3Dfa#v z#Fu&A_t|^&!=5*vg~izKKK4Gw-mkFtT@3Z&lr#$Z`ZxMHRZ@fj^e>}k6hi~<~pSwle<30aYaX)p#t>RAR zO}C1>h|}yn!`>&^`^X6f!``Qu(s)<7xX-wiy&ISU`D(ei`}_CH#U0e&Ef;qq-+Y_r zt$UZ}HL~|8d;bi3pZkjE{Vsb~{L%l<+WP?HS(WeO&kGDO$w*OAr=en^B4Z4;cZSOU z4aYV%*q%>I1)xKZ-g8wnI2Q8iMa$U@mYHR}-$ZXC3EnzzN`oz#cUEQ;%_+#m_s=7mksC z@ycW5=lTc7$Y=F!$H>p~E5JQ3I!@%V@=cz6to$yoIadD0w*wCXZwHRQ>^T2ER({Pd zI8Oex8-U(_I?h$VL0k_UC!f+Waq`hT2iOU`EKa`4e-E6%^*pXgkH`7^@u-&wkF$t) zq{rC|OgPbTt^w{p+T#oZyJJ1haVJ1dpY1rKz@4A-I4Ouzc!tOMGB9qA$LTszzRN#{ zID_*&&V|5T3p~zxV6)fb>;`7!BRyc^B9G%a3FWxN<6O)=n1vo^8!!oY8?Y42VF=g{ z3_lqRWVOdh0}ib5IMu+~bspyq;N+>Ozt17xmv|f>aG%fP^a3YNLwQ0uncV1cGJ$)l z@m=xkCmv@H@Br`$;Py+wY@k#20M7*Oy3FHj049ANkjsUj-x1Z*5dVs^p9_R0;%4hiDr^&zjaX>HViNIcz*HYj<#J>_a3HnXIDDcY% zf!%i~@1qYuw@N^|`y6LEa5t2sUjd_muK^PfFZ^`m8+`A0U@R~Xxc?-NQw8h<{_u49 zoWBRS1L4PjeUCZLJHR-!(?opZ4k%hVK;PrwPr#O^(Vu|hpg#!gLpy(gVPNDLcrV&< z8Zdqk{0TVpgyZ}a=snrvjGiIC=o4qiU;UYh=szVMrv%uI{!|AneaLaX1Z+V&`5|z2 z8tM_~1AYpMZz~3ebAa){9N>On1#nj}_yy^!9Os@h#Jm4g71+X2s4mgJTuLO?Qq5S}7 zFL#_PfkWtTUBDe%_Dz_5!o{0W$X_(x#u?gbz60;7Qy!0sy?=PSTW@byk$8}OOAI{ff+7~6+`2_6Bg z1)u8xjshPA_Wr|h!Z6nFM0g%>68vHvaNvCK6JX0iycgJ8>~YS63@`$$1}1CYSpt07mbIM29x$EPsje}keG^;OfIQfNKcXOSr~y9S*tXv$#&eH3wHN zt|DAMTw8H{4cCuxU5o1`Tz|l|AJ-eWCUHeU9y%6R7A_yII$W3I`Ub8a;`$}7-{I=U z^&qZ6T(9GL4_EZLXot9xaCvbp!c~lG1Fl9~Kf?7ZTwS>Sh-(1XFs^Z2hkXI<9@lJK zr{hY;m5FN^E+4LDTwllaV_di5`ZKO)aJ`Ca0@q>a569t3z%>t7KCb1sF2YrZ>&v*l zjq4}4evRv9T=(GmGp^@wjp2$!#T<+4R9xranvW|7*GgPvxax3SiE9V0-{5)>*UPy6 z#R`SIcsQ;&TxU>r#8rsvB3zf^`ZBKX;Q9rwy|`}2^-o-rxS}9i9*gTNTwYv@ajnMX z!*w~XujBd=u3lUtxL(H+86#!gV*UKjV55*FSKL z;d&2OR2JTg>r`BGaAn{sz;!XMZ{Yd~uHCq9z;zd{hjIN4*V|cKM2r5BZEWx|5;i%@ zRuvYO6_;>pq3&1b<&-R6>6G#K)-vNPSGTVJ-Kn5)-HTvDZCP!auNrssaYL=I?g|=k zoaDKwa~Gy2=WtParMAW8wKyTc#ZdDgR~tKflXZAgSJC3gknhl#D;vL4Rm(H{eU(ic z8fzMic4_D7v@yUeRK_dkdA-T`HJg0?Hv|RTSGcj$kipOc44i3%8b+PJa6Y20mLDJg z%_1bkgEtt%JKu{K#jaPMfZKpr^HS1M5K9M!d7N_(?MrCXM+ifoYUf)-_CjJb+4jbag{O0%_i$1k4&Yc^R6SI`w4w zJQkqe0aL*3RGj&Uv#zqS-k#MRaJyB&ILJYk&sXC95_r!kYp0oQZ{EUrXnXYySJ+LJt93Btq0x9jh0nZ=mg&5=eY||$D3-Ji;OGnsXP2M5AB|g?9Ea< zdWQ)LaOp#BUzWqbhjqgfWmE`UV}^H8*|Q<|ysl6Y&L z3rQh)u6KT_Xia<(+$o{ud|5x+HUrHG4m7gug+tZ8bl+29;A&r|x+6VH17PB&Vh?oNqWJ5Af$}6=|(A1BsCr zrcRIiMP~+5a_an)RPXf2>zo-#jO@=w-X>pgCISeI=+AxFoou#ViqaW+dW)RPmOOsG*V2bc%ziA;BhpRIklnuwRE6 zNQgWyg#)S@OTi|0lEj&TRBuY4$?+c-YQ~e)`DCsk?jk~h5F#|Q!)-Wt!kd(D|kEe}>#+@Q2r+U3ecTGVVI64HtGBlbk4Nhp-4qU11 zujyx{IK1i`cqlFZ&UKRKr685`IrwEx=r41@FVoM)FK18tW&Q&5YUFH3xI7MT!i&-~&O;{nCpZ_W;LVT{Yu)w{c;Cc(HWbE7 z!?=sQL1;3|-<`mFH*eGP5SYWM45cniO=DvAJX%F^rV-W`GzIjqD*vQTmF2xnlxS<+ zaw*mUo_RcE?@%vJ;}BDEnf<_gh+$@6XmVyG)^BNU*wXAYG^gTXVWp=pOby7lX-H0J z*j9(hj?LRt)(=6h@;73$)yI6ecxD!cC56!${AHn%;WI8t@19Qg7uF$4WC7$|^zFRO zTPkWD|ILPqrpn^VO-*J($7yOtgTWsb!2&b`M9TpSvOuA!seTJ3j_D%MyagN)QA9HlX@2Aw)@cUna%7XiIZ}t6Uy} z>{ekK*hdPVHK&(JZVL|8I5o?*)D_iKZm7gJx|r=gKW}dO{Hf=4*q##_4q$IwMUBVO zZ23s=gUq~qlM@nztj^;`5jmap7w{puQ(KE?rn(wm+I;A9e9|n3Fj-ouznO9$~vPP7}kj%m6DRYs3fG; zrxB9V=cdtaLOM5Km=T~1&HVC;MxS#CvX;Z~S^rDivuuJNoksQ9!)(ZZ%(*aiJZ7Ef z-%7z?nK37XkhpxpPx5cJ2#z^LM(m0@qZ}otr!Gi!Ke6$O<*Xl6BR?y<-WJIWbmPMc zh1667{XBgh4HkCO_A(u2qyu}L8Hsox4XS|*9>G7&L__oisZ$NY8ka)Dwhf@_>#M5J z71ze!Fv46C4NHP-=)tvSIVm$juE^<_;cTbf2dM4BDe1hb`(AS2DUk=RA@ z8_7jNf;GXsCOLgUI=a46vV(5GeR8@tb%FVL%E%ZLuzURxZyq}Hs=|S7*{ z;;svGKKfViB5-7;#Kv3{T9MNT{xKNCWAFnk|Dw z<2N~nqT8n%E_bD%`EzOY&0Z2x!qW)J^XGAh3>ZnBpnyc%rZ45#ff~22^7JK_4gC}gjDg&lM)1Fy27#Oqc_3$+ zd@w`ilNY3r`ypj+|K7qwN!*>%W}~!o=wI-tci#M};Cj z`4XL6gs3dDz6u@XM($~J<%%uM)%A@5I#ghp&Lf8|sjrt!)qmsve0O4j-|lYKbgG~u z`2%jvF2Kk#KRLU2MzP}%%s6l}>6^#ug_5OGH&#b7iIb?yklL7u% ze2+g+!%&W!$Awhw&pP-z=j0zwN?zQnG$xjpXd-XloEZt z5Yvr~)3SDVf+#qg%&`)KWfP}DA%Qh>&xV%Z&qQPNd9H1Zy0@843<)NC7tAwdx1}y? zlP|Bfrp`V+or0)o#xzO~8DIoxy_G)u+l)j!ke=>N=4vr}6AHAZ(wTu&cLjHr;u&uW z>VhkJS5|K1_aMCjC7zhb$~XlPi=nvKM?FqLzr zZiFm-U`Q}1dYV^L-GS*X`l$5h%Yq)5;G&O6-#gdXv;Dg=oS=YPW{j9_dY#**XrA=9 z%|p@A=8_je_)l?PVXS_PV(os1Amcezu$lZa4c66Z7A_#0HTH1mM8@h8910l8vYIZF z6hiAzs*_bz2;If0Y-}{DWF^N*_>7+p2vIp4E^3Vy1I@6^9Lwrx4Csz;YwGnyWP=Uf!F&L{d zhR08scyXB|35`-8m0*CGE<1E|1Hl{xK_U2~((sSiLBr@rJ+X!-ZP-W5{l~{PB`zgY zMV(_z?PdP#Tg&@QDOKw?TC=Pzr!1!qnnazC-67C$Q_)C+((2HHW+I0yZ4(_gxiwW) zCS9{aK~^QJ$D0JKEI(yt!kwMnZJZJS+|=Ab-lV9B;{Ri6rqE1wOj`a=ES^R+YdAh( z8&lli22*W_dfv(Zq^&hSsI``zvP%K4+8Xsj%# ztJ|^(Tquw$;3p&02l0WapHN#xI#=3^IUn_g8Pp$po^kL~=7F-{XQwPSA$cET!EP?m`TnnMDl7mAh#2#?5BiVY^2)tM%sixR zO$Wl?@zUjUO{JSE7foKE28-{-KfnvBGkq%mM;Tr6Gs^1S?NkWL(@Y6VxZss!=m|kfwr~Z(!>3zEqQx;ju7I|2USjF(LNIug$ui}s! z-%3F@zv`*Ds8l;Y>l@{C&on*zI{4>d;U8WYZNM&;7gki>Q6@7K$zc5^%NeHIY^#%Z zDY;P+@ql0gB=Hk_)LOwh{u^jxNONI-CVtq`*G zLUPnlG<^7}!LgbH?=E>(6o^RX?zvq#l6*a+?0YQ=D1vM%x1vxny}{U8oe$2x;l`!s zCW?P;$=!YjIR9&T%l4;(;>JRKpy?ss;Z#8XyWvCn@R&}i45f*_;)|#^$aiyj@qN_- zLIppK)oaM6pp;oZLqBEdymGUy96%)p+O|D3ky!qqrPPks8J1+kU{b)J9VILSE--T>jb z-j!T;+4{wh{2mkr-YD!%R+PNguiyzyY##f)9BjH6R{D22rKHE)bUa}0IlWxPfEBhP zAN%fAlSiewJErGdhR=(lXm3-};%4?KY3n?nXNyr}On_f-SWuAu(W5cvse5my$nj6* zl1zphe>AN!ksm#Ey2*0k(RfNxQ1WlKqqGSUq9UAm&0D$|!z!4SGc8pMXZGL6Z(SC&L=igbFh98lxpN2oP*S~j6Xlrm}W_;?^Ag&3MDO16{7Ldj=EeJh* zcPBV16bjAy@X7$vW1lipeCN;jANU=Co&u+x`;OwF%-Me=>Oai0%tNn=%`Wfni)gCts|{drGUg&tidKX$SR#|aZ+I?KnMhKPp6Xtvoz4C02? z;%qkF8bA;M=!IfHL7EmE%dFd84NVeRT-oVGrR2B z{bRn+?H*D)zn!*X4sc&fx0+^VCm*q%f9a4edYvvHP`R?3>fxbdn>#ZQRw`x6rZ2Y0 z>#*@G;{Due$pP@yP@Hn9!CJQFW^uq(O<)#W17G>%WntW)i`y4VTVkej@h@SWY2MAT zHQt57J5u}}N?*M#jJ>_y(tpbiJ?ne#lg*5xPDT$7#?+-*|9->rg~!b3y`^OVg&1(u zqgs!~%(92d_$O6yj9Y_{@Emw8_0P9N@Cn@_w{mJwR?rLfv>wTnsrq4^YbeEtTY1YV zy0SOM06A#7VrMib>O+5F)@t~XC|RN}{mA9{I}B z0sAdTb{-rrMBViM5StE(|~=zHX^ zQd3hm;|+3b=8SW3T3y}alrsNZxpLDG|boDt^H@TlxUX9C-O5vtcxaTFBD4P`nu5{^?boO@a@G5v>cyO| zR`fwdrWTI2gRssjnu3M}oDlR60@IPAJbB~?Zf<8QJ!3k#S^B2+&dNs&R`&cEpyw3V zF#6}~XV*`8bcXPtM3?6(SBe_{c}P?au3RW;{QmQ+sl2IpKh)sLG*G{{2yL%3^05P> zs4*jyps$}gC0XJ!#9Oc+#UuSfTQ4MA3-GHYl7k7S@=w%sj1Avz9^if69+*Yj;ht&B zI4oYSwu|1`t{!S~!_nA8%-oZN`QO^vPL5=E(R zfpD7SoG?rYe21Qb=16wB$GjKb?T#qIAFpekJjvr1p1fP4w=^*WFt9&})4BbZSRM6% z!+%xuuRths#=qg#syqGe?^Y4-z9t*%IME@#JoMJ79yPurVv@#U4xby+dcXbXk;B$z z{xrj8>O-LsUu}a?0U`4aUeK3A9{aanGGkb8Ug@cjNJ188T5v-DHYxq#mHv*&IzQpp zW<}V>d6;q<`L%CFT?q@%@e-m^;9{lv)rU;#XR~&*4f!pCiTLFGVC=G`0o%o<;k6YY zM{KqYh$Z_j)o}vL3#E}l}kCa_ZOqo5KbH%~q znyb94QclX;?8gwN*yLLniLaYZ^MJpgS#pQo$j_lPV2@sEytwaS_OBRvwuS=Oy;eT{ zXUWrdq1J@dZB4}@s;_0^(qYyqj#`ghx0MVXnjF6UD<0L%z?LYLS6bx{7`wbZME%`) z=^dK5^s9$kB>2ThubpKdh4C1>1L|W`TUK49hd0Tn>RJUhXo8- zXiBb!m1IJnE!Y)L*ol@YrLPB>G^)P2eIZL*;anlsOf_7Z|Eu;hJ}@FEGrjiQ!%Oc^ zcxy9-QtT3Ow^vO-SLejpSrzxp7bI`R30C5?Q_y8$l6Y-kVPf!!uw|buwK%vxQX73X zRE&P#@=&3W@lr!ZmD|VXKhX+jF+U68LlLLC&q}(avkI?_vWEMkHo~X9{MD-B*vc>N zeiV|Qxx;sG6tNDA{oyG+I@{H$@N8D{6w@qq*L_ykkh*pqx)H`Fw>q3@nZ<1+$#Qj{ z&~+?(9mOf|JYVe|2utDte7dyLJ96>$qUiWG5`m);7{Ld^nUk6v0!~75E?D^l7BUt- z782tw-fSo;d&%2y;G=(ZZ$)X}j-%b+j7UhJE&Wc&ZK=$QStmbqVt}_=sQ5-}_Y)z$fy{@kd`y zLD~U@W`k&26H>&DdvlRli9@uG@1Jn%{NM)C^;Ay{~xa;GZdR$C-M5V7eB%P8$ zPZb&OiBR#BP;Qb_B;8$Vhewtjjr3Y0~< zv>l_IzGv^Js*jIIS>52E>yW{{{8G137l(Nx_3QIUi8y(xXJMzCU4lRa6RuUfvV{I^ z6`vRlM=`_EX$$tvf^Ll4Ew{_EdWs0T7RRkGeP3Gdf$VqJCcVpcrMF3^WXXX7*svg0 zZKogFIre1N;o^m=H*c^AYiR?6sh;H8Uf)o?P2foZ2i$u6eEKXkeu}#!x5xcUWbnUD zst|X-hKZdREYs#}5{v3~G3mD;AfCDt{d(u!r|Mu=vV1ErCx&)x8D?4v(GE{V6GVM6_he4+5LpwDf|lm>bqS z%|wMR;GYq){$`#+7u}zYUeeD9(ttjeM7FlpZ{~c>HMw)yj+*F9!eVg|GiEeEN-+Z6 zLE~2rGbUmUYWek;@LfK&{ag-nZDOS~BiQFv;;7k71r6{|Fx^@bU^1lSo@lM5C6!x1 z1q%J#q07juM)cyc(+h?oCOUN4&d}Ti!hg5h7JLMO#D>Ri>it=9=u$Xf|CVcGysTJj z`QhsD96Dom7uaLojcl~E-4!psyEfJJ2{a~tvHs2$+W#-&F8laVS>`pJPk@A$W@%H|Ah){c2_PJRj5_O<0f$hTa_c{<1pI6t47<=aZr zvQw}WiT^RMh?E82cdK1?-p$zFxph4OF3VTTv>K28iF&ULdVwd}7JLGMULa~=b1fIf z^^9w3RgA3QO7U2lL(dC4U%7=Ol#6oQ3%58MH31Re!bwnvSK;yA+PAek2S?vH?lTdK zQQ%33E<~GixCx$ajb4#k^yNiZ*UEX7G#i-9Isl6LPdV=8-D^0wFXQ)`qKT-?MSPc6 zz>niSy>-!BqCXN4Y~Wwk*QNE#zPmD!M?2Cp(X_zb6YQ%4oygpeOhh<%nz??hUZElg z%w-eMh^kWt$bnAltD4!tQ&>T49O{p9oZJsi@8sjXz(`V{!jGUhzJg6@vN)*Q>bh6u6Vmm(Xl%sWzkq0ABpy#uuYzltFyguL5){+tL4Q0ePbJ%RyIemvS@F z0N@UuaV^xuQ{iH>YsEHh90;shWZxRrS{B0Sk>2~?} zfDp+c&IB0)le09g(Z{ApbAc&75Vp)}lrC;{uQVGYl`h~m6U~;N!S0tD2g2WTKX;0y zW_x8H?}>lkYTSOgH0gVPBXkH%_lx@RH_y7|FS~1Bj%;VakAC%WeFH@&`9B6Te83mY z4BZFbJ;fkRJ(VvcyH_}yEF*WtBiE!WF^|DaR}atpk5A8DzX80#L#M#pze*lJuKg>G zo{(P7D}Phun~OKyXjC{#{|!JTSmEeKpx`gZKl;8<q%H^Yjkf_`=rdS|}{BJirs&3-LK}=L` z`djT$M5Pfgc=Z+COMod-`y$=&%L&bl7CjJEdC{*$4}CFiFsZ^3=(-LBlBWc1Hb%K8 zDNKfW#i6#}0CeyKX6hIKhkwYjg>W`3|9oZ(87ZhMHhKnzT*azc=JyK@&}YE5X0#p#9%6uJ z4^%w$3dQl6D3-@zeBKx+b< z4fron&eDA56NM_Cb`4#zhPvn#JG9J!5$naXHVcDi|FEuM zDiOk|{9PA+dRDSkzwVfbng@R)$c%wW9=ammjErl{i_n-)GjfPR0(1hrcQ$w8a8MO3 zAHF77g%E*IE_kM)mQ~^`+&m*@q8Wkyrx-w`wCDtJH$hsI*ef1)g!MaEinbpnE%ngg z@p{IgG|P_NqiBu}CQBzMB%$|&<$w^K3p0j{%+Snouo62^RH_*Qor22`|8K(p3aXk z-T57@D*swq94@Xht(H;>{}UyZYPSLNgvO2+Fb&07_U(rJu$ApfGZwWA=36kTLaC~< zbXhf74w$+Jw4roW$LxZMpcyS@V6wQjsC_%K(WVAvs+#c~TlRZ-=$XW65NZ8N*XvtF z=FDU<+xsb8|3JB@f*gdbz%L5i@|;6O8!ShkbxADRKy$|HnJ0s$z`%k&38HR|V|&Su zz7zR_n=#M1x(+I(B?lh{ucTEONrGS6Layzg1qr=q4W!ei&P9AL`mH;(D1lErI0cod znh}o0)}wM&$DB76MU6#Wn5FRHsN&w2cPF!0a(?PL+J=_bE>G|3xx>^*7yB4lj;=9N zPuHn&W~t>Fk)+5M-_ENrFD96X|Dig2C7C)Fz_#2mYnUn{DTOXEXq}$)FWqi2cL}|j zEO}@pa4n(aMbjg&SIzE_7@H0MABMnG-qY`-gUmjtp$hrc~!rm*-Jm}hl#>gje zEIu2Wh+b$H3-lSHO_FZzu{qoA#r&&S7!(WiYZKBY#eZT?<5*7j1P4k;aa)_Ew?i8v z2R0Tkotr3k$8>oZK|p(X1^sP+auM0JdGmNwEbz0f#U$$@XV)fOwWzk`+Ev#k^Kq?M zV5Duw?&IUHVu4XXGTOQ8rMOA6#VngMCX8Qtdv8L=-*0=sQF;-8PlL|CXf}IGaH1{A zHpO4RMSd`cL!`!M*()%$jmvy_%h=;Wtof7w;;_L zoYy94&au1Sx%Kt<%q#F$!BX_{uG4uy?-S^R1+&Q5M*do~k=@70sat^qT0i1T+GXg@Qv1L~ee#}P+P8B!ACCoU zac=_SkR{Z>dE%~0oD>i|VaER06U?aAU%)YxV2LW{-ZZI^dcBM?<=(W3LurWy2^oRs z3oH_r(X%3Z-f>4iEHUY{KqNuz^)d#+@E8#M2E1kqoSi7?L z#4Y2EMK5-x86MX_4lx&peSXSF$f4TBNnh~95U0EG#RVqKL#^5HV*$fn;{Te>AJE^_#a(Voc7cE{}x*Zv=Oqna=;kx-TfVr+F$0ylj_y8oWRL zca8N!L|2|7|I6T|fxl~vuRA@nAFx5L8dgu}H2$t_yzXQSjOgNiye7dw6*;Ng$@6$E zNV!w=>zC^wkA6(ZKxFY*7df7cvecamymiQJ+!a<~~O9r?xT-tD#xfy|8a zdz(-SpT(Q~lU2`3CgaiDDhA%OhPh}Lk98X`pdxejxl?%k{!4C#j@-`> zkaCA_e<;rA*--}3zG|w?@v7OeXqo;ht^qVBb=M9%b02%fOTX%mSgS1;+MQJMkrDZ1LF{2o>qIm$M-VG40P;E93#=m- z9Zt%zQOR}7d>2@!Vwu5*#qI>w@hs=^v94?cCQe@`@MXc}0_$ReWvyfn_ONwDIeDU4 zi22|>myo88JT2nwJ^7SD@9M{ze5<`*yS{ABQT0VOJL?XLl3(wwX z_wnD@1P66t&K%7b&7+6pwq=pzapX-Z1IO)xn(n^^HP8DAaC#?E@?3Q}&Uo}{de`Ig z30*4r47AJYPM9kIvFJm2@{rU?LlPt*iejX}k*i#`&$j9IZITg*%?%6nSv& zGAC}3>k=nvIEHd;4KrE)0>=3=a^;#Il@p;f|EU0;rjI4S1Mv`R78qyxD3W@VG{B4} zxNw>>gV~iJAp%#t->SDYmDuecTxr-F+Y;=nY2l6Vnzt}2;+G@(fJa^rFAbW~KoJJ@ z?3A8QK-9~fepH7Pyy2e9nyoilq6pjSr-|X*JT&(x@~CwwBAtz}cdu9v)+8pX{2;0! zp^PW)5bBc^A!@No|M2>7WCBMQ_HH2!K5T`N+d_Q3-l=AH;|T6Gx<(10-YHS6_@3N{S@>Zrj z_$l0Ja2*b!TnJP9=Byw@HA^Y~UppY+?g^I|Et7BM$E`UyeOMHH4&De)YE31-lv$Np zErXBS@=Q(&<95Qm;S!}06l2@C@Ex*d%AK~evz&anip+cqVZQBp(bW<%*e zg>gPEi?cEMLYyIG+V2pGw7T~2A!88yNz*y~i)|kA8vBqObsVdEz&`}rZKG^NEG02E zm?MDus0p4W1G~8?uc_$8jO}*Yy8?AdVZ5q`lX~YYr^s3H2rooKt+6Ka=ky_f7{KLWzHLbfq*u#J+o5`jkLQ zY=l|BZ>+lGmnRE-Q4~mPEZMbE2U&2~7@v}d^%Xp_pD`@%ZLfMf(B;gp*TC0c3wR0=8z-hbQ-sX>VOyxlQdyd##}Y7>9+gva{!iG3>l2^kD?UZ;bi zwAu&?w`EftRSIo7=kPp{Stb3Skm!c_QLe1x#_n@&1V^sf-fvlce690PQIJCSBm{mP zkL39ec0}0$A|^dkVDv7&G=Fjet4x#VL9(>8e859brYA(R+1zjbwtxxo}jn@{ZgnRWYFQLpwGbqokN#^GUx= zxL5csaM<$a8AkXmwwY3Kh+Dlqn@tgeqw-o&!*lrIO3 zy@P_XE5LCCig~#q&H&l1fK_HUfYsX&48>S+EUkPb^3~SJT2NT_6aKNcAw;;>VhXzPT$$c*{^k{A{sERy#8^sbCUrAenX#1Z zo9J6yUc1uP{j@T4UP9TKaaP%xQ@^32(~x1{h28w8s0^3uqzr+ym$z<9((%}paXxe? z%Vw!F)MYo>2iTejl$5%mBEPr|&@H>Aq=}l5tkRkWUH=bI(^Z#JlW?;eD!PRYMdKOk zcHRn)Ht~?zXLMKM#2LiS&>T~H*vXO4JkMbpn5_5Z0r62}8OOqfr>MizB-pKKaC|3P zm1@?43olM8grtMDSvMvf;8#?T=p(92AewR&z{J@Sf%+Op6o+xPByFX57H?%st$IMD zPY{P{@QRG<-~pWoxa;NOlPcKH!wg6xrB{3CAz6?LJENm@`Qkf83XV8irG!`O! zz7pJnzqgnmN ziU9fG+Xt>0mDVFe-Trc^QAj^g`W1GQVn697?IRIL>!l~fiT*aLaUX_K`kVk)?O>c8 z_G>af@(gI5spWkX#aZ|;3b4sgcHPVn1@L1mgY^Dqs7M`cs8}m#sJQ%S|GG#}NYOn1 z^tbKTeOE6MoWAZme;~o<>s9EX=Cl!BQ;x&5uqqKXU8|3JW5%TOmmoNOT@dX~FkZ9} z3i$x4-7l**##@KhUc&8a_XRuN;H|x**!i>!L#udL_~bMVLvbXKT#0CIR~ULmRdfR; z-j@-d8#x2ru>#Y!IDym1rHaFrR!x+2izezto#uwal*@FMf8%}?xUqMHz2aRc6b??s zxSYndyeL;hvP`eczgTBIa_yEzQ^JM=TpJlyXxbC^s;AkOL{IH9;o!}~KKCMef!8z;L5K+iOD&qG&Q#LEUpf`sqb!wZIPvzcp zsjRClh)l?C(XNWliiE-J{uFio(yubB7I_rvBAjB0uoNIpy%Xq|vJxO1E0tZerAHSb zcr1h<$v(A*Y^@B@A`DjLRK(mP)Q2h(^H%L)rJP7=&ZmsWK{9<`u7A6t2H$cc5)VfaQ$9cAzPfn!D^o}j#DO?3O+Xr6a< zJ5`OwW;{Wb5j}oC)3Cp1-|@&EojO%aOIYCL|vr$5K4AGY$#;#j|G#Vok&khVk1-oLcN(3h$zZSeRv z>m~g>AWSY}Y{)KBo&0#+7jAXdI%JpTbklNVCoJfa?Sw^vdTn1;fx10+Y}!Tl7fS#o z3^1!9r&M*wOn_%)T+7Z$JP(IC23t!n@SNyYdr~a-oRub7*p}-0&L+V_gkURc+QALq%(eP~vZ)s9ChWU0=?)m~OwYiyef)MlwsaB&2M+s&_p`IfFIUux&)E4n|}ffK4sWjJ2JE5JG_w#YU^Z}yKUQ~sR&vSAhZWo z9DZFgD=GoFr{pTMzC(B3y#>1o_gQ_gPk_HB!M-8ygO{1E9G}Z zpt~o0l~$|o8oJ0r2X;&wd7~m^#aRN_&u<_8WTy%cgq;hT`cBdw7BBU#-#Vd9l}?W0 z*dF?DVAS}{S?yIkL-#Lys<{AxNVad}-wQ>zr-?c$!matG;Vi6S>tI zF07``eE!RA(mv5LdSSi+?F`D=M$67iisq6(-tw|WAtVuqyYd_@lmQfCw4Fn4)@z7H zblJUt=fCNh#v0$k4O;jBog>chBlry>EneR1RsfaP8EdBUkFO`by#Hv-$%en4)ZcHp z7a+28E8J?8h5wluGu+3T+zasg*6cm}li!Rjv92{8MK27Q@8=GN(H%J{4*R#w=ogW* zVmVfg!dw>`i;Ed9Uu^HlWwvRPLpWyvivz8rNGF}4L!|5|GOP;DdI^NHhL7JLJ*Ma- zo%hW-Z<#_2Bh+Z@mKhVRlik%)m%>3qiHU$dmG9v7BoT>5V~0kbLvAf z%NvW-LiP$_3fq*%J(q6>v%-1cRobB?+v(Iow3M*!oiHnh)>i?+A5Eq0Wxj#98B6QT}s~3@G1~JyqE68{$ zn96t}Yb|pEww5?)7K+Pr77127@rU2Kg~|yQJ#kO~v_EQL3Sd14v`1jc&}7X=6S*^f ztH4S?G!Pz?aV#=Y8)wW$x!mUyIdO6yMss=VoHF>~ELCv!7J%*l+n7#Z9sJeIj_4QH zaFDpd33CUAF|!MKQ=#vSnzmm4=a(O3NszKer-g7)iBLEqU33)|UI6#UF13BFgFg3r#h@isIIAnsw(_@yomFLw7M2n!9L+*Qse#_P)mc6+ng!AJXnEEYr>%49XSyH~Xt2kB{P)p0W(?JZhjZ z7bVDTfcKW}A+(UkP|GXy32LH(8^YaYf%h!5!a)ieW!>RO{kZxTjI^opOEfE~S+JB` zc8uBHoHHyXq~@f#4taD{wT*3q2ok`X%@ZZ`XwA6MMZva}dFVum|51tpG^plcZyZ{8 z&O_9_`go4C+i}I^Tq93^_;pY?AF~wwC0$m z8BXyN{^T1RB>H{S96LTvR#=)W`=r*DGf~1P*SP#_o3~0rpj88mYRWp4{gu@Kacb@v z%_=nFY}WvPx)?DPO%ABP*e}c~GGbg7g8o4}R58wa*IyjOk|ao|$<>8IG|n?sf!#NH z2KRL72|)(Kus7!FIWC(!VOA&_XZB-v;wq<5WN(Dh!jzNFzv5ye*{qSHms*rm zRhDy`&VjwG$K+KywAtBWxJ5;ZssQBg3e|FGo@u-MQzY8#=L#HN_88gu=;zAe_;aC} zzTE=7nY>^R5`kHqv&ea*vHM;h)mhOlR%4?hnxjKwS429PKShe2(#{Dd9mhlKc4<#_ zR@i4cf+H^-Lo60wNFl#{CB&Z2l#eg&5sAz9HnpJhzs~ z`2=KgNZq;a>Cj)f9??Eh=6SJN@bf0M;E9C-?0>&F=6HsJ?9$w*QvY=<1ZHS_y=iKipul-g#1 zvs;ms_!AqO!_Y?LQpogbJ{;rmkIwUGGs8FFJWeM|Lo%r~>So(Pek1HSyRpSqP;g`4 zhiipX#9Bz5tL`yD+Kox1-i^0((7*;}kI()NO?mqFu4w0UgtYqKJC< z#;dqGtK+MJOSfxI`@SE4jDnj^Ea$`#%YqQIpqk4AspuSxOK#D%Xw2-d{sbz4bXKPCPlfYvK;#HVuh_>lFi+n527 zJtzqO9HVeLFpulzlS9(y9kzn--kS<-mgkT;&ZJ|;JfT*r;JPbFRi7KoD%b}@sFOQ} zS{;bBArZ3LBD_2++Iy2%LuU%7BRL^;T7H#(m-2dUIN0F-uCb&y5-pO$Z9LR)mg1t22Ew*tT%c6%Xgp0iwNI33f%WhHdja=hTOZg4GEf_{S*! z73Ivnm;hq}kZ3caW+z{X{oWbL!}; zD;%dzoI`!T`ZUJ9k5GbMy6*e#M}pTw2=vNs&659FNks)!4|JuHuDDhra7UMnMTjp;_(J@Tv$h3*wtDr zISMvPj%uqWfk=*OCQv52exsT~@={H$@KR5$e8{~&3VD4_wFOeIc5ZChxIG}<1uC_Q zm4DpIyC#~cctTY1(aG;QgE~ccy-jDv|n>OV!tLTV!wo)p^a+Vs*ifwiYBj)fab4CTgE0|xu*4Wlr&iS zT=EY9=|B~nL3ye8$*YDd&xOf-m@NZe?hJVMyW=5v*YQx+DY|M*U1UE*_55A|sboA@ zc|AVIlPNK{!B?FSf{5QNAU+-u)xPVTbG{j$0}6P5e(QA1h2t--a`V>@$41N6HIo4~ zIiX|7`-9Y=yp(oHY~|+DJ71HN^mCK44ee?#WJW|AfPAgaE54?Ai@59=9%QJl&*VSf zOi!jD-Sc~ucLzb^W3J1OPXh;ZG{&?%1cEKmYrl5v8IK!g&)a7P?wPQKSfbaXb{dKH zXMt5g15ByyQ?f!b&SSr45Qm(p?ULd`f&5!FemNsnF90xfO=6nnwE(mpu}*|(LRhdB zm#&K|q&JPoOPnW4pH4$V=@nzZf6vAlyA`7?<2&ZipSMFWFfIjI;^#{z{P)e$Z*+S} zBiw!#yWcOjj_FQ^mgxpmLby%)ysf+Uh7%JWw5I^?WU*zHy-2CsK31~K7AFRDSNKdwqTxHs}x#w4!G z@W|gSxV)>_wNTA!PHf2xdw>6Ikbup7u#b0lTMe6=@-)#3wK97z}#JHYW=q1cc8{7>(7H)0x+WNCz}YMed$ zvw4+MqeZ(LYT*OlJa9qqdzMjYBK;u-XAo?b z3$jCcdbt@|=C@2N-uzrG&BOQ+#@`CX=*+3mw)10w#GjiH4ABd;E*6u^5sCD@fO0Es zHbb0xfYwnLCUss7XXe7WS&HyR1|@Fc$o*g z`Ya9xB1u*>ijx1N8UwId*1vbK#hZqI>_O%Tn;BEV!8_05t zVx3Zp>YTR<{yP1Z&7QBXb~nGwJK{qt&96AOf)+xPk=57VS7M23N0P_DtQ1Y;4r<6BDe zfQ2wb2EmVm+XzEZBaJ(hcd*bk{La}>InrI+D+Nq%dtBciWunGGn?C_ z)=9+sU^t>r1JeD5-bYiCIufBo;uMea297Ck9VHT!ju+&}3V}Xs{RFUg!0n^N+e+0t z8srlS8RUf_KH~A{fd{LMEgqEpgur|7^9(C%UKn+{P9T9PXGMy9^H1JfA9xs?r_F`H zUWS`S&HU04_-v=1a*pE`;QL3hP`Q0+I&Jk?^XW0e>FfVl7?i{B7 z*r6NUlTQC>`K@sL!T0&_#kgkn{!eeZ7yx9gxlU}C?x~Y*IrRokzR!zL_uTcMk)or(eVuyEaqvB)y${FQJF(3eFY{m{W#_u`W~Af#0RPA zl(;bz{G4_aGQ?D4A!>bHr>0q|&6ak=2h4yh;OPHz)8{Oj{AQ58NGW!#EsQPBpK=89 zl4~pKG1pO-_*-6nX$LYH4n~s;av2fN`W71X);j9`(?F(vZ<(!!Hk9!jK63FsQ)mN2 z5gbVeXenaYK9vvhQK}PuUKyrL>T+$B5=gpAnlb}!rF|OPlJZ5~uw>g0x(3)HvJoO9 z*H#8e?K9X*%Ch}Pox$W5`|Qc-Y(2>0`a!J^;V0JzRqDQ#InpLU=Pj1)_sfi*XWwNk zw*FBSWYjHUI4x{TbFw#}1xAN9r_(nueZMmh0&y}T+%6s10~ znu_$K8~Pm1zy;gQBU-Ml!~FUxe8Q%^ftTGpC@sDI%wS!$(p7aXdJXUu^KO{)~yxeotL) zn50J;)q(2<(DUvRs<4K(yA-Y0;n8XXB_AfbOV-1uCg~@p0E{J3peh}M&`Mnc#Y!E6 z>xCR%$aDrTq)}9dP%W)Ps1Vh`yFAkgc6p+M=NGXEE`33Qumz!t+k)UGMuMaT5*RO{ za35%e980gkASgi6IB0TQK6ah5>pTTOo2jk%A$$U4{`10@@fl&_W)r9A{S{Y}pTzc% z@T+qVE>s+lrhDgq0TCqb+YT%cU(58WKeqsB?`id_|L*px{~iERpNPXC&2AAL_o@@IlkorUSx$39AK==%jg{S%fuY@%d{Nz%aR<)@2xqK+jjzKAK#ZFxqTGKd9LVO$?r)( zt_N5Qr2V`BNc;J&T*>vlK-$qqawX4C0%<=_$dg=O2;{o^wmix6yMa7!WDk(`@@^pQ z<@2p=K^W(E?p#fyamYh0K0&+dv`37+&#KTa(BdHkFkHpESB8ewpem^=VF}) z-LqKo_6U&Ya>PSOrQJKJK=QS>KyvjqAnn}UK(41B2hz?Rd!gj*OrTjG0Hl4p^+KJu zya&j20)0T9vvB}OyZ7uR9%J|R0(q`R!xEj3+_^;KcbCQJLXWXK#8^xz)Oem|u>?rF z!`ede1s{-hhlWD&g)X46w-ky$q(JEAdZOy3;tzdGHNGb<6QAf1JXV=x?JP+>>`cR@kJV^*IMi?5}z0@5`Q>QB>oV$Li{0Ph4@4B z3h{-`72*rCS9**+A+}idgxq5BiS58`X1|l{34O)l8~w%N8>7Xt6C5ZOpP(}FBeM@m z<9TU`#_#eHjo-~BvIFcW(YW1NBE7!9MEd+_iTD8zL!ka1yGnX{;wr1BuTsA+U#0%; zTV?zED)sYSt2F+0u2MhWze@dkV3qpy=qio75v$eDqE@S)C9am9T(nyKt!1_P+n&|Z zoBLPWez#ivE^dwbUFI6;&(&+BKkrzhe%J%#yz?NC^Hf7?q%V)IQGbkpa`i8>k4onY zuLW{ms&%dU<*v2TpC{LG<*ZZx+qq8t zZ_hgEzn$yU4|~>0-|btc{x}Kbyi@r_(tmedq<*>oBAu5WTdIDUR;qqk0^~eYOR4(j zP9WFsBwnokS$MJf=eCR0KRYf~|Lnh5{d4?c_0Q-_JZ3+P?-J?D)t5+LZoWkN@(v*P z!E{|By?OK!>B|vi(vuU*be?%`nbniaq$lqHUcm1xlfFC#%woREr7yPt^H?tB(whg$ zb?$itlvnPX@m1*D^Bon^pF4re$=}xNyz_+h(wj5aOK)DgUi$KOAos_Nt(RULu|fK< z7s&HEYJp~d%m(Shu|Da;X+G(_B|ho7y}%~c50K||3;=mv$1w16vroxm&h795xu2#4 z$a6b(0eLRX1n_HSzmdnBOVa}6`5nDg(t|Vb(stGx@VlHh#LIb(M+A`jXi|XON8}UnK9r!%qI%zPfQ2M{V-`jo(s|r95DNO zJZ7KE9w5*A=m7G(k1in3`xpfBY_W)1k2&{aHt?@ze~-s`f$s&5FdlG}{R7DJKN9LZ z=Dd$WAkXz^0P>95VIa@rn5dJUISHhlJZ6*R(*z*x<=#z_QwxE#^Opl@FK^x?Ikg=~ zd-?87l2^MnNlxwCB)xNJljPO0O_Eo6xQkiGRj+X>v0nOTM!odUqIx@C)oYyEUN5_Uj(X{#J@p!=_Sb8iI#@3~bi7{UR8)iH+1U+}XHyy^$L2O@+^TMn{94sd0X34E_n>9WqZPxgdxmn{=`DW>vzRl7v zw{6zA)V^7IX4huPuicv^zxHp|xHP<3;}UhAza2jPyv2cl^9t?pT|7p`{LZr&KW)!W zJkp-KNZpJ2+GlmtnVC~!I@G}q*D*~d4eDY?=$Isv*6}v26YZIMC)qSkv1y!YpFPb! zd%As=I#`^JZ!u}ivFAC?vuUK+Z%emvFR*dhrl>Db=b^oXwjSC`*p_H7xzMJy%%)Xj z(^_HEqVB`?OxcF|mN~P)(cB5!HQNZ=HQNZ=b)}>EE4G32v~A6_?^c_qD{Y=wcbo_M zrp*&&2)1k18RfAZHgA+6D34LMU_1Yr%^P(L%3r%}9yy1=_I-`bE9VR-hf&vOJEtzO z&YpF6u|2o2!k%5|vu78wJwKqWiAMIP9M*5Yg|a{8uZQh7QTE?#+YQ?_W&CC>r!noB zHhRiml}o%Z_K#vAuK5rM||omiiiHVU{Un;j8Sqj4WHq#6Oey!jvs-(?6G* zZS*+WqN&GmjNL7D(dcuuK~s+77|J@J?b)1<4Z{yMWM4nwm`wYiE%7GmFs26hV<9rUsJk}}CsH1$yv#O{ca?GQgNSiP9MA~{O zFLJD-p2)F|ZGd}7*#B74=1)oyYkfo|VV8#j~p@*YeCN&j0XCD$2JUqbT3^&+dB80QtstmQ|hB*2Y_lJfSR zFs=8xK1}CQ-WaC7%e|G9yKf8A_(0yzb~?nt@t5|XR-W9JvL*t9H;C8TQ(Q@;rKz37 z+X9I3+hJ2cT*aUkZ#_T<(;R_;wI6J|dGu&!S*r28o|p6#RBC ziHRY^G!ipIhzF?KE)O7jc)kFMr6Dn6$)#%-=_Fg17(?PWLx|BN{ycz~f({r^}1q=XRrNz4c#_K~$CM#M%&I4~gFlA$F7aix6TLiPwh^ zJ4n1MfauKYb@q_>=Kvyyq1`0@Erhs>#D9hmcar#S2yq9Ahg}%_ncGQ>3n8|fLux{Z z+ekb=gt(aE*P07;M77%ub4k1`gqXS79=PNZ`FIA2+XIM(v5@$)keFkX|E|4IXJV^s z$Bi5y@sY#czW-p0#0YqkLABo=ziP=Nq)gdvvN$d%U*+t@CLSlBN z+9S*YW9}t!*0SK3^#A0Xyev589umJ05_30+YeQo0BJr}2m^(@QYDi2Tr0M)DB<6M! zyFy~Nlej-5<~9-!gv4wk@q>_85sn zeh6_q$xJa!5pMa*5ZU8KNURThQBJutOybu9h#WhHNW3b9$YWidt`K5Bi4O** ziQ#Ntl07Xhkk~`w`10UVGV{WpDw6L65>Hvrbs)GuI)Lb$=5xkLJSBk0FFHu#+yG*) zSx)l*wRQhtRaNO8!1oq4R7{j(Vrrss6%~~PmFbe9Qc}@~qD5vsI8kCzQZWtP!HJ%1 zhCAAf6;5=eD;AApq>Pf17wa@q+C-%z8Wz<_v9z$Tn2D#T?)QD)wbyyuPygwM^?R?q z&f07LSTwWp;c3EaeArHSoz4j5JVco1!xp}eu8^OjCtRbDhakd-vt?HZ#the~AgoZx zy^e6J57!g!(a~uLBHZu8Cc4%BX0~iY;WbDTtrxW^bRrT$O|+Qwi63dD=?fn=5`L?Y zGO8zxT%4_!E?RsXyf}=nBBN+p zWgPFrN?L-vMB%tw&_f89EY`WlJIe`g@!@G&(Y$A|?0UkuQdZfVHQqKbCL}HCIpqqS z`AMJBI-*md5oZUT#LnKIL{G6-zAw_3gMR$+N0%Qh#^KIKKj5EBLRy!qGe~nhTIYHGL{VGJ95Tqu}26P$ZBGFwJ zf}~`r5ofgR=kP06pdeW?;@oK=rWjD0BFG_WHKxFi^cgCGkj6**2xQDuNQaf6h*p%* z2e_Q?_Yo+ZCCDK|KID+J08db%L-z9_hnyK9Kx2x{{TDv$rfO~nW?w4bU$#h_!8HR~ z(#*@PgnQ_#O2F1M)k^y!WScz?IV1Xx5$z93V;^59Qb^j6Eq4xWCh72+14pMX=OoNU zU!SL<$}~1xYyA)Xb8IJt6q0hp^g5L4#`V<$cR4ul9+)bN^@ll+8lClKMCdS zzr<%;2Cx9r?BXm*lRNFz6!L08Dm>RH2Ux6-iA^r65;n)G}y;nY0XfxVBQ1%Jy z9Hd(54TV83yY0o40?xn8lq;FVi;N@Axj8TfEjD#b9>Yk zc?3%XMj__K5#9i7Qpi2I5Ljx^HVD<5nk}nBwdaFA z0Y zM~^uv)$GyeHQf(Bl({0HXES<6wdBsH%(KAqr*XWWHbc;dz;}S@&&p@ZxJHDBfz37Y z4Ksdb`XgXFaJ|7-qfD9|z@}&Lhgvl<#30MUZTQ3UYDPT@SWzo`x!ZY2X0&YVP8-JC z8a)&(yT7M4s7AhRCQzmk3$Ye)C@q>-&M#F zz<6Nb?-H_sFqMk4U%r+@AsY!V1=i7LcV3g}7N>ayu-ZdJ=T`b9Fv%bH1L}O= zYnrW>q6f_3*r(8*am#5(wifx~Owc>yZUx2}w4LEjiuz_4YcWCX^XnM-I1V8%JQ9}s zu$pj_532}w`cUQ!z}$bJd~d0~bY4K9`E98pm-9(r)^ORj%{E&4`-$zTB&@hl4hv>-fNNmWR{;_#Utgm>*JyBLqC8kkg!q+EkyE8!fg-p8>m0 zA%jm;$a4y*EAnq#D#{o5R6Z|3)#QPt0ND2oN^nLEEH497KLdWI%6WDKy$LD1(b>b% zJ%V176r{t^OHKF92-)k7msgNc58;m^WFx$uST$G;h?Qm|w1v;df6BhZ`T3;#0m)Fs<CNBy>ljHX{X{{bNIYDLO5oH7Vrw96nNt z6F*9w+G7>ka^60Ywl;O0rhaa?M*l;2zKfq;H#`6y0A`Pp6+=SK{S9Cekk=NGJ4gTL zNXaBPT5_-AD^t{_nBIlGP|{?~24(>0rBI8DfdP68bd)UQRukR?%pES6$FJAZalepG zEq1L1P=d}%MR z#h{a%XRZx_!9X}oZlyM0gF-I+x4=Ue$u4}h@V&sIG3b}$)%wyvG;qskLF%78;0`yf z?uF#eFxrQl`viqF3*uSjRikJ>K@?}4D$+t=`&gG>6tzWKL>&Rhk1}8e$gOk(uvQ@t z7b|HWMunVvF|fiQrFn=#9tQTE;W|AtBqV1P0w&QjZ^_cgT=z4;p6NIg$?Cv;4om~a zCaVK?j-ng1Me0AAwu=iR7byl93yd?-`M_-i*35RD!c>zcorZ4!s{sY(&9P3xH-Tlq zB6`L>C*Q0q0*iqqzfjR>!^pI&Tz)aeMxQZ;o^NoS>T4uA&lUf33_X4amO%2Z# z=P3+&bHytaMtgI`YkeWPGc59j(HkztjdK_pZl^rkV-11k;MW7G;vonW2>$KcO{ zz`rP?)i|1~-S7qD_X?xUlq(`)yh87AL@Kls-HE{TuW@|7R%>fl0Sf^c-Ri()kC#w{ z^js%LU#igdkUN0&=TMV#s)rQ&kfX0rXmh7~uz{~o|F6|`@#gUoippwdl`m-Y(%h-g z7Kv{5uD=+wpo`sbkq-Niqra!nMyET~?bBSRcbXf{{RfR+^oWbYXrre9QvvNd`aF$Z z^aToSgDeBqCb&-0G0|qC$9NMN?tkFyJeuXy8M}YD1IFBZ(G%J8q@2rU`&8Ibb zX>L_$`x5OGO!ekpSudk@U;=t^<#cs4KLkcjmwR$HqfU)pM&}g9RhSudB*vuZJ@ScV zyZA5!SPF=0RCh)$02V8h6$xO&Zb3VGUjpp=I)?SvB_x+~R=iv&(?d?S+g$ELS|eT*7d9`GTjS+3AVuL0Hp=nwWp*s0NrzDJ>r{sC|U zpiW2sT%#AgOQDTEWP&Va~UOo9aoD^i+~&2Fq>>sKNGnM zDI}k9g@j12?2tO+?w%l-lpj(>dO)LBq_qn9QD>g10?Xb7w#jklOSWoY?=e9q;$ySs z4=gz*#j+W_sL{Jo6;x>Jetd$|Cib}M3}>m8P@HaE`0q4&g{Lp+cxXFV02p@|7fXj# zryVs>LbV-MX-?AUr8!NZEoUY${(V{H;ipD3sez|@OezGSu4?4Ll7_`3W8zwJx3JA6I8sh{J9n{F^L9k@P6T{I0z zpgmMweox;X;<3PxL3@34DKO<)m%l(@F%8%ZpwAE3i>5iif-IcY*>Ziv=g~C@v}vt} z<~MF7Fn5XT#4lAp&^r=ndvBLt!?3p@jsi!g;5sfvt|Db&*N5DmU-(ca&p#J({3M5= zF-5J1Oj2kMM;fpcSL7|`s+^n<0LVgX5HE2WjQY+mkd!yQr{U@VF1}S7IRdWciY`N@;O4a0L z$&tX+rSvHPb@OZ7Wcqf4pgm<0CrcFxZQU>NAxF;u#xItq$@s$d7F^&wGFiSuR5a7P ztU>sc59M|%zVOHWLZ z=VIs?Q#(DV_95r~oI?9!dSHqyZ#RCfeoWt1Xn%S-fEzkd_p>Uavp$rY@(S&Pe3KH9 z=I8R!V(KBhJe8CvnNVoTotLL(DYTD*r2CMIbd|!m2Jb3Tp>6r=b;ugCmi`-H$`=^g zzEtHb1Fk^J9%(nx81-XMpG9?D!#3wi3$Uu7;rgQ{~oZ^>)=tgM3(W1)`6g zDm(ae?o_AtL||k+=HI*2DKizgWJee|%^5!Akk?F=Oz00bWcJVDT~j4$$u9L{`iH3k z)w|Sz+X~FA!*y((x`VT0s@xW)JHfUmzYT0pLvOoGwP(9VuRT9jXrq4zOutMX`?Jvp zOjGDZKVP9Oe8MzIFI}gZHH}`=rq6DwN&ZqF$_+JzHu}TU9ESBe`k#Er(VtLgN5wi| z>k!vzj#0CVUBD=Vc53u8yE3~aNA4v7?)vc7}Ls@iEXrq@*m-K3M?hpEqqyJH% zjsC)PhvBr2zT1Z!{S}2a`ibcdL!vJH=RV}<-3o2=^OGFDL08^iUB`{q=tUo=&_=(4 zo}V7<@-00ZJx8M#Jy)R}*0un<=xfR6siRp7q)ZN;r_$W5(M$9H6xuY80GC8!wl+|8 z+K+%8fP}$nn*9l|O(Ez06_C!?x~^0~_v_C;V*gRX*f2pH*lJ z|1vPW66sZ{0kB1*7yYn8d*IFh`#ve#Z1S=X;a9*a+E;Uvdb06HV1+^ZWMkwEoSP~n zr#Te3Rw3s;5?F?u+c&AgC(n>CiKOeo&(-J^{z`@XQXW4vLTOgv$L}e1Q~n-cmO=Xy z`4BKxh2-4V&5+{I>vlHxCpCJx|F1$@czTPjFoZ@4sROsqhaCNNh5Xhh73qCo%EvgG zCsfm&pqB|kVdRX?06T!e6RMowX!J7bRcH@!Ofsf1fXb69%?pzyRJDF=BSE8==2V5Y z?sJo+HccTFeW6A#`VxiqXZP2@-dV0wIorHWz6x`jKPSsqF0<##g>72_u<|FE5Ncit ztWK8iVlw9BOM7kuCIab0mf^dW!1g)#<(jL0B413Vw*kV)McV7b4u15Dnl25O)pI2G z4!Y**1U6oZ0dS7W{ol#-maAZRNg;(^6cr45PYezL(hX$F%uch1E40(>u|AahaSH7; zd%8kfqggs+jX7`&fx#Tt3FWAv@+OVmf%}z0d)!OubrS%sHQ4BX)aXV3vqBqvJCK$r zmM>Mmn|pjHt8fZ!w|Ng(d4=o5Ua6|t10+bhQq66K&BR~lx%^d98U*;J{77Kpe3w5h zO32sRaljgcqIBt|JYQ>11XdXgdZXeLV1+^+f~Eu48;tgbpjp7R3b{y^0ZRb_X8&1Xw1uAl4BmlIg{ttAfyD;xV zHlr1kkwH5M6i`MAIU{;;Do>%z85G*Z+b4kWXu8_fk`d>=3Ao{|Fmmos&!Vs1g^_dr zE3n?6&3zlN=1w^zHunaF{4o%UeiV2R^{%>8(&VQpjsr^#(n=JU^CPOeLYeGQ-3{8@ xKbs|OS#zh#y<4H3SzRz2KlrzsM)5+%DYPNefdyM|rfpFnuU1GQ{~z&?oO#8i2|NG* diff --git a/addons/sourcemod/scripting/archive/confoglcompmod.sp b/addons/sourcemod/scripting/archive/confoglcompmod.sp new file mode 100644 index 000000000..f02d13bd8 --- /dev/null +++ b/addons/sourcemod/scripting/archive/confoglcompmod.sp @@ -0,0 +1,157 @@ +#pragma semicolon 1 + +#if defined(AUTOVERSION) +#include "version.inc" +#else +#define PLUGIN_VERSION "2.2.4" +#endif + +#if !defined(DEBUG_ALL) +#define DEBUG_ALL 0 +#endif + +#include +#include +#include +#include +#include +#include "includes/constants.sp" +#include "includes/functions.sp" +#include "includes/debug.sp" +#include "includes/survivorindex.sp" +#include "includes/configs.sp" +#include "includes/customtags.inc" + +#include "modules/MapInfo.sp" +#include "modules/WeaponInformation.sp" +#include "modules/ReqMatch.sp" +#include "modules/CvarSettings.sp" +#include "modules/GhostTank.sp" +#include "modules/WaterSlowdown.sp" +#include "modules/UnreserveLobby.sp" +//#include "modules/GhostWarp.sp" +#include "modules/UnprohibitBosses.sp" +#include "modules/PasswordSystem.sp" +#include "modules/BotKick.sp" +//#include "modules/EntityRemover.sp" +#include "modules/ScoreMod.sp" +#include "modules/FinaleSpawn.sp" +#include "modules/BossSpawning.sp" +//#include "modules/WeaponCustomization.sp" +#include "modules/l4dt_forwards.sp" +#include "modules/ClientSettings.sp" +#include "modules/ItemTracking.sp" +//#include "modules/SpectatorHud.sp" + +public Plugin:myinfo = +{ + name = "Confogl's Competitive Mod", + author = "Confogl Team", + description = "A competitive mod for L4D2", + version = PLUGIN_VERSION, + url = "http://confogl.googlecode.com/" +} + +public OnPluginStart() +{ + Debug_OnModuleStart(); + Configs_OnModuleStart(); + MI_OnModuleStart(); + SI_OnModuleStart(); + WI_OnModuleStart(); + + RM_OnModuleStart(); + + CVS_OnModuleStart(); + PS_OnModuleStart(); + UL_OnModuleStart(); + + //ER_OnModuleStart(); + //GW_OnModuleStart(); + WS_OnModuleStart(); + GT_OnModuleStart(); + UB_OnModuleStart(); + + BK_OnModuleStart(); + + SM_OnModuleStart(); + FS_OnModuleStart(); + BS_OnModuleStart(); + //WC_OnModuleStart(); + CLS_OnModuleStart(); + IT_OnModuleStart(); + //SH_OnModuleStart(); + + AddCustomServerTag("confogl", true); +} + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + RM_APL(); + Configs_APL(); + MI_APL(); + RegPluginLibrary("confogl"); +} + +public OnPluginEnd() +{ + CVS_OnModuleEnd(); + PS_OnModuleEnd(); + //ER_OnModuleEnd(); + SM_OnModuleEnd(); + + WS_OnModuleEnd(); + RemoveCustomServerTag("confogl"); +} + +public OnGameFrame() +{ + WS_OnGameFrame(); +} + +public OnMapStart() +{ + MI_OnMapStart(); + RM_OnMapStart(); + + SM_OnMapStart(); + BS_OnMapStart(); + IT_OnMapStart(); +} + +public OnMapEnd() +{ + MI_OnMapEnd(); + WI_OnMapEnd(); + PS_OnMapEnd(); + WS_OnMapEnd(); +} + +public OnConfigsExecuted() +{ + CVS_OnConfigsExecuted(); +} + +public OnClientDisconnect(client) +{ + RM_OnClientDisconnect(client); + //GT_OnClientDisconnect(client); + //SH_OnClientDisconnect(client); +} + +public OnClientPutInServer(client) +{ + RM_OnClientPutInServer(); + UL_OnClientPutInServer(); + PS_OnClientPutInServer(client); +} + +/*public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon) +{ + if(GW_OnPlayerRunCmd(client, buttons)) + { + return Plugin_Handled; + } + + return Plugin_Continue; +}*/ diff --git a/addons/sourcemod/scripting/archive/include/confogl.inc b/addons/sourcemod/scripting/archive/include/confogl.inc new file mode 100644 index 000000000..064fed3fe --- /dev/null +++ b/addons/sourcemod/scripting/archive/include/confogl.inc @@ -0,0 +1,169 @@ +/* * + * ============================================================================= + * Confogl.inc + * Confogl (C)2011 Confogl Team + * ============================================================================= + * + * This file is part of the Confogl competitive L4D2 plugin suite. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, Confogl Team gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, Confogl Team grants + * this exception to all derivative works. Confogl defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + */ +#if defined _confogl_Included + #endinput +#endif +#define _confogl_Included + +/* Forwards */ + +/** + * @brief Called when matchmode is fully loaded, before map restart. + * @remarks Called just before confogl_plugins.cfg executes + * + * @noreturn + */ +forward void LGO_OnMatchModeLoaded(); + +/** + * @brief Called when matchmode is un-loaded, before map restart. + * @remarks Plugins are unloaded immediately after this call finishes + * + * @noreturn + */ +forward void LGO_OnMatchModeUnloaded(); + +/* NATIVES */ + +/** + * @brief Tells if a confogl match is currently running + * @remarks Formerly IsPluginEnabled() internally + * + * @return True if matchmode is loaded, false otherwise + */ +native bool LGO_IsMatchModeLoaded(); + +/** + * @brief Build a filepath relative to the current running config. + * @remarks Should produce a path in cfg/cfgogl/CONFIG/ or addons/sourcemod/configs/confogl + * + * @param buffer Buffer to write the path to + * @param maxlength Buffer size + * @param sFileName Name of the file to look for in the config + * @noreturn + */ +native void LGO_BuildConfigPath(char[] buffer, int maxlength, const char[] sFileName); + +/** + * @brief Execute a cfg file for the current config + * @remarks Should execute the named .cfg in cfg/ or cfg/cfgogl/CURRENT_CONFIG/ + * + * @param sFileName Name of the cfg file to execute + * @noreturn + */ +native void LGO_ExecuteConfigCfg(const char[] sFileName); + +/** + * @brief Tells if map data is available + * @remarks Map data should be available when any map is loaded, after OnMapStart() + * + * @return True if map data is available, false if it is not. + */ +native bool LGO_IsMapDataAvailable(); + +/** + * @brief Get an Int value from the MapInfo keyvalues for the current map with a specific key + * @remarks Mapinfo keyvalues is used to store static data about maps + * + * @param key Key to read the value from + * @param defvalue Default value to return if key is not found (default 0) + * @return Integer value for given key, or defvalue if key is not found + */ +native int LGO_GetMapValueInt(const char[] key, const int defvalue = 0); + +/** + * @brief Get a Float value from the MapInfo keyvalues for the current map with a specific key + * @remarks Mapinfo keyvalues is used to store static data about maps + * + * @param key Key to read the value from + * @param defvalue Default value to return if key is not found (default 0.0) + * @return Float value for given key, or defvalue if key is not found + */ +native float LGO_GetMapValueFloat(const char[] key, const float defvalue = 0.0); + +/** + * @brief Get a Vector from the MapInfo keyvalues for the current map with a specific key + * @remarks Mapinfo keyvalues is used to store static data about maps + * + * @param key Key to read the value from + * @param vector Vector to store the result in + * @param defvalue Default value to use if key is not found (default NULL_VECTOR) + * @noreturn + */ +native void LGO_GetMapValueVector(const char[] key, float vector[3], const float defvalue[3] = NULL_VECTOR); + +/** + * @brief Get a String from the MapInfo keyvalues for the current map with a specific key + * @remarks Mapinfo keyvalues is used to store static data about maps + * + * @param key Key to read the value from + * @param value String to store the result in + * @param maxlength Maximum length to write to the value String buffer + * @param defvalue Default value to use if key is not found (default "") + * @noreturn + */ +native void LGO_GetMapValueString(const char[] key, char[] value, int maxlength, const char[] defvalue = ""); + +/** + * @brief Copy a Subsection from the MapInfo keyvalues for the current map + * @remarks Mapinfo keyvalues is used to store static data about maps + * + * @param kv KeyValues Handle to copy to + * @param section Name of the section to copy + * @noreturn + */ +native void LGO_CopyMapSubsection(KeyValues kv, const char[] section); + +public SharedPlugin __pl_confogl = +{ + name = "confogl", + file = "confoglcompmod.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_confogl_SetNTVOptional() +{ + MarkNativeAsOptional("LGO_BuildConfigPath"); + MarkNativeAsOptional("LGO_ExecuteConfigCfg"); + MarkNativeAsOptional("LGO_IsMapDataAvailable"); + MarkNativeAsOptional("LGO_GetMapValueInt"); + MarkNativeAsOptional("LGO_GetMapValueFloat"); + MarkNativeAsOptional("LGO_GetMapValueVector"); + MarkNativeAsOptional("LGO_GetMapValueString"); + MarkNativeAsOptional("LGO_CopyMapSubsection"); + MarkNativeAsOptional("LGO_IsMatchModeLoaded"); +} +#endif diff --git a/addons/sourcemod/scripting/includes/configs.sp b/addons/sourcemod/scripting/archive/includes/configs.sp similarity index 100% rename from addons/sourcemod/scripting/includes/configs.sp rename to addons/sourcemod/scripting/archive/includes/configs.sp diff --git a/addons/sourcemod/scripting/includes/constants.sp b/addons/sourcemod/scripting/archive/includes/constants.sp similarity index 100% rename from addons/sourcemod/scripting/includes/constants.sp rename to addons/sourcemod/scripting/archive/includes/constants.sp diff --git a/addons/sourcemod/scripting/includes/customtags.inc b/addons/sourcemod/scripting/archive/includes/customtags.inc similarity index 100% rename from addons/sourcemod/scripting/includes/customtags.inc rename to addons/sourcemod/scripting/archive/includes/customtags.inc diff --git a/addons/sourcemod/scripting/includes/debug.sp b/addons/sourcemod/scripting/archive/includes/debug.sp similarity index 100% rename from addons/sourcemod/scripting/includes/debug.sp rename to addons/sourcemod/scripting/archive/includes/debug.sp diff --git a/addons/sourcemod/scripting/includes/functions.sp b/addons/sourcemod/scripting/archive/includes/functions.sp similarity index 100% rename from addons/sourcemod/scripting/includes/functions.sp rename to addons/sourcemod/scripting/archive/includes/functions.sp diff --git a/addons/sourcemod/scripting/includes/survivorindex.sp b/addons/sourcemod/scripting/archive/includes/survivorindex.sp similarity index 100% rename from addons/sourcemod/scripting/includes/survivorindex.sp rename to addons/sourcemod/scripting/archive/includes/survivorindex.sp diff --git a/addons/sourcemod/scripting/modules/BossSpawning.sp b/addons/sourcemod/scripting/archive/modules/BossSpawning.sp similarity index 100% rename from addons/sourcemod/scripting/modules/BossSpawning.sp rename to addons/sourcemod/scripting/archive/modules/BossSpawning.sp diff --git a/addons/sourcemod/scripting/modules/BotKick.sp b/addons/sourcemod/scripting/archive/modules/BotKick.sp similarity index 100% rename from addons/sourcemod/scripting/modules/BotKick.sp rename to addons/sourcemod/scripting/archive/modules/BotKick.sp diff --git a/addons/sourcemod/scripting/modules/CheckVersion.sp b/addons/sourcemod/scripting/archive/modules/CheckVersion.sp similarity index 100% rename from addons/sourcemod/scripting/modules/CheckVersion.sp rename to addons/sourcemod/scripting/archive/modules/CheckVersion.sp diff --git a/addons/sourcemod/scripting/modules/ClientSettings.sp b/addons/sourcemod/scripting/archive/modules/ClientSettings.sp similarity index 100% rename from addons/sourcemod/scripting/modules/ClientSettings.sp rename to addons/sourcemod/scripting/archive/modules/ClientSettings.sp diff --git a/addons/sourcemod/scripting/modules/CvarSettings.sp b/addons/sourcemod/scripting/archive/modules/CvarSettings.sp similarity index 100% rename from addons/sourcemod/scripting/modules/CvarSettings.sp rename to addons/sourcemod/scripting/archive/modules/CvarSettings.sp diff --git a/addons/sourcemod/scripting/modules/EntityRemover.sp b/addons/sourcemod/scripting/archive/modules/EntityRemover.sp similarity index 100% rename from addons/sourcemod/scripting/modules/EntityRemover.sp rename to addons/sourcemod/scripting/archive/modules/EntityRemover.sp diff --git a/addons/sourcemod/scripting/modules/FinaleSpawn.sp b/addons/sourcemod/scripting/archive/modules/FinaleSpawn.sp similarity index 100% rename from addons/sourcemod/scripting/modules/FinaleSpawn.sp rename to addons/sourcemod/scripting/archive/modules/FinaleSpawn.sp diff --git a/addons/sourcemod/scripting/modules/GhostTank.sp b/addons/sourcemod/scripting/archive/modules/GhostTank.sp similarity index 100% rename from addons/sourcemod/scripting/modules/GhostTank.sp rename to addons/sourcemod/scripting/archive/modules/GhostTank.sp diff --git a/addons/sourcemod/scripting/modules/GhostWarp.sp b/addons/sourcemod/scripting/archive/modules/GhostWarp.sp similarity index 100% rename from addons/sourcemod/scripting/modules/GhostWarp.sp rename to addons/sourcemod/scripting/archive/modules/GhostWarp.sp diff --git a/addons/sourcemod/scripting/modules/ItemTracking.sp b/addons/sourcemod/scripting/archive/modules/ItemTracking.sp similarity index 100% rename from addons/sourcemod/scripting/modules/ItemTracking.sp rename to addons/sourcemod/scripting/archive/modules/ItemTracking.sp diff --git a/addons/sourcemod/scripting/modules/MapInfo.sp b/addons/sourcemod/scripting/archive/modules/MapInfo.sp similarity index 100% rename from addons/sourcemod/scripting/modules/MapInfo.sp rename to addons/sourcemod/scripting/archive/modules/MapInfo.sp diff --git a/addons/sourcemod/scripting/modules/PasswordSystem.sp b/addons/sourcemod/scripting/archive/modules/PasswordSystem.sp similarity index 100% rename from addons/sourcemod/scripting/modules/PasswordSystem.sp rename to addons/sourcemod/scripting/archive/modules/PasswordSystem.sp diff --git a/addons/sourcemod/scripting/modules/ReqMatch.sp b/addons/sourcemod/scripting/archive/modules/ReqMatch.sp similarity index 100% rename from addons/sourcemod/scripting/modules/ReqMatch.sp rename to addons/sourcemod/scripting/archive/modules/ReqMatch.sp diff --git a/addons/sourcemod/scripting/modules/ScoreMod.sp b/addons/sourcemod/scripting/archive/modules/ScoreMod.sp similarity index 100% rename from addons/sourcemod/scripting/modules/ScoreMod.sp rename to addons/sourcemod/scripting/archive/modules/ScoreMod.sp diff --git a/addons/sourcemod/scripting/modules/SpectatorHud.sp b/addons/sourcemod/scripting/archive/modules/SpectatorHud.sp similarity index 100% rename from addons/sourcemod/scripting/modules/SpectatorHud.sp rename to addons/sourcemod/scripting/archive/modules/SpectatorHud.sp diff --git a/addons/sourcemod/scripting/modules/UnprohibitBosses.sp b/addons/sourcemod/scripting/archive/modules/UnprohibitBosses.sp similarity index 100% rename from addons/sourcemod/scripting/modules/UnprohibitBosses.sp rename to addons/sourcemod/scripting/archive/modules/UnprohibitBosses.sp diff --git a/addons/sourcemod/scripting/modules/UnreserveLobby.sp b/addons/sourcemod/scripting/archive/modules/UnreserveLobby.sp similarity index 100% rename from addons/sourcemod/scripting/modules/UnreserveLobby.sp rename to addons/sourcemod/scripting/archive/modules/UnreserveLobby.sp diff --git a/addons/sourcemod/scripting/modules/WaterSlowdown.sp b/addons/sourcemod/scripting/archive/modules/WaterSlowdown.sp similarity index 100% rename from addons/sourcemod/scripting/modules/WaterSlowdown.sp rename to addons/sourcemod/scripting/archive/modules/WaterSlowdown.sp diff --git a/addons/sourcemod/scripting/modules/WeaponCustomization.sp b/addons/sourcemod/scripting/archive/modules/WeaponCustomization.sp similarity index 100% rename from addons/sourcemod/scripting/modules/WeaponCustomization.sp rename to addons/sourcemod/scripting/archive/modules/WeaponCustomization.sp diff --git a/addons/sourcemod/scripting/modules/WeaponInformation.sp b/addons/sourcemod/scripting/archive/modules/WeaponInformation.sp similarity index 100% rename from addons/sourcemod/scripting/modules/WeaponInformation.sp rename to addons/sourcemod/scripting/archive/modules/WeaponInformation.sp diff --git a/addons/sourcemod/scripting/modules/l4dt_forwards.sp b/addons/sourcemod/scripting/archive/modules/l4dt_forwards.sp similarity index 100% rename from addons/sourcemod/scripting/modules/l4dt_forwards.sp rename to addons/sourcemod/scripting/archive/modules/l4dt_forwards.sp diff --git a/addons/sourcemod/scripting/confoglcompmod.sp b/addons/sourcemod/scripting/confoglcompmod.sp index f02d13bd8..dea9926ea 100644 --- a/addons/sourcemod/scripting/confoglcompmod.sp +++ b/addons/sourcemod/scripting/confoglcompmod.sp @@ -1,157 +1,494 @@ #pragma semicolon 1 +#pragma newdecls required -#if defined(AUTOVERSION) -#include "version.inc" -#else -#define PLUGIN_VERSION "2.2.4" -#endif +#define DEBUG_ALL 0 -#if !defined(DEBUG_ALL) -#define DEBUG_ALL 0 -#endif +#define PLUGIN_VERSION "2.3.0" + +// Using these macros, you can disable unnecessary modules, +// and they will not be included in the plugin at compile time, +// to disable, specify 0 for the required module. +#define MODULE_MAPINFO 1 //MapInfo +#define MODULE_WEAPONINFORMATION 1 //WeaponInformation +#define MODULE_REQMATCH 1 //ReqMatch +#define MODULE_CVARSETTINGS 1 //CvarSettings +#define MODULE_GHOSTTANK 1 //GhostTank +#define MODULE_UNRESERVELOBBY 1 //UnreserveLobby +#define MODULE_GHOSTWARP 0 //GhostWarp (plugin l4d2_ghost_warp replaces this functionality) +#define MODULE_PASSWORDSYSTEM 1 //PasswordSystem +#define MODULE_BOTKICK 1 //BotKick +#define MODULE_SCOREMOD 1 //ScoreMod +#define MODULE_FINALESPAWN 1 //FinaleSpawn +#define MODULE_BOSSSPAWNING 1 //BossSpawning +#define MODULE_CLIENTSETTINGS 1 //ClientSettings +#define MODULE_ITEMTRACKING 1 //ItemTracking +#define MODULE_WATERSLOWDOWN 1 //WaterSlowdown (config 'pmelite' uses it) +#define MODULE_UNPROHIBITBOSSES 0 //UnprohibitBosses (duplicate code, plugin 'bossspawningfix' does the same). +#define MODULE_ENTITYREMOVER 0 //EntityRemover (the same can be done with the extension 'stripper'). +#define MODULE_WEAPONCUSTOMIZATION 0 //WeaponCustomization (this is deprecated and disabled, plugin 'l4d_weapon_limits' does the same). #include #include #include #include #include -#include "includes/constants.sp" -#include "includes/functions.sp" -#include "includes/debug.sp" -#include "includes/survivorindex.sp" -#include "includes/configs.sp" -#include "includes/customtags.inc" - -#include "modules/MapInfo.sp" -#include "modules/WeaponInformation.sp" -#include "modules/ReqMatch.sp" -#include "modules/CvarSettings.sp" -#include "modules/GhostTank.sp" -#include "modules/WaterSlowdown.sp" -#include "modules/UnreserveLobby.sp" -//#include "modules/GhostWarp.sp" -#include "modules/UnprohibitBosses.sp" -#include "modules/PasswordSystem.sp" -#include "modules/BotKick.sp" -//#include "modules/EntityRemover.sp" -#include "modules/ScoreMod.sp" -#include "modules/FinaleSpawn.sp" -#include "modules/BossSpawning.sp" -//#include "modules/WeaponCustomization.sp" -#include "modules/l4dt_forwards.sp" -#include "modules/ClientSettings.sp" -#include "modules/ItemTracking.sp" -//#include "modules/SpectatorHud.sp" - -public Plugin:myinfo = +//#undef REQUIRE_PLUGIN +//#include //ItemTracking (commented out) + +#include "confoglcompmod/includes/constants.sp" +#include "confoglcompmod/includes/functions.sp" +#include "confoglcompmod/includes/debug.sp" +#include "confoglcompmod/includes/survivorindex.sp" +#include "confoglcompmod/includes/configs.sp" +#include "confoglcompmod/includes/customtags.sp" + +#if MODULE_MAPINFO + #include "confoglcompmod/MapInfo.sp" +#endif + +#if MODULE_WEAPONINFORMATION + #include "confoglcompmod/WeaponInformation.sp" +#endif + +#if MODULE_REQMATCH + #include "confoglcompmod/ReqMatch.sp" +#endif + +#if MODULE_CVARSETTINGS + #include "confoglcompmod/CvarSettings.sp" +#endif + +#if MODULE_GHOSTTANK + #include "confoglcompmod/GhostTank.sp" +#endif + +#if MODULE_UNRESERVELOBBY + #include "confoglcompmod/UnreserveLobby.sp" +#endif + +#if MODULE_GHOSTWARP + #include "confoglcompmod/GhostWarp.sp" +#endif + +#if MODULE_PASSWORDSYSTEM + #include "confoglcompmod/PasswordSystem.sp" +#endif + +#if MODULE_BOTKICK + #include "confoglcompmod/BotKick.sp" +#endif + +#if MODULE_SCOREMOD + #include "confoglcompmod/ScoreMod.sp" +#endif + +#if MODULE_FINALESPAWN + #include "confoglcompmod/FinaleSpawn.sp" +#endif + +#if MODULE_BOSSSPAWNING + #include "confoglcompmod/BossSpawning.sp" +#endif + +#if MODULE_CLIENTSETTINGS + #include "confoglcompmod/ClientSettings.sp" +#endif + +#if MODULE_ITEMTRACKING + #include "confoglcompmod/ItemTracking.sp" +#endif + +#if MODULE_WATERSLOWDOWN + #include "confoglcompmod/WaterSlowdown.sp" +#endif + +#if MODULE_UNPROHIBITBOSSES + #include "confoglcompmod/UnprohibitBosses.sp" +#endif + +#if MODULE_ENTITYREMOVER + #include "confoglcompmod/EntityRemover.sp" +#endif + +#if MODULE_WEAPONCUSTOMIZATION + #include "confoglcompmod/WeaponCustomization.sp" +#endif + +public Plugin myinfo = { name = "Confogl's Competitive Mod", - author = "Confogl Team", + author = "Confogl Team, A1m`", description = "A competitive mod for L4D2", version = PLUGIN_VERSION, - url = "http://confogl.googlecode.com/" -} - -public OnPluginStart() -{ - Debug_OnModuleStart(); - Configs_OnModuleStart(); - MI_OnModuleStart(); - SI_OnModuleStart(); - WI_OnModuleStart(); - - RM_OnModuleStart(); - - CVS_OnModuleStart(); - PS_OnModuleStart(); - UL_OnModuleStart(); - - //ER_OnModuleStart(); - //GW_OnModuleStart(); - WS_OnModuleStart(); - GT_OnModuleStart(); - UB_OnModuleStart(); - - BK_OnModuleStart(); - - SM_OnModuleStart(); - FS_OnModuleStart(); - BS_OnModuleStart(); - //WC_OnModuleStart(); - CLS_OnModuleStart(); - IT_OnModuleStart(); - //SH_OnModuleStart(); - - AddCustomServerTag("confogl", true); -} - -public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) -{ - RM_APL(); - Configs_APL(); - MI_APL(); + url = "https://github.com/L4D-Community/L4D2-Competitive-Framework" +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + //Plugin functions + Configs_APL(); //configs + + //Modules +#if MODULE_REQMATCH + RM_APL(); //ReqMatch +#endif + +#if MODULE_MAPINFO + MI_APL(); //MapInfo +#endif + +#if MODULE_SCOREMOD + SM_APL(); +#endif + + //Other RegPluginLibrary("confogl"); + return APLRes_Success; +} + +public void OnPluginStart() +{ + //Plugin functions + Fns_OnModuleStart(); //functions + Debug_OnModuleStart(); //debug + Configs_OnModuleStart(); //configs + SI_OnModuleStart(); //survivorindex + CT_OnModuleStart(); //customtags + + //Modules +#if MODULE_MAPINFO + MI_OnModuleStart(); //MapInfo +#endif + +#if MODULE_WEAPONINFORMATION + WI_OnModuleStart(); //WeaponInformation +#endif + +#if MODULE_REQMATCH + RM_OnModuleStart(); //ReqMatch +#endif + +#if MODULE_CVARSETTINGS + CVS_OnModuleStart(); //CvarSettings +#endif + +#if MODULE_PASSWORDSYSTEM + PS_OnModuleStart(); //PasswordSystem +#endif + +#if MODULE_UNRESERVELOBBY + UL_OnModuleStart(); //UnreserveLobby +#endif + +#if MODULE_ENTITYREMOVER + ER_OnModuleStart(); //EntityRemover +#endif + +#if MODULE_GHOSTWARP + GW_OnModuleStart(); //GhostWarp +#endif + +#if MODULE_WATERSLOWDOWN + WS_OnModuleStart(); //WaterSlowdown +#endif + +#if MODULE_GHOSTTANK + GT_OnModuleStart(); //GhostTank +#endif + +#if MODULE_UNPROHIBITBOSSES + UB_OnModuleStart(); //UnprohibitBosses +#endif + +#if MODULE_BOTKICK + BK_OnModuleStart(); //BotKick +#endif + +#if MODULE_SCOREMOD + SM_OnModuleStart(); //ScoreMod +#endif + +#if MODULE_FINALESPAWN + FS_OnModuleStart(); //FinaleSpawn +#endif + +#if MODULE_BOSSSPAWNING + BS_OnModuleStart(); //BossSpawning +#endif + +#if MODULE_WEAPONCUSTOMIZATION + WC_OnModuleStart(); //WeaponCustomization +#endif + +#if MODULE_CLIENTSETTINGS + CLS_OnModuleStart(); //ClientSettings +#endif + +#if MODULE_ITEMTRACKING + IT_OnModuleStart(); //ItemTracking +#endif + + //Other + AddCustomServerTag("confogl"); } -public OnPluginEnd() +public void OnPluginEnd() { - CVS_OnModuleEnd(); - PS_OnModuleEnd(); - //ER_OnModuleEnd(); - SM_OnModuleEnd(); - - WS_OnModuleEnd(); + //Modules +#if MODULE_CVARSETTINGS + CVS_OnModuleEnd(); //CvarSettings +#endif + +#if MODULE_PASSWORDSYSTEM + PS_OnModuleEnd(); //PasswordSystem +#endif + +#if MODULE_ENTITYREMOVER + ER_OnModuleEnd(); //EntityRemover +#endif + +#if MODULE_SCOREMOD + SM_OnModuleEnd(); //ScoreMod +#endif + +#if MODULE_WATERSLOWDOWN + WS_OnModuleEnd(); //WaterSlowdown +#endif + +#if MODULE_MAPINFO + MI_OnModuleEnd(); //MapInfo +#endif + + //Other RemoveCustomServerTag("confogl"); } -public OnGameFrame() +#if MODULE_MAPINFO || MODULE_REQMATCH || MODULE_SCOREMOD || MODULE_BOSSSPAWNING || MODULE_ITEMTRACKING +public void OnMapStart() { - WS_OnGameFrame(); + //Modules +#if MODULE_MAPINFO + MI_OnMapStart(); //MapInfo +#endif + +#if MODULE_REQMATCH + RM_OnMapStart(); //ReqMatch +#endif + +#if MODULE_SCOREMOD + SM_OnMapStart(); //ScoreMod +#endif + +#if MODULE_BOSSSPAWNING + BS_OnMapStart(); //BossSpawning +#endif + +#if MODULE_ITEMTRACKING + IT_OnMapStart(); //ItemTracking +#endif +} +#endif + +#if MODULE_MAPINFO || MODULE_WEAPONINFORMATION || MODULE_PASSWORDSYSTEM || MODULE_WATERSLOWDOWN +public void OnMapEnd() +{ + //Modules +#if MODULE_MAPINFO + MI_OnMapEnd(); //MapInfo +#endif + +#if MODULE_WEAPONINFORMATION + WI_OnMapEnd(); //WeaponInformation +#endif + +#if MODULE_PASSWORDSYSTEM + PS_OnMapEnd(); //PasswordSystem +#endif + +#if MODULE_WATERSLOWDOWN + WS_OnMapEnd(); //WaterSlowdown +#endif +} +#endif + +#if MODULE_CVARSETTINGS +public void OnConfigsExecuted() +{ + //Modules + CVS_OnConfigsExecuted(); //CvarSettings +} +#endif + +#if MODULE_REQMATCH +public void OnClientDisconnect(int client) +{ + //Modules + RM_OnClientDisconnect(client); //ReqMatch } +#endif -public OnMapStart() +#if MODULE_BOTKICK +public bool OnClientConnect(int client, char[] rejectmsg, int maxlen) { - MI_OnMapStart(); - RM_OnMapStart(); - - SM_OnMapStart(); - BS_OnMapStart(); - IT_OnMapStart(); + //Modules + if (!BK_OnClientConnect(client)) { //BotKick + return false; + } + + return true; } +#endif -public OnMapEnd() +#if MODULE_REQMATCH || MODULE_UNRESERVELOBBY || MODULE_PASSWORDSYSTEM || MODULE_FINALESPAWN +public void OnClientPutInServer(int client) { - MI_OnMapEnd(); - WI_OnMapEnd(); - PS_OnMapEnd(); - WS_OnMapEnd(); + //Modules +#if MODULE_REQMATCH + RM_OnClientPutInServer(); //ReqMatch +#endif + +#if MODULE_UNRESERVELOBBY + UL_OnClientPutInServer(); //UnreserveLobby +#endif + +#if MODULE_PASSWORDSYSTEM + PS_OnClientPutInServer(client); //PasswordSystem +#endif + +#if MODULE_FINALESPAWN + FS_OnClientPutInServer(client); // FinaleSpawn +#endif } +#endif + +//Hot functions =) -public OnConfigsExecuted() +#if MODULE_WATERSLOWDOWN +public void OnGameFrame() { - CVS_OnConfigsExecuted(); + //Modules + WS_OnGameFrame(); //WaterSlowdown } +#endif -public OnClientDisconnect(client) +#if MODULE_GHOSTWARP +public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, \ + int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) { - RM_OnClientDisconnect(client); - //GT_OnClientDisconnect(client); - //SH_OnClientDisconnect(client); + //Modules + if (GW_OnPlayerRunCmd(client, buttons)) { //GhostWarp + return Plugin_Handled; + } + + return Plugin_Continue; } +#endif + +//Left4Dhooks or Left4Downtown functions -public OnClientPutInServer(client) +#if MODULE_GHOSTTANK +public Action L4D_OnCThrowActivate(int ability) { - RM_OnClientPutInServer(); - UL_OnClientPutInServer(); - PS_OnClientPutInServer(client); + //Modules + if (GT_OnCThrowActivate() == Plugin_Handled) { //GhostTank + return Plugin_Handled; + } + + return Plugin_Continue; } +#endif -/*public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon) +#if MODULE_GHOSTTANK +public Action L4D_OnSpawnTank(const float vector[3], const float qangle[3]) { - if(GW_OnPlayerRunCmd(client, buttons)) - { + //Modules + if (GT_OnTankSpawn_Forward() == Plugin_Handled) { //GhostTank return Plugin_Handled; } - + return Plugin_Continue; -}*/ +} +#endif + +#if MODULE_BOSSSPAWNING +public void L4D_OnSpawnTank_Post(int client, const float vecPos[3], const float vecAng[3]) +{ + //Modules + BS_OnTankSpawnPost_Forward(); //BossSpawning +} +#endif + +#if MODULE_GHOSTTANK +public Action L4D_OnSpawnMob(int &amount) +{ + //Modules + if (GT_OnSpawnMob_Forward(amount) == Plugin_Handled) { //GhostTank + return Plugin_Handled; + } + + return Plugin_Continue; +} +#endif + +#if MODULE_GHOSTTANK +public Action L4D_OnTryOfferingTankBot(int tank_index, bool &enterStasis) +{ + //Modules + if (GT_OnTryOfferingTankBot(enterStasis) == Plugin_Handled) { //GhostTank + return Plugin_Handled; + } + + return Plugin_Continue; +} +#endif + +#if MODULE_UNPROHIBITBOSSES +public Action L4D_OnGetMissionVSBossSpawning(float &spawn_pos_min, float &spawn_pos_max, float &tank_chance, float &witch_chance) +{ + //Modules + if (UB_OnGetMissionVSBossSpawning() == Plugin_Handled) { //UnprohibitBosses + return Plugin_Handled; + } + + return Plugin_Continue; +} +#endif + +#if MODULE_UNPROHIBITBOSSES +public Action L4D_OnGetScriptValueInt(const char[] key, int &retVal) +{ + //Modules + if (UB_OnGetScriptValueInt(key, retVal) == Plugin_Handled) { //UnprohibitBosses + return Plugin_Handled; + } + + return Plugin_Continue; +} +#endif + +public Action L4D_OnFirstSurvivorLeftSafeArea(int client) +{ + if (IsPluginEnabled()) { + CreateTimer(0.1, OFSLA_ForceMobSpawnTimer); + } + + return Plugin_Continue; +} + +public Action OFSLA_ForceMobSpawnTimer(Handle hTimer) +{ + //Workaround to make tank horde blocking always work + //Makes the first horde always start 100s after survivors leave saferoom + static ConVar hCvarMobSpawnTimeMin = null; + static ConVar hCvarMobSpawnTimeMax = null; + + if (hCvarMobSpawnTimeMin == null) { + hCvarMobSpawnTimeMin = FindConVar("z_mob_spawn_min_interval_normal"); + hCvarMobSpawnTimeMax = FindConVar("z_mob_spawn_max_interval_normal"); + } + + float fRand = GetRandomFloat(hCvarMobSpawnTimeMin.FloatValue, hCvarMobSpawnTimeMax.FloatValue); + L4D2_CTimerStart(L4D2CT_MobSpawnTimer, fRand); + + return Plugin_Stop; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/BossSpawning.sp b/addons/sourcemod/scripting/confoglcompmod/BossSpawning.sp new file mode 100644 index 000000000..5a225f1d6 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/BossSpawning.sp @@ -0,0 +1,232 @@ +#if defined __boss_spawning_included + #endinput +#endif +#define __boss_spawning_included + +#define DEBUG_BS 0 +#define BS_MODULE_NAME "BossSpawning" + +#define MAX_TANKS 5 +#define MAX_WITCHES 5 +#define ROUND_MAX_COUNT 2 + +static char + BS_sMap[64] = "\0"; + +static bool + BS_bEnabled = true, + BS_bIsFirstRound = true, + BS_bDeleteWitches = false, + BS_bFinaleStarted = false, + BS_bExpectTankSpawn = false; + +static int + BS_iTankCount[ROUND_MAX_COUNT] = {0, ...}, + BS_iWitchCount[ROUND_MAX_COUNT] = {0, ...}; + +static float + BS_fTankSpawn[MAX_TANKS][3], + BS_fWitchSpawn[MAX_WITCHES][2][3]; + +static ConVar + BS_hEnabled = null; + +void BS_OnModuleStart() +{ + BS_hEnabled = CreateConVarEx("lock_boss_spawns", "1", "Enables forcing same coordinates for tank and witch spawns", _, true, 0.0, true, 1.0); + + BS_bEnabled = BS_hEnabled.BoolValue; + BS_hEnabled.AddChangeHook(BS_ConVarChange); + + HookEvent("tank_spawn", BS_TankSpawn); + HookEvent("witch_spawn", BS_WitchSpawn); + HookEvent("round_end", BS_RoundEnd, EventHookMode_PostNoCopy); + HookEvent("finale_start", BS_FinaleStart, EventHookMode_PostNoCopy); + + GetCurrentMap(BS_sMap, sizeof(BS_sMap)); +} + +void BS_OnMapStart() +{ + BS_bIsFirstRound = true; + BS_bFinaleStarted = false; + BS_bExpectTankSpawn = false; + + for (int i = 0; i < ROUND_MAX_COUNT; i++) { + BS_iTankCount[i] = 0; + BS_iWitchCount[i] = 0; + } + + GetCurrentMap(BS_sMap, sizeof(BS_sMap)); +} + +public void BS_ConVarChange(ConVar convar, const char[] oldValue, const char[] newValue) +{ + BS_bEnabled = BS_hEnabled.BoolValue; +} + +public void BS_WitchSpawn(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!BS_bEnabled || !IsPluginEnabled()) { + return; + } + + int iWitch = hEvent.GetInt("witchid"); + + if (BS_bDeleteWitches) { + // Used to delete round2 extra witches, which spawn on round start instead of by flow + KillEntity(iWitch); + + return; + } + + // Can't track more witches if our witch array is full + if (BS_iWitchCount[view_as(!BS_bIsFirstRound)] >= MAX_WITCHES) { + Debug_LogError(BS_MODULE_NAME, "Failed to save a large number of witches to the array. Count: %d, Max: %d", \ + BS_iWitchCount[view_as(!BS_bIsFirstRound)], MAX_WITCHES); + return; + } + + if (BS_bIsFirstRound) { + // If it's the first round, track our witch. + GetEntPropVector(iWitch, Prop_Send, "m_vecOrigin", BS_fWitchSpawn[BS_iWitchCount[0]][0]); + GetEntPropVector(iWitch, Prop_Send, "m_angRotation", BS_fWitchSpawn[BS_iWitchCount[0]][1]); + BS_iWitchCount[0]++; + } else if (BS_iWitchCount[0] > BS_iWitchCount[1]) { + // Until we have found the same number of witches as from round1, teleport them to round1 locations + TeleportEntity(iWitch, BS_fWitchSpawn[BS_iWitchCount[1]][0], BS_fWitchSpawn[BS_iWitchCount[1]][1], NULL_VECTOR); + BS_iWitchCount[1]++; + } +} + +void BS_OnTankSpawnPost_Forward() +{ + if (BS_bEnabled && IsPluginEnabled()) { + BS_bExpectTankSpawn = true; + } +} + +public void BS_TankSpawn(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!BS_bEnabled || !IsPluginEnabled()) { + return; + } + + // Don't touch tanks on finale events + if (BS_bFinaleStarted) { + return; + } + + // Stop if this isn't the first tank_spawn for this tank + if (!BS_bExpectTankSpawn) { + return; + } + + BS_bExpectTankSpawn = false; + + // Don't track tank spawns on c5m5 or tank can spawn behind other team. + if (strcmp(BS_sMap, "c5m5_bridge") == 0) { + return; + } + + int iTankClient = GetClientOfUserId(hEvent.GetInt("userid")); + + if (GetMapValueInt("tank_z_fix")) { + FixZDistance(iTankClient); // fix stuck tank spawns, ex c1m1 + } + + // If we reach MAX_TANKS, we don't have any room to store their locations + if (BS_iTankCount[view_as(!BS_bIsFirstRound)] >= MAX_TANKS) { + Debug_LogError(BS_MODULE_NAME, "Failed to save a large number of tanks to the array. Count: %d, Max: %d", \ + BS_iTankCount[view_as(!BS_bIsFirstRound)], MAX_TANKS); + return; + } + + if (DEBUG_BS || IsDebugEnabled()) { + LogMessage("[%s] Tracking this tank spawn. Currently, %d tanks", BS_MODULE_NAME, BS_iTankCount[view_as(!BS_bIsFirstRound)]); + } + + if (BS_bIsFirstRound) { + GetClientAbsOrigin(iTankClient, BS_fTankSpawn[BS_iTankCount[0]]); + if (DEBUG_BS || IsDebugEnabled()) { + LogMessage("[%s] Saving tank at %f %f %f", \ + BS_MODULE_NAME, BS_fTankSpawn[BS_iTankCount[0]][0], BS_fTankSpawn[BS_iTankCount[0]][1], BS_fTankSpawn[BS_iTankCount[0]][2]); + } + + BS_iTankCount[0]++; + } else if (BS_iTankCount[0] > BS_iTankCount[1]) { + TeleportEntity(iTankClient, BS_fTankSpawn[BS_iTankCount[1]], NULL_VECTOR, NULL_VECTOR); + + if (DEBUG_BS || IsDebugEnabled()) { + LogMessage("[%s] Teleporting tank to tank at %f %f %f", \ + BS_MODULE_NAME, BS_fTankSpawn[BS_iTankCount[1]][0], BS_fTankSpawn[BS_iTankCount[1]][1], BS_fTankSpawn[BS_iTankCount[1]][2]); + } + + BS_iTankCount[1]++; + } else if (DEBUG_BS || IsDebugEnabled()) { + LogMessage("[%s] Not first round and not acceptable tank", BS_MODULE_NAME); + LogMessage("[%s] IsFirstRound: %d R1Count: %d R2Count: %d", BS_MODULE_NAME, BS_bIsFirstRound, BS_iTankCount[0], BS_iTankCount[1]); + } +} + +public void BS_RoundEnd(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + BS_bIsFirstRound = false; + BS_bFinaleStarted = false; + + if (strcmp(BS_sMap, "c6m1_riverbank") == 0) { + BS_bDeleteWitches = false; + } else { + BS_bDeleteWitches = true; + + CreateTimer(5.0, BS_WitchTimerReset); + } +} + +public void BS_FinaleStart(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + BS_bFinaleStarted = true; +} + +public Action BS_WitchTimerReset(Handle hTimer) +{ + BS_bDeleteWitches = false; + + return Plugin_Stop; +} + +static void FixZDistance(int iTankClient) +{ + int index = 0; + float distance = 99999999999999.9; + float WarpToLocation[3], TankLocation[3], TempSurvivorLocation[3]; + GetClientAbsOrigin(iTankClient, TankLocation); + + if (DEBUG_BS || IsDebugEnabled()) { + LogMessage("[%s] tank z spawn check... Map: %s, Tank Location: %f, %f, %f", BS_MODULE_NAME, BS_sMap, TankLocation[0], TankLocation[1], TankLocation[2]); + } + + for (int i = 0; i < NUM_OF_SURVIVORS; i++) { + distance = GetMapValueFloat("max_tank_z", 99999999999999.9); + index = GetSurvivorIndex(i); + + if (index != 0 && IsValidEntity(index)) { + GetClientAbsOrigin(index, TempSurvivorLocation); + + if (DEBUG_BS || IsDebugEnabled()) { + LogMessage("[%s] Survivor %d Location: %f, %f, %f", BS_MODULE_NAME, i, TempSurvivorLocation[0], TempSurvivorLocation[1], TempSurvivorLocation[2]); + } + + if (FloatAbs(TempSurvivorLocation[2] - TankLocation[2]) > distance) { + GetMapValueVector("tank_warpto", WarpToLocation); + + if (!GetVectorLength(WarpToLocation, true)) { + LogMessage("[%s] tank_warpto missing from mapinfo.txt", BS_MODULE_NAME); + return; + } + + TeleportEntity(iTankClient, WarpToLocation, NULL_VECTOR, NULL_VECTOR); + } + } + } +} diff --git a/addons/sourcemod/scripting/confoglcompmod/BotKick.sp b/addons/sourcemod/scripting/confoglcompmod/BotKick.sp new file mode 100644 index 000000000..a8dd0ff92 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/BotKick.sp @@ -0,0 +1,115 @@ +#if defined __bot_kick_included + #endinput +#endif +#define __bot_kick_included + +#define BK_MODULE_NAME "BotKick" + +#define CHECKALLOWEDTIME 0.1 +#define BOTREPLACEVALIDTIME 0.2 + +static const char InfectedNames[][] = +{ + "smoker", + "boomer", + "hunter", + "spitter", + "jockey", + "charger" +}; + +static int + BK_iEnable = 0, + BK_lastvalidbot = -1; + +static ConVar + BK_hEnable = null; + +void BK_OnModuleStart() +{ + BK_hEnable = CreateConVarEx( \ + "blockinfectedbots", \ + "1", \ + "Blocks infected bots from joining the game, minus when a tank spawns (1 allows bots from tank spawns, 2 removes all infected bots)", \ + _, true, 0.0, true, 2.0 \ + ); + + BK_iEnable = BK_hEnable.IntValue; + BK_hEnable.AddChangeHook(BK_ConVarChange); + + HookEvent("player_bot_replace", BK_PlayerBotReplace); +} + +public void BK_ConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + BK_iEnable = BK_hEnable.IntValue; +} + +bool BK_OnClientConnect(int iClient) +{ + if (BK_iEnable == 0 || !IsPluginEnabled() || !IsFakeClient(iClient)) { // If the BK_iEnable is 0, we don't do anything + return true; + } + + // If the client doesn't have a bot infected's name, let it in + if (IsInvalidInfected(iClient)) { + return true; + } + + if (BK_iEnable == 1 && GT_IsTankInPlay()) { // Bots only allowed to try to connect when there's a tank in play. + // Check this bot in CHECKALLOWEDTIME seconds to see if he's supposed to be allowed. + CreateTimer(CHECKALLOWEDTIME, BK_CheckInfBotReplace_Timer, iClient, TIMER_FLAG_NO_MAPCHANGE); + //BK_bAllowBot = false; + return true; + } + + KickClient(iClient, "[Confogl] Kicking infected bot..."); // If all else fails, bots arent allowed and must be kicked + + return false; +} + +public Action BK_CheckInfBotReplace_Timer(Handle hTimer, any iClient) +{ + if (iClient != BK_lastvalidbot && IsClientInGame(iClient) && IsFakeClient(iClient)) { + KickClient(iClient, "[Confogl] Kicking late infected bot..."); + } else { + BK_lastvalidbot = -1; + } + + return Plugin_Stop; +} + +public void BK_PlayerBotReplace(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!GT_IsTankInPlay()) { + return; + } + + int iClient = GetClientOfUserId(hEvent.GetInt("player")); + + if (iClient > 0 && IsClientInGame(iClient) && GetClientTeam(iClient) == L4D2Team_Infected) { + BK_lastvalidbot = GetClientOfUserId(hEvent.GetInt("bot")); + CreateTimer(BOTREPLACEVALIDTIME, BK_CancelValidBot_Timer, _, TIMER_FLAG_NO_MAPCHANGE); + } +} + +public Action BK_CancelValidBot_Timer(Handle hTimer) +{ + BK_lastvalidbot = -1; + + return Plugin_Stop; +} + +static bool IsInvalidInfected(int iClient) +{ + char sBotName[11]; + GetClientName(iClient, sBotName, sizeof(sBotName)); + + for (int i = 0; i < sizeof(InfectedNames); i++) { + if (StrContains(sBotName, InfectedNames[i], false) != -1) { + return false; + } + } + + return true; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/ClientSettings.sp b/addons/sourcemod/scripting/confoglcompmod/ClientSettings.sp new file mode 100644 index 000000000..a1685aebb --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/ClientSettings.sp @@ -0,0 +1,457 @@ +#if defined __client_settings_include + #endinput +#endif +#define __client_settings_include + +#define CLS_MODULE_NAME "ClientSettings" + +#define CLS_CVAR_MAXLEN 64 +#define CLIENT_CHECK_INTERVAL 5.0 + +enum /*CLSAction*/ +{ + CLSA_Kick = 0, + CLSA_Log +}; + +#if SOURCEMOD_V_MINOR > 9 +enum struct CLSEntry +{ + bool CLSE_hasMin; + float CLSE_min; + bool CLSE_hasMax; + float CLSE_max; + int CLSE_action; + char CLSE_cvar[CLS_CVAR_MAXLEN]; +} +#else +enum CLSEntry +{ + bool:CLSE_hasMin, + Float:CLSE_min, + bool:CLSE_hasMax, + Float:CLSE_max, + CLSE_action, + String:CLSE_cvar[CLS_CVAR_MAXLEN] +}; +#endif + +static ArrayList + ClientSettingsArray = null; + +static Handle + ClientSettingsCheckTimer = null; + +void CLS_OnModuleStart() +{ +#if SOURCEMOD_V_MINOR > 9 + CLSEntry clsetting; +#else + CLSEntry clsetting[CLSEntry]; +#endif + + ClientSettingsArray = new ArrayList(sizeof(clsetting)); + + RegConsoleCmd("confogl_clientsettings", _ClientSettings_Cmd, "List Client settings enforced by confogl"); + + /* Using Server Cmd instead of admin because these shouldn't really be changed on the fly */ + RegServerCmd("confogl_trackclientcvar", _TrackClientCvar_Cmd, "Add a Client CVar to be tracked and enforced by confogl"); + RegServerCmd("confogl_resetclientcvars", _ResetTracking_Cmd, "Remove all tracked client cvars. Cannot be called during matchmode"); + RegServerCmd("confogl_startclientchecking", _StartClientChecking_Cmd, "Start checking and enforcing client cvars tracked by this plugin"); +} + +static void ClearAllSettings() +{ + ClientSettingsArray.Clear(); +} + +/*#if SOURCEMOD_V_MINOR > 9 +static void ClearCLSEntry(CLSEntry entry) +{ + entry.CLSE_hasMin = false; + entry.CLSE_min = 0.0; + entry.CLSE_hasMax = false; + entry.CLSE_max = 0.0; + entry.CLSE_cvar[0] = 0; +} +#else +static void ClearCLSEntry(CLSEntry entry[CLSEntry]) +{ + entry[CLSE_hasMin] = false; + entry[CLSE_min] = 0.0; + entry[CLSE_hasMax] = false; + entry[CLSE_max] = 0.0; + entry[CLSE_cvar][0] = 0; +} +#endif*/ + +public Action _CheckClientSettings_Timer(Handle hTimer) +{ + if (!IsPluginEnabled()) { + if (IsDebugEnabled()) { + LogMessage("[%s] Stopping client settings tracking", CLS_MODULE_NAME); + } + + ClientSettingsCheckTimer = null; + return Plugin_Stop; + } + + EnforceAllCliSettings(); + return Plugin_Continue; +} + +static void EnforceAllCliSettings() +{ + for (int i = 1; i <= MaxClients; i++) { + if (IsClientInGame(i) && !IsFakeClient(i)) { + EnforceCliSettings(i); + } + } +} + +static void EnforceCliSettings(int client) +{ + int iSize = ClientSettingsArray.Length; +#if SOURCEMOD_V_MINOR > 9 + CLSEntry clsetting; + for (int i = 0; i < iSize; i++) { + ClientSettingsArray.GetArray(i, clsetting, sizeof(clsetting)); + + QueryClientConVar(client, clsetting.CLSE_cvar, _EnforceCliSettings_QueryReply, i); + } +#else + CLSEntry clsetting[CLSEntry]; + for (int i = 0; i < iSize; i++) { + ClientSettingsArray.GetArray(i, clsetting[0], sizeof(clsetting)); + + QueryClientConVar(client, clsetting[CLSE_cvar], _EnforceCliSettings_QueryReply, i); + } +#endif +} + +public void _EnforceCliSettings_QueryReply(QueryCookie cookie, int client, ConVarQueryResult result, \ + const char[] cvarName, const char[] cvarValue, any value) +{ + if (!IsClientConnected(client) || !IsClientInGame(client) || IsClientInKickQueue(client)) { + // Client disconnected or got kicked already + return; + } + + if (result) { + LogMessage("[%s] Couldn't retrieve cvar %s from %L, kicked from server", CLS_MODULE_NAME, cvarName, client); + KickClient(client, "CVar '%s' protected or missing! Hax?", cvarName); + return; + } + + float fCvarVal = StringToFloat(cvarValue); + int clsetting_index = value; + +#if SOURCEMOD_V_MINOR > 9 + CLSEntry clsetting; + ClientSettingsArray.GetArray(clsetting_index, clsetting, sizeof(clsetting)); + + if ((clsetting.CLSE_hasMin && fCvarVal < clsetting.CLSE_min) + || (clsetting.CLSE_hasMax && fCvarVal > clsetting.CLSE_max) + ) { + switch (clsetting.CLSE_action) { + case CLSA_Kick: { + LogMessage("[%s] Kicking %L for bad %s value (%f). Min: %d %f Max: %d %f", \ + CLS_MODULE_NAME, client, cvarName, fCvarVal, clsetting.CLSE_hasMin, \ + clsetting.CLSE_min, clsetting.CLSE_hasMax, clsetting.CLSE_max); + + /*PrintToChatAll("\x01[\x05Confogl\x01] Kicking \x04%L\x01 for having an illegal value for '\x04%s\x01' (\x04%f\x01) !!!", \ + client, cvarName, fCvarVal);*/ + CPrintToChatAll("{blue}[{default}Confogl{blue}] {olive}%L{default} was kicked for having an illegal value for '{green}%s{default}' {blue}({default}%f{blue})", \ + client, cvarName, fCvarVal); + + char kickMessage[256] = "Illegal Client Value for "; + Format(kickMessage, sizeof(kickMessage), "%s%s (%.2f)", kickMessage, cvarName, fCvarVal); + + if (clsetting.CLSE_hasMin) { + Format(kickMessage, sizeof(kickMessage), "%s, Min %.2f", kickMessage, clsetting.CLSE_min); + } + + if (clsetting.CLSE_hasMax) { + Format(kickMessage, sizeof(kickMessage), "%s, Max %.2f", kickMessage, clsetting.CLSE_max); + } + + KickClient(client, "%s", kickMessage); + } + case CLSA_Log: { + LogMessage("[%s] Client %L has a bad %s value (%f). Min: %d %f Max: %d %f", \ + CLS_MODULE_NAME, client, cvarName, fCvarVal, clsetting.CLSE_hasMin, \ + clsetting.CLSE_min, clsetting.CLSE_hasMax, clsetting.CLSE_max); + } + } + } +#else + CLSEntry clsetting[CLSEntry]; + ClientSettingsArray.GetArray(clsetting_index, clsetting[0], sizeof(clsetting)); + + if ((clsetting[CLSE_hasMin] && fCvarVal < clsetting[CLSE_min]) + || (clsetting[CLSE_hasMax] && fCvarVal > clsetting[CLSE_max]) + ) { + switch (clsetting[CLSE_action]) { + case CLSA_Kick: { + LogMessage("[%s] Kicking %L for bad %s value (%f). Min: %d %f Max: %d %f", \ + CLS_MODULE_NAME, client, cvarName, fCvarVal, clsetting[CLSE_hasMin], \ + clsetting[CLSE_min], clsetting[CLSE_hasMax], clsetting[CLSE_max]); + + /*PrintToChatAll("\x01[\x05Confogl\x01] Kicking \x04%L\x01 for having an illegal value for '\x04%s\x01' (\x04%f\x01) !!!", \ + client, cvarName, fCvarVal);*/ + CPrintToChatAll("{blue}[{default}Confogl{blue}] {olive}%L{default} was kicked for having an illegal value for '{green}%s{default}' {blue}({default}%f{blue})", \ + client, cvarName, fCvarVal); + + char kickMessage[256] = "Illegal Client Value for "; + Format(kickMessage, sizeof(kickMessage), "%s%s (%.2f)", kickMessage, cvarName, fCvarVal); + + if (clsetting[CLSE_hasMin]) { + Format(kickMessage, sizeof(kickMessage), "%s, Min %.2f", kickMessage, clsetting[CLSE_min]); + } + + if (clsetting[CLSE_hasMax]) { + Format(kickMessage, sizeof(kickMessage), "%s, Max %.2f", kickMessage, clsetting[CLSE_max]); + } + + KickClient(client, "%s", kickMessage); + } + case CLSA_Log: { + LogMessage("[%s] Client %L has a bad %s value (%f). Min: %d %f Max: %d %f", \ + CLS_MODULE_NAME, client, cvarName, fCvarVal, clsetting[CLSE_hasMin], \ + clsetting[CLSE_min], clsetting[CLSE_hasMax], clsetting[CLSE_max]); + } + } + } +#endif +} + +public Action _ClientSettings_Cmd(int client, int args) +{ + int iSize = ClientSettingsArray.Length; + ReplyToCommand(client, "[Confogl] Tracked Client CVars (Total %d)", iSize); + +#if SOURCEMOD_V_MINOR > 9 + CLSEntry clsetting; +#else + CLSEntry clsetting[CLSEntry]; +#endif + + char message[256], shortbuf[64]; + for (int i = 0; i < iSize; i++) { + #if SOURCEMOD_V_MINOR > 9 + ClientSettingsArray.GetArray(i, clsetting, sizeof(clsetting)); + Format(message, sizeof(message), "[Confogl] Client CVar: %s ", clsetting.CLSE_cvar); + + if (clsetting.CLSE_hasMin) { + Format(shortbuf, sizeof(shortbuf), "Min: %f ", clsetting.CLSE_min); + StrCat(message, sizeof(message), shortbuf); + } + + if (clsetting.CLSE_hasMax) { + Format(shortbuf, sizeof(shortbuf), "Max: %f ", clsetting.CLSE_max); + StrCat(message, sizeof(message), shortbuf); + } + + switch (clsetting.CLSE_action) { + case CLSA_Kick: { + StrCat(message, sizeof(message), "Action: Kick"); + } + case CLSA_Log: { + StrCat(message, sizeof(message), "Action: Log"); + } + } + #else + ClientSettingsArray.GetArray(i, clsetting[0], sizeof(clsetting)); + Format(message, sizeof(message), "[Confogl] Client CVar: %s ", clsetting[CLSE_cvar]); + + if (clsetting[CLSE_hasMin]) { + Format(shortbuf, sizeof(shortbuf), "Min: %f ", clsetting[CLSE_min]); + StrCat(message, sizeof(message), shortbuf); + } + + if (clsetting[CLSE_hasMax]) { + Format(shortbuf, sizeof(shortbuf), "Max: %f ", clsetting[CLSE_max]); + StrCat(message, sizeof(message), shortbuf); + } + + switch (clsetting[CLSE_action]) { + case CLSA_Kick: { + StrCat(message, sizeof(message), "Action: Kick"); + } + case CLSA_Log: { + StrCat(message, sizeof(message), "Action: Log"); + } + } + #endif + + ReplyToCommand(client, message); + } + + return Plugin_Handled; +} + +public Action _TrackClientCvar_Cmd(int args) +{ + if (args < 3 || args == 4) { + PrintToServer("Usage: confogl_trackclientcvar [ []]"); + + if (IsDebugEnabled()) { + char cmdbuf[128]; + GetCmdArgString(cmdbuf, sizeof(cmdbuf)); + Debug_LogError(CLS_MODULE_NAME, "Invalid track client cvar: %s", cmdbuf); + } + + return Plugin_Handled; + } + + char sBuffer[CLS_CVAR_MAXLEN], cvar[CLS_CVAR_MAXLEN]; + bool hasMax; + float max; + int action = CLSA_Kick; + + GetCmdArg(1, cvar, sizeof(cvar)); + + if (!strlen(cvar)) { + PrintToServer("Unreadable cvar"); + + if (IsDebugEnabled()) { + char cmdbuf[128]; + GetCmdArgString(cmdbuf, sizeof(cmdbuf)); + Debug_LogError(CLS_MODULE_NAME, "Invalid track client cvar: %s", cmdbuf); + } + + return Plugin_Handled; + } + + GetCmdArg(2, sBuffer, sizeof(sBuffer)); + bool hasMin = view_as(StringToInt(sBuffer)); + + GetCmdArg(3, sBuffer, sizeof(sBuffer)); + float min = StringToFloat(sBuffer); + + if (args >= 5) { + GetCmdArg(4, sBuffer, sizeof(sBuffer)); + hasMax = view_as(StringToInt(sBuffer)); + + GetCmdArg(5, sBuffer, sizeof(sBuffer)); + max = StringToFloat(sBuffer); + } + + if (args >= 6) { + GetCmdArg(6, sBuffer, sizeof(sBuffer)); + action = StringToInt(sBuffer); + } + + _AddClientCvar(cvar, hasMin, min, hasMax, max, action); + + return Plugin_Handled; +} + +public Action _ResetTracking_Cmd(int args) +{ + if (ClientSettingsCheckTimer != null) { + PrintToServer("Can't reset tracking in the middle of a match"); + return Plugin_Handled; + } + + ClearAllSettings(); + PrintToServer("Client CVar Tracking Information Reset!"); + + return Plugin_Handled; +} + +public Action _StartClientChecking_Cmd(int args) +{ + _StartTracking(); + + return Plugin_Handled; +} + +static void _StartTracking() +{ + if (IsPluginEnabled() && ClientSettingsCheckTimer == null) { + if (IsDebugEnabled()) { + LogMessage("[%s] Starting repeating check timer", CLS_MODULE_NAME); + } + + ClientSettingsCheckTimer = CreateTimer(CLIENT_CHECK_INTERVAL, _CheckClientSettings_Timer, _, TIMER_REPEAT); + } else { + PrintToServer("Can't start plugin tracking or tracking already started"); + } +} + +static void _AddClientCvar(const char[] cvar, bool hasMin, float min, bool hasMax, float max, int action) +{ + if (ClientSettingsCheckTimer != null) { + PrintToServer("Can't track new cvars in the middle of a match"); + + if (IsDebugEnabled()) { + LogMessage("[%s] Attempt to track new cvar %s during a match!", CLS_MODULE_NAME, cvar); + } + + return; + } + + if (!(hasMin || hasMax)) { + Debug_LogError(CLS_MODULE_NAME, "Client CVar %s specified without max or min", cvar); + return; + } + + if (hasMin && hasMax && max < min) { + Debug_LogError(CLS_MODULE_NAME, "Client CVar %s specified max < min (%f < %f)", cvar, max, min); + return; + } + + if (strlen(cvar) >= CLS_CVAR_MAXLEN) { + Debug_LogError(CLS_MODULE_NAME, "CVar Specified (%s) is longer than max cvar length (%d)", cvar, CLS_CVAR_MAXLEN); + return; + } + + int iSize = ClientSettingsArray.Length; + +#if SOURCEMOD_V_MINOR > 9 + CLSEntry newEntry; + for (int i = 0; i < iSize; i++) { + ClientSettingsArray.GetArray(i, newEntry, sizeof(newEntry)); + if (strcmp(newEntry.CLSE_cvar, cvar, false) == 0) { + Debug_LogError(CLS_MODULE_NAME, "Attempt to track CVar %s, which is already being tracked.", cvar); + return; + } + } + + newEntry.CLSE_hasMin = hasMin; + newEntry.CLSE_min = min; + newEntry.CLSE_hasMax = hasMax; + newEntry.CLSE_max = max; + newEntry.CLSE_action = action; + strcopy(newEntry.CLSE_cvar, CLS_CVAR_MAXLEN, cvar); + + if (IsDebugEnabled()) { + LogMessage("[%s] Tracking Cvar %s Min %d %f Max %d %f Action %d", CLS_MODULE_NAME, cvar, hasMin, min, hasMax, max, action); + } + + ClientSettingsArray.PushArray(newEntry, sizeof(newEntry)); +#else + CLSEntry newEntry[CLSEntry]; + for (int i = 0; i < iSize; i++) { + ClientSettingsArray.GetArray(i, newEntry[0], sizeof(newEntry)); + if (strcmp(newEntry[CLSE_cvar], cvar, false) == 0) { + Debug_LogError(CLS_MODULE_NAME, "Attempt to track CVar %s, which is already being tracked.", cvar); + return; + } + } + + newEntry[CLSE_hasMin] = hasMin; + newEntry[CLSE_min] = min; + newEntry[CLSE_hasMax] = hasMax; + newEntry[CLSE_max] = max; + newEntry[CLSE_action] = action; + strcopy(newEntry[CLSE_cvar], CLS_CVAR_MAXLEN, cvar); + + if (IsDebugEnabled()) { + LogMessage("[%s] Tracking Cvar %s Min %d %f Max %d %f Action %d", CLS_MODULE_NAME, cvar, hasMin, min, hasMax, max, action); + } + + ClientSettingsArray.PushArray(newEntry[0], sizeof(newEntry)); +#endif +} diff --git a/addons/sourcemod/scripting/confoglcompmod/CvarSettings.sp b/addons/sourcemod/scripting/confoglcompmod/CvarSettings.sp new file mode 100644 index 000000000..55395df47 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/CvarSettings.sp @@ -0,0 +1,402 @@ +#if defined __cvar_settings_included + #endinput +#endif +#define __cvar_settings_included + +#define CVS_MODULE_NAME "CvarSettings" + +#define CVARS_DEBUG 0 +#define CVS_CVAR_MAXLEN 64 + +#if SOURCEMOD_V_MINOR > 9 +enum struct CVSEntry +{ + ConVar CVSE_cvar; + char CVSE_oldval[CVS_CVAR_MAXLEN]; + char CVSE_newval[CVS_CVAR_MAXLEN]; +} +#else +enum CVSEntry +{ + ConVar:CVSE_cvar, + String:CVSE_oldval[CVS_CVAR_MAXLEN], + String:CVSE_newval[CVS_CVAR_MAXLEN] +}; +#endif + +static bool + bTrackingStarted = false; + +static ArrayList + CvarSettingsArray = null; + +void CVS_OnModuleStart() +{ +#if SOURCEMOD_V_MINOR > 9 + CVSEntry cvsetting; +#else + CVSEntry cvsetting[CVSEntry]; +#endif + + CvarSettingsArray = new ArrayList(sizeof(cvsetting)); + + RegConsoleCmd("confogl_cvarsettings", CVS_CvarSettings_Cmd, "List all ConVars being enforced by Confogl"); + RegConsoleCmd("confogl_cvardiff", CVS_CvarDiff_Cmd, "List any ConVars that have been changed from their initialized values"); + + RegServerCmd("confogl_addcvar", CVS_AddCvar_Cmd, "Add a ConVar to be set by Confogl"); + RegServerCmd("confogl_setcvars", CVS_SetCvars_Cmd, "Starts enforcing ConVars that have been added."); + RegServerCmd("confogl_resetcvars", CVS_ResetCvars_Cmd, "Resets enforced ConVars. Cannot be used during a match!"); +} + +void CVS_OnModuleEnd() +{ + ClearAllSettings(); +} + +void CVS_OnConfigsExecuted() +{ + if (bTrackingStarted) { + SetEnforcedCvars(); + } +} + +public Action CVS_SetCvars_Cmd(int args) +{ + if (!IsPluginEnabled()) { + return Plugin_Handled; + } + + if (bTrackingStarted) { + PrintToServer("Tracking has already been started"); + return Plugin_Handled; + } + +#if CVARS_DEBUG + LogMessage("[%s] No longer accepting new ConVars", CVS_MODULE_NAME); +#endif + + SetEnforcedCvars(); + bTrackingStarted = true; + + return Plugin_Handled; +} + +public Action CVS_AddCvar_Cmd(int args) +{ + if (args != 2) { + PrintToServer("Usage: confogl_addcvar "); + + if (IsDebugEnabled()) { + char cmdbuf[MAX_NAME_LENGTH]; + GetCmdArgString(cmdbuf, sizeof(cmdbuf)); + Debug_LogError(CVS_MODULE_NAME, "Invalid Cvar Add: %s", cmdbuf); + } + + return Plugin_Handled; + } + + char cvar[CVS_CVAR_MAXLEN], newval[CVS_CVAR_MAXLEN]; + GetCmdArg(1, cvar, sizeof(cvar)); + GetCmdArg(2, newval, sizeof(newval)); + + AddCvar(cvar, newval); + + return Plugin_Handled; +} + +public Action CVS_ResetCvars_Cmd(int args) +{ + if (IsPluginEnabled()) { + PrintToServer("Can't reset tracking in the middle of a match"); + return Plugin_Handled; + } + + ClearAllSettings(); + PrintToServer("Server CVar Tracking Information Reset!"); + + return Plugin_Handled; +} + +public Action CVS_CvarSettings_Cmd(int client, int args) +{ + if (!IsPluginEnabled()) { + return Plugin_Handled; + } + + if (!bTrackingStarted) { + ReplyToCommand(client, "[Confogl] CVar tracking has not been started!! THIS SHOULD NOT OCCUR DURING A MATCH!"); + return Plugin_Handled; + } + + char buffer[CVS_CVAR_MAXLEN], name[CVS_CVAR_MAXLEN]; + int cvscount = CvarSettingsArray.Length; + + ReplyToCommand(client, "[Confogl] Enforced Server CVars (Total %d)", cvscount); + + GetCmdArg(1, buffer, sizeof(buffer)); + int offset = StringToInt(buffer); + + if (offset < 0 || offset > cvscount) { + return Plugin_Handled; + } + + int temp = cvscount; + if ((offset + 20) < cvscount) { + temp = offset + 20; + } + +#if SOURCEMOD_V_MINOR > 9 + CVSEntry cvsetting; + + for (int i = offset; i < temp && i < cvscount; i++) { + CvarSettingsArray.GetArray(i, cvsetting, sizeof(cvsetting)); + + (cvsetting.CVSE_cvar).GetString(buffer, sizeof(buffer)); + (cvsetting.CVSE_cvar).GetName(name, sizeof(name)); + + ReplyToCommand(client, "[Confogl] Server CVar: %s, Desired Value: %s, Current Value: %s", name, cvsetting.CVSE_newval, buffer); + } +#else + CVSEntry cvsetting[CVSEntry]; + + for (int i = offset; i < temp && i < cvscount; i++) { + CvarSettingsArray.GetArray(i, cvsetting[0], sizeof(cvsetting)); + + cvsetting[CVSE_cvar].GetString(buffer, sizeof(buffer)); + cvsetting[CVSE_cvar].GetName(name, sizeof(name)); + + ReplyToCommand(client, "[Confogl] Server CVar: %s, Desired Value: %s, Current Value: %s", name, cvsetting[CVSE_newval], buffer); + } +#endif + + if ((offset + 20) < cvscount) { + ReplyToCommand(client, "[Confogl] To see more CVars, use confogl_cvarsettings %d", offset + 20); + } + + return Plugin_Handled; +} + +public Action CVS_CvarDiff_Cmd(int client, int args) +{ + if (!IsPluginEnabled()) { + return Plugin_Handled; + } + + if (!bTrackingStarted) { + ReplyToCommand(client, "[Confogl] CVar tracking has not been started!! THIS SHOULD NOT OCCUR DURING A MATCH!"); + return Plugin_Handled; + } + + char buffer[CVS_CVAR_MAXLEN], name[CVS_CVAR_MAXLEN]; + int cvscount = CvarSettingsArray.Length; + + GetCmdArg(1, buffer, sizeof(buffer)); + int offset = StringToInt(buffer); + + if (offset > cvscount) { + return Plugin_Handled; + } + + int foundCvars = 0; + +#if SOURCEMOD_V_MINOR > 9 + CVSEntry cvsetting; + + while (offset < cvscount && foundCvars < 20) { + CvarSettingsArray.GetArray(offset, cvsetting, sizeof(cvsetting)); + + (cvsetting.CVSE_cvar).GetString(buffer, sizeof(buffer)); + (cvsetting.CVSE_cvar).GetName(name, sizeof(name)); + + if (strcmp(cvsetting.CVSE_newval, buffer) != 0) { + ReplyToCommand(client, "[Confogl] Server CVar: %s, Desired Value: %s, Current Value: %s", name, cvsetting.CVSE_newval, buffer); + foundCvars++; + } + + offset++; + } +#else + CVSEntry cvsetting[CVSEntry]; + + while (offset < cvscount && foundCvars < 20) { + CvarSettingsArray.GetArray(offset, cvsetting[0], sizeof(cvsetting)); + + cvsetting[CVSE_cvar].GetString(buffer, sizeof(buffer)); + cvsetting[CVSE_cvar].GetName(name, sizeof(name)); + + if (strcmp(cvsetting[CVSE_newval], buffer) != 0) { + ReplyToCommand(client, "[Confogl] Server CVar: %s, Desired Value: %s, Current Value: %s", name, cvsetting[CVSE_newval], buffer); + foundCvars++; + } + + offset++; + } +#endif + + if (offset < cvscount) { + ReplyToCommand(client, "[Confogl] To see more CVars, use confogl_cvarsettings %d", offset); + } + + return Plugin_Handled; +} + +static void ClearAllSettings() +{ + bTrackingStarted = false; + int iSize = CvarSettingsArray.Length; + +#if SOURCEMOD_V_MINOR > 9 + CVSEntry cvsetting; + + for (int i = 0; i < iSize; i++) { + CvarSettingsArray.GetArray(i, cvsetting, sizeof(cvsetting)); + + (cvsetting.CVSE_cvar).RemoveChangeHook(CVS_ConVarChange); + (cvsetting.CVSE_cvar).SetString(cvsetting.CVSE_oldval); + } +#else + CVSEntry cvsetting[CVSEntry]; + + for (int i = 0; i < iSize; i++) { + CvarSettingsArray.GetArray(i, cvsetting[0], sizeof(cvsetting)); + + cvsetting[CVSE_cvar].RemoveChangeHook(CVS_ConVarChange); + cvsetting[CVSE_cvar].SetString(cvsetting[CVSE_oldval]); + } +#endif + + CvarSettingsArray.Clear(); +} + +static void SetEnforcedCvars() +{ + int iSize = CvarSettingsArray.Length; + +#if SOURCEMOD_V_MINOR > 9 + CVSEntry cvsetting; + + for (int i = 0; i < iSize; i++) { + CvarSettingsArray.GetArray(i, cvsetting, sizeof(cvsetting)); + + #if CVARS_DEBUG + char debug_buffer[CVS_CVAR_MAXLEN]; + (cvsetting.CVSE_cvar).GetName(debug_buffer, sizeof(debug_buffer)); + LogMessage("[%s] cvar = %s, newval = %s", CVS_MODULE_NAME, debug_buffer, cvsetting.CVSE_newval); + #endif + + (cvsetting.CVSE_cvar).SetString(cvsetting.CVSE_newval); + } +#else + CVSEntry cvsetting[CVSEntry]; + + for (int i = 0; i < iSize; i++) { + CvarSettingsArray.GetArray(i, cvsetting[0], sizeof(cvsetting)); + + #if CVARS_DEBUG + char debug_buffer[CVS_CVAR_MAXLEN]; + cvsetting[CVSE_cvar].GetName(debug_buffer, sizeof(debug_buffer)); + LogMessage("[%s] cvar = %s, newval = %s", CVS_MODULE_NAME, debug_buffer, cvsetting[CVSE_newval]); + #endif + + cvsetting[CVSE_cvar].SetString(cvsetting[CVSE_newval]); + } +#endif +} + +static void AddCvar(const char[] cvar, const char[] newval) +{ + if (bTrackingStarted) { + #if CVARS_DEBUG + LogMessage("[%s] Attempt to track new cvar %s during a match!", CVS_MODULE_NAME, cvar); + #endif + return; + } + + if (strlen(cvar) >= CVS_CVAR_MAXLEN) { + Debug_LogError(CVS_MODULE_NAME, "CVar Specified (%s) is longer than max cvar/value length (%d)", cvar, CVS_CVAR_MAXLEN); + return; + } + + if (strlen(newval) >= CVS_CVAR_MAXLEN) { + Debug_LogError(CVS_MODULE_NAME, "New Value Specified (%s) is longer than max cvar/value length (%d)", newval, CVS_CVAR_MAXLEN); + return; + } + + ConVar newCvar = FindConVar(cvar); + + if (newCvar == null) { + Debug_LogError(CVS_MODULE_NAME, "Could not find CVar specified (%s)", cvar); + return; + } + + char cvarBuffer[CVS_CVAR_MAXLEN]; + int iSize = CvarSettingsArray.Length; + +#if SOURCEMOD_V_MINOR > 9 + CVSEntry newEntry; + + for (int i = 0; i < iSize; i++) { + CvarSettingsArray.GetArray(i, newEntry, sizeof(newEntry)); + + (newEntry.CVSE_cvar).GetName(cvarBuffer, CVS_CVAR_MAXLEN); + + if (strcmp(cvar, cvarBuffer, false) == 0) { + Debug_LogError(CVS_MODULE_NAME, "Attempt to track ConVar %s, which is already being tracked.", cvar); + return; + } + } + + newCvar.GetString(cvarBuffer, CVS_CVAR_MAXLEN); + + newEntry.CVSE_cvar = newCvar; + strcopy(newEntry.CVSE_oldval, CVS_CVAR_MAXLEN, cvarBuffer); + strcopy(newEntry.CVSE_newval, CVS_CVAR_MAXLEN, newval); + + newCvar.AddChangeHook(CVS_ConVarChange); + +#if CVARS_DEBUG + LogMessage("[%s] cvar = %s, newval = %s, oldval = %s", CVS_MODULE_NAME, cvar, newval, cvarBuffer); +#endif + + CvarSettingsArray.PushArray(newEntry, sizeof(newEntry)); +#else + CVSEntry newEntry[CVSEntry]; + + for (int i = 0; i < iSize; i++) { + CvarSettingsArray.GetArray(i, newEntry[0], sizeof(newEntry)); + + newEntry[CVSE_cvar].GetName(cvarBuffer, CVS_CVAR_MAXLEN); + + if (strcmp(cvar, cvarBuffer, false) == 0) { + Debug_LogError(CVS_MODULE_NAME, "Attempt to track ConVar %s, which is already being tracked.", cvar); + return; + } + } + + newCvar.GetString(cvarBuffer, CVS_CVAR_MAXLEN); + + newEntry[CVSE_cvar] = newCvar; + strcopy(newEntry[CVSE_oldval], CVS_CVAR_MAXLEN, cvarBuffer); + strcopy(newEntry[CVSE_newval], CVS_CVAR_MAXLEN, newval); + + newCvar.AddChangeHook(CVS_ConVarChange); + +#if CVARS_DEBUG + LogMessage("[%s] cvar = %s, newval = %s, oldval = %s", CVS_MODULE_NAME, cvar, newval, cvarBuffer); +#endif + + CvarSettingsArray.PushArray(newEntry[0], sizeof(newEntry)); +#endif +} + +public void CVS_ConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + if (bTrackingStarted) { + char sName[CVS_CVAR_MAXLEN]; + hConVar.GetName(sName, sizeof(sName)); + + PrintToServer("[Confogl] Tracked Server CVar '%s' changed from '%s' to '%s' !!!", sName, sOldValue, sNewValue); + //PrintToChatAll("[Confogl] Tracked Server CVar '%s' changed from '%s' to '%s' !!!", sName, sOldValue, sNewValue); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Tracked Server CVar '{green}%s{default}' changed from '{blue}%s{default}' to '{blue}%s{default}' !!!", sName, sOldValue, sNewValue); + } +} diff --git a/addons/sourcemod/scripting/confoglcompmod/EntityRemover.sp b/addons/sourcemod/scripting/confoglcompmod/EntityRemover.sp new file mode 100644 index 000000000..4f73d4d04 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/EntityRemover.sp @@ -0,0 +1,403 @@ +#if defined __entity_remover_included + #endinput +#endif +#define __entity_remover_included + +#define ER_MODULE_NAME "EntityRemover" + +#define DEBUG_ER 0 + +#define ER_KV_ACTION_KILL 1 + +#define ER_KV_PROPTYPE_INT 1 +#define ER_KV_PROPTYPE_FLOAT 2 +#define ER_KV_PROPTYPE_BOOL 3 +#define ER_KV_PROPTYPE_STRING 4 + +#define ER_KV_CONDITION_EQUAL 1 +#define ER_KV_CONDITION_NEQUAL 2 +#define ER_KV_CONDITION_LESS 3 +#define ER_KV_CONDITION_GREAT 4 +#define ER_KV_CONDITION_CONTAINS 5 + +static bool + ER_bKillParachutist = true, + ER_bReplaceGhostHurt = false; + +static ConVar + ER_hKillParachutist = null, + ER_hReplaceGhostHurt = null; + +static KeyValues + kERData = null; + +void ER_OnModuleStart() +{ + ER_hKillParachutist = CreateConVarEx("remove_parachutist", "1", "Removes the parachutist from c3m2", _, true, 0.0, true, 1.0); + ER_hReplaceGhostHurt = CreateConVarEx( \ + "disable_ghost_hurt", \ + "0", \ + "Replaces all trigger_ghost_hurt with trigger_hurt, blocking ghost spawns from dying.", \ + _, true, 0.0, true, 1.0 \ + ); + + ER_bKillParachutist = ER_hKillParachutist.BoolValue; + ER_bReplaceGhostHurt = ER_hReplaceGhostHurt.BoolValue; + + ER_hKillParachutist.AddChangeHook(ER_ConVarChange); + ER_hReplaceGhostHurt.AddChangeHook(ER_ConVarChange); + + ER_KV_Load(); + + RegAdminCmd("confogl_erdata_reload", ER_KV_CmdReload, ADMFLAG_CONFIG); + + HookEvent("round_start", ER_RoundStart_Event, EventHookMode_PostNoCopy); +} + +public void ER_ConVarChange(ConVar hConvar, const char[] sOldValue, const char[] sNewValue) +{ + ER_bKillParachutist = ER_hKillParachutist.BoolValue; + ER_bReplaceGhostHurt = ER_hReplaceGhostHurt.BoolValue; +} + +void ER_OnModuleEnd() +{ + ER_KV_Close(); +} + +static void ER_KV_Close() +{ + if (kERData != null) { + delete kERData; + kERData = null; + } +} + +static void ER_KV_Load() +{ + char sNameBuff[PLATFORM_MAX_PATH], sDescBuff[256], sValBuff[32]; + + if (DEBUG_ER || IsDebugEnabled()) { + LogMessage("[%s] Loading EntityRemover KeyValues", ER_MODULE_NAME); + } + + kERData = new KeyValues("EntityRemover"); + + BuildConfigPath(sNameBuff, sizeof(sNameBuff), "entityremove.txt"); //Build our filepath + + if (!kERData.ImportFromFile(sNameBuff)) { + Debug_LogError(ER_MODULE_NAME, "Couldn't load EntityRemover data!"); + ER_KV_Close(); + return; + } + + // Create cvars for all entity removes + if (DEBUG_ER || IsDebugEnabled()) { + LogMessage("[%s] Creating entry CVARs", ER_MODULE_NAME); + } + + kERData.GotoFirstSubKey(); + + do { + kERData.GotoFirstSubKey(); + + do { + kERData.GetString("cvar", sNameBuff, sizeof(sNameBuff)); + kERData.GetString("cvar_desc", sDescBuff, sizeof(sDescBuff)); + kERData.GetString("cvar_val", sValBuff, sizeof(sValBuff)); + + CreateConVarEx(sNameBuff, sValBuff, sDescBuff); + + if (DEBUG_ER || IsDebugEnabled()) { + LogMessage("[%s] Creating CVAR %s", ER_MODULE_NAME, sNameBuff); + } + + } while(kERData.GotoNextKey()); + + kERData.GoBack(); + } while(kERData.GotoNextKey()); + + kERData.Rewind(); +} + +public Action ER_KV_CmdReload(int client, int args) +{ + if (!IsPluginEnabled()) { + return Plugin_Continue; + } + + ReplyToCommand(client, "[ER] Reloading EntityRemoveData"); + ER_KV_Reload(); + + return Plugin_Handled; +} + +static void ER_KV_Reload() +{ + ER_KV_Close(); + ER_KV_Load(); +} + +static bool ER_KV_TestCondition(int lhsval, int rhsval, int condition) +{ + switch (condition) { + case ER_KV_CONDITION_EQUAL: { + return (lhsval == rhsval); + } + case ER_KV_CONDITION_NEQUAL: { + return (lhsval != rhsval); + } + case ER_KV_CONDITION_LESS: { + return (lhsval < rhsval); + } + case ER_KV_CONDITION_GREAT: { + return (lhsval > rhsval); + } + } + + return false; +} + +static bool ER_KV_TestConditionFloat(float lhsval, float rhsval, int condition) +{ + switch (condition) { + case ER_KV_CONDITION_EQUAL: { + return (lhsval == rhsval); + } + case ER_KV_CONDITION_NEQUAL: { + return (lhsval != rhsval); + } + case ER_KV_CONDITION_LESS: { + return (lhsval < rhsval); + } + case ER_KV_CONDITION_GREAT: { + return (lhsval > rhsval); + } + } + + return false; +} + +static bool ER_KV_TestConditionString(const char[] lhsval, const char[] rhsval, int condition) +{ + switch (condition) { + case ER_KV_CONDITION_EQUAL: { + return (strcmp(lhsval, rhsval) == 0); + } + case ER_KV_CONDITION_NEQUAL: { + return (strcmp(lhsval, rhsval) != 0); + } + case ER_KV_CONDITION_CONTAINS: { + return (StrContains(lhsval, rhsval) != -1); + } + } + + return false; +} + +// Returns true if the entity is still alive (not killed) +static bool ER_KV_ParseEntity(KeyValues kEntry, int iEntity) +{ + char sBuffer[64], mapname[64]; + + // Check CVAR for this entry + kEntry.GetString("cvar", sBuffer, sizeof(sBuffer)); + + if (strlen(sBuffer) && !(FindConVarEx(sBuffer).BoolValue)) { + return true; + } + + // Check MapName for this entry + GetCurrentMap(mapname, sizeof(mapname)); + + kEntry.GetString("map", sBuffer, sizeof(sBuffer)); + if (strlen(sBuffer) && StrContains(sBuffer, mapname) == -1) { + return true; + } + + kEntry.GetString("excludemap", sBuffer, sizeof(sBuffer)); + if (strlen(sBuffer) && StrContains(sBuffer, mapname) != -1) { + return true; + } + + // Do property check for this entry + kEntry.GetString("property", sBuffer, sizeof(sBuffer)); + if (strlen(sBuffer)) { + int proptype = kEntry.GetNum("proptype"); + + switch (proptype) { + case ER_KV_PROPTYPE_INT, ER_KV_PROPTYPE_BOOL: { + int rhsval = kEntry.GetNum("propval"); + PropType prop_type = view_as(kEntry.GetNum("propdata")); + int lhsval = GetEntProp(iEntity, prop_type, sBuffer); + + if (!ER_KV_TestCondition(lhsval, rhsval, kEntry.GetNum("condition"))) { + return true; + } + } + case ER_KV_PROPTYPE_FLOAT: { + float rhsval = kEntry.GetFloat("propval"); + PropType prop_type = view_as(kEntry.GetNum("propdata")); + float lhsval = GetEntPropFloat(iEntity, prop_type, sBuffer); + + if (!ER_KV_TestConditionFloat(lhsval, rhsval, kEntry.GetNum("condition"))) { + return true; + } + } + case ER_KV_PROPTYPE_STRING: { + char rhsval[64], lhsval[64]; + kEntry.GetString("propval", rhsval, sizeof(rhsval)); + PropType prop_type = view_as(kEntry.GetNum("propdata")); + GetEntPropString(iEntity, prop_type, sBuffer, lhsval, sizeof(lhsval)); + + if (!ER_KV_TestConditionString(lhsval, rhsval, kEntry.GetNum("condition"))) { + return true; + } + } + } + } + + int iAction = kEntry.GetNum("action"); + return (ER_KV_TakeAction(iAction, iEntity)); +} + +// Returns true if the entity is still alive (not killed) +static bool ER_KV_TakeAction(int action, int iEntity) +{ + switch (action) { + case ER_KV_ACTION_KILL: { + if (DEBUG_ER || IsDebugEnabled()) { + LogMessage("[%s] Killing!", ER_MODULE_NAME); + } + + KillEntity(iEntity); + + return false; + } + default: { + Debug_LogError(ER_MODULE_NAME, "ParseEntity Encountered bad action!"); + } + } + + return true; +} + +static bool ER_KillParachutist(int ent) +{ + char buf[32]; + GetCurrentMap(buf, sizeof(buf)); + + if (strcmp(buf, "c3m2_swamp") == 0) { + GetEntPropString(ent, Prop_Data, "m_iName", buf, sizeof(buf)); + + if (!strncmp(buf, "parachute_", 10)) { + KillEntity(ent); + + return true; + } + } + + return false; +} + +static bool ER_ReplaceTriggerHurtGhost(int ent) +{ + char buf[MAX_ENTITY_NAME_LENGTH]; + GetEdictClassname(ent, buf, sizeof(buf)); + + if (strcmp(buf, "trigger_hurt_ghost") == 0) { + // Replace trigger_hurt_ghost with trigger_hurt + int replace = CreateEntityByName("trigger_hurt"); + if (replace == -1) { + Debug_LogError(ER_MODULE_NAME, "Could not create trigger_hurt entity!"); + return false; + } + + // Get modelname + char model[PLATFORM_MAX_PATH]; + GetEntPropString(ent, Prop_Data, "m_ModelName", model, sizeof(model)); + + // Get position and rotation + float pos[3], ang[3]; + GetEntPropVector(ent, Prop_Send, "m_vecOrigin", pos); + GetEntPropVector(ent, Prop_Send, "m_angRotation", ang); + + // Kill the old one + KillEntity(ent); + + // Set the values for the new one + DispatchKeyValue(replace, "StartDisabled", "0"); + DispatchKeyValue(replace, "spawnflags", "67"); + DispatchKeyValue(replace, "damagetype", "32"); + DispatchKeyValue(replace, "damagemodel", "0"); + DispatchKeyValue(replace, "damagecap", "10000"); + DispatchKeyValue(replace, "damage", "10000"); + DispatchKeyValue(replace, "model", model); + + DispatchKeyValue(replace, "filtername", "filter_infected"); + + // Spawn the new one + TeleportEntity(replace, pos, ang, NULL_VECTOR); + DispatchSpawn(replace); + ActivateEntity(replace); + + return true; + } + + return false; +} + +public void ER_RoundStart_Event(Event hEvent, const char[] sEventName, bool bdontBroadcast) +{ + if (!IsPluginEnabled()) { + return; + } + + CreateTimer(0.3, ER_RoundStart_Timer, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action ER_RoundStart_Timer(Handle hTimer) +{ + char sBuffer[MAX_ENTITY_NAME_LENGTH]; + if (DEBUG_ER || IsDebugEnabled()) { + LogMessage("[%s] Starting RoundStart Event", ER_MODULE_NAME); + } + + if (kERData != null) { + kERData.Rewind(); + } + + int iEntCount = GetEntityCount(); + + for (int ent = (MaxClients + 1); ent <= iEntCount; ent++) { + if (!IsValidEdict(ent)) { + continue; + } + + GetEdictClassname(ent, sBuffer, sizeof(sBuffer)); + + if (ER_bKillParachutist && ER_KillParachutist(ent)) { + //empty + } else if (ER_bReplaceGhostHurt && ER_ReplaceTriggerHurtGhost(ent)) { + //empty + } else if (kERData != null && kERData.JumpToKey(sBuffer)) { + if (DEBUG_ER || IsDebugEnabled()) { + LogMessage("[%s] Dealing with an instance of %s", ER_MODULE_NAME, sBuffer); + } + + kERData.GotoFirstSubKey(); + + do { + // Parse each entry for this entity's classname + // Stop if we run out of entries or we have killed the entity + if (!ER_KV_ParseEntity(kERData, ent)) { + break; + } + } while (kERData.GotoNextKey()); + + kERData.Rewind(); + } + } + + return Plugin_Stop; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/FinaleSpawn.sp b/addons/sourcemod/scripting/confoglcompmod/FinaleSpawn.sp new file mode 100644 index 000000000..2bd6cd56a --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/FinaleSpawn.sp @@ -0,0 +1,100 @@ +#if defined __finale_spawn_included + #endinput +#endif +#define __finale_spawn_included + +#define FS_MODULE_NAME "FinaleSpawn" + +#define SPAWN_RANGE 150 + +static ConVar + FS_hEnabled = null; + +static bool + FS_bIsFinale = false, + FS_bEnabled = true; + +void FS_OnModuleStart() +{ + FS_hEnabled = CreateConVarEx("reduce_finalespawnrange", "1", "Adjust the spawn range on finales for infected, to normal spawning range", _, true, 0.0, true, 1.0); + + FS_bEnabled = FS_hEnabled.BoolValue; + FS_hEnabled.AddChangeHook(FS_ConVarChange); + + HookEvent("round_end", FS_Round_Event, EventHookMode_PostNoCopy); + HookEvent("round_start", FS_Round_Event, EventHookMode_PostNoCopy); + HookEvent("finale_start", FS_FinaleStart_Event, EventHookMode_PostNoCopy); +} + +public void FS_Round_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + FS_bIsFinale = false; +} + +public void FS_FinaleStart_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + FS_bIsFinale = true; +} + +public void FS_ConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + FS_bEnabled = FS_hEnabled.BoolValue; +} + +void FS_OnClientPutInServer(int client) +{ + SDKHook(client, SDKHook_PreThinkPost, HookCallback); +} + +public void HookCallback(int client) +{ + if (!FS_bIsFinale) { + return; + } + + //if (!FS_bEnabled) { // rework version + if (!FS_bEnabled || !IsPluginEnabled()) { // original + return; + } + + if (GetClientTeam(client) != L4D2Team_Infected) { + return; + } + + if (GetEntProp(client, Prop_Send, "m_isGhost", 1) != 1) { + return; + } + + if (GetEntProp(client, Prop_Send, "m_ghostSpawnState") == SPAWNFLAG_TOOCLOSE) { + if (!TooClose(client)) { + SetEntProp(client, Prop_Send, "m_ghostSpawnState", SPAWNFLAG_READY); + } + } +} + +static bool TooClose(int client) +{ + int index = 0; + float fInfLocation[3], fSurvLocation[3], fVector[3]; + GetClientAbsOrigin(client, fInfLocation); + + for (int i = 0; i < 4; i++) { + index = GetSurvivorIndex(i); + if (index == 0) { + continue; + } + + if (!IsPlayerAlive(index)) { + continue; + } + + GetClientAbsOrigin(index, fSurvLocation); + MakeVectorFromPoints(fInfLocation, fSurvLocation, fVector); + + if (GetVectorLength(fVector) <= SPAWN_RANGE) { + return true; + } + } + + return false; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/GhostTank.sp b/addons/sourcemod/scripting/confoglcompmod/GhostTank.sp new file mode 100644 index 000000000..c20931b62 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/GhostTank.sp @@ -0,0 +1,345 @@ +#if defined __ghost_tank_included + #endinput +#endif +#define __ghost_tank_included + +#define GT_MODULE_NAME "GhostTank" + +#define THROWRANGE 99999999.0 +#define FIREIMMUNITY_TIME 5.0 + +static int + g_iPasses = 0, + g_iGT_TankClient = 0; + +static bool + g_bGT_FinaleVehicleIncoming = false, + g_bGT_TankIsInPlay = false, + g_bGT_TankHasFireImmunity = false, + g_bGT_HordesDisabled = false; + +static Handle + g_hGT_TankDeathTimer = null; + +static ConVar + g_hGT_Enabled = null, + g_hCvarTankThrowAllowRange = null, + g_hCvarDirectorTankLotterySelectionTime = null, + g_hCvarZMobSpawnMinIntervalNormal = null, + g_hCvarZMobSpawnMaxIntervalNormal = null, + g_hCvarMobSpawnMinSize = null, + g_hCvarMobSpawnMaxSize = null, + g_hCvarSurvivorIncapHealth = null, + g_hGT_RemoveEscapeTank = null, + g_hGT_BlockPunchRock = null, + g_hGT_DisableTankHordes = null; // Disable Tank Hordes items + +void GT_OnModuleStart() +{ + g_hGT_Enabled = CreateConVarEx( \ + "boss_tank", \ + "1", \ + "Tank can't be prelight, frozen and ghost until player takes over, punch fix, and no rock throw for AI tank while waiting for player", \ + _, true, 0.0, true, 1.0 \ + ); + + g_hGT_RemoveEscapeTank = CreateConVarEx("remove_escape_tank", "1", "Remove tanks that spawn as the rescue vehicle is incoming on finales.", _, true, 0.0, true, 1.0); + g_hGT_DisableTankHordes = CreateConVarEx("disable_tank_hordes", "0", "Disable natural hordes while tanks are in play", _, true, 0.0, true, 1.0); + g_hGT_BlockPunchRock = CreateConVarEx("block_punch_rock", "0", "Block tanks from punching and throwing a rock at the same time", _, true, 0.0, true, 1.0); + + g_hCvarSurvivorIncapHealth = FindConVar("survivor_incap_health"); + g_hCvarTankThrowAllowRange = FindConVar("tank_throw_allow_range"); + g_hCvarDirectorTankLotterySelectionTime = FindConVar("director_tank_lottery_selection_time"); + g_hCvarZMobSpawnMinIntervalNormal = FindConVar("z_mob_spawn_min_interval_normal"); + g_hCvarZMobSpawnMaxIntervalNormal = FindConVar("z_mob_spawn_max_interval_normal"); + g_hCvarMobSpawnMinSize = FindConVar("z_mob_spawn_min_size"); + g_hCvarMobSpawnMaxSize = FindConVar("z_mob_spawn_max_size"); + + HookEvent("round_start", GT_RoundStart, EventHookMode_PostNoCopy); + HookEvent("tank_spawn", GT_TankSpawn); + HookEvent("player_death", GT_TankKilled); + HookEvent("player_hurt", GT_TankOnFire); + HookEvent("item_pickup", GT_ItemPickup); + HookEvent("player_incapacitated", GT_PlayerIncap); + HookEvent("finale_vehicle_incoming", GT_FinaleVehicleIncoming, EventHookMode_PostNoCopy); +} + +Action GT_OnTankSpawn_Forward() +{ + if (IsPluginEnabled() && g_hGT_RemoveEscapeTank.BoolValue && g_bGT_FinaleVehicleIncoming) { + return Plugin_Handled; + } + + return Plugin_Continue; +} + +Action GT_OnCThrowActivate() +{ + if (IsPluginEnabled() + && g_bGT_TankIsInPlay + && g_hGT_BlockPunchRock.BoolValue + && GetClientButtons(g_iGT_TankClient) & IN_ATTACK + ) { + if (IsDebugEnabled()) { + LogMessage("[%s] Blocking Haymaker on %L", GT_MODULE_NAME, g_iGT_TankClient); + } + + return Plugin_Handled; + } + + return Plugin_Continue; +} + +Action GT_OnSpawnMob_Forward(int &amount) +{ + // quick fix. needs normalize_hordes 1 + if (IsPluginEnabled()) { + if (IsDebugEnabled()) { + LogMessage("[%s] SpawnMob(%d), HordesDisabled: %d TimerDuration: %f Minimum: %f Remaining: %f", \ + GT_MODULE_NAME, amount, g_bGT_HordesDisabled, L4D2_CTimerGetCountdownDuration(L4D2CT_MobSpawnTimer), \ + g_hCvarZMobSpawnMinIntervalNormal.FloatValue, L4D2_CTimerGetRemainingTime(L4D2CT_MobSpawnTimer)); + } + + if (g_bGT_HordesDisabled) { + if (amount < g_hCvarMobSpawnMinSize.IntValue || amount > g_hCvarMobSpawnMaxSize.IntValue) { + return Plugin_Continue; + } + + if (!L4D2_CTimerIsElapsed(L4D2CT_MobSpawnTimer)) { + return Plugin_Continue; + } + + float duration = L4D2_CTimerGetCountdownDuration(L4D2CT_MobSpawnTimer); + if (duration < g_hCvarZMobSpawnMinIntervalNormal.FloatValue || duration > g_hCvarZMobSpawnMaxIntervalNormal.FloatValue) { + return Plugin_Continue; + } + + return Plugin_Handled; + } + } + + return Plugin_Continue; +} + +// Disable stasis when we're using GhostTank +Action GT_OnTryOfferingTankBot(bool &enterStasis) +{ + g_iPasses++; + + if (IsPluginEnabled()) { + if (g_hGT_Enabled.BoolValue) { + enterStasis = false; + } + + if (g_hGT_RemoveEscapeTank.BoolValue && g_bGT_FinaleVehicleIncoming) { + return Plugin_Handled; + } + } + + return Plugin_Continue; +} + +public void GT_FinaleVehicleIncoming(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + g_bGT_FinaleVehicleIncoming = true; + + if (g_bGT_TankIsInPlay && IsFakeClient(g_iGT_TankClient)) { + KickClient(g_iGT_TankClient); + GT_Reset(); + } +} + +public void GT_ItemPickup(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!g_bGT_TankIsInPlay) { + return; + } + + char item[MAX_ENTITY_NAME_LENGTH]; + hEvent.GetString("item", item, sizeof(item)); + + if (strcmp(item, "tank_claw") != 0) { + return; + } + + g_iGT_TankClient = GetClientOfUserId(hEvent.GetInt("userid")); + + if (g_hGT_TankDeathTimer != null) { + KillTimer(g_hGT_TankDeathTimer); + g_hGT_TankDeathTimer = null; + } +} + +public void GT_RoundStart(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + g_bGT_FinaleVehicleIncoming = false; + GT_Reset(); +} + +public void GT_TankKilled(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!g_bGT_TankIsInPlay) { + return; + } + + int client = GetClientOfUserId(hEvent.GetInt("userid")); + if (client != g_iGT_TankClient) { + return; + } + + g_hGT_TankDeathTimer = CreateTimer(1.0, GT_TankKilled_Timer); +} + +public void GT_TankSpawn(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + int client = GetClientOfUserId(hEvent.GetInt("userid")); + g_iGT_TankClient = client; + + if (g_bGT_TankIsInPlay) { + return; + } + + g_bGT_TankIsInPlay = true; + + if (g_hGT_DisableTankHordes.BoolValue) { + g_bGT_HordesDisabled = true; + } + + if (!IsPluginEnabled() || !g_hGT_Enabled.BoolValue) { + return; + } + + float fFireImmunityTime = FIREIMMUNITY_TIME; + float fSelectionTime = g_hCvarDirectorTankLotterySelectionTime.FloatValue; + + if (IsFakeClient(client)) { + GT_PauseTank(); + CreateTimer(fSelectionTime, GT_ResumeTankTimer); + fFireImmunityTime += fSelectionTime; + } + + CreateTimer(fFireImmunityTime, GT_FireImmunityTimer); +} + +public void GT_TankOnFire(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + int dmgtype = hEvent.GetInt("type"); + + if (!(dmgtype & DMG_BURN)) { //more performance + return; + } + + if (!g_bGT_TankIsInPlay || !g_bGT_TankHasFireImmunity || !IsPluginEnabled() || !g_hGT_Enabled.BoolValue) { + return; + } + + int client = GetClientOfUserId(hEvent.GetInt("userid")); + if (client < 1 || g_iGT_TankClient != client || !IsClientInGame(client) || GetClientTeam(client) != L4D2Team_Infected) { + return; + } + + ExtinguishEntity(client); + + int iSetHealth = GetClientHealth(client) + hEvent.GetInt("dmg_health"); + SetEntityHealth(client, iSetHealth); +} + +public void GT_PlayerIncap(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!g_bGT_TankIsInPlay || !IsPluginEnabled() || !g_hGT_Enabled.BoolValue) { + return; + } + + char weapon[MAX_ENTITY_NAME_LENGTH]; + hEvent.GetString("weapon", weapon, sizeof(weapon)); + + if (strcmp(weapon, "tank_claw") != 0) { + return; + } + + int userid = hEvent.GetInt("userid"); + int client = GetClientOfUserId(userid); + if (client < 1 || !IsClientInGame(client) || GetClientTeam(client) != L4D2Team_Survivor) { + return; + } + + SetEntProp(client, Prop_Send, "m_isIncapacitated", 0, 1); + SetEntityHealth(client, 1); + + CreateTimer(0.4, GT_IncapTimer, userid, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action GT_IncapTimer(Handle hTimer, int userid) +{ + int client = GetClientOfUserId(userid); + if (client > 0) { + SetEntProp(client, Prop_Send, "m_isIncapacitated", 1, 1); + SetEntityHealth(client, g_hCvarSurvivorIncapHealth.IntValue); + } + + return Plugin_Stop; +} + +public Action GT_ResumeTankTimer(Handle hTimer) +{ + GT_ResumeTank(); + + return Plugin_Stop; +} + +public Action GT_FireImmunityTimer(Handle hTimer) +{ + g_bGT_TankHasFireImmunity = false; + + return Plugin_Stop; +} + +static void GT_PauseTank() +{ + g_hCvarTankThrowAllowRange.SetFloat(THROWRANGE); + + if (!IsValidEntity(g_iGT_TankClient)) { + return; + } + + SetEntityMoveType(g_iGT_TankClient, MOVETYPE_NONE); + SetEntProp(g_iGT_TankClient, Prop_Send, "m_isGhost", 1, 1); +} + +static void GT_ResumeTank() +{ + g_hCvarTankThrowAllowRange.RestoreDefault(); + + if (!IsValidEntity(g_iGT_TankClient)) { + return; + } + + SetEntityMoveType(g_iGT_TankClient, MOVETYPE_CUSTOM); + SetEntProp(g_iGT_TankClient, Prop_Send, "m_isGhost", 0, 1); +} + +static void GT_Reset() +{ + g_iPasses = 0; + g_hGT_TankDeathTimer = null; + + if (g_bGT_HordesDisabled) { + g_bGT_HordesDisabled = false; + } + + g_bGT_TankIsInPlay = false; + g_bGT_TankHasFireImmunity = true; +} + +public Action GT_TankKilled_Timer(Handle hTimer) +{ + GT_Reset(); + + return Plugin_Stop; +} + +// For other modules to use +stock bool GT_IsTankInPlay() +{ + return (g_bGT_TankIsInPlay); +} diff --git a/addons/sourcemod/scripting/confoglcompmod/GhostWarp.sp b/addons/sourcemod/scripting/confoglcompmod/GhostWarp.sp new file mode 100644 index 000000000..076e7a23f --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/GhostWarp.sp @@ -0,0 +1,176 @@ +#if defined __ghost_warp_included + #endinput +#endif +#define __ghost_warp_included + +#define GW_MODULE_NAME "GhostWarp" + +static int + GW_iLastTarget[MAXPLAYERS + 1] = {-1, ...}; + +static bool + GW_bEnabled = true, + GW_bReload = false, + GW_bDelay[MAXPLAYERS + 1] = {false, ...}; + +static ConVar + GW_hGhostWarp = null, + GW_hGhostWarpReload = null; + +void GW_OnModuleStart() +{ + // GhostWarp + GW_hGhostWarp = CreateConVarEx("ghost_warp", "1", "Sets whether infected ghosts can right click for warp to next survivor", _, true, 0.0, true, 1.0); + GW_hGhostWarpReload = CreateConVarEx("ghost_warp_reload", "0", "Sets whether to use mouse2 or reload for ghost warp.", _, true, 0.0, true, 1.0); + + // Ghost Warp + GW_bEnabled = GW_hGhostWarp.BoolValue; + GW_bReload = GW_hGhostWarpReload.BoolValue; + + GW_hGhostWarp.AddChangeHook(GW_ConVarsChanged); + GW_hGhostWarpReload.AddChangeHook(GW_ConVarsChanged); + + RegConsoleCmd("sm_warptosurvivor", GW_Cmd_WarpToSurvivor); + + HookEvent("player_death", GW_PlayerDeath_Event); + HookEvent("round_start", GW_RoundStart); +} + +public void GW_ConVarsChanged(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + GW_bEnabled = GW_hGhostWarp.BoolValue; + GW_bReload = GW_hGhostWarpReload.BoolValue; +} + +bool GW_OnPlayerRunCmd(int iClient, int iButtons) +{ + if (!IsPluginEnabled() || !GW_bEnabled || GW_bDelay[iClient]) { + return false; + } + + if (/*!IsClientInGame(iClient) || */GetClientTeam(iClient) != L4D2Team_Infected || GetEntProp(iClient, Prop_Send, "m_isGhost", 1) != 1) { + return false; + } + + if (GW_bReload && !(iButtons & IN_RELOAD)) { + return false; + } + + if (!GW_bReload && !(iButtons & IN_ATTACK2)) { + return false; + } + + GW_bDelay[iClient] = true; + CreateTimer(0.25, GW_ResetDelay, iClient, TIMER_FLAG_NO_MAPCHANGE); + GW_WarpToSurvivor(iClient, 0); + + return true; +} + +public void GW_RoundStart(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + for (int i = 1; i <= MaxClients; i++) { + GW_bDelay[i] = false; + GW_iLastTarget[i] = -1; + } +} + +public void GW_PlayerDeath_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + int iClient = GetClientOfUserId(hEvent.GetInt("userid")); + + GW_iLastTarget[iClient] = -1; +} + +public Action GW_ResetDelay(Handle hTimer, any iClient) +{ + GW_bDelay[iClient] = false; + + return Plugin_Stop; +} + +public Action GW_Cmd_WarpToSurvivor(int iClient, int iArgs) +{ + if (iClient < 1 || iArgs != 1) { + return Plugin_Handled; + } + + if (!IsPluginEnabled() || !GW_bEnabled) { + return Plugin_Handled; + } + + if (GetClientTeam(iClient) != L4D2Team_Infected || GetEntProp(iClient, Prop_Send, "m_isGhost", 1) != 1) { + return Plugin_Handled; + } + + char sBuffer[2]; + GetCmdArg(1, sBuffer, sizeof(sBuffer)); + if (strlen(sBuffer) == 0) { + return Plugin_Handled; + } + + int iCharacter = StringToInt(sBuffer); + GW_WarpToSurvivor(iClient, iCharacter); + + return Plugin_Handled; +} + +static void GW_WarpToSurvivor(int iClient, int iCharacter) +{ + int iTarget = 0; + + if (iCharacter <= 0) { + iTarget = GW_FindNextSurvivor(iClient, GW_iLastTarget[iClient]); + } else if (iCharacter <= 4) { + iTarget = GetSurvivorIndex(iCharacter - 1); + } else { + return; + } + + if (iTarget == 0) { + return; + } + + // Prevent people from spawning and then warp to survivor + SetEntProp(iClient, Prop_Send, "m_ghostSpawnState", SPAWNFLAG_TOOCLOSE); + + float fPosition[3], fAnglestarget[3]; + GetClientAbsOrigin(iTarget, fPosition); + GetClientAbsAngles(iTarget, fAnglestarget); + + TeleportEntity(iClient, fPosition, fAnglestarget, NULL_VECTOR); +} + +static int GW_FindNextSurvivor(int iClient, int iCharacter) +{ + if (!IsAnySurvivorsAlive()) { + return 0; + } + + bool bHavelooped = false; + iCharacter++; + + if (iCharacter >= NUM_OF_SURVIVORS) { + iCharacter = 0; + } + + for (int i = iCharacter; i <= MaxClients; i++) { + if (i >= NUM_OF_SURVIVORS) { + if (bHavelooped) { + break; + } + + bHavelooped = true; + i = 0; + } + + if (GetSurvivorIndex(i) == 0) { + continue; + } + + GW_iLastTarget[iClient] = i; + return GetSurvivorIndex(i); + } + + return 0; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/ItemTracking.sp b/addons/sourcemod/scripting/confoglcompmod/ItemTracking.sp new file mode 100644 index 000000000..c784b2c50 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/ItemTracking.sp @@ -0,0 +1,612 @@ +#if defined __item_tracking_included + #endinput +#endif +#define __item_tracking_included + +#define IT_MODULE_NAME "ItemTracking" + +// Item lists for tracking/decoding/etc +enum /*ItemList*/ +{ + IL_PainPills, + IL_Adrenaline, + // Not sure we need these. + //IL_FirstAid, + //IL_Defib, + IL_PipeBomb, + IL_Molotov, + IL_VomitJar, + + ItemList_Size +}; + +// Names for cvars, kv, descriptions +// [ItemIndex][shortname = 0, fullname = 1, spawnname = 2] +enum /*ItemNames*/ +{ + IN_shortname, + IN_longname, + IN_officialname, + IN_modelname, + + ItemNames_Size +}; + +// Settings for item limiting. +/*enum ItemLimitSettings +{ + Handle:cvar, + limitnum +};*/ + +// For spawn entires adt_array +#if SOURCEMOD_V_MINOR > 9 +enum struct ItemTracking +{ + int IT_entity; + float IT_origins; + float IT_origins1; + float IT_origins2; + float IT_angles; + float IT_angles1; + float IT_angles2; +} +#else +enum ItemTracking +{ + IT_entity, + Float:IT_origins, + Float:IT_origins1, + Float:IT_origins2, + Float:IT_angles, + Float:IT_angles1, + Float:IT_angles2 +}; +#endif + +static const char g_sItemNames[ItemList_Size][ItemNames_Size][] = +{ + { + "pills", + "pain pills", + "pain_pills", + "painpills" + }, + { + "adrenaline", + "adrenaline shots", + "adrenaline", + "pipebomb" + }, + /*{ + "kits", + "first aid kits", + "first_aid_kit", + "medkit" + }, + { + "defib", + "defibrillators", + "defibrillator", + "defibrillator" + },*/ + { + "pipebomb", + "pipe bombs", + "pipe_bomb", + "pipebomb" + }, + { + "molotov", + "molotovs", + "molotov", + "molotov" + }, + { + "vomitjar", + "bile bombs", + "vomitjar", + "bile_flask" + } +}; + +static int + g_iItemLimits[ItemList_Size] = {0, ...}, // Current item limits array + g_iSaferoomCount[2] = {0, ...}; + +static bool + g_bIsRound1Over = false; // Is round 1 over? + +static ConVar + g_hCvarEnabled = null, + g_hSurvivorLimit = null, + g_hCvarConsistentSpawns = null, + g_hCvarMapSpecificSpawns = null, + g_hCvarLimits[ItemList_Size] = {null, ...}; // CVAR Handle Array for item limits + +static ArrayList + g_hItemSpawns[ItemList_Size] = {null, ...}; // ADT Array Handle for actual item spawns + +static StringMap + g_hItemListTrie = null; + +void IT_OnModuleStart() +{ + g_hCvarEnabled = CreateConVarEx("enable_itemtracking", "0", "Enable the itemtracking module", _, true, 0.0, true, 1.0); + g_hCvarConsistentSpawns = CreateConVarEx("itemtracking_savespawns", "0", "Keep item spawns the same on both rounds", _, true, 0.0, true, 1.0); + g_hCvarMapSpecificSpawns = CreateConVarEx("itemtracking_mapspecific", "0", "Change how mapinfo.txt overrides work. 0 = ignore mapinfo.txt, 1 = allow limit reduction, 2 = allow limit increases.", _, true, 0.0, true, 3.0); + + char sNameBuf[64], sCvarDescBuf[256]; + // Create itemlimit cvars + for (int i = 0; i < ItemList_Size; i++) { + Format(sNameBuf, sizeof(sNameBuf), "%s_limit", g_sItemNames[i][IN_shortname]); + Format(sCvarDescBuf, sizeof(sCvarDescBuf), "Limits the number of %s on each map. -1: no limit; >=0: limit to cvar value", g_sItemNames[i][IN_longname]); + + g_hCvarLimits[i] = CreateConVarEx(sNameBuf, "-1", sCvarDescBuf); + } + + // Create name translation trie + CreateItemListTrie(); + + // Create item spawns array; +#if SOURCEMOD_V_MINOR > 9 + ItemTracking curitem; +#else + ItemTracking curitem[ItemTracking]; +#endif + + for (int i = 0; i < ItemList_Size; i++) { + g_hItemSpawns[i] = new ArrayList(sizeof(curitem)); + } + + HookEvent("round_start", _IT_RoundStartEvent, EventHookMode_PostNoCopy); + HookEvent("round_end", _IT_RoundEndEvent, EventHookMode_PostNoCopy); + + g_hSurvivorLimit = FindConVar("survivor_limit"); +} + +void IT_OnMapStart() +{ + for (int i = 0; i < ItemList_Size; i++) { + g_iItemLimits[i] = g_hCvarLimits[i].IntValue; + } + + int iCvarValue = g_hCvarMapSpecificSpawns.IntValue; + if (iCvarValue) { + int itemlimit = 0, temp = 0; + KeyValues kOverrideLimits = new KeyValues("ItemLimits"); + CopyMapSubsection(kOverrideLimits, "ItemLimits"); + + for (int i = 0; i < ItemList_Size; i++) { + itemlimit = g_hCvarLimits[i].IntValue; + + temp = kOverrideLimits.GetNum(g_sItemNames[i][IN_officialname], itemlimit); + + if (((g_iItemLimits[i] > temp) && (iCvarValue & 1)) || ((g_iItemLimits[i] < temp) && (iCvarValue & 2))) { + g_iItemLimits[i] = temp; + } + + g_hItemSpawns[i].Clear(); + } + + delete kOverrideLimits; + } + + g_bIsRound1Over = false; +} + +public void _IT_RoundEndEvent(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + g_bIsRound1Over = true; +} + +public void _IT_RoundStartEvent(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + g_iSaferoomCount[START_SAFEROOM - 1] = 0; + g_iSaferoomCount[END_SAFEROOM - 1] = 0; + + // Mapstart happens after round_start most of the time, so we need to wait for g_bIsRound1Over. + // Plus, we don't want to have conflicts with EntityRemover. + CreateTimer(1.0, IT_RoundStartTimer, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action IT_RoundStartTimer(Handle hTimer) +{ + if (!g_bIsRound1Over) { + // Round1 + if (IsModuleEnabled()) { + EnumAndElimSpawns(); + } + } else { + // Round2 + if (IsModuleEnabled()) { + if (g_hCvarConsistentSpawns.BoolValue) { + GenerateStoredSpawns(); + } else { + EnumAndElimSpawns(); + } + } + } + + return Plugin_Stop; +} + +static void EnumAndElimSpawns() +{ + if (IsDebugEnabled()) { + LogMessage("[%s] Resetting g_iSaferoomCount and Enumerating and eliminating spawns...", IT_MODULE_NAME); + } + + EnumerateSpawns(); + RemoveToLimits(); +} + +static void GenerateStoredSpawns() +{ + KillRegisteredItems(); + SpawnItems(); +} + +// l4d2lib plugin +// For 3.0 rounds library +/*public void L4D2_OnRealRoundStart(int roundNum) +{ + if (roundNum == 1) { + EnumerateSpawns(); + RemoveToLimits(); + } else { + // We kill off all items we recognize. + // Unlimited items will be replaced, limited items will be spawned, + // and killed items will stay killed + KillRegisteredItems(); + // Spawn up the same items that existed in round 1 + SpawnItems(); + } +}*/ + +// Produces the lookup trie for weapon spawn entities +// to translate to our ADT array of spawns +static void CreateItemListTrie() +{ + g_hItemListTrie = new StringMap(); + g_hItemListTrie.SetValue("weapon_pain_pills_spawn", IL_PainPills); + g_hItemListTrie.SetValue("weapon_pain_pills", IL_PainPills); + g_hItemListTrie.SetValue("weapon_adrenaline_spawn", IL_Adrenaline); + g_hItemListTrie.SetValue("weapon_adrenaline", IL_Adrenaline); + g_hItemListTrie.SetValue("weapon_pipe_bomb_spawn", IL_PipeBomb); + g_hItemListTrie.SetValue("weapon_pipe_bomb", IL_PipeBomb); + g_hItemListTrie.SetValue("weapon_molotov_spawn", IL_Molotov); + g_hItemListTrie.SetValue("weapon_molotov", IL_Molotov); + g_hItemListTrie.SetValue("weapon_vomitjar_spawn", IL_VomitJar); + g_hItemListTrie.SetValue("weapon_vomitjar", IL_VomitJar); +} + +static void KillRegisteredItems() +{ + int itemindex = 0, psychonic = GetEntityCount(); + int iSurvivorLimit = g_hSurvivorLimit.IntValue; + + for (int i = (MaxClients + 1); i <= psychonic; i++) { + if (!IsValidEdict(i)) { + continue; + } + + itemindex = GetItemIndexFromEntity(i); + if (itemindex >= 0/* && !IsEntityInSaferoom(i)*/) { + if (IsEntityInSaferoom(i, START_SAFEROOM) && g_iSaferoomCount[START_SAFEROOM - 1] < iSurvivorLimit) { + g_iSaferoomCount[START_SAFEROOM - 1]++; + } else if (IsEntityInSaferoom(i, END_SAFEROOM) && g_iSaferoomCount[END_SAFEROOM - 1] < iSurvivorLimit) { + g_iSaferoomCount[END_SAFEROOM - 1]++; + } else { + // Kill items we're tracking; + KillEntity(i); + /*if (!AcceptEntityInput(i, "kill")) { + Debug_LogError(IT_MODULE_NAME, "Error killing instance of item %s", g_sItemNames[itemindex][IN_longname]); + }*/ + } + } + } +} + +static void SpawnItems() +{ +#if SOURCEMOD_V_MINOR > 9 + ItemTracking curitem; +#else + ItemTracking curitem[ItemTracking]; +#endif + + float origins[3], angles[3]; + int arrsize = 0, itement = 0, wepid = 0; + char sModelname[PLATFORM_MAX_PATH]; + + for (int itemidx = 0; itemidx < ItemList_Size; itemidx++) { + Format(sModelname, sizeof(sModelname), "models/w_models/weapons/w_eq_%s.mdl", g_sItemNames[itemidx][IN_modelname]); + + arrsize = g_hItemSpawns[itemidx].Length; + + for (int idx = 0; idx < arrsize; idx++) { + #if SOURCEMOD_V_MINOR > 9 + g_hItemSpawns[itemidx].GetArray(idx, curitem, sizeof(curitem)); + #else + g_hItemSpawns[itemidx].GetArray(idx, curitem[0], sizeof(curitem)); + #endif + + GetSpawnOrigins(origins, curitem); + GetSpawnAngles(angles, curitem); + wepid = GetWeaponIDFromItemList(itemidx); + + if (IsDebugEnabled()) { + LogMessage("[%s] Spawning an instance of item %s (%d, wepid %d), number %d, at %.02f %.02f %.02f", \ + IT_MODULE_NAME, g_sItemNames[itemidx][IN_officialname], itemidx, wepid, idx, origins[0], origins[1], origins[2]); + } + + itement = CreateEntityByName("weapon_spawn"); + if (itement == -1) { + continue; + } + + SetEntProp(itement, Prop_Send, "m_weaponID", wepid); + SetEntityModel(itement, sModelname); + DispatchKeyValue(itement, "count", "1"); + TeleportEntity(itement, origins, angles, NULL_VECTOR); + DispatchSpawn(itement); + SetEntityMoveType(itement, MOVETYPE_NONE); + } + } +} + +static void EnumerateSpawns() +{ +#if SOURCEMOD_V_MINOR > 9 + ItemTracking curitem; +#else + ItemTracking curitem[ItemTracking]; +#endif + + float origins[3], angles[3]; + int itemindex = 0, psychonic = GetEntityCount(); + int iSurvivorLimit = g_hSurvivorLimit.IntValue; + + for (int i = (MaxClients + 1); i <= psychonic; i++) { + if (!IsValidEdict(i)) { + continue; + } + + itemindex = GetItemIndexFromEntity(i); + if (itemindex >= 0/* && !IsEntityInSaferoom(i)*/) { + if (IsEntityInSaferoom(i, START_SAFEROOM)) { + if (g_iSaferoomCount[START_SAFEROOM - 1] < iSurvivorLimit) { + g_iSaferoomCount[START_SAFEROOM - 1]++; + } else { + KillEntity(i); + /*if (!AcceptEntityInput(i, "kill")) { + Debug_LogError(IT_MODULE_NAME, "Error killing instance of item %s", g_sItemNames[itemindex][IN_longname]); + }*/ + } + } else if (IsEntityInSaferoom(i, END_SAFEROOM)) { + if (g_iSaferoomCount[END_SAFEROOM - 1] < iSurvivorLimit) { + g_iSaferoomCount[END_SAFEROOM - 1]++; + } else { + KillEntity(i); + /*if (!AcceptEntityInput(i, "kill")) { + Debug_LogError(IT_MODULE_NAME, "Error killing instance of item %s", g_sItemNames[itemindex][IN_longname]); + }*/ + } + } else { + int mylimit = g_iItemLimits[itemindex]; + if (IsDebugEnabled()) { + LogMessage("[%s] Found an instance of item %s (%d), with limit %d", IT_MODULE_NAME, g_sItemNames[itemindex][IN_longname], itemindex, mylimit); + } + + // Item limit is zero, justkill it as we find it + if (!mylimit) { + if (IsDebugEnabled()) { + LogMessage("[%s] Killing spawn", IT_MODULE_NAME); + } + + KillEntity(i); + /*if (!AcceptEntityInput(i, "kill")) { + Debug_LogError(IT_MODULE_NAME, "Error killing instance of item %s", g_sItemNames[itemindex][IN_longname]); + }*/ + } else { + // Store entity, angles, origin + #if SOURCEMOD_V_MINOR > 9 + curitem.IT_entity = i; + #else + curitem[IT_entity] = i; + #endif + + GetEntPropVector(i, Prop_Send, "m_vecOrigin", origins); + GetEntPropVector(i, Prop_Send, "m_angRotation", angles); + + if (IsDebugEnabled()) { + LogMessage("[%s] Saving spawn #%d at %.02f %.02f %.02f", IT_MODULE_NAME, g_hItemSpawns[itemindex].Length, origins[0], origins[1], origins[2]); + } + + SetSpawnOrigins(origins, curitem); + SetSpawnAngles(angles, curitem); + + // Push this instance onto our array for that item + #if SOURCEMOD_V_MINOR > 9 + g_hItemSpawns[itemindex].PushArray(curitem, sizeof(curitem)); + #else + g_hItemSpawns[itemindex].PushArray(curitem[0], sizeof(curitem)); + #endif + } + } + } + } +} + +static void RemoveToLimits() +{ +#if SOURCEMOD_V_MINOR > 9 + ItemTracking curitem; +#else + ItemTracking curitem[ItemTracking]; +#endif + + int curlimit = 0, killidx = 0; + + for (int itemidx = 0; itemidx < ItemList_Size; itemidx++) { + curlimit = g_iItemLimits[itemidx]; + + if (curlimit > 0) { + // Kill off item spawns until we've reduced the item to the limit + while (g_hItemSpawns[itemidx].Length > curlimit) { + // Pick a random + killidx = GetURandomIntRange(0, (g_hItemSpawns[itemidx].Length - 1)); + + if (IsDebugEnabled()) { + LogMessage("[%s] Killing randomly chosen %s (%d) #%d", IT_MODULE_NAME, g_sItemNames[itemidx][IN_longname], itemidx, killidx); + } + + #if SOURCEMOD_V_MINOR > 9 + g_hItemSpawns[itemidx].GetArray(killidx, curitem, sizeof(curitem)); + + if (IsValidEdict(curitem.IT_entity)) { + KillEntity(curitem.IT_entity); + + /*if (!AcceptEntityInput(curitem.IT_entity, "kill")) { + Debug_LogError(IT_MODULE_NAME, "Error killing instance of item %s", g_sItemNames[itemidx][IN_longname]); + }*/ + } + #else + g_hItemSpawns[itemidx].GetArray(killidx, curitem[0], sizeof(curitem)); + + if (IsValidEdict(curitem[IT_entity])) { + KillEntity(curitem[IT_entity]); + + /*if (!AcceptEntityInput(curitem[IT_entity], "kill")) { + Debug_LogError(IT_MODULE_NAME, "Error killing instance of item %s", g_sItemNames[itemidx][IN_longname]); + }*/ + } + #endif + + g_hItemSpawns[itemidx].Erase(killidx); + } + } + // If limit is 0, they're already dead. If it's negative, we kill nothing. + } +} + +#if SOURCEMOD_V_MINOR > 9 +static void SetSpawnOrigins(const float buf[3], ItemTracking spawn) +{ + spawn.IT_origins = buf[0]; + spawn.IT_origins1 = buf[1]; + spawn.IT_origins2 = buf[2]; +} + +static void SetSpawnAngles(const float buf[3], ItemTracking spawn) +{ + spawn.IT_angles = buf[0]; + spawn.IT_angles1 = buf[1]; + spawn.IT_angles2 = buf[2]; +} + +static void GetSpawnOrigins(float buf[3], const ItemTracking spawn) +{ + buf[0] = spawn.IT_origins; + buf[1] = spawn.IT_origins1; + buf[2] = spawn.IT_origins2; +} + +static void GetSpawnAngles(float buf[3], const ItemTracking spawn) +{ + buf[0] = spawn.IT_angles; + buf[1] = spawn.IT_angles1; + buf[2] = spawn.IT_angles2; +} +#else +static void SetSpawnOrigins(const float buf[3], ItemTracking spawn[ItemTracking]) +{ + spawn[IT_origins] = buf[0]; + spawn[IT_origins1] = buf[1]; + spawn[IT_origins2] = buf[2]; +} + +static void SetSpawnAngles(const float buf[3], ItemTracking spawn[ItemTracking]) +{ + spawn[IT_angles] = buf[0]; + spawn[IT_angles1] = buf[1]; + spawn[IT_angles2] = buf[2]; +} + +static void GetSpawnOrigins(float buf[3], const ItemTracking spawn[ItemTracking]) +{ + buf[0] = spawn[IT_origins]; + buf[1] = spawn[IT_origins1]; + buf[2] = spawn[IT_origins2]; +} + +static void GetSpawnAngles(float buf[3], const ItemTracking spawn[ItemTracking]) +{ + buf[0] = spawn[IT_angles]; + buf[1] = spawn[IT_angles1]; + buf[2] = spawn[IT_angles2]; +} +#endif + +static int GetWeaponIDFromItemList(int id) +{ + switch (id) { + case IL_PainPills: { + return WEPID_PAIN_PILLS; + } + case IL_Adrenaline: { + return WEPID_ADRENALINE; + } + case IL_PipeBomb: { + return WEPID_PIPE_BOMB; + } + case IL_Molotov: { + return WEPID_MOLOTOV; + } + case IL_VomitJar: { + return WEPID_VOMITJAR; + } + } + + return -1; +} + +static int GetItemIndexFromEntity(int entity) +{ + char classname[MAX_ENTITY_NAME_LENGTH]; + int index; + + GetEdictClassname(entity, classname, sizeof(classname)); + if (g_hItemListTrie.GetValue(classname, index)) { + return index; + } + + if (strcmp(classname, "weapon_spawn") == 0 || strcmp(classname, "weapon_item_spawn") == 0) { + int id = GetEntProp(entity, Prop_Send, "m_weaponID"); + switch (id) { + case WEPID_VOMITJAR: { + return IL_VomitJar; + } + case WEPID_PIPE_BOMB: { + return IL_PipeBomb; + } + case WEPID_MOLOTOV: { + return IL_Molotov; + } + case WEPID_PAIN_PILLS: { + return IL_PainPills; + } + case WEPID_ADRENALINE: { + return IL_Adrenaline; + } + } + } + + return -1; +} + +static bool IsModuleEnabled() +{ + return (IsPluginEnabled() && g_hCvarEnabled.BoolValue); +} diff --git a/addons/sourcemod/scripting/confoglcompmod/MapInfo.sp b/addons/sourcemod/scripting/confoglcompmod/MapInfo.sp new file mode 100644 index 000000000..b96f828c3 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/MapInfo.sp @@ -0,0 +1,511 @@ +#if defined __map_info_included + #endinput +#endif +#define __map_info_included + +#define DEBUG_MI 0 +#define MI_MODULE_NAME "MapInfo" + +static int + iMapMaxDistance = 0, + iIsInEditMode[MAXPLAYERS + 1] = {0, ...}; + +static bool + MapDataAvailable = false; + +static float + Start_Point[3] = {0.0, ...}, + End_Point[3] = {0.0, ...}, + Start_Dist = 0.0, + Start_Extra_Dist = 0.0, + End_Dist = 0.0, + fLocTemp[MAXPLAYERS + 1][3]; + +static KeyValues + kMIData = null; + +void MI_APL() +{ + CreateNative("LGO_IsMapDataAvailable", _native_IsMapDataAvailable); + CreateNative("LGO_GetMapValueInt", _native_GetMapValueInt); + CreateNative("LGO_GetMapValueFloat", _native_GetMapValueFloat); + CreateNative("LGO_GetMapValueVector", _native_GetMapValueVector); + CreateNative("LGO_GetMapValueString", _native_GetMapValueString); + CreateNative("LGO_CopyMapSubsection", _native_CopyMapSubsection); +} + +void MI_OnModuleStart() +{ + MI_KV_Load(); + + //RegAdminCmd("confogl_midata_reload", MI_KV_CmdReload, ADMFLAG_CONFIG); + RegAdminCmd("confogl_midata_save", MI_KV_CmdSave, ADMFLAG_CONFIG); + RegAdminCmd("confogl_save_location", MI_KV_CmdSaveLoc, ADMFLAG_CONFIG); + + HookEvent("player_disconnect", PlayerDisconnect_Event); +} + +void MI_OnMapStart() +{ + MI_KV_UpdateMapInfo(); +} + +void MI_OnMapEnd() +{ + kMIData.Rewind(); + + MapDataAvailable = false; + + // 0 - server index? + for (int i = 0; i <= MaxClients; i++) { + iIsInEditMode[i] = 0; + } +} + +void MI_OnModuleEnd() +{ + MI_KV_Close(); +} + +public void PlayerDisconnect_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + int client = GetClientOfUserId(hEvent.GetInt("userid")); + if (client > 0 && client <= MaxClients) { + iIsInEditMode[client] = 0; + } +} + +public Action MI_KV_CmdSave(int client, int args) +{ + char sCurMap[128]; + GetCurrentMap(sCurMap, sizeof(sCurMap)); + + if (kMIData.JumpToKey(sCurMap, true)) { + kMIData.SetVector("start_point", Start_Point); + kMIData.SetFloat("start_dist", Start_Dist); + kMIData.SetFloat("start_extra_dist", Start_Extra_Dist); + + char sNameBuff[PLATFORM_MAX_PATH]; + BuildConfigPath(sNameBuff, sizeof(sNameBuff), "mapinfo.txt"); + + kMIData.Rewind(); + kMIData.ExportToFile(sNameBuff); + + ReplyToCommand(client, "%s has been added to %s.", sCurMap, sNameBuff); + } + + return Plugin_Handled; +} + +public Action MI_KV_CmdSaveLoc(int client, int args) +{ + bool updateinfo = false; + char sCurMap[128]; + GetCurrentMap(sCurMap, sizeof(sCurMap)); + + if (!iIsInEditMode[client]) { + if (!args) { + ReplyToCommand(client, "Move to the location of the medkits, then enter the point type (start_point or end_point)"); + return Plugin_Handled; + } + + char sBuffer[16]; + GetCmdArg(1, sBuffer, sizeof(sBuffer)); + + if (strcmp(sBuffer, "start_point", true) == 0) { + iIsInEditMode[client] = 1; + ReplyToCommand(client, "Move a few feet from the medkits and enter this command again to set the start_dist for this point"); + } else if (strcmp(sBuffer, "end_point", true) == 0) { + iIsInEditMode[client] = 2; + ReplyToCommand(client, "Move to the farthest point in the saferoom and enter this command again to set the end_dist for this point"); + } else { + ReplyToCommand(client, "Please enter the location type: start_point, end_point"); + return Plugin_Handled; + } + + if (kMIData.JumpToKey(sCurMap, true)) { + GetClientAbsOrigin(client, fLocTemp[client]); + kMIData.SetVector(sBuffer, fLocTemp[client]); + } + + updateinfo = true; + } else if (iIsInEditMode[client] == 1) { + iIsInEditMode[client] = 3; + + float fDistLoc[3], fDistance; + GetClientAbsOrigin(client, fDistLoc); + + fDistance = GetVectorDistance(fDistLoc, fLocTemp[client]); + if (kMIData.JumpToKey(sCurMap, true)) { + kMIData.SetFloat("start_dist", fDistance); + } + + ReplyToCommand(client, "Move to the farthest point in the saferoom and enter this command again to set start_extra_dist for this point"); + + updateinfo = true; + } else if (iIsInEditMode[client] == 2) { + iIsInEditMode[client] = 0; + + float fDistLoc[3], fDistance; + GetClientAbsOrigin(client, fDistLoc); + + fDistance = GetVectorDistance(fDistLoc, fLocTemp[client]); + if (kMIData.JumpToKey(sCurMap, true)) { + kMIData.SetFloat("end_dist", fDistance); + } + + updateinfo = true; + } else if (iIsInEditMode[client] == 3) { + iIsInEditMode[client] = 0; + + float fDistLoc[3], fDistance; + GetClientAbsOrigin(client, fDistLoc); + + fDistance = GetVectorDistance(fDistLoc, fLocTemp[client]); + if (kMIData.JumpToKey(sCurMap, true)) { + kMIData.SetFloat("start_extra_dist", fDistance); + } + + updateinfo = true; + } + + if (updateinfo) { + char sNameBuff[PLATFORM_MAX_PATH]; + BuildConfigPath(sNameBuff, sizeof(sNameBuff), "mapinfo.txt"); + + kMIData.Rewind(); + kMIData.ExportToFile(sNameBuff); + + ReplyToCommand(client, "mapinfo.txt has been updated!"); + } + + return Plugin_Handled; +} + +static void MI_KV_Close() +{ + if (kMIData != null) { + delete kMIData; + kMIData = null; + } +} + +static void MI_KV_Load() +{ + char sNameBuff[PLATFORM_MAX_PATH]; + + if (DEBUG_MI || IsDebugEnabled()) { + LogMessage("[%s] Loading MapInfo KeyValues", MI_MODULE_NAME); + } + + kMIData = new KeyValues("MapInfo"); + BuildConfigPath(sNameBuff, sizeof(sNameBuff), "mapinfo.txt"); //Build our filepath + if (!kMIData.ImportFromFile(sNameBuff)) { + Debug_LogError(MI_MODULE_NAME, "Couldn't load MapInfo data!"); + MI_KV_Close(); + return; + } +} + +static void MI_KV_UpdateMapInfo() +{ + char sCurMap[128]; + GetCurrentMap(sCurMap, sizeof(sCurMap)); + + if (kMIData.JumpToKey(sCurMap)) { + kMIData.GetVector("start_point", Start_Point); + kMIData.GetVector("end_point", End_Point); + + Start_Dist = kMIData.GetFloat("start_dist"); + Start_Extra_Dist = kMIData.GetFloat("start_extra_dist"); + End_Dist = kMIData.GetFloat("end_dist"); + + iMapMaxDistance = kMIData.GetNum("max_distance", -1); + + // kMIData.Rewind(); + MapDataAvailable = true; + } else { + MapDataAvailable = false; + Start_Dist = FindStartPointHeuristic(Start_Point); + if (Start_Dist > 0.0) { + // This is the largest Start Extra Dist we've encountered; + // May be too much + Start_Extra_Dist = 500.0; + } else { + ZeroVector(Start_Point); + Start_Dist = -1.0; + Start_Extra_Dist = -1.0; + } + + ZeroVector(End_Point); + End_Dist = -1.0; + iMapMaxDistance = -1; + LogMessage("[%s] MapInfo for %s is missing.", MI_MODULE_NAME, sCurMap); + } + + // Let's leave MIData on the current map + // kMIData.Rewind(); +} + +static float FindStartPointHeuristic(float result[3]) +{ + char entclass[MAX_ENTITY_NAME_LENGTH]; + float kitOrigin[4][3], averageOrigin[3]; + int kits = 0, entcount = GetEntityCount(); + + for (int iEntity = (MaxClients + 1); iEntity <= entcount && kits < 4; iEntity++) { + if (!IsValidEdict(iEntity)) { + continue; + } + + GetEdictClassname(iEntity, entclass, sizeof(entclass)); + if (strcmp(entclass, "weapon_first_aid_kit_spawn") == 0) { + GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", kitOrigin[kits]); + AddToVector(averageOrigin, kitOrigin[kits]); + kits++; + } + } + + if (kits < 4) { + return -1.0; + } + + ScaleVector(averageOrigin, 0.25); + + float greatestDist, tempDist; + for (int i = 0; i < 4; i++) { + tempDist = GetVectorDistance(averageOrigin, kitOrigin[i]); + + if (tempDist > greatestDist) { + greatestDist = tempDist; + } + } + + CopyVector(result, averageOrigin); + return (greatestDist + 1.0); +} + +// Old Functions (Avoid using these, use the ones below) +stock float GetMapStartOriginX() +{ + return Start_Point[0]; +} + +stock float GetMapStartOriginY() +{ + return Start_Point[1]; +} + +stock float GetMapStartOriginZ() +{ + return Start_Point[2]; +} + +stock float GetMapEndOriginX() +{ + return End_Point[0]; +} + +stock float GetMapEndOriginY() +{ + return End_Point[1]; +} + +stock float GetMapEndOriginZ() +{ + return End_Point[2]; +} + +// New Super Awesome Functions!!! +stock int GetCustomMapMaxScore() +{ + return iMapMaxDistance; +} + +stock bool IsMapDataAvailable() +{ + return MapDataAvailable; +} + +/** + * Determines if an entity is in a start or end saferoom (based on mapinfo.txt or automatically generated info) + * + * @param ent The entity to be checked + * @param saferoom START_SAFEROOM (1) = Start saferoom, END_SAFEROOM (2) = End saferoom (including finale area), 3 = both + * @return True if it is one of the specified saferoom(s) + * False if it is not in the specified saferoom(s) + * False if no saferoom specified + */ +stock bool IsEntityInSaferoom(int ent, int saferoom = 3) //ItemTracking (commented out) +{ + float origins[3]; + GetEntPropVector(ent, Prop_Send, "m_vecOrigin", origins); + + if ((saferoom & START_SAFEROOM) + && (GetVectorDistance(origins, Start_Point) <= ((Start_Extra_Dist > Start_Dist) ? Start_Extra_Dist : Start_Dist)) + ) { + return true; + } else if ((saferoom & END_SAFEROOM) && (GetVectorDistance(origins, End_Point) <= End_Dist)) { + return true; + } else { + return false; + } + +// return ((GetVectorDistance(origins, Start_Point) <= ((Start_Extra_Dist > Start_Dist) ? Start_Extra_Dist : Start_Dist)) +// || (GetVectorDistance(origins, End_Point) <= End_Dist)); +} + +stock int GetMapValueInt(const char[] key, int defvalue = 0) //BossSpawning +{ + return kMIData.GetNum(key, defvalue); +} + +stock float GetMapValueFloat(const char[] key, float defvalue = 0.0) //BossSpawning +{ + return kMIData.GetFloat(key, defvalue); +} + +stock void GetMapValueVector(const char[] key, float vector[3], float defvalue[3] = NULL_VECTOR) //BossSpawning +{ + kMIData.GetVector(key, vector, defvalue); +} + +stock void GetMapValueString(const char[] key, char[] value, const int maxlength, const char[] defvalue) +{ + kMIData.GetString(key, value, maxlength, defvalue); +} + +stock void CopyMapSubsection(KeyValues kv, const char[] section) //ItemTracking +{ + if (kMIData.JumpToKey(section, false)) { + kMIData.Import(kv); // KvCopySubkeys(kMIData, kv); + kMIData.GoBack(); + } +} + +stock void GetMapStartOrigin(float origin[3]) //not used +{ + origin[0] = Start_Point[0]; + origin[1] = Start_Point[1]; + origin[2] = Start_Point[2]; +} + +stock void GetMapEndOrigin(float origin[3]) //not used +{ + origin[0] = End_Point[0]; + origin[1] = End_Point[1]; + origin[2] = End_Point[2]; +} + +stock float GetMapEndDist() //WeaponInformation use it +{ + return End_Dist; +} + +stock float GetMapStartDist() //WeaponInformation use it +{ + return Start_Dist; +} + +stock float GetMapStartExtraDist() //WeaponInformation use it +{ + return Start_Extra_Dist; +} + +// Natives +public int _native_IsMapDataAvailable(Handle plugin, int numParams) +{ + return IsMapDataAvailable(); +} + +public int _native_GetMapValueInt(Handle plugin, int numParams) +{ + int iLen = 0; + GetNativeStringLength(1, iLen); + + int iNewLen = iLen + 1; + char[] sKey = new char[iNewLen]; + GetNativeString(1, sKey, iNewLen); + + int iDefVal = GetNativeCell(2); + return GetMapValueInt(sKey, iDefVal); +} + +#if SOURCEMOD_V_MINOR > 9 +public any _native_GetMapValueFloat(Handle plugin, int numParams) +#else +public int _native_GetMapValueFloat(Handle plugin, int numParams) +#endif +{ + int iLen = 0; + GetNativeStringLength(1, iLen); + + int iNewLen = iLen + 1; + char[] sKey = new char[iNewLen]; + GetNativeString(1, sKey, iNewLen); + + float iDefVal = GetNativeCell(2); + +#if SOURCEMOD_V_MINOR > 9 + return GetMapValueFloat(sKey, iDefVal); +#else + return view_as(GetMapValueFloat(sKey, iDefVal)); +#endif +} + +public int _native_GetMapValueVector(Handle plugin, int numParams) +{ + int iLen = 0; + GetNativeStringLength(1, iLen); + + int iNewLen = iLen + 1; + char[] sKey = new char[iNewLen]; + GetNativeString(1, sKey, iNewLen); + + float fDefVal[3], fValue[3]; + GetNativeArray(3, fDefVal, sizeof(fDefVal)); + GetMapValueVector(sKey, fValue, fDefVal); + + SetNativeArray(2, fValue, sizeof(fValue)); + return 1; +} + +public int _native_GetMapValueString(Handle plugin, int numParams) +{ + int iLen = 0; + GetNativeStringLength(1, iLen); + + int iNewLen = iLen + 1; + char[] sKey = new char[iNewLen]; + GetNativeString(1, sKey, iNewLen); + + GetNativeStringLength(4, iLen); + + iNewLen = iLen + 1; + char[] sDefVal = new char[iNewLen]; + GetNativeString(4, sDefVal, iNewLen); + + iLen = GetNativeCell(3); + + iNewLen = iLen + 1; + char[] sBuf = new char[iNewLen]; + GetMapValueString(sKey, sBuf, iNewLen, sDefVal); + + SetNativeString(2, sBuf, iNewLen); + return 1; +} + +public int _native_CopyMapSubsection(Handle plugin, int numParams) +{ + int iLen = 0; + GetNativeStringLength(2, iLen); + + int iNewLen = iLen + 1; + char[] sKey = new char[iNewLen]; + GetNativeString(2, sKey, iNewLen); + + KeyValues hKv = GetNativeCell(1); + CopyMapSubsection(hKv, sKey); + + return 1; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/PasswordSystem.sp b/addons/sourcemod/scripting/confoglcompmod/PasswordSystem.sp new file mode 100644 index 000000000..74df8450e --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/PasswordSystem.sp @@ -0,0 +1,161 @@ +#if defined __password_system_included + #endinput +#endif +#define __password_system_included + +#define PS_MODULE_NAME "PasswordSystem" + +static char + PS_sPassword[128] = "\0"; + +static bool + PS_bIsPassworded = false, + PS_bSuppress = false; + +static ConVar + PS_hReloaded = null, + PS_hPassword = null; + +void PS_OnModuleStart() +{ + PS_hPassword = CreateConVarEx( \ + "password", \ + "", \ + "Set a password on the server, if empty password disabled. See Confogl's wiki for more information", \ + FCVAR_DONTRECORD|FCVAR_PROTECTED \ + ); + + PS_hReloaded = FindConVarEx("password_reloaded"); + + if (PS_hReloaded == null) { + PS_hReloaded = CreateConVarEx( \ + "password_reloaded", \ + "", \ + "DONT TOUCH THIS CVAR! This will is to make sure that the password gets set upon the plugin is reloaded", \ + FCVAR_DONTRECORD|FCVAR_UNLOGGED \ + ); + } else { + char sBuffer[128]; + PS_hReloaded.GetString(sBuffer, sizeof(sBuffer)); + + PS_hPassword.SetString(sBuffer); + PS_hReloaded.SetString(""); + + PS_hPassword.GetString(PS_sPassword, sizeof(PS_sPassword)); + + PS_bIsPassworded = true; + PS_SetPasswordOnClients(); + } + + PS_hPassword.AddChangeHook(PS_ConVarChange); + + HookEvent("player_disconnect", PS_SuppressDisconnectMsg, EventHookMode_Pre); +} + +void PS_OnModuleEnd() +{ + if (!PS_bIsPassworded) { + return; + } + + PS_hReloaded.SetString(PS_sPassword); +} + +static void PS_CheckPassword(int client) +{ + if (!PS_bIsPassworded || !IsPluginEnabled()) { + return; + } + + CreateTimer(0.1, PS_CheckPassword_Timer, client, TIMER_REPEAT); +} + +public Action PS_CheckPassword_Timer(Handle hTimer, any client) +{ + if (!IsClientConnected(client) || IsFakeClient(client)) { + return Plugin_Stop; + } + + if (!IsClientInGame(client)) { + return Plugin_Continue; + } + + QueryClientConVar(client, "sv_password", PS_ConVarDone); + + return Plugin_Stop; +} + +public void PS_ConVarDone(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue, any value) +{ + if (result == ConVarQuery_Okay) { + char buffer[128]; + PS_hPassword.GetString(buffer, sizeof(buffer)); + + if (strcmp(buffer, cvarValue) == 0) { + return; + } + } + + PS_bSuppress = true; + KickClient(client, "Bad password"); +} + +public void PS_ConVarChange(ConVar convar, const char[] oldValue, const char[] newValue) +{ + PS_hPassword.GetString(PS_sPassword, sizeof(PS_sPassword)); + + if (strlen(PS_sPassword) > 0) { + PS_bIsPassworded = true; + PS_SetPasswordOnClients(); + } else { + PS_bIsPassworded = false; + } +} + +public Action PS_SuppressDisconnectMsg(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (bDontBroadcast || !PS_bSuppress) { + return Plugin_Continue; + } + + char clientName[33], networkID[22], reason[65]; + hEvent.GetString("name", clientName, sizeof(clientName)); + hEvent.GetString("networkid", networkID, sizeof(networkID)); + hEvent.GetString("reason", reason, sizeof(reason)); + + Handle newEvent = CreateEvent("player_disconnect", true); + SetEventInt(newEvent, "userid", hEvent.GetInt("userid")); + SetEventString(newEvent, "reason", reason); + SetEventString(newEvent, "name", clientName); + SetEventString(newEvent, "networkid", networkID); + FireEvent(newEvent, true); + + PS_bSuppress = false; + + return Plugin_Handled; +} + +void PS_OnMapEnd() +{ + PS_SetPasswordOnClients(); +} + +void PS_OnClientPutInServer(int client) +{ + PS_CheckPassword(client); +} + +static void PS_SetPasswordOnClients() +{ + char pwbuffer[128]; + PS_hPassword.GetString(pwbuffer, sizeof(pwbuffer)); + + for (int client = 1; client <= MaxClients; client++) { + if (!IsClientInGame(client) || IsFakeClient(client)){ + continue; + } + + LogMessage("[%s] Set password on %N, password %s", PS_MODULE_NAME, client, pwbuffer); + ClientCommand(client, "sv_password \"%s\"", pwbuffer); + } +} diff --git a/addons/sourcemod/scripting/confoglcompmod/ReqMatch.sp b/addons/sourcemod/scripting/confoglcompmod/ReqMatch.sp new file mode 100644 index 000000000..923fbfb03 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/ReqMatch.sp @@ -0,0 +1,391 @@ +#if defined __reg_match_included + #endinput +#endif +#define __reg_match_included + +#define RM_DEBUG 0 +#define RM_MODULE_NAME "ReqMatch" + +#define MAPRESTARTTIME 3.0 +#define RESETMINTIME 60.0 + +static bool + //RM_bMatchRequest[2] = {false, ...}, + RM_bIsMatchModeLoaded = false, + RM_bIsAMatchActive = false, + RM_bIsPluginsLoaded = false, + RM_bIsMapRestarted = false; + +static Handle + RM_hFwdMatchLoad = null, + RM_hFwdMatchUnload = null; + +static ConVar + RM_hSbAllBotGame = null, + RM_hDoRestart = null, + //RM_hAllowVoting = null, + RM_hReloaded = null, + RM_hAutoLoad = null, + RM_hAutoCfg = null, + RM_hConfigFile_On = null, + RM_hConfigFile_Plugins = null, + RM_hConfigFile_Off = null; + +void RM_APL() +{ + RM_hFwdMatchLoad = CreateGlobalForward("LGO_OnMatchModeLoaded", ET_Ignore); + RM_hFwdMatchUnload = CreateGlobalForward("LGO_OnMatchModeUnloaded", ET_Ignore); + + CreateNative("LGO_IsMatchModeLoaded", native_IsMatchModeLoaded); +} + +void RM_OnModuleStart() +{ + RM_hDoRestart = CreateConVarEx("match_restart", "1", "Sets whether the plugin will restart the map upon match mode being forced or requested", _, true, 0.0, true, 1.0); + //RM_hAllowVoting = CreateConVarEx("match_allowvoting", "1", "Sets whether players can vote/request for match mode", _, true, 0.0, true, 1.0); + RM_hAutoLoad = CreateConVarEx("match_autoload", "0", "Has match mode start up automatically when a player connects and the server is not in match mode", _, true, 0.0, true, 1.0); + RM_hAutoCfg = CreateConVarEx("match_autoconfig", "", "Specify which config to load if the autoloader is enabled"); + RM_hConfigFile_On = CreateConVarEx("match_execcfg_on", "confogl.cfg", "Execute this config file upon match mode starts and every map after that."); + //RM_hConfigFile_Plugins = CreateConVarEx("match_execcfg_plugins", "confogl_plugins.cfg", "Execute this config file upon match mode starts. This will only get executed once and meant for plugins that needs to be loaded."); //original + RM_hConfigFile_Plugins = CreateConVarEx("match_execcfg_plugins", "generalfixes.cfg;confogl_plugins.cfg;sharedplugins.cfg", "Execute this config file upon match mode starts. This will only get executed once and meant for plugins that needs to be loaded."); //rework + RM_hConfigFile_Off = CreateConVarEx("match_execcfg_off", "confogl_off.cfg", "Execute this config file upon match mode ends."); + + //RegConsoleCmd("sm_match", RM_Cmd_Match); + RegAdminCmd("sm_forcematch", RM_Cmd_ForceMatch, ADMFLAG_CONFIG, "Forces the game to use match mode"); + RegAdminCmd("sm_fm", RM_Cmd_ForceMatch, ADMFLAG_CONFIG, "Forces the game to use match mode"); + RegAdminCmd("sm_resetmatch", RM_Cmd_ResetMatch, ADMFLAG_CONFIG, "Forces match mode to turn off REGRADLESS for always on or forced match"); + + RM_hSbAllBotGame = FindConVar("sb_all_bot_game"); + + RM_hReloaded = FindConVarEx("match_reloaded"); + if (RM_hReloaded == null) { + RM_hReloaded = CreateConVarEx("match_reloaded", "0", "DONT TOUCH THIS CVAR! This is to prevent match feature keep looping, however the plugin takes care of it. Don't change it!", FCVAR_DONTRECORD|FCVAR_UNLOGGED); + } + + if (RM_hReloaded.BoolValue) { + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Plugin was reloaded from match mode, executing match load", RM_MODULE_NAME); + } + + RM_bIsPluginsLoaded = true; + RM_hReloaded.SetInt(0); + RM_Match_Load(); + } +} + +void RM_OnMapStart() +{ + if (!RM_bIsMatchModeLoaded) { + return; + } + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] New map, executing match config...", RM_MODULE_NAME); + } + + RM_Match_Load(); +} + +void RM_OnClientPutInServer() +{ + if (!RM_hAutoLoad.BoolValue || RM_bIsAMatchActive) { + return; + } + + char buffer[128]; + RM_hAutoCfg.GetString(buffer, sizeof(buffer)); + + RM_UpdateCfgOn(buffer); + RM_Match_Load(); +} + +static void RM_Match_Load() +{ + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match Load", RM_MODULE_NAME); + } + + if (!RM_bIsAMatchActive) { + RM_bIsAMatchActive = true; + } + + RM_hSbAllBotGame.SetInt(1); + char sBuffer[128]; + + if (!RM_bIsPluginsLoaded) { + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Loading plugins and reload self", RM_MODULE_NAME); + } + + RM_hReloaded.SetInt(1); + RM_hConfigFile_Plugins.GetString(sBuffer, sizeof(sBuffer)); + + //ExecuteCfg(sBuffer); //original + //rework + char sPieces[32][256]; + int iNumPieces = ExplodeString(sBuffer, ";", sPieces, sizeof(sPieces), sizeof(sPieces[])); + + // Unlocking and Unloading Plugins. + ServerCommand("sm plugins load_unlock"); + ServerCommand("sm plugins unload_all"); + + // Loading Plugins. + for (int i = 0; i < iNumPieces; i++) { + ExecuteCfg(sPieces[i]); + } + //rework end + + return; + } + + RM_hConfigFile_On.GetString(sBuffer, sizeof(sBuffer)); + ExecuteCfg(sBuffer); + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match config executed", RM_MODULE_NAME); + } + + if (RM_bIsMatchModeLoaded) { + return; + } + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Setting match mode active", RM_MODULE_NAME); + } + + RM_bIsMatchModeLoaded = true; + IsPluginEnabled(true, true); + + //PrintToChatAll("\x01[\x05Confogl\x01] Match mode loaded!"); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Match mode loaded!"); + + if (!RM_bIsMapRestarted && RM_hDoRestart.BoolValue) { + //PrintToChatAll("\x01[\x05Confogl\x01] Restarting map!"); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Restarting map!"); + + CreateTimer(MAPRESTARTTIME, RM_Match_MapRestart_Timer); + } + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match mode loaded!", RM_MODULE_NAME); + } + + Call_StartForward(RM_hFwdMatchLoad); + Call_Finish(); +} + +static void RM_Match_Unload(bool bForced = false) +{ + bool bIsHumansOnServer = IsHumansOnServer(); + + if (!bIsHumansOnServer || bForced) { + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match is no longer active, sb_all_bot_game reset to 0, IsHumansOnServer %b, bForced %b", RM_MODULE_NAME, bIsHumansOnServer, bForced); + } + + RM_bIsAMatchActive = false; + RM_hSbAllBotGame.SetInt(0); + } + + if (bIsHumansOnServer && !bForced) { + return; + } + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Unloading match mode...", RM_MODULE_NAME); + } + + char sBuffer[128]; + RM_bIsMatchModeLoaded = false; + IsPluginEnabled(true, false); + RM_bIsMapRestarted = false; + RM_bIsPluginsLoaded = false; + + Call_StartForward(RM_hFwdMatchUnload); + Call_Finish(); + + //PrintToChatAll("\x01[\x05Confogl\x01] Match mode unloaded!"); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Match mode unloaded!"); + + RM_hConfigFile_Off.GetString(sBuffer, sizeof(sBuffer)); + ExecuteCfg(sBuffer); + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match mode unloaded!", RM_MODULE_NAME); + } +} + +public Action RM_Match_MapRestart_Timer(Handle hTimer) +{ + ServerCommand("sm plugins load_lock"); //rework + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Restarting map...", RM_MODULE_NAME); + } + + char sBuffer[128]; + GetCurrentMap(sBuffer, sizeof(sBuffer)); + ServerCommand("changelevel %s", sBuffer); + RM_bIsMapRestarted = true; + + return Plugin_Stop; +} + +static bool RM_UpdateCfgOn(const char[] cfgfile, bool bIsPrint = true) +{ + if (SetCustomCfg(cfgfile)) { + //PrintToChatAll("\x01[\x05Confogl\x01] Using \"\x04%s\x01\" config.", cfgfile); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Loading '{olive}%s{default}'.", cfgfile); + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Starting match on config %s", RM_MODULE_NAME, cfgfile); + } + + return true; + } + + if (bIsPrint) { + //PrintToChatAll("\x01[\x05Confogl\x01] Config \"\x04%s\x01\" not found, using default config!", cfgfile); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Config '{olive}%s{default}' not found, using default config!", cfgfile); + } + + return false; +} + +public Action RM_Cmd_ForceMatch(int client, int args) +{ + if (RM_bIsMatchModeLoaded) { + return Plugin_Handled; + } + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match mode forced to load!", RM_MODULE_NAME); + } + + if (args < 1) { + //SetCustomCfg(""); //old code + //RM_Match_Load(); //old code + + if (client == 0) { + PrintToServer("[Confogl] Please specify a config to load."); + } else { + //PrintToChat(client, "\x01[\x05Confogl\x01] Please specify a \x04config\x01 to load."); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Please specify a {olive}config{default} to load."); + } + return Plugin_Handled; + } + + char sBuffer[128]; + GetCmdArg(1, sBuffer, sizeof(sBuffer)); + + //RM_UpdateCfgOn(sBuffer); //old code + + if (!RM_UpdateCfgOn(sBuffer, false)) { + if (client == 0) { + PrintToServer("[Confogl] Config %s not found!", sBuffer); + } else { + //PrintToChat(client, "\x01[\x05Confogl\x01] Please specify a \"\x04%s\x01\" to load.", sBuffer); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Config '{olive}%s{default}' not found!", sBuffer); + } + + return Plugin_Handled; + } + + RM_Match_Load(); + + return Plugin_Handled; +} + +public Action RM_Cmd_ResetMatch(int client, int args) +{ + if (!RM_bIsMatchModeLoaded) { + return Plugin_Handled; + } + + if (RM_DEBUG || IsDebugEnabled()) { + LogMessage("[%s] Match mode forced to unload!", RM_MODULE_NAME); + } + + RM_Match_Unload(true); + + return Plugin_Handled; +} + +/*public Action RM_Cmd_Match(int client, int args) +{ + if (RM_bIsMatchModeLoaded || (!IsVersus() && !IsScavenge()) || !RM_hAllowVoting.BoolValue) { + return Plugin_Handled; + } + + int iTeam = GetClientTeam(client); + if ((iTeam == L4D2Team_Survivor || iTeam == L4D2Team_Infected) && !RM_bMatchRequest[iTeam - 2]) { + RM_bMatchRequest[iTeam - 2] = true; + } else { + return Plugin_Handled; + } + + if (RM_bMatchRequest[0] && RM_bMatchRequest[1]) { + //PrintToChatAll("\x01[\x05Confogl\x01] Both teams have agreed to start a competitive match!"); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Both teams have agreed to start a competitive match!"); + + RM_Match_Load(); + } else if (RM_bMatchRequest[0] || RM_bMatchRequest[1]) { + //PrintToChatAll("\x01[\x05Confogl\x01] The \x04%s\x01 have requested to start a competitive match. The \x04%s\x01 must accept with match command!", + //g_sTeamName[iTeam + 4], g_sTeamName[iTeam + 3]); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} The {olive}%s{default} have requested to start a competitive match. The {olive}%s{default} must accept with match command!", \ + g_sTeamName[iTeam + 4], g_sTeamName[iTeam + 3]); + + if (args > 0) { // cfgfile specified + char sBuffer[128]; + GetCmdArg(1, sBuffer, sizeof(sBuffer)); + RM_UpdateCfgOn(sBuffer); + } else { + SetCustomCfg(""); + } + + CreateTimer(30.0, RM_MatchRequestTimeout); + } + + return Plugin_Handled; +} + +public Action RM_MatchRequestTimeout(Handle hTimer) +{ + RM_ResetMatchRequest(); + + return Plugin_Stop; +}*/ + +void RM_OnClientDisconnect(int client) +{ + if (!RM_bIsMatchModeLoaded || IsFakeClient(client)) { + return; + } + + CreateTimer(RESETMINTIME, RM_MatchResetTimer); +} + +public Action RM_MatchResetTimer(Handle hTimer) +{ + RM_Match_Unload(); + + return Plugin_Stop; +} + +/*static void RM_ResetMatchRequest() +{ + RM_hConfigFile_On.RestoreDefault(); + + RM_bMatchRequest[0] = false; + RM_bMatchRequest[1] = false; +}*/ + +stock bool IsAMatchActive() +{ + return RM_bIsAMatchActive; +} + +public int native_IsMatchModeLoaded(Handle plugin, int numParams) +{ + return RM_bIsMatchModeLoaded; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/ScoreMod.sp b/addons/sourcemod/scripting/confoglcompmod/ScoreMod.sp new file mode 100644 index 000000000..87dcdea33 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/ScoreMod.sp @@ -0,0 +1,515 @@ +#if defined __scoremod_included + #endinput +#endif +#define __scoremod_included + +#define DEBUG_SM 0 +#define SM_MODULE_NAME "ScoreMod" + +static int + SM_iDefaultSurvivalBonus = 0, + SM_iDefaultTieBreaker = 0, + SM_iPillPercent = 0, + SM_iAdrenPercent = 0, + SM_iFirstScore = 0; + +static float + SM_fHealPercent = 0.0, + SM_fMapMulti = 0.0, + SM_fHBRatio = 0.0, + SM_fSurvivalBonusRatio = 0.0, + SM_fTempMulti[3] = {0.0, ...}; + +static bool + SM_bEventsHooked = false, + SM_bModuleIsEnabled = false, + SM_bHooked = false, + SM_bIsFirstRoundOver = false, + SM_bIsSecondRoundStarted = false, + SM_bIsSecondRoundOver = false; + +// Cvars +static ConVar + SM_hEnable = null, + SM_hHBRatio = null, + SM_hSurvivalBonusRatio = null, + SM_hMapMulti = null, + SM_hCustomMaxDistance = null; + +// Default Cvar Values +static ConVar + SM_hSurvivalBonus = null, + SM_hTieBreaker = null, + SM_hHealPercent = null, + SM_hPillPercent = null, + SM_hAdrenPercent = null, + SM_hTempMulti0 = null, + SM_hTempMulti1 = null, + SM_hTempMulti2 = null; + +void SM_APL() +{ + CreateNative("LGO_IsScoremodEnabled", Native_IsScoremodEnabled); + CreateNative("LGO_GetScoremodBonus", Native_GetScoremodBonus); +} + +void SM_OnModuleStart() +{ + SM_hEnable = CreateConVarEx("SM_enable", "1", "L4D2 Custom Scoring - Enable/Disable", _, true, 0.0, true, 1.0); + SM_hHBRatio = CreateConVarEx("SM_healthbonusratio", "2.0", "L4D2 Custom Scoring - Healthbonus Multiplier", _, true, 0.25, true, 5.0); + SM_hSurvivalBonusRatio = CreateConVarEx("SM_survivalbonusratio", "0.0", "Ratio to be used for a static survival bonus against Map distance. 25% == 100 points maximum health bonus on a 400 distance map", _); + SM_hTempMulti0 = CreateConVarEx("SM_tempmulti_incap_0", "0.30625", "L4D2 Custom Scoring - How important temp health is on survivors who have had no incaps", _, true, 0.0, true, 1.0); + SM_hTempMulti1 = CreateConVarEx("SM_tempmulti_incap_1", "0.17500", "L4D2 Custom Scoring - How important temp health is on survivors who have had one incap", _, true, 0.0, true, 1.0); + SM_hTempMulti2 = CreateConVarEx("SM_tempmulti_incap_2", "0.10000", "L4D2 Custom Scoring - How important temp health is on survivors who have had two incaps (black and white)", _, true, 0.0, true, 1.0); + SM_hMapMulti = CreateConVarEx("SM_mapmulti", "1", "L4D2 Custom Scoring - Increases Healthbonus Max to Distance Max", _, true, 0.0, true, 1.0); + SM_hCustomMaxDistance = CreateConVarEx("SM_custommaxdistance", "0", "L4D2 Custom Scoring - Custom max distance from config", _, true, 0.0, true, 1.0); + + SM_fTempMulti[0] = SM_hTempMulti0.FloatValue; + SM_fTempMulti[1] = SM_hTempMulti1.FloatValue; + SM_fTempMulti[2] = SM_hTempMulti2.FloatValue; + + SM_hEnable.AddChangeHook(SM_ConVarChanged_Enable); + SM_hHBRatio.AddChangeHook(SM_CVChanged_HealthBonusRatio); + SM_hSurvivalBonusRatio.AddChangeHook(SM_CVChanged_SurvivalBonusRatio); + SM_hTempMulti0.AddChangeHook(SM_ConVarChanged_TempMulti0); + SM_hTempMulti1.AddChangeHook(SM_ConVarChanged_TempMulti1); + SM_hTempMulti2.AddChangeHook(SM_ConVarChanged_TempMulti2); + + SM_hSurvivalBonus = FindConVar("vs_survival_bonus"); + SM_hTieBreaker = FindConVar("vs_tiebreak_bonus"); + SM_hHealPercent = FindConVar("first_aid_heal_percent"); + SM_hPillPercent = FindConVar("pain_pills_health_value"); + SM_hAdrenPercent = FindConVar("adrenaline_health_buffer"); + + SM_fHealPercent = SM_hHealPercent.FloatValue; + SM_iPillPercent = SM_hPillPercent.IntValue; + SM_iAdrenPercent = SM_hAdrenPercent.IntValue; + SM_iDefaultSurvivalBonus = SM_hSurvivalBonus.IntValue; + SM_iDefaultTieBreaker = SM_hTieBreaker.IntValue; + + SM_hHealPercent.AddChangeHook(SM_ConVarChanged_Health); + SM_hPillPercent.AddChangeHook(SM_ConVarChanged_Health); + SM_hAdrenPercent.AddChangeHook(SM_ConVarChanged_Health); + + RegConsoleCmd("sm_health", SM_Cmd_Health); + RegConsoleCmd("sm_bonus", SM_Cmd_Health); +} + +void SM_OnModuleEnd() +{ + PluginDisable(false); +} + +void SM_OnMapStart() +{ + if (!IsPluginEnabled()) { + return; + } + + SM_fMapMulti = (!SM_hMapMulti.BoolValue) ? 1.00 : float(L4D_GetVersusMaxCompletionScore()) / 400.0; + + SM_bModuleIsEnabled = SM_hEnable.BoolValue; + + if (SM_bModuleIsEnabled && !SM_bHooked) { + PluginEnable(); + } + + if (SM_bModuleIsEnabled) { + SM_hTieBreaker.SetInt(0); + } + + if (SM_bModuleIsEnabled && SM_hCustomMaxDistance.BoolValue && GetCustomMapMaxScore() > -1) { + L4D_SetVersusMaxCompletionScore(GetCustomMapMaxScore()); + // to allow a distance score of 0 and a health bonus + if (GetCustomMapMaxScore() > 0) { + SM_fMapMulti = float(GetCustomMapMaxScore()) / 400.0; + } + } + + SM_bIsFirstRoundOver = false; + SM_bIsSecondRoundStarted = false; + SM_bIsSecondRoundOver = false; + SM_iFirstScore = 0; + + SM_fTempMulti[0] = SM_hTempMulti0.FloatValue; + SM_fTempMulti[1] = SM_hTempMulti1.FloatValue; + SM_fTempMulti[2] = SM_hTempMulti2.FloatValue; +} + +public void SM_ConVarChanged_Enable(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + if (StringToInt(sNewValue) == 0) { + PluginDisable(); + SM_bModuleIsEnabled = false; + } else { + PluginEnable(); + SM_bModuleIsEnabled = true; + } +} + +public void SM_ConVarChanged_TempMulti0(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + SM_fTempMulti[0] = StringToFloat(sNewValue); +} + +public void SM_ConVarChanged_TempMulti1(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + SM_fTempMulti[1] = StringToFloat(sNewValue); +} + +public void SM_ConVarChanged_TempMulti2(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + SM_fTempMulti[2] = StringToFloat(sNewValue); +} + +public void SM_CVChanged_HealthBonusRatio(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + SM_fHBRatio = StringToFloat(sNewValue); +} + +public void SM_CVChanged_SurvivalBonusRatio(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + SM_fSurvivalBonusRatio = StringToFloat(sNewValue); +} + +public void SM_ConVarChanged_Health(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + SM_fHealPercent = SM_hHealPercent.FloatValue; + SM_iPillPercent = SM_hPillPercent.IntValue; + SM_iAdrenPercent = SM_hAdrenPercent.IntValue; +} + +static void PluginEnable() +{ + ToggleHook(true); + + SM_fHBRatio = SM_hHBRatio.FloatValue; + SM_fSurvivalBonusRatio = SM_hSurvivalBonusRatio.FloatValue; + SM_iDefaultSurvivalBonus = SM_hSurvivalBonus.IntValue; + SM_iDefaultTieBreaker = SM_hTieBreaker.IntValue; + + SM_hTieBreaker.SetInt(0); + + SM_fHealPercent = SM_hHealPercent.FloatValue; + SM_iPillPercent = SM_hPillPercent.IntValue; + SM_iAdrenPercent = SM_hAdrenPercent.IntValue; + + SM_bHooked = true; +} + +static void ToggleHook(bool bIsHook) +{ + if (bIsHook) { + if (!SM_bEventsHooked) { + HookEvent("door_close", SM_DoorClose_Event); + HookEvent("player_death", SM_PlayerDeath_Event); + HookEvent("round_end", SM_RoundEnd_Event, EventHookMode_PostNoCopy); + HookEvent("round_start", SM_RoundStart_Event, EventHookMode_PostNoCopy); + HookEvent("finale_vehicle_leaving", SM_FinaleVehicleLeaving_Event, EventHookMode_PostNoCopy); + + /*AddCommandListener(SM_Command_Say, "say"); + AddCommandListener(SM_Command_Say, "say_team");*/ + + SM_bEventsHooked = true; + } + } else { + if (SM_bEventsHooked) { + UnhookEvent("door_close", SM_DoorClose_Event); + UnhookEvent("player_death", SM_PlayerDeath_Event); + UnhookEvent("round_end", SM_RoundEnd_Event, EventHookMode_PostNoCopy); + UnhookEvent("round_start", SM_RoundStart_Event, EventHookMode_PostNoCopy); + UnhookEvent("finale_vehicle_leaving", SM_FinaleVehicleLeaving_Event, EventHookMode_PostNoCopy); + + /*RemoveCommandListener(SM_Command_Say, "say"); + RemoveCommandListener(SM_Command_Say, "say_team");*/ + + SM_bEventsHooked = false; + } + } +} + +static void PluginDisable(bool unhook = true) +{ + if (unhook) { + ToggleHook(false); + } + + SM_hSurvivalBonus.SetInt(SM_iDefaultSurvivalBonus); + SM_hTieBreaker.SetInt(SM_iDefaultTieBreaker); + + SM_bHooked = false; +} + +public void SM_DoorClose_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled() || !hEvent.GetBool("checkpoint")) { + return; + } + + SM_hSurvivalBonus.SetInt(SM_CalculateSurvivalBonus()); +} + +public void SM_PlayerDeath_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled()) { + return; + } + + int client = GetClientOfUserId(hEvent.GetInt("userid")); + + // Can't just check for fakeclient + if (client > 0 && GetClientTeam(client) == L4D2Team_Survivor) { + SM_hSurvivalBonus.SetInt(SM_CalculateSurvivalBonus()); + } +} + +public void SM_RoundEnd_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled()) { + return; + } + + if (!SM_bIsFirstRoundOver) { + // First round just ended, save the current score. + SM_bIsFirstRoundOver = true; + int iAliveCount; + SM_iFirstScore = RoundToFloor(SM_CalculateAvgHealth(iAliveCount) * SM_fMapMulti * SM_fHBRatio + 400 * SM_fMapMulti * SM_fSurvivalBonusRatio * iAliveCount / 4.0); + + // If the score is nonzero, trust the SurvivalBonus var. + SM_iFirstScore = (SM_iFirstScore) ? (SM_hSurvivalBonus.IntValue * iAliveCount) : 0; + + //PrintToChatAll("\x01[\x05Confogl\x01] Round 1 Bonus: \x04%d", SM_iFirstScore); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Round 1 Bonus: {olive}%d", SM_iFirstScore); + + if (SM_hCustomMaxDistance.BoolValue && GetCustomMapMaxScore() > -1) { + //PrintToChatAll("\x01[\x05Confogl\x01] Custom Max Distance: \x04%d", GetCustomMapMaxScore()); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Custom Max Distance: {olive}%d", GetCustomMapMaxScore()); + } + } else if (SM_bIsSecondRoundStarted && !SM_bIsSecondRoundOver) { + SM_bIsSecondRoundOver = true; + // Second round has ended, print scores + + int iAliveCount; + int iScore = RoundToFloor(SM_CalculateAvgHealth(iAliveCount) * SM_fMapMulti * SM_fHBRatio + 400 * SM_fMapMulti * SM_fSurvivalBonusRatio * iAliveCount / 4.0); + // If the score is nonzero, trust the SurvivalBonus var. + iScore = (iScore) ? (SM_hSurvivalBonus.IntValue * iAliveCount) : 0; + + //PrintToChatAll("\x01[\x05Confogl\x01] Round 1 Bonus: \x04%d", SM_iFirstScore); + //PrintToChatAll("\x01[\x05Confogl\x01] Round 2 Bonus: \x04%d", iScore); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Round 1 Bonus: {olive}%d", SM_iFirstScore); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Round 2 Bonus: {olive}%d", iScore); + + if (SM_hCustomMaxDistance.BoolValue && GetCustomMapMaxScore() > -1) { + //PrintToChatAll("\x01[\x05Confogl\x01] Custom Max Distance: \x04%d", GetCustomMapMaxScore()); + CPrintToChatAll("{blue}[{default}Confogl{blue}]{default} Custom Max Distance: {olive}%d", GetCustomMapMaxScore()); + } + } +} + +public void SM_RoundStart_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled() || !SM_bIsFirstRoundOver) { + return; + } + + // Mark the beginning of the second round. + SM_bIsSecondRoundStarted = true; +} + +public void SM_FinaleVehicleLeaving_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled()) { + return; + } + + SM_hSurvivalBonus.SetInt(SM_CalculateSurvivalBonus()); +} + +public Action SM_Cmd_Health(int client, int args) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled()) { + return Plugin_Handled; + } + + int iAliveCount; + float fAvgHealth = SM_CalculateAvgHealth(iAliveCount); + + if (SM_bIsSecondRoundStarted) { + //PrintToChat(client, "\x01[\x05Confogl\x01] Round 1 Bonus: \x04%d", SM_iFirstScore); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Round 1 Bonus: {olive}%d", SM_iFirstScore); + } + + if (client) { + //PrintToChat(client, "\x01[\x05Confogl\x01] Average Health: \x04%.02f", fAvgHealth); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Average Health: {olive}%.02f", fAvgHealth); + } else { + PrintToServer("[Confogl] Average Health: %.02f", fAvgHealth); + } + + int iScore = RoundToFloor(fAvgHealth * SM_fMapMulti * SM_fHBRatio) * iAliveCount; + + if (DEBUG_SM || IsDebugEnabled()) { + LogMessage("[%s] CalcScore: %d MapMulti: %.02f Multiplier %.02f", SM_MODULE_NAME, iScore, SM_fMapMulti, SM_fHBRatio); + } + + if (client) { + //PrintToChat(client, "\x01[\x05Confogl\x01] Health Bonus: \x04%d", iScore); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Health Bonus: {olive}%d", iScore); + + if (SM_fSurvivalBonusRatio != 0.0) { + //PrintToChat(client, "\x01[\x05Confogl\x01] Static Survival Bonus Per Survivor: \x04%d", RoundToFloor(400 * SM_fMapMulti * SM_fSurvivalBonusRatio)); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Static Survival Bonus Per Survivor: {olive}%d", RoundToFloor(400 * SM_fMapMulti * SM_fSurvivalBonusRatio)); + } + + if (SM_hCustomMaxDistance.BoolValue && GetCustomMapMaxScore() > -1) { + //PrintToChat(client, "\x01[\x05Confogl\x01] Custom Max Distance: \x04%d", GetCustomMapMaxScore()); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Custom Max Distance: {olive}%d", GetCustomMapMaxScore()); + } + } else { + PrintToServer("[Confogl] Health Bonus: %d", iScore); + + if (SM_fSurvivalBonusRatio != 0.0) { + PrintToServer("[Confogl] Static Survival Bonus Per Survivor: %d", RoundToFloor(400 * SM_fMapMulti * SM_fSurvivalBonusRatio)); + } + + if (SM_hCustomMaxDistance.BoolValue && GetCustomMapMaxScore() > -1) { + PrintToServer("[Confogl] Custom Max Distance: %d", GetCustomMapMaxScore()); + } + } + + return Plugin_Handled; +} + +static float SM_CalculateAvgHealth(int &iAliveCount = 0) +{ + // Temporary Storage Variables for inventory + char strTemp[MAX_ENTITY_NAME_LENGTH]; + + int iTotalHealth, iTotalTempHealth[3], iTemp; + int iCurrHealth, iCurrTemp, iIncapCount, iSurvCount; + + float fTotalAdjustedTempHealth; + + bool IsFinale = L4D_IsMissionFinalMap(); + + iAliveCount = 0; + + for (int index = 1; index <= MaxClients; index++) { + if (IsSurvivor(index)) { + iSurvCount++; + if (IsPlayerAlive(index)) { + if (GetEntProp(index, Prop_Send, "m_isIncapacitated", 1) < 1) { + + // Get Main health stats + iCurrHealth = GetSurvivorPermanentHealth(index); + iCurrTemp = GetSurvivorTempHealth(index); + iIncapCount = GetSurvivorIncapCount(index); + + // Adjust for kits + iTemp = GetPlayerWeaponSlot(index, L4D2WeaponSlot_HeavyHealthItem); + if (iTemp > -1) { + GetEdictClassname(iTemp, strTemp, sizeof(strTemp)); + + if (strcmp(strTemp, "weapon_first_aid_kit") == 0) { + iCurrHealth = RoundToFloor(iCurrHealth + ((100 - iCurrHealth) * SM_fHealPercent)); + iCurrTemp = 0; + iIncapCount = 0; + } + } + + // Adjust for pills/adrenaline + iTemp = GetPlayerWeaponSlot(index, L4D2WeaponSlot_LightHealthItem); + if (iTemp > -1) { + GetEdictClassname(iTemp, strTemp, sizeof(strTemp)); + + if (strcmp(strTemp, "weapon_pain_pills") == 0) { + iCurrTemp += SM_iPillPercent; + } else if (strcmp(strTemp, "weapon_adrenaline") == 0) { + iCurrTemp += SM_iAdrenPercent; + } + } + + // Enforce max 100 total health points + if ((iCurrTemp + iCurrHealth) > 100) { + iCurrTemp = 100 - iCurrHealth; + } + + iAliveCount++; + iTotalHealth += iCurrHealth; + + if (iIncapCount < 0) { + iIncapCount = 0; + } else if (iIncapCount > 2) { + iIncapCount = 2; + } + + iTotalTempHealth[iIncapCount] += iCurrTemp; + } else if (!IsFinale) { + iAliveCount++; + } + } + } + } + + for (int i = 0; i < 3; i++) { + fTotalAdjustedTempHealth += iTotalTempHealth[i] * SM_fTempMulti[i]; + } + + // Total Score = Average Health points * numAlive + + // Average Health points = Total Health Points / Survivor Count + // Total Health Points = Total Permanent Health + Total Adjusted Temp Health + + // return Average Health Points + float fAvgHealth = (iTotalHealth + fTotalAdjustedTempHealth) / iSurvCount; + +#if DEBUG_SM + LogMessage("[%s] TotalPerm: %d TotalAdjustedTemp: %.02f SurvCount: %d AliveCount: %d AvgHealth: %.02f", \ + SM_MODULE_NAME, iTotalHealth, fTotalAdjustedTempHealth, iSurvCount, iAliveCount, fAvgHealth); +#endif + + return fAvgHealth; +} + +/*public Action SM_Command_Say(int iClient, const char[] sCommand, int iArgc) +{ + if (iClient == 0 || !SM_bModuleIsEnabled || !IsPluginEnabled()) { + return Plugin_Continue; + } + + char sMessage[MAX_NAME_LENGTH]; + GetCmdArg(1, sMessage, sizeof(sMessage)); + + if (strcmp(sMessage, "!health") == 0) { + return Plugin_Handled; + } + + return Plugin_Continue; +}*/ + +static int SM_CalculateSurvivalBonus() +{ + return RoundToFloor(SM_CalculateAvgHealth() * SM_fMapMulti * SM_fHBRatio + 400 * SM_fMapMulti * SM_fSurvivalBonusRatio); +} + +static int SM_CalculateScore() +{ + int iAliveCount = 0; + float fScore = SM_CalculateAvgHealth(iAliveCount); + + return RoundToFloor(fScore * SM_fMapMulti * SM_fHBRatio + 400 * SM_fMapMulti * SM_fSurvivalBonusRatio) * iAliveCount; +} + +public int Native_IsScoremodEnabled(Handle plugin, int numParams) +{ + return (SM_bModuleIsEnabled && IsPluginEnabled()); +} + +public int Native_GetScoremodBonus(Handle plugin, int numParams) +{ + if (!SM_bModuleIsEnabled || !IsPluginEnabled()) { + return -1; + } + + return SM_CalculateScore(); +} diff --git a/addons/sourcemod/scripting/confoglcompmod/UnprohibitBosses.sp b/addons/sourcemod/scripting/confoglcompmod/UnprohibitBosses.sp new file mode 100644 index 000000000..41d334c70 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/UnprohibitBosses.sp @@ -0,0 +1,58 @@ +#if defined __unprohibit_bosses_included + #endinput +#endif +#define __unprohibit_bosses_included + +#define UB_MODULE_NAME "UnprohibitBosses" + +static bool + UB_bEnabled = true; + +static ConVar + UB_hEnable = null; + +void UB_OnModuleStart() +{ + UB_hEnable = CreateConVarEx("boss_unprohibit", "1", "Enable bosses spawning on all maps, even through they normally aren't allowed", _, true, 0.0, true, 1.0); + + UB_bEnabled = UB_hEnable.BoolValue; //turns on when changing cvar only + UB_hEnable.AddChangeHook(UB_ConVarChange); +} + +public void UB_ConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + UB_bEnabled = UB_hEnable.BoolValue; +} + +Action UB_OnGetScriptValueInt(const char[] key, int &retVal) +{ + if (IsPluginEnabled() && UB_bEnabled) { + if (strcmp(key, "DisallowThreatType") == 0) { + retVal = 0; + return Plugin_Handled; + } + + if (strcmp(key, "ProhibitBosses") == 0) { + retVal = 0; + return Plugin_Handled; + } + } + + return Plugin_Continue; +} + +Action UB_OnGetMissionVSBossSpawning() +{ + //if (IsPluginEnabled() && UB_bEnabled) { + if (UB_bEnabled) { + char mapbuf[32]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + if (strcmp(mapbuf, "c7m1_docks") == 0 || strcmp(mapbuf, "c13m2_southpinestream") == 0) { + return Plugin_Continue; + } + + return Plugin_Handled; + } + + return Plugin_Continue; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/UnreserveLobby.sp b/addons/sourcemod/scripting/confoglcompmod/UnreserveLobby.sp new file mode 100644 index 000000000..650356a3a --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/UnreserveLobby.sp @@ -0,0 +1,36 @@ +#if defined __unreserve_lobby_included + #endinput +#endif +#define __unreserve_lobby_included + +#define UL_MODULE_NAME "UnreserveLobby" + +static ConVar + UL_hEnable = null; + +void UL_OnModuleStart() +{ + UL_hEnable = CreateConVarEx("match_killlobbyres", "1", \ + "Sets whether the plugin will clear lobby reservation once a match have begun", \ + _, true, 0.0, true, 1.0 \ + ); + + RegAdminCmd("sm_killlobbyres", UL_KillLobbyRes, ADMFLAG_BAN, "Forces the plugin to kill lobby reservation"); +} + +void UL_OnClientPutInServer() +{ + if (!IsPluginEnabled() || !UL_hEnable.BoolValue) { + return; + } + + L4D_LobbyUnreserve(); +} + +public Action UL_KillLobbyRes(int client, int args) +{ + L4D_LobbyUnreserve(); + ReplyToCommand(client, "[Confogl] Removed lobby reservation."); + + return Plugin_Handled; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/WaterSlowdown.sp b/addons/sourcemod/scripting/confoglcompmod/WaterSlowdown.sp new file mode 100644 index 000000000..ad5565270 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/WaterSlowdown.sp @@ -0,0 +1,125 @@ +#if defined __water_slowdown_included + #endinput +#endif +#define __water_slowdown_included + +#define WS_MODULE_NAME "WaterSlowdown" + +static float + WS_fSlowdownFactor = 0.90; + +static bool + WS_bEnabled = true, + WS_bJockeyInWater = false, + WS_bPlayerInWater[MAXPLAYERS + 1] = {false, ...}; + +static ConVar + WS_hEnable = null, + WS_hFactor = null; + +void WS_OnModuleStart() +{ + WS_hEnable = CreateConVarEx("waterslowdown", "1", "Enables additional water slowdown", _, true, 0.0, true, 1.0); + WS_hFactor = CreateConVarEx("slowdown_factor", "0.90", "Sets how much water will slow down survivors. 1.00 = Vanilla"); + + WS_SetStatus(); + WS_fSlowdownFactor = WS_hFactor.FloatValue; + + WS_hEnable.AddChangeHook(WS_ConVarChange); + WS_hFactor.AddChangeHook(WS_FactorConVarChange); + + HookEvent("round_start", WS_RoundStart, EventHookMode_PostNoCopy); + HookEvent("jockey_ride", WS_JockeyRide); + HookEvent("jockey_ride_end", WS_JockeyRideEnd); +} + +public void WS_FactorConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + WS_fSlowdownFactor = WS_hFactor.FloatValue; +} + +public void WS_ConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + WS_SetStatus(); +} + +void WS_OnMapEnd() +{ + WS_SetStatus(false); +} + +void WS_OnModuleEnd() +{ + WS_SetStatus(false); +} + +void WS_OnGameFrame() +{ + if (!IsServerProcessing() || !IsPluginEnabled() || !WS_bEnabled) { + return; + } + + int client, flags; + + for (int i = 0; i < NUM_OF_SURVIVORS; i++) { + client = GetSurvivorIndex(i); + + if (client != 0 && IsValidEntity(client)) { + flags = GetEntityFlags(client); + + if (!(flags & IN_JUMP && WS_bPlayerInWater[client])) { + if (flags & FL_INWATER) { + if (!WS_bPlayerInWater[client]) { + WS_bPlayerInWater[client] = true; + SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", WS_fSlowdownFactor); + } + } else { + if (WS_bPlayerInWater[client]) { + WS_bPlayerInWater[client] = false; + SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", 1.0); + } + } + } + } + } +} + +public void WS_RoundStart(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + WS_SetStatus(); +} + +public void WS_JockeyRide(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + int victim = GetClientOfUserId(hEvent.GetInt("victim")); + int jockey = GetClientOfUserId(hEvent.GetInt("userid")); + + if (WS_bPlayerInWater[victim] && !WS_bJockeyInWater) { + WS_bJockeyInWater = true; + SetEntPropFloat(jockey, Prop_Send, "m_flLaggedMovementValue", WS_fSlowdownFactor); + } else if (!WS_bPlayerInWater[victim] && WS_bJockeyInWater) { + WS_bJockeyInWater = false; + SetEntPropFloat(jockey, Prop_Send, "m_flLaggedMovementValue", 1.0); + } +} + +public void WS_JockeyRideEnd(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + int jockey = GetClientOfUserId(hEvent.GetInt("userid")); + + WS_bJockeyInWater = false; + + if (jockey > 0 && IsValidEntity(jockey)) { + SetEntPropFloat(jockey, Prop_Send, "m_flLaggedMovementValue", 1.0); + } +} + +static void WS_SetStatus(bool bEnable = true) +{ + if (!bEnable) { + WS_bEnabled = false; + return; + } + + WS_bEnabled = WS_hEnable.BoolValue; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/WeaponCustomization.sp b/addons/sourcemod/scripting/confoglcompmod/WeaponCustomization.sp new file mode 100644 index 000000000..769222f87 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/WeaponCustomization.sp @@ -0,0 +1,132 @@ +#if defined __weapon_customization_included + #endinput +#endif +#define __weapon_customization_included + +#define WC_MODULE_NAME "WeaponCustomization" + +static const char sSniperNames[][] = +{ + "weapon_hunting_rifle", + "weapon_sniper_military", + "weapon_sniper_awp", + "weapon_sniper_scout", + "weapon_rifle_sg552" +}; + +static char + WC_sLastWeapon[64] = "\0"; + +static int + WC_iLimitCount = 1, + WC_iLastWeapon = -1, + WC_iLastClient = -1; + +static ConVar + WC_hLimitCount = null; + +void WC_OnModuleStart() +{ + WC_hLimitCount = CreateConVarEx("limit_sniper", "1", "Limits the maximum number of sniping rifles at one time to this number", _, true, 0.0, true, 4.0); + + WC_iLimitCount = WC_hLimitCount.IntValue; + WC_hLimitCount.AddChangeHook(WC_ConVarChange); + + HookEvent("player_use", WC_PlayerUse_Event); + HookEvent("weapon_drop", WC_WeaponDrop_Event); +} + +public void WC_ConVarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + WC_iLimitCount = WC_hLimitCount.IntValue; +} + +public void WC_WeaponDrop_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!IsPluginEnabled()) { + return; + } + + WC_iLastWeapon = hEvent.GetInt("propid"); + WC_iLastClient = GetClientOfUserId(hEvent.GetInt("userid")); + hEvent.GetString("item", WC_sLastWeapon, sizeof(WC_sLastWeapon)); +} + +public void WC_PlayerUse_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!IsPluginEnabled()) { + return; + } + + int client = GetClientOfUserId(hEvent.GetInt("userid")); + + int primary = GetPlayerWeaponSlot(client, L4D2WeaponSlot_Primary); + if (primary < 1 || !IsValidEdict(primary)) { + return; + } + + char primary_name[MAX_ENTITY_NAME_LENGTH]; + GetEdictClassname(primary, primary_name, sizeof(primary_name)); + + if (IsValidSniper(primary_name)) { + if (SniperCount(client) >= WC_iLimitCount) { + RemovePlayerItem(client, primary); + //PrintToChat(client, "\x01[\x05Confogl\x01] Maximum \x04%d \x01sniping rifle(s) is enforced.", WC_iLimitCount); + CPrintToChat(client, "{blue}[{default}Confogl{blue}]{default} Maximum {blue}%d {olive}sniping rifle(s) {default}is enforced.", WC_iLimitCount); + + if (WC_iLastClient == client) { + if (WC_iLastWeapon > 0 && IsValidEdict(WC_iLastWeapon)) { + KillEntity(WC_iLastWeapon); + + int flags = GetCommandFlags("give"); + SetCommandFlags("give", flags ^ FCVAR_CHEAT); + + char sTemp[64]; + Format(sTemp, sizeof(sTemp), "give %s", WC_sLastWeapon); + FakeClientCommand(client, sTemp); + + SetCommandFlags("give", flags); + } + } + } + } + + WC_iLastWeapon = -1; + WC_iLastClient = -1; + WC_sLastWeapon[0] = 0; +} + +static int SniperCount(int client) +{ + char temp[MAX_ENTITY_NAME_LENGTH]; + int count = 0, index = 0, ent = 0; + + for (int i = 0; i < 4; i++) { + index = GetSurvivorIndex(i); + + if (index != client && index != 0 && IsClientConnected(index)) { + ent = GetPlayerWeaponSlot(index, L4D2WeaponSlot_Primary); + + if (ent > 0 && IsValidEdict(ent)) { + GetEdictClassname(ent, temp, sizeof(temp)); + + if (IsValidSniper(temp)) { + count++; + } + } + } + } + + return count; +} + +static bool IsValidSniper(const char[] sWeaponName) +{ + for (int i = 0; i < sizeof(sSniperNames); i++) { + if (strcmp(sWeaponName, sSniperNames[i], true) == 0) { + return true; + } + } + + return false; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/WeaponInformation.sp b/addons/sourcemod/scripting/confoglcompmod/WeaponInformation.sp new file mode 100644 index 000000000..bda3e180c --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/WeaponInformation.sp @@ -0,0 +1,1292 @@ +#if defined __weapon_information_included + #endinput +#endif +#define __weapon_information_included + +#define DEBUG_WI 0 + +#if (DEBUG_ALL) + #define DEBUG_WI 1 +#endif + +#define WI_MODULE_NAME "WepInfo" + +#define MODEL_PREFIX "models/w_models/weapons/w_" +#define MODEL_SURFIX ".mdl" +#define SPAWN_PREFIX "weapon_" +#define SPAWN_SURFIX "_spawn" + +//==================================================== +// Weapon Index & ID +//==================================================== +#define WEAPON_REMOVE_INDEX -1 +#define WEAPON_NULL_INDEX 0 + +#define WEAPON_SMG_ID 2 +#define WEAPON_SMG_INDEX 1 +#define WEAPON_PUMPSHOTGUN_ID 3 +#define WEAPON_PUMPSHOTGUN_INDEX 2 + +#define WEAPON_AUTOSHOTGUN_ID 4 +#define WEAPON_AUTOSHOTGUN_INDEX 3 +#define WEAPON_RIFLE_ID 5 +#define WEAPON_RIFLE_INDEX 4 + +#define WEAPON_HUNTING_RIFLE_ID 6 +#define WEAPON_HUNTING_RIFLE_INDEX 5 +#define WEAPON_SMG_SILENCED_ID 7 +#define WEAPON_SMG_SILENCED_INDEX 6 + +#define WEAPON_SHOTGUN_CHROME_ID 8 +#define WEAPON_SHOTGUN_CHROME_INDEX 7 +#define WEAPON_RIFLE_DESERT_ID 9 +#define WEAPON_RIFLE_DESERT_INDEX 8 + +#define WEAPON_SNIPER_MILITARY_ID 10 +#define WEAPON_SNIPER_MILITARY_INDEX 9 +#define WEAPON_SHOTGUN_SPAS_ID 11 +#define WEAPON_SHOTGUN_SPAS_INDEX 10 + +#define WEAPON_GRENADE_LAUNCHER_ID 21 +#define WEAPON_GRENADE_LAUNCHER_INDEX 11 +#define WEAPON_RIFLE_AK47_ID 26 +#define WEAPON_RIFLE_AK47_INDEX 12 + +#define WEAPON_RIFLE_M60_ID 37 +#define WEAPON_RIFLE_M60_INDEX 13 + +#define WEAPON_SMG_MP5_ID 33 +#define WEAPON_SMG_MP5_INDEX 14 +#define WEAPON_RIFLE_SG552_ID 34 +#define WEAPON_RIFLE_SG552_INDEX 15 + +#define WEAPON_SNIPER_AWP_ID 35 +#define WEAPON_SNIPER_AWP_INDEX 16 +#define WEAPON_SNIPER_SCOUT_ID 36 +#define WEAPON_SNIPER_SCOUT_INDEX 17 + +#define WEAPON_CHAINSAW_INDEX 18 + +#define WEAPON_PIPE_BOMB_INDEX 19 +#define WEAPON_MOLOTOV_INDEX 20 +#define WEAPON_VOMITJAR_INDEX 21 + +#define WEAPON_FIRST_AID_KIT_INDEX 22 +#define WEAPON_DEFIBRILLATOR_INDEX 23 +#define WEAPON_UPG_EXPLOSIVE_INDEX 24 +#define WEAPON_UPG_INCENDIARY_INDEX 25 + +#define WEAPON_PAIN_PILLS_INDEX 26 +#define WEAPON_ADRENALINE_INDEX 27 + +//==================================================== +#define NUM_OF_WEAPONS 28 + +#define FIRST_WEAPON 1 +#define LAST_WEAPON 18 +#define FIRST_EXTRA 19 +#define LAST_EXTRA 27 + +#define WEAPON_NUMBER_OF_START_KITS 4 + +enum /*WEAPONATTRIBUTES*/ +{ + WeaponID, + Tier1EquivalentIndex, + ReplacementIndex, + + WeaponAttributes_Size +}; + +static const int Weapon_Attributes[NUM_OF_WEAPONS][WeaponAttributes_Size] = { + + //==================================================== + // Weapons + //==================================================== + + // NULL + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // SMG + { + WEAPON_SMG_ID, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Pumpshotgun + { + WEAPON_PUMPSHOTGUN_ID, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Autoshotgun + { + WEAPON_AUTOSHOTGUN_ID, + WEAPON_PUMPSHOTGUN_INDEX, + WEAPON_NULL_INDEX + }, + + // Rifle + { + WEAPON_RIFLE_ID, + WEAPON_SMG_INDEX, + WEAPON_NULL_INDEX + }, + + // Hunting rifle + { + WEAPON_HUNTING_RIFLE_ID, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // SMG silenced + { + WEAPON_SMG_SILENCED_ID, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Chrome shotgun + { + WEAPON_SHOTGUN_CHROME_ID, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Desert rifle + { + WEAPON_RIFLE_DESERT_ID, + WEAPON_SMG_INDEX, + WEAPON_NULL_INDEX + }, + + // Military sniper + { + WEAPON_SNIPER_MILITARY_ID, + WEAPON_HUNTING_RIFLE_INDEX, + WEAPON_NULL_INDEX + }, + + // Spas shotgun + { + WEAPON_SHOTGUN_SPAS_ID, + WEAPON_SHOTGUN_CHROME_INDEX, + WEAPON_NULL_INDEX + }, + + // Grenade launcher + { + WEAPON_GRENADE_LAUNCHER_ID, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX + }, + + // AK47 + { + WEAPON_RIFLE_AK47_ID, + WEAPON_SMG_SILENCED_INDEX, + WEAPON_NULL_INDEX + }, + + // M60 + { + WEAPON_RIFLE_M60_ID, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX, + }, + + // MP5 + { + WEAPON_SMG_MP5_ID, + WEAPON_NULL_INDEX, + WEAPON_SMG_INDEX + }, + + // SG552 + { + WEAPON_RIFLE_SG552_ID, + WEAPON_SMG_MP5_INDEX, + WEAPON_RIFLE_INDEX + }, + + // AWP + { + WEAPON_SNIPER_AWP_ID, + WEAPON_SNIPER_SCOUT_INDEX, + WEAPON_SNIPER_MILITARY_INDEX + }, + + // Scout + { + WEAPON_SNIPER_SCOUT_ID, + WEAPON_NULL_INDEX, + WEAPON_HUNTING_RIFLE_INDEX + }, + + //==================================================== + // Melee Weapons + //==================================================== + + // Chainsaw + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX + }, + + //==================================================== + // Extra Items + //==================================================== + + // Pipe Bomb + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Molotov + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Vomitjar + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // First Aid Kit + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX + }, + + // Defibrillator + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX + }, + + // Explosive Upgrade Pack + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX + }, + + // Incendiary Upgrade Pack + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_REMOVE_INDEX + }, + + // Pain pills + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + }, + + // Adrenaline + { + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX, + WEAPON_NULL_INDEX + } +}; + +static const char Weapon_Models[NUM_OF_WEAPONS][] = { + + //==================================================== + // Weapons + //==================================================== + + // NULL + "", + + // SMG + "smg_uzi", + + // Shotgun + "shotgun", + + // Autoshotgun + "autoshot_m4super", + + // Rifle + "rifle_m16a2", + + // Hunting rifle + "sniper_mini14", + + // SMG silenced + "smg_a", + + // Chrome shotgun + "pumpshotgun_a", + + // Desert rifle + "rifle_b", + + // Military rifle + "sniper_military", + + // Spas shotgun + "shotgun_spas", + + // Grenade launcher + "", + + // AK47 + "rifle_ak47", + + // M60 + "m60", + + // MP5 + "smg_mp5", + + // SG552 + "", + + // AWP + "", + + // Scout + "sniper_scout", + + //==================================================== + // Melee Weapons + //==================================================== + + // Chainsaw + "", + + //==================================================== + // Extra Items + //==================================================== + + // Pipe Bomb + "", + + // Molotov + "", + + // Vomitjar + "", + + // First Aid Kit + "", + + // Defibrillator + "", + + // Explosive Upgrade Pack + "", + + // Incendiary Upgrade Pack + "", + + // Pain pills + "", + + // Adrenaline + "" +}; + +static const char Weapon_Spawns[NUM_OF_WEAPONS][] = { + + //==================================================== + // Weapons + //==================================================== + + // NULL + "", + + // SMG + "", + + // Shotgun + "", + + // Autoshotgun + "autoshotgun", + + // Rifle + "rifle", + + // Hunting rifle + "", + + // SMG silenced + "", + + // Chrome shotgun + "", + + // Desert rifle + "rifle_desert", + + // Military rifle + "sniper_military", + + // Spas shotgun + "shotgun_spas", + + // Grenade launcher + "grenade_launcher", + + // AK47 + "rifle_ak47", + + // M60 + "rifle_m60", + + // MP5 + "", + + // SG552 + "", + + // AWP + "", + + // Scout + "", + + //==================================================== + // Melee Weapons + //==================================================== + + // Chainsaw + "chainsaw", + + //==================================================== + // Extra Items + //==================================================== + + // Pipe Bomb + "pipe_bomb", + + // Molotov + "molotov", + + // Vomitjar + "vomitjar", + + // First Aid Kit + "first_aid_kit", + + // Defibrillator + "defibrillator", + + // Explosive Upgrade Pack + "upgradepack_explosive", + + // Incendiary Upgrade Pack + "upgradepack_incendiary", + + // Pain pills + "pain_pills", + + // Adrenaline + "adrenaline" +}; + +//==================================================== +// Kit Protection +//==================================================== +static int + Weapon_iKitEntity[WEAPON_NUMBER_OF_START_KITS] = {0, ...}, + Weapon_iKitCount = 0; + +//==================================================== +// Map Info +//==================================================== +static float + Weapon_fMapOrigin_Start[3], + Weapon_fMapOrigin_End[3], + Weapon_fMapDist_Start, + Weapon_fMapDist_StartExtra, + Weapon_fMapDist_End; + +static bool + Weapon_bUpdateMapInfo = true; +//==================================================== + +static bool + Weapon_bConvar[NUM_OF_WEAPONS] = {false, ...}, + Weapon_bReplaceTier2 = true, + Weapon_bReplaceTier2_Finale = true, + Weapon_bReplaceTier2_All = true, + Weapon_bLimitTier2 = true, + Weapon_bLimitTier2_Safehouse = true, + Weapon_bReplaceStartKits = true, + Weapon_bReplaceFinaleKits = true, + Weapon_bRemoveLaserSight = true, + Weapon_bRemoveExtraItems = true; + +static ConVar + Weapon_hConvar[NUM_OF_WEAPONS] = {null, ...}, + Weapon_hReplaceTier2 = null, + Weapon_hReplaceTier2_Finale = null, + Weapon_hReplaceTier2_All = null, + Weapon_hLimitTier2 = null, + Weapon_hLimitTier2_Safehouse = null, + Weapon_hReplaceStartKits = null, + Weapon_hReplaceFinaleKits = null, + Weapon_hRemoveLaserSight = null, + Weapon_hRemoveExtraItems = null; + +//==================================================== +// Module setup +//==================================================== +void WI_OnModuleStart() +{ + WI_Convar_Setup(); + + HookEvent("round_start", WI_RoundStart_Event, EventHookMode_PostNoCopy); + HookEvent("round_end", WI_RoundEnd_Event, EventHookMode_PostNoCopy); + HookEvent("spawner_give_item", WI_SpawnerGiveItem_Event); +} + +void WI_OnMapEnd() +{ + Weapon_bUpdateMapInfo = true; +} + +//==================================================== +// Functions +//==================================================== +static void WI_Convar_Setup() +{ + Weapon_hConvar[WEAPON_SMG_MP5_INDEX] = CreateConVarEx("replace_cssweapons", "1", "Replace CSS weapons with normal L4D2 weapons", _, true, 0.0, true, 1.0); + + Weapon_hConvar[WEAPON_RIFLE_SG552_INDEX] = Weapon_hConvar[WEAPON_SMG_MP5_INDEX]; + Weapon_hConvar[WEAPON_SNIPER_AWP_INDEX] = Weapon_hConvar[WEAPON_SMG_MP5_INDEX]; + Weapon_hConvar[WEAPON_SNIPER_SCOUT_INDEX] = Weapon_hConvar[WEAPON_SMG_MP5_INDEX]; + + Weapon_hConvar[WEAPON_GRENADE_LAUNCHER_INDEX] = CreateConVarEx("remove_grenade", "1", "Remove all grenade launchers", _, true, 0.0, true, 1.0); + Weapon_hConvar[WEAPON_CHAINSAW_INDEX] = CreateConVarEx("remove_chainsaw", "1", "Remove all chainsaws", _, true, 0.0, true, 1.0); + Weapon_hConvar[WEAPON_RIFLE_M60_INDEX] = CreateConVarEx("remove_m60", "1", "Remove all M60 rifles", _, true, 0.0, true, 1.0); + + Weapon_hConvar[WEAPON_FIRST_AID_KIT_INDEX] = CreateConVarEx("remove_statickits", "1", "Remove all static medkits (medkits such as the gun shop, these are compiled into the map)", _, true, 0.0, true, 1.0); + Weapon_hConvar[WEAPON_DEFIBRILLATOR_INDEX] = CreateConVarEx("remove_defib", "1", "Remove all defibrillators", _, true, 0.0, true, 1.0); + Weapon_hConvar[WEAPON_UPG_EXPLOSIVE_INDEX] = CreateConVarEx("remove_upg_explosive", "1", "Remove all explosive upgrade packs", _, true, 0.0, true, 1.0); + Weapon_hConvar[WEAPON_UPG_INCENDIARY_INDEX] = CreateConVarEx("remove_upg_incendiary", "1", "Remove all incendiary upgrade packs", _, true, 0.0, true, 1.0); + + for (int index = FIRST_WEAPON; index < NUM_OF_WEAPONS; index++) { + if (Weapon_hConvar[index] == null) { + continue; + } + + Weapon_bConvar[index] = Weapon_hConvar[index].BoolValue; + Weapon_hConvar[index].AddChangeHook(WI_ConvarChange); + } + + Weapon_hReplaceTier2 = CreateConVarEx("replace_tier2", "1", "Replace tier 2 weapons in start and end safe room with their tier 1 equivalent", _, true, 0.0, true, 1.0); + Weapon_hReplaceTier2_Finale = CreateConVarEx("replace_tier2_finale", "1", "Replace tier 2 weapons in start safe room with their tier 1 equivalent, on finale", _, true, 0.0, true, 1.0); + Weapon_hReplaceTier2_All = CreateConVarEx("replace_tier2_all", "1", "Replace ALL tier 2 weapons with their tier 1 equivalent EVERYWHERE", _, true, 0.0, true, 1.0); + Weapon_hLimitTier2 = CreateConVarEx("limit_tier2", "1", "Limit tier 2 weapons outside safe rooms. Replaces a tier 2 stack with tier 1 upon first weapon pickup", _, true, 0.0, true, 1.0); + Weapon_hLimitTier2_Safehouse = CreateConVarEx("limit_tier2_saferoom", "1", "Limit tier 2 weapons inside safe rooms. Replaces a tier 2 stack with tier 1 upon first weapon pickup", _, true, 0.0, true, 1.0); + Weapon_hReplaceStartKits = CreateConVarEx("replace_startkits", "1", "Replaces start medkits with pills", _, true, 0.0, true, 1.0); + Weapon_hReplaceFinaleKits = CreateConVarEx("replace_finalekits", "1", "Replaces finale medkits with pills", _, true, 0.0, true, 1.0); + Weapon_hRemoveLaserSight = CreateConVarEx("remove_lasersight", "1", "Remove all laser sight upgrades", _, true, 0.0, true, 1.0); + Weapon_hRemoveExtraItems = CreateConVarEx("remove_saferoomitems", "1", "Remove all extra items inside saferooms (items for slot 3, 4 and 5, minus medkits)", _, true, 0.0, true, 1.0); + + ConVarsInType(); + + Weapon_hReplaceTier2.AddChangeHook(WI_ConvarChange); + Weapon_hReplaceTier2_Finale.AddChangeHook(WI_ConvarChange); + Weapon_hReplaceTier2_All.AddChangeHook(WI_ConvarChange); + Weapon_hLimitTier2.AddChangeHook(WI_ConvarChange); + Weapon_hLimitTier2_Safehouse.AddChangeHook(WI_ConvarChange); + Weapon_hReplaceStartKits.AddChangeHook(WI_ConvarChange); + Weapon_hReplaceFinaleKits.AddChangeHook(WI_ConvarChange); + Weapon_hRemoveLaserSight.AddChangeHook(WI_ConvarChange); + Weapon_hRemoveExtraItems.AddChangeHook(WI_ConvarChange); +} + +static void ConVarsInType() +{ + Weapon_bReplaceTier2 = Weapon_hReplaceTier2.BoolValue; + Weapon_bReplaceTier2_Finale = Weapon_hReplaceTier2_Finale.BoolValue; + Weapon_bReplaceTier2_All = Weapon_hReplaceTier2_All.BoolValue; + Weapon_bLimitTier2 = Weapon_hLimitTier2.BoolValue; + Weapon_bLimitTier2_Safehouse = Weapon_hLimitTier2_Safehouse.BoolValue; + Weapon_bReplaceStartKits = Weapon_hReplaceStartKits.BoolValue; + Weapon_bReplaceFinaleKits = Weapon_hReplaceFinaleKits.BoolValue; + Weapon_bRemoveLaserSight = Weapon_hRemoveLaserSight.BoolValue; + Weapon_bRemoveExtraItems = Weapon_hRemoveExtraItems.BoolValue; +} + +public void WI_ConvarChange(ConVar hConVar, const char[] sOldValue, const char[] sNewValue) +{ + for (int index = FIRST_WEAPON; index < NUM_OF_WEAPONS; index++) { + if (Weapon_hConvar[index] == null) { + continue; + } + + Weapon_bConvar[index] = Weapon_hConvar[index].BoolValue; + } + + ConVarsInType(); +} + +//================================================ +// GetWeaponIndex( iEntity, const String:sEntityClassName[128] ) +//================================================ +// Searches the weapon index for the given entity +// class +static int WI_GetWeaponIndex(int iEntity, const char[] sEntityClassName) +{ + //------------------------------------------------ + // Check for weapon in class name + //------------------------------------------------ + // If the class name doesn't contain weapon at all + // we don't need to loop thourgh with this entity + // Return false + + if (StrContains(sEntityClassName, "weapon") == -1) { + return WEAPON_NULL_INDEX; + } + +#if (DEBUG_WI) + LogMessage("[%s] GetWeaponIndex( iEntity %i sEntityClassName \"%s\" )", WI_MODULE_NAME, iEntity, sEntityClassName); + LogMessage("[%s] {", WI_MODULE_NAME); +#endif + + //------------------------------------------------ + // Check class name + //------------------------------------------------ + // If the class name is weapon_spawn we got a + // dynamic spawn and as such read the weapon id + // for detimernation of the weapon index + + int WeaponIndex; + bool bFoundIndex = false; + + if (strcmp(sEntityClassName, "weapon_spawn") == 0) { + int WepID = GetEntProp(iEntity, Prop_Send, "m_weaponID"); + + #if (DEBUG_WI) + LogMessage("[%s] Dynamic weapon spawn, weaponID %i", WI_MODULE_NAME, WepID); + #endif + + for (WeaponIndex = FIRST_WEAPON; WeaponIndex < NUM_OF_WEAPONS; WeaponIndex++) { + if (Weapon_Attributes[WeaponIndex][WeaponID] != WepID) { + continue; + } + + #if (DEBUG_WI) + LogMessage("[%s] Weapon WeaponIndex %i", WI_MODULE_NAME, WeaponIndex); + #endif + + bFoundIndex = true; + break; + } + } + //------------------------------------------------ + // Check static spawns + //------------------------------------------------ + // Otherwise loop through the weapon index for + // static classes + // If we got a match we know the index + else { + char sBuffer[MAX_ENTITY_NAME_LENGTH]; + for (WeaponIndex = FIRST_WEAPON; WeaponIndex < NUM_OF_WEAPONS; WeaponIndex++) { + if (strlen(Weapon_Spawns[WeaponIndex]) < 1) { + continue; + } + + Format(sBuffer, sizeof(sBuffer), "%s%s%s", SPAWN_PREFIX, Weapon_Spawns[WeaponIndex], SPAWN_SURFIX); + + if (strcmp(sEntityClassName, sBuffer) != 0) { + continue; + } + + #if (DEBUG_WI) + LogMessage("[%s] Static spawn, weapon WeaponIndex %i", WI_MODULE_NAME, WeaponIndex); + #endif + + bFoundIndex = true; + break; + } + } + + //------------------------------------------------ + // Check index + //------------------------------------------------ + // If we didn't find the index, return false + + if (!bFoundIndex) { + #if (DEBUG_WI) + LogMessage("[%s] Not found in weapon index", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return WEAPON_NULL_INDEX; + } + +#if (DEBUG_WI) + LogMessage("[%s] }", WI_MODULE_NAME); +#endif + + return WeaponIndex; +} + +//================================================ +// IsStatic( iEntity, iWeaponIndex ) +//================================================ +// Checks if the given entity with matching weapon +// index is a static spawn + +static bool WI_IsStatic(int iEntity, int iWeaponIndex) +{ + if (strlen(Weapon_Spawns[iWeaponIndex]) < 1) { + return false; + } + + char sEntityClassName[MAX_ENTITY_NAME_LENGTH], sBuffer[MAX_ENTITY_NAME_LENGTH]; + + GetEdictClassname(iEntity, sEntityClassName, sizeof(sEntityClassName)); + Format(sBuffer, sizeof(sBuffer), "%s%s%s", SPAWN_PREFIX, Weapon_Spawns[iWeaponIndex], SPAWN_SURFIX); + + if (strcmp(sEntityClassName, sBuffer) != 0) { + return false; + } + + // This is to prevent crashing + // Some static spawns doesn't have a model as we just wish to remove them + if (strlen(Weapon_Models[iWeaponIndex]) < 1) { + return false; + } + + return true; +} + +//================================================ +// ReplaceWeapon( iEntity, iWeaponIndex, bool:bSpawnerEvent ) +//================================================ +// Takes care of handling weapon entities, +// killing, replacing, and updateing. + +static void WI_ReplaceWeapon(int iEntity, int iWeaponIndex, bool bSpawnerEvent = false) +{ + #if (DEBUG_WI) + LogMessage("[%s] ReplaceWeapon( iEntity %i, iWeaponIndex %i, bSpawnerEvent %b )", WI_MODULE_NAME, iEntity, iWeaponIndex, bSpawnerEvent); + LogMessage("[%s] {", WI_MODULE_NAME); + #endif + + //------------------------------------------------ + // Removal of weapons + //------------------------------------------------ + // Checks if the replacement index is equal to -1 + // (WEAPON_REMOVE_INDEX) + // If so, check the cvar boolean and kill the + // weapon + + if (!bSpawnerEvent + && Weapon_Attributes[iWeaponIndex][ReplacementIndex] == WEAPON_REMOVE_INDEX + && Weapon_bConvar[iWeaponIndex] + ) { + KillEntity(iEntity); + + #if (DEBUG_WI) + LogMessage("[%s] Killing weapon as requested...", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + //------------------------------------------------ + // Replacement of static weapons + //------------------------------------------------ + // Replaces all weapon_*weaponname*_spawn with + // weapon_spawn and the old weapon ID + + char sModelBuffer[PLATFORM_MAX_PATH]; + float fOrigin[3], fRotation[3]; + + if (!bSpawnerEvent + && WI_IsStatic(iEntity, iWeaponIndex) + && (Weapon_Attributes[iWeaponIndex][WeaponID] != WEAPON_NULL_INDEX) + ) { + GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fOrigin); + GetEntPropVector(iEntity, Prop_Send, "m_angRotation", fRotation); + KillEntity(iEntity); + + iEntity = CreateEntityByName("weapon_spawn"); + SetEntProp(iEntity, Prop_Send, "m_weaponID", Weapon_Attributes[iWeaponIndex][WeaponID]); + + Format(sModelBuffer, sizeof(sModelBuffer), "%s%s%s", MODEL_PREFIX, Weapon_Models[iWeaponIndex], MODEL_SURFIX); + SetEntityModel(iEntity, sModelBuffer); + + TeleportEntity(iEntity, fOrigin, fRotation, NULL_VECTOR); + DispatchKeyValue(iEntity, "count", "5"); + DispatchSpawn(iEntity); + SetEntityMoveType(iEntity, MOVETYPE_NONE); + + #if (DEBUG_WI) + LogMessage("[%s] Replacing static spawn with weapon_spawn, new iEntity %i, weaponID %i, model \"%s\"", \ + WI_MODULE_NAME, iEntity, Weapon_Attributes[iWeaponIndex][WeaponID], sModelBuffer); + #endif + } + + //------------------------------------------------ + // Replace Weapons + //------------------------------------------------ + // Replace weapons that needs to be done so + // This is to replace CSS weapons, but can be + // adjusted to fit with any weapon + + if ((!bSpawnerEvent + && Weapon_Attributes[iWeaponIndex][ReplacementIndex] != WEAPON_NULL_INDEX + || Weapon_Attributes[iWeaponIndex][ReplacementIndex] != WEAPON_REMOVE_INDEX) + && Weapon_bConvar[iWeaponIndex] + ) { + iWeaponIndex = Weapon_Attributes[iWeaponIndex][ReplacementIndex]; + SetEntProp(iEntity, Prop_Send, "m_weaponID", Weapon_Attributes[iWeaponIndex][WeaponID]); + Format(sModelBuffer, sizeof(sModelBuffer), "%s%s%s", MODEL_PREFIX, Weapon_Models[iWeaponIndex], MODEL_SURFIX); + SetEntityModel(iEntity, sModelBuffer); + + #if (DEBUG_WI) + LogMessage("[%s] Following replacement index, new weaponID %i, new model \"%s\"", WI_MODULE_NAME, iWeaponIndex, sModelBuffer); + #endif + } + + //------------------------------------------------ + // Check for tier 1 equivalent + //------------------------------------------------ + // Check the current weapon index for a tier 1 + // equivalent + + if (Weapon_Attributes[iWeaponIndex][Tier1EquivalentIndex] == WEAPON_NULL_INDEX) { + #if (DEBUG_WI) + LogMessage("[%s] No tier 1 equivalent, no need to proceed", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + //------------------------------------------------ + // Check location + //------------------------------------------------ + // Check the location of the weapon, to see if its + // within a saferoom + + bool bIsInSaferoom = false; + GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fOrigin); + + // Within start safe room + if (!Weapon_bReplaceTier2_All && IsVersus()) { + if (GetVectorDistance(Weapon_fMapOrigin_Start, fOrigin) > Weapon_fMapDist_StartExtra + && GetVectorDistance(Weapon_fMapOrigin_End, fOrigin) > Weapon_fMapDist_End + ) { + #if (DEBUG_WI) + LogMessage("[%s] Weapon is outside of a saferoom", WI_MODULE_NAME); + #endif + + if (!bSpawnerEvent) { + #if (DEBUG_WI) + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + return; + } + } else { + #if (DEBUG_WI) + LogMessage("[%s] Weapon is inside a saferoom", WI_MODULE_NAME); + #endif + bIsInSaferoom = true; + } + } + + //------------------------------------------------ + // Check tier 2 replacement booleans + //------------------------------------------------ + // Check and see if the plugin is set to replace + // tier 2 weapons + // One for non-finale maps and one for finales + + if (!Weapon_bReplaceTier2_All) { + if (!bSpawnerEvent) { + if ((!Weapon_bReplaceTier2 && !L4D_IsMissionFinalMap()) || (!Weapon_bReplaceTier2_Finale && L4D_IsMissionFinalMap())) { + #if (DEBUG_WI) + LogMessage("[%s] We do not want to replace weapons, IsMapFinale %b", WI_MODULE_NAME, L4D_IsMissionFinalMap()); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + } else { + if ((!Weapon_bLimitTier2 && !bIsInSaferoom) || (!Weapon_bLimitTier2_Safehouse && bIsInSaferoom)) { + #if (DEBUG_WI) + LogMessage("[%s] We do not want to replace weapons, bLimitTier2 %b, bLimitTier2_Saferoom %b, bIsInSaferoom %b", \ + WI_MODULE_NAME, Weapon_bLimitTier2, Weapon_bLimitTier2_Safehouse, bIsInSaferoom); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + } + } +#if (DEBUG_WI) + else { + LogMessage("[%s] bReplaceTier2_All %b", WI_MODULE_NAME, Weapon_bReplaceTier2_All); + } +#endif + + //------------------------------------------------ + // Replace tier 2 weapon + //------------------------------------------------ + // And lastly after all these steps, this is where + // the magic happens + // Replace the weapon with its tier 1 equivalent + // and update the model + + iWeaponIndex = Weapon_Attributes[iWeaponIndex][Tier1EquivalentIndex]; + SetEntProp(iEntity, Prop_Send, "m_weaponID", Weapon_Attributes[iWeaponIndex][WeaponID]); + Format(sModelBuffer, sizeof(sModelBuffer), "%s%s%s", MODEL_PREFIX, Weapon_Models[iWeaponIndex], MODEL_SURFIX); + SetEntityModel(iEntity, sModelBuffer); + +#if (DEBUG_WI) + LogMessage("[%s] Replacing Tier 2, new WeaponID %i, model \"%s\"", WI_MODULE_NAME, Weapon_Attributes[iWeaponIndex][WeaponID], sModelBuffer); + LogMessage("[%s] }", WI_MODULE_NAME); +#endif +} + +//================================================ +// ReplaceExtra( iEntity, iWeaponIndex ) +//================================================ +// Takes care of handling extra entities, +// killing, replacing, and updateing. + +static void WI_ReplaceExtra(int iEntity, int iWeaponIndex) +{ +#if (DEBUG_WI) + LogMessage("[%s] ReplaceExtra( iEntity %i, iWeaponIndex %i )", WI_MODULE_NAME, iEntity, iWeaponIndex); + LogMessage("[%s] {", WI_MODULE_NAME); +#endif + + //------------------------------------------------ + // Removal of extras + //------------------------------------------------ + // Checks if the replacement index is equal to -1 + // (WEAPON_REMOVE_INDEX) + // If so, check the cvar boolean and kill the + // weapon, minus medkits as these needs special + // care + + if (Weapon_Attributes[iWeaponIndex][ReplacementIndex] == WEAPON_REMOVE_INDEX + && Weapon_bConvar[iWeaponIndex] + && iWeaponIndex != WEAPON_FIRST_AID_KIT_INDEX + ) { + KillEntity(iEntity); + + #if (DEBUG_WI) + LogMessage("[%s] Killing weapon as requested...", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + //------------------------------------------------ + // Check entity + //------------------------------------------------ + // Stop removing extra items that are protected + // (medkits converted to pain pills) + for (int Index = 0; Index < WEAPON_NUMBER_OF_START_KITS; Index++) { + if (Weapon_iKitEntity[Index] == iEntity) { + #if (DEBUG_WI) + LogMessage("[%s] Start kit found, save entity", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + } + + //------------------------------------------------ + // Check location + //------------------------------------------------ + // If the item is within the end safe room and its + // not finale + // OR + // If the items is within start safe room, and it + // is not a first aid kit + // Remove the item + float fOrigin[3]; + GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", fOrigin); + + bool bIsInStartSaferoom = false, bIsInStartSaferoomExtra = false; + bool bIsInEndSaferoom = false, bIsInFinaleArea = false; + + float fStartDistance = GetVectorDistance(Weapon_fMapOrigin_Start, fOrigin); + + if (fStartDistance <= Weapon_fMapDist_Start) { + bIsInStartSaferoom = true; + bIsInStartSaferoomExtra = true; + } else if (fStartDistance <= Weapon_fMapDist_StartExtra) { + bIsInStartSaferoomExtra = true; + } else if (GetVectorDistance(Weapon_fMapOrigin_End, fOrigin) <= Weapon_fMapDist_End) { + bIsInFinaleArea = (L4D_IsMissionFinalMap()); + } + + if (Weapon_bRemoveExtraItems && + (bIsInEndSaferoom || (bIsInStartSaferoomExtra && iWeaponIndex != WEAPON_FIRST_AID_KIT_INDEX)) + ) { + KillEntity(iEntity); + + #if (DEBUG_WI) + LogMessage("[%s] Extra item is within a safe room, killing...", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + //------------------------------------------------ + // Check for medkit + //------------------------------------------------ + // No need to go on if it is not a medkit + + if (iWeaponIndex != WEAPON_FIRST_AID_KIT_INDEX) { + #if (DEBUG_WI) + LogMessage("[%s] Not a medkit and not inside any saferoom, no need to go on", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + //------------------------------------------------ + // Check location of medkit + //------------------------------------------------ + // If its outside the start safe room we assume + // it is a static medkit and it needs removal + + if (Weapon_bConvar[iWeaponIndex] && !bIsInStartSaferoom && !bIsInFinaleArea) { + KillEntity(iEntity); + + #if (DEBUG_WI) + LogMessage("[%s] Static medkit outside saferoom and finale, killing...", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + if (Weapon_iKitCount >= WEAPON_NUMBER_OF_START_KITS && bIsInStartSaferoom) { + KillEntity(iEntity); + + #if (DEBUG_WI) + LogMessage("[%s] More than 4 saferoom medkits found, killing entity...", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + + return; + } + + float fRotation[3]; + char sSpawnBuffer[MAX_ENTITY_NAME_LENGTH]; + + if (bIsInStartSaferoom && Weapon_bReplaceStartKits) { + GetEntPropVector(iEntity, Prop_Send, "m_angRotation", fRotation); + + KillEntity(iEntity); + + Format(sSpawnBuffer, sizeof(sSpawnBuffer), "%s%s%s", SPAWN_PREFIX, Weapon_Spawns[WEAPON_PAIN_PILLS_INDEX], SPAWN_SURFIX); + + iEntity = CreateEntityByName(sSpawnBuffer); + TeleportEntity(iEntity, fOrigin, fRotation, NULL_VECTOR); + DispatchSpawn(iEntity); + SetEntityMoveType(iEntity, MOVETYPE_NONE); + + #if (DEBUG_WI) + LogMessage("[%s] Replacing start medkit with pills", WI_MODULE_NAME); + #endif + } else if (bIsInFinaleArea && Weapon_bReplaceFinaleKits) { + GetEntPropVector(iEntity, Prop_Send, "m_angRotation", fRotation); + + KillEntity(iEntity); + + Format(sSpawnBuffer, sizeof(sSpawnBuffer), "%s%s%s", SPAWN_PREFIX, Weapon_Spawns[WEAPON_PAIN_PILLS_INDEX], SPAWN_SURFIX); + + iEntity = CreateEntityByName(sSpawnBuffer); + TeleportEntity(iEntity, fOrigin, fRotation, NULL_VECTOR); + DispatchSpawn(iEntity); + SetEntityMoveType(iEntity, MOVETYPE_NONE); + + #if (DEBUG_WI) + LogMessage("[%s] Replacing finale medkit with pills", WI_MODULE_NAME); + #endif + } + + if (bIsInStartSaferoom) { + Weapon_iKitEntity[Weapon_iKitCount++] = iEntity; + + #if (DEBUG_WI) + LogMessage("[%s] Start medkit added to array", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); + #endif + } +} + +//================================================ +// PrecacheModels +//================================================ +// Loops through all the models and precache the +// ones we need +static void WI_PrecacheModels() +{ + char ModelBuffer[PLATFORM_MAX_PATH]; + for (int index = FIRST_WEAPON; index <= LAST_WEAPON; index++) { + if (strlen(Weapon_Models[index]) == 0) { + continue; + } + + Format(ModelBuffer, sizeof(ModelBuffer), "%s%s%s", MODEL_PREFIX, Weapon_Models[index], MODEL_SURFIX); + + if (IsModelPrecached(ModelBuffer)) { + continue; + } + + PrecacheModel(ModelBuffer); + + #if (DEBUG_WI) + LogMessage("[%s] Model precached: %s", WI_MODULE_NAME, ModelBuffer); + #endif + } +} + +//================================================ +// GetMapInfo +//================================================ +// Updates the global map variables if needed +static void WI_GetMapInfo() +{ + if (!Weapon_bUpdateMapInfo/* || !FindMapId() */) { + return; + } + + Weapon_fMapOrigin_Start[0] = GetMapStartOriginX(); + Weapon_fMapOrigin_Start[1] = GetMapStartOriginY(); + Weapon_fMapOrigin_Start[2] = GetMapStartOriginZ(); + Weapon_fMapOrigin_End[0] = GetMapEndOriginX(); + Weapon_fMapOrigin_End[1] = GetMapEndOriginY(); + Weapon_fMapOrigin_End[2] = GetMapEndOriginZ(); + Weapon_fMapDist_Start = GetMapStartDist(); + Weapon_fMapDist_StartExtra = GetMapStartExtraDist(); + Weapon_fMapDist_End = GetMapEndDist(); + + Weapon_bUpdateMapInfo = false; +} + +public void WI_RoundStart_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + CreateTimer(0.3, WI_RoundStartLoop, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public void WI_RoundEnd_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + Weapon_bUpdateMapInfo = true; +} + +public Action WI_RoundStartLoop(Handle hTimer) +{ + if (!IsPluginEnabled()) { + return Plugin_Stop; + } + + WI_GetMapInfo(); + + if (Weapon_bUpdateMapInfo) { + return Plugin_Stop; + } + + WI_PrecacheModels(); + +#if (DEBUG_WI) + LogMessage("[%s] Round Start Loop( )", WI_MODULE_NAME); + LogMessage("[%s] {", WI_MODULE_NAME); +#endif + + for (int KitIndex = 0; KitIndex < WEAPON_NUMBER_OF_START_KITS; KitIndex++) { + Weapon_iKitEntity[KitIndex] = 0; + } + + Weapon_iKitCount = 0; + + char entclass[MAX_ENTITY_NAME_LENGTH]; + int iEntity, iWeaponIndex, entcount = GetEntityCount(); + + for (iEntity = (MaxClients + 1); iEntity <= entcount; iEntity++) { + if (!IsValidEdict(iEntity)) { + continue; + } + + GetEdictClassname(iEntity, entclass, sizeof(entclass)); + + iWeaponIndex = WI_GetWeaponIndex(iEntity, entclass); + if (iWeaponIndex != WEAPON_NULL_INDEX) { + if (iWeaponIndex <= LAST_WEAPON) { + WI_ReplaceWeapon(iEntity, iWeaponIndex); + } else { + WI_ReplaceExtra(iEntity, iWeaponIndex); + } + } + + if (Weapon_bRemoveLaserSight && StrContains(entclass, "upgrade_laser_sight") != -1) { + KillEntity(iEntity); + + #if (DEBUG_WI) + LogMessage("[%s] Killing laser sight...", WI_MODULE_NAME); + #endif + + continue; + } + } + +#if (DEBUG_WI) + LogMessage("[%s] Round Start Loop End", WI_MODULE_NAME); + LogMessage("[%s] }", WI_MODULE_NAME); +#endif + + return Plugin_Stop; +} + +public void WI_SpawnerGiveItem_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + if (!IsPluginEnabled()) { + return; + } + + int iEntity = hEvent.GetInt("spawner"); + + char sEntityClassName[MAX_ENTITY_NAME_LENGTH]; + GetEdictClassname(iEntity, sEntityClassName, sizeof(sEntityClassName)); + + int iWeaponIndex = WI_GetWeaponIndex(iEntity, sEntityClassName); + if (iWeaponIndex == WEAPON_NULL_INDEX) { + return; + } + + WI_ReplaceWeapon(iEntity, iWeaponIndex, true); +} diff --git a/addons/sourcemod/scripting/confoglcompmod/includes/configs.sp b/addons/sourcemod/scripting/confoglcompmod/includes/configs.sp new file mode 100644 index 000000000..a4810e170 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/includes/configs.sp @@ -0,0 +1,176 @@ +#if defined __confogl_configs_included + #endinput +#endif +#define __confogl_configs_included + +#define CONFIGS_MODULE_NAME "Configs" + +static const char + customCfgDir[] = "cfgogl"; + +static char + DirSeparator = '\0', + configsPath[PLATFORM_MAX_PATH] = "\0", + cfgPath[PLATFORM_MAX_PATH] = "\0", + customCfgPath[PLATFORM_MAX_PATH] = "\0"; + +static ConVar + hCustomConfig = null; + +void Configs_APL() +{ + CreateNative("LGO_BuildConfigPath", _native_BuildConfigPath); + CreateNative("LGO_ExecuteConfigCfg", _native_ExecConfigCfg); +} + +void Configs_OnModuleStart() +{ + InitPaths(); + + hCustomConfig = CreateConVarEx("customcfg", "", "DONT TOUCH THIS CVAR! This is more magic bullshit!", FCVAR_DONTRECORD|FCVAR_UNLOGGED); + + char cfgString[PLATFORM_MAX_PATH]; + hCustomConfig.GetString(cfgString, sizeof(cfgString)); + SetCustomCfg(cfgString); + + hCustomConfig.RestoreDefault(); +} + +static void InitPaths() +{ + BuildPath(Path_SM, configsPath, sizeof(configsPath), "configs/confogl/"); + BuildPath(Path_SM, cfgPath, sizeof(cfgPath), "../../cfg/"); + + DirSeparator = cfgPath[(strlen(cfgPath) - 1)]; +} + +bool SetCustomCfg(const char[] cfgname) +{ + if (!strlen(cfgname)) { + customCfgPath[0] = 0; + hCustomConfig.RestoreDefault(); + + if (IsDebugEnabled()) { + LogMessage("[%s] Custom Config Path Reset - Using Default", CONFIGS_MODULE_NAME); + } + + return true; + } + + Format(customCfgPath, sizeof(customCfgPath), "%s%s%c%s", cfgPath, customCfgDir, DirSeparator, cfgname); + if (!DirExists(customCfgPath)) { + Debug_LogError(CONFIGS_MODULE_NAME, "Custom config directory %s does not exist!", customCfgPath); + // Revert customCfgPath + customCfgPath[0] = 0; + return false; + } + + int thislen = strlen(customCfgPath); + if ((thislen + 1) < sizeof(customCfgPath)) { + customCfgPath[thislen] = DirSeparator; + customCfgPath[(thislen + 1)] = 0; + } else { + Debug_LogError(CONFIGS_MODULE_NAME, "Custom config directory %s path too long!", customCfgPath); + customCfgPath[0] = 0; + return false; + } + + hCustomConfig.SetString(cfgname); + + return true; +} + +void BuildConfigPath(char[] buffer, const int maxlength, const char[] sFileName) +{ + if (customCfgPath[0]) { + Format(buffer, maxlength, "%s%s", customCfgPath, sFileName); + + if (FileExists(buffer)) { + if (IsDebugEnabled()) { + LogMessage("[%s] Built custom config path: %s", CONFIGS_MODULE_NAME, buffer); + } + + return; + } else { + if (IsDebugEnabled()) { + LogMessage("[%s] Custom config not available: %s", CONFIGS_MODULE_NAME, buffer); + } + } + } + + Format(buffer, maxlength, "%s%s", configsPath, sFileName); + if (IsDebugEnabled()) { + LogMessage("[%s] Built default config path: %s", CONFIGS_MODULE_NAME, buffer); + } +} + +void ExecuteCfg(const char[] sFileName) +{ + if (strlen(sFileName) == 0) { + return; + } + + char sFilePath[PLATFORM_MAX_PATH]; + + if (customCfgPath[0]) { + Format(sFilePath, sizeof(sFilePath), "%s%s", customCfgPath, sFileName); + + if (FileExists(sFilePath)) { + if (IsDebugEnabled()) { + LogMessage("[%s] Executing custom cfg file %s", CONFIGS_MODULE_NAME, sFilePath); + } + + ServerCommand("exec %s%s", customCfgPath[strlen(cfgPath)], sFileName); + + return; + } else { + if (IsDebugEnabled()) { + LogMessage("[%s] Couldn't find custom cfg file %s, trying default", CONFIGS_MODULE_NAME, sFilePath); + } + } + } + + Format(sFilePath, sizeof(sFilePath), "%s%s", cfgPath, sFileName); + + if (FileExists(sFilePath)) { + if (IsDebugEnabled()) { + LogMessage("[%s] Executing default config %s", CONFIGS_MODULE_NAME, sFilePath); + } + + ServerCommand("exec %s", sFileName); + } else { + Debug_LogError(CONFIGS_MODULE_NAME, "Could not execute server config \"%s\", file not found", sFilePath); + } +} + +public int _native_BuildConfigPath(Handle plugin, int numParams) +{ + int iLen = 0; + GetNativeStringLength(3, iLen); + + int iNewLen = iLen + 1; + char[] sFileName = new char[iNewLen]; + GetNativeString(3, sFileName, iNewLen); + + iLen = GetNativeCell(2); + + char[] sBuf = new char[iLen]; + BuildConfigPath(sBuf, iLen, sFileName); + SetNativeString(1, sBuf, iLen); + + return 1; +} + +public int _native_ExecConfigCfg(Handle plugin, int numParams) +{ + int iLen = 0; + GetNativeStringLength(1, iLen); + + int iNewLen = iLen + 1; + char[] sFileName = new char[iNewLen]; + GetNativeString(1, sFileName, iNewLen); + + ExecuteCfg(sFileName); + + return 1; +} diff --git a/addons/sourcemod/scripting/confoglcompmod/includes/constants.sp b/addons/sourcemod/scripting/confoglcompmod/includes/constants.sp new file mode 100644 index 000000000..6ee8fc2ed --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/includes/constants.sp @@ -0,0 +1,115 @@ +#if defined __confogl_constants_included + #endinput +#endif +#define __confogl_constants_included + +#define MAX_ENTITY_NAME_LENGTH 64 + +#define NUM_OF_SURVIVORS 4 + +#define START_SAFEROOM (1 << 0) +#define END_SAFEROOM (1 << 1) + +#define SPAWNFLAG_READY 0 +#define SPAWNFLAG_CANSPAWN (0 << 0) +#define SPAWNFLAG_DISABLED (1 << 0) +#define SPAWNFLAG_WAITFORSURVIVORS (1 << 1) +#define SPAWNFLAG_WAITFORFINALE (1 << 2) +#define SPAWNFLAG_WAITFORTANKTODIE (1 << 3) +#define SPAWNFLAG_SURVIVORESCAPED (1 << 4) +#define SPAWNFLAG_DIRECTORTIMEOUT (1 << 5) +#define SPAWNFLAG_WAITFORNEXTWAVE (1 << 6) +#define SPAWNFLAG_CANBESEEN (1 << 7) +#define SPAWNFLAG_TOOCLOSE (1 << 8) +#define SPAWNFLAG_RESTRICTEDAREA (1 << 9) +#define SPAWNFLAG_BLOCKED (1 << 10) + +enum +{ + L4D2Team_None = 0, + L4D2Team_Spectator, + L4D2Team_Survivor, + L4D2Team_Infected, + + L4D2Team_Size //4 size +}; + +enum +{ + L4D2Infected_Common = 0, + L4D2Infected_Smoker = 1, + L4D2Infected_Boomer, + L4D2Infected_Hunter, + L4D2Infected_Spitter, + L4D2Infected_Jockey, + L4D2Infected_Charger, + L4D2Infected_Witch, + L4D2Infected_Tank, + L4D2Infected_Survivor, + + L4D2Infected_Size //10 size +}; + +enum +{ + L4D2WeaponSlot_Primary = 0, + L4D2WeaponSlot_Secondary, + L4D2WeaponSlot_Throwable, + L4D2WeaponSlot_HeavyHealthItem, + L4D2WeaponSlot_LightHealthItem, + + L4D2WeaponSlot_Size //5 size +}; + +enum /*WeaponIDs*/ +{ + WEPID_PISTOL = 1, + WEPID_SMG, // 2 + WEPID_PUMPSHOTGUN, // 3 + WEPID_AUTOSHOTGUN, // 4 + WEPID_RIFLE, // 5 + WEPID_HUNTING_RIFLE, // 6 + WEPID_SMG_SILENCED, // 7 + WEPID_SHOTGUN_CHROME, // 8 + WEPID_RIFLE_DESERT, // 9 + WEPID_SNIPER_MILITARY, // 10 + WEPID_SHOTGUN_SPAS, // 11 + WEPID_FIRST_AID_KIT, // 12 + WEPID_MOLOTOV, // 13 + WEPID_PIPE_BOMB, // 14 + WEPID_PAIN_PILLS, // 15 + WEPID_GASCAN, // 16 + WEPID_PROPANE_TANK, // 17 + WEPID_AIR_CANISTER, // 18 + WEPID_CHAINSAW = 20, + WEPID_GRENADE_LAUNCHER, // 21 + WEPID_ADRENALINE = 23, + WEPID_DEFIBRILLATOR, // 24 + WEPID_VOMITJAR, // 25 + WEPID_RIFLE_AK47, // 26 + WEPID_GNOME_CHOMPSKI, // 27 + WEPID_COLA_BOTTLES, // 28 + WEPID_FIREWORKS_BOX, // 29 + WEPID_INCENDIARY_AMMO, // 30 + WEPID_FRAG_AMMO, // 31 + WEPID_PISTOL_MAGNUM, // 32 + WEPID_SMG_MP5, // 33 + WEPID_RIFLE_SG552, // 34 + WEPID_SNIPER_AWP, // 35 + WEPID_SNIPER_SCOUT, // 36 + WEPID_RIFLE_M60, // 37 + + WEPID_SIZE +}; + +/*stock const char g_sTeamName[8][] = +{ + "Spectator", + "" , + "Survivor", + "Infected", + "", + "Infected", + "Survivors", + "Infected" +};*/ diff --git a/addons/sourcemod/scripting/confoglcompmod/includes/customtags.sp b/addons/sourcemod/scripting/confoglcompmod/includes/customtags.sp new file mode 100644 index 000000000..df0d0cc7f --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/includes/customtags.sp @@ -0,0 +1,105 @@ +#if defined __confogl_customtags_included + #endinput +#endif +#define __confogl_customtags_included + +// COPYRIGHT PSYCHONIC +// USED WITH PERMISSION + +#define CT_MODULE_NAME "CustomTags" + +#define SV_TAG_SIZE 64 + +static stock bool + are_tags_hooked = false, + ignore_next_change = false; + +static stock ConVar + sv_tags = null; + +static stock ArrayList + custom_tags = null; + +void CT_OnModuleStart() +{ + custom_tags = new ArrayList(ByteCountToCells(SV_TAG_SIZE)); + + sv_tags = FindConVar("sv_tags"); +} + +stock void AddCustomServerTag(const char[] tag) +{ + if (custom_tags.FindString(tag) == -1) { + custom_tags.PushString(tag); + } + + char current_tags[SV_TAG_SIZE]; + sv_tags.GetString(current_tags, sizeof(current_tags)); + + if (StrContains(current_tags, tag) > -1) { + // already have tag + return; + } + + char new_tags[SV_TAG_SIZE]; + Format(new_tags, sizeof(new_tags), "%s%s%s", current_tags, (current_tags[0] != 0) ? "," : "", tag); + + int flags = sv_tags.Flags; + sv_tags.Flags = flags & ~FCVAR_NOTIFY; + + ignore_next_change = true; + sv_tags.SetString(new_tags); + ignore_next_change = false; + + sv_tags.Flags = flags; + + if (!are_tags_hooked) { + sv_tags.AddChangeHook(OnTagsChanged); + are_tags_hooked = true; + } +} + +stock void RemoveCustomServerTag(const char[] tag) +{ + int idx = custom_tags.FindString(tag); + if (idx > -1) { + custom_tags.Erase(idx); + } + + char current_tags[SV_TAG_SIZE]; + sv_tags.GetString(current_tags, sizeof(current_tags)); + + if (StrContains(current_tags, tag) == -1) { + // tag isn't on here, just bug out + return; + } + + ReplaceString(current_tags, sizeof(current_tags), tag, ""); + ReplaceString(current_tags, sizeof(current_tags), ",,", ""); + + int flags = sv_tags.Flags; + sv_tags.Flags = flags & ~FCVAR_NOTIFY; + + ignore_next_change = true; + sv_tags.SetString(current_tags); + ignore_next_change = false; + + sv_tags.Flags = flags; +} + +public void OnTagsChanged(ConVar hConvar, const char[] sOldValue, const char[] sNewValue) +{ + if (ignore_next_change) { + // we fired this callback, no need to reapply tags + return; + } + + // reapply each custom tag + char tag[SV_TAG_SIZE]; + int iSize = custom_tags.Length; + + for (int i = 0; i < iSize; i++) { + custom_tags.GetString(i, tag, sizeof(tag)); + AddCustomServerTag(tag); + } +} diff --git a/addons/sourcemod/scripting/confoglcompmod/includes/debug.sp b/addons/sourcemod/scripting/confoglcompmod/includes/debug.sp new file mode 100644 index 000000000..7b6199262 --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/includes/debug.sp @@ -0,0 +1,77 @@ +#if defined __confogl_debug_included + #endinput +#endif +#define __confogl_debug_included + +#if DEBUG_ALL + #define DEBUG_DEFAULT "1" +#else + #define DEBUG_DEFAULT "0" +#endif + +static char + g_sLogAction[256]; + +static bool + g_bConfoglDebug = false; + +static ConVar + g_hCvarCustomErrorLog = null, + g_hCvarDebugConVar = null; + +void Debug_OnModuleStart() +{ + g_hCvarDebugConVar = CreateConVarEx("debug", DEBUG_DEFAULT, "Turn on Debug Logging in all Confogl Modules", _, true, 0.0, true, 1.0); + + //confogl_custom_error_logs + g_hCvarCustomErrorLog = CreateConVarEx( \ + "custom_error_logs", \ + "1", \ + "Write logs to custom error log file (0 - use sourcemod error log file, 1 - use custom error log file)", \ + _, true, 0.0, true, 1.0 \ + ); + + g_bConfoglDebug = g_hCvarDebugConVar.BoolValue; + g_hCvarDebugConVar.AddChangeHook(Debug_ConVarChange); + + char sTime[64], sBuffer[64]; + FormatTime(sTime, sizeof(sTime), "%Y%m%d"); + FormatEx(sBuffer, sizeof(sBuffer), "logs/confoglcompmod/errors_%s.log", sTime); //errors_20211201.log + BuildPath(Path_SM, g_sLogAction, sizeof(g_sLogAction), sBuffer); + + BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "logs/confoglcompmod"); + if (!DirExists(sBuffer)) { + CreateDirectory(sBuffer, 511); + } +} + +public void Debug_ConVarChange(ConVar hConvar, const char[] sOldValue, const char[] sNewValue) +{ + g_bConfoglDebug = hConvar.BoolValue; +} + +stock bool IsDebugEnabled() +{ + return (g_bConfoglDebug || DEBUG_ALL); +} + +stock void Debug_LogError(const char[] sModuleName, const char[] sMessage, any ...) +{ + static char sFormat[512]; + VFormat(sFormat, sizeof(sFormat), sMessage, 3); + + static char sMap[64]; + GetCurrentMap(sMap, sizeof(sMap)); + + Format(sFormat, sizeof(sFormat), "[%s] [%s] %s", sModuleName, sMap, sFormat); + + if (!g_hCvarCustomErrorLog.BoolValue) { + // L 12/16/2021 - 12:10:15: [confoglcompmod.smx] [CvarSettings] [c4m1_milltown_a] Could not find CVar specified (l4d2_meleecontrol_enable) + LogError(sFormat); + return; + } + + // Same as LogToFile(), except no plugin logtag is prepended. + // L 12/16/2021 - 12:11:45: [CvarSettings] [c4m1_milltown_a] Could not find CVar specified (l4d2_meleecontrol_enable) + LogToFileEx(g_sLogAction, sFormat); +} diff --git a/addons/sourcemod/scripting/confoglcompmod/includes/functions.sp b/addons/sourcemod/scripting/confoglcompmod/includes/functions.sp new file mode 100644 index 000000000..5c498dc0a --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/includes/functions.sp @@ -0,0 +1,201 @@ +#if defined __confogl_functions_included + #endinput +#endif +#define __confogl_functions_included + +#define CVAR_PREFIX "confogl_" +#define CVAR_FLAGS FCVAR_NONE +#define CVAR_PRIVATE (FCVAR_DONTRECORD|FCVAR_PROTECTED) + +static ConVar + g_hCvarMpGameMode = null, + g_hCvarPainPillsDecayRate = null; + +static bool + bIsPluginEnabled = false; + +void Fns_OnModuleStart() +{ + g_hCvarMpGameMode = FindConVar("mp_gamemode"); + g_hCvarPainPillsDecayRate = FindConVar("pain_pills_decay_rate"); +} + +stock ConVar CreateConVarEx(const char[] name, const char[] defaultValue, const char[] description = "", int flags = FCVAR_NONE, \ + bool hasMin = false, float min = 0.0, bool hasMax = false, float max = 0.0) +{ + char sBuffer[128]; + ConVar cvar = null; + + Format(sBuffer, sizeof(sBuffer), "%s%s", CVAR_PREFIX, name); + flags = flags | CVAR_FLAGS; + cvar = CreateConVar(sBuffer, defaultValue, description, flags, hasMin, min, hasMax, max); + + return cvar; +} + +stock ConVar FindConVarEx(const char[] name) +{ + char sBuffer[128]; + Format(sBuffer, sizeof(sBuffer), "%s%s", CVAR_PREFIX, name); + + return FindConVar(sBuffer); +} + +stock bool IsHumansOnServer() +{ + for (int i = 1; i <= MaxClients; i++) { + if (IsClientConnected(i) && !IsFakeClient(i)) { + return true; + } + } + + return false; +} + +stock bool IsVersus() +{ + char GameMode[32]; + g_hCvarMpGameMode.GetString(GameMode, sizeof(GameMode)); + return (StrContains(GameMode, "versus", false) != -1); +} + +/*stock bool IsScavenge() +{ + char GameMode[32]; + g_hCvarMpGameMode.GetString(GameMode, sizeof(GameMode)); + return (StrContains(GameMode, "scavenge", false) != -1); +}*/ + +stock bool IsPluginEnabled(bool bSetStatus = false, bool bStatus = false) +{ + if (bSetStatus) { + bIsPluginEnabled = bStatus; + } + + return bIsPluginEnabled; +} + +stock int GetSurvivorPermanentHealth(int client) +{ + return GetEntProp(client, Prop_Send, "m_iHealth"); +} + +stock int GetSurvivorTempHealth(int client) +{ + float fHealthBuffer = GetEntPropFloat(client, Prop_Send, "m_healthBuffer"); + float fHealthBufferDuration = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime"); + + int iTempHp = RoundToCeil(fHealthBuffer - (fHealthBufferDuration * g_hCvarPainPillsDecayRate.FloatValue)) - 1; + + return (iTempHp > 0) ? iTempHp : 0; +} + +stock int GetSurvivorIncapCount(int client) +{ + return GetEntProp(client, Prop_Send, "m_currentReviveCount"); +} + +stock bool IsSurvivor(int client) +{ + return (IsClientInGame(client) && GetClientTeam(client) == L4D2Team_Survivor); +} + +stock void ZeroVector(float vector[3]) +{ + vector = NULL_VECTOR; +} + +stock void AddToVector(float to[3], float from[3]) +{ + to[0] += from[0]; + to[1] += from[1]; + to[2] += from[2]; +} + +stock void CopyVector(float to[3], float from[3]) +{ + to = from; +} + +stock int GetURandomIntRange(int min, int max) +{ + return RoundToNearest((GetURandomFloat() * (max - min)) + min); +} + +stock void KillEntity(int iEntity) +{ +#if SOURCEMOD_V_MINOR > 8 + RemoveEntity(iEntity); +#else + AcceptEntityInput(iEntity, "Kill"); +#endif +} + +/** + * Finds the first occurrence of a pattern in another string. + * + * @param str String to search in. + * @param pattern String pattern to search for + * @param reverse False (default) to search forward, true to search + * backward. + * @return The index of the first character of the first + * occurrence of the pattern in the string, or -1 if the + * character was not found. + */ +/*stock int FindPatternInString(const char[] str, const char[] pattern, bool reverse = false) +{ + int i = 0, len = strlen(pattern); + char c = pattern[0]; + + while (i < len && (i = FindCharInString(str[i], c, reverse)) != -1) { + if (strncmp(str[i], pattern, len)) { + return i; + } + } + + return -1; +}*/ + +/** + * Counts the number of occurences of pattern in another string. + * + * @param str String to search in. + * @param pattern String pattern to search for + * @param overlap False (default) to count only non-overlapping + * occurences, true to count matches within other + * occurences. + * @return The number of occurences of the pattern in the string + */ +/*stock int CountPatternsInString(const char[] str, const char[] pattern, bool overlap = false) +{ + int off = 0, i = 0, delta = 0, cnt = 0; + int len = strlen(str); + + delta = (overlap) ? strlen(pattern) : 1; + + while (i < len && (off = FindPatternInString(str[i], pattern)) != -1) { + cnt++; + i += off + delta; + } + + return cnt; +}*/ + +/** + * Counts the number of occurences of pattern in another string. + * + * @param str String to search in. + * @param c Character to search for. + * @return The number of occurences of the pattern in the string + */ +/*stock int CountCharsInString(const char[] str, int c) +{ + int off, i, cnt, len = strlen(str); + + while (i < len && (off = FindCharInString(str[i], c)) != -1) { + cnt++; + i += off + 1; + } + + return cnt; +}*/ diff --git a/addons/sourcemod/scripting/confoglcompmod/includes/survivorindex.sp b/addons/sourcemod/scripting/confoglcompmod/includes/survivorindex.sp new file mode 100644 index 000000000..74e28e0cb --- /dev/null +++ b/addons/sourcemod/scripting/confoglcompmod/includes/survivorindex.sp @@ -0,0 +1,98 @@ +#if defined __confogl_survivor_index_included + #endinput +#endif +#define __confogl_survivor_index_included + +#define SI_MODULE_NAME "SurvivorIndex" + +static int + iSurvivorIndex[NUM_OF_SURVIVORS] = {0, ...}; + +void SI_OnModuleStart() +{ + HookEvent("round_start", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("round_end", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("player_spawn", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("player_disconnect", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("player_death", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("player_bot_replace", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("bot_player_replace", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("defibrillator_used", SI_BuildIndex_Event, EventHookMode_PostNoCopy); + HookEvent("player_team", SI_BuildIndexDelay_Event, EventHookMode_PostNoCopy); +} + +static void SI_BuildIndex() +{ + if (!IsServerProcessing() || !IsPluginEnabled()) { + return; + } + + int ifoundsurvivors = 0, character = 0; + + // Make sure kicked survivors don't freak us out. + for (int i = 0; i < NUM_OF_SURVIVORS; i++) { + iSurvivorIndex[i] = 0; + } + + for (int client = 1; client <= MaxClients; client++) { + if (ifoundsurvivors == NUM_OF_SURVIVORS) { + break; + } + + if (!IsClientInGame(client) || GetClientTeam(client) != L4D2Team_Survivor) { + continue; + } + + character = GetEntProp(client, Prop_Send, "m_survivorCharacter"); + ifoundsurvivors++; + + if (character > 3 || character < 0) { + continue; + } + + iSurvivorIndex[character] = 0; + + if (!IsPlayerAlive(client)) { + continue; + } + + iSurvivorIndex[character] = client; + } +} + +public void SI_BuildIndexDelay_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + CreateTimer(0.3, SI_BuildIndex_Timer, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action SI_BuildIndex_Timer(Handle hTimer) +{ + SI_BuildIndex(); + + return Plugin_Stop; +} + +public void SI_BuildIndex_Event(Event hEvent, const char[] sEventName, bool bDontBroadcast) +{ + SI_BuildIndex(); +} + +stock int GetSurvivorIndex(int index) +{ + if (index < 0 || index > 3) { + return 0; + } + + return iSurvivorIndex[index]; +} + +stock bool IsAnySurvivorsAlive() +{ + for (int index = 0; index < NUM_OF_SURVIVORS; index++) { + if (iSurvivorIndex[index]) { + return true; + } + } + + return false; +} diff --git a/addons/sourcemod/scripting/include/confogl.inc b/addons/sourcemod/scripting/include/confogl.inc index 064fed3fe..595b0f331 100644 --- a/addons/sourcemod/scripting/include/confogl.inc +++ b/addons/sourcemod/scripting/include/confogl.inc @@ -1,6 +1,6 @@ /* * * ============================================================================= - * Confogl.inc + * Confogl.inc * Confogl (C)2011 Confogl Team * ============================================================================= * @@ -9,10 +9,10 @@ * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License, version 3.0, as published by the * Free Software Foundation. - * + * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with @@ -28,17 +28,18 @@ * or . * */ -#if defined _confogl_Included + +#if defined _confogl_included #endinput #endif -#define _confogl_Included +#define _confogl_included /* Forwards */ /** * @brief Called when matchmode is fully loaded, before map restart. * @remarks Called just before confogl_plugins.cfg executes - * + * * @noreturn */ forward void LGO_OnMatchModeLoaded(); @@ -46,7 +47,7 @@ forward void LGO_OnMatchModeLoaded(); /** * @brief Called when matchmode is un-loaded, before map restart. * @remarks Plugins are unloaded immediately after this call finishes - * + * * @noreturn */ forward void LGO_OnMatchModeUnloaded(); @@ -56,7 +57,7 @@ forward void LGO_OnMatchModeUnloaded(); /** * @brief Tells if a confogl match is currently running * @remarks Formerly IsPluginEnabled() internally - * + * * @return True if matchmode is loaded, false otherwise */ native bool LGO_IsMatchModeLoaded(); @@ -64,7 +65,7 @@ native bool LGO_IsMatchModeLoaded(); /** * @brief Build a filepath relative to the current running config. * @remarks Should produce a path in cfg/cfgogl/CONFIG/ or addons/sourcemod/configs/confogl - * + * * @param buffer Buffer to write the path to * @param maxlength Buffer size * @param sFileName Name of the file to look for in the config @@ -75,7 +76,7 @@ native void LGO_BuildConfigPath(char[] buffer, int maxlength, const char[] sFile /** * @brief Execute a cfg file for the current config * @remarks Should execute the named .cfg in cfg/ or cfg/cfgogl/CURRENT_CONFIG/ - * + * * @param sFileName Name of the cfg file to execute * @noreturn */ @@ -84,7 +85,7 @@ native void LGO_ExecuteConfigCfg(const char[] sFileName); /** * @brief Tells if map data is available * @remarks Map data should be available when any map is loaded, after OnMapStart() - * + * * @return True if map data is available, false if it is not. */ native bool LGO_IsMapDataAvailable(); @@ -92,7 +93,7 @@ native bool LGO_IsMapDataAvailable(); /** * @brief Get an Int value from the MapInfo keyvalues for the current map with a specific key * @remarks Mapinfo keyvalues is used to store static data about maps - * + * * @param key Key to read the value from * @param defvalue Default value to return if key is not found (default 0) * @return Integer value for given key, or defvalue if key is not found @@ -102,7 +103,7 @@ native int LGO_GetMapValueInt(const char[] key, const int defvalue = 0); /** * @brief Get a Float value from the MapInfo keyvalues for the current map with a specific key * @remarks Mapinfo keyvalues is used to store static data about maps - * + * * @param key Key to read the value from * @param defvalue Default value to return if key is not found (default 0.0) * @return Float value for given key, or defvalue if key is not found @@ -112,7 +113,7 @@ native float LGO_GetMapValueFloat(const char[] key, const float defvalue = 0.0); /** * @brief Get a Vector from the MapInfo keyvalues for the current map with a specific key * @remarks Mapinfo keyvalues is used to store static data about maps - * + * * @param key Key to read the value from * @param vector Vector to store the result in * @param defvalue Default value to use if key is not found (default NULL_VECTOR) @@ -123,7 +124,7 @@ native void LGO_GetMapValueVector(const char[] key, float vector[3], const float /** * @brief Get a String from the MapInfo keyvalues for the current map with a specific key * @remarks Mapinfo keyvalues is used to store static data about maps - * + * * @param key Key to read the value from * @param value String to store the result in * @param maxlength Maximum length to write to the value String buffer @@ -135,14 +136,26 @@ native void LGO_GetMapValueString(const char[] key, char[] value, int maxlength, /** * @brief Copy a Subsection from the MapInfo keyvalues for the current map * @remarks Mapinfo keyvalues is used to store static data about maps - * + * * @param kv KeyValues Handle to copy to * @param section Name of the section to copy * @noreturn */ native void LGO_CopyMapSubsection(KeyValues kv, const char[] section); -public SharedPlugin __pl_confogl = +/** + * @brief Informs if the module 'ScoreMod' is activated now (confogl activated and scoremod activated) + * @return (bool) returns true if activated, false otherwise + */ +native bool LGO_IsScoremodEnabled(); + +/** + * @brief Returns the amount of bonus if the module 'Scoremod' is activated + * @return (int) bonus amount, or return -1 if confogl disable or scoremod disable + */ +native int LGO_GetHealthScore(); + +public SharedPlugin __pl_confogl = { name = "confogl", file = "confoglcompmod.smx", @@ -165,5 +178,7 @@ public void __pl_confogl_SetNTVOptional() MarkNativeAsOptional("LGO_GetMapValueString"); MarkNativeAsOptional("LGO_CopyMapSubsection"); MarkNativeAsOptional("LGO_IsMatchModeLoaded"); + MarkNativeAsOptional("LGO_IsScoremodEnabled"); + MarkNativeAsOptional("LGO_GetHealthScore"); } #endif