From 89d4cfbd1740ae1cbdd0b1782f6364ff885b7b98 Mon Sep 17 00:00:00 2001 From: Jackz Date: Sat, 13 Jul 2024 22:26:16 -0500 Subject: [PATCH] Split out editor from hats --- plugins/l4d2_editor.smx | Bin 0 -> 79724 bytes plugins/l4d2_hats.smx | Bin 104206 -> 46773 bytes plugins/l4d2_randomizer.smx | Bin 31221 -> 31352 bytes scripting/include/hats/editor.sp | 1024 ------- scripting/include/hats/hats.sp | 1688 ++++++------ scripting/include/hats/natives.sp | 122 - scripting/include/hats/props/base.sp | 569 ---- scripting/include/hats/props/cmd.sp | 139 - scripting/include/hats/props/db.sp | 125 - scripting/include/hats/props/menu_handlers.sp | 481 ---- scripting/include/hats/props/menu_methods.sp | 315 --- scripting/include/hats/props/methods.sp | 513 ---- scripting/include/hats_editor.inc | 155 -- scripting/l4d2_editor.sp | 591 ++++ scripting/l4d2_hats.sp | 1825 +++++-------- scripting/l4d2_randomizer.sp | 2410 ++++++++--------- 16 files changed, 3391 insertions(+), 6566 deletions(-) create mode 100644 plugins/l4d2_editor.smx delete mode 100644 scripting/include/hats/editor.sp delete mode 100644 scripting/include/hats/natives.sp delete mode 100644 scripting/include/hats/props/base.sp delete mode 100644 scripting/include/hats/props/cmd.sp delete mode 100644 scripting/include/hats/props/db.sp delete mode 100644 scripting/include/hats/props/menu_handlers.sp delete mode 100644 scripting/include/hats/props/menu_methods.sp delete mode 100644 scripting/include/hats/props/methods.sp delete mode 100644 scripting/include/hats_editor.inc create mode 100644 scripting/l4d2_editor.sp diff --git a/plugins/l4d2_editor.smx b/plugins/l4d2_editor.smx new file mode 100644 index 0000000000000000000000000000000000000000..41841d6ee00e267dbe160d48eecafa41fb93342f GIT binary patch literal 79724 zcmYhB2UL?m)2Ku5O+-L?k=_KPhoDGPnxYgb0cj$=*CZecO7BPqK~d>NdIt%;ONY=y z4?Tnga{0dd|L5MEoioqw&Q50Moi{njzM7hbMuY?eF7gC`2Tw=<_qYiF0A51Ce_#L6 z^qqtN(%YVNHzD8%2LMptGH(YFV1*n2V7g_(ej-4}?HbNo?iwHhgc1P&g17u+ga|;a z4**EqvL-t*V22U_P`G7DL1MrV0|20L%UA_s05ur^@bZ?))QAD4w`(nLxkjBBpnI#G zZv}tLvbQ?$R%D(M143@~=Ub_`<!~fUww)gp;o~?_GxA*^>ba1kF`CoVc_2A>>Yx}>p|8D^NKXwv! zZyhBZoLv4B-+$1>$?bp9$<4w2{|^3djQ_Q8ba8)cQSp? zSla}5#hnM%31p<^Jg(|GJRzDC^xW@40Wa>C)j6Hc_x#Y&w&M9DL_kDyC%@b`w%IcF z{vYk0&o2tI{x$S29p0CHwirv#T}cqGNq%Sga{=aXQT}vmvC(mBq!sli-x=1vu;}kN zf>ZR~8p@DCW}C6ovSz@uNHOWIK=>PjlElmU>7c{vT&8IwTVUXHP5O@v?y}d3)4#Np z^R?Lwi)chAG?|{J z#^gj4RcoU$|C87CNupe#wz*-EeZ<}Ma_wl53Cg=p529+HX}l@pEz?V^Gf12+(Pn#9 z6e}|EwQIYG5qQ6W|98tB`QMFbY^ZC373IFw2qAer6=`
iI7F`vTg`XVvSxAg5IlhdcDT4{}W0gX4wyk+W% z(`DLwM*m4jFOySERBfq!)FZ9`M*F(R^;OY7(TRB`CoI!{QqcWhPqne(QtZDR`&(G0cSFsw%f46k&iSZ_DP>EZ9yrdnB+Kz9*V#!?YPe)k zOe)hqwg66=xe3YXZZ1jP1CtIM^N$_(y$@o8ctI1R5j~8A(sLd*BjqanF z+u1XJQPpMwaKLKgK|$uU97}j z;)0G_Ni|S2s#T7}WCcj3HllhP`#PT{qc#?mcn0}; z@t-C)gk>jyfFB3m*&D4a0x?|0)}qIB4l%C?=Ubzoj<$ay>e5$EeGF&l%burcU2u%> z%{oksr224vQOO)L2&To$X)jm`tORRc#`PQXTBc3u!(p&#Ed+zfP+99IB|m=66cWASpPZB)Y>l zt-f-vo1#U$g=AkQ{ZzV|q|ufn)y8zG*Xt&?eX-H4QhJ$qI;uJ0Ony@7w@~iA9i06h zr4xex9hl)Kqw}DZk_2l+a(#{(Hp^g!|FbhW-El+IjX-eJL~;Pv?}SGuPrl{#ktorQ z<+9swx!iZN__FRVeSINNd1F%Xm=k*<>SV@C1 zr&04T+Agx3I>O8y;0K?1g83QAf=Oc%a>oN~1dd&w){r*dX1 zdwXn=l3v zbQ|Umw+$EqB#BWZD?qN63Q|_j27&^WBbq-0z zjrf(~cKBpI#W!1pD`mm^aa%nl#!Ia&M5E0jp~5tg?j+MoqfI5jow3h9Gr|2)pGjd~ zI^D@5;S;70A+Av&8fpmj6F=drmmjy6_PrrAUn>DMU?PF%p*zMMcf&)-?qVp_+QO4U zUUQ!C77h1Ny0bo{4B%X>V1M&Ba6)BM=QKr7!^8u~xS=D3q8`qY>^CRbI#C%rzgy<9 z7hrer$$ltQ`WYmJz_ZTc-K1ARNUDBn?jwuOJ0-rptqfmAQvPTaxlc=Qw0bEmFdNrO ziaCKV$?GJ!`U;hhn#GeCwT(Ze|AK<2+Dgy%U&QyS`jEI*#Kk??pix&du{fedS~cCB zL)m%>DTkXu(r}9!LkqrX!N8Qw9Oc1Qfq@g_+yg6S`@ovh9EC7Q@8tpIV=~G>lyzDr zSq_0u`D=nb7DYyJM0l@95J+zH-2#?;K3oNfNoZdg0Sw5E_7s2`&|g+R_vBbD)u#gD5VKhou}Ve ze5AF$$W#u>;M}8ohUo41>&T15!q^H6L7HE)7ogQ`*$cka_1RSRQ-{gd{aez3jc7Z( zG?YIyyII%BK~KZj@`teh4_STG&!o`R1Rp`X&eetW`? zXxl_O5A_NAGpWOHke<^E9_aDg@T!Q zNjcMq+SBFU5qff0N9IOsP25EEfaacQXPjh6RI{F8oRHddXTGn-qrHo3%$@=SxWqzryzsoX+s9jUbp7lyq$@OOB8^H2ktvH#2`AhKibG}|H2 z^m<)&7j$n5M7S&HYNj*z7NNOa|L}-rfSB?AK{oAY!P$`)I@h+UdwJvv`t$~ccLfz9DH`>^J0@jQDLJOk!O?9m5So%5p2 z1}W*dI6ZR)n}vALy5NuXSc@L0hDr_h(Rb!S^wC=Iu<$N0B&+~zY*8qdYM{r{fw!7x zHhNn_d|YRmq=2FX6NX%ou-TNhohF>vA9PNPGEdk$3#RNve{AJX8I=UAQ@Jkb%;Om+ zwwOF>xDRqZaFFX@0hLwa629wVYy~#DfclL{axnhM(^jzp@|I z8E7-CR6g3x8=_rZOqetPEO=Wtygn|zL$M@-eIw_0nyz%No{{94JZG2GBtdu~E)pD+ z5kDrf+S0%=hfH|b-^z{>oh17r7rC)ix6`!f%BN4dc|R)r)1U`Cy;TOJQKjtVL~e8^ zCUA4p3s-%S5OR>-e16Z=%I^?9uNre!mV9McM(kH5l{3Z{J}Ti|kH0epO*;H{BX*UO zYE~kY{CzKlmBgMly?>^KC|zJ)snpDkLP#D->Dwq4uit26=yuKI#-2jhGVPqk8m3^F z(m(Oqz8W)T9yj(%pu+Jyc=6nVa$bMJcV3^qYrw_mcK**m%#pNUqO@e8eH2tbxoJ}? z5c3yxE&-G2D|rp2kc4r)yx9bdVtoc(koNkb19m%a?BCFr4o!GH;(P)a{l3|>8JV<+ z4ob-*7(j`mttxyV0ImlA>K?6@_Jj!PJK z=*Tgzyy}IBEP49+ypv=z^zNtBV2K;kYCZSg`{xvg2Lk;b9geP4^ELO08)zm}1pzM8 zDlx|oY*4d@FJ?R%8PVUlVH5!{;M}-v+qfy0$%N`XaQC(0m5R^)!7KRk$iASyUI%Nf zr6K7E0BLuiMn;1LjIKvLyId~g7=IXzB%1Yq2rJKjK58cy&fKs^={%j97131vvQ!kV zWyzqE(WG!@Wa1a)8@NheShDv?t_!oK0dCAZpi!cAX|>|n=3EF}uH1%<_;soT|D-js zDCCA65s@7~^duM^onDu#oFWJX8w3+rg6Hk?$Kmnz&`*-G5ARQ zJ{}Bg55`FY-&NHN0HK2&mu1U+@Vu!PEywP*0sL@}wIIB=6s~hd@{l#h?xj*7FeaQ5 zPxf2k=JeOTX!3d7$9-J75MQ1ew%R_qEkP3~_;M3QJW-}0x~ z-GL2u28TOU=KZO&wzjVQk6n;YGl**>haS6>+IuA5Y0#l-2<)*keBl21;O@dc3E2Uo z;GMmGuU+#xONH=yP(J&iuwB#hmp7eCfAr1+ z{>+b9S7na!y@OkMWy*|b^cm*WGe8{Yj9W@$ld8$Z%=qJqVHA>ad8A(}cQzLKTc~zh z-m)P?x7Sw!opG}ClLE={qQ9a$)2SRQJP<=CjVsIwxKu)wLBXq%#|jN(xfR~nKb0ZX z4FY@%LL*(vo*b)~>n=G#TAOUDW|bzt`#uLQB({dU$2Fn;Hd3Z$y6MJhq9!)T>BuGI zbk0JcZc~X9+iakj^u>%{y{oc{d8n@#yWhNQ%HKsfQI1eIC4fCj{_kh!JXD2)av(ObR8`s8ipGTOWQ&F|9XZe_!QcgzB{zcMEO*$s%~ zzm~AkU70&x$=XmQlsfDe>gZNsN#3LOchMb!zq23&3x_eoZC(=MRSBS^G5AvAt?RCq zTrw%DOewnpy{jwDbL(n=ZVwS@uaRqoQGCI%gMaCh()~S#cE*Yj5B8oAHYZgD*u~4t zN89MiUvXr0SF(aYgPF$7&pT|4!L|R215x%5F+{zyuGgQlz+0i7{jqD<=L%?^c50MQ zdj0*BbX1{ARW`4oe$}W~r9vFZxxPgL;wuim|8-4y>BNk<&ymc}>wegtli4eYJPrPn zxtO9-@uX_*+<8FmJY-F0i!Q<7pg6u*uK(hSS+6KE5u!Y(+;?%A)KY5M8exv$J$=E4 zBD&M~l*x#nt_Tu)boBE%zwc(fEtV)77P=-vH;x>(JGwY0fLOKsRTSi@2=n?bcJX7t z>i+zb3!?0hv6W*N*H#ZAxKlgt1sGJ>&8Fr<$$OCfu?&0W{;4N|<*;Ai<7rS8Bn#l$ zg-bxt)i&7YQ}h@ZeFD9li6qXGb3j6w68OsR>@(R|zD1BSRm-H1{OAbDiCYV}|8(O~ zX(=lJv{xgR=Eq*S8pviE?Uk8>>0vosGwTjWmcgGtF-OYIo6t^QXR@{IfoXRuS{_^C zLprXoUpbtp?tu46d*mNQO@5Eb>pYj8*#B|xn49rRyyC~4t1a!Ioius)b!De5y^N_n zK5OY3tXqI*5Z-;44IGCC%lqt@HXs7dzs4b{zKU6!|H07q|sEuo3J8 zxJ0+1{Z*j1fWKK1?MDv)j-Ef~UcT)ge>YD?ofE9c15bT9_Y?skI{kMLGsHXiGBr9D z0Q6)tJrMT~aKty+a=oOwx*9akn12`vgYT)b5c?PAZ9W7Lb$}ojf=Q@uf|XIFy+I4~ zJpdH7!k8UL)PW9hlS!W;>R_`z93l;cIWI{zs<0M8o#9ziqdBWwH#49^WK$F12({zB zcJ&=_#8mHfHB1G-V-03JI9>rR`$?=|o`B0g81ULvKH%u@Tyi!#FY!JKaTjpoc4Wm! zeB(y1t_;9MDU&rqMFF^LG-7n!wU`{i0m$VS7_}qpK;aJC`4L#6Sj3WwpE=;tR~eBW z0=ip-w+y+_B@X4X9G%JA;XV()86^pA$Ds1oaq<8hL>=vUgRKLsqsyx|g8cxOB^6e1 zjAPW%CxR8&vCNIteZUQaEeHuCSOKLD@3=n)Tz=;|L0#NZu6nDSqrg?M)T|yd)pN3bgoH*0t*jlb52|L42Ki8A2 z!dhOnauCt!s+d!6B9sO|+t{B7k(u@7?N)*%Sw z)dJv%Q7>-|xk|9&erfzL9fUaKy1~qwH|T)@m#_em*&~uru4|GRa0+o~`??-}WV0S{ zq$ij+gudMb*lf)aq(`ulN9YK0C0J>vSy3kHkhEOx$Cv;vD?pulC<*|A_Mr7PC1>_(Ox_njRo0>Hr@9bcndZe0wkj2@H| zcKG8Pvrq^Vk`>q}tedHOIsK;6>BHb{UBS)?;jqVmOB9fDB#*EI96*MErICc%&8&<-sCu{g3hx(qln4?<$D7yvg{%`;xuPq#_4SB3JGyi~?>-qyy}JZE_{4FIH~ zGK5CmMniT1tGTV^n+`1nJRP9DB4<5b0sDh6j|p(Yl;7S@0f33l;x&jm=oX=@|0$3P zeBWr}4L2ZU22c+YHsg4TCmz8Qb{OCiD%v**RwNO<@Cs=FZuS+$%x1a%F!YC??|UTk zG2jTofRI{oY|iya{sIXRcASiDW;1_6<4nF(xo zM>*@u<&i(yt0gk5A`sj*FP}Xd8qZT%%9%s%5*&P|N<6 zWBbrx??9;B1s`}-;luh;9;$9nP~iX@HQxrhNuAo`>_gX~P{|#%TO|ir(ROXPF<%^| zFe;W5%B;6wvec%fZrv7J?uR948}C>b*Hj6ye~q4qf&D$0N!n=52l8RA&VPmU(&ePC zL^hHwsUp@9e&u9>W4Qf=%elkLF<$*wzK6<$zFRyP-lV$D2J+HbD9TC`^*J1wI1jcvaeyp%kXXW#dqp$I(uKsGx3B&14X&bBEYzJof12S$~0 zKAFL`m&bd9TxVDrXRZ49UL7o5axYuu_;K{BRyMwc{|-3%1OK(3TT}-PaW(u$bNxmT zdyM^}4UBvYtU&}Ec+jI@kcrQb>FVA_=F=0J^8HhPE>Z#K$o8GKc!PVBuaRzvfgbhR z7wU)20zJ*c%OvSJfIp*{ zNYOEmoR5Ond~84cd%j{{)zxEa?@7_ZB3jDPN5*l7xLiKO5-;xxMo;08)!*-&M5$SuAr65YwNt~ZjWURODa3;8dnR{Be>}3 z9-bgwdDeePZs3S^UD@EK1DsdFzy5v?J=0)o7_AiA!WERBd|0KD6w>otuE(dYy7ho} zcEOMO^h$HoD=*c8f4gZl+rg2WRjoY(xW8n)KP2INFb=Az@kKb_QE6Dya#QiUX2HlM z^?@$=aEs1&_sz8PUL6ngv9ff|ebY3XJILVZ}m*w3oFU6 z8aS057&sLYtzKY5#d#+@a${HWi3jVQ)wq5{nhW$hA`U)nFb+B(TbRfguOsZaN`%-3 zi$%;G5tQg(MR^ZEp2c}9iJZsU;GIP1w}g?yI#DbImuSXFqROeyl384~sQq=8jCCnk2qJ{rP+l_L-=3*R6!To5OJ;(;RgV@wetv{r$^p?V|Pr=gp9Z z2FdWjcSS@Yk^c8atn@aw^ZJnkULMok)4SK8N#yk;A>^&pAOqAm$u_85Mtc6=FUCUW zcHc;&pUS0XXv*%u;wSx^=$=Rs0BWfhIC7#8+u;IqUOdfcxRCA zqo!P3iIdut*&U@!R&+Vhp>w3{Zr|8X^^g5`tw~|$S78qszAiYKZR8YxG$;JObefKuBkkRDw9@LrZ5UgCOIy)okW_96m3C7V} zxr!0&Xw}4qyB|ryd`o>Na{~MXryJ9AnLpCEzR68+XWq^`Fqhj&bG5(bmmUa%xB8k2 zlAp<3G=N&uC5AEqQe5N!TVAR9$!mr{!JVb#FVJ{>fvdMFx$nTM>tB?dxzKR7fxII( zlj$J$Hr@GVgTm6T^B@(gbWNkV@FHhg(`KCi-cZ=I;6HD`uD4CA9b)bs(2-z~J@P2Z zEX;&HH{(%i=CbKf^Ufnq(6MgVk_;)jb-Z}Wk-iQbE1?GxpRu<+{e=>SHMuM6&j$f9 zT_xg%)51Y-mS{6dWwImce(sz8m@XDTwWOWaZ^Nce{U_kKVYMw3zch^QI=)>76oALE z|C#Aik@t)fUk1@Z&hsS#?_KV`R^h9FDH3*XqsC-W&Q4)62+qp%cFI}k5J7O%zV~tP z0rKM|*--k=m5iy~!SfefqUo50A;Nb4>XlC5H&cVYW)pp*@#7)HO`|c|)rjsi(_gz5< z9gY;;{AF?M;O^Dgc>HwUvhSq(`GQSLcBt^RI2fUmv)R7se1H0$a#|7LXe_~~_A>D$ zyLq?W89RXjH{@$p>K@i+A(LL2=_`~(8%*jU^yACJl=6hjG)~D&SgX<7y%g~?L2qLS zlwbkpz>R$!nC&QqKlx^Mn579t4|}z!=M`ugj?$=&bm3O3i8;xyt+N=oS~KoqS$e>x z*&vkGme(FCJP-A2g#F<%A!KF_DtH=*mxCtwi;ZD;uhQI9(!1-%pnV~Wt7h*HPFwQ6 z8B`28bYH-L%<=N~$h;C=%IdGdkgn)+xKU}l9?^2zUTEpugRnXAU*36!j`a>X!2FRH zu}ZF7qyU)$3K<$}2U%-!i0=6H0P#}A(B$#y2N6rV_x@3K zRndvccAJ7)peS+Ps|GH~Qy~{j!eVk=`_VKNoB`x5On=}C% z@e+;p%bXqSq}$Cxkw2orUpAjWAN+U>{dd;>zER7e#81{`(z;p0TsERYofO*sR{3Np zzKl(@efw@e?zAt@w8Xt`{&sCw1kv?JSA0B4G(84lx;^#6w$`6_<-2sSq^X39|IN=|wdP@t6mu0`iuSn|5B0GM&#+&4 z`h>;eX@Qk;;^pBdQ0ylqzeht{PKO4&Ej(&;-WA((`<1v)6$;ZaTuG0rDCWS?UV4lN zpl*`vN`>Dn=EQ`IjhGeLf9hpJm5yrk+`Ex+tNX8Z?wdwDcrj->J;b;$ayWRflP zYOhh6!K}#gn~}wni%k(s_b4aOYgYJF4M-kTkrRm{n)x}G`bCMB-FSUG&g54kd|aXv^!TcJ%>@FRU0iyd!cITUbeBZ8@_sjG!MZSzIxYWHc^q1PZ|S1ja_h zIell9d;sQD)_af%Z!G{gs)(B2Yl4Km+eRs(+lPAHnwrm=+Jr@Ai(#;WkLzLXL|{}f zXQvmpYZvgE{zyK!3DzDOAvn-P_=#OXal`Vc!x$5`N1g^~e-3;qlCw)7wnjh`f zukVEg(Y~(>Bp>sv8)!!_!L*g>2Rr1&6&h~>Fdzstl zru_cm8sJggPI|*(Nzc*r^Bm&oE5Hj4%%}SV51~k$Hu#^ z4T7&v^uFE!7?o4hFm$*~eEdZsb&kX+Cnp>?_++b3alxH_ovR>o_yqWqjjki%bWLQt^3&}`lVm6 z(X$%7J<@<@ZZ|S+lyE&-JWiqGJ)f)o8+aJ({Is=7;^r@KXeH3?CQa~2xB+JCx)h+& zCU_rc(3-B~(~=>_`IS~R2Tj@noTSXQJ-rt<^G!fb(FW{; zMt+>7e(K{OrA`Skonu$Gf6RXGj_yY~xMnh2MxYDRvqA$r1fd%AvWJLjAj z3A@@KZ0&HW(Tki}taoVW*=jmASz}m0-0yB8l zq=4U2t|Vnz5B9iNejLcOI^E0CVg=A0;SN~1qt(sRbJv%=OQ?kn{*h|6WIZ1)Y5_ku z{xu7$rWkq9s5dpGRBu7*DdU^*%XRX#2OCs!Vj)a&l4qXwxNZL1g2$p9Wfg;6by8Wp z5&}ZEq7iZBR?-GG1cw!nkB!eJr99yO1X~7FeT(9NuvK z(s<|PWs5z_p>kp$yXeT~0WFc`72#~;@FXxzcz?-_#Fl6Nd^F6P>fJgbZa)#b{8q{3 z-1ubx=&}b_u=NXML4LfDK~$|;$n!3J^f2meo7?F$rmU;N)7e$x%%N>xvlm?Gd?GMg{N;W<|RWkF|0uIa*?bEf)Kf)UOn#BjC z{GI3VokOUOVea1wrUrVJV{7LS!Dp-abSlku=S<+fH6hACYPrvA?O+*ljXW1Q_9{^o#0;2O_ zth@irm!03(QIEafbniOme~>t4Zd(#rd7cV9aHfgL5GeQrxS+bt>Q7`T07G6@srpF_@V#?d7{fM$a((D+FP}p&iOPye(%qz zUnWv5*D2n9vFp2D`?ULq8u7p{iWwRik$>xptw2+PG2tikkZIT$ALvXnb-e4RfH-xwYtzPx=RgS@6gSu@h2qKdf5wNR zA66Fa&)T&@btqr9aG?9iUSB<)HfY~GFiMfXo8Mv+dtz-KzV>*^vh&aDl<~Olwo;dz zGLPdOPah_9R{n9gd^_0*CQX#-*ssxzWbb;HxAg0)!FdkJ6Gh(GnDi!JVF_HwVwnjC zAb9!bN_W~E&nRk4eRC{0VN}}kT)ipEXM2WjFtj#huaKIfDeS1prvq0!$MWlPYL^zM z`lxn9mn#0Ng{{D5@HlBk(NgM~1;fmyu1)wQpvy8UdMC*Ryd zgUsd@zX}@g82udKUkw7|WJVMP^ON zfczX@7?NL{ae6EMLNyvmf=PMVx*9l!(1l0(DsGW1Frq6CJ8hrm-%wo{ly%IB9_ ztARV4T;ZWGT{kJn;E+h>a)6+Ow{&Jih&BCD*y9c|FS!Oxhg1Tnpw!&EIJ;aUNCF4~ zs@RYpeOx=$OK;Z}x!h}-RMclNFkCG(XXH9^pR#SEL;R9Q6h}>rdc&j60wty}QAz{c zUXseeET30Y&c||=gCJSpgMY~o-vIX;E1;$CB#DFcsHa+SJ0b+%2o zhI)pAf~PF??uMVBgHRdH%c*bLw{%D7RxhKGEZ=i(rYR=sa8#3{h*JsbUW5sE@Hd~M z=t4-Y_{ca>PLT6}i{sUF%8_M|cln>5vj>j)go-tFAMho--_}q6w*%(Pm+a+j&%N6}WILG%U(g*bHotrcHzpan0Z6SASZ@(_q(Hhrx>|s* zw*$5W)+dJ-5uF#`te|~IYqDS3+0toh!1c zhZc}uXqGMReJRqm08l4&mc)!aI|Up$x5 zs%dqP%KtGRN~jrGNHt0|DzCLVzx+MUL`0eIQ;72Y$`~g@O=yR7e(9UL`PAtM;@B@i z{D*Rp4Uw*q4fLCu0VaW}U+I1~lRpKb7nS~i;ZybwZ?-gtgLP%;ROHVGiQJj(JU~#H z@0JaS7(Rz_8-FYNp>N$SV2-XgX7k@K+QYndIS4C$?|UJXZ>nrPxN?;>Acr=pWbYw$ z1iK>h335I@tFE)rr9|q+%K5SG!Hr+8SbhCgZ?@{g$sNeVs)<&fSSZrh6GJw-HeiD=Xmlg4h; z6NE&b?;Oubg$_-6Dw-W=A;+O%;Yadwu#Tx!kOj|5xJP)3w(}QFlvUPG|7F!qOWt(e z7OW7jWM-Ny&Cq6>x5^XbGmc`svX!#RfQcjN$oR4A`kjJ%p*>L6-QoAtK=2DcW|O!u zgs;)t22^wM=F%FUdvh2(q`YAyWlQg0dwo)z_X7^(KrlujkET~qea!zguV<nXVmDWRMSR(zA8SuD*ws= zzYTq}FAjq9|&# ztST7uHTk5SVI)H6y0hY2*^01%#_RdV)QcLxG#`zw@bF-M4*WARg=c7q1qFkRF|PMrN3NqmJeAUHrDAqF_4OY zGVzW+&SXNVopr?qkiH~+8+6xo^(;`1*Q5rgWP9Xf14@nU`}S=p_Y8d1V3hYpq>(JT zC^fv+{G)GVEmPd!nu9(SQ$E-CzBZm<$OkB|xvrM7@k`Tp=5I(xECxC<20Q7p%CWM8 z!uz35Z*uP7W>{lCGC8sh!A?3LS2xLIsN}rxzLEQdpJAL9x2JB$>u}sMHwTPVvAH+o zpPdwDit`{qNwvtG$l5AL;EgLq{CM@Tji7kC zSbv~le^M414cmLp6jz0Hw_%q@-vd7^dS>yZfa``+wkTdg%MEWH7vUD~4#ZP#Mmp;{ z<#KOoeLQhaJmGU1i?;Uce&tD0L(dWa`#SKxb1X}+!-{HsNV;Pm3iTfJr9yI&z@|hN z87{V!&m5v3b<`-_rFDA%$~`MKa9J-#b8_VN)FjdM=V&ZQv~O84LdB}+Y`>@%A}3OV zUgVPDkn>dvh~*B_1z|F%6OH!L4)(teV?T_`2-IuHZq#XT*Z-LgTqqAnMcLOAj#i28 z>Y8u{a4u)M3r%Wq`Co1KyBOC^HEjycHKjLCx}JX7w7ky^~hPD)~1>UJoZXBmKQ{Xls3=P~Lg!-GN%YKi>($ z%__)7o+@q6?#36_R7sSg<)8Qph(+f?D@scd75_)ty<3-O@)nG(WmW?oML*FC|n^idtPnublTg zExq!hNZuhAHms;xjzVrf5$NNdjogpU+&BehI&iiKRsGXVB~x3_i_z>Na4M?k&(eHZ zKrjyf$?YbB{_L^%K6AJT*9R@Jo_q*p(3uzMlvg^@2`Rg?KGJ5Cq&XmT!|zs1g7QX8 zxdhEeMb`xf_RYa2IDMI5@VDmb&9PrzpH!s{51SLWek;RX7uAcUh!%ul?g`Q)AgUX) zgHzshjxPRj-DkQ68_uG!W<+x1Hyv#xOX_aY*=PcRYkL2$6hI;ANPDS2%1eRr?|1KY zWnw<;ir8VQZx4;$&3ArQn8QCb3WTNp9?_@B-K%&FA9Sq!m@1m~+B%aIZsps{; z_0E>q8_2lG>U!N<1d1NfW<;PZIcHnamkz!er8lIp$Y;F+4n7Wg+5WT2!Tv`+`t`Bg zYy4A;D}Gqu{T{p*Lrnt|^8*X|`NJ(g&?M-yeW6ZS=q)Aj0dZua7;E8ZlpA8j1eHvMdLrurr>uOf#h+nnwGHP4P` ze)DG-b^W*t3FI@D1=Zkj$acdr%KA3aPGEpktbQ=6D%kCfn z6Ic0FQe;8!A8_10{x@VL37x36%)WXgEE7hGv@0sIwL8!(B^t<=yMN7#szBJAY?NQkllw7zE zl=}OZMx!bJQpqr7tA|+rhotU)WKe(C(Zbzk=jRVRuH#9YgX{E!45l8u(g~Q*dJyEb z5P$fhHP{3W7}i()4P#bz@}J>oS(3@mHXCkY1`K^8i{Yrhp`ARskDzBkj4R(kzq3=- zM6ngLcgKe*Pja2QiEfSNUbDEVKWGLy-|JHuZ~YY9Q@3WPJ*tPkaBTtDNGskx>mwOtxzPaWioJ9h&SlpfO zjps$-+!{$ojW*5PR{VFQmS-Tpo59IB4F-x+=;Zcmbc1gTFD=eI5-ljSAXfId;)ax#(Z6dt`XCE9Jscs^mbzVG57#5Dc0}2pm~usOc0=siLN8|+Zn$97<2xBaXc`B| z#M4pMUe4q^PbA;teN0BOaO-+HG&H$%0zB#!z` zc>_%8QrN=3hIWT)YRRW{e231AcmqkeTCUn<>qdgpsdqPw7s&0D!S-mV8EGW9?7vk5 zQ}wA5&kLA9h3wLm-v+?Vy0)AAK&tfv0>_OcPJ|;>lA>D(le-r{t5e2#??3*7NXP5q zP7ZJK8>!}2_NRG6I7_dOf$aBin=I|%{F`*5WQBIYz=b(hk|cSN4SQ8%wofgli>^j% z_}b&jU_Ay;&-#@?awJUj$o`|kmB=^S-b?*{#}2a|8>j=}!Xwo$SfJ#Xih!kzDTa(j zMS!vB0eSm}o8EyY#ESnPdEe%B-A%(qq{@hAd6Z~cVVGp1fdf&?MyNk)o3=S_O;oJY zByKeV{21*d5;EuMv5nwNzGCppnm-lhs?|vceF8cN4}2g8Fbh>dR?8Ib zK)$XwV(Y}Mbje2+?q<1R517m?-*T3{RYGmltq(1p{@xWb)8L9b2Ajaj|ZN{|y zc?T}iRqR_ZvCDO?;fjQj>2qJF;2uF&13aJpLNDXa_k%UTGbgq!;GJ5_Get#LW*lSj z?a+ z4ECM%83=JHr+$gaH;^`|R7e@Su-O_uX+<1cgTyBmxD#aVb>w0RlyWbCoIr~UUd$>` z&bgp1bgKqcjB8;O2y|!qSxC3NX$Gz0;5`sy-wMhmxUW@VAp_OV`vASlCBIj4u01w% zBY9bvF+RgH91}9GyZgQeD5o=7P<##EII{~G=K@yYw32c)ADr>$@~JJ$?k10{+z>~@ zm9Zb8DpoJr>n|S|{jLwcwhVbRj!@Kq@zo9;T0!$T6~n_XOwL>N@t_6yixM|;<{@T0 z@ta&xV}=R2hsF#&GCNd1Y;j804hZ2ZrWLNM-^r>7(j${gd;Pq~pd6`w7S)QT(R*a7 zHsAbAKUv2;Rxr!{dSbHV$COyHa`0Juq+yG=QHn7JCa|4#sq7I`K<|bnS?_|f$KYuQ z!5@5*(ELz2z_}V7Z=0rH2u6@#IqxjulhasR%p8N7`3Ltgx!dQi>cF98NwYnjesWYw zk27>X4fp>IA5j#}%_#}-(4q^%u0(BOj+;@yci#eciP@}D4Gz0ZavWd14dxzx!r2W) zFRg<}cNecI7NWD;y?+ERxqq5F9a=(dF|DyrmuYR~Rc$KybHr#;v@dkJEVVl{`S>7; zE^9{X+j>#(Myjf%JFc=(s8sCgYn?Mp^arqn@wB>H{oRX3xKPkb_xKlQc0ubEZSRBN z7m=5DM>j-(^F#-guTOf?1j7dzk5zFkm0LQEhXl#W^TbPj$}Gu~B$h6+JeeTBd~iRFVT0{lOmh14q-)>z&3{Zd{yOKut3LmBEmyU7fJs{f z89%a;&T4qo`+P$kct>^PJNnM%Q;t@XQ9zsIVc?y5`8&R^SR`NV{FH;vG{!SQ9+0-L zr#RXL{a$nT2CAV#cfMT<_#H|VK$yYMJM|P!O8Y6B+}CnE4^yv8AM=iI-;hBD(xhmd zv)<=0AiXASLTV0GB*6oPTwTR*j-#8tlOq}r09*cfzDZL@D>EQw^aDHIJ&Z%OwXnjc)M! zdC|yylHt4;{^>>RvJskC^bX6(+UX3+^bY%-_-Ky)jw3B($C0MZnqkt=5kBs%=u$vk zH&$c!wV%f$OPW&S5Qet&-_00bG-X9-U?kf`W+F*Km+|Rt#mtiqLMi&xe)wc@rp>un z_Pcwy^q`_RI})E|R>jLvL6x(0>4qbyUNg4iz{PtbEPf-W_oobaTZzyF7Ho^~HTzeJ1kl!p1x#u392R%xKU);tH-e7Jq zmS;t6ZHV7~c zB@p0edn*{M`IKPCmM0E|(5M}uCT=+t}1v$p@9KJovYfs2~+4lu0p$zX1wU<;@ zArW8XbXH{gXZpejXqO90&J&KFD{czXeGiy>isSj-cawSeU&5ff zE|A>-%FW*g1U<8nB1x=aJ5F&8GdfeM-FBSu%UL$%b}uD20+Vy~K-S!{&gWD=J9R7e zcYgCXgY(n%3}A+tTs@%Byvxl6g*cxZ_^4QhWo3z8zJLyYP#NO#3g z4X_+Hu<)5xVtHkP9==vIqSF>{Y%UFvL-g#!QppSg8Fhzsq4?H!{|)Gdmvs9rsbnQ1 z!@7PUNNr%Bugl0gGLsnTJGPR_VzMYP__UK-?20Rnu_JwY#-fj9K}8G6ankP{k<0^9 z*0=nje=K03B3qXqY(3NEzKo7s|LrC|KDA!+Wet^1n6wG)zt)(WGxYW#{xZaYKdqeT}O(B7{L41~an-+(<-jSX9bw7Qf)s=I* zYjI7a!MukF;m%+X5#^@X*+<*S*?ZSn2PF(|g^yv}+WutsrS*2Mps8lN$27WRN7Z=r zgn*>PNjbSHeQAWGGge29@67q%Fl(e{aoQXEwK^YunVDF^3mMnb-j%Gd(c-up=Vtl< zq{0F-hkt%N0lhdEp)z@$b7z`SyEbOB%?=yYO*YcYY8q9%5^JYW&t7itG#y*CsV+wRlOzY1Z;c>6Bl)^{ z_>vI^=ryWDs(knbw=o~<=H#qw3mriZQn_Tha7rKcr_R9ZbA<)$Cz=Doa86X7@w*kN;d7|#N69(^PaGc&+$&&R zIKXNu{_2q$8F-2Pg=&x#d`he215#&)aoC}Ca%WonAnrZ-E~`wVf$~DIV#lseWfy1e zfSk!JNLlB#@q$u!;*EkdPjjc&LUzu9c$ICt^;n0&pKKLitgMbS>fY(C*!o!_&UGy7 zb%V%RuTOv1rF8W`Y>hZTe((w0*d}N&WAyw)!p@ttZ13gdl)%6Svxvi?khH%;YU)zk8Y`k6b$S%%9F{O{O&QMTt}=^mN9oZ|qK z*^p~|VPr<=7K`glS8ofC+j1l7ihPMWbV6H-em100NXE5oVx|{Gz z&z@*#1uTi7?=wyEJW55pmxbUD(mE8$g`D!i^-|VioqXQJi$o)NbH8PJplki^fii!a7ME5 zWaDnUq(R`W-3MgCTF&5rMAm&Gz9`$FPLYD7Ar5d|5=&{yqP`fOhX%n8L#zeD*xKAZ@hHJLPWIJ8&`yTHmW3Pas( z1>SLhrpqAkF;mfJ1YC0=Q}FXpl#oxLorn+kH7aSo*r)>UI08k+;VpM+Wn)5@QvQm zMXfyF#WbrNwG^QDeJf4!I`m78QuH@umh7m~4y>QzcZXIQ-p>#r;wjddYt z5X$ZeE3XaxMQwft@EzcRA3!nyAl2Jb-52N3Hiry+*#U;do1! zZ7c@STofYK(aUU_QSXK+oF-bRgJ_HX1{)O}GME~|;do^-$n>$7H^oU~2HTdTk?pUI6)o zG0kAT&#Dh?UN8HhtK9g zQEi?-fYDVp>q{v$kW0rCNWit5t5a!PHTP;)1X99sfEcq|jO46{hq6k|N$%;Ti<$gl z=VU8_foAk4o#Onc9@v);9d|P2s(^L%P_5RL_P*1Yv@V*;uX!fF=BDkG@U%5!;4Ed6WQzee}YN7)F9~|+{)mmnuEu; zpEjvFXBZhPNsywGXgB0f)m$8f#TsTL4y3(&%&HxkiL4ijfY+>wjLsd!?7vKguO8M^ z+x@I21-Xw)=&TxM9xGGz&%J`(wzz*0lS{h4NSZdW7@6&5sP zGjRbn*D8)9cMPBH1{*(8E_=>D1OwGL*ht0l2?-?oI$#@JO{$-CG@f??LH?n}woZDt zF)5memQR=324WR#on9=St!ZUtIhyr z*TLDcmE4!~nZA$|4(hBC)v%3gg8+GS<}G<--eyDi>~H@`x31Xk^q09~YPd4pe}dkQ z8|BGbpXktdyHUM3`f|xC(T$w1p-H|SYtB2ZZT*@POP^34Y^?j1C+&Naz9Gq$5x>mp z?XP=Q&+xg6_HM#(EQC3y$`Ucc{QW5;>F|7_-z)O$1H|!?K7*swa0mYz?VL1AqY25u zKI(_u{=uV-lcV)*e4UsZ?ckFuDtAo5lpqFjnp>w$bq@x-u^2VuO`r9o zc}oCHqlqhpx76QX{*qIE>@t;=?{lJF$b|ekh&q2ZJFBfrQP$>AgQX!wkRFf#lL|v6 zu^xTy5$AdKGan1@=mm&I@q(BDzf^# zL$bTmXTY_C)|2IqF(E}gS?Tp?l4Q)B+!wd!9J;&6AjMuV@zQYWr2_gA&l)C+5w@pG z(hAt)U=xkpcKSReJ%!CbS64Yr*S;tu4!A>jQ^W175lode1`S;m%@x5y4S4Uy3C0k? ze|Bk9f`-{KyJ4+e!_-C3FIz35WBJ#5pbdFKbMQrD%hx=vk!~{5=~QRR`o^K_ZMd_u zoxDlaKHzvkNt0Dl4JexYH==!#u%Irrzo`zz_tE_`((SD;kUm7Jtm3`)weLp?*=7zA zIJa>LaM?skX(Xgn1Riywup1(Hf24Da__O0W?{Wd)vB+P$aq#~mfipWRvx|KeJzV%J z{NhNh@5tZO(n_L0Gux}VS#b7VD$(T;Z1{0(`(qH0CNEl^R65%oALA}th8}< zR-bp8Wo^+Cv~fMM z!4pJx9KsJo{s~;nAF{m-;O1+5KOzAF4NkE5*Zd4~+V;dodeQubYHsw2;RS!VE!~ZN zrP`|CtVE}q-ximQO^v#X9OSY5x#NvVRUT}9hxKaz?uj>+OjD_vZkiZ}{b$?Cd%r9W z$n@y0!z`9daN@P7zg`~7A|(gx_b{lnztAJ{w6rWV@ncAHXrlGyXZN8iQ26Khuf)GM zm!B>u@@jlEI@gUv*I>EK&fPridN9Q}$7N20bp7+FISgO8?!D^FA~*qWQ=QyT&W6s) zdF-t|ws6PVMD-J2;qF}ZYuFZ(KF=6$zC?<;Y6Ye?GYjlGt@ZDA5Fg({2d|VL_@eG` zzIs=hIiU%z)_%ShZ|Du0I-kV@rlOBiDv1K}=Bdsv6YuyUrLru&;G6ZScjLFE&mKkv zqmE1almhj?(Sk>L zV^q>kc>ZAU3$J<7^^0pSr+=gsV=tZn`ezhu-r|Y%DBw>f z<^qqbjq4LPb4I+Mn>~jf1Mlle?$V0di3bYmQ2Yx+m3s}VqjwG}Gr1npfM21EC}(7TKCF2nUgk91cyO>M^;AzYmgs_8WYOmrG(DFKk_K zD6H#Hwn62+hg@6DR6^jjy3reD#lFjT z*pF`^6ys|RHKDX31~z4Me;w2V<2Zaf-T1e7BUxOp1>xMQFc;2rb3B;pQ&)leUewv>HQ_jKAd7tqallFW1^M+Bi#7QCxJiwk6 z!2N1PF65;3Dt-+>yh(k85A1O(9B^Knpz_XrZ2=U(K8k!JKKXL?A9WG#q;bc{xR9uU z7f=9y2i5Z;2)l8T2FKx2J!s(L6QBU_>12787c#+{1n6!0P1=s|BthV%(cQ0kj>+eU z>6vxfYy7UL)xf0w0crs0yiD)qRi`U^@LE_y+}Sr>&LFJ3dGf|YrSHK!GhJh)7ATLr zR?){~ZOY0k-ZQS!$x7=$(T=ZhWM8|o!)_w8?i(Cw=SIQLKv%;^NG1zXW7oWaZh!&@ zfC=Q})(9eX#SE}CdAAQlQ~W%NCzF=L4RW+{7<#WO$i5#hPLg->FPlGEhnC5bc28=P zgJ3>ps+~!Hor8e$gj_CEcp4n}o44qCO=)%*AsA;_WA4Lu_vy10(aU?=x$!Q!t#;k) zpSlZ6&qhJva%EgWHPEs631o0Ng6lRqkj@4wr^wha-@e&c^2aOOOmk(}n#N zHAhwVNVp4JyZCmY*fxoUnw$qbv10tzBkexZ5&`V_8aUotpM>L3{qs(3pfV=3w^zp! z!2KuQ!M~rRa$9Br^f)AZb$0M;J}ZyP9*Dg?>XqQXH54X`5IDMx21<)&um@{(5Kv}@ zo{+f6QK$qCGKHp-MKRjS@(NOGtXMJj-U`4NHExK@p>1?Z;Xs~cg0B}>CYq=)+`jmm zXx}iQ)PT6q0ly}qc7nq5-VS$ijH|9wGU8dL6X(;3?T#$*yG&faJg`)j@{f+JTbKnP zp)b}bz_Y=4*QXcFYuEc%PPe^ZX@2(dyIg(%$M50$tlRyPb__R!<^yV?slzXtd=B72 z57=*-CR$rp;A7##nk7o=Dvkj1-|O8>khi($$>|O)?m&s4^N}yb$vTb5e@{LVrbgBM zWBBi9;G2X6r8^hCF}0p>t7O?)O}mK1en56SMmv=@!+VEvWL#Z`Kv%6%4^fiQUlfZus6Aw1m7i|8;(WcunK!8<7g9(!H0F-80Chy%7U+DEscx{ zXk8T?HzlQ#5ikxmNq~R~Z^CfxUK)&sFIr^70p$8Day~O{JA9lu1&khDud?k zOo+;$ZUTpPRdz@Fi7q`u(5Du!!&L=p8Ch?cgBG>tR90J_F*oe%sMBP6?RS$%0QVUyEp;$>2NEYR?$2 ztj+gk+e^-?4L#tkOcGwxuUPIIsF}U(#{8ry7miZ}n7fk?pZz5t(S`-;r1;GjUHm|I zPKAUs1nPjWUb_ZEYMvkfGT@IUI3_d@THd(dw{$p8ritC&(_{m;+Mj>s(_-o(+ z4|82t9E^&t+3UAAa#lyw(85#jeePw^d$Cq3$+@=mu;;#imeQk!pbGobFCLW&X~%E7 zq$>YmQCr{TV4mXyk2yX2p@rq4)|Jh+dq-xwsIV)!*;y@8>g47jw_Pt$C%02Pnm7L= zt3*;x&s`NpjQl#55#Dc}I&G(>kW9@GG*x1xrmOnR(aLqn)|&(E;k}e(-K883Kbyhp zfIeml@Agw@Kb?7IA2N*)?(amb7k#bt0|&nB!X0P4ddZ8V!!u7B)WYdX5|yn?;^%ep zkO^rSTf3i69R0JL#K6lno1kgZ=4M6Sc*lPUXbVc$U{~Xq7^Rt+-RY)m_0dxTkGa}3i&H{Sw#cbz5$Y>0A??)8Day3 zzX5c6kQ&Ne4^cseTcOw+Cf`YBW4}uq&B~+&=}g?d`oY^7;W?BF(NeQ>&&IxZnDK(u zF`s?rT~hJk+i;7L@>KhtwO|%^!PbI>Fvzd&5I z+WOPl_9uwq50~M#xOse0f`g>F0jq-D&QEE-u)K3r&KX(34lXLz3bl-Cw4#;{M;z$3 z*ts{ZsUoDVp0(&YVKi%6Dx-=8AAWvb@+imdY<+^m;doidMZnSSw%; zg;}si5C+Nu{hSA9$(Nx+YU}%>7eZdnZ^yJTrObf%->zL4AGZkALp?=<#(*^Z;p}0h zy>uRVz#wk~?0M`` zufB;QR>g3C5c@$(f%<&tNiO`LVah9w$rjufF78JitF0Shtu{zb&~gL}n*8%m20)xA z?;c?QpOUnp|K#tec>PcJ3J`U97+UTg!cglU6L{k|eX6l$6Xf~2dUqSgIruW4I;`M# zfIxi^aupq%ZGTYi#>p|^3qmiQZI~h;c|3XJyh3@X0~u-4dyhjx)I4@F$QhkHb;(P9 zCXLDq!&!j<*eIGBxBPNz~+ph07K;jm%Z|WqXI)1%7 z>w30iiFuT=ch!5g#&y~obvGG;HHbVNGZf4D3G;{?T`@YjhJHLj7iD*98*Z(qK$xK?j@U z3Z{yd6zK%5A^NEbz@LWcm_e~;Fs>6lyc% z!KJY1lcL!R0f0BMB$uBF*4#PH0245H?~rY;!;@_3xBFkxW$ZILwQ76q-05#Z#i~<7 zHPvo%BXu>V>UeHyo+YM-$!@u@%_eMZljh1CA8aeA_2L3Q{3;CYlqq2-=t67|w-Jz^#ka!XSo1F=C_q>er8B&2mt}*dPDSUUi z$|*JqR)mctrRED~Du`y4%tP*Z8kqe8Oz6KKKs<fE9^k&m7hMY=LGj#!e*8?9dZ)dTiJrtPS7`5BNc4L?#N--5tZ#3yLnk# z$Zhf>-9PwfYA{hSqAmuh^BjGA%vX+Tk+2f4cQ<8kRBuFriU9R!f>&89zn2#7zNEYw zq0rS^%o?UB3@g?cIGHu-?VB$gqLNs<-z%Usz72oPN%dWTldu+M(?x=8tf>z>S)iKb zh}Du&*ex5olP_a>+0CqT8E)@%At&*HrYmJIrgzHKn6)b#Bi{ zn};@)-HZ3;{c!6Z1}jNLb69l_loF$Q?mYJ09gd?a_ra@wk2XIDH-J<3I-S+tyysvs zhWDYQv>SK-f>+un_nf!|^SC-}YmjEwerCzPpk#BKH)v5P;xN~^c|lh6k{;#<^K3#W zpqj5pv>jGRY6OmgUwx`uh10JLt$>n%WY7Fs0fSl@GbAPhQ ztE8BxqibFZCT&mmuYH?5)rIR!%UiXPS0{JD=Ca)A+lEUMIQCu&&~?a^LArXJx+ z+37A-z$4t}tM+L(Rl!r5QSrphWurB4N)E^p?QuO#Af_$r&2rgHLCsescLD3&jp8(! z6j%fUR6-vwf?nB~U>d}{wFouHJ z3i8|eI%{!+Ee#_v=eC3zx;f)&UMxQBlR%l@mMm1Z8|_|q+$n6a((tXr!B*f$Vw~1- zJbr*x_;S)BeXVGNaK=&UN8Nw(q8i0%KOx>;01$2vn0sjnD~jbpE<5HX&1v;N|ME}u z$r=Chucvp_?T?)a*1u%214t%JLMJ3#3828hp5QMkJ=gFFiNMvHPZyN>X5IG>)v%KX z=<+-cz0x&jyPf)Npt_xqx4ST8y1PV$+3^iJFqk{sq)M`@T_V6@{z@UUqD?PK- zv9+2?cef;3x}BSW9h%GVxnqlAMAn>`VJ~35KNn6Da01H`?SpTGUOnbt1O5)m-)qvS z_uQh;cFSa-D?}wtZlJ+Dts*tgCBoHiP9z|3L292kthrqYM!u@vv20O*2r$|UE#wlG zgJa+2A3iR7*wwL+tPYOc$%xq<%$Cfz63{)H$Vb6J_w^=SL_6hBjC|~Ss;2G`D(=9Ri-45+MXd3KEXRl|G!oG?!(~ijFMw6OA-Yf-Uv<;}zn=;y^Ji@-FN#OhfMN7@EiHo38NE)H%s!$~ylyjx zGmwh1uU&^X{P@WAn!mNnWHb&*3#YvuxWVnN>-ytaHF-h{m%1JB`gfoC8w;I8BB5KR z#COYX;RCQECkQY4Z~Fnidzx3Gt8N&(TVTXLqM^O7TVIbXTxxktee866&QZrF$rCK< zOXdggZCX_S?gg+f-ltQdK5A8;d(Uy@_ORU4di?$7ATu-Oz(PO?$(ZU4J+6IXihB#W z*>SZa63Kk=6vpGDaWc{8Nvk~_GZ5715U8KlUt=dST^(9@y404r2Oh{dp9{5!4B<;M zK>Yex<@$Abd+p`2W1RiCH(GN8RYD{ZXT{pNJ~EJFd`DrpUmXW?n7c?kxQC-q45{bfG0}yP6Hb zjcCm9B;IQB_L_!fR_4M3{)!9@4o(C*SSV?pb+%=6%$q(9O1o$@)os7(b5K{b~jNDzKZbt74OM%7iT1vQpCmrU0GS^2zK=!-#Pm;<10JW;_7NK#=bNn zqy0>M)6gUhD&#KH02T6-iHD{+Wo9l_- ztM28s2MZr%M4;r&zySDuJrL96lCF{|q4y?~ewoA=kzqKU;cwW4*H+2ZwBzpnG_d`b z*{JQ@g{aKR2X|6O2Rr4k3kE43;vU?U?TbXQ#cHoEu@sbRC2hde&6TI?*D9D)73lK3 z-)pwS;fX;fztU_mvq={?XFbpdzGy3>{l6FV{&#^8wBe(S38^d0Lq-|eFdbrA?F70wf2*eD*(+`@ z^Y|T{WU{%c@3Jno%yjW2^y`48aOVbK7?MUG!+t4y_<58U^2Ni^117@ z)YxC&u6HM9Am(MLXMKRtC9`})^Wjn|z;mgyvZ4ec&}A2>bY^yO98`2w-RcplXjt5h zw`j8GAJ#mwwxVam=(^yj?S9Gg9=hY+G@dBR7v_M4R{{Ss?!9Mxxl6tf{icn#+61q1g`>s$>W5>`L87 zotc3{Pjb-iuykN2e>sQKvkVp}cJvXlq;1;LIzclPlOq|bR(o#j zsvl;jxtjP~Rw0m*WMMjGp%;T@ajsF}XLKe#t!S0lGs*IV9$^nrhlC2O9f z>#7Wzs#jxp%~WFLUX2XK)eiNk-thDo=3kR>8(n2uHxn;BgZu9+8+4A_DhY(Xp}vev z_5W3|mN}clJ?)qKr7i@{x9*|c4tF);yWgwaReR4yst9G8VPoQ!`pMB-c6Ek7qj+3dkhrb7 zg_`lDG0v%K->n$5~5ql)|A zMSCqts|IQ_nxKc{%RG-$Vian1{ciDg;uFR(^ z;U+O!E$b$cjS#y#=F_cNqpWEcc(rYUe)#56Sr`;4@EFoAnf3GzLvr5^S$5pNsf?l+ z_iwMt_I)o6+@nB;Tez9oDG}+KFpJ`yE8NpNEg4lRSMB~g3FW!hGo*3_d#o91+AE#q z+8;>u7vjOGYk~joZEfJxb-+h#y2#i;g*;@)%UCCR!KJ5fz@6eYlfkD=c*}x5Ykt>F zRMc5%($EO1EGv8a&i?PzRUGpnh0unj5d5mLp%9I#vXziG)lO*_e@%5U=M2R}IRd_q zL+|@SAY_Sup`T4h?lrpx?dBj_23E~Qq8_B0RZ&w3o$exgxr(tYOJK0FM`S)_WbocS8i^j+w)>a_krqxBG2)(c>4Y3 zhKvyn8ynu3<+N8()6c(e$pNyas(wY&LhuDsFGq_;a6fbyD)Kh)U1vkEW4yP;1yF-v z6Ha7J!h3HP;MfyP>zsqN5!otD83AI8dq)wdDBxt!(h4rksIn8?KXh5FqdsWM{gqo8 z*tFM~R@dGCowzb^eo#(h-J%eHQth7j%Nd9 z-%TGZ=IAki_vesfeTVV*Ur6l-DFG+3|}o@F8mVgC8G_fzx*C}6{czz zrd?IA=uvr>3oeL5y#zGRe>m%EfP^^FhrtZo5%q_GEo)2N`;x$*=B=KbJ}Hy#VHhQC zcAO_q=3CuLwOjUvz~@Cdlq|& zFq?XTyp3dbHmlO^z@hr`i@(3h^L&5dP{`0>Rgb*?@b^u5dAY$miAekr1!?9&!+sW3 zcwLD6`>tIR#8T*`*7v6e59oUsLaf~)7-|}N)HH9I71#+EgsTg1NbJY-Wb;OBN#QH} z`ukQCRGTm(kUDv6`-`L0PTx=vU#sM>qck5TsooSIox6*uN;R9UicHftskC-^Jt`09 z0oQx&3$jTj4rIT@pOe>CmawBV(qGiFWd}}43ViUsYgPYH1W$7-D4^0n13@spZ3u5KtdiKs(#Q@rs{pmd_WS@UPxdJUuPG=G2f`=J5U{ zK97n!?o&TOpf?4!mgV+9AF)n6FF_TUX4k;|o4gp!~R{#QARnn-!l| z>bBD<#`}(bdjw;(i)5q-WMrsm7Z=q7T?)i(88N9lEovUJ-#i+RdbI9jIvc7ieQ9sB zFnAF#{O_qLMVr^12UwWuBXRwRvkb4b?NnE7~CUa?HXZXY5P6pxq z$k0aaxF`3>Zglu|4tc$H2HvtEA!I_dT6<5<8qgaS#w`Uq!M6C!0`I-Wi(dK`s?RTF z6VSVYJ3HF>wnZ#`6^NTh|8khpeoO-Yj<+B}iYXKyJ(Xr#TkT&K7Ie(;(9wnPZe(3&Ol~KuQ(X^R-GQ2sC(q@EoZ)V3@8s@8k;IR44SWIeTW0Ft>i= zf(7xFASr@VV3gOaG?M+^la67K=m|Lpi9xR$;s7o(Ii_k!i}<0Rb~&W(on4I$6UT zVhf8SA9+=W2P49$_UB`G7Tp{_PgDkwAy0BM9q1Bf&&iT#CXfGGz72W*Ltjq!FrlP$ zGcJgoO8`WpAv{@m2S4~k(OEmpfY72=fE<`Hh+;Ta&YYj_Qgfh4uINn4XQiI;dr zqPq|pW$7bRBf{|QXH7lxMVkyo3r;DM3={y@Td5o-Y_jSJ1Co=+{WwH%ErkkAdy4`B zfjbxMYrl!l%P?Ts<#Y<6o`g3t;o=0u`E8bf+zf_JWq+mX%(W0#n@$7B!w3|=&!$`9 zjEmjuw}CJDpfs*%>Ng$Jv-_`|tt!-k0BkF`Gy6q|d(?miTPjiK+S{-!F5E z^rJhDJ43#xhMT)F&JWiVL&$yh_8_pv@^zqKMfA_dWwi8Bw{_icRdOJh^zm)an-qlk+GXE9;Ng~ zI*ezHgxWOr>Bq>KRu8GQA#z@PBde5YaMFpO$m7AgiyzXquUl_KfymbKB{`0+>(y78xh+CaGHp{F~tW+gjL; zfdKpFyL8x8t5910#4^dkdwBGd3q~uJUaXBptv}>LJL3vpd}>UOUX}(A_MfWX(x(mfbo!aq&@) z39@*OE$d#11JUF^JW!XvC;94{QpLw$TrD%ac9m@W;z>m=Ce3$EWAwoa*4lz=xVuaB zf{(eyO^5NqDzLJU*0(q{VuSR^;m;Hwa?0$$;wGFWpC;~f)=YVD$iFW$lk$wlPG@Vq zYV-B~DbT4MF`QxOW5oO@2(Zhp<7o%$NsAe6;mq2Ok{OwAILtKm7Vq<~Ys`qIJ~|Zb zO3bilyJ4|@O&g6887SgU)2yoIy}Q?JicEXr)_y6F!54hGJf!WB)WEH-s|Z3=k^3g) z(|o`N-UPQe1uPtO9Fq2bGcq&^(Y8oOvIggEarf+~O&YPzUxE8ZUCeww{gd7oWTxj- zO19x4F*otF5V4}!Rnr|-Z`-aPJI)j)1*qiMs)w$Vra4TG&E4(2_;|K8SMz%E&~wYR zn9Pa8oYDLyyM%u6aKg1Kh;-X#B;Z~L?&XlHs6v`g?7+5NRoZ4XjNt*N$TDoWJJU1aEjiNN~P46eEE@S_njcHHd8Zaq*x9}u^(?- zMs~A~O`mS#FQT9pb1-MZlsY&bW}yn!8mRem7ZtY=H^Dww6h-)gF;Lm1QRYn}}WFoDSMCM$* zSF!VPl3j%B>Av%e!=R@R1X5&iT0ileH{$9XFz7ALGc0|9JO@zn9dRdN2A;RH%GXn> z@t5b9{_Zq%@#dm}{7~A(1rewfO=;a4{s24s+zq(EGG@1+@{$85#Q(=!fG2Q#on^AT zdBV6yG%AUSPw-08ASCHnBs0&>EHp-T?jm9CEM=4USz|1hxnabMrepU<#`*yZgRgkhBcWsV66=47EXXAN`PUwVe0$ zy+n*^yUOPBbj&o4p~BIt5l8(+2z4Ooa_Pn1v*BgKXJ^v5-Fq_mskFF!ICEA|k)YGy zT$RuZg^NW(N!0l_`N`7b!ZK=zJjrJC$A`n2^At`?H&B)`@@1Y{iks}$wPu@Z7wKG$ zMAcJAU@1x5z~&@1W?!`f8;6k3t#`e=$nA-DUD5jDUDUH|Og~uY)d@hPkP*at(yya< zhyvrfO|l0YwjGmeqP=&8;$!cmQUm)p1*c5u7t|bogL`_sj8Oei=0FSPc4QWnp~{!2 z8*dS(D|5=TmAgmV&Gq`fh8hys;-BX&a`mGwY;L}F^u>J)|Cyo|TgJgo7My7$3`(B( z$fgitIYal-GNb8HWbp6k zVY*|_cGFBpA_?yxajmO;yafRuM$LCAMoagYNCG!YA<(w1w zQs)KEABT=c@$5b4EV;pKG(H+jWD*T>x@7(M^3_OOG9_(GP79T`sLuumcH0N!y+nHT z`+|j?JbSO?Y|COUG4N`}&hWG2($z|TQ~5ENB9qKJR1x*J@sx4vs*xW_gf7U;Ct=!UtzapcEmg|JH7b?auNb77Da&sW7hWhzFQyP$^4aXSfR!1%Z)Sd zlA-Ri?{fq-vxDmhUIoEH#Zsf_UMF62{1UwK?g%r^obIVU>MFqKWkKQYl4a-SSx6PnT}*A3uE&-WOG8uprxOEPI&nhbID6xC_r5U=1iX)06l{` z?mV|5pdVWi&`$*ehxuN&zFZG+!~@>t$nyGN)5LTjx+6OCa`YrVTW^&_0 zjoHfQ^3wZmJzy#UeZe#!zC-GBIe!HTM;|lquI;N|GQIX$|1Wj1X_S@a?6ddPoag4- zWiYKzbRVa4Rk}U?HEFW>CH{}3$=zlzP3i;BP2-Lh0xv^2 z&!bBk)xy*S_aG!#I;Jbjdn8m#3LZ?1`cJWGpO+srGDV?JCF--<(Pd5I{Rt}=3@Tq) z@9fT3D=mXP_jt+eZ@LT1sBq=}x=NM#*;FU9;((0M2`%%-M?@#t(96S{V|>kdzQ}?? z)Y`V%=#!cf#&XX3RjSkk)vuvs zJf5P4hO`Qi4KsU1z&Gp096UfpO<4Dy`h8T<%fCzVw3v3u-O@09&R|7H;{+~`OC&`y z1LZ1OFrA{-ZRYEDUun5;6jfH9?mTrqf?x3ealW#S>x@N`O^%xOAwrnPm!qeP=5pwn z67iSrYs_;c&)EHxEl8LXbPq*m#!*h$Y$mAe!`Vxf479?`{IYJ|zQyPy9zHx^7vj(T3?*I9+hw{p<78?f6e^>wc6ro?P~roqTvSZI;KZD#R`;;=C_G5 zzQP{r6mOz?%|b}^5A==MC*zx%*gZBfIP0){b9GCPHA96;pMgJ39u9tpoV6Zk(B1e2 zHZ8~X#?)I2%iFcggXwp&{p6%61w3Y^WZt&=QaaA;s*2XpJ(bOBg_K89TCuu^Z=O)>qhr$k4Plzyi{$m#No#WyEl%IUKZs<9)8gQn|c4#Tbg%#{XPE;xPhE0|yB8{F}~+-LM*{z`K|Y)_(&%gBxlKEm6! zQxEhqnudnt0s8h1RA7|F_9SKH=u~zG$peNGndyf54a>{=2Qz8yOxre)&jyh!FCT9? z*t%h5wN~ch@bFk7owHN4I5@-lM}uDRYh(v=h6%jGBJR?yoL2eVN^-x%Jnznr}+v z3@K3Terau>%k)Ib>65zRaox!9{KQHlOBgm9kFUhLC~Asf%x&HiZaI0UO$8E=NMBxG zjfAZZ6c=h{eXr^J>^@MJ(b>9;0&4^AQbOC(H%X(Z{jRd(EPZCQWR6;%ar~TTuG9=3 zCbIIIW&P@luJJH)o@1Y&mFAR#y7%`=-48=jv%%J$P@o?-k(gc+RuA^lqVY8co1A&N z$z)+_dOQe4y73|j(r96X9!sFsugs;UCx!M&J&CxVgE6ae@O~YQ92DBH8+)q6ogxdAB$sX*VtPqFU4+;rM1so1spXrqeifnYNGCH;MWi+8LS{G>4^I~gB8#V!jpK<#u4z?p zM|ArfZuB`um(=SX_We+(G`!=k4>psp%=}^h9E-PCy%hVXNBH4+FWGvB$ zE}Y$PBpv0NZz7nEqf$W#;-M&lTRGjqHl|Qf^5f`UgWJ;ub#YQxJ+ntZ)Z|x^AIZ#w zp3c-d+@F+M1G5ok3rq@TH_S~idtvT`x%W?{bx_+aJpf9fZ_ULLMR*f08DSR5tE>XFfU^&&qf6u?YMq5FbI>4*sb$eN7Ym#+|3^KF5v!KJnB!t zTi@+bt9%Ie9*?qs_w4nkZvksQ;8BNw4MOa`<+C33 zW8l&iN*!5?^Z+*i3t#rA*8(4b`!QhaeI9keLOl0%kGcwY=f5K!;EIPm>S5q!$k=Cq zw>;ueXD&i|9`&eh;M8LtH4S{`agW*yT>6AZeF<3ipC0vd;GIu;)Jco+eLqD00e3&; zQEA`{jY_=(*zmMReHXX~c*GL47hoUoz)w7C9C+w}N9_SV@pF&*6mTgN+J++#9ykDe z(&JUH1>WlQs^@_HRbJI}B+^;!RTID`FxL7waLWR(`U$YE#;a=TlzIRd0v2k$YAdi| zp;zqzu4z^3o4}2Wz3RfFPyvqes$IYp%e?AG!0S)+s$-8vJg0iq`M@XIk*C0hdawE@ z@YV*e`Yo`q(yLmI!E+r-T@8Gu#jD-|T)f(=_5)Au#J2&*fTt`)_;p@&5%3APuK|wr zc-7Ouc%N5QAB%ikkM9C@^n2A6z`&4K?FMeX#H-#9thv;y9tQ5c(yN{a9@^qn=N*T5 zuJWoO;6dOH;O!Bw`VeqU)T@4W9M>Yf>h$CB{FGO10xsU>ReOQ=q`m6bz^1%cEnJ59 zEU(%OymQ*CZUi=L_o};rw*#L6?%(NEffJDbGhQ_Yd<3`$c=B~#^>yIV>(Pi#RO)8n z8sPp{dDV7c-D|w+Uf`D9_|B7*y5qH8)er1gr_>F=`0KpteZXa}_o|-)_X1Bi8SlTr zs|J8CAlw^(XLTv{5#S!+qrm<TI|lRO-jTeGegjAXBeL zT|XAM|JzDk2E6?dR0WSV;q#3eVVVtuWcq`h->wtUytkj2q z0Sv?*1RnHw)GvV#_&n;5zz)Ah9SfO!Bicj<@T^51l>#1w`^~_+>(K82mmY)gz&*!8 zzOF=lJ>H|5fa{h)ZUXNI?gfqoJnE;wTTsUvAj9{EJzADO)`NZtxEpo!oxt0B(T_nU zKi%h1D}eV8cvLU&o{JD4aOWi+brUc(;!&SxIMms10Jn~M)Rrc+&oPhM0o<|){S$EK z<>(K&Ir3_cx&wH_xJUhf{&A1GxEXRF;Zauu9|P_HE}ihGTY+Q1`*az z{z+g1aB3U+Rp1?Iqz~Ai@u(c|9@NFR0#iA>2RK;3C>FB+7U1`Q`?q`40bqQGN7X{c z*X+c10-pt50^B^~Q8xfPc6roCflmS-2EK5eNBs(T@OqD0+=g_$+N0Wl>t5qgR{$Ra zz6Ln88~F{q_4OY0Mc#*edK`H0%d$cA=@UHaDBv^bzt0A4*@}38cm4(W0lfX=UX=$f z{gy|)0XX$$ulgYU_j%Q4fqOoIegpUb{2v1D`=nQ0whHfglUK!n4UlU)fj4~8qizGD z%U2%MZ)C4#=|&z(*QI=h^scvf|b} z6QHj3Qy&6C)TxJYKTAYg+66p-HnN-Z((wOBCGUDS3fx}{3;+j# zcfT2B3cML^R3$Zra41TZLtpep5BqPW-p+{*=qT?6J_g+eMWg!B7QP51+bmQc^$6PU z4}e?IexCuNnyLQ*-hlfr0G~ztP$kr|w?TdZ*+v4u0^|x*0JR3sg@C&e9$mUR@EqhM z@J^%;U9#E-ycT%RGYA9hLB6~bxDI$bp9i9-)gHvVAK3H<$PFUo(xbphl;QO-Z-KcD z<^wQy!h9O$OEBMnc?{;qFwejog83875zsT5Vb;QIgvr9Z2IeN1_rrV^=02E*U>=A0 zG0d|te}H)r=7`mhw=fMboiOWRMqn<7xf&)5^IDj9!h9I!KA3O9{0QdPFn@trd_M9G zrU9lCrWa;2Oaf**%ndN_fVl(ab1)CVd=KWwFu#HM3ryV_=ua?Vm@$|rOabO~FgL-x z59Z@A_rlx{^L?17VV;3`9%kVMNCV7SFvBnrm>kUOVcrRI2h6=N-+*}v=D%S63{$%n z@*SogrUm8_n5{4cm^ZVLFj<&4 z!u$)&oiLw=c?jllm_smCsQAahoCVVYa{aG=fjM`#9(&7ydLJCVQz!@Fw7TW zz72B#<^`BV-6$8BW|%IR6wEG|x5L~4^BI`?V15Ae8<-bimh_-3VAjH10+WGx6U@6{ zJ_hq?mh($;4RZ!eBg_Rb7r|TslY)6QOnB44U{CKzqe|q?zIQ|%BfUKly^%wlm-P?en}%EofSn05A3#?=nNWO5WCM3y z4@Gx&r;>UVZo{U*!N};?$iRm6V!yPvdkpzCydg5&*Js}u9vN6aupu%!a7C{g86F;s zba##Qt{)z`T#b%(bzdCm85qH119(WsABo^IBe85gVOw+g#DrxW`NSmhOYByB+Z6KM zDmuB)YdZ_s94Gte%%vj5$v^Eh>onp>wZ^Al+n&fzepxhy zcqdtMIsP;QRBvq@Sc!tlBno<~q?*8A z{wzdNGi?6?2n9aEuT^>d*X(KKYeVg=LG!bjp0Fy~>|fm|l&4<9)S=rt+k^A`a7NAV zFT26<6=v_J?W1JlxL$?RxvY1Hasf|uaaVwDgL;`PCN;xMlbZeJOi@c~n@-VKb`ph9 z{?iO~m^*miMq2E3M`ac!m0mDcIo`tyT3I#b{T+&${|=sQYgrWv&ilI&HUAwv+m2TU zonK*C%4O9*r9fRITfOQ@=Ji^J&MX#u%3vSFs;(jvwj~!WOzEiGJDNEfZgS2J zYe!a0MJ*AWve76OMMsN2&1woyE4^Ju+HpvrS-*YmPt!f8n)e1CY;J}CipZz9k~-T5 zNzmb2QAM=)ASl>$V43{_RaniSc6H(}I5vFN9vj`oenc4Cc;_dG7GhPSl{DtZed9js=A%uk8$V=&!V8 zj_wG{-9~Q->l)nI*^CCQqmlMF+pb%1usPJ!)+TM=ywzpp-45K1$ab(|lPPl}TyqNw z!uZQuN*u;633Vb5H9@qxzJs!~%*jwK&FyA|-rUg}yk`3_Y%5ysa7MaS?k3|ZeYu zFrUAEj;e(I3~s0^+IX)WadP)oe-c&EvG8{hW>*K7L>aQR8KR(6sZnK4hHBTHss0H; zL_}GsOZFT=oKJ>azs`}p49eah&tG8abxPEEm9&PnXVQ{7khF@wf^xftt1+73+Rr_SPmnEHB7p0xB0Y&oXCD9NeH7ozai9p<5^u!R58#C2k{@=GJ1W-Mj0unR((U zy@?n?tDyBrd$4^=UGg&l25q5c07cL2U2|C{VHHg|m7GYJ9-y_A{j`<}wg4;<73H3I zqOB9XfxgKOxg@mPgQ8qyJDy>OG=?|kBYIS&HPLeSln#i&&k!_YG}3K}OGO%%yLYJ> zrDI*yhSo=MV!zXE$Ni3`4v9?WhuqPX_+Th_1^-;mKbz63XVyp2y+QS}!~&l%l~Z_V zgu5%1o6;U)$wT&RPsH?F+o)8c)74o*QL9Q_2-C?Sj>ux(bJWjSJ`CF$YI23OwX$*# zhHPt>eqSnw)^f|7!DcAXP@m|{+;YM=4upXIDIx6AcS4nTAY@AuLPjjtwslqNO4=3# zZf%2h=Z?h*HK!+^=xCQGN0{b#hcsG!<<@+<+1r+z_Qk8YymQp5?JIpWKeh(U;Bd zi00#JVn6xOgpr7mPXe~`w`GaM?cqY{BPG8v;?)m)rT*#GY=B}hm&FwppicadX z3$=B0%zBcFkDfD__XeNrXlbA8$cSS0FRSDR(2-DyUq>TW^zg+qdCQHwUa3x~gr7OosWhs;wC@4?yc5x0HdX_NVTw zS3V;Wy$tT=@lo+?rV!4f=ZW#egT!|EwpDE`-QkQVvzx4ha@%pgy;Ek72D901$;_m! zDVBR651oTI<4j9$4@hq=Y4kr4Z7ou~YLjGE=akgFiR^m!KHR5Z3tuhNt`5sDK6 zQi8~SJ)BczPW^WvF26_^i0g-_6OHAQlapL9UMjB!{vyHhK({NevtmNP&v834Vfz0w>rXyp?8)mJ)~N*1Q$7=K3Ni9wm!z!>zh zWE_?q`Y#6$Y65+4VuWk17Q2N$I@MG(l@*O&FK`;=Kh(4e{p?}Vbhz}YYY+j(|0NOd z+@#?N`{2S^-#piCGf9KEn7#7`oB1Bh0gMyzd5B3j9@$^ub|64=sFeXm5;-1qrN@KZ4xu+B%=R47y8>iy z06zt-V^Zm3P?Q{ggeW^XxMS)I`LXO^b_d6^B`z*uBUnd=c{BTpF{%-zE(Gap@30d$ zgA9~8Mm5_f2*t5QXZ=7XSRjy%>A%hJW!KiI8ev-nUEn-*9&PP>FxW!PX02Ka8@r}8Y7K2%woOcq zWk)bXbS%Nvrgpt@AVX07BTd_iab$>p@V3@wjzUVSRH@6_)ooW9Ygbs7ajjjpSFkk{ zYGwpw88|yfp-pfR)LkH@rPCVXmvqZw8`>0#mhsHxWaySAWOQkjQe{pIw#~|2YYU}x zccf1rPo^A8azy`X#qMflt|Asq*#mdC-J%^~+VuKqq%WC?yPpovaPy6t_l8VXGv#fo z&~0_Q_Z2zTDU-nngs(&8>b-V4no^Dh{VJzu> zm{l-ZyIgx=q&JCD@N(f%=1cQ>7H3rVU9$LN+j{I7)2XP1v_XYnrn((w&k%++v2BTb zk24=@m3weYlRhI*pJp4gw^bzdfX~@wk%Zhyq6!4tnmLu}s0R9XQKhb6Td0{kc+klx z{RbX!U!0h^l#xh^`YQkOnK5&_#o_M1nY>R;w8 zBy~2jgDQEYtu)N(8En%tN7UlXz2QtkmAjGHjFjQ3CU!{r#k#S%thm|MWD*&6Hn){I zgKZs6%_at;XrwVT;m~C+znEjggXl5CGlhhnNq|5~aPQzO4}^oxU(C}{uF;mdqG_3n zXzOI7(f1&@r7tRVp~i=R@pikyXulI|foA{iLng&GwPAUHJqR7_HcjWjFBfA*(RJLwumOt9iVW zwgpdjtm2Ldoo=2V7674Id|xd zzphi`vnhLe3Vtn!q-~Y%LS~o3oSwmsP3+S#=3US)NbOO1&rnxfeYn@eJJueN7X)BO2$Vmk^lHWkfefELHt z3BD0SQLVO_WV)<~MXy<6IAK=vbTe@*!H!l|C0W+dJ}xV6wj<`=ow@=VaBZr#^v1Kq zPSemk+FQ`Dv>vECgh6VZWzLk=L_74PndoeAU@$P&D;g|E?HjX6&Lhm>W%>zSOkK_d zLtk-%q1T>ZkiUbRVCZuu7)FZ|4E@do!%%U80hK@|A(*I+jwaW+9L!?{?IGwrz)B2j|U9!wo_*G@11x=a8LJmswqQ+`&%00wEbh8R%YEfyKxWsO7fG zBmNLc=k zv(8nSHGSRLys9NscDzd2>@AXDO8aBZE-q_Yd|KQLWkP)l)O%jB%Zu%dW=F&0Nls zss2>@aKW?alDy23p#_d87E^){*nUkP0CjxQ559;4QF|$ft z%(~2Uu4@A2rl-I=+u64D&I4DlayNo?pbBoBwu&1YOB@K$N&O<5E8ch6gPp556|pgq z*jBvnb|64oNVc#UuQ9#BbCw4J>S<{C4oRM+%A7%{ALw?u%;DP4S>{9_E-}#8`JF_G z0|8o5Kj~*`vCA6l>Jyx_I^&2atKA;c1NZ&-RZd6%suay z%XsZ_uIOmnX0dHgbjg?_X_>L477jzWLCwcLC&*$?$zh!AypE;gnd!8$rpNU!gm#E3 zOl=~W&P}IKmB-D4Obhd0k|=gbg*k8W=@T#^l|}Uh+Fq&Z=832GR9tg!`P+6 zdY=*W#7M+Sr;_7bmynpOfy_uWo}9KK+%IgV(?SNDo$zMUkN&k3GfWF7qn%Jl>K)el zr!+dGDtXMz&Qo>J3v15ZS=;M|qWNu7Yq~5a&6g(f$qAHr-kD@8)rQrCo(I;m!9&w2 zvGgRjiv@CBEThxo_?*7M;jXdB`mspFaSYO774orkP7UxEVfjrt&LqQ&Q{c1ba@f+{ zgK`#fx!XMwFZOR65QF}S7PUru@HxQko3!*L6K1r{+icgUwEC_@pp_Gu6|~1PL1$1? z^hp%#BtzC@EwkB7e<24n{4L)OeSoTo)}92it&aArwL)DRVbN-(nX|Q>8&Jz_yG+Ix z6KaB*WmzL|MxEObAQsirQT-=TxHOtiM$xq8^SWZOM9$=yEch|+?Om46f}PvbOgfH@ zNVOV?+I0#xou|0dlZ^MH?}kmMGwwu$V%PBuWY(L(sgBN`*)M(-fG&p8O`!Ne^8LMx z1O2!bvFu@GXf2JRFPo2<<<`;ZWQ@PFp#)^J;T(vPNA#nj4A^(gnuqP0&cy6>%#;*R zsh95U5TSm*?V;mvd}M+_W^#42)n(|U#Bs@+i%$DJrUl6IEwhW3zEpHlmDpI0mZc{H z?JRcax!}`>&ch7Q?Au*>Ni4Zb-#1IsDQcEZS?g(kJNWQ8Qn8(R%Y<@T1|p#}7i4TB zS~Z4ZZD#549e=D{ws1oy!G0) zvGdm()8i%rKsSH=Lp|DAnPU(UGK1MkiN<8csFVl`EFIUY5VDY<+;%(T3=Kvx+Tzlm z%g=rle^xGgDD+=Ci#A=&_=6$6O2ll{iz%|hUdwLUboP!xfp$&dIP$qqZ<87=bvpe- zFZv0cZW&8(ZAskxnTB?`TBOW;kE|H7EOpW$jth3OdbX;QZ4!a^}Qz|yV;vfrSP)$80({g1C zPE!8R&rCs%Wpx|v-RZbdL~s>f)62s&U2blkkUjbKB#54m))OK8BlFZc_ncqAs$q>eDg7GlDt^)iBlU|i2u)u=zohtZ@i)muy`Z?VQ}ipg+oIn^)9Kt;7H+pr zfkwRnRj14s6<$iK#BWxNGEIb>BzehRkRFpz?ttqWhYV?x9i0M|G zpRDWkDyVW+!lgS5Pf)m{Xk9-?5B8BnaU{EAwx@Zq{VQ=ZOm;b;bhCpg@o_z*(_uOm z$9;+ZWjT_pEw0~zBMZ5US!!^xK9~Qtn`OufQ&yp!t7zSth;)~q&m4Ix&7nQ zS-(qlOR(8DMX4?AeP+e&s@z&RbIL_afzqOKcaTnNb5@_tuf_Cu+NjUA7qZit2^e#a zSo7{6I>)8M39`r0)meX=4zXS)I+W=G%!5w6QfKrx%zq%Wj%bvh%DD!2kJ80CDt5$n z|3Ak$(68t)XZNI{J<+N2!BpPEp5QFrG9q`LC=p5JGDw${<8lNEG!rvhA%lD)`X!W^ zZ0MH5&kH<9c~lSBIVYhNzEo6>RYx>hN9EujB}C8ygbV83vL-|UWcSC#^(ph(UbC8H zlwSMihR)DZv;^rGWf-M36kXQL(!ALzcB#0FM`RI!4ZK~CdTHl1kH z=ae`0WMk7@A+;Jsvtp;;Xr&{~k>=(|duMy=%2YBly|XcyiKV9FiAK%}1mWOJsGj7O z%-@!n*)Dw;&(|)au8{1|n`+%8LIet6$^5i1i1gZ7gnBK?8g(a=+PDN!FMNe#$ zxFT}EQ;Am>$M6|x;-z;+otHWOCP|_fmIW@}iIt?laj|r*qEqjl`>S)y?9bJim6M?k z1VtpVQwK6DL6fs5;fi#Zx#GyO?6JST2xS|>Ut15-(2bIMQliMC(M7$48C3_OHqDRgD5c%jTCeXQ*qvtK2g_KFceeC`@JJ z)-3;6IzC6(%ydK!l*n6i-<=(u)MJ;A>(sLD!LHF!9{4q?q?c3igvk*-c`UOJ(|L4n zQzRxbf%1uKr<0`A>fKq8gATZ>IIBtcHl{dfp-(O0^PHniypm$j2ap%@R8{5rRPzyj zFTpy(@9~E{et%Py-=jR$xv)1}MPJ7j_N%bZ>w(SV%^@iMz^-yYzlYw{e!oBL3on8P zBgut*o?M^T3&c%$dORM))>LIJroo@Z-W=}mCZ6=@U{we$7LTzYNqn78$GX^)^H=Hj z@~Q9=-1E@ep9>!mJ~CYA$LCc0!wbVl`~6jT((er+fImcgUA4#0m-Z+`wHUF$;YS?d zMM%|Rot`QubvkkWKCh3jW-9o7#x7qIKE~_EqgD8Xy5>40e<{=A!3P|tv&iqqi#+nj z@AWtP{YScD7lQ=)@n)o?+VcC4$~80l4;N%fZq9p%y6n;7t*UEve$`2~;ZvJg(jI`9 zRuw)O+2UEe*o)9!&tjBP_!N}cDdAJ9n48FaoeT-e+n8e34X?Ro?t#a!@1zjYxoxRmC5!UyLxkygKS7k|oTlV(xkP zt^5?f+;!(_HS#iC%iP2_`-+88T1fZ=pZUv_div06`gENR`}%zP9e8{J(zXCKP-nt| z@UbCX%Z+EZV(u;Q`ui5JzO&+2(asF7LE-h)RM8@NT7!m0f3!6Z|I$mEwWqHdf2;d= zTOmLDlzw}4_?++}4}w&o_DgN8LsdqLMKLTfdFo-?Tm&cc6A8foypz98UcWRcKQj{T zK(`}E2tR5NYSLoVq{UTsOdg$)refGpsBlBIE^|EZ*U>O+KD=CR`?Qa7vg}yutdE}X zS!|0QOCvvmWzRNwZBEy7hGsFNO&&=v|D1(fr)3ZA+Tz!LSv~ZNC{p~!UWe#Bb{xeb z51-qk>QI*#=>`&BWJ@;lzuF|(ubfC1QyMX!>Ovj@K7VnK2gyX{dk}-?C`J}ubd)ET zM+M6-_W1L4^ziVHwxO*YkDL!5A3iULg+l0^L}m7y$2heV=eW_`$Ilc`}3&zp&FONZV;i`IXv-PPS>_j zO|G=og=)(@&@qWzEsNb>d=}Pfc4VHABf}(qTJ*=TXAMc$;}3^=s=UmDI-NxxiqIn@ zyYkW=^L|~BK7Vx`|3ac@(Rr;u++tTBkFGl`3@z(S?^ks#^1fDDn8kjuj?E(6%I?I= zn5uO1HUil+b}gI5>haRzskZxvDxCv9JD@aS6PC3Kb+!(DxvporE2#5(uMHm?ZVR_F z%46A1Ab6lAAgoRjgPRl}O^fvhcy;QL1$ac;fmJ?)u96B|&CGzTMP}$4Z!&{{B{xEj z{!_=adb=4*U3gVhc!8}JAj$aw|vvFus)O z>8{p|*g9fC(I;GI{Fu0-YIIxE*)MGx39S?TL01smUJB(xzOb%ZTBg4`ycGVXUxTe% z=_Vz1pMc+#FCxK597VB6RVt)=AuWh|s--#l>F8cyiWPmv5iIHB^!u4*l!;nZvt@6m z#k#dX|2?M3dqk55NmgCkxJdAmhI^YK`ys*4wQ#?~kp7V9 zhxz{}L;gcD2(rmtvQS8XNC3q9I}I5SiGY}&?=_@ABm`pmpEBe?BrJFx+_j4&E)x4N zzSc!T7DS>T=I@n;G>C*jBnbEiLmoupptrz%mmv`%fe_<=+>i;8Na$v!*N_U4Q0O*( z?_wbrBC*g18NVSJBEb;9_YH<@hy+6)hI^kO9U_6y$4NY3$cOfr`@tnbLPP>0mfvPW zMnoba#%~!?A`%ka&GIqiL?kBqtP-LY+DYiJAMVa03}HBr^IQ-2ZOKjYwGZBeM1rK};r@;xQ6hm7?;m%JkSUQ!iSB`8gj9({ zN_6K8xe|$#{>=7kNR~*j#QWbjWJ@GgqI=O&2}{DIY7$2*6*~!+7Q%hCAz>o%62rgV zkTH>n=xE&kgCS)iArs60)MJI5iNs8F4;Ydr5;W0$ts!e7Q4`Dm9fq`tgiUmR!H_qR zxal;+{}V&vL;|O?;6Cm+A#);;6YsA(PDq_d=+p%Fgdul2Xxwi%Bu^xG;`<*oWKSe| zqI<#dLi$9)C%*r}g%Opy*c1gcOQ|Q0L=)XD$UQJyogm~L^6?ny#IMFJ|iuQ6m) zB%q@EoraW(gj2n6f8CH%k(i42y(bGv)n4OnJ6Xu8NK{4lHbYuPVwfSg-)+dNNL)pE z^%X;6wa2&*7&0pow_JkoN1r03RwT5dJZdxKRwTCCg!|KmWLG4*x(fHd zWk|0`ctv^PJypoBNPI>2xu*&V7KyL;{$WFgMWPtGUvEgUNQgyw|4l=V^}>Fy8b|z| z(}X081X+`CH=ibU63}deJ8qL^U4pg?_tJpaNt^{WKy40)odjCw=G1!)nU*Hfg8RpY zREvaK5VPvU)5T6=tzW}^ts&VW!4~`5cNwxR5^en+_kV6kw@A1};+7?6h@He+3SzDG z46&1di}7D=$hb(HqG!kLEL>nM>=-G=0g1Yb=5n+(|( ziN5H*(~y3V@QdI3ZA1P=;;-Z2{<9$gBLNu1wyHl%>?8mqq0I(E3Pu93vq@-U$iYYq z#`<`dAqgWf*b3bL4?`A4f-s0>wYXmHkr0XH6RwwgBo5>ANkbw=BCs_G{}w|gMj|n$ z|5JukjD%uzKW50qNG!(qerHI=NH9kIWy#rMC(&3p;`@Uk9V6iw#ImY8N9-gV8-%;j zkdToeY!lqu3>g`T$Qb^uhLntiWLx3>m?0-4F&W)oGbCjsD2v1Wq#-LKQQ0+c|IUz> zk+6*JBO1g`;xZBdo!20C5}5IQ1BT3uL}qmFGNfiCG^6`NhTQC4a> z8HvpxR@IryA660q@m&Rrp7Y$RYK@zN!Rl#N7eBw*TY$k|B9#`|{|k~R{pJp%VLhOCW5Z9ian zuN2ZY61M#m?&T|myp6I zly{TR?GWxK4cQxs-u?vljfV7%L~ksQj~ntg61jOWHaoFVNZ?2S$NOsx861h=mf`+o zh7^v3aMTBKh8&K>aHrw^2MtLa3F1Ok(*9^CLEM@uApqmMNYu6t?sSuo$B{U0Gu*E; zByuE>yBhAd88SH%$xT#Ae$q}txohD5q9K29tv?H_g$)4d1o7DG};f;zsR z>gc}Okk*l~?)_Cl3Pn2!>*&7Skl1m|{2`M+yhkFtkHfvVMM&*PXm_{yUfM}) z_c^%#Xh`lzaL4z38PZQz6 z{Ubw)M?$>s!2O~j$0IQw-DkB4NgfIEo`CyeLzYLPJjRzZq0WLRF8yr&sPbl9{UpZ5#DO0$@SPabekYKoFUuen4A4W zn<3p}9a)6%7aQ_Dj=}2S&KnXw678{mzrm34aSXc@_djSz`AEQbD%_tnxcq(675%2=}NfQfC{i7Lq^Sqs~S521EKs9gX@K=W-YyM7SoA<2R8=ZnRp+ym#G%=xmQ;U0o`k> zg@leWl|*!Vs)da1wrU}y`$)Br(tV~{i0L>_L~=To842pXQ7t5O->w#-y2q=9tnLTZ zLRj~cY9X!T_mQ~nFV#X`x4#>mo_EZG&WetNcRP*W(w`zKwn%?qj_qdEXU&A@F0psW(U>zqnt>{Fn|B z`cd~Mso$hui2dFyeR({T-}g8MlVr5m*D3F^6O!GK3JD=wwq)cT!r0fbeM;G8S|y@| zCfSwjhL|x)WLK7%jHNLc+nB*DzvumZ{r))5bMJkgd+xdCo_n8j&$-XNB*{qFp#LKh zO%r)?FCf9?{dSV=ZrmroL);gR|FFqYMZKw^ivMr}SjtlW2tlcO4|!-i!qOxk=&3FHiIjQ)Mr;*Sh<>3GQ@2r>7#s3w9PfPz1(Z68B{33Jm z^goYZZ20J3a3JhC9T!7>zHDuO!%hl*J$V#^dC$KsOe#z_$CvPT`FP!u*^FDoyY$ADBT4|cRoCmX&hSgnrAZ;}1 z5P5m!^FRNV75$S}dPC)6UZ~$cAq&MYAz4_*H5*y%=W7pSJ+*D-;)xedMkGhP*}3OE zNowtStL7Rxdwq#xRhZ-xQ;}3QVCJ)gY#(4N_*EQy3S19hm#-TW23tLllIl}&@T0Gw)vMrH>tw0(O6UHVbKG}I`MU7;8QK1s zQkAHnA(a?d%%NL?MP%V4WtnPpUK#Lz-jW4Kk-V=0M2 z6(%>9IA2$s87(3GTWtfgv6jCWPg}5FOY1&fE6BLQg9_pATj4;#*9wzto5xbT3r&9e zte7Xg9!r_N1Z<=nUcPzn;^gY(moID6lP$dq+U+Zzp@ChXGvX{c-UkZ*Ta-J)Pu&y5 zf|GZ8aLrQ-oNLPbW6P&HO-<*rX`RnoLGv7u4R-vJVr~^Fv3xdn)GBx`WC-1BHc984 zOkj9VVzL&@7A4sdGM@M5q-{Th>lP4sGB3+62GviKUbOAakS`l zi5{J$>ff=oC$=Z$ri-3GxLdE@p&fsk9jZnif2=Im2|N7j(kAZALFbJhxa#Jr9f*g@ zm*(|&hSQes`R#z8=L`6p#8#v)9zD0IIQ}*(^9G~s5WRf*De9Hrim!S9+Q&f0kix@B zY1AJqxBaHo$<5NR)qnQFUUwu_p0SLBefM^D1@CGY7wbKkqGVz#s_vk1bwhkt109ltAEuci(*yr24& zn$g>{zbSrSdi#UI?wd0T6Q#s$r5pb&JDmHoXIoqGygl%F!plX^3G!+DV5r%TDNpeW z_`&mYvl^RqA&}2DrtKN{!AMHzqjPl?M?Q-YlgjJ2J8T0OEi1CbgGBrZM&!O*$31Co zdbL1o5BCDs4W%_II?R2ssq>}L@r&5@A1Zz@c`DhdelS!o_WhcOr1!|@ z;I`L(OJ8?)zMJ1G2Q~OkpNCQ-!llskPpOJ%BNVs8X31pNTZ4KL<*PwGX`iWUJ!Ymt z<)z6#P573Q*`T->lG%V-cxdT~g&C@BUl_M)m`lQ=tD<}j|Dnbe9QkHCmU;Pd>crx(R5P9ko4HU^{AXlC%6uM7+ zrC(lwbcOST3z~PCiFBoc&5tCbb&nAmldq@H>mJ7xg{yfw?n!(vpqj60w{NW%8PXh7 zhv=$Z@BL>01EJu=jm$IsC8f;)vC@+_% zU7U&SN=h5zRRnP!MJh0rcXj^F(XL}j5`6!sK=Sk8bV+)0gv(CO^6l`@1QX44FW2&! zw^{7|GxpkY&ydTz6ZJ!}nWwq@@(SMJrsl?y%&$*%qRoexXCrhrq)Eqb>hu3N*2S}7 zLz1~EKk1Qfod6I0rr%SP))#RXos{K)N$ZQeD;HmEZ9Ey+^(0-H$0SXeuP;rRH%d5A zdrE<%+GS!m5ilzdIrMcDKqEk+bOrvM9NHkvz1B;Ym9+J7tv z$D8GtavRS(B&KY5#p0K(_RfIlY-xeJbbz=Z&xO#=zEHm1b^7R{QBWCI-!$(D4_<_e18$V z%Ihn-8qk^sVmXj-1K|Y6nsIlKQkMd$QQ?;`TqiY+b`uHW-dp6K>5dgG_D@QK{=T%w z)1JRp?f@UUMIbkQ>I4zHclnZ)dw99`)_|$0Jvd$Zi|Eb#HTJ({d2}$-b;632QY;CY z8=~}a7iKD z)NNsN5C{+vKuS_N`>B-$#Hz&HZAliw16FNH?|Itvck;5dcEky?fbW$SzV_TLNW8!{ z+wjhiEb2*Y`?leoz9u>4HHIfN3k61xP3=jB zm4v)RYj+WKiRoF*Zpil4bg9h8Ol+uf*YebQ+K?Xi`L~h>9pB+|+*r~D5+_iXmI6b2 zVJ9pVjgL1Acz|aTGv@f-zfZGEHvpjmiIEwrs_{qSO_zBc(PjckKZ)GJB-J$S$UK?m zoHZGyOh5JsTB63D$4@wvr!$|z-&sNzW(xL8lVnw!(!$>+^Kg{Nyq$%Syr9pix6Mm6 zlI-wl;en>gcEp6fZzK5-KH|6CgzV24+4Lr!aDll&77r5G5|4FgTb$`MVNH7V)CzL} z6=Wue%%GXL?l0(&lvT6R;`9_qL8_@~r2)q2r5JC1Wcp_D{e8mz5ti;o$!OW^xTW^~ z;l)sk-$aov#@ScA3Ea-KcJ8CJcIyS6yMKRi8J-#_{)8V{XL0Csf9C(W_ux$S@->K8d$8y z%=d!VA4Ueq(Hn$9f%p;bv<)cCmzD|Uk~)i-&*3_ zCRwN3e-A`;OOTYCpAUA3b+x6JE+nOu-fEYUnicToW$BiJkoS_*)BFNEz{n?S{LH)N zaW$*rhWrZ(>C0&_{@t6eSz-}-8wJVQ=n&5MQOFQq0WY_J!f!sb3uY-1KV67Ew{Gtt zl6)lo99@B=^HfPakTuXDFeB4o_@OEJ1Lw7bez4uO@NE~bbh#6A$DVea3$i@)>f+un zl_@tivIXNLRH+71PHA%}!KS;MrV}MYv zYYv;fOgfR)$1~YDZ<21-<&u^p0t`^%%)0L7BauIfk`x8gd2jN@3o^PvL-6*lPifzO zznSE5>558AIx;t=MP5wlib@ydao6?fI%6&xy0k525#@-J$)u)fvb1ihZ@_Z_1pxko zasH+{I_Y>APiFy9WCl1Sn@LHL69vJ#c0l1l$z7@Bl7|k%ZB5bHogJTCcGfyxy$y%` zRNdcw^6D)ZHmkZH{v&ue{J=fOH<}#$4w!AG?EVZM4r&?v#;ILS9uA6Hwu8k}_eMm` zBRc=qipnOBkfH<&YL0cp7Ivp~iwl}eJm^dsk{OOO(K?zEx4}0Y*W?RLTpyKuQ1YE_ zw$kEFG%2C@swam1xc7E>F8?R2rc?jdmCCmc?kuEc3X1X&`Aqqf z)l-9pvzTerv{e2uUi0sJhNRkb7hvNAGr@2^jzqI4(}ctpRQ&sd{s|;l%FF3=J0n+G z`HWt%oURP=14im!O^HkBwbQrmmulaRaCdwh8&6hpYTZ~OT{Z}e9M8)*8jQHEe?DZ43<755({U7&k6bFwxRk}%3-XFfDD-F5tZ)g}CC?(Nz_DIej zrlHh*e-BTM_s@Y_qxtj&lxE+fUKmfBi-5ONd7 z6p5HH1S3y)^I_?{cP!u{%)51+P6-1@G7r;CVfpf+K1Sx*WU*izndWRVPm`Iu-K673vV>~?SF_~b6Qt8|7^{aJ(8x@{D>?WugS)r8F1v**doK| z>swLOSodOgY}=V63~Tdu!Hw%etA^P$1))>ExCRF3N=jGb%Yx_FXWXu3~Bh z%AJ2-Bu*P=Uq$$I)b{`jfeFnw9dSmd@y+$IjyN-)B(-&l#ZS6%sS+vG&{sAJ-`F7r z9&c2EWUo~leo#!#Lfl7uH53Y4iW0D#X-np;W&%!e1*ZId!oi38&4M!mq5N^>T)y-5 zH5K||zvZu{W&QL6IhOea6ra_~#Fi3cU9lM@%9jhza6pPnom-~@k@)mWE zO8KQ@^n5|7-xqhSynEvztK{Un9~aknhZc9&wMFVcrYQYt=xL4uI+5GO*;-!8-9GqU z>ox6%^v3btTIAhYmVsOwJR^jBhV0?(?{Q4K8dW3z+SGn6y&qHtxemFxYC++Sf>&B! z8C{!PdyJ57FG&=PSDGxiW@aaB*iYmQf8%#YjxT&MG4BcK?vB^-5#K1h{zhyV!*T>6 z+l>h0T8=~&h(0dyimThnSm+)RP85A^em16V=XZlu@0L~B^n}8x*)P;1i zcqy1S(r4s_u{US-=bsT}N-y1uY`KH6)W~OOD85H9myw-BAbP zzw^Fa%o-g#_SOO4&NFc9uX=+J$IOj{JcZx9Uf71!PmSiS9DO+ zWJd_*MztGVMkIf#UzDOed>9}$gvs^kQ>iI6zr4vB(8*rT$=9FV^uW<50peIxCU~l^JEx~ zoD3s|2x5ivGl}FKzmIq`j(YQcF@%#g+AVJ+U$wkbbM0?dF=V}$LK-Zd!^OdL*pqL5 zq73C(I+#q@pXjOrg z@-2omoPi;AS&|Om)Ikc#ycqH&a$oh$&raaM?NqrL)M)p42N=-RN=LgvhVrK9(QbYW zgS1bMM(cCPq?5q=QTunK_>AP?Ke&eRhL}wGVq~I9G(RTOI?^~^3{zS&bDfooPBMTB zDD+TB^~I1gMjes`TA0kfDC2lOOs1*>SF_|hP`}5cmJ?gcU0Mz>7g`IZix^={h!cst zX@Ducw|owhiHR_dSHonQ70+cYp9i8qZb3|D@-h^8zJy#1DFJHZS&aM=8MYa zA?E-dTHJ}c-XsB&9ExGgrKm)kV=^WG!zwk;Zgw=1Z+|e_ons^~x3IGvQ3?>NipeZM z7#sFdmhHlAUTJsuvyv$yE!*V=z1rwGG~Zr+4^v=7K2p*q?t!RHoK3o{QHll4d_nN3 zU9R7QWf&%N5}*l$$y6#v9%J!3fcNry4deANneU48Z?YsDz~PJ08|VK^zL zBSmjGIDq?%H*U7M!P*7tdDLU0(fN| zz&gA*8%2`Ci1wrcd4(Lnr3=w$T14-$Xj2@8G+GQX2ik}aqa?Klj(Jyk-R@`Nvq)kl z{!Qg|7oCJ>TPk+=Ky;ul&RrdK9Q*s8^PArBeoS;`Ta;0R=)UTqNDtstdN<$l@u}x- z$iSmBMN~WWAzjfgy1rLTB$Yq&DLBAHESB-XPK5)0}#a0V(U&%3#^B;WDF zddYsgQMGo{m@Gt49df}(%jl~kp}3jJ$>sMmh~UX^hnIqi7m3M(Zx#S2}HHw^~rw9kAcL)*G)un$SsIYSE{SVcG|9MSGwzd^OM^Z;;dOY5bClEWKk6ehi% zE_9oB6nDk?1&ByWXmQya>qjiQ$o7>h{{z60JUho1G6f}Q z0pfeV+?!je-bA1NPiNL2*Zhnth`v|fR;I2oQ+}%Q@9cZ+v}1LD!p3(#%bKizi5ux= zWROV*FC1IF_NLz^ovn%3OM2ROx6?XdrUnSew_DSShoqNjPQGz#3X3u7%1f-}|1RS&Vp&NJ#nwzT z=nUP|yX3k^$haciv`T-1?KFgY4_j%O@?T}WD?yP{$F>oCa8&W%ltlCAvlwP4%Y0ma zoQP?C|K;HN#3{t-=it^2sn7X!2D+nb|;N{}aN+|k4eTVUqm{lI zbDhPrE)YfpIh+z|G<&_AOavtkqF^_N_Q3RF#e{1yno*)FM%dlgCEKo}iGv2|eGxDW z@Z{x06f*e6(B88x= zf;KxR(e08j$IZ~mS{5?R10eH&cXx#L;+S1GG14q`{2<6_L!(4HKxC?K4Lr0Ivl&64 zyUFkjk&&<}xAI^2OGjze*!?yuzar_stJGfBUV=m-Wrn%xc%1Cd`nlOK}|IK(qdP$`8ntWg_Vnb-$#lh^2asNwYSLr|0+dJOz zwlxnV_q)hg<4c^)<5{Y*j=A3*@0e1OZY0ceF#0vQQUOE%Cg0H=G<}D-y(vw}w;QsF z3-vqtM0l$bEVP93k5X)g-xIP^EDzRLz;n1@^5K zf_*tzJGlJR?26QWqQV|#>uU~v{t^XcpEDwRAX%>WUb0;Kw!|~V61_ofHAnAFZAzX! zekt`E%8_lyIUrycutTzvoYQQbR$EST@{^U5AqKXoS3@7C60x>1Xo~$7Rj+8EHq|W` zraoIw_N3(Z;3+}X3R|AzR!h0!cDEKEM}K>Mdh~QbncWxDpqNKP6`cr<5#_PxhRKd% zsY{2`_V&nW<=iJjcBKoKY*QNM1t?GSwLc81a57eM1%1&DMq$NCmWhiWby}-h-%SLL z%g1O|u76m$gwtu%Rtu|0azfbkOOSNhFw9eu4+i`x*$)kenIK#!$ zID>cEs-1=Wj6bm*Wy~`{-buEb_KV-$utHxp<9ywUM+F+(owehkLzCaLw@%r)#sS#SRE z<@hI0u&zZe?3+mri0O+C$jM*_dZu{z!)M{&hJusrkZv@uk4kd!b}|1`$B#EHq`J0i zPPA7i)B2~k`eeAu=h==(&w_ex>c@j>zfa5v`%V9mskZlq^*i#od#`9JkkwS{O=6cx zHuwIV6U%^`KaVZ0hwGJ~Ob7A6FY_?byKdfwZJr6sf7|*^+&e2xqsH6%_4>L}5k>*? zKDbAeIC1O2j>_aHgiS|mu%#qS#>5hjvVxMW&2yz-6KT1MC!pj$e>8$DM`%Wf!;@ct zq!ryR_sXCVw`2*1Iv`#1Vj!hPCTBAupm<71*WqeruCP1vX8hE3WW1ml14+>dEmA6 zchH-2GK6^TT~1vN<2Z?{qb1O^h)mDr_M0D#Mfg?ML*T4QzOW&tG@NB{NS9N@E6rWB zz^Aw>9%*Z?m4UZU@^8t%^hG0lKYM8KVf%<+?9jIV)pXcRQ=tZihaj*6twHk%256@6 zfPJ!l%W8#poR9NhQ^~4foBhcCGr+!&6>ac+3x;>}$`KMD_G^B9;}UOfIk|lV&b|mElmvmG z)YA=kao{VZY?xJp{5r)~(nHUvD)r#=ggNnHHj{n1c*A&Jn(W4@FwuA+;KQfNkc87d ziAOx>6B#^1eNMHU&@@P^@+uMZtThFkhepC-3moJX4PH;Ix9DwedF0f%j%Y?z9rfC8MRv+ z0rz1z+HbZp6$3UjYV!{WLdxxw+FkWp?tCxez$C>~^1UhEw(ji}uEHnNF zNRkxm5Vf-V_|j%x`JpiLe$x{K<25aG%y05d)$JW9aj|ClyxcxNwWi6nw$(KN!V=qd z8Na1YU=)KbClVZ`lr1~SF9bYkK3jqHV(?dS^n!7+mQcf>iO7~bCV-BaTbzC< zPLlM1@ieTr4ZhHgO|RmX@Hg?kJ2c|K_2CcuzJIsw<@DW90Z+*iKF{T|ybU$}KM16J zy2v<`Dr=nk3QCo`ocj(+mA`Ei+#xql+O=DuXU}I;rT*msis1*aFUk75GmdiaCmuUvF=4hNa{yq; zO{+@v5b)&w%)R-74gujU(J`VfKsnFzJkfMf+-6c(q9+F9L zaB81jw{p^A!YVrL|0gn1=+PScmcV;s8C6+-`GJB2J;6Ex4eS0g@Y$YK(Xj)a`gc>1 z2F@EvppFj^VgTtg`73k}XT{i;dM#v;^1&G~o-d=Q7ZIgKt){ZqY@@Pc#U@ zUs)|>t?U`{HI(>&Yd+FY^X4|Dt}^6bLC?vbfva=29nwIR^fc0|{5%9bvDZFFO^s_` zmkoeYrEcV!;ho5Qg#G*3_vO4f2h{~rJ$99dQJtU++ZGsO#xiP6eeKcLTF)zL| zDmAwnN|kQyPTe$3D~eah6()UZFNy7rdi8zn?cB3W{K<_31?vrgFwy1#@|TdQ6z<#6 z7yN#IXz8_)usAEZZ|q<89a_rWlkuHJU*Aj`b*6=HFjw>AA5ZEK%&)JJQWMNAcY9X_ ziA$Tj+&+3{GQ+$T%;G7rj?iPANG_NLHJCXchu5Nz=-L_k)i>dx0J$J7cEuF`R!S{D z!I%__ro{o-upxYKvj``EL7;-n7ghUo&+*FD>Tox>#YA;g+Q4^L zw(07eaG1`QjH&>N;9kiF4`Kh=9W;@rVoG^+#RKR99=as+u|>;?Pfr_9LC72#Suc7u znl}QrvII?XxW1mV4B5Vaurht}#LbKYAzhRN`_k0nkL)SCf`hwN0)?;;a3RbtW1v(d z9RS$^pcn3aW+MFy9PQRI;o-Sy0~mXtUo4GJ0HBhsTk8haMNJ6mhCk;iG@ybC4P?Qo zJ!^u6u*x~bA<;q6R@2{{{awO81k>Lib-=o%ZqXKo(Z-V?-O>pNkWpBdHU`$Eox9~N z7{0V;el3)z5Vdmlc9)lTN~_1yLv5_Il8?f0u~Z>wxm0lC@M}y1AY;Ibo;Khm zk}=>eBAsP9@v1gsRnj@ZW@@qiICR^?3Q8Tawp^;mTkHyX@_WX0@&k>DX!8(k*lr(5bfb}Tlb zP6nM&2Ur&frXIgv@cIysqW9`rmA=-@nG5LB^a*dztPDZ7ecnOKvEpY}R;F;e=!Du} z^j^(nG|l>ada|f);Hp3&qd%=myGYQ-N1DKL-*dVp0ASN27=H>2&?dA`Nil8R!HTha zb@PLD`{`)|sru#4HYcJt^gpz>*)>? z(M@eUtn26c_(&ULApk&b58b}-1Aqv1p`1up@? zJO2gDE)Bk6f^d!sSU3E7@!_^)C!u&#hNNlwK8(N`R^i}S!+eDH;ls=j1#JLp_T@@|RwJOa!>i}047I$wc%U*) zGVPD|E}&_eV(c@kTbIqC+m|w-+ZKRe?&B?&GMDm%0S;X<{7^;CNnfR26V-Jb<F(b3J(iBm?qzUX-0(RJ=0k*y+4C5&2XL&V1|R0dEuZ&ar zE3eBI0a4dKr)Dh7){iWD3g%e_sSN2J2C&85-_q#l=&wCOSHm{&ARibL!|7}H`-y6` z&H@ejS}E_hRLD^3{F%Y)9606bqW#A~=Q%i2W6}Qkkn`L>AsTBZzVAP^rchlJ%9){y z_E&Nd0h&yKFvhQbOz)tRLOFgaIMl>C;aN-6Q};VC_r0fD=s(s51oP;^sPO}>q?-LX zae5Apy^A!xSxzGbI-so<}sTT7-X0cXJt;g|OIZk;CVeo~E4-?xP(DQgwi zmL!B+hREbTnU_>?c-d^Rc}+<0Sf`|?(QCsG!4`;Xuj-LNnhC*NG6j^Kby-jh{NsoU zq+iBeP)tw5B7 zOi5Mqe%~_YhxJ=HYr3Sm!b-Ep=FbgWcIIU*H@WNPADS_;aQRz9ujfmKEpa9;nc3m* z!Qw;>nF2xaWig+(hM4{1I3vvhl|f3&Ma1CL>Fg-L)mfl2=c!ZU`!*i4>WnjqDNvic z(|H}?Kr5G6POFj~@c9^zq3fo|){C0{??9j->}*Qme5nw5Auv1jxt7a}%ek^<%xxQl z!!&xnm~#?m^bTKt5@(`^{{>ttt+M0#Q!(+%f(njU8919nqKPpE(yJ76{c4yPb|enw zQ$Q_xISXB64=lEF-LxjzO|dQO*eUT=noQnZ_rDx!+XV)3N4%ALMvdz^+^G zTc}&_If-5?RlXksbAU{z7*-wB9T(NxMLh%-1nVHm*L%XqUC&xYm21aRnqz_Jm&)E= z*ZnW>S_DaLw$#O)f*kNIKd5I0&&Hi5EzcgDY~}9PJU7>gYNVXNH&({L+{()x$7HA@ zBdRxY5aG1w@Vgi`y?VD8Kbv9o5fY zY5I5S!@;MNck6xmd98}z^DE}Xvnm~Lr#j7kM^vx(m4pCPd#iWc+PyJb5H-2dIUwU+ z^UT-j*|AE!R=*R{v$Y-g&fJ}xmfUq>BLMT%uWx)>IwozKKD0Y95T*58V~V8uB!yGm zX1+StL}~S9Y!1*{QIfTj?6&BR_33%pXzY37>w`wF;B2k1!C$1N;v@581{3*zId^4; z*+p{0DJG_;UfsHVkwL4f|9iQfd3kn-Zm@z3Gl-q}aF9R(qIw2bh91n89W-yAneF>l z6wQUqlpREGoSDS{4O~>_I6y2r&`)}@J}Qom=?pZ;d1f%3lJsePI=!geYhX@e_HF$2 z)=S$TqH}hgC=&ATMl=O4m%9(-U8%jfDN0SHhCK=!PRiNIHb=)`1MuGfMQI#BgdLB7 zcrv`e4eL!Dw-lE~kZHjXZvftqZ+Dx+>nSOhO%)IRs)=(+dPunIxWTZ zCNfx|J|=(ny=H2{zufMYZDz;)&&Le01)&&|<7|cs4YX!XnqjsaD2AGGS1^uB^#lk!oG8P!*St**@_(H_^l^HK)Q=AXuwPnPwSPY&eU)q2Vn_@2PpRdv|4+E4q8 z+$lk%A1sdE4g;TIAIWeEFl3F;^FmPN)$)CjhqTt#JvuD&59ac@=cr8$ollp-Hzo?hi6kM&cxBdMV3Dib){! zpbJkcC~--`?iAk+m}HN{=t8MQ1r`(f?hdxg6?*3#ZE2JjkLUCbHOwUhUuhlce;Qr7 z0()2wvt>|NPohf$(e%MQQ5){Jv}9C-1YEKD8MurK%*0Z)lCBwFV_8xx9)in3beIY| zXx@?ndx9^1gi9GqUwK~+5^=Btzbt$X5lgk%cQhgFFLScWKzSKeNeUoOMmtZ#TnlT+ z-<>w|Ux+a@lCz%+!vBHU8NB56q({kM9vx48ggh5m%+1+(*NC-&W&g0z{-}cw zDwEZh^}1cA7$ehmyG%9^m|IJ-T{Q$f8FIj05@o%9&Xkl{bmM@D6X?mYO!9_NVVHinU|}n*6hbW2#VXczH%=SnAWV?UGKA6 z|20JFsnwNI*%EZkGvtNViGdR%jSY@A*c%BK_FaEH8L7QtaYe|?@k-yzL}z2SU+vMA zTL~05`u0fm?_AprIRyh?8XfA}I?><0Q#FF=ZNQWTozCrWw4Gc5d2$C%d9JvZJKAB@ zl5Xs~$`e|bApj#f@NY2XAtJKNdBTo%%W6rNTiw3vjWo9A&JNiS`%RTnLLkvGBf=vs z!Fg;b^{ut#q_uc^cSqC7=V?_rVXU^DI>3cW8^H;3wah}r+HoC|$71QjQBYz~9!F_) z&aP6|Ewid1jL)az9Ko0Zuzt9M)Uj@cuB${ToE?-3vYfD!Wb3W2lvU>GpkqcvxKB9~ z97jrc0F?S#5-@8NXx{f>N9hw70RcH94=eKDF>P*lIC3I;rbEez|gH5?;Q!+L@^ zd{z%<&?zaaKOsZy!pt5h`MZmnK^nMjgU!3zl%&O?-fa7kz4@XtV&mP%$Vq_)g~Y@s z(d~=&Bh&``Gy9K8HAuI8pT18cG<@kZWYeu&E!)0I!97ms_T!!Qn$whc3IKL?A9DsZ zZTq~u9_PF70j{ILQq0=1{k35tMPRh{xj1Z6Lme6B<2Ez%sVY7J?zy=&)38(77>N7v4De@! z3GOA7c>>nza8Omphj`=N$MY0`-=}foR@$mHB`LND`bQnc5@Gxh5i^l^)ulVwFQoo8l4m-KOz9ALy2 zt95U$iSJmC!BOwxd#TyeeZKB9=A72~-4z23NUGIl-0v3&RtV-(gv>%x=P!fn?$??= zGTV`^F#__H*`rzR;u!5JH3#ifm#TOdL}E`-_GsH2ATY!^)8NfB3wNT z#dS3H>Yt*+aI`|MZMfI@+gwb%dFvU8YSofjPZv`nz#13tQco9eKX(57{+~FpNet1+ z8+MRCzxVjZEy`^iee*{)Rn|bgCY)H++!^p`<=DP~dWqs})n;($Cw%3vA2OIoeB%$9 z+Ly%f`0+beJ1Y?(yFR0{_2>Gkk!bS7VFIF3RSrR(P+^{Abf6!|mP%OWo7h47WIMeZ z?j0Z>be?t*MvkNyTnrVMWr8ASqy>!z%PjNtPT+0PWP3yR#I;AW98J4<`mj0iZi5U(Q;*-pri)4oEKMUXFP6A&kXNuZI|Rlxd@FFMSi z$Xux9uiP1!c!U0>)FJr$cNr;WGFKe3G$_?aS6tul{)=!_;`lqMif-LB#_ z@{LX#TRzBTL;La&Xfo~yVbfottg{KwHsrL`h>Act9re5m&5wADe9J8IPxwIl9`cf* zgw0zL3-R^0yag&4&r+cC_?%z>=aB{GPhe(UL5l&(ykaf|WY#2Xz7;O3F@Vbf$@YMC zIbX>LO}^}j=*^Rjfzmr~qsf52{M?fguIF4w& zd5p064pIi0x_vcm4Ahoq294=HV#5xXDdRNpIdgN6=jJ89<1MgQNjvRz(L43$*^}w3phL=Ts7ChuI$7~?(213~Fs2=qSD)P{}v7P+gZA^`9&eT*BN z+X#)pYN1>G1k2VXiE8uFSTVwW%}MfE8~F&CGDtIzGgrO#s5(#>@G)#Z)7>=FXM$6) zF{_*K06R-wO&=5Z&#<*mI%nfk$J{=K0pWA?fwC?GwU7jw5Lj&M3?2YTsBDr5fCBr6 zWQ+-z0L;jN9@GKrJ9=(7Nh6H?U)3wH9vt{u_fqcl;Lq92Mo5(THuE_vY8+eunsOYd zlN5k{cGB)QOj?cs#S*1b~f& z%EFrY8#q;4++H={H8esBz}b4Ym+}BG4A_{}gUj*_g`KV8^#U=y#sOwF?6X47u&e2- z@6HN3!;19=Gslj(=NX}mZ|xN*Il!s}DiqIH?7G;buNM3iXz=^`8o*Zq7z4Oo)YxMf z_1(-dab=5LM-M;~8k3x~SjnzmQ*v-$<*88Ll|HbXgz=WuH1l(c`}8Fc`?mll!8>Ns z5J$__0Wpu$*t^n8$t#D(;I)3zQJ@O-K|t&9;_=jWXN^A?l@7qmpp65RY<%hgBY1^2 z1n>eD@p!@v?iBNSTD$KzKBz3J)WKU$3<0XB#mZwqgugkVTNg}id~}7Kv%GmLtRyBM zltbx##^`;rh7B7b0_Waas?E`a#t{6`#tXWH*4B~mi^U4R1f5ZG0u|O;Qj(S{j{}Lh zhCU7d$wuia|F1qRA!m#nxWW#QT~r{VUM@N&27d!hj}b!e2Mj}Q>Db6O0B!;wKs9Rx zIxAe+#w_wk8OIRp9PSOOFnFGC*ZR_SOgD%T(|+& zlZ_BBQ}E<;JQzLs-~7%PE(0gtKSJPmL_z6<1b~tRpI%CSBik{Idwz9J0rjZ}wI2asu z#(4cFO_c1|e8X2Yu|c@+_9p7)3co!On|}whbNVbMTB_Enn9Uzg&hIq;7Q}sv5y?hP z=gEs?KLN{#DLNXcn3sfHOCGq*<4|z$Xnvs=C8j9Kwb|VKiV_o*WP2bYexSs`eR;3& z3n+0((X=rlK6e!6R_wj=* zJ2lxk1$iNDYUVYwn)5rKIkRHQL+kGi69NrzeP;IAnHYj!MscI9P>xc+#8+PbE zCOhjBl>W)%)BOC)+qj-rKeDryUy5^|bFL!7w}-@*^V7$^N5Wj%z1lJJvyaWhX>QV$ zmMeksNot%m{oS<+W?Zx*4!aFRe`Jh(bKAXM?1e3upYH4sBi?2>dwWfsdfuecSsmi! zJw5f-@cj5&!`Kg(5Uba7=BJ)X;OZBX)bp#)ev$HGxOtm2H_iWQ>=5sZWtjpzxs0qa zJbw^{tIa#Ocwu?F9dMJ`(%v?lT6Tu=AaJZ1LqwqSuPuMH&X*E%M;v%)lz0RDA~-4CGpS zQ*_#V$KUi@ztXc0WJIse7$m=*@4SSjL*HEN9Zx^x>$Y^W z9kxU(I`5=p0=2}pvM=+NC`kH0{yPR6<;_t*om_4AONhMN-@}`8TvlrVwRqC7SZ@MY zo!CR30-AhC$fjBoYJR{P zg;k1qKAWWY5IXahHEKpE_W7(_VEAHHoxlxLDYu8tUCUyX7dxi7_^1*8*jMRtmyGI( zuAeG(?pc~C1L;eKQ4?Pf?lW`J zDzl;byzc2o^8=>+y!excTyb7}JvyRl#R}RxZLUcbAhS;?dD*P^4}I z*Bz+XeaRk^nDs^3=)7yXV%MPi%dWw-CnRAH;K=Xjl6DuRAnR{>%^;}Z&@V^A~S7!C?rzGAXt1e>UapEbW+p24z@B|1@;ucW&|K=G-kmtH^ zoj1v+$Xy8%Xh(v?5oWO~98r%qK=^cJ-94{5{rtnKC#&<+^!)EADj#KVVG3^_MlBhS zo@*A5)pHxn z;O*u$r%_P}?cWlXtoAd+F5cp8No={oJ*X7LH*l>(+tm$Z!D9zEb**!)4w2yzm_dA2 zyrLGICQWE);I8^>xE6J1fca+8uB5*~qE)=w*#7>@C4*-f_jsG~bl_TEv8xxV)0t$x zB_D2oPt~e}6{WS!Auri!fgrDDor&w}-tFrJYqEO=1|wcymGl7P?sxU@9|j`_l^?#9 zW&IlQIvdR>TQ4~CB$b{OtSfP=TTBcsuGYh&`UelLD{D>|oN?8=a`z8iIAT226XU zXW-YPE*2N!h}W;dg7vp`1`}Ev&J_e_Qnt9mI(bFGYy#L)c2`~o6JP0-fygRdhXD61 zDjlAptyIj&fd`7i#5AP%@?*3?mcSf*pJ(-O$fG^SW2vUSKM^fw&!cV(pGk+Fo3E0G z;3Q}w<|mxwp^5ni2ULS+yF{gd1odPUgL89&3dv&xle&kWSn5x(GWKpZfj6Hve63gy zAo*!7_Y62SYn%i2N%b1=`C#bwB@(QMqZ2F2eKndEPrT3ac!HlN&Z@@IoI8#`nKzD~ zqv5%KVBg2xWmCzo*l~FFY4tqA63PxCz|rjnux87<7o{7wNiKbuq-Z=^Q1T009UAX> zk`CRxP4e1_UYe+~5JS6mDg;!VHhhd;!fMejO|UU_qs+L!S|x+NS{ZwM-apmH9SiR6 z86mxryHV>+uizFtc5l1vc8R8l(&Vmz_2y^r`4zj}J%VIHma&TM`Fz}I6F%;=5dpP* zd~Uf)#A5{!o9KiXtV2nYIo#@xNx;X`<{zT?<@;7P@XU%MdJ^oqaVM(B;ugBh*<=Hh z6eRz)q*i)y%|8v%XRM1NHokz*FC^~nC6#^6GKMCeHgp%`vG+qumR*{3;g-o2C-+FO zO5;?tPvU7K8*c6O?h!XC2|Z!6(P#?C;>UJRriXX;>XEANNHB(XBw$`kt}Yin;fXwL z1XMlTs|PX@ox~nsx*}d5TW)x6fzu}WbRl<%G-Zj*>h?TnqjO6`ZKZ(33MGLH$<+9Z ziRAbT`L{fMoh-d}c>@#X!m7Nw`TS(uc}|}{@z#UWJR5pDQv24enpQ{zAORXp9`fn>9mzaM{R z{ro?(ytLSm5-cxTtDcktU1eV-B-TW|uF+L@yY!|bt?qR_=_{0Mw7Lu|kZQE6d7_S@ zdD8)E&?EaXbdqPsXY$f~utH5L9%=g@K%7b2*;{}IdB?cK3Z?d}WQ*411aPNVo31fiDBh79% z&^3;jTKP`sWI+1?JSeX(g<>kUdO*PyFobOdoI!#ucT`>7X^%3JKAq@yiFGzt-ABjP zs#S?VIF2ojni4;)o0%QbR`B4Dbf;U14aCAn<`>iBFoCk5&2lNG@yY?APIz@j0_((p zH?wWlv{>i)Z-fZTsbl9wq?7~>YY;x+ayr07o<&9{lCX$CE*%+y9nYCa8iw;L)U)c0 z8KSjzro2)))o<*ea;9m<5$KK?QLo145&L}WyWnZfa`LgwfMffD)nWcte%w3qA4CCH z?sKdI;!iEveMShQgeYEZb1fRMQVPtf(ilA``Y7H1W4uctZCWsv&dHh6p)x4~Dp&Ow zGi5xj_G|njRPH_yF440|*UL)UVGVqZ(b<2(1}w zkIyL0SjKg=HZFC;1e?>p4w5)G!mpx}z0^*B1wzic=GxeRM`yY;PDHKES0WW;Z{D9S zf&0)b@W-PkT@)uucuU~^#?pKA8;L3<|K{d!8-cw_@`pWj|6i#*bLpA%*ohW9U?>L2Jpf zmnTc%-X2QZo1Z>k!0XeKPAO1HS}3Mz<5q9Q zC)IGS_mG03{Mki71`wTW&$%`&(km|lMHobgK2%} zo%vpYXmU@*F31O^Vyy?vfREetsL-OPdMaYD({}JMRlDVfWN(Q*(1u`7z1$(fE}(b5 z91%OUm$)MZOO;l$XL&(TKHhwd>Dsv8t&oa+1Uz8*9T7d%lK_>1`AVZ&-D0IB>{;|N z?HjFiH->A}vlv!ES%}Cfssy0O=F-ge22U}q8x~zs-P(E>Ik7q-?ONADuE6ry)_gzY@<^h&y!L2d2h878V#?2so=}AFITT7zWlj^b|JF?4<&y z%Ga;3nDRo)tXhNA;x*7rH4E5t`B(E_{i{FR3ff?Ud)zo5Cm!>dmQ zIT<)b-S192mWZcRURTqz6|!cW!^)MnRHlR)S^wM?>&$D4&ugDNDO+&> z_ix;WIIb5x7e;oRK8p7D82!ZKwM=d`aY?-o2_Aa)&mHbGhk^KAn%M$bNKdM($=%U;s1RjOLP zTi+)Wli{M4?BHB%J8+jXRwBhC91}>TomtCcp{2%N(0#i3%ogJB(wafo}l zSRptdH+($weWyThV7#NZ{Sc`v5%Cj3&?IZ8*V1!?!Z0Yk2;H{?UXz**C2_dFQKMJ;13^LROqkA_>68v+%j&p+QE-=iE?j^SzM^ zhgO|!`36UoO6#3`7cu+}0%cQ@X4AOabE=qCI^P{iKR+!C=bwMdhUD2)6_A{c8ZcE@ z|E7ilGs>yv<_H$^aT59J>fytOU`i))J~Q{S7FXD zlc&d|lI#Ans=j}zliLTu75F!*1#+V_%YPSph|MRiDZDFFtEHRf;#;68A&EDRwy}x> z3J(M_()#bQD>?;a=EYmNNlFPwdTG~yCcg#jJe5u|dEEFfCO95)!{ARuQE)|HD3K{h z99%y)d)vC?Zq-~siFJQf-{c*+-+ZkR28WSz0$yDF2Nt?|*4<6bi1m(OBl&6Gf z0WBp{%1!=EF|1(~B)ZwT$D?P-&3xa=zBH#%Z%yke7J{kwalxAP0o4be@RlKfdf3l4 zZKLa3d*}gQO9Yn!bq9ClK#03?Bs{W%v_w6V${G4^H*^z`Cgr9a9SE^jp30H%^K13? z$24y4FEhFpxRv0|@=b>Ph^?R76y)VfqP^2G(KgRg;?wXF5oy?5)}g-C-)T76lDK8n3d(V<+x@P;S8EhaUrJ35w_E>0)K3+NyVizQL3=Sf2pbX~rm@J) zgFq^E>d7WGauf(KwP`a2I}su3A}`^fTig6RE2vhFxRp9`#|3W3UON;196t zq>$sjJ}qrACnj(HPiwaf`DiGW_}aL(!l~l$H`1%|p<{Zp5qg!KNXMCx+}@@4C0qf@})YUI23Y%;Rm>6 z^_w6wUG^>kDZ>h9F)qT&^#`d?j574yz$fu1e#v^>h-}`Taf8`KCTT*3f_Juuhre)) zK`M`2!MmBzO-63x*-BjCr_ss0&7^V<|CgtIj`2!We%d~6&Fa=AoRzS>wg-?(-u&T( z#rf$PF>sP^Y^Ra4{*x-c_;YW6?PVtaf|S15w>9seH5m>!ig0FUF4Ouo3P@*MwKJx_ z&pU2?Qgf4;>ujdnpE1qTq1DCYA6L^%TtQgryyHI0u7H*_^DI}bV-_=~pT^s?%@-SW z!aSy=Fja5)nd{Zy=`Dm?F-jB|5lj(oP$pfVqDbBVptAr>=^`1S?4)h1$c z#om0V-Y()?F{k5&Wgkb$!}Xd&2qk3RZiJ}~!8OOX<5^x~vT@$RHy2Tr?2q|2NQU_M zz0kg>IN|73biqW4DDTeZrZ_NK&W{}VM?(Nm^{{OBNy063f2K94sXJio0cRZGxr4gE z7KK&iI6n^zpJzjzW{A{IOol@BO;CVF!_;}qCP9{GWr_B8ngG#v{v%gi+SvTm#y&FZ z4LUlRm<)iTfp|nHG2Pf_mIfu*@eNNW%-hK!0LNNPAa(D~&F_59I!pMpYo}2DjT;Fn6jS#+92GTJc9F>6@@Sk#>~3 zPp1C*Q9-bjoAnN|HKg52MWOsM^7=gGYQ(K0W0%H@uQrh1Um~e|Xk%Xtu2@;S%=X?i)d}h z9+L47p^_}ul_7^v`BXZ%&ZX>1q>T$#MDLONOo_j2I-~|xtmHDKh`)e;@4;B9_)c9$ zp1+JJ?fhWCreZgVE;)Y3gm%S7Zwo99w3J(W`0ISm_$$p2(vW+@V;qL@Z(#NP+N}a9 z;=M{{%nyoe+dFFU{&3~(1bCp9K$3H&96yL{NjFz+<+^xxA9mF-6z8LZgJ*P<|=oxq*dUHrO5-#11swL*u7f^)aw?bin}P= zft7((6;aLzb}GPtIt)|dBM8XJR0l#_#*Kj}22SIqKnMAXcD)d)KY;_{WuQPSsY(;D z1Bd+io7uFdA&LXRP_L2tA*_BitJw|#-`+wYOybGeU1nRTZOy8Y1BDEAwxUNtC64JZ zwFP^Ra(eDFHb3n$$IWdzSboWp*7KIqSzr*78JEyIsod+ZLH`TH}}(JV{bDaTf=i&a1e=rZq- za289(4tdCE;x51r_vO?o-ymId96kMOxcvGWsyxyiP9z5uv>>d@vH}BmS)?F8fp%pA zDI=TIItc=kh0D4TD$r{tg(uA+0*G_QD+?K=X8W@tvbO7HeioVJ}F6i=?F;h;@qyyC^9`_ zkm~~Y&vpK(B1Fd$(3CZasmd+HPp zvTYa+t`OrLdS?W%Db;K{zgELvZ(YbRJfk_0%qG8g3fZ+MsXZa%y(wvJ&IxKe|rFR^-#mDe-Zh zqNE_N8yCI-B}FTSW7Cemz&+03kK0--`5v) z;K7-^>fnkQGZ?u?n*8J{-_=85Q?h9*B^~!fq?HVT_bPTpwJPYN-p|&eyOq+Qn&Wqr zINR-_O|H5eW`mGkI4_!{m#BR_acYj_3Y!Q>1wdKQ*ghpy;1$_fpV>QStNcC3jcvmo zp!W}e-g?*+InsW9?A{30WXQ)QL52F)FhZqM#0}?xmBz(HE9uN0n_?Y3KH#S2?bl$` zpaDMB3@>UxBq4){Tzhi!^8Qb*THZ=jSkqI!oL(sYbOQNGadtYj!euUGF6@%-tH9Oz zA>$nHd*FJk^Ib3*GKW7B$d2Kqf@FZkFKJu7aAe>-+siCVka8=5bY%V@{}ELLK>wvL zB}fpa%_9hSsQtwMd2G>o^c~LPj+aR6ieFYhoxS*%DX{fPzh^K z9eCzUT^QorZ@FCqqG!>#2cQaw`|y`6Uq{we>)l~*HI-|%btPL=Z-Xyqa@n#JfKLlPkm67}CnA(k3eA}kIh z%G>!UyH2iAI%Drch6N_H> z=I}J-Q4Vmyru+z_FC!WC`#o>H&%cLgeG0V+;yM!z$GkrDSG=>W@h`4lTWaVq6b5_( zRiC3NkUiFR39Y#QgE+jQjWP^C$djZ=GX*>Cv*65`garFmVjK$C9J zra%Wuato#ExIeK4ASu)0^_qd0$^X9X7hSp9{p{c3gIshifYPH;N$A+wy>em#xcV~U zym?vwtCSmd*}yz�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 diff --git a/plugins/l4d2_randomizer.smx b/plugins/l4d2_randomizer.smx index 11fb7341f7f3805839f517e16c307bf8016498dd..edebd1dfb0bc6411db5696a5c17378537e9c6014 100644 GIT binary patch literal 31352 zcmYhB1yoyIv#_bsqNNmy)8g)~Z7HRA@#5Ct4hbaKOKG8ak>G*i8XSUCpg06~3GRWS zAwb~gd++_${j)NeXYV~{_9R(n&bAL9KIz=Qch3ichqqsI50CQIJv=<7`*{Dp{-aIK z@bDhrl>#t$cnh3(ctm$>^!gs&$Xy%79dohV!*jdS40n?HAHJ(+zheUSdwA2Y@bLKV zctz8JncJPHn@j3c-Ob-9hVy3!(+MAj(0Nh zzYMwON(?9OS&FIe0oldwDI^%nIK6g6rKjk}azVp+3$Dd5^;Z@w}o;$%ayNCBt z7!Pmk4nM};!+Un87w+Wq9hct4ymQA76Yk+D-0}GxD<$5;%e(theAcdC0C;>~EWIr8 z_}qY2PWING{{qO;;|^Rbz3f4Nf5G`qd3br*|Bvmz z6W@Q($=>C^Xzya<`ahNbjqzU#TPIg5OQ-*uyITK;edhN4N&R#Uptq_*x%WcEMrzbw ze-8?I?nLwM$+Mqzub)1f!{f{qaklRKN!>fsHz*<;^vOO-)VV0zDuR-Cu24&Bt>3}K zT``KUOv_LaKmpa$1$9w4n&fI zyfqt_5v~%s7c`_9xwIngF(@{Yj0WUFNMJmbXGDfBYvcRRW;c>Y)L{LkQddwhf^ ziAxgQ!Uy_=?JguQs(ol{S^r>JAH6)ayL;b^YbENt{tYNvqMrEfEm;o( zx;5=DZ?<$;BHi7%z1>PY-5fz~gm>}&>r#4iS!{h7)ZLEG4xUdAP7@yvO7d_k@p3Ei zc5`%hQ@t~CcauK2v*<=EV@vxmzF`3ta=E1g9)$t9S({5XYwYHRZC?g_BA|a8jibH1 zRkoB2oJ-9Lza8}oboDo)ZLk_Ql**Vqk%OU%=%(KZnMM7+t$qe;esKkoWc;({w&8mW zDg2@ui%2?zgZ!w-G0#|UwKB8oeS3-8T0L3bIA;~$f;h8mn^_WYRV+ho0~Fp<=JY6E zsychv#NL}xtwd2KZ-USavXe*sT>jl&lMFA`0Dj6Y8}o&4lX@qr?P5$d0X1*m3bS92 z*+26$dELoda5U1bnNgH!Yn%|aZ=Ns=$vFF~1DKZqc%<z@xk-{HMIQ&0V%eJzo-7>j(}C%8p9y}^DKJox?sv`p{eCXd81RY9bdxo#kK3*;gS_ zgIlU#UXAsBQKJ|(ZIXgxikV0l)}!pGUuwy+Kd_a~z`M7Z!X!YI{Xn-4U_K^ z=Eid99&3r~rRXG7nZy4QYZ}(S>=ikHf*F!f% zQK9YGr1C07u5f8i7hX{7%xSrf|L|kWaA1siT3&FmWw&E*W~;)@lnD%QvI9BLCu9Af z(cKBxlq%9K@=2wMw zNkw-gG(L&9CD1uwikyCtKlXZ%rpp`}+~(0`4vT72bF|9d%CH}tUxHeiovSb;UmUK2 zcl^uVH10J&!)k#~F z7I5rjYL68(4<@+pfC_RKsk;v1;tnKVVw)xUAlfJ3IX0Xyx!Ual%e*qYV7y8L2`DTe zBrh#EiFXn~4BCUAPALJ;uxlCcx25%3dP)Vl!7M4Z;er9s|+o|*PJ{lwPe^0UT( zpxhOYA%7EIzEzL8@n)@}TXyK#KhQxHxVW?6z;LrT_jL_ZJO@JAHB0dtgst>w>sxmb z_g*UbW|&+0-6r{q8>`9{QlRfCW!l{XuftS-!3+>b_H*7pe7~_#qy<#2SsBIKaG_engqAfq&K-QA+}3e=i&9aV zs?OVHNydB5;vm)H^hN7&mrXI<`J#8R{yucaHVA{J-Wr1u>q=zS(KzByV`*bElE*XO zmMh0r`ErR+wBNLfqzYb-A6rDH#&zvNjp;ar>Oxu6d>H#oYAyeLTmM7QZ3?Qz{WFyC}i z9vN_+`OP?|%YH~tqFbFfiB9_ z^nOPUCs)afR4hQ$h4TN#G|CyPQX>p&i4;74w{IY?BwWs;yVZP^tW{7Tp?l{+vgYBqThtm z6V9}OP8Ib}G?wdJ8*mRhCTwwSG-!kQ3^G)qThBKdK7-F^_my7SjPwa$^h#t=igAc+ zA^ma>#Gy(6Bsa1}sefj^eAO*=Fsc8+7RM#KY&ek-Z*cK=MgE`9O=q8blZUPz=%3r% zDzX_iD&*K zUO)i*Jjtb>>>`WrLU}Lo^g?PX|KzY-gP6#$*6xKsteb;>dZz2e^wt`#-*2Ib4JZfF zNo<~HGfNChZ2Fze6T}T081pIdHB$yFn{KQeMzl zd6+mukscDt6%YgX>-+9VI-&irVigk^Y~CbPDCO1ax7HwqGMd6U#0OL~B61Eeqh`}9 zQr=hoY0!&ljy+O;Q!56?d{ye$4M>3hWk3*^9_Z%Zcm)qv%ZHdY`W7*je42*hPZu2e z>TwUhOgJwZKqOX|>}*4p^`)FPB_^>gvht$x8fb0A63z_9kCMu~G+Pu(ld1?G*67ae z-eS3xFiV<~m!-bkQWcY$MR59J8c~n)oQ;m*m^UusJ4Fa?%MQNIeS?nEdLmB&dDH0< z)x^GiC8#rFGuNW&_Vrw@F+A6CY7gy6)}4;5RL=EW07w;-_TvsgddAFj{3LBTI<9RIvsl>6WAxw<{Fu; zle>oFW$;=8&+;mE^W9pPg8LDTTJx^4G7p$GE1>7c8Yftx;Q>0r{qs*Hhc%i5D&{v; z1Ff>fBVEY@0wT)WmdCb@ggUw3jvAh<>E|aF#Ym!GYUS$BqV@&MJRHP9b)p$oo2p; z-`dGr+JzxqveJE=FQb!hwQoXc!t3${#MrjIEj`sfa7oEISXdZ6d=$xzm6spCF$qZ- z-iE=`9oA}Um)OBHKy%80<)`^S;@qNTXH~8C>bec0rmmx9e8OE#?VA}J_W>ZU>DiY zg{12!SNLs748^WVNw+#F)G}}jGiqs zXG2-O>`Ja^@?VHN>g}Oo;y=j-7Mh`w1kO<@NO0l{J8fEiXq~Qw=Wc3?Z0i zZDZ$7vj=_}I1sKomDiX!*u*A_W6!%WsC%woia%dE-}0Bn8y)b$K6sUAM=B4|4S)ch zcZ9T|NIubmvg*l3!Eqy)LIC_rrGwNJg7#={%?0@TQby%C-N3lGG;!g!v^P^}j;IA# zQhz*IO>%F~Q`2VdNW)5o8wHiiNI6V#Z0t_;zA=up~i z+=zT1^X}F$>20tA0dcq4V`4Fs>w<5=_;QgcQlvgGNtl=oL@i=Rk)#q_e8sIE96B#N zzTAGBC*m{0N}O27jkjGqm)(9RI0dx;~GGM;E5 zYF`;$(vxuA{Gl5Nok;WPf0xOVzpp9tYmn5iim1`JqzYsDqOPoMrz(c(lFolpe{0Mc zh|hcRHzKOY(W$EB66P=Cf==)EmUMbe4ukSl^WnU9sh^5_e<3z^>?IHxZy^SGA07xS zE?Ris)}Tb%e4WpSbs;HcaDXw~deYsyKr!?6l~^9KnEBlC=`n4^=aQfPpJs!P=C(C` zM7ORkCU6D$c2X?~Q~4NZg0IXA>Jj}k|2j1h3vYP~QxP1JxH7+KsuAv;@&)-Wgi5D1 z*pl1@p$zgf8(Rb`d*{R*9sYz5u#EzO`~)qKOE1SHpJN1YHInpj;0?{O-V(b zPI~e6dlfs4Y2h;B1NP{ycdIhU$6VKdB)*SSV40u#;{eQ^ctA(9%}YQjD>#S@j2|@E5-hk zGo9D-)?T(Sw|qB_f`uqm*0zybtM+7O!njRgcK;wy4rnUnYzXIWHr+X?RDxnnZQ=Ok zb;h4J70{?H(;EB@-mC=AiPw?f1-bg3#)XV`-W%upmGXqY>Po0|2!*GEAe3(+)4m_{ zm*?x4ZJPwtF1Q?<%C`HZb&7xKjakHH>U)Q$_T(mZ42r;G-fq_O8~uR^=~FcB@hb_} z#I{aRY_*&ljvHqZLYQ&nm(R+_sr*X?{GD`9)@>pg(%;ZoMY<{H>@JAx$FZ>qBVnG( z8tkS;i(m zNi;TH)zjqO1d4{Ri%pdiKjIya56@h2yG#FmmY>a#hD_|W>HFS_>T)t z|krQgSA>v~KH-bvE*#Bn;{rD+{%R0(z{p4YsAZvYR@td7j}}TuMoMQECo#A*GN6o-FKcBg>tG4NNv>tkSmy@m~T z$Z`h}5M6uyA;wkcn7w*`tAg;xHnynL0f!h?ju|MvN%M^gc-K7iwkSew#F5^$1JJzr z7qVcJGWlB3X7ajTu#8@JT_9BL(rZ&BGqs;u)Q_k+RE(#=w*;==8jF1;YvH>r;f}# zsLLgr84xM0(Ojp%RpsE;XpU3hDa9{-(P*ugr@*&;fp4{JF1adym9J*^MFpj-?+q!Y zY%C?^qW&RlyUh4F*j*00whkO!UuR58kRVYLA_2Co^*&tvG`)H``_B#2UTuA2V)b5u z67M;~VsFk>MCsW+dVMr>DWMVn3xjHO>*5dJH(AnWe(!?2W-Wm?awj&n4aXK*n}<$D zebCSZw*u#5O5vTEm|n46sI!-TU2Kn)lV01~`I4F*OWGE|WaeiBKyQ42xy;$2sH^+U zWAyS{Tf=fG5>q~p+r_bU|Hmp+mp`=f8$}9Il3qs|eGmuqwpm&M1VZ^e3`0hVM9lpZ zY*Gz+7@B-kT-~OD0{aiKAF+`a}Y27k+{>gi)T8Mcb0=k`qySi)>~eQU+0g1OFzRx7MU550=FDHm85- zcKfxX=`T87T$(Gy&z%>CEO&}%Vf2(9PC&1oM;1=DnA*(lBzK9o4Wc(jh5UlgoOdvG z#*>+_GMgA;Syrcm)(S4A*0||=FCo^O!rl5Pu<*2F()WPaNqV?T$GZBf$Z;6u?t&!K z7P6IgVF<>|H&tW5mrpy^X$;4YZV^61I+X~Y^A1x5Ja+Bnncwy;Xo)^@H#}Drub;P- zJg{on&^2dB6A)Iz*~RQQ6;Z?}TjD3w^K$~-)xsRh zA3z*k>RtfpSV4_yTZ9OD+jWGMp3Xn%N;WBRYkyqiWQkctGKB4ObZo=Hy1dH)V@0+z zG;t6^S_*ct!jMW#!q3x1Wg~}%nv?YH$;DjFHY+9%H5TKCy|ehs6P;6enz+^^@+Km@ z%{I>)rfc{uH{mp!>gkAn_des=LC>=!W=Kb1XSk6w>+(0-Nvk$lb>528g5d?M-|!_eQV#KVThB#&ij}qWEXYp0Kp=i zZ`&C)SxJMej?|_w6Emfd#xTD2T4b+nOTcgozp0J2#`uuwkCeGT2O0|E-aK=QdJ>}* zOHV^;uNW|@;iYOZ2qXJU@lVEnz1S;Y!ol9%;9zy|*nnHE?zD3O^oUUd6+eYJ4fj9y z9RtdFV+B!tfv3Tgj%H~s4G{B9S5zuSqV$|>@XBlxeDoI|zA>e72C7HZKX?c4*@^S6 zp+PiLVq!%!su=kFdcke*KE=fp%>1B-4Q`&ePqvj*y)+|3XlN+G_6&E8uY)QgVH^iw z8518)4`WGBJr^D@4)Vm*&J%Lde@wz%CU_>TyL6eO|K_21i=!PAF=g~RUQhgYj<5Y9 zD$b_eW-anH1>&(xIk7P@&cdYw}#to+)w4Tb)U+@>jJLrfPuxp7|#?-f! zU`vM^oyc2Guzn#v>HR+L&jB)*S{N{`|Mz~08-lL2l*87t(t{?hp?ub-4x}PQ%Fxn6`bfn9lECc`C$9K7zzty#1 z4pBR<+I&PQg2}GT(6(PCHrj1T3O1aoIZ0-U|AjB^KX$N?fV9oq4rlakRh4OeC947Z zP*{`qFk;{ zi8;oLJrIkaCh$S2P+81e?}BV_w~4|H5L<0f*Rph@BoFYEb9@Vd==&*O6=;uF8om77 z=ZQ>4uHNmeSYc{2^w(cTWeTmz!d6gib1pEs~q{?VWk^V|T(Un0T#$la#re-yj|rEoV5O)Sot7d2P7-@=(cuU=(ppiU}j zgS+XwfB8j7TJEP!S!TLXwcY4Db0g(Zb}brSC-gtG!L24O!g;$Ntgq7nbD>g}t6^t{ zdswxALCl<5(vi{8J$#-`$`YS^hayzhNLyaxr|y%4=cJ>*FKpA-R(zWwE{O0wbrqBM z!BOn#1GaJn=Kj`Ps1)l}>AfM_IQ66L8u*m?@BEu3r#R}pl|83TU`zy9)+s*mQavVQ z*a)RH0#Cph5oQ?kHi6r=d_ugseEu4C-YV1H?)t9;_e+STx3^8+jJk({z22lKk>2Kv zV(_5>Lce4f5h*(8gYv>#69+o7ySpTdIa3|lmV$Yu@p(ZVAlgmmHPe&e{D{AXnvs5B@*kWrqf>wNax+ZB)j{>AsTsd_Ykj)P7Uu=D+zD%JBr7sV} zJzTA53Ldo;45e?CI-wHS&E`{W`{fr?;Irz#SEYbz%-yvLT-r8Ha`Lhn#Pu!6>C5a^ zuKfE~ADRYsx3WPP9p{5UI!Y$4{Ui`R@J-R(Pw+~A72hU^TQQ_TN$ z3j+b2R~_LCsbfmp{G2~OJw6<|epGS{VbfG%2)!iqn@x!(2|U^ujUC|!YQ=!f#jcYY zOO=4U{zmdSrcN!1AmgphyLk;ZPBb&8Of=MaZWYe9v{_>xxTtadSIqE(=)n)asd<(| z)p*BvE8dfUwsD5rxAACUHl!ZuOCey2(0h#;@HT$$_;&<>EK1n141R|gCjkA`JX}O#w=*W$2s|eksB-g zIajSi5zn_*1+A%)84-NjC=dSL`S1w}=?y=|8FYRrmb7ke8&bjK_wP8xgRZ&HQ zkOlpkKdSMMMNl9FD|HoakE4W_Dbf zRE=zF64GDl%zVfgU#*gn(=ky7cXtf>S06e)HZ8L3BqW}VFN@j3LW<3$Q(9)twF9lQ zPRv#;dC#syAuam?d*ggwQ{0jlQKu3e`4X4xWJ(Cm=zB@-3rveUJY&lpr?nVALZfZb zJYYW?uYbA!dX1=msObpCa_FMYFyB=;ZB1)Kub|BmXKK?_0op2@tX-HL-`UIZbv|t( zv7bVD@O?t2+KiD6yim$WyrCx$cmEPc-c)4m&bF2oUox!*#GD)aI#{|Pn1Gqm>7z=; z0+qIw#mC2WF;*TX`JCrjLVwQU5^rF{?hJQ#Jn9R_gm#Y(d+T7cxf>+wEFh>6vwsRx zMqDxK3r%;sN)N~mZMnk6p3e4;M$jW)O=eb3tw~!}CQ;qIM!dx$#)o6(`fz%NLK{c+ ziiex)DPFid9zyVnS_)E!dVO@*Zp!=9h8k05D75L({+DTWl!|#^iFX|o( zXZ4~iMT?j-9Hk?ubJ;f$3Sl1&-9L6gDEW`rxUL+(^Ieoh;E-JMHdv&);-;8|!`9t6 zB*PL5;zAdrSnZQ)486z^IoKwa-3+DbQ)B=5vy7fE>la;f16Mc7Ib-Z9ZLYGdb=)zsIYi&GgL21Ihn*=~-OTQ+Xwysioem&L! zqsPEWMRX2y>MDVs0QtlOi{cz!=9euKzScApfl<2%TV(z@DNx3xii*6{?r@)UA*Lrv z3;;<|WH8e}6&f6Sub6DMYRUJt#sY-->ZG^hh+(SoSM#rW&H+Mg8qT22tDO_U?=MlG zuy1#xjB(>Y?&e3VzdC|2pDO085ze+m?U^Wap_9BqLHEX&=>TdZyH0z>tYRm=s zsrdRNh;erN59$kl1n2a^QA-!)2^t`YI%w;|%51;yw^qb#9@8~*n%-DVJ;>Om);-(z zn2dq9HR?r5eV>+83l zKOueL6=0~Y0P{W*35Q?bCX@n{v4>!qQMq*s$-(`JL+bcJG5#tqmZ z{qDIFL);>-E-!EF`ey9PHR-()EIn`LInE%u^hRQ%6x7aHYG5~NN)PZZE% zjWhK8173+D?=te<$^P&drsYibAp%96f9cVPZ?uIdxs`=!j$qt%!Bw!Pp7uqjHfDup zX`w$&olHx)Wyef8kzf*AW>M$*57?U{qXH3KL-kQMm$aO=+Ok8JmuI}|uv?cS94N_o zE6omwt;}am=HZ$F#V}w_vFcPn@XFn;zCgFy5r#H zO_X57m3U*|hTm=XhT_y!ur+yop!I^+?T4%K8}I1b%|l>}WaKSBBD3hMY3k2b^6*WO z?=TlJ;(F{xjyY4Ip;)DKdcoyxh3JpzEtUUkSoCC5gy!yX$n#+Q0Yt30TG9=h=j~wZ z%k)F6e9@A-fe_Z$Z=ybZGpEK?v*mAFnnBfApwy>h#Gd$?814fUoiQ?fkQCL(D!mHj zYh7M2_+u)_I_2>Q7n!JYOXWE$|GW)2EiV%xj3E_9tLFy3xH-R-z08ri%6t=e6&SA( z7=*IPZ@cU|*cl$wVWX5jNwA?-sLe$7TYfx!I!F= zCG=cUoYxMM6;0`ell%c+?@l1Na?Sr#oE6%b-r=cVGSG~k+oORQI>8-93{A7&DkhTA z4mFF*UI=-G-OXobuKt}wnI?{6n!I3ulBTx{3u~VZC(c|*)@JWgR1OAdR7W1+`5rW#&wmCc7pB3s% zeU`K@FvRVDE`Q07PEs!<;{D_>oTu?Cg*i+Oz1g5RVv#1U-t4hHOoo6 zkcIgozAd%mYf%GIJgQ=imq@R4zoeRUKZ9*ad+(T07l5+WrzMIxu~orsY5Rf^pD~Rr zPiD=>aCYVj6 zhFi8@Sjo^L*GImJ^ew>wh{x7cp9UsV_+X3jJPpgVSkQ6=Xmi=Ow z8U7B)LVve3R?WHhS=($z53M=u?OJC{6F-r05rfAXr*W%N)q@$j%?f$XlnU{BPF}B`I@c7wBjlas$2Z4QH6h|;e#SwD0 z>M|{nwd~1IsHB*eEeBm0=_6?z%o+WHu_eBs^Ron9kR-u4{y3>8kqzFa#NeyJr^)dG z3T9?KPO*(4#*QQWKMa1W#+w*8%{1z#ThD#XObw}>`XpJ(1Iyd}?M==+3`m1KOWI} z1J6j+Ih#6e7Tve%In?OtUPNc$!oE>)+`l2I;5ZVK7lLoW*j+SOo_*8td&*W@hi@%{ zxssO7@f}F23lGY7(!~FPPx#yIw}Fye17Dq22q=_Egr6N7lDZ2;OgoWnYW|qjhb_6J=sNTVyxWUPIl@(_|qOV_y3|Il0Ik6QAcc5 zh5t-kIVFfEn`xpY6V;v4yCAwhWfl>;x)Lpx$e09mM2EfSX@&H~EoQy!(0LA<<=7`#@^wW1j_eQ_E&h(X_*+BBP0P~so`pW@8wSeh z{0{6gKg~83=0gJN`D#TR&l6E){tncaj(FVlgX-B@0Mm?ed{3NDWGa8_L+(0@EsY9W znJ2}^b}HP<LKYX-YBgg2g!FUBo7XatAs zYYJ%gBD+7L0K4}E<#|4%n*uEjyUMgPkFID|Ip=Wn^o4W{l!TKJ(^0@DNx%PuTM&9N z68_QK*LnFPB3 zlUA{_rfTetUz#X*K5I^f2`;5-Yc&2k_!`S6D5f;D@>4eQUN58K;e5o_7mdFB34C*R zL2j*#gm+XY)SnnJaci&Zd4Oh)MEIiylEktP@-BT?Z8;gGWHSIU+^Q?{r(idsqaU*q zO0t|96bC)J1V51k{_0{eF1HhM#pmLK=(mzxcPZ{eAB+k#Vl$>y!!?|8^)BcZDRLMs zLlu@yNv4=mUgU{#$o@xL`StrJc^*PsXSJ*;@dj=@6FqM?<8*rJ6NK{lnYAwh{GD|6 z-jP~g2zCoEMk^7hpA1A1eivVO9=mU3njpywHB>IL{k}VfD_qbd02>{P&!1aTkF-e5 zG7^(?B0^MvX|C$V(qV3X8-NXjMRyyq7axx{mj4Xw`tu=O5w;xtVoVREuAFFe9-J1p zUL_D<-H<P^)~3otgy|Nr%RZ-x@Y28ASD->jn6mXG_;J_Fes|~7 z+ErzFnsB1GtApE9`>jhp9^uVQzbGe(mm3*r1&v)tO#PUi-=FJurZ2(q>w#<~c17zY zi*-$34s3eq``4;B8RemKCi?v_sA`DivS{m-tBI1t(~M0$6%+(o6j*2VopN!Wru#Sd zl!%+-{b9Cqj>#?GYgp{$C0D^0OAEniFvlx4FY_jO4MLf?ZpB8ahh01$!&2qs$5g5w z-klJoxh+UNk3F_VL*EcF;A@!8Y-3#32;7@qS{#{x3{OR=?-|ifi%qKDglPXN zC5zJ(9JeVunQ}8w;&C~}`0&hxY~T^6!LE{bPP!P0x~wh(1fUrO9$GGZQLlK~ETVrX z8YR<7SB4vY)^8w$(d3c7qNf=SbG9SwR@5J2I6cyhWPcnL!sL5x`3z1Ms7iTYq~>82 z`7A%6VTmzYl;E+iL*t~Jjtu>44Nwq+&Aks2&!y-4xX&ecCSl!pXOb_%fLcUS*~q!C za4CbNn+*3DwOL)AEfXM8KU2K#ohL=OiJSBxLG9Dl-v-&f_yt)?(PZ+}7%Sw}ZC(w| zd9Zmu);C$|_SXs3*O;gK!COB+N4idg<#A{H0I^U%x}f~_v)g<7dRUchoDBn5>_f&? z*S`|meaR!1BS=J_%{)^nJVR|R(|5)jv(`;Sfk6|49z$8fVnL9bZ{){sWi2dU0hTs> zfDNd34ouY0({hZA^18YOYv*>z=rf4G6&|gsvC~Xd_$ke zySSlx!*dS&@Esu8GLYFtr&Q{1E;B*tx~P0YZlh%R^8Anx#0Gye+?0Kf6ETUkfayQ- z4^x5WGOJjR#HgS)!6{-lan1H|HLiI_WY*daAVo55_!nQQ>Y=hvf7lwI$EpiTre0?E zi|20k{8e08*!bv))Nx4QTvKqEQ%(wFL8pUM61wRUr&e^Z|4f$beU)wkwS5@u;M@P+ zmHJ%WmtXOlmmDd_y&LV_cU!jhMkwA?{Rd9%$7fp0MJno7_uana<>P28AkcjJVH^5! z)P1FYsy#2#0nC3F=zlPW4sYNbNiz9y<#cja^}loMU($-gpGtTn<>Q|DfVjIkmOsVk z^JZq-C#8y_EiF!PKgeeud}Y8F`>JB>vBFr3<@w9;V0rDT<1ohmH3n>NLk0 z@bwwqr2lTEPCeII=3MTjXf97p3-wycw(t7gh{%fm=iZDwq0A*{g0Mv%ZZH3!PNu-# zTYa)7*uZ}7D!gNHCDZ9}{t=?zMosD3 zhd%|0ik_8%wW4JREPnMu++~`!Vj1o3)ml4Vc{^Ct3Le@xcc~ ztp)|4$0)tO-PC?{Et%(a<>NJn$mQx#;qou^H>L*1^aL#i#|Yxgd*{fh8q9;xt=}en zs2_gB{eCnTPb$Ez`TjIM3>V}UgqyH#zngr6SD9^H7y8ThY6e1;#ThOPE$&|cEb!7# z?Je{^j<=F7w3&|IsNKlH?F|MeYDQxm=b*xdhYc@2%aO`F`#b2NKL1qaYx}aH#Ug?F zmF6PwpZJhVWQ<#U0U_GXGraPqxm z{*Lq@?4v6A3gE#@Q|NMCEel(lX0&QQh!X3{pS69_)4XA#s;?Rt4r%K_Rh;5Q+?=!MR)}nZG4llcmLwhB0THyoYuVoBux`@eph#UlXDr^0Aji*7F#{(uw+$q52GBmr$w@)2 zI4c4g4%F>L3x`Qp3+m`4KUCJ>2gg29Z1z&r15-~kF&WWrIqg(!Z1nx5{rXfLydUDZQAO~=W}5Hs zA{+?2X~|tRAV*Vv)o_T&-!|g1WFw6&BY~;Rv2=ewr?0-b$<1HlQy}FpEICF~v{{aw z&qHMp_Nj)n>PXQu(1{SHCSi zlzr$vLlV6BmC7rCi&)ymhWsS6q8v^N#h!5!|RTJH7UdrajP;C>85ZN(m_xYFJnJwZVO=_LE;7{B2F;lf?WJ zUeXP%;_pw?$@Gaw`?>m)$f5`|OFOdULf?C$?ce8;iq}inno;pm7!`l7bdA|w%)@(Amn~&)`&~%HhXgXCw^>~c+^*F62x+?o5 zba*~Jdrxo+g+lcJRi?Y`y`hz-^XsIF-}t3{FAWE>AJ=_+C~akxRoHdCJxbB|D!bH5 zGAc6PCt+;1DM7FFbBN2A;|jOis0#n`-pBO&G~1HRh?iP)^$*oQTp91H2?d!4N*FrQx9M6D~(`m{Q?KRGLx{X_8&%Taa0bq*!1v_kBAofYwm4QVn@_IbV`jie~I`IY3ZvW0u^p33QixF07h zmNL+w5H1}n!)bVmnc)FJW(gO!B1^4Fb-%SqW`F(e0GEMQO$feLS}ImHp%g7pYc_j) zoJX-blltjg$@yXDa{D;Xva96BU@OP%-DN|ujqeQG+d)2@#;plGZ;9yaV(|nCjX~&e zM@oII>u0y*tR%mPl`a1iOtC!8VxyYhPz|#eAYf9;5g}C8+>30easqd0y9g++bdxs< zW6&I28||-t+6WTaRoOojPW5~8@a|v>$q}I+yyM4nafyHG6OW&VM}*c|{uK-IFWvn5 zN#yO+)mm?+MMi?saZ8W@?QNU6VE-Z@y7Lm_z@eyn2kV=RWmFoS(8*J17P zw+#x@v^we3Wm%b_a`Xhqu@FaL1!Gz-yj|tNzXsQ+zxZS>vvMx!&Q&ZHZN~!}%{dQ)fO>R6m0}Lq~zeD%$^F z0Qxuu#}Cry9khc>HjREd3g(Z2Yp4|z6<^mKZ0jQB>Map?q=P}?VPhne;3q!Uv?bCg zbVK=VT?CIPC<`Taw-T`mt~kllf-@u`1!gPMf1It-z@s7y?l!K{A(2MXUBGFP}lxt+7c_GtCx~4I>=*bCM@NXi*+vM+jaI>FA zv$gSL0a34^UHj+~2EB%Ni9?>^sApYh4?k?`3iAUvIhu3ZP^%l`ELGST5iNp>+YpZK z^oNm)NL;S5x3A|xB6Z?7re2LtiZ(@QE?1s;dgh{R4$9T^daPFUiEJY+Sm;5V`xGNN zIaB2df`ThYKz`(LRDF~yOAdF2O^}voe0)?Wc*qq76_un_P3@i0z3VzVljEoeE=Mwf zf>iOKAK|B7slSqrivG+;Xlt=1p(QQ-2%BwPJ2ffuk`u#v;_{g0wsnQtLTKx?ZEeAr ztQF=h=o~9i39$?$-q;%4)f8Q)bF;cDrI=WCTbs=y9iMIA6>6tBbIs!wY zEDCjBVhJbdk;q*7Hb)|_Kj;j$`P6PYlrun24rHtoTaTwLG z+>r8Sh+LF7x21TY>JyTPvZPExN&?sxzMDb`^gr=NhtU(k4t}YVvJ)k4AzVq7wTJ`W zR4h6EY^2dgwu*=bSr%~`R3hg-9yZh&htBHzL*Z@W;MvyTZb5_?zR-yoM8o0*jju^a zE{w~JC9>WEg4qxxXUNw%SQ(wA7i}ggI7yZ3_61eBM$%H=h0(N?NB*sTeb~p77!u)9 z7Yfv4cEyt@Siq2mNE;qZXt)rAbgfW1Z=w^_TZ6IiJ{q*SVvI5=L||A_M-{EU!Ay{- zca$&TCqb~`sJ5UYP)t2O%?~7NsMTl7l(X1;^g6{omFSWeLWuci8~5jwlglY3NKRDk zelxvtH`3=QeoIZu%5GyMVd`3BW28xZa;KQO^pU|}?~R_#;xfTc38sdpyKrzFv+qg* z>pPw;C3Vp@q4OMHvP~S@q8_y*-qE_Wb?MSpy3y;Fa46E%y(AQA3uA?~MBG_{ z7h1GsVMB5&WC+~4+C1EpfK$&fHubrV7#-1gDWf$wM zO>3L=HP>sq5}|NhzsVtUVcHNGsf;GeAlT$3m=M}UNrVJsW8+rh&*PJiPZT>YVw`jw z`W}ObrLA2sgkn4l#kXNFN0n5xCNKWbI%2J)%q%8Cak!WG7Gh{L8o0#TSg^n;Z>_b& zDy3SjrRgFf)&wS(ZOO!v_A*Oi^f7xjT-s7{kbK)LZaYc7Hqi#?q-#rVV~Wa(@rd07 zY72G?PjY*9clweofF47V-?$!%ieU;i`6d*rvPshcZ4A=}V0=lkXlo<;Xb9#*`V*=y z(_!+mcuZ5&tobMcS@JqtL#iAUoz5?qWKCBB=;{n>`tqOUo_FpMQUIEIh3HV1j$tMd z2X$NN)WU zSS-CVcS?+E4Hn)RG3$Dy%;$Pyu%NtBgt?qTR!l^_U<6*^63*G#F^YN(3iHB+dPse-0(Msg|G z6skE7g42r!`Sd&_>nuM{IOC<0evr;p6@rp;sp4w&;^gMlTq6rfgy78rxla?F7H^2* znrtd1=!AZA4U($4woc2>%A(4K1FAQdOuS~AhkkM~1Q_+1833a_UaWL;iqB#@8R)VyAKV>jL4dz`-b zRKl9SbPLaSr4}%008f=y^U(7l`F>*-Ka=Jq#nUXcgGuv}+}|y=gh@k~(l{fvg-K(R zZmFG;&052xF-%`J`ffHqljiU$I$@o|&!j<2H{C4B;b+n$CXLL9)G8*8Vp5_0jMOeB z&0;#i@e8SCOd7`Y?(y(5X&N7(+loE>Od7}ZmG(BNeN39i^aaabOD$y5Kqigof+_q= zn#dod6F5`&nKV^DN*dBqJDD_-|DJAfms-lCp-k^{a`~Awl?UkrcrHJa#xlMCqtsp| z4On{rh16mu4d#;+{tA93&DwvV8<(Y4GifycD}BvLYB!T+GrgacTF#`&{6oC|QffPs zrt<~51zu`Blg9JU=}TjI{7jn8lfBwk8ehC3v4Zowcq>)4Brhw@U46 z(zITU_kh&GCJpS{@%|2}jZGTZ8}R-Osg+Hd*c9K0)XpZ2Y;wQR%WkAKy9w`6sjW?# z+T{KLskKcS+f8`?j?~^J&Fu%W_(n2%Ce3Z4pCvOmF4EY35boQhRyS#M$MOEG)b1wD zZmO@(NG)&D=woHWA!K8v-|p-v2DM(5X*T{gW0trA;(L>0c?e(kV}0&SI@}QZ}bPM&Z{>Ep;j* zz3-IT>ZGCmwJg?Fr?Hd9%$jr8#>)=JOLX3g}xY}QUE znjsDK{A|`zpPwx?)w5Y!eRVc#tXF5V*7}{RelVLg z$5r>c5rETJ=;f&r5%#T~F;n z8umZTW-a@lXS1gLud`X(UflM7E!r=eweD&DB+dK$9M-<(a#%CJI)}CM>GTe1=--pWTKfJR*3|FFVQu|H4r}Z`oWolCkL9rD zKG73t?;p%z4Su3C(&B$Ehc)>rAEeFSpTipcpUPpa{!=-u*-!Zw+Wk4K;s4DX*7E;u z4r}`VAcwX6f0Dx*|G&v$t^eQUu;%}tb6ESI(tSW8odS351$aI_3$ZydJmrzp#0Nm0ir`XF|f_Urv^lN!23OXdcg1D69l_F ze2O6I;gba29zIRb9rAPbkco!lx9jnZhR(=1k$!3iGG%iG`b|@TrBRQ~2b*7QDR zdf$+j`rc!DUpp=Jef5mg_j=Q9o%{1s-~Zk8J~-2K8|19i_mP6s_dlE7@4Ghj{pssc z-wREjWcc0nsqgpBPJREt9MfsmxvB3znV0&m&s+4hjISau4`B`PDDv=AF!?`4+IQ8Y z`Y8in`X%C@QSJ}Xn2!8Zde~2q^40IPKKm24Z}S;noiP7>)W>OA{e9lY>6rh#@(X(0 zG`C}@&0j`5alKD}YDzxcpL}XdJ{cj7ChgwmOOMZR9Zb3!{wU@h;^|b<-@hcEz9XN! z$`=hEl$Av!ab_yl36akary5^}DWAq)IiY6%>vT?wj@tHkIUIe(HvkW_Wt_9-8tt&# z$G2H{lrII*$+^d1;gfXuCizU=_^9~Y_C@ivnE`1}`5E$X$*z{Qq2>Hcgsqy&U!kraCCmH`m;r~vQ-8aZ(Zk)#P&vfu} zcdp^5VLJP{Ui#7V{H+|er#ydz5??kXzIt**K2!I3OAbF%*SVeSYj=pRYpZ3iljWj# zd~=O5E|bqx#!YiMUlhf|bE%A3qKr{$mtGz?_f9wJq}ylI$zwjFPAHA?BIaF*m!x%F zR}rtT_80NG>WxLb-g$EouWNoLzgYQ>BA#E46*2$+k44P)f47MF{ofQZAN+?R=7s-S z#613{8<^1EegpHsU!2SQZ__+p_rGf%ug71Q$9#CHkNI-6PITF+2FK*gS>_R8ip`WoV)Nj3uf7M)(t=Clt1zfFAr_#KUT zK$5py1fG?jb*42>5%7D|4}jmNQHEq3bSf421C4nqTH7B3p4XUX`ZwSO;*sb}V8EYf z%*zn}OylTJiPk;beqj%<{ST1rgJcN6f0zC7KZw8f@Y;V2cuC4Y=rnja@IR>!&|FS8 zVNBt=?4820fj;01)CR!c0M7wO=!=WM-%`8f@*1xc_&XX$ful6e0Dn(o5AY8}U%)@o z7np!!B+~%?ndUs;Uue9+Trp1V5Bw{&Kk!8<8zC9Q{wuhn906vL%mJ7UJOj+3H8Idb zYppz%NzjS!Tsp-Lyn^Icc}yGKc`TzioX1y|9|umSH6+jryab#9^h{;hg%5ZYaLrUc zHQx-JN$a1fyf!}syc#$FEFhW!UIV;1m1P?=*%ShcfY$@7rm>8JZoi+C#ix~vfCqs$ z0FMFZ672%#0WZ*)MKT27d|)BQfCa$H>CCS-PG?#V0~Z1h0B-{J0~Y~@fF)Uc(tI&+ zY&y$O3efRx29^U$fepZ0XdMA81NM4(eM7gjmuK;f>`Q^?yewm(o7R_Q@eS*hv}Oh_ z2i5~uWbr(+64(P=MfEg;WifP8o#Ln4&?(+wX@8n-W(RsPUcH0jrBnKpA0XX!8V1r; z>w{PF*Q^S!;@ckgU&Uj?A>i}Wuj%wY^+VwMi9Ru~e1Q5bu%F_|=P!K4fOHejAn->- zm-({4<#Ru~DA7BU`&<4@?r-HYxxZD;4 z_543jz0P7;LqG5ol^^&OqC1SoUnM%fT7E_TYChFIK(Z+sude3XQBPjYUnd*An%Dhf zz+Y$aJp3D?ci;$c2k^I9ye_7746TcQPjrcO<{ya;fMY~gz&}wtUc>9WhHF?}&cYg27ZG2Q6Z1v&4paw2MW2K`wLl?bRPIw z>Oa7L%wp!~a}>|@eA`6;_(h^`;1JaZ=D#mdea+_fIWU{&rGvA1UOF_J=cS{wd0rZv z&9`fuka%G>&r=s?^E~C5Bl(^=JWqAc;d$xA9G;g>)4GbrG2m@9{>)(+WquLQT~)xf z)Q&}bTgL%lEsa~iI_i(WbyP0Qz3WM~dIQTO8-evS-T^mKeE{zu|G+z`pJ9$}pn9Ck zGR=12X6mQFE#!YL^O47Z#OIs=&cYmY7I-zVavsmwRr8qdYn;bCUw9t#ng`|y64UyD zv}_rg$6t&)Kab-bo5y@;fsc96a-S%I)&L}4s@=z51nHH0X&-)wuQ~~&>ru}6_|}I} zAM>gC^O?Ub1>QvS(0u+{7wH}ZUPM&bpD;}Q#td7def zSS+zz;+kTfe@+(j{Ig~u&p(X|dHxA3eG|_+-bFm`99+cn zPTwM)cj&9|G|!w}B%f>q`eNuefct^p1Re(dC-4aHGVr8C`l{s%z;nRg(0&Z?zkuTs>B^wr z0qcRk2Q~oz06Yr(F?fnT;054m;7^GskvInY8PIz(=mJ;@{10Hm&Ad+&2L2M*4g62q zw*meNcmjBt_H}^2295wVYTzia9r%075AYAbVc;KubUx590{zcK|Qbz76mN zU>G<8JS6cj@VCH0;O~IL5>Eq1ftMs+2L7II!NKG+2FwHgiPm7iabTUqdf;Dy%}bc) z2}_Itvw*!o53pb2G2j&7DPSJZQ_h$NoJu?s&B*m6eQD zz*ht7fwxgOiAR99Q#}J~=~g1(CdwCZ3&mT>>*RA1>65R79^h7rUt$$-8|4$Yo$3jA zFNFvGioyebP3f=T@t|P^j|c6*_s~4Bg7;jHtziCacm?xkmn4p?U_Py2CG%IIv7F@c zCYF=28tHu2Zanwt2FXWhWND0~xt{p=L*Zi)xE zo#?)azg#~qksRsE&TAxA-_5!}y?67T^=Tm8=zQjGc?;ThmaVPX&R_8hZ|5)i^=xPP zV()gAEe>pFx#HRFEE^t`IJTW-(B(*+M5zfNiDw_c7Mr z$K%t%`&*ReY{X8C>mN+WW*TUmc zOAC)nM_O2hdA5aR?-yEloVwh?U-BN2Sn?Xi(%10#)%+SBzYe^H$FCuY!>{3StnvXK z$JRW+<5=GVO!sFWV7jkrWqPM;L5bdbTbaHO0M`&5NgQZpdOz37bl$#$>HL_)fgMcm zr*|;D6LHX2_lhL?{5-DJ`&mXC^Yi#N09;S>?dSgC*~$H)WGDBF(w*ENcI;%j?~~ZS zlj;A=PNx6-HpYTB?g!;<{Dri-HtrAgZQLKa+qfSLv@!jkmUsr(O!eKy{a~z(`$1uV zu?ToS(N%!^LsNkJL34onK|K@AEr@4VS5n(w|Ai{E@9*MmXmKPn4u$<^rg!eN>BD|lGALaSFDa!M8 zbCl=l{ZXE$k41T&9*{UHaV*O7c3q6;?V%XY)8}Hme{nv>`xl;vc>kjMA>O|TKg9F) zk%xHy;?zUDe=+tD?_U&lG8T36{zXeC?_czFvOM)-C(Bdwo(%T@alEWa5}@IHpOi{&^C5*xc%p3~RG@|@u=mgji(@P0<#9^TJr z-oyJD2lwzk#-%;Hk5RXm_c8YG<$a8vy}XZcaxd>=5V6ueMqM}WV}!eTAEU3EUsNyeaWImNy-J9m|_ezK-Qg=U>PB7}S|)AESIf%a;=SS&lR)@x*?XC!Ldcem~2V z3Las(QsE;kSE_%6@sDRJl#mM@Jx!uuQJkFcDn@=@mV5)!)~Wq$AI zqs;G}lsNP#^L-csXFH-PE-WMr-BkzYa1FL9#`9|Is8G0k{e~iA7<-jz^(LPAE z#5&-u6t0)$!$%|@?PWRfKrhROPfI-0%W`7R<18m`ew^jREswLD_|W4lA3h`T?Bgsa z9)FzW#J(q3PTcea%ZYb9!E)k0iTzKoy!gZuEH56DIQ|67jlEA|o~H6jEC+5Sxg>BK zg?o~9WKRO`rg{S2L*;*x_09`1nBPZoZ{SW^4+GmMyu?EiPXYr}Ul{a*R8PQNv`zrF z6TJXKv|b0kmi)bmb#3b zdn@KYidSMikkkPZz{e@Sz$b{_flpF7--bFMIWq7~9ejo+f$#K120lm(~DJQvJS@_nCG)EBBk8<^86^&+>lL zu*B2P^1jpAXL;W#?+Bw8_)ip{#Ofm~zv?~0`-KO9|4j7?9H#snVV&eL;5R7$M_9L^ z1kBGjseUDff#0HW{YrpF`>yjRNK-pg_~Vj_jy4ftoG7vNur9-rs^)b{6D?$-Z2%iWGi zydd$?^DKue>|-UM;y#wgHTAK+a4#?ic)X8wQ+9w!y@K?O-^cr}-S6Z5*FNAZ(p>>w zO*$$P3*XOJ^nTu-ZGS)S&mMn2@5_z?3u!<11FR431ug*A0dJ)JTVOHpByb_c3w$-{ zxO{+hW6lF_C4oUd<|m-1pY@^2`&o}X0IZ>Ofwi;`46LK@63+qG0ZTrJbtL(d7zSyQ80$vL*N0h`rxJKC?Q2U+ z0PiFJ5{H2I1IHwe16zO%$FP2+{qbY0|9%j-gYqTuJkU@1`UvJRiXYfU@k$&52B@As z%6jq*z+J$jz}?i2z;@s;@U_%_A7efGH4=M(9n=oM2z{ktfc569f#0BZ8(=;3Fz{c1 zM}Xf1UKn7#^umub7SXu}@(=truo-xk;s^dKIgy`!N=7r-1)K=>UI0=>RX0e!+3Z zLf|hc9pGhPJ@5r!3-C9TU*HI(cbvaok%z_Ge^Gk_N6A0%_e7V#KT`h!juCwU|3vx? z5=Vf4rgT5aSP1+JLDrWq1ll!D95@Gf z;?sO?;55)j@d4)p^RQT504xR;Q#jy4@&_ydo&hcfjsagqU;DtK_GVznp@6R(90=EFGKhNhX>Ll)$*aO^3_49ew`78YbpS!4+*Z^z>_DVbeypPiV zB50ZX16zSD5_bT1kiReTxs8JoPe~jG?xcJSp>2Q(;4Wal#ACqS)Ske0$`>${!%CEd zRT9?#UrY4^e2{b%zr^Q5^1clI7})e>)?I7|b`m`U>nrd>?GAj3=oa{9%Kukb_wo?%t(2a`^T4-JdjHH=3_L{T2ELv04}6;FOkyAK z8LC&{Vah-79aO)-cT#`(Dx(khEVV!I2(>%#U6g;|yD6Tp@_Cw5U*)gs9devHoJ0tblhz7GDF z@(26`)#v|d>-wXzuFm)?FM^usYf&*J#bQN2$C5Q`mN==bSW&T}bdzH&@r;ahT+y^M z&!S=*mL*%9Snl(=&vWm4-|zMZ=X1`7 zd+&4aeeSRCeV*sOz;$dFa6QMxR-BJHzPCzz&vM}F)I0DEV4Gs+R*B;s1h%mMI|b8$ zf1uugZ!$k{Bl`>Z7H|OA#(cm(vER1|W&<}-f56S`uWjNZ+X;M!{j?qDV9rC}7S?;a z#2K{%|H}3Pw{pDzZlhkdtAFeWx1jc$KK5g_zf)U`wz+B)Z>__0Gl&d%g_%&b+Fb7x%yo}{4b|`iNrvUTz zVSi!0EtgQIyU40Kd(Ch)R52 zD=?q!2VTSa@5jE&^7c#IT_Ny#_BZeb_M>9EVh8X>>hlxiVfnzD**?XFPs9hl19%JD zshHL)m=3%ZSlTP`e67ISfnC62{wf*pPPVg8Fb#MY+tnxWetCa`Oy*N;1AdS3E(dV# z=eSfX125*q7OViiL+HHmLZJ!~oPQHURHszXKPteV+=ZeJb&aF<=ES|1iEA7|#g&G5Z7f z0Mo?;(_<2+ml>1z#sc6&ln49?@VOG zoY%l7nIE{E^#(r8aSyCze;=3l%pPDJ-uBbF{;>iLgJ}%2121VlnY$T_5#;a4sZkK z=igCh_7m{+WU+lONcrDby&^C6ZJ7Qg>0em?XwM(6f3)GJXpCH{SI*hWouj|LcAox^ zwrj?m(Wi#?eEL|>o`03b^w5?}yZ#J~SDUG^Vb^Lb+l}IiZfv}4!yWpoZuC*0-TrQk zXQNGZyqF`6Kk~gAL->H!jW)xHa?NY(_`lR&XQu6s_WUQb?kn`y;-1m?wr4diu3pZF?7Kj1t>UTizualSF$xZFRKQ(?&;oJ#BBa*KgB4qD_r< z`)>UOyAQ?n&e+Omzo+es_WJ|cm$YTkUxhX+`W?|GMY}$2PqgQMseMfw67Bi4`OuC} zn-2S)KELdH+FXqNewgThHWk|MX)~eyo;D2XgtiOX?-|2E`#o(FwBOSvK>I!K{;4zG z;nPmfyZb_YGlus0JBNvVem3y7ki36EJ3a66saxLB(>{OiFtN|`E`79)0qS)r23gX_ z8Sw`va^|C75AFWby42^C?uC4HbFXZ)iXzmR0FM{*1nkR0^LOw^viyGh9t z6H^vrC@0l=WN35AqIODuA%TqamXcf_B*(JMNfQ0WKUQfD1)V)9{$xbx-1bm0$tx#G zR-3XPRY*E8Nglspz+7hYF!OVSX0$S5jxToP+>va4%g{^_rXtBOMgEYXvEf^i`wxHR z&^Qb@vPyme(aa=EvP1a$hsK1?&_0t=e?-xY2gd!0^fdEJi)OG>KSE}iKqfO{8$8nf zW}^u^G~|3;lFrq2#iry~H4BR+L#O8)q(*? zl611+h%!IG;$Trn<_~t-%>MML#ASGA9@ZABDOJ{gN zfaJsuFAS7Pb%!ehBvCQ zykw-4Zo_mDjs%qrp!;&jh3YAx<|utGNOBSc$r7Ku3HfxP1il+ zXP5C!IdGH8*>m%nw#ew%JZTGe8y(TZ zdO9lbNLTsY$>LKRyG8dAk__CZqiol$fs~eHN+X?N{aM)mTd`1QW@&a-iGH&r-CDQX zCzEx|&qnqLjt!-^OGKk7lg|Lq{ne0J+DrE-SxHhCR@ zOXf<8I!Q8`soRohW0&pEdN1WJThGDSLb9F%ww|4qd?XsLXNi)mXE({KQ^cKiDjgGP6{BiB)nWq_6IAjpxvpk$mymX-ws(a$n>K|qjCIzg695adWZ7*3{$ zZ{bim-*E_uj<~SUp|E(NEWd7H@kt{nFEvi)`KJp@vPz3s=KZPSG&i)!+mE;|_1}-L z&IhHLEhHBsW&Mv(m4`%|W-xHIsjwuZ_mSkjneUOu>x8f81Gvq9a3+#S)(MS_gKZ%Rx62S_FKi3d8E_PG>?qf_0y8<5O*ZD zq-)XPREgSeJJs8dzDSkO{p2xTN}kLu={Bi}BP6K5M9D$B09NADJmX1O)`o1`XIU~$ zr(w|u@rlYKS?jIymQ3h*pEQH^7bC=#sdKqEXt^fS->LE`@3=+jO(3hD^0ppETGE56 z?kR8UVU_qSLG@U#KQ|*s{*v&|f=7m}6lx6N#|WR_Nm4+WLg z$Fs+5ne^7ldS1vLJHVS9yPMds?f)8F&n=vbhUOBNByzv=kXe_h72r|n%vCt2vLrK#jp6BUAQ~#2;=`%8u z9xz=mdn0-7C~=pGzU+NCm_t6wOZ!dWXhbrbWUdZx-o)^gjHZb9x%z}p+7sZ406Fxf zHj|HN^pDdAk0^c9D0yjPh*yHWlcGKLTc-0>D6YvLAz|@>A<1UC`A&zOsZD{(J2F?N zcG4;G^hOD~Q(nRgkvdnTj4O>v1imf1v zG&{E*+ViWo1}g8!MxDiL@UJcqu}*J4dSkSh;Yqsv=uMAwReIYa-5$p>X=QKTE@he{ z@!rv5iXYf6m#$8bPdY)COb}#A3aYXTb;(Z5^Y^4f3hd1ZZONlQ5OS6!X%Jrhp_g(b zP8O{oi}vViZ+3Xo#9X!~pt$riF}2U~6h|HLQ5%x1r>Q(&eX}GzOx!8Rl5Sm^cdFP{ z)AxBFcZ)!lL@^Eb153RPg;w7D{odSU+oOBqWXvx9&6c#Mpv5S&MoH>D*_KHQXH&1f zC}$@E&7jS_GULi@O4?F8dIO`b(2_+eh#gXW5VND-TdcUD(1cia*xOJzl4)x}|>z{tTl&0-7kg}U^Zl_CDBmI^!@<0*E%w0-y zVOb5Le7xT9VChU^C-Z3|l9Z4}k`@e0+BZ_e-&b|RW(X9dHa!WmKXSlvTogsl2pY=tKK!yd^g7AP?4Wu+`ax;ye(oylI37$H5o817ieS+L(o1WhULA6SjsC_IvFh(9%s@KOB&BgT> z9?2_ib8$_(r6t|P^{BCl+0DiE8G)4M;(B6$G#A%d0n%JtUl1tM=zU6nG*qbp6RrCOI|R+u!-6A177C`$i;^W_HlhRJRao=zXHNZacXcGTKt%eKc_z zE(O_DA={bhWFGM8)LgtlQkqV_KO!E`wHxHj&}FmJoq1q`Z2G3>c|53I59Ph?eYWt^ zvP?;STAHc-C|~clWS>rLj!(LR85YocYN5dNytz*J8C-Q#fW&s zlXQm@k_-#3D);W^Pedf9x!{K=b08GIKIMZ5UB!WGUXp#q-fqasx1x^iZ%E~xjW1V8 zH;*XYlIGc8wrD)Qi;7-@I@U_%U5m0k(v*4rIPtmfTDG*;$&v>FsV|XPgbdP4u&SwG4@TV(txc}jgqXAksijURk@N0 i-^B$U>7-jSLAoQWH09*e@JS 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 P@>3)0r-lCo8@O(^@Pfaq diff --git a/scripting/include/hats/editor.sp b/scripting/include/hats/editor.sp deleted file mode 100644 index 783c767..0000000 --- a/scripting/include/hats/editor.sp +++ /dev/null @@ -1,1024 +0,0 @@ -int BUILDER_COLOR[4] = { 0, 255, 0, 235 }; -int GLOW_BLUE[4] = { 3, 148, 252 }; -int GLOW_RED_ALPHA[4] = { 255, 0, 0, 235 }; -int GLOW_WHITE[4] = { 255, 255, 255, 255 }; -int GLOW_GREEN[4] = { 3, 252, 53 }; -float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 }; - -char ON_OFF_STRING[2][] = { - "\x05OFF\x01", - "\x05ON\x01" -} -char COLOR_INDEX[4] = "RGBA"; - -enum editMode { - INACTIVE = 0, - MOVE_ORIGIN, - SCALE, - COLOR, - FREELOOK, -} -char MODE_NAME[5][] = { - "Error", - "Move & Rotate", - "Scale", - "Color", - "Freelook" -} - -enum { - Edit_None, - Edit_Copy = 1, - Edit_Preview = 2, - Edit_WallCreator = 4, - Edit_Manager = 8 -} - -enum buildType { - Build_Solid, - Build_Physics, - Build_NonSolid, - // TODO: Build_Weapon (spawn as weapon?) -} - - -enum StackerDirection { - Stack_Off, - Stack_Left, - Stack_Right, - Stack_Forward, - Stack_Backward, - Stack_Up, - Stack_Down -} - -char STACK_DIRECTION_NAME[7][] = { - "\x05OFF", - "\x04Left", - "\x04Right", - "\x04Forward", - "\x04Backward", - "\x04Up", - "\x04Down", -} - -ArrayList createdWalls; - -enum struct EditorData { - int client; - char classname[64]; - char data[32]; - char name[32]; - - float origin[3]; - float angles[3]; - float prevOrigin[3]; // for cancelling edits - float prevAngles[3]; - - float mins[3]; - float size[3]; - - int color[4]; - int colorIndex; - int axis; - int snapAngle; - float rotateSpeed; - int moveSpeed; - float moveDistance; - int entity; - bool hasCollision; /// possibly merge into .flags - bool hasCollisionRotate; //^ - StackerDirection stackerDirection; - - editMode mode; - buildType buildType; - int flags; - - PrivateForward callback; - bool isEditCallback; - - void Reset(bool initial = false) { - // Clear preview entity - if(this.entity != INVALID_ENT_REFERENCE && (this.flags & Edit_Preview) && IsValidEntity(this.entity)) { - RemoveEntity(this.entity); - } - this.stackerDirection = Stack_Off; - this.entity = INVALID_ENT_REFERENCE; - this.data[0] = '\0'; - this.name[0] = '\0'; - this.size[0] = this.size[1] = this.size[2] = 5.0; - this.angles[0] = this.angles[1] = this.angles[2] = 0.0; - this.colorIndex = 0; - this.axis = 0; - this.moveDistance = 200.0; - this.flags = Edit_None; - this.classname[0] = '\0'; - this.CalculateMins(); - this.SetMode(INACTIVE); - this.rotateSpeed = 0.1; - // Settings that don't get reset on new spawns: - if(initial) { - this.color[0] = this.color[1] = this.color[2] = this.color[3] = 255; - this.moveSpeed = 1; - this.snapAngle = 30; - this.hasCollision = true; - this.hasCollisionRotate = false; - this.buildType = Build_Solid; - } - } - - void CalculateMins() { - this.mins[0] = -this.size[0]; - this.mins[1] = -this.size[1]; - this.mins[2] = -this.size[2]; - } - - void Draw(int color[4], float lifetime, float amplitude = 0.1) { - if(this.flags & Edit_WallCreator || this.entity == INVALID_ENT_REFERENCE) { - Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0); - } else { - if(this.snapAngle != 1) { - this.angles[0] = RoundToNearestInterval(this.angles[0], this.snapAngle); - this.angles[1] = RoundToNearestInterval(this.angles[1], this.snapAngle); - this.angles[2] = RoundToNearestInterval(this.angles[2], this.snapAngle); - - } - TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); - } - Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0); - } - - // Updates the entity with certain changed settings - void UpdateEntity() { - int alpha = this.color[3]; - // Keep previews transparent - SetEntityRenderColor(this.entity, this.color[0], this.color[1], this.color[2], alpha); - } - - bool CheckEntity() { - if(this.entity != INVALID_ENT_REFERENCE) { - if(this.entity == -1 && !IsValidEntity(this.entity)) { - PrintToChat(this.client, "\x04[Editor]\x01 Entity has vanished, editing cancelled."); - this.Reset(); - return false; - } - } - return true; - } - - bool IsActive() { - return this.mode != INACTIVE && this.CheckEntity(); - } - - void SetMode(editMode mode) { - this.mode = mode; - } - - void SetData(const char[] data) { - strcopy(this.data, sizeof(this.data), data); - } - void SetName(const char[] name) { - strcopy(this.name, sizeof(this.name), name); - } - void SetCallback(PrivateForward callback, bool isEditCallback) { - this.callback = callback; - this.isEditCallback = isEditCallback; - } - - void CycleMode() { - // Remove frozen state when cycling - int flags = GetEntityFlags(this.client) & ~FL_FROZEN; - SetEntityFlags(this.client, flags); - switch(this.mode) { - // MODES: - // - MOVE & ROTAT - // - SCALE or COLOR - // - FREELOOK - case MOVE_ORIGIN: { - if(this.flags & Edit_WallCreator) { - this.mode = SCALE; - } else if(this.flags & Edit_Preview) { - this.mode = COLOR; - } else { - this.mode = FREELOOK; - } - } - case SCALE: { - this.mode = FREELOOK; - } - case COLOR: { - this.mode = FREELOOK; - } - case FREELOOK: { - this.mode = MOVE_ORIGIN; - } - } - PrintToChat(this.client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change)", MODE_NAME[this.mode]); - } - - void CycleStacker() { - int newDirection = view_as(this.stackerDirection) + 1; - if(newDirection == view_as(Stack_Down)) newDirection = 0; - this.stackerDirection = view_as(newDirection); - - PrintToChat(this.client, "\x04[Editor]\x01 Stacker: %s\x01", STACK_DIRECTION_NAME[this.stackerDirection]); - } - - void ToggleCollision() { - this.hasCollision = !this.hasCollision - PrintToChat(this.client, "\x04[Editor]\x01 Collision: %s", ON_OFF_STRING[view_as(this.hasCollision)]); - } - - void ToggleCollisionRotate() { - this.hasCollisionRotate = !this.hasCollisionRotate - PrintToChat(this.client, "\x04[Editor]\x01 Rotate with Collision: %s", ON_OFF_STRING[view_as(this.hasCollisionRotate)]); - } - - void CycleAxis() { - // if(tick - cmdThrottle[this.client] <= 0.1) return; - if(this.axis == 0) { - this.axis = 1; - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01"); - } else { - this.axis = 0; - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH AND HEADING (X, Y)\x01"); - } - // cmdThrottle[this.client] = tick; - } - - void IncrementAxis(int axis, int mouse) { - if(this.snapAngle == 1) { - this.angles[axis] += mouse * this.rotateSpeed; - } else { - if(mouse > 0) this.angles[axis] += this.snapAngle; - else if(mouse < 0) this.angles[axis] -= this.snapAngle; - } - } - - void CycleSnapAngle(float tick) { - if(tick - cmdThrottle[this.client] <= 0.1) return; - switch(this.snapAngle) { - case 1: this.snapAngle = 15; - case 15: this.snapAngle = 30; - case 30: this.snapAngle = 45; - case 45: this.snapAngle = 90; - case 90: this.snapAngle = 1; - } - - // this.angles[0] = SnapTo(this.angles[0], float(this.snapAngle)); - // this.angles[1] = SnapTo(this.angles[1], float(this.snapAngle)); - // this.angles[2] = SnapTo(this.angles[2], float(this.snapAngle)); - - if(this.snapAngle == 1) - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle); - else - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle); - cmdThrottle[this.client] = tick; - } - - void CycleSpeed(float tick) { - if(tick - cmdThrottle[this.client] <= 0.25) return; - this.moveSpeed++; - if(this.moveSpeed > 10) this.moveSpeed = 1; - PrintToChat(this.client, "\x04[Editor]\x01 Scale Speed: \x05%d\x01", this.moveSpeed); - cmdThrottle[this.client] = tick; - } - - void CycleBuildType() { - // No tick needed, is handled externally - if(this.classname[0] != '\0') { - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05%s\x01 (fixed)", this.classname); - } else if(this.buildType == Build_Physics) { - this.buildType = Build_Solid; - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Solid\x01"); - } else if(this.buildType == Build_Solid) { - this.buildType = Build_Physics; - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Physics\x01"); - } else { - this.buildType = Build_NonSolid; - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Non Solid\x01"); - } - } - - void CycleColorComponent(float tick) { - if(tick - cmdThrottle[this.client] <= 0.25) return; - this.colorIndex++; - if(this.colorIndex > 3) this.colorIndex = 0; - char component[16]; - for(int i = 0; i < 4; i++) { - if(this.colorIndex == i) - Format(component, sizeof(component), "%s \x05%c\x01", component, COLOR_INDEX[i]); - else - Format(component, sizeof(component), "%s %c", component, COLOR_INDEX[i]); - } - PrintToChat(this.client, "\x04[Editor]\x01 Color: %s", component); - cmdThrottle[this.client] = tick; - } - - void IncrementSize(int axis, float amount) { - this.size[axis] += amount; - if(this.size[axis] < 0.0) { - this.size[axis] = 0.0; - } - this.CalculateMins(); - } - - void IncreaseColor(int amount) { - int newValue = this.color[this.colorIndex] + amount; - if(newValue > 255) newValue = 255; - else if(newValue < 0) newValue = 0; - this.color[this.colorIndex] = newValue; - this.UpdateEntity(); - PrintCenterText(this.client, "%d %d %d %d", this.color[0], this.color[1], this.color[2], this.color[3]); - } - - // Complete the edit, wall creation, or spawning - CompleteType Done(int& entity) { - CompleteType type; - if(this.flags & Edit_WallCreator) { - type = this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError; - } else if(this.flags & Edit_Preview) { - type = this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError; - } else { - // Is edit, do nothing, just reset - PrintHintText(this.client, "Edit Complete"); - this.Reset(); - entity = 0; - - type = Complete_EditSuccess; - } - if(this.callback) { - Call_StartForward(this.callback); - Call_PushCell(this.client); - Call_PushCell(entity); - Call_PushCell(type); - bool result; - Call_Finish(result); - // Cancel menu: - if(this.isEditCallback) delete this.callback; - if(this.isEditCallback || !result) { - // No native way to close a menu, so open a dummy menu and close it: - // Handler doesn't matter, no options are added - Menu menu = new Menu(Spawn_RootHandler); - menu.Display(this.client, 1); - } else { - delete this.callback; - } - } - return type; - } - - bool _FinishWall(int& id) { - if(~this.flags & Edit_WallCreator) { - this.Reset(); - return false; - } - // Don't need to build a new one if we editing: - int blocker = this.entity; - bool isEdit = false; - if(blocker != INVALID_ENT_REFERENCE) { - RemoveEntity(this.entity); - isEdit = true; - } - blocker = CreateEntityByName("func_brush"); - if(blocker == -1) return false; - DispatchKeyValueVector(blocker, "mins", this.mins); - DispatchKeyValueVector(blocker, "maxs", this.size); - DispatchKeyValueVector(blocker, "boxmins", this.mins); - DispatchKeyValueVector(blocker, "boxmaxs", this.size); - DispatchKeyValue(blocker, "excludednpc", "player"); - - DispatchKeyValueVector(blocker, "angles", this.angles); - DispatchKeyValue(blocker, "model", DUMMY_MODEL); - DispatchKeyValue(blocker, "intialstate", "1"); - // DispatchKeyValueVector(blocker, "angles", this.angles); - DispatchKeyValue(blocker, "BlockType", "4"); - char name[32]; - Format(name, sizeof(name), "l4d2_hats_%d", createdWalls.Length); - DispatchKeyValue(blocker, "targetname", name); - // DispatchKeyValue(blocker, "excludednpc", "player"); - TeleportEntity(blocker, this.origin, this.angles, NULL_VECTOR); - if(!DispatchSpawn(blocker)) return false; - SetEntPropVector(blocker, Prop_Send, "m_vecMins", this.mins); - SetEntPropVector(blocker, Prop_Send, "m_vecMaxs", this.size); - SetEntProp(blocker, Prop_Send, "m_nSolidType", 2); - int enteffects = GetEntProp(blocker, Prop_Send, "m_fEffects"); - enteffects |= 32; //EF_NODRAW - SetEntProp(blocker, Prop_Send, "m_fEffects", enteffects); - AcceptEntityInput(blocker, "Enable"); - SDKHook(blocker, SDKHook_Use, OnWallClicked); - - this.Draw(GLOW_GREEN, 5.0, 1.0); - this.Reset(); - if(!isEdit) { - id = createdWalls.Push(EntIndexToEntRef(blocker)); - PrintToChat(this.client, "\x04[Editor]\x01 Created wall \x05#%d\x01.", id); - } - return true; - } - - bool _FinishPreview(int& entity) { - if(StrContains(this.classname, "weapon") > -1) { - entity = this._CreateWeapon(); - } else { - entity = this._CreateProp(); - } - - DispatchKeyValue(entity, "targetname", "propspawner_prop"); - TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); - if(!DispatchSpawn(entity)) { - return false; - } - SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], this.color[3]); - SetEntityRenderColor(this.entity, 255, 128, 255, 200); // reset ghost color - GlowEntity(entity, 1.1); - - // Confusing when we resume into freelook, so reset - if(this.mode == FREELOOK) - this.SetMode(MOVE_ORIGIN); - - // Add to spawn list and add to recent list - AddSpawnedItem(entity, this.client); - char model[128]; - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - AddRecent(model, this.name); - - // Get the new position for preview with regards to this.stackerDirection - if(this.stackerDirection != Stack_Off) { - float size[3]; - GetEntityDimensions(this.entity, size); - float sign = 1.0; - if(this.stackerDirection == Stack_Left || this.stackerDirection == Stack_Right) { - if(this.stackerDirection == Stack_Left) sign = -1.0; - GetSidePositionFromOrigin(this.origin, this.angles, sign * size[1] * 0.90, this.origin); - } else if(this.stackerDirection == Stack_Forward || this.stackerDirection == Stack_Backward) { - if(this.stackerDirection == Stack_Backward) sign = -1.0; - GetHorizontalPositionFromOrigin(this.origin, this.angles, sign * size[0] * 0.90, this.origin); - } else { - if(this.stackerDirection == Stack_Down) sign = -1.0; - this.origin[2] += (size[2] * sign); - } - } - PrintHintText(this.client, "%s\n%s", this.classname, this.data); - // PrintToChat(this.client, "\x04[Editor]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity); - // Don't kill preview until cancel - return true; - } - - int _CreateWeapon() { - int entity = -1; - entity = CreateEntityByName(this.classname); - if(entity == -1) return -1; - if(StrEqual(this.classname, "weapon_melee_spawn")) { - DispatchKeyValue(entity, "melee_weapon", this.data); - } - DispatchKeyValue(entity, "count", "1"); - DispatchKeyValue(entity, "spawnflags", "10"); - return entity; - } - - int _CreateProp() { - int entity = -1; - if(this.classname[0] != '\0') { - entity = CreateEntityByName(this.classname); - } else if(this.buildType == Build_Physics) - entity = CreateEntityByName("prop_physics"); - else - entity = CreateEntityByName("prop_dynamic_override"); - if(entity == -1) return false; - - char model[128]; - GetEntPropString(this.entity, Prop_Data, "m_ModelName", model, sizeof(model)); - DispatchKeyValue(entity, "model", model); - if(this.buildType == Build_NonSolid) - DispatchKeyValue(entity, "solid", "0"); - else - DispatchKeyValue(entity, "solid", "6"); - return entity; - } - - // Turns current entity into a copy (not for walls) - int Copy() { - if(this.entity == INVALID_ENT_REFERENCE) return -1; - char classname[64]; - GetEntityClassname(this.entity, classname, sizeof(classname)); - - int entity = CreateEntityByName(classname); - if(entity == -1) return -1; - GetEntPropString(this.entity, Prop_Data, "m_ModelName", classname, sizeof(classname)); - DispatchKeyValue(entity, "model", classname); - - - Format(classname, sizeof(classname), "hats_copy_%d", this.entity); - DispatchKeyValue(entity, "targetname", classname); - - DispatchKeyValue(entity, "solid", "6"); - - DispatchSpawn(entity); - if(StrEqual(this.classname, "prop_wall_breakable")) { - DispatchKeyValue(entity, "classname", "prop_door_rotating"); - } - TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); - this.entity = entity; - this.flags |= Edit_Copy; - return entity; - } - - // Start editing a new wall entity - void StartWall() { - this.Reset(); - this.flags |= Edit_WallCreator; - } - - bool PreviewWeapon(const char[] classname, const char[] data) { - int entity; - // Melee weapons don't have weapon_ prefix - this.Reset(); - // Rotate on it's side: - this.angles[2] = 90.0; - if(StrEqual(classname, "weapon_melee_spawn")) { - // no weapon_ prefix, its a melee - entity = CreateEntityByName(classname); - if(entity == -1) return false; - DispatchKeyValue(entity, "melee_weapon", data); - this.SetData(data); - strcopy(this.classname, sizeof(this.classname), classname); - } else { - entity = CreateEntityByName(data); - if(entity == -1) return false; - strcopy(this.classname, sizeof(this.classname), data); - } - DispatchKeyValue(entity, "count", "1"); - DispatchKeyValue(entity, "spawnflags", "10"); - DispatchKeyValue(entity, "targetname", "prop_preview"); - DispatchKeyValue(entity, "rendercolor", "255 128 255"); - DispatchKeyValue(entity, "renderamt", "200"); - DispatchKeyValue(entity, "rendermode", "1"); - TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR); // MUST teleport before spawn or it crashes - if(!DispatchSpawn(entity)) { - PrintToServer("Failed to spawn"); - return false; - } - this.entity = entity; - this.flags |= (Edit_Copy | Edit_Preview); - this.SetMode(MOVE_ORIGIN); - // Seems some entities fail here: - return IsValidEntity(entity); - } - - bool PreviewModel(const char[] model, const char[] classname = "") { - // Check for an invalid model - // this.origin is not cleared by this.Reset(); - this.Reset(); - GetClientAbsOrigin(this.client, this.origin); - if(StrEqual(classname, "_weapon") || StrEqual(classname, "weapon_melee_spawn")) { - // Pass in melee ID as data: - return this.PreviewWeapon(classname, model); - } - if(PrecacheModel(model) == 0) { - PrintToServer("Invalid model: %s", model); - return false; - } - this.Reset(); - int entity = CreateEntityByName("prop_door_rotating"); - if(classname[0] == '\0') { - entity = CreateEntityByName("prop_dynamic_override"); - } else { - strcopy(this.classname, sizeof(this.classname), classname); - entity = CreateEntityByName(classname); - } - if(entity == -1) { - PrintToServer("Invalid classname: %s", classname); - return false; - } - DispatchKeyValue(entity, "model", model); - DispatchKeyValue(entity, "targetname", "prop_preview"); - DispatchKeyValue(entity, "solid", "0"); - DispatchKeyValue(entity, "rendercolor", "255 128 255"); - DispatchKeyValue(entity, "renderamt", "255"); - DispatchKeyValue(entity, "rendermode", "1"); - TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR); - if(!DispatchSpawn(entity)) { - PrintToServer("Failed to spawn"); - return false; - } - this.entity = entity; - this.flags |= (Edit_Copy | Edit_Preview); - this.SetMode(MOVE_ORIGIN); - // Seems some entities fail here: - return IsValidEntity(entity); - } - - /** - * Adds an existing entity to the editor, to move it. - * asWallCopy: to instead copy the wall's size and position (walls only) - * @deprecated - */ - void Import(int entity, bool asWallCopy = false, editMode mode = SCALE) { - this.Reset(); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); - this.prevOrigin = this.origin; - this.prevAngles = this.angles; - GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size); - if(!asWallCopy) { - this.entity = entity; - } - this.SetMode(mode); - } - - /** - * Imports an entity - */ - void ImportEntity(int entity, int flags = 0, editMode mode = SCALE) { - this.Reset(); - this.flags = flags; - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); - this.prevOrigin = this.origin; - this.prevAngles = this.angles; - GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size); - this.entity = entity; - this.SetMode(mode); - } - - // Cancels the current placement. If the edit is a copy/preview, the entity is also deleted - // If entity is not a wall, it will be returned - void Cancel() { - // Delete any copies: - if(this.flags & Edit_Copy || this.flags & Edit_Preview) { - RemoveEntity(this.entity); - } else if(~this.flags & Edit_WallCreator) { - // Is an edit of a prop - TeleportEntity(this.entity, this.prevOrigin, this.prevAngles, NULL_VECTOR); - } - this.SetMode(INACTIVE); - PrintHintText(this.client, "Cancelled"); - if(this.callback) { - delete this.callback; - } - // CPrintToChat(this.client, "\x04[Editor]\x01 Cancelled."); - } -} - -void SendEditorMessage(int client, const char[] format, any ...) { - char message[256]; - VFormat(message, sizeof(message), format, 3); - CPrintToChat(client, "\x04[Editor]\x01 %s", message); -} - -stock float RoundToNearestInterval(float value, int interval) { - return float(RoundFloat(value / float(interval)) * interval); -} -EditorData Editor[MAXPLAYERS+1]; - -Action OnWallClicked(int entity, int activator, int caller, UseType type, float value) { - int wallId = FindWallId(entity); - if(wallId > 0) { - GlowWall(wallId, GLOW_BLUE); - AcceptEntityInput(entity, "Toggle"); - } else { - PrintHintText(activator, "Invalid wall entity (%d)", entity); - } - return Plugin_Continue; -} - - - -// TODO: Stacker, copy tool, new command? -public Action Command_MakeWall(int client, int args) { - if(Editor[client].IsActive()) { - ReplyToCommand(client, "\x04[Editor]\x01 You are currently editing an entity. Finish editing your current entity with with \x05/edit done\x01 or cancel with \x04/edit cancel\x01"); - } else { - Editor[client].StartWall(); - if(args > 0) { - // Get values for X, Y and Z axis (defaulting to 1.0): - char arg2[8]; - for(int i = 0; i < 3; i++) { - GetCmdArg(i + 1, arg2, sizeof(arg2)); - float value; - if(StringToFloatEx(arg2, value) == 0) { - value = 1.0; - } - Editor[client].size[i] = value; - } - - float rot[3]; - GetClientEyeAngles(client, rot); - // Flip X and Y depending on rotation - // TODO: Validate - if(rot[2] > 45 && rot[2] < 135 || rot[2] > -135 && rot[2] < -45) { - float temp = Editor[client].size[0]; - Editor[client].size[0] = Editor[client].size[1]; - Editor[client].size[1] = temp; - } - - Editor[client].CalculateMins(); - } - - Editor[client].SetMode(SCALE); - GetCursorLimited(client, 100.0, Editor[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Editor]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01"); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); - } - return Plugin_Handled; -} - -// TODO: move wall ids to own subcommand -Action Command_Editor(int client, int args) { - if(args == 0) { - PrintToChat(client, "\x04[Editor]\x01 Created Walls: \x05%d\x01", createdWalls.Length); - for(int i = 1; i <= createdWalls.Length; i++) { - GlowWall(i, GLOW_WHITE, 20.0); - } - return Plugin_Handled; - } - char arg1[16], arg2[16]; - GetCmdArg(1, arg1, sizeof(arg1)); - GetCmdArg(2, arg2, sizeof(arg2)); - if(StrEqual(arg1, "build") || StrEqual(arg1, "done")) { - // Remove frozen flag from user, as some modes use this - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - - int entity; - CompleteType result = Editor[client].Done(entity); - switch(result) { - case Complete_WallSuccess: { - if(entity > 0) - PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x05Wall #%d Created\x01", entity + 1); - else - PrintToChat(client, "\x04[Editor]\x01 Wall Edit: \x04Complete\x01"); - } - case Complete_PropSpawned: - PrintToChat(client, "\x04[Editor]\x01 Prop Spawned: \x04%d\x01", entity); - - case Complete_EditSuccess: - PrintToChat(client, "\x04[Editor]\x01 Entity Edited: \x04%d\x01", entity); - - default: - PrintToChat(client, "\x04[Editor]\x01 Unknown result"); - } - } else if(StrEqual(arg1, "cancel")) { - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - Editor[client].Cancel(); - if(Editor[client].flags & Edit_Preview) - PrintToChat(client, "\x04[Editor]\x01 Prop Spawer: \x04Cancelled\x01"); - else if(Editor[client].flags & Edit_WallCreator) { - PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x04Cancelled\x01"); - } else { - PrintToChat(client, "\x04[Editor]\x01 Entity Edit: \x04Cancelled\x01"); - } - } else if(StrEqual(arg1, "export")) { - // TODO: support exp #id - float origin[3], angles[3], size[3]; - if(Editor[client].IsActive()) { - origin = Editor[client].origin; - angles = Editor[client].angles; - size = Editor[client].size; - Export(client, arg2, Editor[client].entity, origin, angles, size); - } else { - int id = GetWallId(client, arg2); - if(id == -1) return Plugin_Handled; - int entity = GetWallEntity(id); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - if(HasEntProp(entity, Prop_Send, "m_vecAngles")) - GetEntPropVector(entity, Prop_Send, "m_vecAngles", angles); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); - Export(client, arg2, entity, origin, angles, size); - } - } else if(StrEqual(arg1, "delete")) { - if(Editor[client].IsActive() && args == 1) { - int entity = Editor[client].entity; - if(IsValidEntity(entity)) { - PrintToChat(client, "\x04[Editor]\x01 You are not editing any existing entity, use \x05/wall cancel\x01 to stop or \x05/wall delete "); - } else if(entity > MaxClients) { - RemoveEntity(entity); - Editor[client].Reset(); - PrintToChat(client, "\x04[Editor]\x01 Deleted current entity"); - } else { - PrintToChat(client, "\x04[Editor]\x01 Cannot delete player entities."); - } - } else if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - DeleteWall(i); - } - PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 Walls", walls); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - DeleteWall(id); - PrintToChat(client, "\x04[Editor]\x01 Deleted Wall: \x05#%d\x01", id); - } - } - } else if(StrEqual(arg1, "create")) { - ReplyToCommand(client, "\x04[Editor]\x01 Syntax: /mkwall [size x] [size y] [size z]"); - } else if(StrEqual(arg1, "toggle")) { - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - AcceptEntityInput(entity, "Toggle"); - GlowWall(i, GLOW_BLUE); - } - PrintToChat(client, "\x04[Editor]\x01 Toggled \x05%d\x01 walls", walls); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - AcceptEntityInput(entity, "Toggle"); - GlowWall(id, GLOW_BLUE); - PrintToChat(client, "\x04[Editor]\x01 Toggled Wall: \x05#%d\x01", id); - } - } - } else if(StrEqual(arg1, "filter")) { - if(args < 3) { - ReplyToCommand(client, "\x04[Editor]\x01 Syntax: \x05/walls filter \x04"); - ReplyToCommand(client, "\x04[Editor]\x01 Valid filters: \x05player"); - return Plugin_Handled; - } - - char arg3[32]; - GetCmdArg(3, arg3, sizeof(arg3)); - - SetVariantString(arg3); - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - AcceptEntityInput(entity, "SetExcluded"); - } - PrintToChat(client, "\x04[Editor]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - AcceptEntityInput(entity, "SetExcluded"); - PrintToChat(client, "\x04[Editor]\x01 Set wall #%d filter to \x05%s\x01", id, arg3); - } - } - } else if(StrEqual(arg1, "edit")) { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - Editor[client].Import(entity); - PrintToChat(client, "\x04[Editor]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); - } - } else if(StrEqual(arg1, "edite") || (arg1[0] == 'c' && arg1[1] == 'u')) { - int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false); - if(index > 0) { - Editor[client].Import(index, false, MOVE_ORIGIN); - char classname[64]; - char targetname[32]; - GetEntityClassname(index, classname, sizeof(classname)); - GetEntPropString(index, Prop_Data, "m_target", targetname, sizeof(targetname)); - PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/wall done\x01", index, classname, targetname); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Move & Rotate\x01"); - } else { - ReplyToCommand(client, "\x04[Editor]\x01 Invalid or non existent entity"); - } - } else if(StrEqual(arg1, "copy")) { - if(Editor[client].IsActive()) { - int oldEntity = Editor[client].entity; - if(oldEntity == INVALID_ENT_REFERENCE) { - PrintToChat(client, "\x04[Editor]\x01 Finish editing your wall first: \x05/wall done\x01 or \x04/wall cancel\x01"); - } else { - int entity = Editor[client].Copy(); - PrintToChat(client, "\x04[Editor]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity); - } - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - Editor[client].Import(entity, true); - GetCursorLimited(client, 100.0, Editor[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Editor]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); - } - } - } else if(StrEqual(arg1, "list")) { - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - ReplyToCommand(client, "Wall #%d - EntIndex: %d", i, EntRefToEntIndex(entity)); - } - } else { - ReplyToCommand(client, "\x04[Editor]\x01 See console for list of commands"); - GetCmdArg(0, arg1, sizeof(arg1)); - PrintToConsole(client, "%s done / build - Finishes editing, creates wall if making wall", arg1); - PrintToConsole(client, "%s cancel - Cancels editing (for entity edits is same as done)", arg1); - PrintToConsole(client, "%s list - Lists all walls", arg1); - PrintToConsole(client, "%s filter - Sets classname filter for walls, doesnt really work", arg1); - PrintToConsole(client, "%s toggle - Toggles if wall is active (collides)", arg1); - PrintToConsole(client, "%s delete - Deletes the wall(s)", arg1); - PrintToConsole(client, "%s edit - Edits wall id", arg1); - PrintToConsole(client, "%s copy [id] - If editing creates a new copy of wall/entity, else copies wall id", arg1); - PrintToConsole(client, "%s cursor - Starts editing the entity you looking at", arg1); - } - return Plugin_Handled; -} - -int GetWallId(int client, const char[] arg) { - int id; - if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) { - int entity = GetWallEntity(id); - if(!IsValidEntity(entity)) { - ReplyToCommand(client, "\x04[Editor]\x01 The wall with specified id no longer exists."); - createdWalls.Erase(id - 1); - return -2; - } - return id; - } else { - ReplyToCommand(client, "\x04[Editor]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 ); - return -1; - } -} - -int GetWallEntity(int id) { - if(id <= 0 || id > createdWalls.Length) { - ThrowError("Invalid wall id (%d)", id); - } - return createdWalls.Get(id - 1); -} - -/// Tries to find the id of the wall based off entity -int FindWallId(int entity) { - for(int i = 1; i <= createdWalls.Length; i++) { - int entRef = createdWalls.Get(i - 1); - int ent = EntRefToEntIndex(entRef); - if(ent == entity) { - return i; - } - } - return -1; -} - -void GlowWall(int id, int glowColor[4], float lifetime = 5.0) { - int ref = GetWallEntity(id); - if(IsValidEntity(ref)) { - float pos[3], mins[3], maxs[3], angles[3]; - GetEntPropVector(ref, Prop_Send, "m_angRotation", angles); - GetEntPropVector(ref, Prop_Send, "m_vecOrigin", pos); - GetEntPropVector(ref, Prop_Send, "m_vecMins", mins); - GetEntPropVector(ref, Prop_Send, "m_vecMaxs", maxs); - Effect_DrawBeamBoxRotatableToAll(pos, mins, maxs, angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, 1.0, glowColor, 0); - } -} - -void DeleteWall(int id) { - GlowWall(id, GLOW_RED_ALPHA); - int ref = GetWallEntity(id); - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - createdWalls.Erase(id - 1); -} - - void Export(int client, const char[] expType, int entity, const float origin[3], const float angles[3], const float size[3]) { - char sPath[PLATFORM_MAX_PATH]; - char currentMap[64]; - GetCurrentMap(currentMap, sizeof(currentMap)); - - BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports"); - CreateDirectory(sPath, 1406); - BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports/%s.cfg", currentMap); - File file = OpenFile(sPath, "w"); - if(file == null) { - PrintToServer("[Editor] Export: Cannot open \"%s\", cant write", sPath); - } - - PrintWriteLine(client, file, "{"); - if(entity != INVALID_ENT_REFERENCE) { - char model[64]; - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - if(StrEqual(expType, "json")) { - PrintWriteLine(client, file, "\t\"model\": \"%s\",", model); - } else{ - PrintWriteLine(client, file, "\t\"model\" \"%s\"", model); - } - } - - if(StrEqual(expType, "json")) { - PrintWriteLine(client, file, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); - PrintWriteLine(client, file, "\t\"angles\": [%.2f, %.2f, %.2f],", angles[0], angles[1], angles[2]); - PrintWriteLine(client, file, "\t\"size\": [%.2f, %.2f, %.2f]", size[0], size[1], size[2]); - } else { - PrintWriteLine(client, file, "\t\"origin\" \"%.2f %.2f %.2f\"", origin[0], origin[1], origin[2]); - PrintWriteLine(client, file, "\t\"angles\" \"%.2f %.2f %.2f\"", angles[0], angles[1], angles[2]); - PrintWriteLine(client, file, "\t\"size\" \"%.2f %.2f %.2f\"", size[0], size[1], size[2]); - } - PrintWriteLine(client, file, "}"); - delete file; -} - -void PrintWriteLine(int client, File file, const char[] format, any ...) { - char line[100]; - VFormat(line, sizeof(line), format, 4); - if(file != null) - file.WriteLine(line); - PrintToChat(client, line); -} \ No newline at end of file diff --git a/scripting/include/hats/hats.sp b/scripting/include/hats/hats.sp index d8f846c..5734a1a 100644 --- a/scripting/include/hats/hats.sp +++ b/scripting/include/hats/hats.sp @@ -1,846 +1,842 @@ -enum hatFlags { - HAT_NONE = 0, - HAT_POCKET = 1, - HAT_REVERSED = 2, - HAT_COMMANDABLE = 4, - HAT_RAINBOW = 8, - HAT_PRESET = 16 -} -enum struct HatInstance { - int entity; // The entity REFERENCE - int visibleEntity; // Thee visible entity REF - Handle yeetGroundTimer; - - // Original data for entity - float orgPos[3]; - float orgAng[3]; - float offset[3]; - float angles[3]; - int collisionGroup; - int solidType; - int moveType; - - float scale; - int flags; - float rainbowColor[3]; - int rainbowTicks; - bool rainbowReverse; - char attachPoint[32]; -} -enum hatFeatures { - HatConfig_None = 0, - HatConfig_PlayerHats = 1, - HatConfig_RespectAdminImmunity = 2, - HatConfig_FakeHat = 4, - HatConfig_NoSaferoomHats = 8, - HatConfig_PlayerHatConsent = 16, - HatConfig_InfectedHats = 32, - HatConfig_ReversedHats = 64, - HatConfig_DeleteThrownHats = 128 -} -char ActivePreset[MAXPLAYERS+1][32]; -int lastHatRequestTime[MAXPLAYERS+1]; -HatInstance hatData[MAXPLAYERS+1]; -StringMap g_HatPresets; - -#define MAX_FORBIDDEN_CLASSNAMES 15 -char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { - "prop_door_rotating_checkpoint", - "env_physics_blocker", - "env_player_blocker", - "func_brush", - "func_simpleladder", - "prop_door_rotating", - "func_button", - "func_elevator", - "func_button_timed", - "func_tracktrain", - "func_movelinear", - // "infected", - "func_lod", - "func_door", - "prop_ragdoll", - "move_rope" -}; - -#define MAX_FORBIDDEN_MODELS 2 -char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = { - "models/props_vehicles/c130.mdl", - "models/props_vehicles/helicopter_rescue.mdl" -}; - - -#define MAX_REVERSE_CLASSNAMES 2 -// Classnames that should automatically trigger reverse infected -static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { - "infected", - "func_movelinear", -}; - -Action Command_DoAHat(int client, int args) { - int hatter = GetHatter(client); - if(hatter > 0) { - ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); - PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); - return Plugin_Handled; - } - - static char cmdName[8]; - GetCmdArg(0, cmdName, sizeof(cmdName)); - AdminId adminId = GetUserAdmin(client); - bool isForced = adminId != INVALID_ADMIN_ID && StrEqual(cmdName, "sm_hatf"); - if(cvar_sm_hats_enabled.IntValue == 1) { - if(adminId == INVALID_ADMIN_ID) { - PrintToChat(client, "[Hats] Hats are for admins only"); - return Plugin_Handled; - } else if(!adminId.HasFlag(Admin_Custom2)) { - PrintToChat(client, "[Hats] You do not have permission"); - return Plugin_Handled; - } - } else if(cvar_sm_hats_enabled.IntValue == 0) { - ReplyToCommand(client, "[Hats] Hats are disabled"); - return Plugin_Handled; - } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { - PrintToChat(client, "[Hats] Hats are only available for survivors."); - return Plugin_Handled; - } - - int entity = GetHat(client); - if(entity > 0) { - if(HasFlag(client, HAT_PRESET)) { - PrintToChat(client, "[Hats] Your hat is a preset, use /hatp to remove it."); - return Plugin_Handled; - } - char arg[4]; - GetCmdArg(1, arg, sizeof(arg)); - if(arg[0] == 'e') { - ReplyToCommand(client, "\t\t\"origin\"\t\"%f %f %f\"", hatData[client].offset[0], hatData[client].offset[1], hatData[client].offset[2]); - ReplyToCommand(client, "\t\t\"angles\"\t\"%f %f %f\"", hatData[client].angles[0], hatData[client].angles[1], hatData[client].angles[2]); - return Plugin_Handled; - } else if(arg[0] == 'v') { - ReplyToCommand(client, "Flags: %d", hatData[client].flags); - // ReplyToCommand(client, "CurOffset: %f %f %f", ); - return Plugin_Handled; - } else if(arg[0] == 'a') { - ShowAttachPointMenu(client); - return Plugin_Handled; - } - // int orgEntity = entity; - if(HasFlag(client, HAT_REVERSED)) { - entity = client; - } - ClearParent(entity); - - if(arg[0] == 's') { - char sizeStr[4]; - GetCmdArg(2, sizeStr, sizeof(sizeStr)); - float size = StringToFloat(sizeStr); - if(size == 0.0) { - ReplyToCommand(client, "[Hats] Invalid size"); - return Plugin_Handled; - } - if(HasEntProp(entity, Prop_Send, "m_flModelScale")) - SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); - else - PrintHintText(client, "Hat does not support scaling"); - // Change the size of it's parent instead - int child = -1; - while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) - { - int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); - if(parent == entity) { - if(HasEntProp(child, Prop_Send, "m_flModelScale")) { - PrintToConsole(client, "found child %d for %d", child, entity); - SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); - } else { - PrintToChat(client, "Child %d for %d cannot be scaled", child, entity); - } - break; - } - } - // Reattach entity: - EquipHat(client, entity); - return Plugin_Handled; - } else if(arg[0] == 'r' && arg[1] == 'a') { - SetFlag(client, HAT_RAINBOW); - hatData[client].rainbowTicks = 0; - hatData[client].rainbowReverse = false; - hatData[client].rainbowColor[0] = 0.0; - hatData[client].rainbowColor[1] = 255.0; - hatData[client].rainbowColor[2] = 255.0; - EquipHat(client, entity); - ReplyToCommand(client, "Rainbow hats enabled"); - return Plugin_Handled; - } - - // Re-enable physics and restore collision/solidity - AcceptEntityInput(entity, "EnableMotion"); - SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); - SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); - - // Remove frozen flag (only "infected" and "witch" are frozen, but just incase:) - int flags = GetEntityFlags(entity) & ~FL_FROZEN; - SetEntityFlags(entity, flags); - - // Clear visible hats (HatConfig_FakeHat is enabled) - int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); - SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); - if(visibleEntity > 0) { - SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - RemoveEntity(visibleEntity); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - } - // Grant temp god & remove after time - tempGod[client] = true; - if(client <= MaxClients) { - SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); - } - if(entity <= MaxClients) { - SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity)); - } - - // Restore movement: - if(entity <= MaxClients) { - // If player, remove roll & and just default to WALK movetype - hatData[client].orgAng[2] = 0.0; - SetEntityMoveType(entity, MOVETYPE_WALK); - } else { - // If not player, then just use whatever they were pre-hat - SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); - } - - if(arg[0] == 'y') { // Hat yeeting: - char classname[16]; - GetEntityClassname(entity, classname, sizeof(classname)); - if(StrEqual(classname, "prop_dynamic")) { - ReplyToCommand(client, "You cannot yeet this prop (it has no physics)"); - return Plugin_Handled; - } - GetClientEyeAngles(client, hatData[client].orgAng); - GetClientAbsOrigin(client, hatData[client].orgPos); - hatData[client].orgPos[2] += 45.0; - float ang[3], vel[3]; - - // Calculate the angle to throw at - GetClientEyeAngles(client, ang); - ang[2] = 0.0; - if(ang[0] > 0.0) ang[0] = -ang[0]; - // ang[0] = -45.0; - - // Calculate velocity to throw based on direction - vel[0] = Cosine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); - vel[1] = Sine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); - vel[2] = GetRandomFloat(700.0, 900.0); - if(entity <= MaxClients) { - // For players, use the built in fling function - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - L4D2_CTerrorPlayer_Fling(entity, client, vel); - } /*else if(visibleEntity > 0) { - PrintToChat(client, "Yeeting fake car..."); - ClearParent(visibleEntity); - - SetEntProp(visibleEntity, Prop_Send, "movetype", 6); - - AcceptEntityInput(visibleEntity, "EnableMotion"); - - TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); - TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); - DataPack pack; - CreateDataTimer(4.0, Timer_PropYeetEnd, pack); - pack.WriteCell(hatData[client].entity); - pack.WriteCell(hatData[client].visibleEntity); - pack.WriteCell(hatData[client].collisionGroup); - pack.WriteCell(hatData[client].solidType); - pack.WriteCell(hatData[client].moveType); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - hatData[client].entity = INVALID_ENT_REFERENCE; - } */ else { - // For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw - GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos); - hatData[client].orgPos[2] += 35.0; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); - // Sleep the physics after enoug time for it to most likely have landed - if(hatData[client].yeetGroundTimer != null) { - // TODO: FIX null issue - delete hatData[client].yeetGroundTimer; - } - DataPack pack1; - hatData[client].yeetGroundTimer = CreateDataTimer(0.5, Timer_GroundKill, pack1, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - pack1.WriteCell(hatData[client].entity); - pack1.WriteCell(GetClientUserId(client)); - DataPack pack2; - CreateDataTimer(7.7, Timer_PropSleep, pack2); - pack2.WriteCell(hatData[client].entity); - pack2.WriteCell(GetClientUserId(client)); - } - PrintToChat(client, "[Hats] Yeeted hat"); - hatData[client].entity = INVALID_ENT_REFERENCE; - return Plugin_Handled; - } else if(arg[0] == 'c') { - float pos[3]; - // Grabs a cursor position with some checks to prevent placing into (in)visible walls - if(GetSmartCursorLocation(client, pos)) { - if(CanHatBePlaced(client, pos)) { - if(entity <= MaxClients) - L4D_WarpToValidPositionIfStuck(entity); - hatData[client].orgPos = pos; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(client, "[Hats] Placed hat on cursor."); - } - } else { - PrintToChat(client, "[Hats] Could not find cursor position."); - } - } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { - // Place the hat down on the cursor if specified OR if entity is hat - float pos[3], ang[3]; - if(HasFlag(client, HAT_REVERSED)) { - // If we are reversed, then place ourselves where our "hatter" is - GetClientEyePosition(entity, hatData[client].orgPos); - GetClientEyeAngles(entity, hatData[client].orgAng); - TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(entity, "[Hats] Placed hat in front of you."); - } else { - // If we are normal, then get position infront of us, offset by model size - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos); - ang[0] = 0.0; - float mins[3]; - GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); - pos[2] += mins[2]; - // Find the nearest ground (raytrace bottom->up) - FindGround(pos, pos); - // Check if the destination is acceptable (not saferooms if enabled) - if(CanHatBePlaced(client, pos)) { - hatData[client].orgPos = pos; - hatData[client].orgAng = ang; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(client, "[Hats] Placed hat in front of you."); - } - } - } else if(arg[0] == 'd') { - // Use the new wall editor - Editor[client].Reset(); - Editor[client].entity = EntIndexToEntRef(entity); - Editor[client].SetMode(MOVE_ORIGIN); - PrintToChat(client, "\x04[Hats] \x01Beta Prop Mover active for \x04%d", entity); - } else { - PrintToChat(client, "[Hats] Restored hat to its original position."); - } - - // Restore the scale pre-hat - if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) - SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); - - // If no other options performed, then restore to original position and remove our reference - AcceptEntityInput(entity, "Sleep"); - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - hatData[client].entity = INVALID_ENT_REFERENCE; - } else { - // Find a new hatable entity - int flags = 0; - entity = GetLookingEntity(client, Filter_ValidHats); - if(entity <= 0) { - PrintCenterText(client, "[Hats] No entity found"); - return Plugin_Handled; - } else if(entity == EntRefToEntIndex(Editor[client].entity)) { - // Prevent making an entity you editing a hat - return Plugin_Handled; - } else if(!isForced && cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) { - float posP[3], posE[3]; - GetClientEyePosition(client, posP); - GetEntPropVector(entity, Prop_Data, "m_vecOrigin", posE); - if(GetVectorDistance(posP, posE) > cvar_sm_hats_max_distance.FloatValue) { - PrintCenterText(client, "[Hats] Entity too far away"); - return Plugin_Handled; - } - } - - // Make hat reversed if 'r' passed in - char arg[4]; - if(args > 0) { - GetCmdArg(1, arg, sizeof(arg)); - if(arg[0] == 'r') { - flags |= view_as(HAT_REVERSED); - } - } - - int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); - if(parent > 0 && entity > MaxClients) { - PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); - entity = parent; - } else if(entity <= MaxClients) { // Checks for hatting a player entity - if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) > 0) { - PrintToChat(client, "[Hats] Cannot hat idle bots"); - return Plugin_Handled; - } else if(!isForced && GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { - PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); - return Plugin_Handled; - } else if(entity == EntRefToEntIndex(Editor[client].entity)) { - // Old check left in in case you hatting child entity - PrintToChat(client, "[Hats] You are currently editing this entity"); - return Plugin_Handled; - } else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - PrintToChat(client, "[Hats] Hats are not allowed in the saferoom"); - return Plugin_Handled; - } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { - PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying."); - return Plugin_Handled; - } else if(EntRefToEntIndex(hatData[entity].entity) == entity || EntRefToEntIndex(hatData[entity].entity) == client) { - PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); - return Plugin_Handled; - } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { - PrintToChat(client, "[Hats] Player hats are disabled"); - return Plugin_Handled; - } else if(!CanTarget(entity)) { - PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); - return Plugin_Handled; - } else if(!CanTarget(client)) { - PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); - return Plugin_Handled; - } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { - AdminId targetAdmin = GetUserAdmin(entity); - AdminId clientAdmin = GetUserAdmin(client); - if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { - PrintToChat(client, "[Hats] Cannot target an admin"); - return Plugin_Handled; - } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { - PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); - return Plugin_Handled; - } - } - if(!isForced && - !IsFakeClient(entity) && - cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && - ~flags & view_as(HAT_REVERSED) - ) { - int lastRequestDiff = GetTime() - lastHatRequestTime[client]; - if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { - PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", PLAYER_HAT_REQUEST_COOLDOWN - lastRequestDiff); - return Plugin_Handled; - } - - Menu menu = new Menu(HatConsentHandler); - menu.SetTitle("%N: Requests to hat you", client); - char id[8]; - Format(id, sizeof(id), "%d|1", GetClientUserId(client)); - menu.AddItem(id, "Accept"); - Format(id, sizeof(id), "%d|0", GetClientUserId(client)); - menu.AddItem(id, "Reject"); - menu.Display(entity, 12); - PrintHintText(client, "Sent hat request to %N", entity); - PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); - return Plugin_Handled; - } - } - - - char classname[64]; - GetEntityClassname(entity, classname, sizeof(classname)); - - // Check for any class that should always be reversed - if(~flags & view_as(HAT_REVERSED)) { - for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { - if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { - flags |= view_as(HAT_REVERSED); - break; - } - } - } - - EquipHat(client, entity, classname, flags); - } - return Plugin_Handled; -} - - -#define MAX_ATTACHMENT_POINTS 20 -char ATTACHMENT_POINTS[MAX_ATTACHMENT_POINTS][] = { - "eyes", - "molotov", - "pills", - "grenade", - "primary", - "medkit", - "melee", - "survivor_light", - "bleedout", - "forward", - "survivor_neck", - "muzzle_flash", - "spine", - "legL", - "legR", - "thighL", - "thighR", - "lfoot", - "rfoot", - "mouth", -}; - -void ShowAttachPointMenu(int client) { - Menu menu = new Menu(AttachPointHandler); - menu.SetTitle("Choose an attach point"); - for(int i = 0; i < MAX_ATTACHMENT_POINTS; i++) { - menu.AddItem(ATTACHMENT_POINTS[i], ATTACHMENT_POINTS[i]); - } - menu.Display(client, 0); -} - -int AttachPointHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char attachPoint[32]; - menu.GetItem(param2, attachPoint, sizeof(attachPoint)); - if(!HasHat(client)) { - ReplyToCommand(client, "No hat is equipped"); - } else { - int hat = GetHat(client); - char classname[32]; - GetEntityClassname(hat, classname, sizeof(classname)); - EquipHat(client, hat, classname, hatData[client].flags, attachPoint); - CReplyToCommand(client, "Attachment point set to {olive}%s", attachPoint); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -// Handles consent that a person to be hatted by another player -int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { - if (action == MenuAction_Select) { - char info[8]; - menu.GetItem(param2, info, sizeof(info)); - char str[2][8]; - ExplodeString(info, "|", str, 2, 8, false); - int activator = GetClientOfUserId(StringToInt(str[0])); - int hatAction = StringToInt(str[1]); - if(activator == 0) { - ReplyToCommand(target, "Player has disconnected"); - return 0; - } else if(hatAction == 1) { - if(EntRefToEntIndex(hatData[target].entity) == activator ) - PrintToChat(activator, "[Hats] Woah you can't be making a black hole, jesus be careful."); - else - EquipHat(activator, target, "player", 0); - } else { - ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); - PrintHintText(activator, "%N refused your request", target); - lastHatRequestTime[activator] = GetTime(); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -bool IsHatsEnabled(int client) { - return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 -} - -void ClearHats() { - for(int i = 1; i <= MaxClients; i++) { - if(HasHat(i)) { - ClearHat(i, false); - } - if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); - } -} -void ClearHat(int i, bool restore = false) { - - int entity = EntRefToEntIndex(hatData[i].entity); - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; - - if(visibleEntity > 0) { - SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - RemoveEntity(visibleEntity); - } - if(modifyEntity > 0) { - SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); - ClearParent(modifyEntity); - } else { - return; - } - - int flags = GetEntityFlags(entity) & ~FL_FROZEN; - SetEntityFlags(entity, flags); - // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) - // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); - SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); - // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); - SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); - - hatData[i].entity = INVALID_ENT_REFERENCE; - hatData[i].visibleEntity = INVALID_ENT_REFERENCE; - - if(HasFlag(i, HAT_REVERSED)) { - entity = i; - i = modifyEntity; - } - - if(entity <= MAXPLAYERS) { - AcceptEntityInput(entity, "EnableLedgeHang"); - } - if(restore) { - // If hat is a player, override original position to hat wearer's - if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { - GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); - } - // Restore to original position - if(HasFlag(i, HAT_REVERSED)) { - TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); - } else { - TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); - } - } -} - -bool HasHat(int client) { - return GetHat(client) > 0; -} - -int GetHat(int client) { - if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; - int index = EntRefToEntIndex(hatData[client].entity); - if(index <= 0) return -1; - if(!IsValidEntity(index)) return -1; - return index; -} - -int GetHatter(int client) { - for(int i = 1; i <= MaxClients; i++) { - if(EntRefToEntIndex(hatData[i].entity) == client) { - return i; - } - } - return -1; -} - -bool CanTarget(int victim) { - static char buf[2]; - noHatVictimCookie.Get(victim, buf, sizeof(buf)); - return StringToInt(buf) == 0; -} - -bool IsHatAllowedInSaferoom(int client) { - if(L4D_IsMissionFinalMap()) return true; - if(HasFlag(client, HAT_PRESET)) return true; - char name[32]; - GetEntityClassname(hatData[client].entity, name, sizeof(name)); - // Don't allow non-weapons in saferoom - if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { - GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); - if(StrContains(name, "gnome") != -1 || StrContains(name, "propanecanist") != -1) { - return true; - } - float mins[3], maxs[3]; - GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMins", mins); - GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMaxs", maxs); - PrintToConsoleAll("Dropping hat for %N: prop_something (%s) (min %.0f,%.0f,%.0f) (max %.0f,%.0f,%.0f)", client, name, mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); - return false; - } else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { - return true; - } - PrintToConsole(client, "Dropping hat: %s", name); - return false; -} - -bool IsHatAllowed(int client) { - char name[32]; - GetEntityClassname(hatData[client].entity, name, sizeof(name)); - if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { - GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); - if(StrContains(name, "models/props_vehicles/c130.mdl") != -1) { - return false; - } - } - return true; -} - -bool CanHatBePlaced(int client, const float pos[3]) { - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - if(IsHatAllowedInSaferoom(client)) return true; - Address nav = L4D_GetNearestNavArea(pos, 200.0); - if(nav != Address_Null) { - int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ; - if(spawnFlags & NAV_SPAWN_CHECKPOINT) { - PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client); - PrintToChat(client, "[Hats] Hat is not allowed in saferoom and has been returned."); - return false; - } - } - } - return true; -} - -void SetFlag(int client, hatFlags flag) { - hatData[client].flags |= view_as(flag); -} - -bool HasFlag(int client, hatFlags flag) { - return hatData[client].flags & view_as(flag) != 0; -} - -void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE, const char[] attachPoint = "eyes") { - if(HasHat(client)) - ClearHat(client, true); - - // Player specific tweaks - int visibleEntity; - if(entity == 0) { - ThrowError("Attempted to equip world (client = %d)", client); - return; - } - - hatData[client].entity = EntIndexToEntRef(entity); - int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; - hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); - hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); - hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); - strcopy(hatData[client].attachPoint, 32, attachPoint); - - if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - - if(modifyEntity <= MaxClients) { - AcceptEntityInput(modifyEntity, "DisableLedgeHang"); - } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { - return; - // char model[64]; - // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - // visibleEntity = CreateEntityByName("prop_dynamic"); - // DispatchKeyValue(visibleEntity, "model", model); - // DispatchKeyValue(visibleEntity, "disableshadows", "1"); - // DispatchSpawn(visibleEntity); - // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); - // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); - // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); - } - SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - // Temp remove the hat to be yoinked by another player - for(int i = 1; i <= MaxClients; i++) { - if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { - ClearHat(i); - } - } - - // Called on initial hat - if(classname[0] != '\0') { - if(entity <= MaxClients && !IsFakeClient(entity)) { - PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); - } - - // Reset things: - hatData[client].flags = 0; - hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; - hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; - - if(flags & view_as(HAT_PRESET)) { - hatData[client].flags |= view_as(HAT_PRESET); - } - - if(modifyEntity <= MaxClients) { - if(HasFlag(client, HAT_REVERSED)) { - hatData[client].offset[2] += 7.2; - } else { - hatData[client].offset[2] += 4.2; - } - } else { - float mins[3], maxs[3]; - GetEntPropVector(modifyEntity, Prop_Send, "m_vecMaxs", maxs); - GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); - PrintToServer("%s mins: %f height:%f", classname, mins[2], maxs[2] - mins[2]); - if(StrContains(classname, "weapon_molotov") > -1 || StrContains(classname, "weapon_pipe_bomb") > -1 || StrContains(classname, "weapon_vomitjar") > -1) { - hatData[client].offset[2] += 10.0; - } else { - hatData[client].offset[2] += 10.0 + mins[2]; - } - } - - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { - SetFlag(client, HAT_REVERSED); - if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { - SetFlag(client, HAT_COMMANDABLE); - } - PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); - if(entity <= MaxClients) { - LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); - PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); - } - } else { - // TODO: freeze tank - if(StrEqual(classname, "infected") || StrEqual(classname, "witch") || (entity <= MaxClients && GetClientTeam(entity) == 3 && L4D2_GetPlayerZombieClass(entity) == L4D2ZombieClass_Tank)) { - int eflags = GetEntityFlags(entity) | FL_FROZEN; - SetEntityFlags(entity, eflags); - hatData[client].offset[2] = 36.0; - } - if(entity <= MaxClients) - PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); - else - PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); - if(entity <= MaxClients) - LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); - else - LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); - } - hatData[client].scale = -1.0; - - } - AcceptEntityInput(modifyEntity, "DisableMotion"); - - // Get the data (position, angle, movement shit) - - GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); - GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); - hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); - hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); - hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); - - - if(!HasFlag(client, HAT_POCKET)) { - // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); - if(HasFlag(client, HAT_REVERSED)) { - SetParent(client, entity); - if(StrEqual(classname, "infected")) { - SetParentAttachment(modifyEntity, "head", true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, "head", true); - } else { - SetParentAttachment(modifyEntity, attachPoint, true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, attachPoint, true); - } - - if(HasFlag(client, HAT_COMMANDABLE)) { - ChooseRandomPosition(hatData[client].offset); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); - } - } else { - SetParent(entity, client); - SetParentAttachment(modifyEntity, attachPoint, true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, attachPoint, true); - } - - if(visibleEntity > 0) { - SetParent(visibleEntity, client); - SetParentAttachment(visibleEntity, attachPoint, true); - hatData[client].offset[2] += 10.0; - TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(visibleEntity, attachPoint, true); - #if defined DEBUG_HAT_SHOW_FAKE - L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); - #endif - } - - #if defined DEBUG_HAT_SHOW_FAKE - L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); - #endif - - // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); - SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); - SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); - } -} +enum hatFlags { + HAT_NONE = 0, + HAT_POCKET = 1, + HAT_REVERSED = 2, + HAT_COMMANDABLE = 4, + HAT_RAINBOW = 8, + HAT_PRESET = 16 +} +enum struct HatInstance { + int entity; // The entity REFERENCE + int visibleEntity; // Thee visible entity REF + Handle yeetGroundTimer; + + // Original data for entity + float orgPos[3]; + float orgAng[3]; + float offset[3]; + float angles[3]; + int collisionGroup; + int solidType; + int moveType; + + float scale; + int flags; + float rainbowColor[3]; + int rainbowTicks; + bool rainbowReverse; + char attachPoint[32]; +} +enum hatFeatures { + HatConfig_None = 0, + HatConfig_PlayerHats = 1, + HatConfig_RespectAdminImmunity = 2, + HatConfig_FakeHat = 4, + HatConfig_NoSaferoomHats = 8, + HatConfig_PlayerHatConsent = 16, + HatConfig_InfectedHats = 32, + HatConfig_ReversedHats = 64, + HatConfig_DeleteThrownHats = 128 +} +char ActivePreset[MAXPLAYERS+1][32]; +int lastHatRequestTime[MAXPLAYERS+1]; +HatInstance hatData[MAXPLAYERS+1]; +StringMap g_HatPresets; + +#define MAX_FORBIDDEN_CLASSNAMES 15 +char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { + "prop_door_rotating_checkpoint", + "env_physics_blocker", + "env_player_blocker", + "func_brush", + "func_simpleladder", + "prop_door_rotating", + "func_button", + "func_elevator", + "func_button_timed", + "func_tracktrain", + "func_movelinear", + // "infected", + "func_lod", + "func_door", + "prop_ragdoll", + "move_rope" +}; + +#define MAX_FORBIDDEN_MODELS 2 +char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = { + "models/props_vehicles/c130.mdl", + "models/props_vehicles/helicopter_rescue.mdl" +}; + + +#define MAX_REVERSE_CLASSNAMES 2 +// Classnames that should automatically trigger reverse infected +static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { + "infected", + "func_movelinear", +}; + +Action Command_DoAHat(int client, int args) { + int hatter = GetHatter(client); + if(hatter > 0) { + ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); + PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); + return Plugin_Handled; + } + + static char cmdName[8]; + GetCmdArg(0, cmdName, sizeof(cmdName)); + AdminId adminId = GetUserAdmin(client); + bool isForced = adminId != INVALID_ADMIN_ID && StrEqual(cmdName, "sm_hatf"); + if(cvar_sm_hats_enabled.IntValue == 1) { + if(adminId == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Hats are for admins only"); + return Plugin_Handled; + } else if(!adminId.HasFlag(Admin_Custom2)) { + PrintToChat(client, "[Hats] You do not have permission"); + return Plugin_Handled; + } + } else if(cvar_sm_hats_enabled.IntValue == 0) { + ReplyToCommand(client, "[Hats] Hats are disabled"); + return Plugin_Handled; + } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Hats are only available for survivors."); + return Plugin_Handled; + } + + int entity = GetHat(client); + if(entity > 0) { + if(HasFlag(client, HAT_PRESET)) { + PrintToChat(client, "[Hats] Your hat is a preset, use /hatp to remove it."); + return Plugin_Handled; + } + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + if(arg[0] == 'e') { + ReplyToCommand(client, "\t\t\"origin\"\t\"%f %f %f\"", hatData[client].offset[0], hatData[client].offset[1], hatData[client].offset[2]); + ReplyToCommand(client, "\t\t\"angles\"\t\"%f %f %f\"", hatData[client].angles[0], hatData[client].angles[1], hatData[client].angles[2]); + return Plugin_Handled; + } else if(arg[0] == 'v') { + ReplyToCommand(client, "Flags: %d", hatData[client].flags); + // ReplyToCommand(client, "CurOffset: %f %f %f", ); + return Plugin_Handled; + } else if(arg[0] == 'a') { + ShowAttachPointMenu(client); + return Plugin_Handled; + } + // int orgEntity = entity; + if(HasFlag(client, HAT_REVERSED)) { + entity = client; + } + ClearParent(entity); + + if(arg[0] == 's') { + char sizeStr[4]; + GetCmdArg(2, sizeStr, sizeof(sizeStr)); + float size = StringToFloat(sizeStr); + if(size == 0.0) { + ReplyToCommand(client, "[Hats] Invalid size"); + return Plugin_Handled; + } + if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); + else + PrintHintText(client, "Hat does not support scaling"); + // Change the size of it's parent instead + int child = -1; + while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) + { + int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); + if(parent == entity) { + if(HasEntProp(child, Prop_Send, "m_flModelScale")) { + PrintToConsole(client, "found child %d for %d", child, entity); + SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); + } else { + PrintToChat(client, "Child %d for %d cannot be scaled", child, entity); + } + break; + } + } + // Reattach entity: + EquipHat(client, entity); + return Plugin_Handled; + } else if(arg[0] == 'r' && arg[1] == 'a') { + SetFlag(client, HAT_RAINBOW); + hatData[client].rainbowTicks = 0; + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + hatData[client].rainbowColor[1] = 255.0; + hatData[client].rainbowColor[2] = 255.0; + EquipHat(client, entity); + ReplyToCommand(client, "Rainbow hats enabled"); + return Plugin_Handled; + } + + // Re-enable physics and restore collision/solidity + AcceptEntityInput(entity, "EnableMotion"); + SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); + SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); + + // Remove frozen flag (only "infected" and "witch" are frozen, but just incase:) + int flags = GetEntityFlags(entity) & ~FL_FROZEN; + SetEntityFlags(entity, flags); + + // Clear visible hats (HatConfig_FakeHat is enabled) + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + RemoveEntity(visibleEntity); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + } + // Grant temp god & remove after time + tempGod[client] = true; + if(client <= MaxClients) { + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); + } + if(entity <= MaxClients) { + SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity)); + } + + // Restore movement: + if(entity <= MaxClients) { + // If player, remove roll & and just default to WALK movetype + hatData[client].orgAng[2] = 0.0; + SetEntityMoveType(entity, MOVETYPE_WALK); + } else { + // If not player, then just use whatever they were pre-hat + SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); + } + + if(arg[0] == 'y') { // Hat yeeting: + char classname[16]; + GetEntityClassname(entity, classname, sizeof(classname)); + if(StrEqual(classname, "prop_dynamic")) { + ReplyToCommand(client, "You cannot yeet this prop (it has no physics)"); + return Plugin_Handled; + } + GetClientEyeAngles(client, hatData[client].orgAng); + GetClientAbsOrigin(client, hatData[client].orgPos); + hatData[client].orgPos[2] += 45.0; + float ang[3], vel[3]; + + // Calculate the angle to throw at + GetClientEyeAngles(client, ang); + ang[2] = 0.0; + if(ang[0] > 0.0) ang[0] = -ang[0]; + // ang[0] = -45.0; + + // Calculate velocity to throw based on direction + vel[0] = Cosine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); + vel[1] = Sine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); + vel[2] = GetRandomFloat(700.0, 900.0); + if(entity <= MaxClients) { + // For players, use the built in fling function + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + L4D2_CTerrorPlayer_Fling(entity, client, vel); + } /*else if(visibleEntity > 0) { + PrintToChat(client, "Yeeting fake car..."); + ClearParent(visibleEntity); + + SetEntProp(visibleEntity, Prop_Send, "movetype", 6); + + AcceptEntityInput(visibleEntity, "EnableMotion"); + + TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); + TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); + DataPack pack; + CreateDataTimer(4.0, Timer_PropYeetEnd, pack); + pack.WriteCell(hatData[client].entity); + pack.WriteCell(hatData[client].visibleEntity); + pack.WriteCell(hatData[client].collisionGroup); + pack.WriteCell(hatData[client].solidType); + pack.WriteCell(hatData[client].moveType); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + hatData[client].entity = INVALID_ENT_REFERENCE; + } */ else { + // For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw + GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos); + hatData[client].orgPos[2] += 35.0; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); + // Sleep the physics after enoug time for it to most likely have landed + if(hatData[client].yeetGroundTimer != null) { + // TODO: FIX null issue + delete hatData[client].yeetGroundTimer; + } + DataPack pack1; + hatData[client].yeetGroundTimer = CreateDataTimer(0.5, Timer_GroundKill, pack1, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + pack1.WriteCell(hatData[client].entity); + pack1.WriteCell(GetClientUserId(client)); + DataPack pack2; + CreateDataTimer(7.7, Timer_PropSleep, pack2); + pack2.WriteCell(hatData[client].entity); + pack2.WriteCell(GetClientUserId(client)); + } + PrintToChat(client, "[Hats] Yeeted hat"); + hatData[client].entity = INVALID_ENT_REFERENCE; + return Plugin_Handled; + } else if(arg[0] == 'c') { + float pos[3]; + // Grabs a cursor position with some checks to prevent placing into (in)visible walls + if(GetSmartCursorLocation(client, pos)) { + if(CanHatBePlaced(client, pos)) { + if(entity <= MaxClients) + L4D_WarpToValidPositionIfStuck(entity); + hatData[client].orgPos = pos; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat on cursor."); + } + } else { + PrintToChat(client, "[Hats] Could not find cursor position."); + } + } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { + // Place the hat down on the cursor if specified OR if entity is hat + float pos[3], ang[3]; + if(HasFlag(client, HAT_REVERSED)) { + // If we are reversed, then place ourselves where our "hatter" is + GetClientEyePosition(entity, hatData[client].orgPos); + GetClientEyeAngles(entity, hatData[client].orgAng); + TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(entity, "[Hats] Placed hat in front of you."); + } else { + // If we are normal, then get position infront of us, offset by model size + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos); + ang[0] = 0.0; + float mins[3]; + GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); + pos[2] += mins[2]; + // Find the nearest ground (raytrace bottom->up) + FindGround(pos, pos); + // Check if the destination is acceptable (not saferooms if enabled) + if(CanHatBePlaced(client, pos)) { + hatData[client].orgPos = pos; + hatData[client].orgAng = ang; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat in front of you."); + } + } + } else { + PrintToChat(client, "[Hats] Restored hat to its original position."); + } + + // Restore the scale pre-hat + if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); + + // If no other options performed, then restore to original position and remove our reference + AcceptEntityInput(entity, "Sleep"); + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + hatData[client].entity = INVALID_ENT_REFERENCE; + } else { + // Find a new hatable entity + int flags = 0; + entity = GetLookingEntity(client, Filter_ValidHats); + if(entity <= 0) { + PrintCenterText(client, "[Hats] No entity found"); + return Plugin_Handled; + } else if(false) { + // TODO: editor API call to check + // Prevent making an entity you editing a hat + return Plugin_Handled; + } else if(!isForced && cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) { + float posP[3], posE[3]; + GetClientEyePosition(client, posP); + GetEntPropVector(entity, Prop_Data, "m_vecOrigin", posE); + if(GetVectorDistance(posP, posE) > cvar_sm_hats_max_distance.FloatValue) { + PrintCenterText(client, "[Hats] Entity too far away"); + return Plugin_Handled; + } + } + + // Make hat reversed if 'r' passed in + char arg[4]; + if(args > 0) { + GetCmdArg(1, arg, sizeof(arg)); + if(arg[0] == 'r') { + flags |= view_as(HAT_REVERSED); + } + } + + int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); + if(parent > 0 && entity > MaxClients) { + PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); + entity = parent; + } else if(entity <= MaxClients) { // Checks for hatting a player entity + if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) > 0) { + PrintToChat(client, "[Hats] Cannot hat idle bots"); + return Plugin_Handled; + } else if(!isForced && GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); + return Plugin_Handled; + } else if(false) { + // TODO: use editor API to check + // Old check left in in case you hatting child entity + PrintToChat(client, "[Hats] You are currently editing this entity"); + return Plugin_Handled; + } else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + PrintToChat(client, "[Hats] Hats are not allowed in the saferoom"); + return Plugin_Handled; + } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { + PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying."); + return Plugin_Handled; + } else if(EntRefToEntIndex(hatData[entity].entity) == entity || EntRefToEntIndex(hatData[entity].entity) == client) { + PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); + return Plugin_Handled; + } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { + PrintToChat(client, "[Hats] Player hats are disabled"); + return Plugin_Handled; + } else if(!CanTarget(entity)) { + PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); + return Plugin_Handled; + } else if(!CanTarget(client)) { + PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); + return Plugin_Handled; + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { + AdminId targetAdmin = GetUserAdmin(entity); + AdminId clientAdmin = GetUserAdmin(client); + if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Cannot target an admin"); + return Plugin_Handled; + } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { + PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); + return Plugin_Handled; + } + } + if(!isForced && + !IsFakeClient(entity) && + cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && + ~flags & view_as(HAT_REVERSED) + ) { + int lastRequestDiff = GetTime() - lastHatRequestTime[client]; + if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { + PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", PLAYER_HAT_REQUEST_COOLDOWN - lastRequestDiff); + return Plugin_Handled; + } + + Menu menu = new Menu(HatConsentHandler); + menu.SetTitle("%N: Requests to hat you", client); + char id[8]; + Format(id, sizeof(id), "%d|1", GetClientUserId(client)); + menu.AddItem(id, "Accept"); + Format(id, sizeof(id), "%d|0", GetClientUserId(client)); + menu.AddItem(id, "Reject"); + menu.Display(entity, 12); + PrintHintText(client, "Sent hat request to %N", entity); + PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); + return Plugin_Handled; + } + } + + + char classname[64]; + GetEntityClassname(entity, classname, sizeof(classname)); + + // Check for any class that should always be reversed + if(~flags & view_as(HAT_REVERSED)) { + for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { + if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { + flags |= view_as(HAT_REVERSED); + break; + } + } + } + + EquipHat(client, entity, classname, flags); + } + return Plugin_Handled; +} + + +#define MAX_ATTACHMENT_POINTS 20 +char ATTACHMENT_POINTS[MAX_ATTACHMENT_POINTS][] = { + "eyes", + "molotov", + "pills", + "grenade", + "primary", + "medkit", + "melee", + "survivor_light", + "bleedout", + "forward", + "survivor_neck", + "muzzle_flash", + "spine", + "legL", + "legR", + "thighL", + "thighR", + "lfoot", + "rfoot", + "mouth", +}; + +void ShowAttachPointMenu(int client) { + Menu menu = new Menu(AttachPointHandler); + menu.SetTitle("Choose an attach point"); + for(int i = 0; i < MAX_ATTACHMENT_POINTS; i++) { + menu.AddItem(ATTACHMENT_POINTS[i], ATTACHMENT_POINTS[i]); + } + menu.Display(client, 0); +} + +int AttachPointHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char attachPoint[32]; + menu.GetItem(param2, attachPoint, sizeof(attachPoint)); + if(!HasHat(client)) { + ReplyToCommand(client, "No hat is equipped"); + } else { + int hat = GetHat(client); + char classname[32]; + GetEntityClassname(hat, classname, sizeof(classname)); + EquipHat(client, hat, classname, hatData[client].flags, attachPoint); + CReplyToCommand(client, "Attachment point set to {olive}%s", attachPoint); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +// Handles consent that a person to be hatted by another player +int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { + if (action == MenuAction_Select) { + char info[8]; + menu.GetItem(param2, info, sizeof(info)); + char str[2][8]; + ExplodeString(info, "|", str, 2, 8, false); + int activator = GetClientOfUserId(StringToInt(str[0])); + int hatAction = StringToInt(str[1]); + if(activator == 0) { + ReplyToCommand(target, "Player has disconnected"); + return 0; + } else if(hatAction == 1) { + if(EntRefToEntIndex(hatData[target].entity) == activator ) + PrintToChat(activator, "[Hats] Woah you can't be making a black hole, jesus be careful."); + else + EquipHat(activator, target, "player", 0); + } else { + ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); + PrintHintText(activator, "%N refused your request", target); + lastHatRequestTime[activator] = GetTime(); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +bool IsHatsEnabled(int client) { + return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 +} + +void ClearHats() { + for(int i = 1; i <= MaxClients; i++) { + if(HasHat(i)) { + ClearHat(i, false); + } + if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); + } +} +void ClearHat(int i, bool restore = false) { + + int entity = EntRefToEntIndex(hatData[i].entity); + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; + + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + RemoveEntity(visibleEntity); + } + if(modifyEntity > 0) { + SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); + ClearParent(modifyEntity); + } else { + return; + } + + int flags = GetEntityFlags(entity) & ~FL_FROZEN; + SetEntityFlags(entity, flags); + // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); + // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); + SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); + + hatData[i].entity = INVALID_ENT_REFERENCE; + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + + if(HasFlag(i, HAT_REVERSED)) { + entity = i; + i = modifyEntity; + } + + if(entity <= MAXPLAYERS) { + AcceptEntityInput(entity, "EnableLedgeHang"); + } + if(restore) { + // If hat is a player, override original position to hat wearer's + if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { + GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); + } + // Restore to original position + if(HasFlag(i, HAT_REVERSED)) { + TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } else { + TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } + } +} + +bool HasHat(int client) { + return GetHat(client) > 0; +} + +int GetHat(int client) { + if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; + int index = EntRefToEntIndex(hatData[client].entity); + if(index <= 0) return -1; + if(!IsValidEntity(index)) return -1; + return index; +} + +int GetHatter(int client) { + for(int i = 1; i <= MaxClients; i++) { + if(EntRefToEntIndex(hatData[i].entity) == client) { + return i; + } + } + return -1; +} + +bool CanTarget(int victim) { + static char buf[2]; + noHatVictimCookie.Get(victim, buf, sizeof(buf)); + return StringToInt(buf) == 0; +} + +bool IsHatAllowedInSaferoom(int client) { + if(L4D_IsMissionFinalMap()) return true; + if(HasFlag(client, HAT_PRESET)) return true; + char name[32]; + GetEntityClassname(hatData[client].entity, name, sizeof(name)); + // Don't allow non-weapons in saferoom + if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { + GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); + if(StrContains(name, "gnome") != -1 || StrContains(name, "propanecanist") != -1) { + return true; + } + float mins[3], maxs[3]; + GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMins", mins); + GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMaxs", maxs); + PrintToConsoleAll("Dropping hat for %N: prop_something (%s) (min %.0f,%.0f,%.0f) (max %.0f,%.0f,%.0f)", client, name, mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); + return false; + } else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { + return true; + } + PrintToConsole(client, "Dropping hat: %s", name); + return false; +} + +bool IsHatAllowed(int client) { + char name[32]; + GetEntityClassname(hatData[client].entity, name, sizeof(name)); + if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { + GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); + if(StrContains(name, "models/props_vehicles/c130.mdl") != -1) { + return false; + } + } + return true; +} + +bool CanHatBePlaced(int client, const float pos[3]) { + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + if(IsHatAllowedInSaferoom(client)) return true; + Address nav = L4D_GetNearestNavArea(pos, 200.0); + if(nav != Address_Null) { + int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ; + if(spawnFlags & NAV_SPAWN_CHECKPOINT) { + PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client); + PrintToChat(client, "[Hats] Hat is not allowed in saferoom and has been returned."); + return false; + } + } + } + return true; +} + +void SetFlag(int client, hatFlags flag) { + hatData[client].flags |= view_as(flag); +} + +bool HasFlag(int client, hatFlags flag) { + return hatData[client].flags & view_as(flag) != 0; +} + +void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE, const char[] attachPoint = "eyes") { + if(HasHat(client)) + ClearHat(client, true); + + // Player specific tweaks + int visibleEntity; + if(entity == 0) { + ThrowError("Attempted to equip world (client = %d)", client); + return; + } + + hatData[client].entity = EntIndexToEntRef(entity); + int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + strcopy(hatData[client].attachPoint, 32, attachPoint); + + if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + + if(modifyEntity <= MaxClients) { + AcceptEntityInput(modifyEntity, "DisableLedgeHang"); + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { + return; + // char model[64]; + // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + // visibleEntity = CreateEntityByName("prop_dynamic"); + // DispatchKeyValue(visibleEntity, "model", model); + // DispatchKeyValue(visibleEntity, "disableshadows", "1"); + // DispatchSpawn(visibleEntity); + // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); + // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); + // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); + } + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + // Temp remove the hat to be yoinked by another player + for(int i = 1; i <= MaxClients; i++) { + if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + } + } + + // Called on initial hat + if(classname[0] != '\0') { + if(entity <= MaxClients && !IsFakeClient(entity)) { + PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); + } + + // Reset things: + hatData[client].flags = 0; + hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; + hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; + + if(flags & view_as(HAT_PRESET)) { + hatData[client].flags |= view_as(HAT_PRESET); + } + + if(modifyEntity <= MaxClients) { + if(HasFlag(client, HAT_REVERSED)) { + hatData[client].offset[2] += 7.2; + } else { + hatData[client].offset[2] += 4.2; + } + } else { + float mins[3], maxs[3]; + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMaxs", maxs); + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); + PrintToServer("%s mins: %f height:%f", classname, mins[2], maxs[2] - mins[2]); + if(StrContains(classname, "weapon_molotov") > -1 || StrContains(classname, "weapon_pipe_bomb") > -1 || StrContains(classname, "weapon_vomitjar") > -1) { + hatData[client].offset[2] += 10.0; + } else { + hatData[client].offset[2] += 10.0 + mins[2]; + } + } + + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { + SetFlag(client, HAT_REVERSED); + if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { + SetFlag(client, HAT_COMMANDABLE); + } + PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); + if(entity <= MaxClients) { + LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); + PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); + } + } else { + // TODO: freeze tank + if(StrEqual(classname, "infected") || StrEqual(classname, "witch") || (entity <= MaxClients && GetClientTeam(entity) == 3 && L4D2_GetPlayerZombieClass(entity) == L4D2ZombieClass_Tank)) { + int eflags = GetEntityFlags(entity) | FL_FROZEN; + SetEntityFlags(entity, eflags); + hatData[client].offset[2] = 36.0; + } + if(entity <= MaxClients) + PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); + else + PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); + if(entity <= MaxClients) + LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); + else + LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); + } + hatData[client].scale = -1.0; + + } + AcceptEntityInput(modifyEntity, "DisableMotion"); + + // Get the data (position, angle, movement shit) + + GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); + GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + + + if(!HasFlag(client, HAT_POCKET)) { + // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); + if(HasFlag(client, HAT_REVERSED)) { + SetParent(client, entity); + if(StrEqual(classname, "infected")) { + SetParentAttachment(modifyEntity, "head", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "head", true); + } else { + SetParentAttachment(modifyEntity, attachPoint, true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, attachPoint, true); + } + + if(HasFlag(client, HAT_COMMANDABLE)) { + ChooseRandomPosition(hatData[client].offset); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); + } + } else { + SetParent(entity, client); + SetParentAttachment(modifyEntity, attachPoint, true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, attachPoint, true); + } + + if(visibleEntity > 0) { + SetParent(visibleEntity, client); + SetParentAttachment(visibleEntity, attachPoint, true); + hatData[client].offset[2] += 10.0; + TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(visibleEntity, attachPoint, true); + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); + #endif + } + + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); + #endif + + // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); + SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); + } +} diff --git a/scripting/include/hats/natives.sp b/scripting/include/hats/natives.sp deleted file mode 100644 index f2911ad..0000000 --- a/scripting/include/hats/natives.sp +++ /dev/null @@ -1,122 +0,0 @@ - -int Native_StartEdit(Handle plugin, int numParams) { - int client = GetNativeCell(1); - int entity = GetNativeCell(2); - Editor[client].Import(entity, false); - PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell); - fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(3)); - Editor[client].SetCallback(fwd, true); - return 0; -} -int Native_StartSpawner(Handle plugin, int numParams) { - int client = GetNativeCell(1); - g_PropData[client].Selector.Cancel(); - PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell); - fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(2)); - Editor[client].SetCallback(fwd, false); - ShowCategoryList(client, ROOT_CATEGORY); - return 0; -} -int Native_CancelEdit(Handle plugin, int numParams) { - int client = GetNativeCell(1); - Editor[client].Cancel(); - return 0; -} -int Native_IsEditorActive(Handle plugin, int numParams) { - int client = GetNativeCell(1); - Editor[client].IsActive(); - return 0; -} - -int Native_StartSelector(Handle plugin, int numParams) { - int client = GetNativeCell(1); - int color[3] = { 0, 255, 0 }; - PrivateForward fwd = new PrivateForward(ET_Single, Param_Cell, Param_Cell); - fwd.AddFunction(plugin, GetNativeFunction(2)); - GetNativeArray(3, color, 3); - int limit = GetNativeCell(4); - g_PropData[client].Selector.Start(color, 0, limit); - g_PropData[client].Selector.SetOnEnd(fwd); - return 0; -} -int Native_CancelSelector(Handle plugin, int numParams) { - int client = GetNativeCell(1); - g_PropData[client].Selector.Cancel(); - return 0; -} -int Native_IsSelectorActive(Handle plugin, int numParams) { - int client = GetNativeCell(1); - g_PropData[client].Selector.IsActive(); - return 0; -} -int Native_Selector_Start(Handle plugin, int numParams) { - int client = GetNativeCell(1); - int color[3] = { 0, 255, 0 }; - GetNativeArray(2, color, 3); - int flags = GetNativeCell(3); - int limit = GetNativeCell(4); - g_PropData[client].Selector.Start(color, flags, limit); - return 0; -} -int Native_Selector_GetCount(Handle plugin, int numParams) { - int client = GetNativeCell(1); - if(!g_PropData[client].Selector.IsActive()) { - return -1; - } else { - return g_PropData[client].Selector.list.Length; - } -} -int Native_Selector_GetActive(Handle plugin, int numParams) { - int client = GetNativeCell(1); - return g_PropData[client].Selector.IsActive(); -} -int Native_Selector_SetOnEnd(Handle plugin, int numParams) { - int client = GetNativeCell(1); - PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - fwd.AddFunction(plugin, GetNativeFunction(2)); - g_PropData[client].Selector.SetOnEnd(fwd); - return 0; -} -int Native_Selector_SetOnPreSelect(Handle plugin, int numParams) { - int client = GetNativeCell(1); - PrivateForward fwd = new PrivateForward(ET_Single, Param_Cell, Param_Cell); - if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0; - g_PropData[client].Selector.SetOnPreSelect(fwd); - return 1; -} -int Native_Selector_SetOnPostSelect(Handle plugin, int numParams) { - int client = GetNativeCell(1); - PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0; - g_PropData[client].Selector.SetOnPostSelect(fwd); - return 1; -} -int Native_Selector_SetOnUnselect(Handle plugin, int numParams) { - int client = GetNativeCell(1); - PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0; - g_PropData[client].Selector.SetOnUnselect(fwd); - return 1; -} -int Native_Selector_AddEntity(Handle plugin, int numParams) { - int client = GetNativeCell(1); - int entity = GetNativeCell(2); - g_PropData[client].Selector.AddEntity(entity, false); - return 0; -} -int Native_Selector_RemoveEntity(Handle plugin, int numParams) { - int client = GetNativeCell(1); - int entity = GetNativeCell(2); - g_PropData[client].Selector.RemoveEntity(entity); - return 0; -} -int Native_Selector_Cancel(Handle plugin, int numParams) { - int client = GetNativeCell(1); - g_PropData[client].Selector.Cancel(); - return 0; -} -int Native_Selector_End(Handle plugin, int numParams) { - int client = GetNativeCell(1); - g_PropData[client].Selector.End(); - return 0; -} \ No newline at end of file diff --git a/scripting/include/hats/props/base.sp b/scripting/include/hats/props/base.sp deleted file mode 100644 index 60e3e2f..0000000 --- a/scripting/include/hats/props/base.sp +++ /dev/null @@ -1,569 +0,0 @@ -int g_pendingSaveClient; -ArrayList g_previewItems; -CategoryData ROOT_CATEGORY; -ArrayList g_spawnedItems; // ArrayList(block=2) -ArrayList g_savedItems; // ArrayList -StringMap g_recentItems; // Key: model[128], value: RecentEntry - -/* Wish to preface this file: -* It's kinda messy. The main structs are: -* - ItemData -* - CategoryData - -The rest are kinda necessary, for sorting reasons (SearchData, RecentEntry). - -*/ -enum ChatPrompt { - Prompt_None, - Prompt_Search, - Prompt_SaveScene, - Prompt_SaveSchematic -} -enum SaveType { - Save_None, - Save_Scene, - Save_Schematic -} - -int GLOW_MANAGER[3] = { 52, 174, 235 }; - -enum struct Schematic { - char name[64]; - char creatorSteamid[32]; - char creatorName[32]; - ArrayList entities; - - void Reset() { - this.name[0] = '\0'; - this.creatorSteamid[0] = '\0'; - this.creatorName[0] = '\0'; - if(this.entities != null) delete this.entities; - } - - void AddEntity(int entity, int client) { - SaveData save; - save.FromEntity(entity); - this.entities.PushArray(save); - } - - void New(int client, const char[] name) { - if(client > 0) { - GetClientName(client, this.creatorName, sizeof(this.creatorName)); - GetClientAuthId(client, AuthId_Steam2, this.creatorSteamid, sizeof(this.creatorSteamid)); - } - strcopy(this.name, sizeof(this.name), name); - this.entities = new ArrayList(sizeof(SaveData)); - } - - bool Save() { - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics/%s.schem", this.name); - CreateDirectory("data/prop_spawner/schematics", 0775); - KeyValues kv = new KeyValues(this.name); - kv.SetString("creator_steamid", this.creatorSteamid); - kv.SetString("creator_name", this.creatorName); - kv.JumpToKey("entities"); - this.entities = new ArrayList(sizeof(SaveData)); - SaveData ent; - while(kv.GotoNextKey()) { - kv.GetVector("offset", ent.origin); - kv.GetVector("angles", ent.angles); - kv.GetColor4("color", ent.color); - kv.GetString("model", ent.model, sizeof(ent.model)); - this.entities.PushArray(ent); - } - kv.ExportToFile(path); - delete kv; - return true; - } - - bool Import(const char[] name) { - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics/%s.schem", name); - KeyValues kv = new KeyValues("root"); - if(kv.ImportFromFile(path)) { - delete kv; - return false; - } - strcopy(this.name, sizeof(this.name), name); - kv.GetString("creator_steamid", this.creatorSteamid, sizeof(this.creatorSteamid)); - kv.GetString("creator_name", this.creatorName, sizeof(this.creatorName)); - kv.JumpToKey("entities"); - this.entities = new ArrayList(sizeof(SaveData)); - SaveData ent; - while(kv.GotoNextKey()) { - kv.GetVector("offset", ent.origin); - kv.GetVector("angles", ent.angles); - kv.GetColor4("color", ent.color); - kv.GetString("model", ent.model, sizeof(ent.model)); - this.entities.PushArray(ent); - } - delete kv; - return true; - } - - /// Spawns all schematics entities, returns list of entities, first being parent. - ArrayList SpawnEntities(const float origin[3], bool asPreview = true) { - if(this.entities == null) return null; - SaveData ent; - int parent = -1; - ArrayList spawnedEntities = new ArrayList(); - for(int i = 0; i < this.entities.Length; i++) { - this.entities.GetArray(i, ent, sizeof(ent)); - int entity = ent.ToEntity(origin, asPreview); - spawnedEntities.Push(EntIndexToEntRef(entity)); - if(i == 0) { - SetParent(entity, parent) - } else { - parent = entity; - } - } - return spawnedEntities; - } -} -public any Native_SpawnSchematic(Handle plugin, int numParams) { - char name[32]; - float pos[3]; - float ang[3]; - GetNativeString(0, name, sizeof(name)); - GetNativeArray(1, pos, 3); - GetNativeArray(1, ang, 3); - Schematic schem; - if(!schem.Import(name)) { - return false; - } - ArrayList list = schem.SpawnEntities(pos, false); - delete list; - return true; -} - -enum struct PropSelectorIterator { - ArrayList _list; - int _index; - int Entity; - - void _Init(ArrayList list) { - this._list = list; - this._index = -1; - } - - bool Next() { - this._index++; - return this._index + 1 < this._list.Length; - } - -} - - -enum struct PropSelector { - int selectColor[3]; - int limit; - ArrayList list; - PrivateForward endCallback; - PrivateForward selectPreCallback; - PrivateForward selectPostCallback; - PrivateForward unSelectCallback; - int _client; - - PropSelectorIterator Iter() { - PropSelectorIterator iter; - iter._Init(this.list); - return iter; - } - - void Reset() { - if(this.endCallback) delete this.endCallback; - if(this.selectPreCallback) delete this.selectPreCallback; - if(this.selectPostCallback) delete this.selectPostCallback; - if(this.unSelectCallback) delete this.unSelectCallback; - if(this.list) delete this.list; - } - - void Start(int color[3], int flags = 0, int limit = 0) { - this.selectColor = color; - this.limit = 0; - this.list = new ArrayList(); - SendEditorMessage(this._client, "Left click to select, right click to unselect"); - SendEditorMessage(this._client, "Press WALK+USE to confirm, DUCK+USE to cancel"); - } - - void SetOnEnd(PrivateForward callback) { - this.endCallback = callback; - } - void SetOnPreSelect(PrivateForward callback) { - this.selectPreCallback = callback; - } - void SetOnPostSelect(PrivateForward callback) { - this.selectPostCallback = callback; - } - void SetOnUnselect(PrivateForward callback) { - this.unSelectCallback = callback; - } - - void StartDirect(int color[3], SelectDoneCallback callback, int limit = 0) { - PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell); - fwd.AddFunction(INVALID_HANDLE, callback); - this.Start(color, 0, limit); - this.SetOnEnd(fwd); - } - - bool IsActive() { - return this.list != null; - } - - void End() { - if(this.list == null) return; - SendEditorMessage(this._client, "Selection completed"); - // Reset glows, remove selection from our spawned props - for(int i = 0; i < this.list.Length; i++) { - int ref = this.list.Get(i); - if(IsValidEntity(ref)) { - L4D2_RemoveEntityGlow(ref); - RemoveSpawnedProp(ref); - } - } - if(this.endCallback) { - if(GetForwardFunctionCount(this.endCallback) == 0) { - PrintToServer("[Editor] Warn: Selector.End(): callback has no functions assigned to it."); - } - Call_StartForward(this.endCallback); - Call_PushCell(this._client); - Call_PushCell(this.list.Clone()); - int result = Call_Finish(); - if(result != SP_ERROR_NONE) { - PrintToServer("[Editor] Warn: Selector.End() forward error: %d", result); - } - } else { - PrintToServer("[Editor] Warn: Selector.End() called but no callback assigned, voiding list"); - } - this.Reset(); - } - - void Cancel() { - if(this.endCallback) { - Call_StartForward(this.endCallback); - Call_PushCell(this._client); - Call_PushCell(INVALID_HANDLE); - Call_Finish(); - } - if(this.list) { - for(int i = 0; i < this.list.Length; i++) { - int ref = this.list.Get(i); - L4D2_RemoveEntityGlow(ref); - } - } - PrintToChat(this._client, "\x04[Editor]\x01 Selection cancelled."); - this.Reset(); - } - - int GetEntityRefIndex(int ref) { - int index = this.list.FindValue(ref); - if(index > -1) { - return index; - } - return -1; - } - - /** Removes entity from list - * @return returns entity ref of entity removed - */ - int RemoveEntity(int entity) { - if(this.list == null) return -2; - - L4D2_RemoveEntityGlow(entity); - int ref = EntIndexToEntRef(entity); - int index = this.GetEntityRefIndex(ref); - if(index > -1) { - this.list.Erase(index); - if(this.unSelectCallback != null) { - Call_StartForward(this.unSelectCallback) - Call_PushCell(this._client); - Call_PushCell(EntRefToEntIndex(ref)); - Call_Finish(); - } - return ref; - } - return INVALID_ENT_REFERENCE; - } - - /** - * Adds entity to list - * @return index into list of entity - * @return -1 if already added - * @return -2 if callback rejected - */ - int AddEntity(int entity, bool useCallback = true) { - if(this.list == null) return -2; - - int ref = EntIndexToEntRef(entity); - if(this.GetEntityRefIndex(ref) == -1) { - if(this.selectPreCallback != null && useCallback) { - Call_StartForward(this.selectPreCallback) - Call_PushCell(this._client); - Call_PushCell(entity); - bool allowed = true; - PrintToServer("Selector.AddEntity: PRE CALLBACK pre finish"); - Call_Finish(allowed); - PrintToServer("Selector.AddEntity: PRE CALLBACK pre result %b", allowed); - if(!allowed) return -2; - } - - L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, this.selectColor, false); - int index = this.list.Push(ref); - PrintToServer("Selector.AddEntity: post CALLBACK pre"); - if(this.selectPostCallback != null && useCallback) { - Call_StartForward(this.selectPostCallback) - Call_PushCell(this._client); - Call_PushCell(entity); - //Call_PushCell(index); - Call_Finish(); - } - PrintToServer("Selector.AddEntity: post CALLBACK post"); - return index; - } - return -1; - } -} -enum struct PlayerPropData { - ArrayList categoryStack; - ArrayList itemBuffer; - bool clearListBuffer; - int lastCategoryIndex; - int lastItemIndex; - // When did the user last interact with prop spawner? (Shows hints after long inactivity) - int lastActiveTime; - char classnameOverride[64]; - ChatPrompt chatPrompt; - PropSelector Selector; - SaveType pendingSaveType; - - Schematic schematic; - int highlightedEntityRef; - int managerEntityRef; - - void Init(int client) { - this.Selector._client = client; - } - // Called on PlayerDisconnect - void Reset() { - if(this.Selector.IsActive()) this.Selector.Cancel(); - this.chatPrompt = Prompt_None; - this.clearListBuffer = false; - this.lastCategoryIndex = 0; - this.lastItemIndex = 0; - this.lastActiveTime = 0; - this.classnameOverride[0] = '\0'; - this.CleanupBuffers(); - this.pendingSaveType = Save_None; - this.schematic.Reset(); - this.managerEntityRef = INVALID_ENT_REFERENCE; - this.StopHighlight(); - } - - void StartHighlight(int entity) { - this.highlightedEntityRef = EntIndexToEntRef(entity); - L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, GLOW_MANAGER, false); - } - void StopHighlight() { - if(IsValidEntity(this.highlightedEntityRef)) { - L4D2_RemoveEntityGlow(this.highlightedEntityRef); - } - this.highlightedEntityRef = INVALID_ENT_REFERENCE; - } - - void StartSchematic(int client, const char[] name) { - this.schematic.New(client, name); - this.pendingSaveType = Save_Schematic; - PrintToChat(client, "\x04[Editor]\x01 Started new schematic: \x05%s", name); - ShowCategoryList(client, ROOT_CATEGORY); - } - - // Sets the list buffer - void SetItemBuffer(ArrayList list, bool cleanupAfterUse = false) { - // Cleanup previous buffer if exist - this.itemBuffer = list; - this.clearListBuffer = cleanupAfterUse; - } - void ClearItemBuffer() { - if(this.itemBuffer != null && this.clearListBuffer) { - PrintToServer("ClearItemBuffer(): arraylist deleted."); - delete this.itemBuffer; - } - this.clearListBuffer = false; - } - - void PushCategory(CategoryData category) { - if(this.categoryStack == null) this.categoryStack = new ArrayList(sizeof(CategoryData)); - this.categoryStack.PushArray(category); - } - - bool PopCategory(CategoryData data) { - if(this.categoryStack == null || this.categoryStack.Length == 0) return false; - int index = this.categoryStack.Length - 1; - this.categoryStack.GetArray(index, data); - this.categoryStack.Erase(index); - return true; - } - bool PeekCategory(CategoryData data) { - if(this.categoryStack == null || this.categoryStack.Length == 0) return false; - int index = this.categoryStack.Length - 1; - this.categoryStack.GetArray(index, data); - return true; - } - - void GetCategoryTitle(char[] title, int maxlen) { - CategoryData cat; - for(int i = 0; i < this.categoryStack.Length; i++) { - this.categoryStack.GetArray(i, cat); - if(i == 0) - Format(title, maxlen, "%s", cat.name); - else - Format(title, maxlen, "%s>[%s]", title, cat.name); - } - } - - bool HasCategories() { - return this.categoryStack != null && this.categoryStack.Length > 0; - } - - // Is currently only called on item/category handler cancel (to clear search/recents buffer) - void CleanupBuffers() { - this.ClearItemBuffer(); - if(this.categoryStack != null) { - delete this.categoryStack; - } - this.clearListBuffer = false; - } -} -PlayerPropData g_PropData[MAXPLAYERS+1]; - - -enum struct CategoryData { - // The display name of category - char name[64]; - // If set, overwrites the classname it is spawned as - char classnameOverride[64]; - bool hasItems; // true: items is ArrayList, false: items is ArrayList - ArrayList items; -} -enum struct ItemData { - char model[128]; - char name[64]; - - void FromSearchData(SearchData search) { - strcopy(this.model, sizeof(this.model), search.model); - strcopy(this.name, sizeof(this.name), search.name); - } -} -enum struct SearchData { - char model[128]; - char name[64]; - int index; - - void FromItemData(ItemData item) { - strcopy(this.model, sizeof(this.model), item.model); - strcopy(this.name, sizeof(this.name), item.name); - } -} - - -enum struct SaveData { - char model[128]; - buildType type; - float origin[3]; - float angles[3]; - int color[4]; - - void FromEntity(int entity) { - // Use this.model as a buffer: - GetEntityClassname(entity, this.model, sizeof(this.model)); - this.type = Build_Solid; - if(StrEqual(this.model, "prop_physics")) this.type = Build_Physics; - else if(StrEqual(this.model, "prop_dynamic")) { - if(GetEntProp(entity, Prop_Send, "m_nSolidType") == 0) { - this.type = Build_NonSolid; - } - } - - GetEntPropString(entity, Prop_Data, "m_ModelName", this.model, sizeof(this.model)); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); - GetEntityRenderColor(entity, this.color[0],this.color[1],this.color[2],this.color[3]); - } - - int ToEntity(const float offset[3], bool asPreview = true) { - int entity = -1; - if(this.type == Build_Physics) - entity = CreateEntityByName("prop_physics"); - else - entity = CreateEntityByName("prop_dynamic_override"); - if(entity == -1) { - return -1; - } - PrecacheModel(this.model); - DispatchKeyValue(entity, "model", this.model); - DispatchKeyValue(entity, "targetname", "saved_prop"); - if(asPreview) { - DispatchKeyValue(entity, "rendermode", "1"); - DispatchKeyValue(entity, "solid", "0"); - } else { - DispatchKeyValue(entity, "solid", this.type == Build_NonSolid ? "0" : "6"); - } - float pos[3]; - for(int i = 0; i < 3; i++) - pos[i] = this.origin[i] + offset[i]; - - TeleportEntity(entity, pos, this.angles, NULL_VECTOR); - if(!DispatchSpawn(entity)) { - return -1; - } - int alpha = asPreview ? 200 : this.color[3]; - SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], alpha); - - if(asPreview) - g_previewItems.Push(EntIndexToEntRef(entity)); - else - AddSpawnedItem(entity); - return entity; - } - - - void Serialize(char[] output, int maxlen) { - Format( - output, maxlen, "%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d", - this.model, this.type, this.origin[0], this.origin[1], this.origin[2], - this.angles[0], this.angles[1], this.angles[2], - this.color[0], this.color[1], this.color[2], this.color[3] - ); - } - - void Deserialize(const char[] input) { - char buffer[16]; - int index = SplitString(input, ",", this.model, sizeof(this.model)); - index += SplitString(input[index], ",", buffer, sizeof(buffer)); - this.type = view_as(StringToInt(buffer)); - for(int i = 0; i < 3; i++) { - index += SplitString(input[index], ",", buffer, sizeof(buffer)); - this.origin[i] = StringToFloat(buffer); - } - for(int i = 0; i < 3; i++) { - index += SplitString(input[index], ",", buffer, sizeof(buffer)); - this.angles[i] = StringToFloat(buffer); - } - for(int i = 0; i < 4; i++) { - index += SplitString(input[index], ",", buffer, sizeof(buffer)); - this.color[i] = StringToInt(buffer); - } - } -} - -enum struct RecentEntry { - char name[64]; - int count; -} - -#include -#include -#include -#include -#include \ No newline at end of file diff --git a/scripting/include/hats/props/cmd.sp b/scripting/include/hats/props/cmd.sp deleted file mode 100644 index 8167c3b..0000000 --- a/scripting/include/hats/props/cmd.sp +++ /dev/null @@ -1,139 +0,0 @@ -Action Command_Props(int client, int args) { - char arg[32]; - GetCmdArg(1, arg, sizeof(arg)); - if(args == 0 || StrEqual(arg, "help")) { - PrintToChat(client, "See console for available sub-commands"); - PrintToConsole(client, "help - this"); - PrintToConsole(client, "list - lists all props and their distances"); - PrintToConsole(client, "search "); - PrintToConsole(client, "edit "); - PrintToConsole(client, "del "); - PrintToConsole(client, "add "); - PrintToConsole(client, "favorite - favorites active editor entity"); - PrintToConsole(client, "controls - list all the controls"); - PrintToConsole(client, "reload - reload prop list"); - PrintToConsole(client, "schem[atic] "); - } else if(StrEqual(arg, "schem") || StrEqual(arg, "schematic")) { - char arg2[16]; - GetCmdArg(2, arg2, sizeof(arg2)); - if(StrEqual(arg2, "new")) { - char name[32]; - GetCmdArg(3, name, sizeof(name)); - if(name[0] == '\0') { - PrintToChat(client, "\x04[Editor]\x01 Please enter a name"); - } else { - g_PropData[client].StartSchematic(client, name); - } - } else if(StrEqual(arg2, "save")) { - if(g_PropData[client].pendingSaveType == Save_Schematic) { - g_PropData[client].schematic.Save(); - } else { - PrintToChat(client, "\x04[Editor]\x01 No schematic to save."); - } - } else { - PrintToChat(client, "\x04[Editor]\x01 Unknown option: %s", arg2); - } - } else if(StrEqual(arg, "list")) { - char arg2[16]; - GetCmdArg(2, arg2, sizeof(arg2)); - bool isClassname = StrEqual(arg2, "classname"); - bool isIndex = StrEqual(arg2, "index"); - bool isOwner = StrEqual(arg2, "owner"); - if(args == 1 || isClassname || isIndex || isOwner) { - PrintToChat(client, "\x04[Editor]\x01 Please specify: \x05classname, index, owner. "); - return Plugin_Handled; - } - float pos[3], propPos[3], dist; - GetAbsOrigin(client, pos); - for(int i = 0; i < g_spawnedItems.Length; i++) { - int ref = GetSpawnedItem(i); - if(ref > -1) { - GetEntPropVector(ref, Prop_Send, "m_vecOrigin", propPos); - dist = GetVectorDistance(pos, propPos, false); - if(isIndex) { - int entity = EntRefToEntIndex(ref); - PrintToConsole(client, "%d. ent #%d - %.0fu away", i, entity, dist); - } else if(isClassname) { - char classname[32]; - GetEntityClassname(ref, classname, sizeof(classname)); - PrintToConsole(client, "%d. %s - %.0fu away", i, classname, dist); - } else if(isOwner) { - int spawner = g_spawnedItems.Get(i, 1); - int player = GetClientOfUserId(spawner); - if(player > 0) { - PrintToConsole(client, "%d. %N - %.0fu away", i, player, dist); - } else { - PrintToConsole(client, "%d. (disconnected) - %.0fu away", i, dist); - } - } - } - } - PrintToChat(client, "\x04[Editor]\x01 Check console"); - } else if(StrEqual(arg, "search")) { - if(args == 1) { - PrintToChat(client, "\x04[Editor]\x01 Enter your search query:"); - g_PropData[client].chatPrompt = Prompt_Search; - } else { - char query[32]; - GetCmdArg(2, query, sizeof(query)); - DoSearch(client, query); - } - } else if(StrEqual(arg, "edit")) { - char arg2[32]; - GetCmdArg(2, arg2, sizeof(arg2)); - int index; - if(StrEqual(arg2, "last")) { - // Get last one - index = GetSpawnedIndex(client, -1); - } else { - index = StringToInt(arg2); - } - if(index >= 0 && index < g_spawnedItems.Length) { - int entity = EntRefToEntIndex(g_spawnedItems.Get(index)); - Editor[client].Import(entity); - PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity); - } else { - PrintToChat(client, "\x04[Editor]\x01 Invalid index, out of bounds. Enter a value between [0, %d]", g_spawnedItems.Length - 1); - } - } else if(StrEqual(arg, "del")) { - char arg2[32]; - GetCmdArg(2, arg2, sizeof(arg2)); - int index; - if(StrEqual(arg2, "last")) { - // Get last one - index = GetSpawnedIndex(client, -1); - } else { - index = StringToInt(arg2); - } - - if(index >= 0 && index < g_spawnedItems.Length) { - int entity = EntRefToEntIndex(g_spawnedItems.Get(index)); - if(entity > 0 && IsValidEntity(entity)) { - RemoveEntity(entity); - } - g_spawnedItems.Erase(index); - PrintToChat(client, "\x04[Editor]\x01 Deleted entity \x05%d", entity); - } else { - PrintToChat(client, "\x04[Editor]\x01 Invalid index, out of bounds. Enter a value between [0, %d]", g_spawnedItems.Length - 1); - } - } else if(StrEqual(arg, "controls")) { - PrintToChat(client, "View controls at https://admin.jackz.me/docs/props"); - } else if(StrEqual(arg, "favorite")) { - if(g_db == null) { - PrintToChat(client, "\x04[Editor]\x01 Cannot connect to database."); - } else if(Editor[client].IsActive()) { - char model[128]; - GetEntPropString(Editor[client].entity, Prop_Data, "m_ModelName", model, sizeof(model)); - ToggleFavorite(client, model, Editor[client].data); - } else { - PrintToChat(client, "\x04[Editor]\x01 Edit a prop to use this command."); - } - } else if(StrEqual(arg, "reload")) { - PrintHintText(client, "Reloading categories..."); - UnloadCategories(); - LoadCategories(); - } else { - PrintToChat(client, "\x05Not implemented"); - } - return Plugin_Handled; -} \ No newline at end of file diff --git a/scripting/include/hats/props/db.sp b/scripting/include/hats/props/db.sp deleted file mode 100644 index 37af63e..0000000 --- a/scripting/include/hats/props/db.sp +++ /dev/null @@ -1,125 +0,0 @@ -#define DATABASE_CONFIG_NAME "hats_editor" -Database g_db; - -bool ConnectDB() { - char error[255]; - Database db = SQL_Connect(DATABASE_CONFIG_NAME, true, error, sizeof(error)); - if (db == null) { - LogError("Database error %s", error); - return false; - } else { - PrintToServer("l4d2_hats: Connected to database %s", DATABASE_CONFIG_NAME); - db.SetCharset("utf8mb4"); - g_db = db; - return true; - } -} - -void DB_GetFavoritesCallback(Database db, DBResultSet results, const char[] error, int userid) { - if(results == null) { - PrintToServer("l4d2_hats: DB_GetFavoritesCallback returned error: \"%s\"", error); - } - int client = GetClientOfUserId(userid); - if(client > 0) { - if(results == null) { - PrintToChat(client, "\x04[Editor]\x01 Error occurred fetching favorites"); - return; - } - ArrayList list = new ArrayList(sizeof(ItemData)); - ItemData item; - while(results.FetchRow()) { - results.FetchString(0, item.model, sizeof(item.model)); - DBResult result; - results.FetchString(1, item.name, sizeof(item.name), result); - if(result == DBVal_Null) { - // No name set - use the end part of the model - int index = FindCharInString(item.model, '/', true); - strcopy(item.name, sizeof(item.name), item.model[index + 1]); - } - } - ShowTempItemMenu(client, list, "Favorites"); - } -} - -void DB_ToggleFavoriteCallback(Database db, DBResultSet results, const char[] error, DataPack pack) { - if(results == null) { - PrintToServer("l4d2_hats: DB_GetFavoriteCallback returned error: \"%s\"", error); - } - pack.Reset(); - int userid = pack.ReadCell(); - int client = GetClientOfUserId(userid); - if(client > 0) { - if(results == null) { - PrintToChat(client, "\x04[Editor]\x01 Error occurred fetching favorite data"); - delete pack; - return; - } - char query[256]; - char model[128]; - char steamid[32]; - GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid)); - pack.ReadString(model, sizeof(model)); - if(results.FetchRow()) { - // Model was favorited, erase it - g_db.Format(query, sizeof(query), "DELETE FROM editor_favorites WHERE steamid = '%s' AND model = '%s'", steamid, model); - g_db.Query(DB_DeleteFavoriteCallback, query, userid); - } else { - // Model is not favorited, save it. - char name[64]; - pack.ReadString(name, sizeof(name)); - // TODO: calculate next position automatically - int position = 0; - g_db.Format(query, sizeof(query), - "INSERT INTO editor_favorites (steamid, model, name, position) VALUES ('%s', '%s', '%s', %d)", - steamid, model, name, position - ); - g_db.Query(DB_InsertFavoriteCallback, query, pack); - } - } else { - // Only delete if we lost client - otherwise we will reuse it - delete pack; - } -} - -void DB_DeleteFavoriteCallback(Database db, DBResultSet results, const char[] error, DataPack pack) { - if(results == null) { - PrintToServer("l4d2_hats: DB_DeleteFavoriteCallback returned error: \"%s\"", error); - } - pack.Reset(); - char model[128]; - char name[64]; - int client = GetClientOfUserId(pack.ReadCell()); - if(client > 0) { - if(results == null) { - PrintToChat(client, "\x04[Editor]\x01 Could not delete favorite"); - delete pack; - return; - } - pack.ReadString(model, sizeof(model)); - pack.ReadString(name, sizeof(name)); - int index = FindCharInString(model, '/', true); - PrintToChat(client, "\x04[Editor]\x01 Removed favorite: \"%s\" \x05(%s)", model[index], name); - } - delete pack; -} -void DB_InsertFavoriteCallback(Database db, DBResultSet results, const char[] error, DataPack pack) { - if(results == null) { - PrintToServer("l4d2_hats: DB_InsertFavoriteCallback returned error: \"%s\"", error); - } - pack.Reset(); - char model[128]; - char name[64]; - int client = GetClientOfUserId(pack.ReadCell()); - if(client > 0) { - if(results == null) { - PrintToChat(client, "\x04[Editor]\x01 Could not add favorite"); - delete pack; - return; - } - pack.ReadString(model, sizeof(model)); - pack.ReadString(name, sizeof(name)); - int index = FindCharInString(model, '/', true); - PrintToChat(client, "\x04[Editor]\x01 Added favorite: \"%s\" \x05(%s)", model[index], name); - } - delete pack; -} \ No newline at end of file diff --git a/scripting/include/hats/props/menu_handlers.sp b/scripting/include/hats/props/menu_handlers.sp deleted file mode 100644 index 55133b3..0000000 --- a/scripting/include/hats/props/menu_handlers.sp +++ /dev/null @@ -1,481 +0,0 @@ -TopMenuObject g_propSpawnerCategory; -public void OnAdminMenuReady(Handle topMenuHandle) { - TopMenu topMenu = TopMenu.FromHandle(topMenuHandle); - if(g_topMenu != topMenuHandle) { - g_propSpawnerCategory = topMenu.AddCategory("hats_editor", Category_Handler, "sm_prop"); - if(g_propSpawnerCategory != INVALID_TOPMENUOBJECT) { - topMenu.AddItem("editor_spawn", AdminMenu_Spawn, g_propSpawnerCategory, "sm_prop"); - topMenu.AddItem("editor_edit", AdminMenu_Edit, g_propSpawnerCategory, "sm_prop"); - topMenu.AddItem("editor_delete", AdminMenu_Delete, g_propSpawnerCategory, "sm_prop"); - topMenu.AddItem("editor_saveload", AdminMenu_SaveLoad, g_propSpawnerCategory, "sm_prop"); - topMenu.AddItem("editor_manager", AdminMenu_Manager, g_propSpawnerCategory, "sm_prop"); - topMenu.AddItem("editor_selector", AdminMenu_Selector, g_propSpawnerCategory, "sm_prop"); - } - g_topMenu = topMenu; - } -} - -///////////// -// HANDLERS -///////////// -void Category_Handler(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayTitle) { - Format(buffer, maxlength, "Select a task:"); - } else if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Spawn Props"); - } -} - -void AdminMenu_Selector(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Selector"); - } else if(action == TopMenuAction_SelectOption) { - ShowManagerSelectorMenu(param); - } -} - - -void AdminMenu_Spawn(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Spawn Props"); - } else if(action == TopMenuAction_SelectOption) { - ConVar cheats = FindConVar("sm_cheats"); - if(cheats != null && !cheats.BoolValue) { - CReplyToCommand(param, "\x04[Editor] \x01Set \x05sm_cheats\x01 to \x051\x01 to use the prop spawner"); - return; - } - ShowSpawnRoot(param); - } -} - -int Spawn_RootHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[2]; - menu.GetItem(param2, info, sizeof(info)); - switch(info[0]) { - case 'f': Spawn_ShowFavorites(client); - case 'r': Spawn_ShowRecents(client); - case 's': Spawn_ShowSearch(client); - case 'n': ShowCategoryList(client, ROOT_CATEGORY); - } - // TODO: handle back (to top menu) - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} -void AdminMenu_Edit(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Edit Props"); - } else if(action == TopMenuAction_SelectOption) { - ShowEditList(param); - } -} -void AdminMenu_Delete(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Delete Props"); - } else if(action == TopMenuAction_SelectOption) { - ShowDeleteList(param); - } -} - -void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Save / Load"); - } else if(action == TopMenuAction_SelectOption) { - Spawn_ShowSaveLoadMainMenu(param); - } -} - -void AdminMenu_Manager(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) { - if(action == TopMenuAction_DisplayOption) { - Format(buffer, maxlength, "Manager (ALPHA)"); - } else if(action == TopMenuAction_SelectOption) { - Spawn_ShowManagerMainMenu(param); - } -} - -int SaveLoadMainMenuHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[2]; - menu.GetItem(param2, info, sizeof(info)); - SaveType type = view_as(StringToInt(info)); - ShowSaves(client, type); - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -int SaveLoadSceneHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char saveName[64]; - menu.GetItem(param2, saveName, sizeof(saveName)); - if(saveName[0] == '\0') { - // Save new - FormatTime(saveName, sizeof(saveName), "%Y-%m-%d_%H-%I-%M"); - if(CreateSceneSave(saveName)) { - PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, saveName); - } else { - PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry."); - } - } else if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) { - PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a save."); - } else if(g_PropData[client].pendingSaveType == Save_Schematic) { - PrintToChat(client, "\x04[Editor]\x01 Please complete or cancel current schematic to continue."); - } else if(LoadScene(saveName, true)) { - ConfirmSave(client, saveName); - g_pendingSaveClient = client; - PrintToChat(client, "\x04[Editor]\x01 Previewing save \x05%s", saveName); - PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel"); - } else { - PrintToChat(client, "\x04[Editor]\x01 Could not load save file."); - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - Spawn_ShowSaveLoadMainMenu(client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - - -int SaveLoadSchematicHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char saveName[64]; - menu.GetItem(param2, saveName, sizeof(saveName)); - Schematic schem; - if(saveName[0] == '\0') { - if(g_PropData[client].pendingSaveType == Save_Schematic) { - if(g_PropData[client].schematic.Save()) { - PrintToChat(client, "\x04[Editor]\x01 Saved schematic as \x05%s", g_PropData[client].schematic.name); - } else { - PrintToChat(client, "\x04[Editor]\x01 Failed to save schematic."); - } - g_PropData[client].schematic.Reset(); - g_PropData[client].pendingSaveType = Save_None; - } else { - g_PropData[client].chatPrompt = Prompt_SaveSchematic; - PrintToChat(client, "\x04[Editor]\x01 Enter in chat a name for schematic"); - } - } else if(schem.Import(saveName)) { - float pos[3]; - GetCursorLocation(client, pos); - ArrayList list = schem.SpawnEntities(pos, true); - SaveData save; - int parent = list.GetArray(0, save); - delete list; - Editor[client].Import(parent); - if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) { - PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a scene."); - } else { - g_pendingSaveClient = client; - PrintToChat(client, "\x04[Editor]\x01 Previewing schematic \x05%s", saveName); - PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel"); - } - } else { - PrintToChat(client, "\x04[Editor]\x01 Could not load save file."); - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - Spawn_ShowSaveLoadMainMenu(client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -int SaveLoadConfirmHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - ClearSavePreview(); - char info[64]; - menu.GetItem(param2, info, sizeof(info)); - if(info[0] != '\0') { - PrintToChat(client, "\x04[Editor]\x01 Loaded scene \x05%s", info); - LoadScene(info, false); - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - Spawn_ShowSaveLoadMainMenu(client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} -int ManagerHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[8]; - menu.GetItem(param2, info, sizeof(info)); - if(info[0] != '\0') { - int index = StringToInt(info); - int ref = g_spawnedItems.Get(index); - // TODO: add delete confirm - if(!IsValidEntity(ref)) { - SendEditorMessage(client, "Entity has disappeared"); - } else { - int entity = EntRefToEntIndex(ref); - g_PropData[client].managerEntityRef = ref; - g_PropData[client].StartHighlight(entity); - ShowManagerEntityMenu(client, entity); - } - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - Spawn_ShowSaveLoadMainMenu(client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} -int ManagerEntityHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - g_PropData[client].StopHighlight(); - char info[32]; - menu.GetItem(param2, info, sizeof(info)); - int ref = g_PropData[client].managerEntityRef; - if(!IsValidEntity(ref)) { - SendEditorMessage(client, "Entity disappeared"); - return 0; - } - if(StrEqual(info, "edit")) { - Editor[client].ImportEntity(EntRefToEntIndex(ref), Edit_Manager); - Spawn_ShowManagerMainMenu(client); - } else if(StrEqual(info, "delete")) { - for(int i = 0; i < g_spawnedItems.Length; i++) { - int spawnedRef = g_spawnedItems.Get(i); - if(spawnedRef == ref) { - g_spawnedItems.Erase(i); - break; - } - } - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - Spawn_ShowManagerMainMenu(client); - } else if(StrEqual(info, "view")) { - ReplyToCommand(client, "Maybe soon."); - } else if(StrEqual(info, "select")) { - ShowManagerSelectorMenu(client); - int entity = EntRefToEntIndex(ref); - g_PropData[client].Selector.AddEntity(entity); - } else { - SendEditorMessage(client, "Unknown option / not implemented"); - } - } else if (action == MenuAction_Cancel) { - g_PropData[client].StopHighlight(); - if(param2 == MenuCancel_ExitBack) { - Spawn_ShowManagerMainMenu(client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} -int ManagerSelectorMainMenuHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - if(!EntitySelector.FromClient(client).Active) { - return 0; - } - char info[32]; - menu.GetItem(param2, info, sizeof(info)); - if(StrEqual(info, "list")) { - SendEditorMessage(client, "Not implemented"); - } else if(StrEqual(info, "actions")) { - ShowManagerSelectorActionsMenu(client); - } else if(StrEqual(info, "cancel")) { - g_PropData[client].Selector.Cancel(); - } - } else if (action == MenuAction_Cancel) { - g_PropData[client].Selector.Cancel(); - } else if (action == MenuAction_End) - delete menu; - return 0; -} -int ManagerSelectorActionHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - if(!g_PropData[client].Selector.IsActive()) { - return 0; - } - char info[32]; - menu.GetItem(param2, info, sizeof(info)); - if(StrEqual(info, "delete")) { - for(int i = 0; i < g_PropData[client].Selector.list.Length; i++) { - int ref = g_PropData[client].Selector.list.Get(i); - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - } - g_PropData[client].Selector.End(); - Spawn_ShowManagerMainMenu(client); - } else if(StrEqual(info, "save")) { - // TODO: implement - SendEditorMessage(client, "Not implemented"); - } else { - SendEditorMessage(client, "Unknown option / not implemented"); - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - Spawn_ShowSaveLoadMainMenu(client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} -int COLOR_DELETE[3] = { 255, 0, 0 } - -int DeleteHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[128]; - menu.GetItem(param2, info, sizeof(info)); - int ref = StringToInt(info[2]); - int option = StringToInt(info); - if(option == -1) { - // Delete all (everyone) - int count = DeleteAll(); - PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count); - ShowDeleteList(client); - } else if(option == -2) { - // Delete all (mine only) - int count = DeleteAll(client); - PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count); - ShowDeleteList(client); - } else if(option == -3) { - if(g_PropData[client].Selector.IsActive()) { - g_PropData[client].Selector.End(); - PrintToChat(client, "\x04[Editor]\x01 Delete tool cancelled"); - } else { - g_PropData[client].Selector.StartDirect(COLOR_DELETE, OnDeleteToolEnd); - PrintToChat(client, "\x04[Editor]\x01 Delete tool active. Press \x05Left Mouse\x01 to mark props, \x05Right Mouse\x01 to undo. SHIFT+USE to spawn, CTRL+USE to cancel"); - } - ShowDeleteList(client); - } else { - int index = g_spawnedItems.FindValue(ref); - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - if(index > -1) { - g_spawnedItems.Erase(index); - index--; - } else { index = 0; } - ShowDeleteList(client, index); - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -int SpawnCategoryHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[8]; - menu.GetItem(param2, info, sizeof(info)); - int index = StringToInt(info); - // Reset item index when selecting new category - if(g_PropData[client].lastCategoryIndex != index) { - g_PropData[client].lastCategoryIndex = index; - g_PropData[client].lastItemIndex = 0; - } - CategoryData category; - g_PropData[client].PeekCategory(category); // Just need to get the category.items[index], don't want to pop - category.items.GetArray(index, category); - if(category.items == null) { - LogError("Category %s has null items array (index=%d)", category.name, index); - } else if(category.hasItems) { - ShowCategoryItemMenu(client, category); - } else { - // Reset the category index for nested - g_PropData[client].lastCategoryIndex = 0; - // Make the list now be the selected category's list. - ShowCategoryList(client, category); - } - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - CategoryData category; - // Double pop - if(g_PropData[client].PopCategory(category) && g_PropData[client].PopCategory(category)) { - // Use the last category (go back one) - ShowCategoryList(client, category); - } else { - ShowSpawnRoot(client); - } - } else { - g_PropData[client].CleanupBuffers(); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -int SpawnItemHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[132]; - menu.GetItem(param2, info, sizeof(info)); - char index[4]; - char model[128]; - int nameIndex = SplitString(info, "|", index, sizeof(index)); - nameIndex += SplitString(info[nameIndex], "|", model, sizeof(model)); - g_PropData[client].lastItemIndex = StringToInt(index); - if(Editor[client].PreviewModel(model, g_PropData[client].classnameOverride)) { - Editor[client].SetName(info[nameIndex]); - PrintHintText(client, "%s\n%s", info[nameIndex], model); - ShowHint(client); - } else { - PrintToChat(client, "\x04[Editor]\x01 Error spawning preview \x01(%s)", model); - } - // Use same item menu again: - ShowItemMenu(client); - } else if(action == MenuAction_Cancel) { - g_PropData[client].ClearItemBuffer(); - if(param2 == MenuCancel_ExitBack) { - CategoryData category; - if(g_PropData[client].PopCategory(category)) { - // Use the last category (go back one) - ShowCategoryList(client, category); - } else { - // If there is no categories, it means we are in a temp menu (search / recents / favorites) - ShowSpawnRoot(client); - } - } else { - g_PropData[client].CleanupBuffers(); - } - } else if (action == MenuAction_End) { - delete menu; - } - return 0; -} - -int EditHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char info[8]; - menu.GetItem(param2, info, sizeof(info)); - int ref = StringToInt(info); - int index = g_spawnedItems.FindValue(ref); - int entity = EntRefToEntIndex(ref); - if(entity > 0) { - Editor[client].Import(entity, false); - PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity); - } else { - PrintToChat(client, "\x04[Editor]\x01 Entity disappeared."); - if(index > -1) { - g_spawnedItems.Erase(index); - index--; - } else { index = 0; } - } - ShowEditList(client, index); - } else if (action == MenuAction_Cancel) { - if(param2 == MenuCancel_ExitBack) { - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} diff --git a/scripting/include/hats/props/menu_methods.sp b/scripting/include/hats/props/menu_methods.sp deleted file mode 100644 index 716bb1e..0000000 --- a/scripting/include/hats/props/menu_methods.sp +++ /dev/null @@ -1,315 +0,0 @@ -///////////// -// METHODS -///////////// -void ShowSpawnRoot(int client) { - Menu menu = new Menu(Spawn_RootHandler); - menu.SetTitle("Choose spawn list:"); - menu.AddItem("f", "Favorites (Broken :D)"); - menu.AddItem("r", "Recently Spawned Props"); - menu.AddItem("s", "Search for Props"); - menu.AddItem("n", "Browse Props"); - menu.ExitBackButton = true; - menu.ExitButton = true; - menu.Display(client, MENU_TIME_FOREVER); -} -void Spawn_ShowRecents(int client) { - if(g_recentItems == null) LoadRecents(); - ArrayList items = GetRecentsItemList(); - if(items.Length == 0) { - CReplyToCommand(client, "\x04[Editor] \x01No recent props spawned."); - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - return; - } - ShowTempItemMenu(client, items, "Recents"); -} -void Spawn_ShowSearch(int client) { - g_PropData[client].chatPrompt = Prompt_Search; - CReplyToCommand(client, "\x04[Editor] \x01Please enter search query in chat:"); -} -void ShowDeleteList(int client, int index = -3) { - if(g_spawnedItems.Length == 0) { - SendEditorMessage(client, "No spawned items to delete"); - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - return; - } - Menu menu = new Menu(DeleteHandler); - menu.SetTitle("Delete Props"); - - menu.AddItem("-1", "Delete All"); - menu.AddItem("-2", "Delete All (Mine Only)"); - menu.AddItem("-3", "Delete Tool"); - // menu.AddItem("-4", "Delete Last Save"); - char info[8]; - char buffer[128]; - for(int i = 0; i < g_spawnedItems.Length; i++) { - int ref = GetSpawnedItem(i); - if(ref == -1) continue; - Format(info, sizeof(info), "0|%d", ref); - GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - index = FindCharInString(buffer, '/', true); - if(index != -1) - menu.AddItem(info, buffer[index + 1]); - } - - menu.ExitBackButton = true; - menu.ExitButton = true; - // Add +3 to the index for the 3 "Delete ..." buttons - // TODO: restore the delete index issue, use /7*7 - menu.DisplayAt(client, 0, MENU_TIME_FOREVER); -} -void ShowEditList(int client, int index = 0) { - if(g_spawnedItems.Length == 0) { - SendEditorMessage(client, "No spawned items to edit"); - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - return; - } - Menu menu = new Menu(EditHandler); - menu.SetTitle("Edit Prop"); - - char info[8]; - char buffer[32]; - for(int i = 0; i < g_spawnedItems.Length; i++) { - int ref = GetSpawnedItem(i); - if(ref == -1) continue; - Format(info, sizeof(info), "%d", ref); - GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - index = FindCharInString(buffer, '/', true); - if(index != -1) - menu.AddItem(info, buffer[index + 1]); - } - - menu.ExitBackButton = true; - menu.ExitButton = true; - // Add +2 to the index for the two "Delete ..." buttons - menu.DisplayAt(client, index, MENU_TIME_FOREVER); -} -void ShowCategoryList(int client, CategoryData category) { - LoadCategories(); - char info[4]; - // No category list provided, use the global one. - g_PropData[client].PushCategory(category); - Menu menu = new Menu(SpawnCategoryHandler); - char title[32]; - g_PropData[client].GetCategoryTitle(title, sizeof(title)); - menu.SetTitle(title); - CategoryData cat; - for(int i = 0; i < category.items.Length; i++) { - category.items.GetArray(i, cat); - Format(info, sizeof(info), "%d", i); - if(cat.hasItems) - menu.AddItem(info, cat.name); - else { - Format(title, sizeof(title), "[%s]", cat.name); - menu.AddItem(info, title); - } - } - menu.ExitBackButton = true; - menu.ExitButton = true; - // Round to page instead of index (int division) - int index = g_PropData[client].lastCategoryIndex / 7 * 7; - menu.DisplayAt(client, index, MENU_TIME_FOREVER); -} -void _showItemMenu(int client, ArrayList items, const char[] title = "", bool clearArray = false, const char[] classnameOverride = "") { - if(items == null) { - // Use previous list buffer - items = g_PropData[client].itemBuffer; - if(items == null) { - LogError("Previous list does not exist and no new list was provided ShowItemMenu(%N)", client); - PrintToChat(client, "\x04[Editor]\x01 An error occurred (no list)"); - return; - } - } else { - // Populate the buffer with this list - g_PropData[client].SetItemBuffer(items, clearArray); - // Reset the index, so we start on the first item - g_PropData[client].lastItemIndex = 0; - strcopy(g_PropData[client].classnameOverride, 32, classnameOverride); - } - if(items.Length == 0) { - PrintToChat(client, "\x04[Editor]\x01 No items to show."); - return; - } - Menu itemMenu = new Menu(SpawnItemHandler); - if(title[0] != '\0') - itemMenu.SetTitle(title); - ItemData item; - char info[8+128+64]; //i[8] + item.model[128] + item.name[64] - for(int i = 0; i < items.Length; i++) { - items.GetArray(i, item); - // Sadly need to duplicate item.name, for recents to work - Format(info, sizeof(info), "%d|%s|%s", i, item.model, item.name); - itemMenu.AddItem(info, item.name); - } - itemMenu.ExitBackButton = true; - itemMenu.ExitButton = true; - // We don't want to start at the index but the page of the index - int index = (g_PropData[client].lastItemIndex / 7) * 7; - itemMenu.DisplayAt(client, index, MENU_TIME_FOREVER); -} -/** - * Show a list of a category's items to spawn to the client - * - * @param client client to show menu to - * @param category the category to show items of - */ -void ShowCategoryItemMenu(int client, CategoryData category) { - char title[32]; - g_PropData[client].GetCategoryTitle(title, sizeof(title)); - Format(title, sizeof(title), "%s>%s", title, category.name); - _showItemMenu(client, category.items, title, false, category.classnameOverride); -} -/** - * Show a list of items to spawn to the client - * - * @param client client to show menu to - * @param items A list of ItemData. Optional, null to reuse last list - * @param title An optional title to show - * @param clearArray Should the items array be destroyed when menu is closed? - * @param classnameOverride Override the classname to spawn as - */ -void ShowItemMenu(int client, ArrayList items = null, const char[] title = "", const char[] classnameOverride = "") { - _showItemMenu(client, items, title, false, classnameOverride); -} -/** - * Show a list of items, deleting the arraylist on completion - * @param client client to show menu to - * @param items A list of ItemData - * @param title An optional title to show - * @param classnameOverride Override the classname to spawn as - */ -void ShowTempItemMenu(int client, ArrayList items, const char[] title = "", const char[] classnameOverride = "") { - if(items == null) { - LogError("ShowTempItemMenu: Given null item list"); - } - _showItemMenu(client, items, title, true, classnameOverride); -} - -void Spawn_ShowFavorites(int client) { - if(g_db == null) { - PrintToChat(client, "\x04[Editor]\x01 Cannot connect to database."); - return; - } - PrintCenterText(client, "Loading favorites...\nPlease wait"); - char query[256]; - GetClientAuthId(client, AuthId_Steam2, query, sizeof(query)); - g_db.Format(query, sizeof(query), "SELECT model, name FROM editor_favorites WHERE steamid = '%s' ORDER BY position DESC", query); - g_db.Query(DB_GetFavoritesCallback, query, GetClientUserId(client)); -} - -void Spawn_ShowSaveLoadMainMenu(int client) { - Menu menu = new Menu(SaveLoadMainMenuHandler); - menu.SetTitle("Save / Load"); - // Id is SaveType - menu.AddItem("1", "Map Scenes"); - menu.AddItem("2", "Schematics"); - menu.ExitBackButton = true; - menu.ExitButton = true; - menu.Display(client, MENU_TIME_FOREVER); -} - -void Spawn_ShowManagerMainMenu(int client, int index = 0) { - if(g_spawnedItems.Length == 0) { - SendEditorMessage(client, "No spawned items to manage"); - DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client); - return; - } - Menu menu = new Menu(ManagerHandler); - menu.SetTitle("Manager"); - // Id is SaveType - char info[8]; - char buffer[128]; - for(int i = 0; i < g_spawnedItems.Length; i++) { - int ref = GetSpawnedItem(i); - if(ref == -1) continue; - IntToString(i, info, sizeof(info)); - GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - index = FindCharInString(buffer, '/', true); - if(index != -1) - menu.AddItem(info, buffer[index + 1]); - } - - menu.ExitBackButton = true; - menu.ExitButton = true; - menu.DisplayAt(client, index, MENU_TIME_FOREVER); -} -void ShowManagerEntityMenu(int client, int entity) { - if(!IsValidEntity(entity)) { - SendEditorMessage(client, "Item has vanished"); - Spawn_ShowManagerMainMenu(client); - return; - } - Menu menu = new Menu(ManagerEntityHandler); - menu.SetTitle("Manage %d", entity); - menu.AddItem("edit", "Edit"); - menu.AddItem("delete", "Delete"); - menu.AddItem("select", "Select"); - menu.AddItem("view", "View"); - menu.ExitBackButton = true; - menu.ExitButton = true; - menu.Display(client, MENU_TIME_FOREVER); -} -void ShowManagerSelectorMenu(int client) { - EntitySelector sel = EntitySelector.FromClient(client); - if(!sel.Active) { - sel.Start(GLOW_MANAGER); - sel.SetOnEnd(OnManagerSelectorEnd); - sel.SetOnPostSelect(OnManagerSelectorSelect); - sel.SetOnUnselect(OnManagerSelectorSelect); - } - Menu menu = new Menu(ManagerSelectorMainMenuHandler); - menu.SetTitle("Selector"); - menu.AddItem("list", "> List Entities"); - menu.AddItem("actions", "> Actions"); - menu.AddItem("add-self", "Add All Self-Spawned"); - menu.AddItem("add-all", "Add All Spawned"); - menu.ExitBackButton = false; - menu.ExitButton = true; - menu.Display(client, MENU_TIME_FOREVER); -} -void ShowManagerSelectorActionsMenu(int client) { - Menu menu = new Menu(ManagerSelectorActionHandler); - menu.SetTitle("Selector: Select action"); - char display[32]; - Format(display, sizeof(display), "Entities: %d", g_PropData[client].Selector.list.Length); - menu.AddItem("", display, ITEMDRAW_DISABLED); - - // menu.AddItem("edit", "Edit"); - menu.AddItem("delete", "Delete"); - // menu.AddItem("select", "Select"); - menu.AddItem("save", "Save"); - menu.ExitBackButton = true; - menu.ExitButton = true; - menu.Display(client, MENU_TIME_FOREVER); -} - -void ShowSaves(int client, SaveType type) { - ArrayList saves; - Menu newMenu; - if(type == Save_Scene) { - newMenu = new Menu(SaveLoadSceneHandler); - newMenu.SetTitle("Save & Load > Map Scenes"); - newMenu.AddItem("", "[Save New Scene]"); - saves = LoadScenes(); - } else if(type == Save_Schematic) { - newMenu = new Menu(SaveLoadSchematicHandler); - newMenu.SetTitle("Save & Load > Schematics"); - if(g_PropData[client].pendingSaveType == Save_Schematic) { - newMenu.AddItem("", "[Save Schematic]"); - } else { - newMenu.AddItem("", "[Start New Schematic]"); - // Don't load saves when in middle of creating schematic - saves = LoadSchematics(); - } - } - if(saves != null) { - char name[64]; - for(int i = 0; i < saves.Length; i++) { - saves.GetString(i, name, sizeof(name)); - newMenu.AddItem(name, name); - } - delete saves; - } - newMenu.ExitBackButton = true; - newMenu.ExitButton = true; - newMenu.Display(client, MENU_TIME_FOREVER); -} \ No newline at end of file diff --git a/scripting/include/hats/props/methods.sp b/scripting/include/hats/props/methods.sp deleted file mode 100644 index 227c750..0000000 --- a/scripting/include/hats/props/methods.sp +++ /dev/null @@ -1,513 +0,0 @@ - -ArrayList LoadScenes() { - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap); - FileType fileType; - DirectoryListing listing = OpenDirectory(path); - if(listing == null) return null; - char buffer[64]; - ArrayList saves = new ArrayList(ByteCountToCells(64)); - while(listing.GetNext(buffer, sizeof(buffer), fileType)) { - if(buffer[0] == '.') continue; - saves.PushString(buffer); - } - delete listing; - return saves; -} - -ArrayList LoadSchematics() { - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics"); - FileType fileType; - DirectoryListing listing = OpenDirectory(path); - if(listing == null) return null; - char buffer[64]; - ArrayList saves = new ArrayList(ByteCountToCells(64)); - while(listing.GetNext(buffer, sizeof(buffer), fileType) && fileType == FileType_File) { - if(buffer[0] == '.') continue; - saves.PushString(buffer); - } - delete listing; - return saves; -} - -bool LoadScene(const char[] save, bool asPreview = false) { - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s/%s", g_currentMap, save); - // ArrayList savedItems = new ArrayList(sizeof(SaveData)); - File file = OpenFile(path, "r"); - if(file == null) return false; - char buffer[256]; - if(asPreview) { - // Kill any previous preview - if(g_previewItems != null) ClearSavePreview(); - g_previewItems = new ArrayList(); - } - SaveData data; - while(file.ReadLine(buffer, sizeof(buffer))) { - if(buffer[0] == '#') continue; - data.Deserialize(buffer); - int entity = data.ToEntity(NULL_VECTOR, asPreview); - if(entity == -1) { - PrintToServer("[Editor] LoadScene(\"%s\", %b): failed to create %s", save, asPreview, buffer); - continue; - } - } - delete file; - return true; -} - -void ConfirmSave(int client, const char[] name) { - Menu newMenu = new Menu(SaveLoadConfirmHandler); - newMenu.AddItem(name, "Spawn"); - newMenu.AddItem("", "Cancel"); - newMenu.ExitBackButton = false; - newMenu.ExitButton = false; - newMenu.Display(client, 0); -} -void ClearSavePreview() { - if(g_previewItems != null) { - for(int i = 0; i < g_previewItems.Length; i++) { - int ref = g_previewItems.Get(i); - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - } - delete g_previewItems; - } - g_pendingSaveClient = 0; -} - -void AddSpawnedItem(int entity, int client = 0) { - if(client > 0 && g_PropData[client].pendingSaveType == Save_Schematic) { - g_PropData[client].schematic.AddEntity(entity, client); - } - // TODO: confirm if we want it to be in list, otherwise we need to clean manually - int userid = client > 0 ? GetClientUserId(client) : 0; - int index = g_spawnedItems.Push(EntIndexToEntRef(entity)); - g_spawnedItems.Set(index, userid, 1); -} - -bool CreateSceneSave(const char[] name) { - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap); - CreateDirectory(path, 509); - Format(path, sizeof(path), "%s/%s.txt", path, name); - File file = OpenFile(path, "w"); - if(file == null) { - PrintToServer("[Editor] Could not save: %s", path); - return false; - } - char buffer[132]; - SaveData data; - for(int i = 0; i < g_spawnedItems.Length; i++) { - int ref = g_spawnedItems.Get(i); - if(IsValidEntity(ref)) { - data.FromEntity(ref); - data.Serialize(buffer, sizeof(buffer)); - file.WriteLine("%s", buffer); - } - } - file.Flush(); - delete file; - return true; -} - -void UnloadSave() { - if(g_savedItems != null) { - delete g_savedItems; - } -} - -public void LoadCategories() { - if(ROOT_CATEGORY.items != null) return; - ROOT_CATEGORY.items = new ArrayList(sizeof(CategoryData)); - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/models"); - LoadFolder(ROOT_CATEGORY.items, path); - ROOT_CATEGORY.items.SortCustom(SortCategories); -} -int SortCategories(int index1, int index2, ArrayList array, Handle hndl) { - CategoryData cat1; - array.GetArray(index1, cat1); - CategoryData cat2; - array.GetArray(index2, cat2); - return strcmp(cat1.name, cat2.name); -} -public void UnloadCategories() { - if(ROOT_CATEGORY.items == null) return; - _UnloadCategories(ROOT_CATEGORY.items); - delete ROOT_CATEGORY.items; -} -void _UnloadCategories(ArrayList list) { - CategoryData cat; - for(int i = 0; i < list.Length; i++) { - list.GetArray(i, cat); - _UnloadCategory(cat); - } -} -void _UnloadCategory(CategoryData cat) { - // Is a sub-category: - if(!cat.hasItems) { - _UnloadCategories(cat.items); - } - delete cat.items; -} - -void LoadFolder(ArrayList parent, const char[] rootPath) { - char buffer[PLATFORM_MAX_PATH]; - FileType fileType; - DirectoryListing listing = OpenDirectory(rootPath); - if(listing == null) { - LogError("Cannot open \"%s\"", rootPath); - } - while(listing.GetNext(buffer, sizeof(buffer), fileType)) { - if(fileType == FileType_Directory) { - // TODO: support subcategory - if(buffer[0] == '.') continue; - CategoryData data; - Format(data.name, sizeof(data.name), "%s", buffer); - data.items = new ArrayList(sizeof(CategoryData)); - - Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer); - LoadFolder(data.items, buffer); - parent.PushArray(data); - } else if(fileType == FileType_File) { - Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer); - LoadProps(parent, buffer); - } - } - delete listing; -} - -void LoadProps(ArrayList parent, const char[] filePath) { - File file = OpenFile(filePath, "r"); - if(file == null) { - PrintToServer("[Props] Cannot open file \"%s\"", filePath); - return; - } - CategoryData category; - category.items = new ArrayList(sizeof(ItemData)); - category.hasItems = true; - char buffer[128]; - if(!file.ReadLine(buffer, sizeof(buffer))) { - delete file; - return; - } - ReplaceString(buffer, sizeof(buffer), "\n", ""); - ReplaceString(buffer, sizeof(buffer), "\r", ""); - Format(category.name, sizeof(category.name), "%s", buffer); - while(file.ReadLine(buffer, sizeof(buffer))) { - if(buffer[0] == '#') continue; - ReplaceString(buffer, sizeof(buffer), "\n", ""); - ReplaceString(buffer, sizeof(buffer), "\r", ""); - ItemData item; - int index = SplitString(buffer, ":", item.model, sizeof(item.model)); - if(index == -1) { - index = SplitString(buffer, " ", item.model, sizeof(item.model)); - if(index == -1) { - // No name provided, use the model's filename - index = FindCharInString(buffer, '/', true); - strcopy(item.name, sizeof(item.name), item.model[index + 1]); - } else { - strcopy(item.name, sizeof(item.name), buffer[index]); - } - category.items.PushArray(item); - } else if(StrEqual(item.model, "Classname")) { - strcopy(category.classnameOverride, sizeof(category.classnameOverride), buffer[index]); - } else if(StrEqual(item.model, "Type")) { - Format(category.classnameOverride, sizeof(category.classnameOverride), "_%s", buffer[index]); - } - } - parent.PushArray(category); - delete file; -} -bool recentsChanged = false; -bool SaveRecents() { - if(!recentsChanged) return true; // Nothing to do, nothing changed - if(g_recentItems == null) return false; - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/recents_cache.csv"); - File file = OpenFile(path, "w"); - if(file == null) { - PrintToServer("[Editor] Could not write to %s", path); - return false; - } - StringMapSnapshot snapshot = g_recentItems.Snapshot(); - char model[128]; - RecentEntry entry; - for(int i = 0; i < snapshot.Length; i++) { - snapshot.GetKey(i, model, sizeof(model)); - g_recentItems.GetArray(model, entry, sizeof(entry)); - file.WriteLine("%s,%s,%d", model, entry.name, entry.count); - } - file.Flush(); - delete file; - delete snapshot; - recentsChanged = false; - return true; -} -bool LoadRecents() { - if(g_recentItems != null) delete g_recentItems; - char path[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/recents_cache.csv"); - File file = OpenFile(path, "r"); - if(file == null) return false; - g_recentItems = new StringMap(); - char buffer[128+64+16]; - char model[128]; - RecentEntry entry; - while(file.ReadLine(buffer, sizeof(buffer))) { - int index = SplitString(buffer, ",", model, sizeof(model)); - index += SplitString(buffer[index], ",", entry.name, sizeof(entry.name)); - entry.count = StringToInt(buffer[index]); - g_recentItems.SetArray(model, entry, sizeof(entry)); - } - delete file; - return true; -} - -// Returns an ArrayList of all the recents -ArrayList GetRecentsItemList() { - ArrayList items = new ArrayList(sizeof(ItemData)); - StringMapSnapshot snapshot = g_recentItems.Snapshot(); - char model[128]; - RecentEntry entry; - ItemData item; - for(int i = 0; i < snapshot.Length; i++) { - snapshot.GetKey(i, model, sizeof(model)); - g_recentItems.GetArray(model, entry, sizeof(entry)); - strcopy(item.model, sizeof(item.model), model); - strcopy(item.name, sizeof(item.name), entry.name); - } - // This is pretty expensive in terms of allocations but shrug - items.SortCustom(SortRecents); - delete snapshot; - return items; -} - -int SortRecents(int index1, int index2, ArrayList array, Handle handle) { - ItemData data1; - array.GetArray(index1, data1); - ItemData data2; - array.GetArray(index2, data2); - - int count1, count2; - RecentEntry entry; - if(g_recentItems.GetArray(data1.model, entry, sizeof(entry))) return 0; //skip if somehow no entry - count1 = entry.count; - if(g_recentItems.GetArray(data2.model, entry, sizeof(entry))) return 0; //skip if somehow no entry - count2 = entry.count; - return count2 - count1; // desc -} - -void AddRecent(const char[] model, const char[] name) { - if(g_recentItems == null) { - if(!LoadRecents()) return; - } - RecentEntry entry; - if(!g_recentItems.GetArray(model, entry, sizeof(entry))) { - entry.count = 0; - strcopy(entry.name, sizeof(entry.name), name); - } - entry.count++; - recentsChanged = true; - g_recentItems.SetArray(model, entry, sizeof(entry)); -} -public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { - if(g_PropData[client].chatPrompt == Prompt_None) { - return Plugin_Continue; - } - switch(g_PropData[client].chatPrompt) { - case Prompt_Search: DoSearch(client, sArgs); - case Prompt_SaveScene: { - if(CreateSceneSave(sArgs)) { - PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, sArgs); - } else { - PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry."); - } - } - case Prompt_SaveSchematic: { - g_PropData[client].StartSchematic(client, sArgs); - } - default: - PrintToChat(client, "\x04[Editor]\x01 Not implemented."); - } - g_PropData[client].chatPrompt = Prompt_None; - return Plugin_Handled; -} -void DoSearch(int client, const char[] query) { - ArrayList results = SearchItems(query); - if(results.Length == 0) { - CPrintToChat(client, "\x04[Editor]\x01 No results found. :("); - } else { - char title[64]; - Format(title, sizeof(title), "Results for \"%s\"", query); - ShowTempItemMenu(client, results, title); - } -} -// Gets the index of the spawned item, starting at index. negative to go from back -int GetSpawnedIndex(int client, int index) { - int userid = GetClientUserId(client); - if(index >= 0) { - for(int i = index; i < g_spawnedItems.Length; i++) { - int spawnedBy = g_spawnedItems.Get(i, 1); - if(spawnedBy == userid) { - return i; - } - } - } else { - for(int i = g_spawnedItems.Length + index; i >= 0; i--) { - int spawnedBy = g_spawnedItems.Get(i, 1); - if(spawnedBy == userid) { - return i; - } - } - } - return -1; -} -#define MAX_SEARCH_RESULTS 10 -ArrayList SearchItems(const char[] query) { - // We have to put it into SearchData enum struct, then convert it back to ItemResult - LoadCategories(); - ArrayList results = new ArrayList(sizeof(SearchData)); - _searchCategory(results, ROOT_CATEGORY.items, query); - results.SortCustom(SortSearch); - ArrayList items = new ArrayList(sizeof(ItemData)); - ItemData item; - SearchData data; - for(int i = 0; i < results.Length; i++) { - results.GetArray(i, data); - item.FromSearchData(data); - items.PushArray(item); - } - delete results; - return items; -} - -int SortSearch(int index1, int index2, ArrayList array, Handle handle) { - SearchData data1; - array.GetArray(index1, data1); - SearchData data2; - array.GetArray(index2, data2); - return data1.index - data2.index; -} - -void _searchCategory(ArrayList results, ArrayList categories, const char[] query) { - CategoryData cat; - if(categories == null) return; - for(int i = 0; i < categories.Length; i++) { - categories.GetArray(i, cat); - if(cat.hasItems) { - //cat.items is of CatetoryData - if(!_searchItems(results, cat.items, query)) return; - } else { - //cat.items is of ItemData - _searchCategory(results, cat.items, query); - } - } -} -bool _searchItems(ArrayList results, ArrayList items, const char[] query) { - ItemData item; - SearchData search; - if(items == null) return false; - for(int i = 0; i < items.Length; i++) { - items.GetArray(i, item); - int searchIndex = StrContains(item.name, query, false); - if(searchIndex > -1) { - search.FromItemData(item); - search.index = searchIndex; - results.PushArray(search); - if(results.Length > MAX_SEARCH_RESULTS) return false; - } - } - return true; -} - -int GetSpawnedItem(int index) { - if(index < 0 || index >= g_spawnedItems.Length) return -1; - int ref = g_spawnedItems.Get(index); - if(!IsValidEntity(ref)) { - g_spawnedItems.Erase(index); - return -1; - } - return ref; -} - -bool RemoveSpawnedProp(int ref) { - // int ref = EntIndexToEntRef(entity); - int index = g_spawnedItems.FindValue(ref); - if(index > -1) { - g_spawnedItems.Erase(index); - return true; - } - return false; -} - -void OnDeleteToolEnd(int client, ArrayList entities) { - int count; - for(int i = 0; i < entities.Length; i++) { - int ref = entities.Get(i); - if(IsValidEntity(ref)) { - count++; - RemoveSpawnedProp(ref); - RemoveEntity(ref); - } - } - delete entities; - PrintToChat(client, "\x04[Editor]\x01 \x05%d\x01 entities deleted", count); -} - -void OnManagerSelectorEnd(int client, ArrayList entities) { - // TODO: implement manager selector cb - ReplyToCommand(client, "Not Implemented"); - delete entities; -} -void OnManagerSelectorSelect(int client, int entity) { - // update entity count - // ShowManagerSelectorMenu(client); -} - -int DeleteAll(int onlyPlayer = 0) { - int userid = onlyPlayer > 0 ? GetClientUserId(onlyPlayer) : 0; - int count; - for(int i = 0; i < g_spawnedItems.Length; i++) { - int ref = g_spawnedItems.Get(i); - int spawnedBy = g_spawnedItems.Get(i, 1); - // Skip if wishing to only delete certain items: - if(onlyPlayer == 0 || spawnedBy == userid) { - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - // TODO: erasing while removing - g_spawnedItems.Erase(i); - i--; // go back up one - count++; - } - } - return count; -} - -#define SHOW_HINT_MIN_DURATION 600 // 600 s (10min) -void ShowHint(int client) { - int time = GetTime(); - int lastActive = g_PropData[client].lastActiveTime; - g_PropData[client].lastActiveTime = time; - if(time - lastActive < SHOW_HINT_MIN_DURATION) return; - PrintToChat(client, "\x05ZOOM: \x01Change Mode"); - PrintToChat(client, "\x05USE: \x01Place \x05Shift + USE: \x01Cancel \x05Ctrl + USE: \x01Change Type"); - PrintToChat(client, "\x05R: \x01Rotate (hold, use mouse) \x05Left Click: \x01Change Axis \x05Right Click: \x01Snap Angle"); - PrintToChat(client, "Type \x05/prop favorite\x01 to (un)favorite."); - PrintToChat(client, "More information & cheatsheat: \x05%s", "https://admin.jackz.me/docs/props"); -} - -void ToggleFavorite(int client, const char[] model, const char[] name = "") { - char query[256]; - GetClientAuthId(client, AuthId_Steam2, query, sizeof(query)); - DataPack pack; - pack.WriteCell(GetClientUserId(client)); - pack.WriteString(model); - pack.WriteString(name); - g_db.Format(query, sizeof(query), "SELECT name FROM editor_favorites WHERE steamid = '%s' AND model = '%s'", query, model); - g_db.Query(DB_ToggleFavoriteCallback, query, pack); -} \ No newline at end of file diff --git a/scripting/include/hats_editor.inc b/scripting/include/hats_editor.inc deleted file mode 100644 index c2f7330..0000000 --- a/scripting/include/hats_editor.inc +++ /dev/null @@ -1,155 +0,0 @@ -#if defined _editor_included_ - #endinput -#endif -#define _editor_included_ - -public SharedPlugin __pl_editor_ = { - name = "editor", - file = "hats.smx", -#if defined REQUIRE_PLUGIN - required = 1, -#else - required = 0, -#endif -}; - -#if !defined REQUIRE_PLUGIN -public void __pl_editor__SetNTVOptional() -{ - MarkNativeAsOptional("SpawnSchematic"); - MarkNativeAsOptional("StartEdit"); - MarkNativeAsOptional("StartSpawner"); - MarkNativeAsOptional("CancelEdit"); - MarkNativeAsOptional("IsEditorActive"); - - MarkNativeAsOptional("StartSelector"); - MarkNativeAsOptional("CancelSelector"); - MarkNativeAsOptional("IsSelectorActive"); - - MarkNativeAsOptional("Selector.Count.get"); - MarkNativeAsOptional("Selector.Active.get"); - MarkNativeAsOptional("Selector.Start"); - MarkNativeAsOptional("Selector.SetOnEnd"); - MarkNativeAsOptional("Selector.SetOnPreSelect"); - MarkNativeAsOptional("Selector.SetOnPostSelect"); - MarkNativeAsOptional("Selector.SetOnUnselect"); - MarkNativeAsOptional("Selector.AddEntity"); - MarkNativeAsOptional("Selector.RemoveEntity"); - MarkNativeAsOptional("Selector.Cancel"); - MarkNativeAsOptional("Selector.End"); -} -#endif - - -native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR); - -/** Called when edit is done or cancelled - * @param client - client doing the edit - * @param entity - The entity edited - * @param result - Result of the edit, or cancelled - * @return boolean - only for StartSpawner, true to continue, false to end spawning - */ -typeset EditorDoneCallback { - function void (int client, int entity, CompleteType result); - function bool (int client, int entity, CompleteType result); -} - -/** Called when an item is to be selected. - * @return boolean - TRUE to allow item to be selected, FALSE to reject - */ -typedef SelectPreAddCallback = function bool (int client, int entity); -/** Called when an item has been selected */ -typedef SelectPostAddCallback = function void (int client, int entity); - -/** Called when an item is to be unselected. */ -typedef SelectRemoveCallback = function void (int client, int entity); -/** Called when a user is done selecting items - * @param client - client doing the selection - * @param entities - if null, selection was cancelled. if not null, contains list of entity references, must be deleted. - */ -typedef SelectDoneCallback = function void (int client, ArrayList entities); - -/** Starts editing an entity - * @param client - The client that is editing - * @param entity - The entity to edit - * @param doneCallback - Called when edit is done - */ -native void StartEdit(int client, int entity, EditorDoneCallback doneCallback); -/** Let client pick prop(s) to spawn - * @param client - The client that is editing - * @param entity - The entity to edit - * @param doneCallback - Called when edit is done - */ -native void StartSpawner(int client, EditorDoneCallback doneCallback); -native void CancelEdit(int client); -// Includes non-plugin started edits -native bool IsEditorActive(int client); - -/** Starts a selection, where the client can click on entities to select or deselect them. - * @param client - the client that can select - * @param callback - called when user is done seleting or cancelled - * @param highlightColor - the color to highlight selected items, default solid green - * @param maxEntities - the max number of selections, 0 for infinite - */ -native void StartSelector(int client, SelectDoneCallback callback, int highlightColor[3] = { 0, 255, 0 }, int maxEntities = 0); - -methodmap EntitySelector { - public EntitySelector(int client) { - return view_as(client); - } - - public static EntitySelector FromClient(int client) { - return view_as(client); - } - - /** Starts a new selector for client - * @param highlightColor - the color to highlight selected items, default solid green - * @param flags - not used. - * @param maxEntities - the max number of selections, 0 for infinite - */ - public native EntitySelector Start(int highlightColor[3], int flags = 0, int maxEntities = 0); - - - property int Count { - /** Returns the number of entities in selector. Returns -1 if not active */ - public native get(); - } - - property bool Active { - public native get(); - } - - /** Sets the callback for when the selector is ended (or cancelled) */ - public native void SetOnEnd(SelectDoneCallback callback); - - /** Sets the callback for when an item is to be added to the selector. */ - public native void SetOnPreSelect(SelectPreAddCallback callback); - - /** Sets the callback for when an item has been added to the selector. */ - public native void SetOnPostSelect(SelectPostAddCallback callback); - - /** Sets the callback for when an item is removed from selector. */ - public native void SetOnUnselect(SelectRemoveCallback callback); - - /** Adds an entity to selection. Does not call SelectAddCallback */ - public native void AddEntity(int entity); - - /** Removes an entity from selection. Does not call SelectAddCallback */ - public native void RemoveEntity(int entity); - - public native void Cancel(); - - public native void End(); -} - - -native void CancelSelector(int client); -native bool IsSelectorActive(int client); - -enum CompleteType { - Complete_WallSuccess, - Complete_WallError, - Complete_PropSpawned, - Complete_PropError, - Complete_EditSuccess -} diff --git a/scripting/l4d2_editor.sp b/scripting/l4d2_editor.sp new file mode 100644 index 0000000..cfc204e --- /dev/null +++ b/scripting/l4d2_editor.sp @@ -0,0 +1,591 @@ +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0" + +#define DUMMY_MODEL "models/props/cs_office/vending_machine.mdl" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int g_iLaserIndex; + +float cmdThrottle[MAXPLAYERS+1]; + +TopMenu g_topMenu; + +char g_currentMap[64]; + +//int g_markedMode + +#include +#include +#include +#include + +public Plugin myinfo = { + name = "L4D2 Hats & Editor", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + RegPluginLibrary("editor"); + // CreateNative("SpawnSchematic", Native_SpawnSchematic); + CreateNative("StartEdit", Native_StartEdit); + CreateNative("StartSpawner", Native_StartSpawner); + CreateNative("CancelEdit", Native_CancelEdit); + CreateNative("IsEditorActive", Native_IsEditorActive); + + + CreateNative("StartSelector", Native_StartSelector); + CreateNative("CancelSelector", Native_CancelSelector); + CreateNative("IsSelectorActive", Native_IsSelectorActive); + + CreateNative("EntitySelector.Start", Native_Selector_Start); + CreateNative("EntitySelector.Count.get", Native_Selector_GetCount); + CreateNative("EntitySelector.Active.get", Native_Selector_GetActive); + CreateNative("EntitySelector.SetOnEnd", Native_Selector_SetOnEnd); + CreateNative("EntitySelector.SetOnPreSelect", Native_Selector_SetOnPreSelect); + CreateNative("EntitySelector.SetOnPostSelect", Native_Selector_SetOnPostSelect); + CreateNative("EntitySelector.SetOnUnselect", Native_Selector_SetOnUnselect); + CreateNative("EntitySelector.AddEntity", Native_Selector_AddEntity); + CreateNative("EntitySelector.RemoveEntity", Native_Selector_RemoveEntity); + CreateNative("EntitySelector.Cancel", Native_Selector_Cancel); + CreateNative("EntitySelector.End", Native_Selector_End); + return APLRes_Success; +} + + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D/L4D2 only."); + } + + createdWalls = new ArrayList(); + g_spawnedItems = new ArrayList(2); + ROOT_CATEGORY.name = "Categories"; + + LoadTranslations("common.phrases"); + HookEvent("player_spawn", Event_PlayerSpawn); + + RegAdminCmd("sm_mkwall", Command_MakeWall, ADMFLAG_CUSTOM2); + RegAdminCmd("sm_edit", Command_Editor, ADMFLAG_CUSTOM2); + RegAdminCmd("sm_wall", Command_Editor, ADMFLAG_CUSTOM2); + RegAdminCmd("sm_prop", Command_Props, ADMFLAG_CUSTOM2); + + if(SQL_CheckConfig(DATABASE_CONFIG_NAME)) { + if(!ConnectDB()) { + LogError("Failed to connect to database."); + } + } + + int entity = -1; + char targetName[32]; + while((entity = FindEntityByClassname(entity, "func_brush")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName)); + if(StrContains(targetName, "l4d2_hats_") == 0) { + createdWalls.Push(EntIndexToEntRef(entity)); + SDKHook(entity, SDKHook_Use, OnWallClicked); + } + + } + + for(int i = 1; i <= MaxClients; i++) { + Editor[i].client = i; + Editor[i].Reset(true); + g_PropData[i].Init(i); + } + + TopMenu topMenu; + if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) { + OnAdminMenuReady(topMenu); + } +} + +public void OnLibraryRemoved(const char[] name) { + if (StrEqual(name, "adminmenu", false)) { + g_topMenu = null; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +// Tries to find a valid location at user's cursor, avoiding placing into solid walls (such as invisible walls) or objects +stock bool GetSmartCursorLocation(int client, float outPos[3]) { + float start[3], angle[3], ceilPos[3], wallPos[3], normal[3]; + // Get the cursor location + GetClientEyePosition(client, start); + GetClientEyeAngles(client, angle); + TR_TraceRayFilter(start, angle, MASK_SOLID, RayType_Infinite, Filter_NoPlayers, client); + if(TR_DidHit()) { + TR_GetEndPosition(outPos); + // Check if the position is a wall + TR_GetPlaneNormal(null, normal); + if(normal[2] < 0.1) { + + // Find a suitable position above + start[0] = outPos[0]; + start[1] = outPos[1]; + start[2] = outPos[2] += 100.0; + TR_TraceRayFilter(outPos, start, MASK_SOLID, RayType_EndPoint, TraceEntityFilterPlayer, client); + bool ceilCollided = TR_DidHit(); + bool ceilOK = !TR_AllSolid(); + TR_GetEndPosition(ceilPos); + // float distCeil = GetVectorDistance(outPos, ceilPos, true); + + // Find a suitable position backwards + angle[0] = 70.0; + angle[1] += 180.0; + TR_TraceRayFilter(outPos, angle, MASK_SOLID, RayType_Infinite, TraceEntityFilterPlayer, client); + bool wallCollided = TR_DidHit(); + TR_GetEndPosition(wallPos); + float distWall = GetVectorDistance(outPos, wallPos, true); + + if(ceilCollided && wallCollided) + + if(wallCollided && distWall < 62500) { + outPos = wallPos; + } else if(ceilOK) { + outPos = ceilPos; + } + } + + return true; + } else { + return false; + } +} + + +bool g_inRotate[MAXPLAYERS+1]; +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]) { + float tick = GetGameTime(); + int oldButtons = GetEntProp(client, Prop_Data, "m_nOldButtons"); + if(g_pendingSaveClient == client) { + if(g_PropData[client].pendingSaveType == Save_Schematic) { + // move cursor? or should be editor anyway + } + } else if(g_PropData[client].Selector.IsActive()) { + SetWeaponDelay(client, 0.5); + if(tick - cmdThrottle[client] >= 0.20) { + if(buttons & IN_ATTACK) { + int entity = GetLookingEntity(client, Filter_ValidHats); + if(entity > 0) { + if(g_PropData[client].Selector.AddEntity(entity) != -1) { + PrecacheSound("ui/beep07.wav"); + EmitSoundToClient(client, "ui/beep07.wav", entity, SND_CHANGEVOL, .volume = 0.5); + } + } else { + PrintHintText(client, "No entity found"); + } + } else if(buttons & IN_ATTACK2) { + int entity = GetLookingEntity(client, Filter_ValidHats); + if(entity > 0) { + if(g_PropData[client].Selector.RemoveEntity(entity)) { + PrecacheSound("ui/beep22.wav"); + EmitSoundToClient(client, "ui/beep22.wav", entity, SND_CHANGEVOL, .volume = 0.5); + } + } + } else if(buttons & IN_USE) { + if(buttons & IN_SPEED) { + //Delete + g_PropData[client].Selector.End(); + } else if(buttons & IN_DUCK) { + //Cancel + g_PropData[client].Selector.Cancel(); + } + } + cmdThrottle[client] = tick; + } + } else if(Editor[client].IsActive()) { + // if(buttons & IN_USE && buttons & IN_RELOAD) { + // ClientCommand(client, "sm_wall done"); + // return Plugin_Handled; + // } + bool allowMove = true; + switch(Editor[client].mode) { + case MOVE_ORIGIN: { + SetWeaponDelay(client, 0.5); + + bool isRotate; + int flags = GetEntityFlags(client); + if(buttons & IN_RELOAD) { + if(!g_inRotate[client]) { + g_inRotate[client] = true; + } + if(!(oldButtons & IN_JUMP) && (buttons & IN_JUMP)) { + buttons &= ~IN_JUMP; + Editor[client].CycleStacker(); + } else if(!(oldButtons & IN_SPEED) && (buttons & IN_SPEED)) { + Editor[client].ToggleCollision(); + return Plugin_Handled; + } else if(!(oldButtons & IN_DUCK) && (buttons & IN_DUCK)) { + Editor[client].ToggleCollisionRotate(); + return Plugin_Handled; + } else { + PrintCenterText(client, "%.1f %.1f %.1f", Editor[client].angles[0], Editor[client].angles[1], Editor[client].angles[2]); + isRotate = true; + SetEntityFlags(client, flags |= FL_FROZEN); + if(!(oldButtons & IN_ATTACK) && (buttons & IN_ATTACK)) Editor[client].CycleAxis(); + else if(!(oldButtons & IN_ATTACK2) && (buttons & IN_ATTACK2)) Editor[client].CycleSnapAngle(tick); + + // Rotation control: + // Turn off rotate when player wants rotate + Editor[client].hasCollisionRotate = false; + if(tick - cmdThrottle[client] > 0.1) { + if(Editor[client].axis == 0) { + int mouseXAbs = IntAbs(mouse[0]); + int mouseYAbs = IntAbs(mouse[1]); + bool XOverY = mouseXAbs > mouseYAbs; + if(mouseYAbs > 10 && !XOverY) { + Editor[client].IncrementAxis(0, mouse[1]); + } else if(mouseXAbs > 10 && XOverY) { + Editor[client].IncrementAxis(1, mouse[0]); + } + } + else if(Editor[client].axis == 1) { + if(mouse[0] > 10) Editor[client].angles[2] += Editor[client].snapAngle; + else if(mouse[0] < -10) Editor[client].angles[2] -= Editor[client].snapAngle; + } + cmdThrottle[client] = tick; + } + } + } else { + if(g_inRotate[client]) { + g_inRotate[client] = false; + } + // Move position + float moveAmount = (buttons & IN_SPEED) ? 2.0 : 1.0; + if(buttons & IN_ATTACK) Editor[client].moveDistance += moveAmount; + else if(buttons & IN_ATTACK2) Editor[client].moveDistance -= moveAmount; + } + + // Clear IN_FROZEN when no longer rotate + if(!isRotate && flags & FL_FROZEN) { + flags = flags & ~FL_FROZEN; + SetEntityFlags(client, flags); + } + if(Editor[client].stackerDirection == Stack_Off) + CalculateEditorPosition(client, Filter_IgnorePlayerAndWall); + } + case SCALE: { + SetWeaponDelay(client, 0.5); + allowMove = false; + if(buttons & IN_USE) { + Editor[client].CycleSpeed(tick); + } else { + if(buttons & IN_MOVELEFT) { + Editor[client].IncrementSize(0, -1.0); + } else if(buttons & IN_MOVERIGHT) { + Editor[client].IncrementSize(0, 1.0); + Editor[client].size[0] += Editor[client].moveSpeed; + } + if(buttons & IN_FORWARD) { + Editor[client].IncrementSize(0, 1.0); + } else if(buttons & IN_BACK) { + Editor[client].IncrementSize(0, -1.0); + } + if(buttons & IN_JUMP) { + Editor[client].IncrementSize(0, 1.0); + } else if(buttons & IN_DUCK) { + Editor[client].IncrementSize(0, -1.0); + } + } + } + case COLOR: { + SetWeaponDelay(client, 0.5); + PrintHintText(client, "%d %d %d %d", Editor[client].color[0], Editor[client].color[1], Editor[client].color[2], Editor[client].color[3]); + if(buttons & IN_USE) { + Editor[client].CycleColorComponent(tick); + } else if(buttons & IN_ATTACK2) { + Editor[client].IncreaseColor(1); + allowMove = false; + } else if(buttons & IN_ATTACK) { + Editor[client].IncreaseColor(-1); + allowMove = false; + } + } + } + if(buttons & IN_DUCK) { + + } + if(Editor[client].mode != COLOR && !(oldButtons & IN_USE) && buttons & IN_USE) { + if(buttons & IN_SPEED) { + Editor[client].Cancel(); + } else if(buttons & IN_DUCK) { + Editor[client].CycleBuildType(); + // Editor[client].ShowExtraOptions(); + } else { + int entity; + Editor[client].Done(entity); + } + + } else if(!(oldButtons & IN_ZOOM) && buttons & IN_ZOOM) { + Editor[client].CycleMode(); // ZOOM: Cycle forward + } + + Editor[client].Draw(BUILDER_COLOR, 0.1, 0.1); + return allowMove ? Plugin_Continue : Plugin_Handled; + } + + return Plugin_Continue; +} + +int IntAbs(int a) { + if(a < 0) { + return a * -1; + } + return a; +} + +void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0) { + SDKHook(client, SDKHook_WeaponCanUse, OnWeaponUse); + } +} + +Action OnWeaponUse(int client, int weapon) { + int ref = EntIndexToEntRef(weapon); + // Prevent picking up weapons that are previews + for(int i = 1; i <= MaxClients; i++) { + if(Editor[i].entity == ref && Editor[i].flags & Edit_Preview) { + return Plugin_Handled; + } + } + return Plugin_Continue; +} + +public void OnClientDisconnect(int client) { + Editor[client].Reset(); + g_PropData[client].Reset(); + if(g_pendingSaveClient == client) { + g_pendingSaveClient = 0; + ClearSavePreview(); + } +} + +public void OnMapStart() { + PrecacheModel(DUMMY_MODEL); + g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt"); + for(int i = 1; i <= MaxClients; i++) { + cmdThrottle[i] = 0.0; + } + GetCurrentMap(g_currentMap, sizeof(g_currentMap)); +} + + +public void OnMapEnd() { + g_spawnedItems.Clear(); + for(int i = 1; i <= createdWalls.Length; i++) { + DeleteWall(i); + } + createdWalls.Clear(); + UnloadCategories(); + UnloadSave(); + SaveRecents(); +} +public void OnPluginEnd() { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + int flags = GetEntityFlags(i) & ~FL_FROZEN; + SetEntityFlags(i, flags); + } + } + if(g_spawnedItems != null) { + delete g_spawnedItems; + } + TriggerInput("prop_preview", "Kill"); +} + +public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { + return entity != data; +} + +int GetLookingEntity(int client, TraceEntityFilter filter) { + static float pos[3], ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); + if(TR_DidHit()) { + return TR_GetEntityIndex(); + } + return -1; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +stock bool Filter_OnlyPlayers(int entity, int mask, int data) { + return entity > 0 && entity <= MaxClients && entity != data; +} + +stock bool Filter_NoPlayers(int entity, int mask, int data) { + return entity > MaxClients && entity != data; +} + +stock bool Filter_IgnorePlayerAndWall(int entity, int mask, int data) { + if(entity > MaxClients && entity != data && EntRefToEntIndex(Editor[data].entity) != entity) { + static char classname[16]; + GetEntityClassname(entity, classname, sizeof(classname)); + // Ignore infected + return !StrEqual(classname, "infected"); + } + return false; +} + + +bool Filter_ValidHats(int entity, int mask, int data) { + if(entity == data) return false; + if(entity <= MaxClients && entity > 0) { + return true; + } + return CheckBlacklist(entity); +} + + +#define MAX_FORBIDDEN_CLASSNAMES 10 +static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { + // "env_physics_blocker", + // "env_player_blocker", + "func_brush", + "func_simpleladder", + "func_button", + "func_elevator", + "func_button_timed", + "func_movelinear", + "func_tracktrain", + // "infected", + "func_lod", + "prop_ragdoll", + "move_rope" +}; + +#define MAX_FORBIDDEN_MODELS 2 +char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = { + "models/props_vehicles/c130.mdl", + "models/props_vehicles/helicopter_rescue.mdl" +}; + +bool CheckBlacklist(int entity) { + if(entity == 0) return false; + static char buffer[64]; + GetEntityClassname(entity, buffer, sizeof(buffer)); + for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { + if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) { + return false; + } + } + if(StrContains(buffer, "prop_") > -1) { + GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { + if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { + return false; + } + } + } + GetEntPropString(entity, Prop_Data, "m_iName", buffer, sizeof(buffer)); + if(StrEqual(buffer, "l4d2_randomizer")) { + return false; + } + return true; +} + +//////////////////////////////// + +stock void TriggerInput(const char[] targetName, const char[] input) { + int entity = -1; + char _targetName[32]; + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", _targetName, sizeof(_targetName)); + if(StrEqual(_targetName, targetName)) { + AcceptEntityInput(entity, input); + } + } +} + + +stock bool FindGround(const float start[3], float end[3]) { + float angle[3]; + angle[0] = 90.0; + + Handle trace = TR_TraceRayEx(start, angle, MASK_SHOT, RayType_Infinite); + if(!TR_DidHit(trace)) { + delete trace; + return false; + } + TR_GetEndPosition(end, trace); + delete trace; + return true; +} + +stock bool L4D_IsPlayerCapped(int client) { + if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) + return true; + return false; +} +stock void LookAtPoint(int entity, const float destination[3]){ + float angles[3], pos[3], result[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); + MakeVectorFromPoints(destination, pos, result); + GetVectorAngles(result, angles); + if(angles[0] >= 270){ + angles[0] -= 270; + angles[0] = (90-angles[0]); + } else { + if(angles[0] <= 90){ + angles[0] *= -1; + } + } + angles[1] -= 180; + TeleportEntity(entity, NULL_VECTOR, angles, NULL_VECTOR); +} + +stock float SnapTo(const float value, const float degree) { + return float(RoundFloat(value / degree)) * degree; +} + +stock bool CalculateEditorPosition(int client, TraceEntityFilter filter) { + if (client > 0 && client <= MaxClients && IsClientInGame(client)) { + float clientEye[3], clientAngle[3], direction[3]; + GetClientEyePosition(client, clientEye); + GetClientEyeAngles(client, clientAngle); + + GetAngleVectors(clientAngle, direction, NULL_VECTOR, NULL_VECTOR); + ScaleVector(direction, Editor[client].moveDistance); + AddVectors(clientEye, direction, Editor[client].origin); + + if(Editor[client].hasCollision) { + TR_TraceRayFilter(clientEye, Editor[client].origin, MASK_OPAQUE, RayType_EndPoint, filter, client); + if (TR_DidHit(INVALID_HANDLE)) { + TR_GetEndPosition(Editor[client].origin); + GetEntPropVector(Editor[client].entity, Prop_Send, "m_vecMins", direction); + Editor[client].origin[2] -= direction[2]; + if(Editor[client].hasCollisionRotate) { + TR_GetPlaneNormal(INVALID_HANDLE, Editor[client].angles); + GetVectorAngles(Editor[client].angles, Editor[client].angles); + Editor[client].angles[0] += 90.0; //need to rotate for some reason + } + } + } + + return true; + } + return false; +} \ No newline at end of file diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index c10dbaf..dbaca63 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -1,1073 +1,754 @@ -#pragma semicolon 1 -#pragma newdecls required - -#define PLUGIN_VERSION "1.0" -#define PLAYER_HAT_REQUEST_COOLDOWN 10 -// #define DEBUG_GLOW 1 -static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; - -#define DUMMY_MODEL "models/props/cs_office/vending_machine.mdl" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -bool tempGod[MAXPLAYERS+1]; -bool inSaferoom[MAXPLAYERS+1]; - -int g_iLaserIndex; - -float cmdThrottle[MAXPLAYERS+1]; -static bool onLadder[MAXPLAYERS+1]; - -Cookie noHatVictimCookie; -Cookie hatPresetCookie; - -ConVar cvar_sm_hats_enabled; -ConVar cvar_sm_hats_flags; -ConVar cvar_sm_hats_rainbow_speed; -ConVar cvar_sm_hats_blacklist_enabled; -ConVar cvar_sm_hats_max_distance; - -TopMenu g_topMenu; - -char g_currentMap[64]; - -//int g_markedMode - -#include -#include -#include -#include -#include -#include - -public Plugin myinfo = { - name = "L4D2 Hats & Editor", - author = "jackzmc", - description = "", - version = PLUGIN_VERSION, - url = "https://github.com/Jackzmc/sourcemod-plugins" -}; - -ArrayList NavAreas; -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { - RegPluginLibrary("editor"); - // CreateNative("SpawnSchematic", Native_SpawnSchematic); - CreateNative("StartEdit", Native_StartEdit); - CreateNative("StartSpawner", Native_StartSpawner); - CreateNative("CancelEdit", Native_CancelEdit); - CreateNative("IsEditorActive", Native_IsEditorActive); - - - CreateNative("StartSelector", Native_StartSelector); - CreateNative("CancelSelector", Native_CancelSelector); - CreateNative("IsSelectorActive", Native_IsSelectorActive); - - CreateNative("EntitySelector.Start", Native_Selector_Start); - CreateNative("EntitySelector.Count.get", Native_Selector_GetCount); - CreateNative("EntitySelector.Active.get", Native_Selector_GetActive); - CreateNative("EntitySelector.SetOnEnd", Native_Selector_SetOnEnd); - CreateNative("EntitySelector.SetOnPreSelect", Native_Selector_SetOnPreSelect); - CreateNative("EntitySelector.SetOnPostSelect", Native_Selector_SetOnPostSelect); - CreateNative("EntitySelector.SetOnUnselect", Native_Selector_SetOnUnselect); - CreateNative("EntitySelector.AddEntity", Native_Selector_AddEntity); - CreateNative("EntitySelector.RemoveEntity", Native_Selector_RemoveEntity); - CreateNative("EntitySelector.Cancel", Native_Selector_Cancel); - CreateNative("EntitySelector.End", Native_Selector_End); - return APLRes_Success; -} - - -public void OnPluginStart() { - EngineVersion g_Game = GetEngineVersion(); - if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { - SetFailState("This plugin is for L4D/L4D2 only."); - } - - createdWalls = new ArrayList(); - g_spawnedItems = new ArrayList(2); - ROOT_CATEGORY.name = "Categories"; - - LoadTranslations("common.phrases"); - HookEvent("player_entered_checkpoint", OnEnterSaferoom); - HookEvent("player_left_checkpoint", OnLeaveSaferoom); - HookEvent("player_bot_replace", Event_PlayerToIdle); - HookEvent("bot_player_replace", Event_PlayerOutOfIdle); - HookEvent("player_spawn", Event_PlayerSpawn); - - RegConsoleCmd("sm_hat", Command_DoAHat, "Hats"); - RegAdminCmd("sm_hatf", Command_DoAHat, ADMFLAG_ROOT, "Hats"); - RegAdminCmd("sm_mkwall", Command_MakeWall, ADMFLAG_CUSTOM2); - RegAdminCmd("sm_edit", Command_Editor, ADMFLAG_CUSTOM2); - RegAdminCmd("sm_wall", Command_Editor, ADMFLAG_CUSTOM2); - RegAdminCmd("sm_prop", Command_Props, ADMFLAG_CUSTOM2); - RegConsoleCmd("sm_hatp", Command_DoAHatPreset); - - cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); - cvar_sm_hats_enabled = CreateConVar("sm_hats_enabled", "1.0", "Enable hats.\n0=OFF, 1=Admins Only, 2=Any", FCVAR_NONE, true, 0.0, true, 2.0); - cvar_sm_hats_enabled.AddChangeHook(Event_HatsEnableChanged); - cvar_sm_hats_flags = CreateConVar("sm_hats_features", "153", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats\n128 = Delete Thrown Hats", FCVAR_CHEAT, true, 0.0); - cvar_sm_hats_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0); - cvar_sm_hats_max_distance = CreateConVar("sm_hats_distance", "240", "The max distance away you can hat something. 0 = disable", FCVAR_NONE, true, 0.0); - - if(SQL_CheckConfig(DATABASE_CONFIG_NAME)) { - if(!ConnectDB()) { - LogError("Failed to connect to database."); - } - } - - - noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public); - noHatVictimCookie.SetPrefabMenu(CookieMenu_OnOff_Int, "Disable player hats for self", OnLocalPlayerHatCookieSelect); - - hatPresetCookie = new Cookie("hats_preset", "Sets the preset hat you spawn with", CookieAccess_Public); - - int entity = -1; - char targetName[32]; - while((entity = FindEntityByClassname(entity, "func_brush")) != INVALID_ENT_REFERENCE) { - GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName)); - if(StrContains(targetName, "l4d2_hats_") == 0) { - createdWalls.Push(EntIndexToEntRef(entity)); - SDKHook(entity, SDKHook_Use, OnWallClicked); - } - - } - - for(int i = 1; i <= MaxClients; i++) { - Editor[i].client = i; - Editor[i].Reset(true); - g_PropData[i].Init(i); - hatData[i].yeetGroundTimer = null; - } - - LoadPresets(); - - TopMenu topMenu; - if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) { - OnAdminMenuReady(topMenu); - } -} - -public void OnLibraryRemoved(const char[] name) { - if (StrEqual(name, "adminmenu", false)) { - g_topMenu = null; - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -public void OnEnterSaferoom(Event event, const char[] name, bool dontBroadcast) { - int userid = event.GetInt("userid"); - int client = GetClientOfUserId(userid); - if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2 && !IsFakeClient(client)) { - inSaferoom[client] = true; - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - if(HasHat(client) && !HasFlag(client, HAT_PRESET)) { - if(!IsHatAllowedInSaferoom(client)) { - PrintToChat(client, "[Hats] Hat is not allowed in the saferoom and has been returned"); - ClearHat(client, true); - } else { - CreateTimer(2.0, Timer_PlaceHat, userid); - } - } - } - } -} - -public void OnLeaveSaferoom(Event event, const char[] name, bool dontBroadcast) { - int userid = event.GetInt("userid"); - int client = GetClientOfUserId(userid); - if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { - inSaferoom[client] = false; - } -} - -Action Timer_PlaceHat(Handle h, int userid) { - int client = GetClientOfUserId(userid); - if(client > 0 && HasHat(client)) { - GetClientAbsOrigin(client, hatData[client].orgPos); - GetClientEyeAngles(client, hatData[client].orgAng); - // GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 40.0, hatData[client].orgPos); - hatData[client].orgAng[0] = 0.0; - PrintToChat(client, "[Hats] Hat has been placed down"); - ClearHat(client, true); - } - return Plugin_Handled; -} - -// Tries to find a valid location at user's cursor, avoiding placing into solid walls (such as invisible walls) or objects -stock bool GetSmartCursorLocation(int client, float outPos[3]) { - float start[3], angle[3], ceilPos[3], wallPos[3], normal[3]; - // Get the cursor location - GetClientEyePosition(client, start); - GetClientEyeAngles(client, angle); - TR_TraceRayFilter(start, angle, MASK_SOLID, RayType_Infinite, Filter_NoPlayers, client); - if(TR_DidHit()) { - TR_GetEndPosition(outPos); - // Check if the position is a wall - TR_GetPlaneNormal(null, normal); - if(normal[2] < 0.1) { - - // Find a suitable position above - start[0] = outPos[0]; - start[1] = outPos[1]; - start[2] = outPos[2] += 100.0; - TR_TraceRayFilter(outPos, start, MASK_SOLID, RayType_EndPoint, TraceEntityFilterPlayer, client); - bool ceilCollided = TR_DidHit(); - bool ceilOK = !TR_AllSolid(); - TR_GetEndPosition(ceilPos); - // float distCeil = GetVectorDistance(outPos, ceilPos, true); - - // Find a suitable position backwards - angle[0] = 70.0; - angle[1] += 180.0; - TR_TraceRayFilter(outPos, angle, MASK_SOLID, RayType_Infinite, TraceEntityFilterPlayer, client); - bool wallCollided = TR_DidHit(); - TR_GetEndPosition(wallPos); - float distWall = GetVectorDistance(outPos, wallPos, true); - - if(ceilCollided && wallCollided) - - if(wallCollided && distWall < 62500) { - outPos = wallPos; - } else if(ceilOK) { - outPos = ceilPos; - } - } - - return true; - } else { - return false; - } -} - -// Periodically fixes hat offsets, as some events/actions/anything can cause entities to offset from their parent -Action Timer_RemountHats(Handle h) { - float p1[3], p2[3]; - for(int i = 1; i <= MaxClients; i++) { - int entity = GetHat(i); - if(IsClientConnected(i) && IsClientInGame(i) && !HasFlag(i, HAT_POCKET)) { - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - if(entity > 0) { - GetClientAbsOrigin(i, p1); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", p2); - if(GetVectorDistance(p1, p2) > 40000.0) { - ClearParent(entity); - if(visibleEntity > 0) { - ClearParent(visibleEntity); - } - RequestFrame(Frame_Remount, i); - } - } else if(visibleEntity > 0) { - RemoveEntity(visibleEntity); - hatData[i].visibleEntity = INVALID_ENT_REFERENCE; - } - } - } - return Plugin_Handled; -} - -// Remounts entity in a new frame to ensure their parent was properly cleared -void Frame_Remount(int i) { - int entity = GetHat(i); - if(entity == -1) return; - SetParent(entity, i); - SetParentAttachment(entity, hatData[i].attachPoint, false); - SetParentAttachment(entity, hatData[i].attachPoint, true); - - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - if(visibleEntity > 0) { - SetParent(visibleEntity, i); - SetParentAttachment(visibleEntity, hatData[i].attachPoint, false); - SetParentAttachment(visibleEntity, hatData[i].attachPoint, true); - } -} - - -// Handles making a prop sleep after a set amount of time (called after hat yeet) -Action Timer_PropSleep(Handle h, DataPack pack) { - pack.Reset(); - int ref = pack.ReadCell(); - int client = GetClientOfUserId(pack.ReadCell()); - if(client > 0 && IsValidEntity(ref)) { - // CheckKill(ref, client); - float vel[3]; - TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); - PrintToServer("Hats: Yeet delete timeout"); - if(hatData[client].yeetGroundTimer != null) { - delete hatData[client].yeetGroundTimer; - } - } - return Plugin_Continue; -} -Action Timer_GroundKill(Handle h, DataPack pack) { - pack.Reset(); - int ref = pack.ReadCell(); - int client = GetClientOfUserId(pack.ReadCell()); - if(client > 0 && IsValidEntity(ref)) { - float vel[3]; - GetEntPropVector(ref, Prop_Data, "m_vecVelocity", vel); - if(FloatAbs(vel[2]) < 0.2 || IsNearGround(ref)) { - PrintToServer("Hats: Yeet ground check %b %b", FloatAbs(vel[2]) < 0.2, IsNearGround(ref)); - vel[0] = 0.0; - vel[1] = 0.0; - vel[2] = 0.0; - TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); - // CheckKill(ref, client); - hatData[client].yeetGroundTimer = null; - return Plugin_Stop; - } - return Plugin_Continue; - } - return Plugin_Stop; -} - - - -void CheckKill(int ref, int client) { - // Check if we should delete thrown hat objects, such as physic props - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { - // Don't delete if someone has hatted it (including ourself): - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { - return; - } - } - - // Check for prop_ class, only yeetable non-player entity we care as they may be large/collidabl - // Things like weapons aren't a problem as you can't "collide" and get thrown - if(EntRefToEntIndex(ref) > MaxClients) { - char classname[64]; - GetEntityClassname(ref, classname, sizeof(classname)); - if(StrContains(classname, "prop_") > -1) { - RemoveEntity(ref); - return; - } - } - } - AcceptEntityInput(ref, "Sleep"); -} - -Action Timer_PropYeetEnd(Handle h, DataPack pack) { - pack.Reset(); - int realEnt = EntRefToEntIndex(pack.ReadCell()); - // int visibleEnt = EntRefToEntIndex(pack.ReadCell()); - // if(IsValidEntity(visibleEnt)) { - // float pos[3], ang[3]; - // GetEntPropVector(visibleEnt, Prop_Send, "m_vecOrigin", pos); - // GetEntPropVector(visibleEnt, Prop_Send, "m_angRotation", ang); - // AcceptEntityInput(visibleEnt, "kill"); - // if(IsValidEntity(realEnt)) { - // TeleportEntity(realEnt, pos, ang, NULL_VECTOR); - // } - // } - if(IsValidEntity(realEnt)) { - SetEntProp(realEnt, Prop_Send, "m_CollisionGroup", pack.ReadCell()); - SetEntProp(realEnt, Prop_Send, "m_nSolidType", pack.ReadCell()); - SetEntProp(realEnt, Prop_Send, "movetype", pack.ReadCell()); - AcceptEntityInput(realEnt, "Sleep"); - } - - return Plugin_Handled; -} - -Action Timer_RemoveGod(Handle h, int userid) { - int client = GetClientOfUserId(userid); - if(client) { - tempGod[client] = false; - SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - } - return Plugin_Handled; -} - -void Event_PlayerOutOfIdle(Event event, const char[] name, bool dontBroadcast) { - int bot = GetClientOfUserId(event.GetInt("bot")); - int client = GetClientOfUserId(event.GetInt("player")); - if(GetClientTeam(client) != 2) return; - float pos[3]; - for(int i = 1; i <= MaxClients; i++) { - if(hatData[i].entity == bot) { - GetClientAbsOrigin(i, pos); - ClearHat(i); - hatData[i].entity = EntIndexToEntRef(client); - TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); - return; - } - } - PrintToServer("Fixing hatted player to bot: Bot %N to client %N", bot, client); - // Incase they removed hat right after, manually fix them - ClearParent(client); - ClearParent(bot); - SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); - SetEntProp(client, Prop_Send, "m_nSolidType", 2); - SetEntityMoveType(client, MOVETYPE_WALK); - RequestFrame(Frame_FixClient, client); - // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); -} - -void Frame_FixClient(int client) { - if(IsClientConnected(client) && GetClientTeam(client) == 2) { - ClearParent(client); - SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); - SetEntProp(client, Prop_Send, "m_nSolidType", 2); - SetEntityMoveType(client, MOVETYPE_WALK); - } - // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); -} -public void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) { - int bot = GetClientOfUserId(event.GetInt("bot")); - int client = GetClientOfUserId(event.GetInt("player")); - if(GetClientTeam(client) != 2) return; - float pos[3]; - for(int i = 1; i <= MaxClients; i++) { - if(hatData[i].entity == client) { - GetClientAbsOrigin(i, pos); - ClearHat(i); - hatData[i].entity = EntIndexToEntRef(bot); - TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); - return; - } - } - // Incase they removed hat right after, manually fix them - ClearParent(bot); - SetEntProp(bot, Prop_Send, "m_CollisionGroup", 5); - SetEntProp(bot, Prop_Send, "m_nSolidType", 2); - SetEntityMoveType(bot, MOVETYPE_WALK); -} - -void OnLocalPlayerHatCookieSelect(int client, CookieMenuAction action, any info, char[] buffer, int maxlen) { - if(action != CookieMenuAction_SelectOption) return; - bool value = StringToInt(buffer) == 1; - if(value) { - for(int i = 1; i <= MaxClients; i++) { - int hat = GetHat(i); - if(hat == client) { - ClearHat(i, false); - PrintToChat(i, "%N has blocked player hats for themselves", client); - } - } - ClearHat(client, false); - } -} - -public void Event_HatsEnableChanged(ConVar convar, const char[] sOldValue, const char[] sNewValue) { - if(convar.IntValue == 0) { - ClearHats(); - } else if(convar.IntValue == 1) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && GetUserAdmin(i) == INVALID_ADMIN_ID && HasHat(i)) { - ClearHat(i, false); - } - } - } -} - -ArrayList GetSpawnLocations() { - ArrayList list = new ArrayList(); - ArrayList newList = new ArrayList(); - L4D_GetAllNavAreas(list); - for(int i = 0; i < list.Length; i++) { - Address nav = list.Get(i); - if(L4D_GetNavArea_SpawnAttributes(nav) & NAV_SPAWN_THREAT) { - newList.Push(nav); - } - } - delete list; - PrintToServer("[Hats] Got %d valid locations", newList.Length); - return newList; -} - - -void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { - if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) { - int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1)); - L4D_FindRandomSpot(nav, pos); - } else { - int survivor = GetRandomClient(5, 1); - if(ignoreClient > 0 && survivor == ignoreClient) survivor = GetRandomClient(5, 1); - if(survivor > 0) { - GetClientAbsOrigin(survivor, pos); - } - } -} - -bool g_inRotate[MAXPLAYERS+1]; -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]) { - float tick = GetGameTime(); - ////////////////////////////// - // OnPlayerRunCmd :: HATS - ///////////////////////////// - int oldButtons = GetEntProp(client, Prop_Data, "m_nOldButtons"); - if(IsHatsEnabled(client)) { - int entity = GetHat(client); - int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); - if(entity > 0) { - // Crash prevention: Prevent hat from touching ladder as that can cause server crashes - if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { - onLadder[client] = true; - ClearParent(entity); - - // If hat is not a player, we teleport them to the void (0, 0, 0) - // Ostherwise, we just simply dismount the player while hatter is on ladder - if(entity >= MaxClients) - TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); - if(visibleEntity > 0) { - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - RemoveEntity(visibleEntity); - } - } - // Player is no longer on ladder, restore hat: - else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { - onLadder[client] = false; - EquipHat(client, entity); - } - - // Do the same crash protection for the hat itself, just to be safe: - if(entity <= MaxClients) { - if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { - onLadder[entity] = true; - ClearParent(entity); - } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { - onLadder[entity] = false; - EquipHat(client, entity); - } - } - - // Rainbow hat processing - if(HasFlag(client, HAT_RAINBOW)) { - // Decrement and flip, possibly when rainbowticks - if(hatData[client].rainbowReverse) { - hatData[client].rainbowColor[0] -= cvar_sm_hats_rainbow_speed.FloatValue; - } else { - hatData[client].rainbowColor[0] += cvar_sm_hats_rainbow_speed.FloatValue; - } - - if(hatData[client].rainbowColor[0] > 360.0) { - hatData[client].rainbowReverse = true; - hatData[client].rainbowColor[0] = 360.0; - } else if(hatData[client].rainbowColor[0] < 0.0) { - hatData[client].rainbowReverse = false; - hatData[client].rainbowColor[0] = 0.0; - } - - static int rgb[3]; - HSVToRGBInt(hatData[client].rainbowColor, rgb); - SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); - hatData[client].rainbowTicks = -cvar_sm_hats_rainbow_speed.IntValue; - EquipHat(client, entity); - } - - // If bot is commandable and reversed (player reverse-hat common/survivor), change position: - if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { - float pos[3]; - ChooseRandomPosition(pos, client); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); - } - } - // Detect E + R to offset hat or place down - if(buttons & IN_USE && buttons & IN_RELOAD) { - if(entity > 0) { - if(buttons & IN_ZOOM) { - // Offset controls: - // if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; - // if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; - if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; - if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; - if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; - if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; - TeleportEntity(entity, hatData[client].offset, angles, vel); - return Plugin_Handled; - } else if(tick - cmdThrottle[client] > 0.25) { - if(buttons & IN_ATTACK) { // doesn't work reliably for some reason - ClientCommand(client, "sm_hat y"); - } else if(buttons & IN_DUCK) { - ClientCommand(client, "sm_hat p"); - } - } - } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { - ClientCommand(client, "sm_hat"); - } - cmdThrottle[client] = tick; - hatData[client].angles = angles; - return Plugin_Handled; - } - } - - ////////////////////////////// - // OnPlayerRunCmd :: ENTITY EDITOR - ///////////////////////////// - if(g_pendingSaveClient == client) { - if(g_PropData[client].pendingSaveType == Save_Schematic) { - // move cursor? or should be editor anyway - } - } else if(g_PropData[client].Selector.IsActive()) { - SetWeaponDelay(client, 0.5); - if(tick - cmdThrottle[client] >= 0.20) { - if(buttons & IN_ATTACK) { - int entity = GetLookingEntity(client, Filter_ValidHats); - if(entity > 0) { - if(g_PropData[client].Selector.AddEntity(entity) != -1) { - PrecacheSound("ui/beep07.wav"); - EmitSoundToClient(client, "ui/beep07.wav", entity, SND_CHANGEVOL, .volume = 0.5); - } - } else { - PrintHintText(client, "No entity found"); - } - } else if(buttons & IN_ATTACK2) { - int entity = GetLookingEntity(client, Filter_ValidHats); - if(entity > 0) { - if(g_PropData[client].Selector.RemoveEntity(entity)) { - PrecacheSound("ui/beep22.wav"); - EmitSoundToClient(client, "ui/beep22.wav", entity, SND_CHANGEVOL, .volume = 0.5); - } - } - } else if(buttons & IN_USE) { - if(buttons & IN_SPEED) { - //Delete - g_PropData[client].Selector.End(); - } else if(buttons & IN_DUCK) { - //Cancel - g_PropData[client].Selector.Cancel(); - } - } - cmdThrottle[client] = tick; - } - } else if(Editor[client].IsActive()) { - // if(buttons & IN_USE && buttons & IN_RELOAD) { - // ClientCommand(client, "sm_wall done"); - // return Plugin_Handled; - // } - bool allowMove = true; - switch(Editor[client].mode) { - case MOVE_ORIGIN: { - SetWeaponDelay(client, 0.5); - - bool isRotate; - int flags = GetEntityFlags(client); - if(buttons & IN_RELOAD) { - if(!g_inRotate[client]) { - g_inRotate[client] = true; - } - if(!(oldButtons & IN_JUMP) && (buttons & IN_JUMP)) { - buttons &= ~IN_JUMP; - Editor[client].CycleStacker(); - } else if(!(oldButtons & IN_SPEED) && (buttons & IN_SPEED)) { - Editor[client].ToggleCollision(); - return Plugin_Handled; - } else if(!(oldButtons & IN_DUCK) && (buttons & IN_DUCK)) { - Editor[client].ToggleCollisionRotate(); - return Plugin_Handled; - } else { - PrintCenterText(client, "%.1f %.1f %.1f", Editor[client].angles[0], Editor[client].angles[1], Editor[client].angles[2]); - isRotate = true; - SetEntityFlags(client, flags |= FL_FROZEN); - if(!(oldButtons & IN_ATTACK) && (buttons & IN_ATTACK)) Editor[client].CycleAxis(); - else if(!(oldButtons & IN_ATTACK2) && (buttons & IN_ATTACK2)) Editor[client].CycleSnapAngle(tick); - - // Rotation control: - // Turn off rotate when player wants rotate - Editor[client].hasCollisionRotate = false; - if(tick - cmdThrottle[client] > 0.1) { - if(Editor[client].axis == 0) { - int mouseXAbs = IntAbs(mouse[0]); - int mouseYAbs = IntAbs(mouse[1]); - bool XOverY = mouseXAbs > mouseYAbs; - if(mouseYAbs > 10 && !XOverY) { - Editor[client].IncrementAxis(0, mouse[1]); - } else if(mouseXAbs > 10 && XOverY) { - Editor[client].IncrementAxis(1, mouse[0]); - } - } - else if(Editor[client].axis == 1) { - if(mouse[0] > 10) Editor[client].angles[2] += Editor[client].snapAngle; - else if(mouse[0] < -10) Editor[client].angles[2] -= Editor[client].snapAngle; - } - cmdThrottle[client] = tick; - } - } - } else { - if(g_inRotate[client]) { - g_inRotate[client] = false; - } - // Move position - float moveAmount = (buttons & IN_SPEED) ? 2.0 : 1.0; - if(buttons & IN_ATTACK) Editor[client].moveDistance += moveAmount; - else if(buttons & IN_ATTACK2) Editor[client].moveDistance -= moveAmount; - } - - // Clear IN_FROZEN when no longer rotate - if(!isRotate && flags & FL_FROZEN) { - flags = flags & ~FL_FROZEN; - SetEntityFlags(client, flags); - } - if(Editor[client].stackerDirection == Stack_Off) - CalculateEditorPosition(client, Filter_IgnorePlayerAndWall); - } - case SCALE: { - SetWeaponDelay(client, 0.5); - allowMove = false; - if(buttons & IN_USE) { - Editor[client].CycleSpeed(tick); - } else { - if(buttons & IN_MOVELEFT) { - Editor[client].IncrementSize(0, -1.0); - } else if(buttons & IN_MOVERIGHT) { - Editor[client].IncrementSize(0, 1.0); - Editor[client].size[0] += Editor[client].moveSpeed; - } - if(buttons & IN_FORWARD) { - Editor[client].IncrementSize(0, 1.0); - } else if(buttons & IN_BACK) { - Editor[client].IncrementSize(0, -1.0); - } - if(buttons & IN_JUMP) { - Editor[client].IncrementSize(0, 1.0); - } else if(buttons & IN_DUCK) { - Editor[client].IncrementSize(0, -1.0); - } - } - } - case COLOR: { - SetWeaponDelay(client, 0.5); - PrintHintText(client, "%d %d %d %d", Editor[client].color[0], Editor[client].color[1], Editor[client].color[2], Editor[client].color[3]); - if(buttons & IN_USE) { - Editor[client].CycleColorComponent(tick); - } else if(buttons & IN_ATTACK2) { - Editor[client].IncreaseColor(1); - allowMove = false; - } else if(buttons & IN_ATTACK) { - Editor[client].IncreaseColor(-1); - allowMove = false; - } - } - } - if(buttons & IN_DUCK) { - - } - if(Editor[client].mode != COLOR && !(oldButtons & IN_USE) && buttons & IN_USE) { - if(buttons & IN_SPEED) { - Editor[client].Cancel(); - } else if(buttons & IN_DUCK) { - Editor[client].CycleBuildType(); - // Editor[client].ShowExtraOptions(); - } else { - int entity; - Editor[client].Done(entity); - } - - } else if(!(oldButtons & IN_ZOOM) && buttons & IN_ZOOM) { - Editor[client].CycleMode(); // ZOOM: Cycle forward - } - - Editor[client].Draw(BUILDER_COLOR, 0.1, 0.1); - return allowMove ? Plugin_Continue : Plugin_Handled; - } - - return Plugin_Continue; -} - -int IntAbs(int a) { - if(a < 0) { - return a * -1; - } - return a; -} - -// Don't show real entity to hat wearer (Show for ALL but hat wearer) -Action OnRealTransmit(int entity, int client) { - #if defined DEBUG_HAT_SHOW_FAKE - return Plugin_Continue; - #endif - if(hatData[client].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].entity) == entity) - return Plugin_Handled; - return Plugin_Continue; -} - -// Only show to hat wearer (do not show to ALL) -Action OnVisibleTransmit(int entity, int client) { - #if defined DEBUG_HAT_SHOW_FAKE - return Plugin_Continue; - #endif - if(hatData[client].visibleEntity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].visibleEntity) != entity) - return Plugin_Handled; - return Plugin_Continue; -} - - -public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { - if(victim > MaxClients || victim <= 0) return Plugin_Continue; - if(damage > 0.0 && tempGod[victim]) { - damage = 0.0; - return Plugin_Handled; - } - if(attacker > MaxClients || attacker <= 0) return Plugin_Continue; - if(victim == EntRefToEntIndex(hatData[attacker].entity) || attacker == EntRefToEntIndex(hatData[victim].entity)) { - damage = 0.0; - return Plugin_Handled; - } - return Plugin_Continue; -} - -void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if(client > 0) { - if(!HasHat(client) && !IsFakeClient(client)) { - hatPresetCookie.Get(client, ActivePreset[client], 32); - if(ActivePreset[client][0] != '\0') { - RestoreActivePreset(client); - ReplyToCommand(client, "[Hats] Applied your hat preset! Clear it with /hatp"); - } - } - SDKHook(client, SDKHook_WeaponCanUse, OnWeaponUse); - } -} - -Action OnWeaponUse(int client, int weapon) { - int ref = EntIndexToEntRef(weapon); - // Prevent picking up weapons that are previews - for(int i = 1; i <= MaxClients; i++) { - if(Editor[i].entity == ref && Editor[i].flags & Edit_Preview) { - return Plugin_Handled; - } - } - return Plugin_Continue; -} - -public void OnClientDisconnect(int client) { - tempGod[client] = false; - Editor[client].Reset(); - g_PropData[client].Reset(); - if(hatData[client].yeetGroundTimer != null) - delete hatData[client].yeetGroundTimer; - if(g_pendingSaveClient == client) { - g_pendingSaveClient = 0; - ClearSavePreview(); - } - ClearHat(client, true); -} - -public void OnEntityDestroyed(int entity) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { - if(hatData[i].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[i].entity) == entity) { - ClearHat(i); - PrintHintText(i, "Hat has vanished"); - ClientCommand(i, "play ui/menu_back.wav"); - break; - } - } - } -} -public void OnMapStart() { - PrecacheModel(DUMMY_MODEL); - g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt"); - CreateTimer(30.0, Timer_RemountHats, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - for(int i = 1; i <= MaxClients; i++) { - cmdThrottle[i] = 0.0; - tempGod[i] = false; - } - GetCurrentMap(g_currentMap, sizeof(g_currentMap)); - NavAreas = GetSpawnLocations(); -} - - -public void OnMapEnd() { - delete NavAreas; - g_spawnedItems.Clear(); - for(int i = 1; i <= createdWalls.Length; i++) { - if(hatData[i].yeetGroundTimer != null) { - delete hatData[i].yeetGroundTimer; - } - DeleteWall(i); - } - createdWalls.Clear(); - ClearHats(); - UnloadCategories(); - UnloadSave(); - SaveRecents(); -} -public void OnPluginEnd() { - ClearHats(); - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i)) { - int flags = GetEntityFlags(i) & ~FL_FROZEN; - SetEntityFlags(i, flags); - } - } - if(g_spawnedItems != null) { - delete g_spawnedItems; - } - TriggerInput("prop_preview", "Kill"); -} - -public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { - if(EntRefToEntIndex(hatData[data].entity) == entity) { - return false; - } - return entity != data; -} - -int GetLookingEntity(int client, TraceEntityFilter filter) { - static float pos[3], ang[3]; - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); - if(TR_DidHit()) { - return TR_GetEntityIndex(); - } - return -1; -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -stock bool Filter_OnlyPlayers(int entity, int mask, int data) { - return entity > 0 && entity <= MaxClients && entity != data; -} - -stock bool Filter_NoPlayers(int entity, int mask, int data) { - return entity > MaxClients && entity != data; -} - -stock bool Filter_IgnorePlayerAndWall(int entity, int mask, int data) { - if(entity > MaxClients && entity != data && EntRefToEntIndex(Editor[data].entity) != entity && EntRefToEntIndex(hatData[data].entity) != entity) { - static char classname[16]; - GetEntityClassname(entity, classname, sizeof(classname)); - // Ignore infected - return !StrEqual(classname, "infected"); - } - return false; -} - - -bool Filter_ValidHats(int entity, int mask, int data) { - if(entity == data) return false; - if(entity <= MaxClients && entity > 0) { - int client = GetRealClient(data); - if(client == -1) client = data; - return CanTarget(client); // Don't target if player targetting off - } - return CheckBlacklist(entity); -} - -bool CheckBlacklist(int entity) { - if(entity == 0) return false; - if(cvar_sm_hats_blacklist_enabled.BoolValue) { - static char buffer[64]; - GetEntityClassname(entity, buffer, sizeof(buffer)); - for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { - if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) { - return false; - } - } - if(StrContains(buffer, "prop_") > -1) { - GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); - for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { - if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { - return false; - } - } - } - GetEntPropString(entity, Prop_Data, "m_iName", buffer, sizeof(buffer)); - if(StrEqual(buffer, "l4d2_randomizer")) { - return false; - } - } - return true; -} - -//////////////////////////////// - -stock void TriggerInput(const char[] targetName, const char[] input) { - int entity = -1; - char _targetName[32]; - while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { - GetEntPropString(entity, Prop_Data, "m_iName", _targetName, sizeof(_targetName)); - if(StrEqual(_targetName, targetName)) { - AcceptEntityInput(entity, input); - } - } -} - - -stock bool FindGround(const float start[3], float end[3]) { - float angle[3]; - angle[0] = 90.0; - - Handle trace = TR_TraceRayEx(start, angle, MASK_SHOT, RayType_Infinite); - if(!TR_DidHit(trace)) { - delete trace; - return false; - } - TR_GetEndPosition(end, trace); - delete trace; - return true; -} - -stock bool L4D_IsPlayerCapped(int client) { - if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || - GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) - return true; - return false; -} -stock void LookAtPoint(int entity, const float destination[3]){ - float angles[3], pos[3], result[3]; - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); - MakeVectorFromPoints(destination, pos, result); - GetVectorAngles(result, angles); - if(angles[0] >= 270){ - angles[0] -= 270; - angles[0] = (90-angles[0]); - } else { - if(angles[0] <= 90){ - angles[0] *= -1; - } - } - angles[1] -= 180; - TeleportEntity(entity, NULL_VECTOR, angles, NULL_VECTOR); -} - -stock float SnapTo(const float value, const float degree) { - return float(RoundFloat(value / degree)) * degree; -} - -stock bool CalculateEditorPosition(int client, TraceEntityFilter filter) { - if (client > 0 && client <= MaxClients && IsClientInGame(client)) { - float clientEye[3], clientAngle[3], direction[3]; - GetClientEyePosition(client, clientEye); - GetClientEyeAngles(client, clientAngle); - - GetAngleVectors(clientAngle, direction, NULL_VECTOR, NULL_VECTOR); - ScaleVector(direction, Editor[client].moveDistance); - AddVectors(clientEye, direction, Editor[client].origin); - - if(Editor[client].hasCollision) { - TR_TraceRayFilter(clientEye, Editor[client].origin, MASK_OPAQUE, RayType_EndPoint, filter, client); - if (TR_DidHit(INVALID_HANDLE)) { - TR_GetEndPosition(Editor[client].origin); - GetEntPropVector(Editor[client].entity, Prop_Send, "m_vecMins", direction); - Editor[client].origin[2] -= direction[2]; - if(Editor[client].hasCollisionRotate) { - TR_GetPlaneNormal(INVALID_HANDLE, Editor[client].angles); - GetVectorAngles(Editor[client].angles, Editor[client].angles); - Editor[client].angles[0] += 90.0; //need to rotate for some reason - } - } - } - - return true; - } - return false; +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0" +#define PLAYER_HAT_REQUEST_COOLDOWN 10 +// #define DEBUG_GLOW 1 +static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; + +#define DUMMY_MODEL "models/props/cs_office/vending_machine.mdl" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +bool tempGod[MAXPLAYERS+1]; +bool inSaferoom[MAXPLAYERS+1]; + +float cmdThrottle[MAXPLAYERS+1]; +static bool onLadder[MAXPLAYERS+1]; + +Cookie noHatVictimCookie; +Cookie hatPresetCookie; + +ConVar cvar_sm_hats_enabled; +ConVar cvar_sm_hats_flags; +ConVar cvar_sm_hats_rainbow_speed; +ConVar cvar_sm_hats_blacklist_enabled; +ConVar cvar_sm_hats_max_distance; + +char g_currentMap[64]; + +//int g_markedMode + +#include +#include + +public Plugin myinfo = { + name = "L4D2 Hats & Editor", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +ArrayList NavAreas; +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + return APLRes_Success; +} + + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D/L4D2 only."); + } + + LoadTranslations("common.phrases"); + HookEvent("player_entered_checkpoint", OnEnterSaferoom); + HookEvent("player_left_checkpoint", OnLeaveSaferoom); + HookEvent("player_bot_replace", Event_PlayerToIdle); + HookEvent("bot_player_replace", Event_PlayerOutOfIdle); + HookEvent("player_spawn", Event_PlayerSpawn); + + RegConsoleCmd("sm_hat", Command_DoAHat, "Hats"); + RegAdminCmd("sm_hatf", Command_DoAHat, ADMFLAG_ROOT, "Hats"); + RegConsoleCmd("sm_hatp", Command_DoAHatPreset); + + cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); + cvar_sm_hats_enabled = CreateConVar("sm_hats_enabled", "1.0", "Enable hats.\n0=OFF, 1=Admins Only, 2=Any", FCVAR_NONE, true, 0.0, true, 2.0); + cvar_sm_hats_enabled.AddChangeHook(Event_HatsEnableChanged); + cvar_sm_hats_flags = CreateConVar("sm_hats_features", "153", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats\n128 = Delete Thrown Hats", FCVAR_CHEAT, true, 0.0); + cvar_sm_hats_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0); + cvar_sm_hats_max_distance = CreateConVar("sm_hats_distance", "240", "The max distance away you can hat something. 0 = disable", FCVAR_NONE, true, 0.0); + + noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public); + noHatVictimCookie.SetPrefabMenu(CookieMenu_OnOff_Int, "Disable player hats for self", OnLocalPlayerHatCookieSelect); + + hatPresetCookie = new Cookie("hats_preset", "Sets the preset hat you spawn with", CookieAccess_Public); + + for(int i = 1; i <= MaxClients; i++) { + hatData[i].yeetGroundTimer = null; + } + + LoadPresets(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +public void OnEnterSaferoom(Event event, const char[] name, bool dontBroadcast) { + int userid = event.GetInt("userid"); + int client = GetClientOfUserId(userid); + if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2 && !IsFakeClient(client)) { + inSaferoom[client] = true; + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + if(HasHat(client) && !HasFlag(client, HAT_PRESET)) { + if(!IsHatAllowedInSaferoom(client)) { + PrintToChat(client, "[Hats] Hat is not allowed in the saferoom and has been returned"); + ClearHat(client, true); + } else { + CreateTimer(2.0, Timer_PlaceHat, userid); + } + } + } + } +} + +public void OnLeaveSaferoom(Event event, const char[] name, bool dontBroadcast) { + int userid = event.GetInt("userid"); + int client = GetClientOfUserId(userid); + if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { + inSaferoom[client] = false; + } +} + +Action Timer_PlaceHat(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client > 0 && HasHat(client)) { + GetClientAbsOrigin(client, hatData[client].orgPos); + GetClientEyeAngles(client, hatData[client].orgAng); + // GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 40.0, hatData[client].orgPos); + hatData[client].orgAng[0] = 0.0; + PrintToChat(client, "[Hats] Hat has been placed down"); + ClearHat(client, true); + } + return Plugin_Handled; +} + +// Tries to find a valid location at user's cursor, avoiding placing into solid walls (such as invisible walls) or objects +stock bool GetSmartCursorLocation(int client, float outPos[3]) { + float start[3], angle[3], ceilPos[3], wallPos[3], normal[3]; + // Get the cursor location + GetClientEyePosition(client, start); + GetClientEyeAngles(client, angle); + TR_TraceRayFilter(start, angle, MASK_SOLID, RayType_Infinite, Filter_NoPlayers, client); + if(TR_DidHit()) { + TR_GetEndPosition(outPos); + // Check if the position is a wall + TR_GetPlaneNormal(null, normal); + if(normal[2] < 0.1) { + + // Find a suitable position above + start[0] = outPos[0]; + start[1] = outPos[1]; + start[2] = outPos[2] += 100.0; + TR_TraceRayFilter(outPos, start, MASK_SOLID, RayType_EndPoint, TraceEntityFilterPlayer, client); + bool ceilCollided = TR_DidHit(); + bool ceilOK = !TR_AllSolid(); + TR_GetEndPosition(ceilPos); + // float distCeil = GetVectorDistance(outPos, ceilPos, true); + + // Find a suitable position backwards + angle[0] = 70.0; + angle[1] += 180.0; + TR_TraceRayFilter(outPos, angle, MASK_SOLID, RayType_Infinite, TraceEntityFilterPlayer, client); + bool wallCollided = TR_DidHit(); + TR_GetEndPosition(wallPos); + float distWall = GetVectorDistance(outPos, wallPos, true); + + if(ceilCollided && wallCollided) + + if(wallCollided && distWall < 62500) { + outPos = wallPos; + } else if(ceilOK) { + outPos = ceilPos; + } + } + + return true; + } else { + return false; + } +} + +// Periodically fixes hat offsets, as some events/actions/anything can cause entities to offset from their parent +Action Timer_RemountHats(Handle h) { + float p1[3], p2[3]; + for(int i = 1; i <= MaxClients; i++) { + int entity = GetHat(i); + if(IsClientConnected(i) && IsClientInGame(i) && !HasFlag(i, HAT_POCKET)) { + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + if(entity > 0) { + GetClientAbsOrigin(i, p1); + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", p2); + if(GetVectorDistance(p1, p2) > 40000.0) { + ClearParent(entity); + if(visibleEntity > 0) { + ClearParent(visibleEntity); + } + RequestFrame(Frame_Remount, i); + } + } else if(visibleEntity > 0) { + RemoveEntity(visibleEntity); + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + } + } + } + return Plugin_Handled; +} + +// Remounts entity in a new frame to ensure their parent was properly cleared +void Frame_Remount(int i) { + int entity = GetHat(i); + if(entity == -1) return; + SetParent(entity, i); + SetParentAttachment(entity, hatData[i].attachPoint, false); + SetParentAttachment(entity, hatData[i].attachPoint, true); + + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + if(visibleEntity > 0) { + SetParent(visibleEntity, i); + SetParentAttachment(visibleEntity, hatData[i].attachPoint, false); + SetParentAttachment(visibleEntity, hatData[i].attachPoint, true); + } +} + + +// Handles making a prop sleep after a set amount of time (called after hat yeet) +Action Timer_PropSleep(Handle h, DataPack pack) { + pack.Reset(); + int ref = pack.ReadCell(); + int client = GetClientOfUserId(pack.ReadCell()); + if(client > 0 && IsValidEntity(ref)) { + // CheckKill(ref, client); + float vel[3]; + TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); + PrintToServer("Hats: Yeet delete timeout"); + if(hatData[client].yeetGroundTimer != null) { + delete hatData[client].yeetGroundTimer; + } + } + return Plugin_Continue; +} +Action Timer_GroundKill(Handle h, DataPack pack) { + pack.Reset(); + int ref = pack.ReadCell(); + int client = GetClientOfUserId(pack.ReadCell()); + if(client > 0 && IsValidEntity(ref)) { + float vel[3]; + GetEntPropVector(ref, Prop_Data, "m_vecVelocity", vel); + if(FloatAbs(vel[2]) < 0.2 || IsNearGround(ref)) { + PrintToServer("Hats: Yeet ground check %b %b", FloatAbs(vel[2]) < 0.2, IsNearGround(ref)); + vel[0] = 0.0; + vel[1] = 0.0; + vel[2] = 0.0; + TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); + // CheckKill(ref, client); + hatData[client].yeetGroundTimer = null; + return Plugin_Stop; + } + return Plugin_Continue; + } + return Plugin_Stop; +} + + + +void CheckKill(int ref, int client) { + // Check if we should delete thrown hat objects, such as physic props + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { + // Don't delete if someone has hatted it (including ourself): + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { + return; + } + } + + // Check for prop_ class, only yeetable non-player entity we care as they may be large/collidabl + // Things like weapons aren't a problem as you can't "collide" and get thrown + if(EntRefToEntIndex(ref) > MaxClients) { + char classname[64]; + GetEntityClassname(ref, classname, sizeof(classname)); + if(StrContains(classname, "prop_") > -1) { + RemoveEntity(ref); + return; + } + } + } + AcceptEntityInput(ref, "Sleep"); +} + +Action Timer_PropYeetEnd(Handle h, DataPack pack) { + pack.Reset(); + int realEnt = EntRefToEntIndex(pack.ReadCell()); + // int visibleEnt = EntRefToEntIndex(pack.ReadCell()); + // if(IsValidEntity(visibleEnt)) { + // float pos[3], ang[3]; + // GetEntPropVector(visibleEnt, Prop_Send, "m_vecOrigin", pos); + // GetEntPropVector(visibleEnt, Prop_Send, "m_angRotation", ang); + // AcceptEntityInput(visibleEnt, "kill"); + // if(IsValidEntity(realEnt)) { + // TeleportEntity(realEnt, pos, ang, NULL_VECTOR); + // } + // } + if(IsValidEntity(realEnt)) { + SetEntProp(realEnt, Prop_Send, "m_CollisionGroup", pack.ReadCell()); + SetEntProp(realEnt, Prop_Send, "m_nSolidType", pack.ReadCell()); + SetEntProp(realEnt, Prop_Send, "movetype", pack.ReadCell()); + AcceptEntityInput(realEnt, "Sleep"); + } + + return Plugin_Handled; +} + +Action Timer_RemoveGod(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client) { + tempGod[client] = false; + SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + } + return Plugin_Handled; +} + +void Event_PlayerOutOfIdle(Event event, const char[] name, bool dontBroadcast) { + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + if(GetClientTeam(client) != 2) return; + float pos[3]; + for(int i = 1; i <= MaxClients; i++) { + if(hatData[i].entity == bot) { + GetClientAbsOrigin(i, pos); + ClearHat(i); + hatData[i].entity = EntIndexToEntRef(client); + TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); + return; + } + } + PrintToServer("Fixing hatted player to bot: Bot %N to client %N", bot, client); + // Incase they removed hat right after, manually fix them + ClearParent(client); + ClearParent(bot); + SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(client, MOVETYPE_WALK); + RequestFrame(Frame_FixClient, client); + // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); +} + +void Frame_FixClient(int client) { + if(IsClientConnected(client) && GetClientTeam(client) == 2) { + ClearParent(client); + SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(client, MOVETYPE_WALK); + } + // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); +} +public void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) { + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + if(GetClientTeam(client) != 2) return; + float pos[3]; + for(int i = 1; i <= MaxClients; i++) { + if(hatData[i].entity == client) { + GetClientAbsOrigin(i, pos); + ClearHat(i); + hatData[i].entity = EntIndexToEntRef(bot); + TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); + return; + } + } + // Incase they removed hat right after, manually fix them + ClearParent(bot); + SetEntProp(bot, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(bot, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(bot, MOVETYPE_WALK); +} + +void OnLocalPlayerHatCookieSelect(int client, CookieMenuAction action, any info, char[] buffer, int maxlen) { + if(action != CookieMenuAction_SelectOption) return; + bool value = StringToInt(buffer) == 1; + if(value) { + for(int i = 1; i <= MaxClients; i++) { + int hat = GetHat(i); + if(hat == client) { + ClearHat(i, false); + PrintToChat(i, "%N has blocked player hats for themselves", client); + } + } + ClearHat(client, false); + } +} + +public void Event_HatsEnableChanged(ConVar convar, const char[] sOldValue, const char[] sNewValue) { + if(convar.IntValue == 0) { + ClearHats(); + } else if(convar.IntValue == 1) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && GetUserAdmin(i) == INVALID_ADMIN_ID && HasHat(i)) { + ClearHat(i, false); + } + } + } +} + +ArrayList GetSpawnLocations() { + ArrayList list = new ArrayList(); + ArrayList newList = new ArrayList(); + L4D_GetAllNavAreas(list); + for(int i = 0; i < list.Length; i++) { + Address nav = list.Get(i); + if(L4D_GetNavArea_SpawnAttributes(nav) & NAV_SPAWN_THREAT) { + newList.Push(nav); + } + } + delete list; + PrintToServer("[Hats] Got %d valid locations", newList.Length); + return newList; +} + + +void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { + if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) { + int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1)); + L4D_FindRandomSpot(nav, pos); + } else { + int survivor = GetRandomClient(5, 1); + if(ignoreClient > 0 && survivor == ignoreClient) survivor = GetRandomClient(5, 1); + if(survivor > 0) { + GetClientAbsOrigin(survivor, pos); + } + } +} + +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]) { + float tick = GetGameTime(); + ////////////////////////////// + // OnPlayerRunCmd :: HATS + ///////////////////////////// + if(IsHatsEnabled(client)) { + int entity = GetHat(client); + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + if(entity > 0) { + // Crash prevention: Prevent hat from touching ladder as that can cause server crashes + if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { + onLadder[client] = true; + ClearParent(entity); + + // If hat is not a player, we teleport them to the void (0, 0, 0) + // Ostherwise, we just simply dismount the player while hatter is on ladder + if(entity >= MaxClients) + TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); + if(visibleEntity > 0) { + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + RemoveEntity(visibleEntity); + } + } + // Player is no longer on ladder, restore hat: + else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { + onLadder[client] = false; + EquipHat(client, entity); + } + + // Do the same crash protection for the hat itself, just to be safe: + if(entity <= MaxClients) { + if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { + onLadder[entity] = true; + ClearParent(entity); + } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { + onLadder[entity] = false; + EquipHat(client, entity); + } + } + + // Rainbow hat processing + if(HasFlag(client, HAT_RAINBOW)) { + // Decrement and flip, possibly when rainbowticks + if(hatData[client].rainbowReverse) { + hatData[client].rainbowColor[0] -= cvar_sm_hats_rainbow_speed.FloatValue; + } else { + hatData[client].rainbowColor[0] += cvar_sm_hats_rainbow_speed.FloatValue; + } + + if(hatData[client].rainbowColor[0] > 360.0) { + hatData[client].rainbowReverse = true; + hatData[client].rainbowColor[0] = 360.0; + } else if(hatData[client].rainbowColor[0] < 0.0) { + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + } + + static int rgb[3]; + HSVToRGBInt(hatData[client].rainbowColor, rgb); + SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); + hatData[client].rainbowTicks = -cvar_sm_hats_rainbow_speed.IntValue; + EquipHat(client, entity); + } + + // If bot is commandable and reversed (player reverse-hat common/survivor), change position: + if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { + float pos[3]; + ChooseRandomPosition(pos, client); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); + } + } + // Detect E + R to offset hat or place down + if(buttons & IN_USE && buttons & IN_RELOAD) { + if(entity > 0) { + if(buttons & IN_ZOOM) { + // Offset controls: + // if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; + // if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; + if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; + if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; + if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; + if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; + TeleportEntity(entity, hatData[client].offset, angles, vel); + return Plugin_Handled; + } else if(tick - cmdThrottle[client] > 0.25) { + if(buttons & IN_ATTACK) { // doesn't work reliably for some reason + ClientCommand(client, "sm_hat y"); + } else if(buttons & IN_DUCK) { + ClientCommand(client, "sm_hat p"); + } + } + } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { + ClientCommand(client, "sm_hat"); + } + cmdThrottle[client] = tick; + hatData[client].angles = angles; + return Plugin_Handled; + } + } + return Plugin_Continue; +} + +// Don't show real entity to hat wearer (Show for ALL but hat wearer) +Action OnRealTransmit(int entity, int client) { + #if defined DEBUG_HAT_SHOW_FAKE + return Plugin_Continue; + #endif + if(hatData[client].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].entity) == entity) + return Plugin_Handled; + return Plugin_Continue; +} + +// Only show to hat wearer (do not show to ALL) +Action OnVisibleTransmit(int entity, int client) { + #if defined DEBUG_HAT_SHOW_FAKE + return Plugin_Continue; + #endif + if(hatData[client].visibleEntity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].visibleEntity) != entity) + return Plugin_Handled; + return Plugin_Continue; +} + + +public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { + if(victim > MaxClients || victim <= 0) return Plugin_Continue; + if(damage > 0.0 && tempGod[victim]) { + damage = 0.0; + return Plugin_Handled; + } + if(attacker > MaxClients || attacker <= 0) return Plugin_Continue; + if(victim == EntRefToEntIndex(hatData[attacker].entity) || attacker == EntRefToEntIndex(hatData[victim].entity)) { + damage = 0.0; + return Plugin_Handled; + } + return Plugin_Continue; +} + +void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0) { + if(!HasHat(client) && !IsFakeClient(client)) { + hatPresetCookie.Get(client, ActivePreset[client], 32); + if(ActivePreset[client][0] != '\0') { + RestoreActivePreset(client); + ReplyToCommand(client, "[Hats] Applied your hat preset! Clear it with /hatp"); + } + } + } +} + +public void OnClientDisconnect(int client) { + tempGod[client] = false; + if(hatData[client].yeetGroundTimer != null) + delete hatData[client].yeetGroundTimer; + ClearHat(client, true); +} + +public void OnEntityDestroyed(int entity) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { + if(hatData[i].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + PrintHintText(i, "Hat has vanished"); + ClientCommand(i, "play ui/menu_back.wav"); + break; + } + } + } +} +public void OnMapStart() { + CreateTimer(30.0, Timer_RemountHats, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + for(int i = 1; i <= MaxClients; i++) { + cmdThrottle[i] = 0.0; + tempGod[i] = false; + } + GetCurrentMap(g_currentMap, sizeof(g_currentMap)); + NavAreas = GetSpawnLocations(); +} + + +public void OnMapEnd() { + delete NavAreas; + ClearHats(); +} +public void OnPluginEnd() { + ClearHats(); + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + int flags = GetEntityFlags(i) & ~FL_FROZEN; + SetEntityFlags(i, flags); + } + } +} + +public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { + if(EntRefToEntIndex(hatData[data].entity) == entity) { + return false; + } + return entity != data; +} + +int GetLookingEntity(int client, TraceEntityFilter filter) { + static float pos[3], ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); + if(TR_DidHit()) { + return TR_GetEntityIndex(); + } + return -1; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +stock bool Filter_OnlyPlayers(int entity, int mask, int data) { + return entity > 0 && entity <= MaxClients && entity != data; +} + +stock bool Filter_NoPlayers(int entity, int mask, int data) { + return entity > MaxClients && entity != data; +} + +stock bool Filter_IgnorePlayerAndWall(int entity, int mask, int data) { + if(entity > MaxClients && entity != data && EntRefToEntIndex(hatData[data].entity) != entity) { + static char classname[16]; + GetEntityClassname(entity, classname, sizeof(classname)); + // Ignore infected + return !StrEqual(classname, "infected"); + } + return false; +} + + +bool Filter_ValidHats(int entity, int mask, int data) { + if(entity == data) return false; + if(entity <= MaxClients && entity > 0) { + int client = GetRealClient(data); + if(client == -1) client = data; + return CanTarget(client); // Don't target if player targetting off + } + return CheckBlacklist(entity); +} + +bool CheckBlacklist(int entity) { + if(entity == 0) return false; + if(cvar_sm_hats_blacklist_enabled.BoolValue) { + static char buffer[64]; + GetEntityClassname(entity, buffer, sizeof(buffer)); + for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) { + if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) { + return false; + } + } + if(StrContains(buffer, "prop_") > -1) { + GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) { + if(StrEqual(FORBIDDEN_MODELS[i], buffer)) { + return false; + } + } + } + GetEntPropString(entity, Prop_Data, "m_iName", buffer, sizeof(buffer)); + if(StrEqual(buffer, "l4d2_randomizer")) { + return false; + } + } + return true; +} + +//////////////////////////////// + +stock void TriggerInput(const char[] targetName, const char[] input) { + int entity = -1; + char _targetName[32]; + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", _targetName, sizeof(_targetName)); + if(StrEqual(_targetName, targetName)) { + AcceptEntityInput(entity, input); + } + } +} + + +stock bool FindGround(const float start[3], float end[3]) { + float angle[3]; + angle[0] = 90.0; + + Handle trace = TR_TraceRayEx(start, angle, MASK_SHOT, RayType_Infinite); + if(!TR_DidHit(trace)) { + delete trace; + return false; + } + TR_GetEndPosition(end, trace); + delete trace; + return true; +} + +stock bool L4D_IsPlayerCapped(int client) { + if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 || + GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) + return true; + return false; +} +stock void LookAtPoint(int entity, const float destination[3]){ + float angles[3], pos[3], result[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); + MakeVectorFromPoints(destination, pos, result); + GetVectorAngles(result, angles); + if(angles[0] >= 270){ + angles[0] -= 270; + angles[0] = (90-angles[0]); + } else { + if(angles[0] <= 90){ + angles[0] *= -1; + } + } + angles[1] -= 180; + TeleportEntity(entity, NULL_VECTOR, angles, NULL_VECTOR); } \ No newline at end of file diff --git a/scripting/l4d2_randomizer.sp b/scripting/l4d2_randomizer.sp index ee1740b..49da6b3 100644 --- a/scripting/l4d2_randomizer.sp +++ b/scripting/l4d2_randomizer.sp @@ -1,1206 +1,1206 @@ -#pragma semicolon 1 -#pragma newdecls required - -//#define DEBUG - -#define PLUGIN_VERSION "1.0" -#define DEBUG_SCENE_PARSE 1 -#define DEBUG_BLOCKERS 1 - -#include -#include -//#include -#include -// #include -#include -#include -#include -#undef REQUIRE_PLUGIN -#include - -int g_iLaserIndex; -#if defined DEBUG_BLOCKERS -#include -#endif -#define ENT_PROP_NAME "l4d2_randomizer" -#define ENT_ENV_NAME "l4d2_randomizer" -#define ENT_BLOCKER_NAME "l4d2_randomizer" -#include - -#define MAX_SCENE_NAME_LENGTH 32 -#define MAX_INPUTS_CLASSNAME_LENGTH 64 - - -ConVar cvarEnabled; -enum struct ActiveSceneData { - char name[MAX_SCENE_NAME_LENGTH]; - int variantIndex; -} -MapData g_MapData; -BuilderData g_builder; -char currentMap[64]; - -enum struct BuilderData { - JSONObject mapData; - - JSONObject selectedSceneData; - char selectedSceneId[64]; - - JSONObject selectedVariantData; - int selectedVariantIndex; - - void Cleanup() { - this.selectedSceneData = null; - this.selectedVariantData = null; - this.selectedVariantIndex = -1; - this.selectedSceneId[0] = '\0'; - if(this.mapData != null) - delete this.mapData; - // JSONcleanup_and_delete(this.mapData); - } - - bool SelectScene(const char[] group) { - if(!g_builder.mapData.HasKey(group)) return false; - this.selectedSceneData = view_as(g_builder.mapData.Get(group)); - strcopy(this.selectedSceneId, sizeof(this.selectedSceneId), group); - return true; - } - - /** - * Select a variant, enter -1 to not select any (scene's entities) - */ - bool SelectVariant(int index = -1) { - if(this.selectedSceneData == null) LogError("SelectVariant called, but no group selected"); - JSONArray variants = view_as(this.selectedSceneData.Get("variants")); - if(index >= variants.Length) return false; - else if(index < -1) return false; - else if(index > -1) { - this.selectedVariantData = view_as(variants.Get(index)); - } else { - this.selectedVariantData = null; - } - this.selectedVariantIndex = index; - return true; - } - - void AddEntity(int entity, ExportType exportType = Export_Model) { - JSONArray entities; - if(g_builder.selectedVariantData == null) { - // Create .entities if doesn't exist: - if(!g_builder.selectedSceneData.HasKey("entities")) { - g_builder.selectedSceneData.Set("entities", new JSONArray()); - } - entities = view_as(g_builder.selectedSceneData.Get("entities")); - } else { - entities = view_as(g_builder.selectedVariantData.Get("entities")); - } - JSONObject entityData = ExportEntity(entity, Export_Model); - entities.Push(entityData); - } -} - -#include - -public Plugin myinfo = -{ - name = "L4D2 Randomizer", - author = "jackzmc", - description = "", - version = PLUGIN_VERSION, - url = "https://github.com/Jackzmc/sourcemod-plugins" -}; - -public void OnPluginStart() { - EngineVersion g_Game = GetEngineVersion(); - if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { - SetFailState("This plugin is for L4D/L4D2 only."); - } - - RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS); - RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC); - RegAdminCmd("sm_rbuild", Command_RandomizerBuild, ADMFLAG_CHEATS); - - cvarEnabled = CreateConVar("sm_randomizer_enabled", "0"); - - g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData)); -} - - -// TODO: on round start -public void OnMapStart() { - g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); - GetCurrentMap(currentMap, sizeof(currentMap)); - if(cvarEnabled.BoolValue) - CreateTimer(5.0, Timer_Run); -} - -public void OnMapEnd() { - g_builder.Cleanup(); - Cleanup(); -} - -public void OnMapInit(const char[] map) { - // if(cvarEnabled.BoolValue) { - // if(LoadMapData(currentMap, FLAG_NONE) && g_MapData.lumpEdits.Length > 0) { - // Log("Found %d lump edits, running...", g_MapData.lumpEdits.Length); - // LumpEditData lump; - // for(int i = 0; i < g_MapData.lumpEdits.Length; i++) { - // g_MapData.lumpEdits.GetArray(i, lump); - // lump.Trigger(); - // } - // hasRan = true; - // } - // } -} - -public void OnConfigsExecuted() { - -} - -Action Timer_Run(Handle h) { - if(cvarEnabled.BoolValue) - RunMap(currentMap, FLAG_NONE); - return Plugin_Handled; -} - -stock int GetLookingEntity(int client, TraceEntityFilter filter) { - float pos[3], ang[3]; - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); - if(TR_DidHit()) { - return TR_GetEntityIndex(); - } - return -1; -} - -stock int GetLookingPosition(int client, TraceEntityFilter filter, float pos[3]) { - float ang[3]; - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); - if(TR_DidHit()) { - TR_GetEndPosition(pos); - return TR_GetEntityIndex(); - } - return -1; -} - - -public Action Command_CycleRandom(int client, int args) { - if(args > 0) { - DeleteCustomEnts(); - - int flags = GetCmdArgInt(1) | view_as(FLAG_REFRESH); - RunMap(currentMap, flags); - if(client > 0) - PrintCenterText(client, "Cycled flags=%d", flags); - } else { - ReplyToCommand(client, "Active Scenes:"); - ActiveSceneData scene; - for(int i = 0; i < g_MapData.activeScenes.Length; i++) { - g_MapData.activeScenes.GetArray(i, scene); - ReplyToCommand(client, "\t%s: variant #%d", scene.name, scene.variantIndex); - } - } - return Plugin_Handled; -} - -Action Command_ExportEnt(int client, int args) { - float origin[3]; - int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); - float angles[3]; - float size[3]; - char arg1[32]; - GetCmdArg(1, arg1, sizeof(arg1)); - if(entity > 0) { - - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); - - char model[64]; - ReplyToCommand(client, "{"); - GetEntityClassname(entity, model, sizeof(model)); - if(StrContains(model, "prop_") == -1) { - ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); - } - if(StrEqual(arg1, "hammerid")) { - int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); - ReplyToCommand(client, "\t\"type\": \"hammerid\","); - ReplyToCommand(client, "\t\"model\": \"%d\",", hammerid); - } else if(StrEqual(arg1, "targetname")) { - GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); - ReplyToCommand(client, "\t\"type\": \"targetname\","); - ReplyToCommand(client, "\t\"model\": \"%s\",", model); - } else { - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - ReplyToCommand(client, "\t\"model\": \"%s\",", model); - } - ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); - ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); - ReplyToCommand(client, "}"); - } else { - if(!StrEqual(arg1, "cursor")) - GetEntPropVector(client, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(client, Prop_Send, "m_angRotation", angles); - ReplyToCommand(client, "{"); - ReplyToCommand(client, "\t\"type\": \"%s\",", arg1); - ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); - ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); - ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); - ReplyToCommand(client, "}"); - } - return Plugin_Handled; -} -Action Command_RandomizerBuild(int client, int args) { - char arg[64]; - GetCmdArg(1, arg, sizeof(arg)); - if(StrEqual(arg, "new")) { - JSONObject temp = LoadMapJson(currentMap); - GetCmdArg(2, arg, sizeof(arg)); - if(temp != null && !StrEqual(arg, "confirm")) { - delete temp; - ReplyToCommand(client, "Existing map data found, enter /rbuild new confirm to overwrite."); - return Plugin_Handled; - } - g_builder.Cleanup(); - g_builder.mapData = new JSONObject(); - SaveMapJson(currentMap, g_builder.mapData); - ReplyToCommand(client, "Started new map data for %s", currentMap); - } else if(StrEqual(arg, "load")) { - if(args >= 2) { - GetCmdArg(2, arg, sizeof(arg)); - } else { - strcopy(arg, sizeof(arg), currentMap); - } - g_builder.Cleanup(); - g_builder.mapData = LoadMapJson(arg); - if(g_builder.mapData != null) { - ReplyToCommand(client, "Loaded map data for %s", arg); - } else { - ReplyToCommand(client, "No map data found for %s", arg); - } - } else if(StrEqual(arg, "menu")) { - OpenMainMenu(client); - } else if(g_builder.mapData == null) { - ReplyToCommand(client, "No map data for %s, either load with /rbuild load, or start new /rbuild new", currentMap); - return Plugin_Handled; - } else if(StrEqual(arg, "save")) { - SaveMapJson(currentMap, g_builder.mapData); - ReplyToCommand(client, "Saved %s", currentMap); - } else if(StrEqual(arg, "scenes")) { - Command_RandomizerBuild_Scenes(client, args); - } else if(StrEqual(arg, "sel") || StrEqual(arg, "selector")) { - if(g_builder.selectedVariantData == null) { - ReplyToCommand(client, "Please load map data, select a scene and a variant."); - return Plugin_Handled; - } - StartSelector(client, OnSelectorDone); - } else if(StrEqual(arg, "spawner")) { - if(g_builder.selectedVariantData == null) { - ReplyToCommand(client, "Please load map data, select a scene and a variant."); - return Plugin_Handled; - } - StartSpawner(client, OnSpawnerDone); - ReplyToCommand(client, "Spawn props to add to variant"); - } else if(StrEqual(arg, "cursor")) { - if(g_builder.selectedVariantData == null) { - ReplyToCommand(client, "Please load map data, select a scene and a variant."); - return Plugin_Handled; - } - float origin[3]; - char arg1[32]; - int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); - GetCmdArg(2, arg1, sizeof(arg1)); - ExportType exportType = Export_Model; - if(StrEqual(arg1, "hammerid")) { - exportType = Export_HammerId; - } else if(StrEqual(arg1, "targetname")) { - exportType = Export_TargetName; - } - if(entity > 0) { - g_builder.AddEntity(entity, exportType); - ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); - } else { - ReplyToCommand(client, "No entity found"); - } - } else if(StrEqual(arg, "entityid")) { - char arg1[32]; - int entity = GetCmdArgInt(2); - GetCmdArg(3, arg1, sizeof(arg)); - ExportType exportType = Export_Model; - if(StrEqual(arg1, "hammerid")) { - exportType = Export_HammerId; - } else if(StrEqual(arg1, "targetname")) { - exportType = Export_TargetName; - } - if(entity > 0) { - g_builder.AddEntity(entity, exportType); - ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); - } else { - ReplyToCommand(client, "No entity found"); - } - } else { - ReplyToCommand(client, "Unknown arg. Try: new, load, save, scenes, cursor"); - } - return Plugin_Handled; -} - -enum ExportType { - Export_HammerId, - Export_TargetName, - Export_Model -} -JSONObject ExportEntity(int entity, ExportType exportType = Export_Model) { - float origin[3], angles[3], size[3]; - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); - - char model[64]; - JSONObject entityData = new JSONObject(); - GetEntityClassname(entity, model, sizeof(model)); - if(StrContains(model, "prop_") == -1) { - entityData.Set("scale", VecToArray(size)); - } - if(exportType == Export_HammerId) { - int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); - entityData.SetString("type", "hammerid"); - char id[16]; - IntToString(hammerid, id, sizeof(id)); - entityData.SetString("model", id); - } else if(exportType == Export_TargetName) { - GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); - entityData.SetString("type", "targetname"); - entityData.SetString("model", model); - } else { - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - entityData.SetString("model", model); - } - entityData.Set("origin", VecToArray(origin)); - entityData.Set("angles", VecToArray(angles)); - return entityData; -} - -bool OnSpawnerDone(int client, int entity, CompleteType result) { - PrintToServer("Randomizer OnSpawnerDone"); - if(result == Complete_PropSpawned && entity > 0) { - JSONObject entityData = ExportEntity(entity, Export_Model); - JSONArray entities = view_as(g_builder.selectedVariantData.Get("entities")); - entities.Push(entityData); - ReplyToCommand(client, "Added entity to variant"); - RemoveEntity(entity); - } - return result == Complete_PropSpawned; -} -void OnSelectorDone(int client, ArrayList entities) { - JSONArray entArray = view_as(g_builder.selectedVariantData.Get("entities")); - if(entities != null) { - JSONObject entityData; - for(int i = 0; i < entities.Length; i++) { - int ref = entities.Get(i); - entityData = ExportEntity(ref, Export_Model); - entArray.Push(entityData); - delete entityData; //? - RemoveEntity(ref); - } - PrintToChat(client, "Added %d entities to variant", entities.Length); - delete entities; - } -} - -JSONArray VecToArray(float vec[3]) { - JSONArray arr = new JSONArray(); - arr.PushFloat(vec[0]); - arr.PushFloat(vec[1]); - arr.PushFloat(vec[2]); - return arr; -} - -void Command_RandomizerBuild_Scenes(int client, int args) { - char arg[16]; - GetCmdArg(2, arg, sizeof(arg)); - if(StrEqual(arg, "new")) { - if(args < 4) { - ReplyToCommand(client, "Syntax: /rbuild scenes new "); - } else { - char name[64]; - GetCmdArg(3, name, sizeof(name)); - GetCmdArg(4, arg, sizeof(arg)); - float chance = StringToFloat(arg); - JSONObject scene = new JSONObject(); - scene.SetFloat("chance", chance); - scene.Set("variants", new JSONArray()); - g_builder.mapData.Set(name, scene); - g_builder.SelectScene(name); - JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); - - JSONObject variantObj = new JSONObject(); - variantObj.SetInt("weight", 1); - variantObj.Set("entities", new JSONArray()); - variants.Push(variantObj); - g_builder.SelectVariant(0); - ReplyToCommand(client, "Created & selected scene & variant %s#0", name); - StartSelector(client, OnSelectorDone); - } - } else if(StrEqual(arg, "select") || StrEqual(arg, "load") || StrEqual(arg, "choose")) { - GetCmdArg(3, arg, sizeof(arg)); - if(g_builder.SelectScene(arg)) { - int variantIndex; - if(GetCmdArgIntEx(4, variantIndex)) { - if(g_builder.SelectVariant(variantIndex)) { - ReplyToCommand(client, "Selected scene: %s#%d", arg, variantIndex); - } else { - ReplyToCommand(client, "Unknown variant for scene"); - } - } else { - ReplyToCommand(client, "Selected scene: %s", arg); - } - } else { - ReplyToCommand(client, "No scene found"); - } - } else if(StrEqual(arg, "variants")) { - Command_RandomizerBuild_Variants(client, args); - } else if(args > 1) { - ReplyToCommand(client, "Unknown argument, try: new, select, variants"); - } else { - ReplyToCommand(client, "Scenes:"); - JSONObjectKeys iterator = g_builder.mapData.Keys(); - while(iterator.ReadKey(arg, sizeof(arg))) { - if(StrEqual(arg, g_builder.selectedSceneId)) { - ReplyToCommand(client, "\t%s (selected)", arg); - } else { - ReplyToCommand(client, "\t%s", arg); - } - } - } -} - -void Command_RandomizerBuild_Variants(int client, int args) { - if(g_builder.selectedSceneId[0] == '\0') { - ReplyToCommand(client, "No scene selected, select with /rbuild groups select "); - return; - } - char arg[16]; - GetCmdArg(3, arg, sizeof(arg)); - if(StrEqual(arg, "new")) { - // /rbuild group variants new [weight] - int weight; - if(!GetCmdArgIntEx(4, weight)) { - weight = 1; - } - JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); - JSONObject variantObj = new JSONObject(); - variantObj.SetInt("weight", weight); - variantObj.Set("entities", new JSONArray()); - int index = variants.Push(variantObj); - g_builder.SelectVariant(index); - ReplyToCommand(client, "Created variant #%d", index); - } else if(StrEqual(arg, "select")) { - int index = GetCmdArgInt(4); - if(g_builder.SelectVariant(index)) { - ReplyToCommand(client, "Selected variant: %s#%d", g_builder.selectedSceneId, index); - } else { - ReplyToCommand(client, "No variant found"); - } - } else { - ReplyToCommand(client, "Variants:"); - JSONObject variantObj; - JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); - for(int i = 0; i < variants.Length; i++) { - variantObj = view_as(variants.Get(i)); - int weight = 1; - if(variantObj.HasKey("weight")) - weight = variantObj.GetInt("weight"); - JSONArray entities = view_as(variantObj.Get("entities")); - ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length); - } - } -} - - -enum struct SceneData { - char name[MAX_SCENE_NAME_LENGTH]; - float chance; - char group[MAX_SCENE_NAME_LENGTH]; - ArrayList variants; - - void Cleanup() { - g_MapData.activeScenes.Clear(); - SceneVariantData choice; - for(int i = 0; i < this.variants.Length; i++) { - this.variants.GetArray(i, choice); - choice.Cleanup(); - } - delete this.variants; - } -} - -enum struct SceneVariantData { - int weight; - ArrayList inputsList; - ArrayList entities; - ArrayList forcedScenes; - - void Cleanup() { - delete this.inputsList; - delete this.entities; - delete this.forcedScenes; - } -} - -enum struct VariantEntityData { - char type[32]; - char model[64]; - float origin[3]; - float angles[3]; - float scale[3]; - int color[4]; -} - -enum InputType { - Input_Classname, - Input_Targetname, - Input_HammerId -} -enum struct VariantInputData { - char name[MAX_INPUTS_CLASSNAME_LENGTH]; - InputType type; - char input[32]; - - void Trigger() { - int entity = -1; - switch(this.type) { - case Input_Classname: { - entity = FindEntityByClassname(entity, this.name); - this._trigger(entity); - } - case Input_Targetname: { - char targetname[32]; - while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { - GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); - if(StrEqual(targetname, this.name)) { - this._trigger(entity); - } - } - } - case Input_HammerId: { - int targetId = StringToInt(this.name); - while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { - int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID"); - if(hammerId == targetId ) { - this._trigger(entity); - break; - } - } - } - } - } - - void _trigger(int entity) { - if(entity > 0 && IsValidEntity(entity)) { - if(StrEqual(this.input, "_allow_ladder")) { - if(HasEntProp(entity, Prop_Send, "m_iTeamNum")) { - SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); - } else { - Log("Warn: Entity (%d) with id \"%s\" has no teamnum for \"_allow_ladder\"", entity, this.name); - } - } else if(StrEqual(this.input, "_lock")) { - AcceptEntityInput(entity, "Close"); - AcceptEntityInput(entity, "Lock"); - } else if(StrEqual(this.input, "_lock_nobreak")) { - AcceptEntityInput(entity, "Close"); - AcceptEntityInput(entity, "Lock"); - AcceptEntityInput(entity, "SetUnbreakable"); - }else { - char cmd[32]; - // Split input "a b" to a with variant "b" - int len = SplitString(this.input, " ", cmd, sizeof(cmd)); - if(len > -1) SetVariantString(this.input[len]); - - Debug("_trigger(%d): %s (v=%s)", entity, this.input, cmd); - AcceptEntityInput(entity, this.input); - } - } - } -} - -enum struct LumpEditData { - char name[MAX_INPUTS_CLASSNAME_LENGTH]; - InputType type; - char action[32]; - char value[64]; - - int _findLumpIndex(int startIndex = 0, EntityLumpEntry entry) { - int length = EntityLump.Length(); - char val[64]; - Debug("Scanning for \"%s\" (type=%d)", this.name, this.type); - for(int i = startIndex; i < length; i++) { - entry = EntityLump.Get(i); - int index = entry.FindKey("hammerid"); - if(index != -1) { - entry.Get(index, "", 0, val, sizeof(val)); - if(StrEqual(val, this.name)) { - return i; - } - } - - index = entry.FindKey("classname"); - if(index != -1) { - entry.Get(index, "", 0, val, sizeof(val)); - Debug("%s vs %s", val, this.name); - if(StrEqual(val, this.name)) { - return i; - } - } - - index = entry.FindKey("targetname"); - if(index != -1) { - entry.Get(index, "", 0, val, sizeof(val)); - if(StrEqual(val, this.name)) { - return i; - } - } - delete entry; - } - Log("Warn: Could not find any matching lump for \"%s\" (type=%d)", this.name, this.type); - return -1; - } - - void Trigger() { - int index = 0; - EntityLumpEntry entry; - while((index = this._findLumpIndex(index, entry) != -1)) { - // for(int i = 0; i < entry.Length; i++) { - // entry.Get(i, a, sizeof(a), v, sizeof(v)); - // Debug("%s=%s", a, v); - // } - this._trigger(entry); - } - } - - void _updateKey(EntityLumpEntry entry, const char[] key, const char[] value) { - int index = entry.FindKey(key); - if(index != -1) { - Debug("update key %s = %s", key, value); - entry.Update(index, key, value); - } - } - - void _trigger(EntityLumpEntry entry) { - if(StrEqual(this.action, "setclassname")) { - this._updateKey(entry, "classname", this.value); - } - - delete entry; - } -} - -enum struct MapData { - StringMap scenesKv; - ArrayList scenes; - ArrayList lumpEdits; - ArrayList activeScenes; -} - -enum loadFlags { - FLAG_NONE = 0, - FLAG_ALL_SCENES = 1, // Pick all scenes, no random chance - FLAG_ALL_VARIANTS = 2, // Pick all variants (for debug purposes), - FLAG_REFRESH = 4, // Load data bypassing cache - FLAG_FORCE_ACTIVE = 8 // Similar to ALL_SCENES, bypasses % chance -} - -// Reads (mapname).json file and parses it -public JSONObject LoadMapJson(const char[] map) { - Debug("Loading config for %s", map); - char filePath[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); - if(!FileExists(filePath)) { - Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map); - return null; - } - - JSONObject data = JSONObject.FromFile(filePath); - if(data == null) { - LogError("Could not parse map config file (data/randomizer/%s.json)", map); - return null; - } - return data; -} -public void SaveMapJson(const char[] map, JSONObject json) { - Debug("Saving config for %s", map); - char filePath[PLATFORM_MAX_PATH], filePathTemp[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, filePathTemp, sizeof(filePath), "data/randomizer/%s.json.tmp", map); - BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); - - json.ToFile(filePathTemp, JSON_INDENT(4)); - RenameFile(filePath, filePathTemp); - SetFilePermissions(filePath, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ); -} - -public bool LoadMapData(const char[] map, int flags) { - JSONObject data = LoadMapJson(map); - if(data == null) { - return false; - } - - Debug("Starting parsing json data"); - - Cleanup(); - g_MapData.scenes = new ArrayList(sizeof(SceneData)); - g_MapData.scenesKv = new StringMap(); - g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData)); - g_MapData.activeScenes.Clear(); - - Profiler profiler = new Profiler(); - profiler.Start(); - - JSONObjectKeys iterator = data.Keys(); - char key[32]; - while(iterator.ReadKey(key, sizeof(key))) { - if(key[0] == '_') { - if(StrEqual(key, "_lumps")) { - JSONArray lumpsList = view_as(data.Get(key)); - if(lumpsList != null) { - for(int l = 0; l < lumpsList.Length; l++) { - loadLumpData(g_MapData.lumpEdits, view_as(lumpsList.Get(l))); - } - } - } else { - Debug("Unknown special entry \"%s\", skipping", key); - } - } else { - // if(data.GetType(key) != JSONType_Object) { - // Debug("Invalid normal entry \"%s\" (not an object), skipping", key); - // continue; - // } - JSONObject scene = view_as(data.Get(key)); - // Parses scene data and inserts to scenes - loadScene(key, scene); - } - } - - delete data; - profiler.Stop(); - Log("Parsed map file for %s(%d) and found %d scenes in %.4f seconds", map, flags, g_MapData.scenes.Length, profiler.Time); - delete profiler; - return true; -} - -// Calls LoadMapData (read&parse (mapname).json) then select scenes -public bool RunMap(const char[] map, int flags) { - if(g_MapData.scenes == null || flags & view_as(FLAG_REFRESH)) { - if(!LoadMapData(map, flags)) { - return false; - } - } - Profiler profiler = new Profiler(); - - profiler.Start(); - selectScenes(flags); - profiler.Stop(); - - Log("Done processing in %.4f seconds", g_MapData.scenes.Length, profiler.Time); - return true; -} - -void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) { - SceneData scene; - scene.name = key; - scene.chance = sceneData.GetFloat("chance"); - if(scene.chance < 0.0 || scene.chance > 1.0) { - LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance); - return; - } - // TODO: load "entities", merge with choice.entities - sceneData.GetString("group", scene.group, sizeof(scene.group)); - scene.variants = new ArrayList(sizeof(SceneVariantData)); - if(!sceneData.HasKey("variants")) { - ThrowError("Failed to load: Scene \"%s\" has missing \"variants\" array", scene.name); - return; - } - JSONArray entities; - if(sceneData.HasKey("entities")) { - entities = view_as(sceneData.Get("entities")); - } - - JSONArray variants = view_as(sceneData.Get("variants")); - for(int i = 0; i < variants.Length; i++) { - // Parses choice and loads to scene.choices - loadChoice(scene, view_as(variants.Get(i)), entities); - } - g_MapData.scenes.PushArray(scene); - g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene)); -} - -void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) { - SceneVariantData choice; - choice.weight = 1; - if(choiceData.HasKey("weight")) - choice.weight = choiceData.GetInt("weight"); - choice.entities = new ArrayList(sizeof(VariantEntityData)); - choice.inputsList = new ArrayList(sizeof(VariantInputData)); - choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); - // Load in any variant-based entities - if(choiceData.HasKey("entities")) { - JSONArray entities = view_as(choiceData.Get("entities")); - for(int i = 0; i < entities.Length; i++) { - // Parses entities and loads to choice.entities - loadChoiceEntity(choice.entities, view_as(entities.Get(i))); - } - delete entities; - } - // Load in any entities that the scene has - if(extraEntities != null) { - for(int i = 0; i < extraEntities.Length; i++) { - // Parses entities and loads to choice.entities - loadChoiceEntity(choice.entities, view_as(extraEntities.Get(i))); - } - delete extraEntities; - } - // Load all inputs - if(choiceData.HasKey("inputs")) { - JSONArray inputsList = view_as(choiceData.Get("inputs")); - for(int i = 0; i < inputsList.Length; i++) { - loadChoiceInput(choice.inputsList, view_as(inputsList.Get(i))); - } - delete inputsList; - } - if(choiceData.HasKey("force_scenes")) { - JSONArray scenes = view_as(choiceData.Get("force_scenes")); - char sceneId[32]; - for(int i = 0; i < scenes.Length; i++) { - scenes.GetString(i, sceneId, sizeof(sceneId)); - choice.forcedScenes.PushString(sceneId); - } - delete scenes; - } - scene.variants.PushArray(choice); -} - -void loadChoiceInput(ArrayList list, JSONObject inputData) { - VariantInputData input; - // Check classname -> targetname -> hammerid - if(!inputData.GetString("classname", input.name, sizeof(input.name))) { - if(inputData.GetString("targetname", input.name, sizeof(input.name))) { - input.type = Input_Targetname; - } else { - if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { - input.type = Input_HammerId; - } else { - int id = inputData.GetInt("hammerid"); - if(id > 0) { - input.type = Input_HammerId; - IntToString(id, input.name, sizeof(input.name)); - } else { - LogError("Missing valid input specification (hammerid, classname, targetname)"); - return; - } - } - } - } - inputData.GetString("input", input.input, sizeof(input.input)); - list.PushArray(input); -} - -void loadLumpData(ArrayList list, JSONObject inputData) { - LumpEditData input; - // Check classname -> targetname -> hammerid - if(!inputData.GetString("classname", input.name, sizeof(input.name))) { - if(inputData.GetString("targetname", input.name, sizeof(input.name))) { - input.type = Input_Targetname; - } else { - if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { - input.type = Input_HammerId; - } else { - int id = inputData.GetInt("hammerid"); - if(id > 0) { - input.type = Input_HammerId; - IntToString(id, input.name, sizeof(input.name)); - } else { - LogError("Missing valid input specification (hammerid, classname, targetname)"); - return; - } - } - } - } - inputData.GetString("action", input.action, sizeof(input.action)); - inputData.GetString("value", input.value, sizeof(input.value)); - list.PushArray(input); -} - -void loadChoiceEntity(ArrayList list, JSONObject entityData) { - VariantEntityData entity; - entityData.GetString("model", entity.model, sizeof(entity.model)); - if(!entityData.GetString("type", entity.type, sizeof(entity.type))) { - entity.type = "prop_dynamic"; - } else if(entity.type[0] == '_') { - LogError("Invalid custom entity type \"%s\"", entity.type); - return; - } - GetVector(entityData, "origin", entity.origin); - GetVector(entityData, "angles", entity.angles); - GetVector(entityData, "scale", entity.scale); - GetColor(entityData, "color", entity.color); - list.PushArray(entity); -} - -bool GetVector(JSONObject obj, const char[] key, float out[3]) { - if(!obj.HasKey(key)) return false; - JSONArray vecArray = view_as(obj.Get(key)); - if(vecArray != null) { - out[0] = vecArray.GetFloat(0); - out[1] = vecArray.GetFloat(1); - out[2] = vecArray.GetFloat(2); - } - return true; -} - -void GetColor(JSONObject obj, const char[] key, int out[4], int defaultColor[4] = { 255, 255, 255, 255 }) { - if(obj.HasKey(key)) { - JSONArray vecArray = view_as(obj.Get(key)); - out[0] = vecArray.GetInt(0); - out[1] = vecArray.GetInt(1); - out[2] = vecArray.GetInt(2); - if(vecArray.Length == 4) - out[3] = vecArray.GetInt(3); - else - out[3] = 255; - } else { - out = defaultColor; - } -} - -void selectScenes(int flags = 0) { - SceneData scene; - StringMap groups = new StringMap(); - ArrayList list; - // Select and spawn non-group scenes - // TODO: refactor to use .scenesKv - for(int i = 0; i < g_MapData.scenes.Length; i++) { - g_MapData.scenes.GetArray(i, scene); - // TODO: Exclusions - // Select scene if not in group, or add to list of groups - if(scene.group[0] == '\0') { - selectScene(scene, flags); - } else { - // Load it into group list - if(!groups.GetValue(scene.group, list)) { - list = new ArrayList(); - } - list.Push(i); - groups.SetValue(scene.group, list); - } - } - - // Iterate through groups and select a random scene: - StringMapSnapshot snapshot = groups.Snapshot(); - char key[MAX_SCENE_NAME_LENGTH]; - for(int i = 0; i < snapshot.Length; i++) { - snapshot.GetKey(i, key, sizeof(key)); - groups.GetValue(key, list); - // Select a random scene from the group: - int index = GetURandomInt() % list.Length; - index = list.Get(index); - g_MapData.scenes.GetArray(index, scene); - - Debug("Selected scene \"%s\" for group %s (%d members)", scene.name, key, list.Length); - selectScene(scene, flags); - delete list; - } - // Traverse active scenes, loading any other scene it requires (via .force_scenes) - ActiveSceneData aScene; - SceneVariantData choice; - ArrayList forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); - for(int i = 0; i < g_MapData.activeScenes.Length; i++) { - g_MapData.activeScenes.GetArray(i, aScene); - g_MapData.scenes.GetArray(i, scene); - scene.variants.GetArray(aScene.variantIndex, choice); - if(choice.forcedScenes != null) { - for(int j = 0; j < choice.forcedScenes.Length; j++) { - choice.forcedScenes.GetString(j, key, sizeof(key)); - forcedScenes.PushString(key); - } - } - } - // Iterate and activate any forced scenes - for(int i = 0; i < forcedScenes.Length; i++) { - forcedScenes.GetString(i, key, sizeof(key)); - // Check if scene was already loaded - bool isSceneAlreadyLoaded = false; - for(int j = 0; j < g_MapData.activeScenes.Length; i++) { - g_MapData.activeScenes.GetArray(j, aScene); - if(StrEqual(aScene.name, key)) { - isSceneAlreadyLoaded = true; - break; - } - } - if(isSceneAlreadyLoaded) continue; - g_MapData.scenesKv.GetArray(key, scene, sizeof(scene)); - selectScene(scene, flags | view_as(FLAG_FORCE_ACTIVE)); - } - - delete forcedScenes; - delete snapshot; - delete groups; -} - -void selectScene(SceneData scene, int flags) { - // Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set - if(~flags & view_as(FLAG_ALL_SCENES) && ~flags & view_as(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) { - return; - } - - if(scene.variants.Length == 0) { - LogError("Warn: No variants were found for scene \"%s\"", scene.name); - return; - } - - ArrayList choices = new ArrayList(); - SceneVariantData choice; - int index; - Debug("Scene %s has %d variants", scene.name, scene.variants.Length); - // Weighted random: Push N times dependent on weight - for(int i = 0; i < scene.variants.Length; i++) { - scene.variants.GetArray(i, choice); - if(flags & view_as(FLAG_ALL_VARIANTS)) { - spawnVariant(choice); - } else { - if(choice.weight <= 0) { - PrintToServer("Warn: Variant %d in scene %s has invalid weight", i, scene.name); - continue; - } - for(int c = 0; c < choice.weight; c++) { - choices.Push(i); - } - } - } - Debug("Total choices: %d", choices.Length); - if(flags & view_as(FLAG_ALL_VARIANTS)) { - delete choices; - } else if(choices.Length > 0) { - index = GetURandomInt() % choices.Length; - index = choices.Get(index); - delete choices; - Log("Spawned scene \"%s\" with variant #%d", scene.name, index); - scene.variants.GetArray(index, choice); - spawnVariant(choice); - } - ActiveSceneData aScene; - strcopy(aScene.name, sizeof(aScene.name), scene.name); - aScene.variantIndex = index; - g_MapData.activeScenes.PushArray(aScene); -} - -void spawnVariant(SceneVariantData choice) { - VariantEntityData entity; - for(int i = 0; i < choice.entities.Length; i++) { - choice.entities.GetArray(i, entity); - spawnEntity(entity); - } - - if(choice.inputsList.Length > 0) { - VariantInputData input; - for(int i = 0; i < choice.inputsList.Length; i++) { - choice.inputsList.GetArray(i, input); - input.Trigger(); - } - } -} - -void spawnEntity(VariantEntityData entity) { - if(StrEqual(entity.type, "env_fire")) { - Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - CreateFire(entity.origin, 20.0, 100.0, 0.0); - } else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) { - CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale); - } else if(StrEqual(entity.type, "infodecal")) { - CreateDecal(entity.model, entity.origin); - } else if(StrContains(entity.type, "prop_") == 0) { - if(entity.model[0] == '\0') { - LogError("Missing model for entity with type \"%s\"", entity.type); - return; - } - PrecacheModel(entity.model); - int prop = CreateProp(entity.type, entity.model, entity.origin, entity.angles); - SetEntityRenderColor(prop, entity.color[0], entity.color[1], entity.color[2], entity.color[3]); - } else if(StrEqual(entity.type, "hammerid")) { - int targetId = StringToInt(entity.model); - if(targetId > 0) { - int ent = -1; - while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { - int hammerId = GetEntProp(ent, Prop_Data, "m_iHammerID"); - if(hammerId == targetId) { - Debug("moved entity (hammerid=%d) to %.0f %.0f %.0f rot %.0f %.0f %.0f", targetId, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); - return; - } - } - } - Debug("Warn: Could not find entity (hammerid=%d) (model=%s)", targetId, entity.model); - } else if(StrEqual(entity.type, "targetname")) { - int ent = -1; - char targetname[64]; - bool found = false; - while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { - GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); - if(StrEqual(entity.model, targetname)) { - Debug("moved entity (targetname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); - found = true; - } - } - if(!found) - Debug("Warn: Could not find entity (targetname=%s)", entity.model); - } else if(StrEqual(entity.type, "classname")) { - int ent = -1; - char classname[64]; - bool found; - while((ent = FindEntityByClassname(ent, classname)) != INVALID_ENT_REFERENCE) { - Debug("moved entity (classname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); - found = true; - } - if(!found) - Debug("Warn: Could not find entity (classname=%s)", entity.model); - } else { - LogError("Unknown entity type \"%s\"", entity.type); - } -} - -void Debug(const char[] format, any ...) { - #if defined DEBUG_SCENE_PARSE - char buffer[192]; - - VFormat(buffer, sizeof(buffer), format, 2); - - PrintToServer("[Randomizer::Debug] %s", buffer); - PrintToConsoleAll("[Randomizer::Debug] %s", buffer); - - #endif -} - -void Log(const char[] format, any ...) { - char buffer[192]; - - VFormat(buffer, sizeof(buffer), format, 2); - - PrintToServer("[Randomizer] %s", buffer); -} - -void Cleanup() { - if(g_MapData.scenes != null) { - SceneData scene; - for(int i = 0; i < g_MapData.scenes.Length; i++) { - g_MapData.scenes.GetArray(i, scene); - scene.Cleanup(); - } - delete g_MapData.scenes; - } - delete g_MapData.lumpEdits; - - DeleteCustomEnts(); - g_MapData.activeScenes.Clear(); +#pragma semicolon 1 +#pragma newdecls required + +//#define DEBUG + +#define PLUGIN_VERSION "1.0" +#define DEBUG_SCENE_PARSE 1 +#define DEBUG_BLOCKERS 1 + +#include +#include +//#include +#include +// #include +#include +#include +#include +#undef REQUIRE_PLUGIN +#include + +int g_iLaserIndex; +#if defined DEBUG_BLOCKERS +#include +#endif +#define ENT_PROP_NAME "l4d2_randomizer" +#define ENT_ENV_NAME "l4d2_randomizer" +#define ENT_BLOCKER_NAME "l4d2_randomizer" +#include + +#define MAX_SCENE_NAME_LENGTH 32 +#define MAX_INPUTS_CLASSNAME_LENGTH 64 + + +ConVar cvarEnabled; +enum struct ActiveSceneData { + char name[MAX_SCENE_NAME_LENGTH]; + int variantIndex; +} +MapData g_MapData; +BuilderData g_builder; +char currentMap[64]; + +enum struct BuilderData { + JSONObject mapData; + + JSONObject selectedSceneData; + char selectedSceneId[64]; + + JSONObject selectedVariantData; + int selectedVariantIndex; + + void Cleanup() { + this.selectedSceneData = null; + this.selectedVariantData = null; + this.selectedVariantIndex = -1; + this.selectedSceneId[0] = '\0'; + if(this.mapData != null) + delete this.mapData; + // JSONcleanup_and_delete(this.mapData); + } + + bool SelectScene(const char[] group) { + if(!g_builder.mapData.HasKey(group)) return false; + this.selectedSceneData = view_as(g_builder.mapData.Get(group)); + strcopy(this.selectedSceneId, sizeof(this.selectedSceneId), group); + return true; + } + + /** + * Select a variant, enter -1 to not select any (scene's entities) + */ + bool SelectVariant(int index = -1) { + if(this.selectedSceneData == null) LogError("SelectVariant called, but no group selected"); + JSONArray variants = view_as(this.selectedSceneData.Get("variants")); + if(index >= variants.Length) return false; + else if(index < -1) return false; + else if(index > -1) { + this.selectedVariantData = view_as(variants.Get(index)); + } else { + this.selectedVariantData = null; + } + this.selectedVariantIndex = index; + return true; + } + + void AddEntity(int entity, ExportType exportType = Export_Model) { + JSONArray entities; + if(g_builder.selectedVariantData == null) { + // Create .entities if doesn't exist: + if(!g_builder.selectedSceneData.HasKey("entities")) { + g_builder.selectedSceneData.Set("entities", new JSONArray()); + } + entities = view_as(g_builder.selectedSceneData.Get("entities")); + } else { + entities = view_as(g_builder.selectedVariantData.Get("entities")); + } + JSONObject entityData = ExportEntity(entity, Export_Model); + entities.Push(entityData); + } +} + +#include + +public Plugin myinfo = +{ + name = "L4D2 Randomizer", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D/L4D2 only."); + } + + RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS); + RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC); + RegAdminCmd("sm_rbuild", Command_RandomizerBuild, ADMFLAG_CHEATS); + + cvarEnabled = CreateConVar("sm_randomizer_enabled", "0"); + + g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData)); +} + + +// TODO: on round start +public void OnMapStart() { + g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); + GetCurrentMap(currentMap, sizeof(currentMap)); + if(cvarEnabled.BoolValue) + CreateTimer(5.0, Timer_Run); +} + +public void OnMapEnd() { + g_builder.Cleanup(); + Cleanup(); +} + +public void OnMapInit(const char[] map) { + // if(cvarEnabled.BoolValue) { + // if(LoadMapData(currentMap, FLAG_NONE) && g_MapData.lumpEdits.Length > 0) { + // Log("Found %d lump edits, running...", g_MapData.lumpEdits.Length); + // LumpEditData lump; + // for(int i = 0; i < g_MapData.lumpEdits.Length; i++) { + // g_MapData.lumpEdits.GetArray(i, lump); + // lump.Trigger(); + // } + // hasRan = true; + // } + // } +} + +public void OnConfigsExecuted() { + +} + +Action Timer_Run(Handle h) { + if(cvarEnabled.BoolValue) + RunMap(currentMap, FLAG_NONE); + return Plugin_Handled; +} + +stock int GetLookingEntity(int client, TraceEntityFilter filter) { + float pos[3], ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); + if(TR_DidHit()) { + return TR_GetEntityIndex(); + } + return -1; +} + +stock int GetLookingPosition(int client, TraceEntityFilter filter, float pos[3]) { + float ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); + if(TR_DidHit()) { + TR_GetEndPosition(pos); + return TR_GetEntityIndex(); + } + return -1; +} + + +public Action Command_CycleRandom(int client, int args) { + if(args > 0) { + DeleteCustomEnts(); + + int flags = GetCmdArgInt(1) | view_as(FLAG_REFRESH); + RunMap(currentMap, flags); + if(client > 0) + PrintCenterText(client, "Cycled flags=%d", flags); + } else { + ReplyToCommand(client, "Active Scenes:"); + ActiveSceneData scene; + for(int i = 0; i < g_MapData.activeScenes.Length; i++) { + g_MapData.activeScenes.GetArray(i, scene); + ReplyToCommand(client, "\t%s: variant #%d", scene.name, scene.variantIndex); + } + } + return Plugin_Handled; +} + +Action Command_ExportEnt(int client, int args) { + float origin[3]; + int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); + float angles[3]; + float size[3]; + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + if(entity > 0) { + + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); + + char model[64]; + ReplyToCommand(client, "{"); + GetEntityClassname(entity, model, sizeof(model)); + if(StrContains(model, "prop_") == -1) { + ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); + } + if(StrEqual(arg1, "hammerid")) { + int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); + ReplyToCommand(client, "\t\"type\": \"hammerid\","); + ReplyToCommand(client, "\t\"model\": \"%d\",", hammerid); + } else if(StrEqual(arg1, "targetname")) { + GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); + ReplyToCommand(client, "\t\"type\": \"targetname\","); + ReplyToCommand(client, "\t\"model\": \"%s\",", model); + } else { + GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + ReplyToCommand(client, "\t\"model\": \"%s\",", model); + } + ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); + ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); + ReplyToCommand(client, "}"); + } else { + if(!StrEqual(arg1, "cursor")) + GetEntPropVector(client, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(client, Prop_Send, "m_angRotation", angles); + ReplyToCommand(client, "{"); + ReplyToCommand(client, "\t\"type\": \"%s\",", arg1); + ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); + ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); + ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); + ReplyToCommand(client, "}"); + } + return Plugin_Handled; +} +Action Command_RandomizerBuild(int client, int args) { + char arg[64]; + GetCmdArg(1, arg, sizeof(arg)); + if(StrEqual(arg, "new")) { + JSONObject temp = LoadMapJson(currentMap); + GetCmdArg(2, arg, sizeof(arg)); + if(temp != null && !StrEqual(arg, "confirm")) { + delete temp; + ReplyToCommand(client, "Existing map data found, enter /rbuild new confirm to overwrite."); + return Plugin_Handled; + } + g_builder.Cleanup(); + g_builder.mapData = new JSONObject(); + SaveMapJson(currentMap, g_builder.mapData); + ReplyToCommand(client, "Started new map data for %s", currentMap); + } else if(StrEqual(arg, "load")) { + if(args >= 2) { + GetCmdArg(2, arg, sizeof(arg)); + } else { + strcopy(arg, sizeof(arg), currentMap); + } + g_builder.Cleanup(); + g_builder.mapData = LoadMapJson(arg); + if(g_builder.mapData != null) { + ReplyToCommand(client, "Loaded map data for %s", arg); + } else { + ReplyToCommand(client, "No map data found for %s", arg); + } + } else if(StrEqual(arg, "menu")) { + OpenMainMenu(client); + } else if(g_builder.mapData == null) { + ReplyToCommand(client, "No map data for %s, either load with /rbuild load, or start new /rbuild new", currentMap); + return Plugin_Handled; + } else if(StrEqual(arg, "save")) { + SaveMapJson(currentMap, g_builder.mapData); + ReplyToCommand(client, "Saved %s", currentMap); + } else if(StrEqual(arg, "scenes")) { + Command_RandomizerBuild_Scenes(client, args); + } else if(StrEqual(arg, "sel") || StrEqual(arg, "selector")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + StartSelector(client, OnSelectorDone); + } else if(StrEqual(arg, "spawner")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + StartSpawner(client, OnSpawnerDone); + ReplyToCommand(client, "Spawn props to add to variant"); + } else if(StrEqual(arg, "cursor")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + float origin[3]; + char arg1[32]; + int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); + GetCmdArg(2, arg1, sizeof(arg1)); + ExportType exportType = Export_Model; + if(StrEqual(arg1, "hammerid")) { + exportType = Export_HammerId; + } else if(StrEqual(arg1, "targetname")) { + exportType = Export_TargetName; + } + if(entity > 0) { + g_builder.AddEntity(entity, exportType); + ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); + } else { + ReplyToCommand(client, "No entity found"); + } + } else if(StrEqual(arg, "entityid")) { + char arg1[32]; + int entity = GetCmdArgInt(2); + GetCmdArg(3, arg1, sizeof(arg)); + ExportType exportType = Export_Model; + if(StrEqual(arg1, "hammerid")) { + exportType = Export_HammerId; + } else if(StrEqual(arg1, "targetname")) { + exportType = Export_TargetName; + } + if(entity > 0) { + g_builder.AddEntity(entity, exportType); + ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); + } else { + ReplyToCommand(client, "No entity found"); + } + } else { + ReplyToCommand(client, "Unknown arg. Try: new, load, save, scenes, cursor"); + } + return Plugin_Handled; +} + +enum ExportType { + Export_HammerId, + Export_TargetName, + Export_Model +} +JSONObject ExportEntity(int entity, ExportType exportType = Export_Model) { + float origin[3], angles[3], size[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); + + char model[64]; + JSONObject entityData = new JSONObject(); + GetEntityClassname(entity, model, sizeof(model)); + if(StrContains(model, "prop_") == -1) { + entityData.Set("scale", VecToArray(size)); + } + if(exportType == Export_HammerId) { + int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); + entityData.SetString("type", "hammerid"); + char id[16]; + IntToString(hammerid, id, sizeof(id)); + entityData.SetString("model", id); + } else if(exportType == Export_TargetName) { + GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); + entityData.SetString("type", "targetname"); + entityData.SetString("model", model); + } else { + GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + entityData.SetString("model", model); + } + entityData.Set("origin", VecToArray(origin)); + entityData.Set("angles", VecToArray(angles)); + return entityData; +} + +bool OnSpawnerDone(int client, int entity, CompleteType result) { + PrintToServer("Randomizer OnSpawnerDone"); + if(result == Complete_PropSpawned && entity > 0) { + JSONObject entityData = ExportEntity(entity, Export_Model); + JSONArray entities = view_as(g_builder.selectedVariantData.Get("entities")); + entities.Push(entityData); + ReplyToCommand(client, "Added entity to variant"); + RemoveEntity(entity); + } + return result == Complete_PropSpawned; +} +void OnSelectorDone(int client, ArrayList entities) { + JSONArray entArray = view_as(g_builder.selectedVariantData.Get("entities")); + if(entities != null) { + JSONObject entityData; + for(int i = 0; i < entities.Length; i++) { + int ref = entities.Get(i); + entityData = ExportEntity(ref, Export_Model); + entArray.Push(entityData); + delete entityData; //? + RemoveEntity(ref); + } + PrintToChat(client, "Added %d entities to variant", entities.Length); + delete entities; + } +} + +JSONArray VecToArray(float vec[3]) { + JSONArray arr = new JSONArray(); + arr.PushFloat(vec[0]); + arr.PushFloat(vec[1]); + arr.PushFloat(vec[2]); + return arr; +} + +void Command_RandomizerBuild_Scenes(int client, int args) { + char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + if(StrEqual(arg, "new")) { + if(args < 4) { + ReplyToCommand(client, "Syntax: /rbuild scenes new "); + } else { + char name[64]; + GetCmdArg(3, name, sizeof(name)); + GetCmdArg(4, arg, sizeof(arg)); + float chance = StringToFloat(arg); + JSONObject scene = new JSONObject(); + scene.SetFloat("chance", chance); + scene.Set("variants", new JSONArray()); + g_builder.mapData.Set(name, scene); + g_builder.SelectScene(name); + JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); + + JSONObject variantObj = new JSONObject(); + variantObj.SetInt("weight", 1); + variantObj.Set("entities", new JSONArray()); + variants.Push(variantObj); + g_builder.SelectVariant(0); + ReplyToCommand(client, "Created & selected scene & variant %s#0", name); + StartSelector(client, OnSelectorDone); + } + } else if(StrEqual(arg, "select") || StrEqual(arg, "load") || StrEqual(arg, "choose")) { + GetCmdArg(3, arg, sizeof(arg)); + if(g_builder.SelectScene(arg)) { + int variantIndex; + if(GetCmdArgIntEx(4, variantIndex)) { + if(g_builder.SelectVariant(variantIndex)) { + ReplyToCommand(client, "Selected scene: %s#%d", arg, variantIndex); + } else { + ReplyToCommand(client, "Unknown variant for scene"); + } + } else { + ReplyToCommand(client, "Selected scene: %s", arg); + } + } else { + ReplyToCommand(client, "No scene found"); + } + } else if(StrEqual(arg, "variants")) { + Command_RandomizerBuild_Variants(client, args); + } else if(args > 1) { + ReplyToCommand(client, "Unknown argument, try: new, select, variants"); + } else { + ReplyToCommand(client, "Scenes:"); + JSONObjectKeys iterator = g_builder.mapData.Keys(); + while(iterator.ReadKey(arg, sizeof(arg))) { + if(StrEqual(arg, g_builder.selectedSceneId)) { + ReplyToCommand(client, "\t%s (selected)", arg); + } else { + ReplyToCommand(client, "\t%s", arg); + } + } + } +} + +void Command_RandomizerBuild_Variants(int client, int args) { + if(g_builder.selectedSceneId[0] == '\0') { + ReplyToCommand(client, "No scene selected, select with /rbuild groups select "); + return; + } + char arg[16]; + GetCmdArg(3, arg, sizeof(arg)); + if(StrEqual(arg, "new")) { + // /rbuild group variants new [weight] + int weight; + if(!GetCmdArgIntEx(4, weight)) { + weight = 1; + } + JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); + JSONObject variantObj = new JSONObject(); + variantObj.SetInt("weight", weight); + variantObj.Set("entities", new JSONArray()); + int index = variants.Push(variantObj); + g_builder.SelectVariant(index); + ReplyToCommand(client, "Created variant #%d", index); + } else if(StrEqual(arg, "select")) { + int index = GetCmdArgInt(4); + if(g_builder.SelectVariant(index)) { + ReplyToCommand(client, "Selected variant: %s#%d", g_builder.selectedSceneId, index); + } else { + ReplyToCommand(client, "No variant found"); + } + } else { + ReplyToCommand(client, "Variants:"); + JSONObject variantObj; + JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); + for(int i = 0; i < variants.Length; i++) { + variantObj = view_as(variants.Get(i)); + int weight = 1; + if(variantObj.HasKey("weight")) + weight = variantObj.GetInt("weight"); + JSONArray entities = view_as(variantObj.Get("entities")); + ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length); + } + } +} + + +enum struct SceneData { + char name[MAX_SCENE_NAME_LENGTH]; + float chance; + char group[MAX_SCENE_NAME_LENGTH]; + ArrayList variants; + + void Cleanup() { + g_MapData.activeScenes.Clear(); + SceneVariantData choice; + for(int i = 0; i < this.variants.Length; i++) { + this.variants.GetArray(i, choice); + choice.Cleanup(); + } + delete this.variants; + } +} + +enum struct SceneVariantData { + int weight; + ArrayList inputsList; + ArrayList entities; + ArrayList forcedScenes; + + void Cleanup() { + delete this.inputsList; + delete this.entities; + delete this.forcedScenes; + } +} + +enum struct VariantEntityData { + char type[32]; + char model[64]; + float origin[3]; + float angles[3]; + float scale[3]; + int color[4]; +} + +enum InputType { + Input_Classname, + Input_Targetname, + Input_HammerId +} +enum struct VariantInputData { + char name[MAX_INPUTS_CLASSNAME_LENGTH]; + InputType type; + char input[32]; + + void Trigger() { + int entity = -1; + switch(this.type) { + case Input_Classname: { + entity = FindEntityByClassname(entity, this.name); + this._trigger(entity); + } + case Input_Targetname: { + char targetname[32]; + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrEqual(targetname, this.name)) { + this._trigger(entity); + } + } + } + case Input_HammerId: { + int targetId = StringToInt(this.name); + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID"); + if(hammerId == targetId ) { + this._trigger(entity); + break; + } + } + } + } + } + + void _trigger(int entity) { + if(entity > 0 && IsValidEntity(entity)) { + if(StrEqual(this.input, "_allow_ladder")) { + if(HasEntProp(entity, Prop_Send, "m_iTeamNum")) { + SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); + } else { + Log("Warn: Entity (%d) with id \"%s\" has no teamnum for \"_allow_ladder\"", entity, this.name); + } + } else if(StrEqual(this.input, "_lock")) { + AcceptEntityInput(entity, "Close"); + AcceptEntityInput(entity, "Lock"); + } else if(StrEqual(this.input, "_lock_nobreak")) { + AcceptEntityInput(entity, "Close"); + AcceptEntityInput(entity, "Lock"); + AcceptEntityInput(entity, "SetUnbreakable"); + }else { + char cmd[32]; + // Split input "a b" to a with variant "b" + int len = SplitString(this.input, " ", cmd, sizeof(cmd)); + if(len > -1) SetVariantString(this.input[len]); + + Debug("_trigger(%d): %s (v=%s)", entity, this.input, cmd); + AcceptEntityInput(entity, this.input); + } + } + } +} + +enum struct LumpEditData { + char name[MAX_INPUTS_CLASSNAME_LENGTH]; + InputType type; + char action[32]; + char value[64]; + + int _findLumpIndex(int startIndex = 0, EntityLumpEntry entry) { + int length = EntityLump.Length(); + char val[64]; + Debug("Scanning for \"%s\" (type=%d)", this.name, this.type); + for(int i = startIndex; i < length; i++) { + entry = EntityLump.Get(i); + int index = entry.FindKey("hammerid"); + if(index != -1) { + entry.Get(index, "", 0, val, sizeof(val)); + if(StrEqual(val, this.name)) { + return i; + } + } + + index = entry.FindKey("classname"); + if(index != -1) { + entry.Get(index, "", 0, val, sizeof(val)); + Debug("%s vs %s", val, this.name); + if(StrEqual(val, this.name)) { + return i; + } + } + + index = entry.FindKey("targetname"); + if(index != -1) { + entry.Get(index, "", 0, val, sizeof(val)); + if(StrEqual(val, this.name)) { + return i; + } + } + delete entry; + } + Log("Warn: Could not find any matching lump for \"%s\" (type=%d)", this.name, this.type); + return -1; + } + + void Trigger() { + int index = 0; + EntityLumpEntry entry; + while((index = this._findLumpIndex(index, entry) != -1)) { + // for(int i = 0; i < entry.Length; i++) { + // entry.Get(i, a, sizeof(a), v, sizeof(v)); + // Debug("%s=%s", a, v); + // } + this._trigger(entry); + } + } + + void _updateKey(EntityLumpEntry entry, const char[] key, const char[] value) { + int index = entry.FindKey(key); + if(index != -1) { + Debug("update key %s = %s", key, value); + entry.Update(index, key, value); + } + } + + void _trigger(EntityLumpEntry entry) { + if(StrEqual(this.action, "setclassname")) { + this._updateKey(entry, "classname", this.value); + } + + delete entry; + } +} + +enum struct MapData { + StringMap scenesKv; + ArrayList scenes; + ArrayList lumpEdits; + ArrayList activeScenes; +} + +enum loadFlags { + FLAG_NONE = 0, + FLAG_ALL_SCENES = 1, // Pick all scenes, no random chance + FLAG_ALL_VARIANTS = 2, // Pick all variants (for debug purposes), + FLAG_REFRESH = 4, // Load data bypassing cache + FLAG_FORCE_ACTIVE = 8 // Similar to ALL_SCENES, bypasses % chance +} + +// Reads (mapname).json file and parses it +public JSONObject LoadMapJson(const char[] map) { + Debug("Loading config for %s", map); + char filePath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); + if(!FileExists(filePath)) { + Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map); + return null; + } + + JSONObject data = JSONObject.FromFile(filePath); + if(data == null) { + LogError("Could not parse map config file (data/randomizer/%s.json)", map); + return null; + } + return data; +} +public void SaveMapJson(const char[] map, JSONObject json) { + Debug("Saving config for %s", map); + char filePath[PLATFORM_MAX_PATH], filePathTemp[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, filePathTemp, sizeof(filePath), "data/randomizer/%s.json.tmp", map); + BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); + + json.ToFile(filePathTemp, JSON_INDENT(4)); + RenameFile(filePath, filePathTemp); + SetFilePermissions(filePath, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ); +} + +public bool LoadMapData(const char[] map, int flags) { + JSONObject data = LoadMapJson(map); + if(data == null) { + return false; + } + + Debug("Starting parsing json data"); + + Cleanup(); + g_MapData.scenes = new ArrayList(sizeof(SceneData)); + g_MapData.scenesKv = new StringMap(); + g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData)); + g_MapData.activeScenes.Clear(); + + Profiler profiler = new Profiler(); + profiler.Start(); + + JSONObjectKeys iterator = data.Keys(); + char key[32]; + while(iterator.ReadKey(key, sizeof(key))) { + if(key[0] == '_') { + if(StrEqual(key, "_lumps")) { + JSONArray lumpsList = view_as(data.Get(key)); + if(lumpsList != null) { + for(int l = 0; l < lumpsList.Length; l++) { + loadLumpData(g_MapData.lumpEdits, view_as(lumpsList.Get(l))); + } + } + } else { + Debug("Unknown special entry \"%s\", skipping", key); + } + } else { + // if(data.GetType(key) != JSONType_Object) { + // Debug("Invalid normal entry \"%s\" (not an object), skipping", key); + // continue; + // } + JSONObject scene = view_as(data.Get(key)); + // Parses scene data and inserts to scenes + loadScene(key, scene); + } + } + + delete data; + profiler.Stop(); + Log("Parsed map file for %s(%d) and found %d scenes in %.4f seconds", map, flags, g_MapData.scenes.Length, profiler.Time); + delete profiler; + return true; +} + +// Calls LoadMapData (read&parse (mapname).json) then select scenes +public bool RunMap(const char[] map, int flags) { + if(g_MapData.scenes == null || flags & view_as(FLAG_REFRESH)) { + if(!LoadMapData(map, flags)) { + return false; + } + } + Profiler profiler = new Profiler(); + + profiler.Start(); + selectScenes(flags); + profiler.Stop(); + + Log("Done processing in %.4f seconds", g_MapData.scenes.Length, profiler.Time); + return true; +} + +void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) { + SceneData scene; + scene.name = key; + scene.chance = sceneData.GetFloat("chance"); + if(scene.chance < 0.0 || scene.chance > 1.0) { + LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance); + return; + } + // TODO: load "entities", merge with choice.entities + sceneData.GetString("group", scene.group, sizeof(scene.group)); + scene.variants = new ArrayList(sizeof(SceneVariantData)); + if(!sceneData.HasKey("variants")) { + ThrowError("Failed to load: Scene \"%s\" has missing \"variants\" array", scene.name); + return; + } + JSONArray entities; + if(sceneData.HasKey("entities")) { + entities = view_as(sceneData.Get("entities")); + } + + JSONArray variants = view_as(sceneData.Get("variants")); + for(int i = 0; i < variants.Length; i++) { + // Parses choice and loads to scene.choices + loadChoice(scene, view_as(variants.Get(i)), entities); + } + g_MapData.scenes.PushArray(scene); + g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene)); +} + +void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) { + SceneVariantData choice; + choice.weight = 1; + if(choiceData.HasKey("weight")) + choice.weight = choiceData.GetInt("weight"); + choice.entities = new ArrayList(sizeof(VariantEntityData)); + choice.inputsList = new ArrayList(sizeof(VariantInputData)); + choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); + // Load in any variant-based entities + if(choiceData.HasKey("entities")) { + JSONArray entities = view_as(choiceData.Get("entities")); + for(int i = 0; i < entities.Length; i++) { + // Parses entities and loads to choice.entities + loadChoiceEntity(choice.entities, view_as(entities.Get(i))); + } + delete entities; + } + // Load in any entities that the scene has + if(extraEntities != null) { + for(int i = 0; i < extraEntities.Length; i++) { + // Parses entities and loads to choice.entities + loadChoiceEntity(choice.entities, view_as(extraEntities.Get(i))); + } + delete extraEntities; + } + // Load all inputs + if(choiceData.HasKey("inputs")) { + JSONArray inputsList = view_as(choiceData.Get("inputs")); + for(int i = 0; i < inputsList.Length; i++) { + loadChoiceInput(choice.inputsList, view_as(inputsList.Get(i))); + } + delete inputsList; + } + if(choiceData.HasKey("force_scenes")) { + JSONArray scenes = view_as(choiceData.Get("force_scenes")); + char sceneId[32]; + for(int i = 0; i < scenes.Length; i++) { + scenes.GetString(i, sceneId, sizeof(sceneId)); + choice.forcedScenes.PushString(sceneId); + } + delete scenes; + } + scene.variants.PushArray(choice); +} + +void loadChoiceInput(ArrayList list, JSONObject inputData) { + VariantInputData input; + // Check classname -> targetname -> hammerid + if(!inputData.GetString("classname", input.name, sizeof(input.name))) { + if(inputData.GetString("targetname", input.name, sizeof(input.name))) { + input.type = Input_Targetname; + } else { + if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { + input.type = Input_HammerId; + } else { + int id = inputData.GetInt("hammerid"); + if(id > 0) { + input.type = Input_HammerId; + IntToString(id, input.name, sizeof(input.name)); + } else { + LogError("Missing valid input specification (hammerid, classname, targetname)"); + return; + } + } + } + } + inputData.GetString("input", input.input, sizeof(input.input)); + list.PushArray(input); +} + +void loadLumpData(ArrayList list, JSONObject inputData) { + LumpEditData input; + // Check classname -> targetname -> hammerid + if(!inputData.GetString("classname", input.name, sizeof(input.name))) { + if(inputData.GetString("targetname", input.name, sizeof(input.name))) { + input.type = Input_Targetname; + } else { + if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { + input.type = Input_HammerId; + } else { + int id = inputData.GetInt("hammerid"); + if(id > 0) { + input.type = Input_HammerId; + IntToString(id, input.name, sizeof(input.name)); + } else { + LogError("Missing valid input specification (hammerid, classname, targetname)"); + return; + } + } + } + } + inputData.GetString("action", input.action, sizeof(input.action)); + inputData.GetString("value", input.value, sizeof(input.value)); + list.PushArray(input); +} + +void loadChoiceEntity(ArrayList list, JSONObject entityData) { + VariantEntityData entity; + entityData.GetString("model", entity.model, sizeof(entity.model)); + if(!entityData.GetString("type", entity.type, sizeof(entity.type))) { + entity.type = "prop_dynamic"; + } else if(entity.type[0] == '_') { + LogError("Invalid custom entity type \"%s\"", entity.type); + return; + } + GetVector(entityData, "origin", entity.origin); + GetVector(entityData, "angles", entity.angles); + GetVector(entityData, "scale", entity.scale); + GetColor(entityData, "color", entity.color); + list.PushArray(entity); +} + +bool GetVector(JSONObject obj, const char[] key, float out[3]) { + if(!obj.HasKey(key)) return false; + JSONArray vecArray = view_as(obj.Get(key)); + if(vecArray != null) { + out[0] = vecArray.GetFloat(0); + out[1] = vecArray.GetFloat(1); + out[2] = vecArray.GetFloat(2); + } + return true; +} + +void GetColor(JSONObject obj, const char[] key, int out[4], int defaultColor[4] = { 255, 255, 255, 255 }) { + if(obj.HasKey(key)) { + JSONArray vecArray = view_as(obj.Get(key)); + out[0] = vecArray.GetInt(0); + out[1] = vecArray.GetInt(1); + out[2] = vecArray.GetInt(2); + if(vecArray.Length == 4) + out[3] = vecArray.GetInt(3); + else + out[3] = 255; + } else { + out = defaultColor; + } +} + +void selectScenes(int flags = 0) { + SceneData scene; + StringMap groups = new StringMap(); + ArrayList list; + // Select and spawn non-group scenes + // TODO: refactor to use .scenesKv + for(int i = 0; i < g_MapData.scenes.Length; i++) { + g_MapData.scenes.GetArray(i, scene); + // TODO: Exclusions + // Select scene if not in group, or add to list of groups + if(scene.group[0] == '\0') { + selectScene(scene, flags); + } else { + // Load it into group list + if(!groups.GetValue(scene.group, list)) { + list = new ArrayList(); + } + list.Push(i); + groups.SetValue(scene.group, list); + } + } + + // Iterate through groups and select a random scene: + StringMapSnapshot snapshot = groups.Snapshot(); + char key[MAX_SCENE_NAME_LENGTH]; + for(int i = 0; i < snapshot.Length; i++) { + snapshot.GetKey(i, key, sizeof(key)); + groups.GetValue(key, list); + // Select a random scene from the group: + int index = GetURandomInt() % list.Length; + index = list.Get(index); + g_MapData.scenes.GetArray(index, scene); + + Debug("Selected scene \"%s\" for group %s (%d members)", scene.name, key, list.Length); + selectScene(scene, flags); + delete list; + } + // Traverse active scenes, loading any other scene it requires (via .force_scenes) + ActiveSceneData aScene; + SceneVariantData choice; + ArrayList forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); + for(int i = 0; i < g_MapData.activeScenes.Length; i++) { + g_MapData.activeScenes.GetArray(i, aScene); + g_MapData.scenes.GetArray(i, scene); + scene.variants.GetArray(aScene.variantIndex, choice); + if(choice.forcedScenes != null) { + for(int j = 0; j < choice.forcedScenes.Length; j++) { + choice.forcedScenes.GetString(j, key, sizeof(key)); + forcedScenes.PushString(key); + } + } + } + // Iterate and activate any forced scenes + for(int i = 0; i < forcedScenes.Length; i++) { + forcedScenes.GetString(i, key, sizeof(key)); + // Check if scene was already loaded + bool isSceneAlreadyLoaded = false; + for(int j = 0; j < g_MapData.activeScenes.Length; i++) { + g_MapData.activeScenes.GetArray(j, aScene); + if(StrEqual(aScene.name, key)) { + isSceneAlreadyLoaded = true; + break; + } + } + if(isSceneAlreadyLoaded) continue; + g_MapData.scenesKv.GetArray(key, scene, sizeof(scene)); + selectScene(scene, flags | view_as(FLAG_FORCE_ACTIVE)); + } + + delete forcedScenes; + delete snapshot; + delete groups; +} + +void selectScene(SceneData scene, int flags) { + // Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set + if(~flags & view_as(FLAG_ALL_SCENES) && ~flags & view_as(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) { + return; + } + + if(scene.variants.Length == 0) { + LogError("Warn: No variants were found for scene \"%s\"", scene.name); + return; + } + + ArrayList choices = new ArrayList(); + SceneVariantData choice; + int index; + Debug("Scene %s has %d variants", scene.name, scene.variants.Length); + // Weighted random: Push N times dependent on weight + for(int i = 0; i < scene.variants.Length; i++) { + scene.variants.GetArray(i, choice); + if(flags & view_as(FLAG_ALL_VARIANTS)) { + spawnVariant(choice); + } else { + if(choice.weight <= 0) { + PrintToServer("Warn: Variant %d in scene %s has invalid weight", i, scene.name); + continue; + } + for(int c = 0; c < choice.weight; c++) { + choices.Push(i); + } + } + } + Debug("Total choices: %d", choices.Length); + if(flags & view_as(FLAG_ALL_VARIANTS)) { + delete choices; + } else if(choices.Length > 0) { + index = GetURandomInt() % choices.Length; + index = choices.Get(index); + delete choices; + Log("Spawned scene \"%s\" with variant #%d", scene.name, index); + scene.variants.GetArray(index, choice); + spawnVariant(choice); + } + ActiveSceneData aScene; + strcopy(aScene.name, sizeof(aScene.name), scene.name); + aScene.variantIndex = index; + g_MapData.activeScenes.PushArray(aScene); +} + +void spawnVariant(SceneVariantData choice) { + VariantEntityData entity; + for(int i = 0; i < choice.entities.Length; i++) { + choice.entities.GetArray(i, entity); + spawnEntity(entity); + } + + if(choice.inputsList.Length > 0) { + VariantInputData input; + for(int i = 0; i < choice.inputsList.Length; i++) { + choice.inputsList.GetArray(i, input); + input.Trigger(); + } + } +} + +void spawnEntity(VariantEntityData entity) { + if(StrEqual(entity.type, "env_fire")) { + Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + CreateFire(entity.origin, 20.0, 100.0, 0.0); + } else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) { + CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale); + } else if(StrEqual(entity.type, "infodecal")) { + CreateDecal(entity.model, entity.origin); + } else if(StrContains(entity.type, "prop_") == 0) { + if(entity.model[0] == '\0') { + LogError("Missing model for entity with type \"%s\"", entity.type); + return; + } + PrecacheModel(entity.model); + int prop = CreateProp(entity.type, entity.model, entity.origin, entity.angles); + SetEntityRenderColor(prop, entity.color[0], entity.color[1], entity.color[2], entity.color[3]); + } else if(StrEqual(entity.type, "hammerid")) { + int targetId = StringToInt(entity.model); + if(targetId > 0) { + int ent = -1; + while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { + int hammerId = GetEntProp(ent, Prop_Data, "m_iHammerID"); + if(hammerId == targetId) { + Debug("moved entity (hammerid=%d) to %.0f %.0f %.0f rot %.0f %.0f %.0f", targetId, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); + return; + } + } + } + Debug("Warn: Could not find entity (hammerid=%d) (model=%s)", targetId, entity.model); + } else if(StrEqual(entity.type, "targetname")) { + int ent = -1; + char targetname[64]; + bool found = false; + while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrEqual(entity.model, targetname)) { + Debug("moved entity (targetname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); + found = true; + } + } + if(!found) + Debug("Warn: Could not find entity (targetname=%s)", entity.model); + } else if(StrEqual(entity.type, "classname")) { + int ent = -1; + char classname[64]; + bool found; + while((ent = FindEntityByClassname(ent, classname)) != INVALID_ENT_REFERENCE) { + Debug("moved entity (classname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); + found = true; + } + if(!found) + Debug("Warn: Could not find entity (classname=%s)", entity.model); + } else { + LogError("Unknown entity type \"%s\"", entity.type); + } +} + +void Debug(const char[] format, any ...) { + #if defined DEBUG_SCENE_PARSE + char buffer[192]; + + VFormat(buffer, sizeof(buffer), format, 2); + + PrintToServer("[Randomizer::Debug] %s", buffer); + PrintToConsoleAll("[Randomizer::Debug] %s", buffer); + + #endif +} + +void Log(const char[] format, any ...) { + char buffer[192]; + + VFormat(buffer, sizeof(buffer), format, 2); + + PrintToServer("[Randomizer] %s", buffer); +} + +void Cleanup() { + if(g_MapData.scenes != null) { + SceneData scene; + for(int i = 0; i < g_MapData.scenes.Length; i++) { + g_MapData.scenes.GetArray(i, scene); + scene.Cleanup(); + } + delete g_MapData.scenes; + } + delete g_MapData.lumpEdits; + + DeleteCustomEnts(); + g_MapData.activeScenes.Clear(); } \ No newline at end of file