From 2a27af00d1213dea1d9723d61ce271d1b4fcb0ce Mon Sep 17 00:00:00 2001 From: thetrouper Date: Sun, 16 Mar 2025 21:35:07 -0500 Subject: [PATCH] Really exciting changes, got some more packet events to play around with. Might make most of the checks packet based. --- .gradle/8.5/checksums/checksums.lock | Bin 17 -> 17 bytes .gradle/8.5/checksums/md5-checksums.bin | Bin 37947 -> 41047 bytes .gradle/8.5/checksums/sha1-checksums.bin | Bin 162557 -> 170009 bytes .../8.5/executionHistory/executionHistory.bin | Bin 1833410 -> 2031932 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 17 bytes .gradle/8.5/fileHashes/fileHashes.bin | Bin 242019 -> 256969 bytes .gradle/8.5/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../8.5/fileHashes/resourceHashesCache.bin | Bin 193843 -> 199487 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .gradle/buildOutputCleanup/outputFiles.bin | Bin 22097 -> 22511 bytes .gradle/file-system.probe | Bin 8 -> 8 bytes .idea/gradle.xml | 2 +- .idea/misc.xml | 4 + .../main/me/trouper/sentinel/Sentinel.class | Bin 5494 -> 4953 bytes .../sentinel/data/config/AdvancedConfig.class | Bin 2609 -> 2564 bytes .../sentinel/data/config/MainConfig.class | Bin 2412 -> 2368 bytes .../server/commands/SentinelCommand.class | Bin 22151 -> 22354 bytes .../trouper/sentinel/startup/Telemetry.class | Bin 4990 -> 5022 bytes .../trouper/sentinel/utils/ServerUtils.class | Bin 6086 -> 6737 bytes .../compileJava/previous-compilation-data.bin | Bin 90180 -> 90395 bytes gradlew | 0 .../java/me/trouper/sentinel/Sentinel.java | 6 +- .../sentinel/data/config/ViolationConfig.java | 14 +- .../data/storage/CommandBlockStorage.java | 22 +- .../data/types/CommandBlockHolder.java | 274 ++++++++++++++---- .../server/commands/SentinelCommand.java | 18 +- .../server/events/admin/WandEvents.java | 196 ++++--------- .../events/extras/ShadowRealmEvents.java | 3 +- .../blocks/command/CommandBlockBreak.java | 6 +- .../blocks/command/CommandBlockEdit.java | 59 ++-- .../blocks/command/CommandBlockUse.java | 17 +- .../entities/CommandMinecartBreak.java | 38 ++- .../entities/CommandMinecartEdit.java | 172 +++++++++++ .../entities/CommandMinecartPlace.java | 19 +- .../entities/CommandMinecartUse.java | 26 +- .../players/PluginCloakingPacket.java | 11 +- .../whitelist/CommandBlockExecute.java | 14 +- .../whitelist/CommandMinecartExecute.java | 19 +- .../helpers/ActionConfiguration.java | 5 +- .../functions/helpers/CBWhitelistManager.java | 46 ++- .../server/gui/whitelist/WhitelistGUI.java | 24 +- .../me/trouper/sentinel/startup/drm/Auth.java | 3 - .../trouper/sentinel/startup/drm/Loader.java | 11 +- .../trouper/sentinel/utils/ServerUtils.java | 32 ++ 44 files changed, 704 insertions(+), 337 deletions(-) mode change 100644 => 100755 gradlew create mode 100644 src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartEdit.java diff --git a/.gradle/8.5/checksums/checksums.lock b/.gradle/8.5/checksums/checksums.lock index a5d19998c6c0c68b7140b78e56bd264ba4e5350d..d8436f88010a62044c1668fd3803b52b88a8b955 100644 GIT binary patch literal 17 VcmZSfyWC&;{v_{G1~A|e0RTCc1hW7D literal 17 VcmZSfyWC&;{v_{G1~A}u0RTC~1mXYy diff --git a/.gradle/8.5/checksums/md5-checksums.bin b/.gradle/8.5/checksums/md5-checksums.bin index 13a4e1f326811eece109f3e655dc4c8a924f95f2..733ccbcf552e5bbd1315d45a5d8e2dfa04858552 100644 GIT binary patch delta 3776 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 GNcu}+?P68{ diff --git a/.gradle/8.5/checksums/sha1-checksums.bin b/.gradle/8.5/checksums/sha1-checksums.bin index 8b2632346e5c1936f136fca0e76cd64f1ff41421..3058fdd70636367fea9b07f5529066d3924c5127 100644 GIT binary patch delta 8702 zcmbVR2{=_-|32#&3b9E=rV=tlgQ0TiMw;k0m;=!4#%+~lvJoI!=cll zlp)ceZYj}VNSY`~a#Q|mpS9iRdkp`t@9yV0$Jy_G-`}*?`mMFMAALfpg~Elp(x8K* zC+!M|bQhZYX3fns@}Nh>u-Mb}RUd0y?P7z55)xgI)UBR7lI*S8OY(3qt+qm|%OuF91TFT+@3HAi$NQrix?YGo=n zW6<|Fhds-KpO{vm&OC6m0fX#B&X3v4fS9PdiPP>w7)zGo=&4-<;>S*COz^y2AA-@? z_pA=}BI3-Egilq2vKaF-X2ofk6LT`^WLq95VJzwfr$IvksO|wOo>$fnU@($1oMvX6 zQ#`$A>kW)Er?Q^U(I+_ZF~-|=Ou|^iX_ly_D`9>0UBOb=(H%FF zsoDj0a2mAo=)}T+w~0ZyI|Lg!GxEr(>epHle{Y9CVNu_PE;C>{oMSi&EKl%;g}np%35P{++5;R8~sM7YejJ`t!U0v>*QA` zWHu!~TZ=CnZ5j@lf8Cvn=aR23#dsp;)T?EH*y(u0MNdf;V+l)G&pTR)@{OJmE(-P- zi`L?lzD5y8ihR=b^Z^)*HDz_YttHOVJ`9BJx{R^p5Z38;MXCkMFGs99=|*8|hnW-%#&hf9P5(Zrz_CCe)g>;bNfvuPuE6LLO zR7-Rkb)Wh3GBx`AL|9#)T#3ovDWsO6USd{^lvG1#aQ5B7Q1J$__XgG zMg0MW6c{ywQ{5v?7oXm(Yw`8GUN)wNEMhhEDiVgRj;4v%sR0y9u)2C{Rp&GmoC|2W zf_d=ZaF}w2!~*&AUUe8}>}KVEX(fbrXU-S#lff9vn-kV&%uj4`ShjIl!6pnQIdiTK zTo(|ZJ6PcJS$$s%MkDlD-b0FnLWIep)OI&A;s_901=?q+a z!KBdg;iPnvpM7#pv(v(O%5iQ!AL%XPP(#Z7yk{6?WWf%8U{0)jt8}b=n#8z01+zUm9WRO98=jppuZrZW-t7a?|PJ~t6x61S`eUOeV1~X zI_N`n-hd(Q4`FbaPTab6(8OrnY;3@{0lJHV#o{L4zvdVA&85tS0B(jTpaZ(rr5w}A zU^i%jX8zzQF|F0{A>X?bSjyybWdcw}VrO2ZD{i?#jZqdfXMi>$MqGdKgp(66(|V)^J;a_?dLcU%(^;+m;D=|fRaclAhjE|ke{lGze%jQg zrx*`1hiSoJrg-hGT9Fq!-xp&v{4TdR7@!3p$Ia>Gr{Bae8Y2LI3j?l%)@9j*)ms!W z2-{&=7|`*#>%(#0RPsRX#I&Wglv(nA;= ziMX;>`@MF}reiSv7`HeQr~&bYOJ!cp54}%gm~{^R6$Oe25$mv4-Mzji3Y1huNMgsx}ldTNBlDGrm{(&tQlyyGB0HWKq3 zHZf6o+rHasYtM_HLz&*p{mcaO>EazfOuGG|rYxX>^X1C009t2s_Ez{t7gEy?=EJmP z6ms1B2Md%9J25rk0Jkm~ph>Aw?a=?%Kmg^cS64_DrZb*!PaXwo{6>B6^U7mR*5qI~#dVneRjRnCrtyRX z#zUU|gKKnbQRqykCQ^zHtVl=o|N5-{@}AdUIGF7p24`iU;$7RuF0m=2sNr*lO?-RU zl31@0zYCKinz?NmAXSj4{phS8A5TGT^&Ys7TjeP2nC= zr$S~1vD{as67sj+wcn|%!Rk0$){j+-mHGbu`lz$<;06rF&l?V4_%Ds78C0am-Ifl1$rkAXBqDD`cJPf%CQi+|F zM^!6s07_m10|foRcC*-2bL`9SAeXP4yz?_M?p%t>@uK2lbCs@xB0gf5X~ydBf-^80 zlf%7T2|NI?UM+e12}>!A2K91vs>w0w_|rj1eaT&W497p<+SiatEbqE1B;5G|qk$%H z$1OAu%gpL~)6$kNVx^j&CSBw>z7*mPr%nN`(4iUV)75K>6juv!(MWYlf&T7v zG3Am?+K;Q`vpuXkt|>}Qrf(=N?klCy0Q-boIBujg`boH8E?rXTSBMWBfuz!a(;NXg)eFd^kyTD5+2mZithRj6H& zAx3@W=*V|IXm#(rL%Y=P`$FUH)TkNW#RjAGut6ho->t2KzO>Ls zh5Ss(4)mcrz0j~gm5Dmv@92z9j~)Lc1sLJ+=(q}3&RK7kj1}S?rv5|eqiMBFqeuU$M?;@+ZpxuZ3{Fl>D~IkHBi z`R`Qj^{ZB!m7UF1OR$K&{lZ5|fpcT2HRTMi0yNKsqn#CRc^YkhKlb?S$%3{?e9RWV~ms-~zySNxLVoP|=XtLz8 zGopm(;)4~ACQcYfqZ|s0Jkb<7TXS#zVa+_N zi%jjv7N-g!8GE$8gX98-N`Q%x@Bcp{Dp8!;Kg1qAWYNe?p;oZK#v)^O>u0tni!Qm4 zz!uB!o|ZPR6_P~8*hJDIH7Vz{BFiHx2JSk}ke>XVd;ZXim}|BbSB%vg(BnCISGOzR zIO@p(_zNlqRS^7COdCtqx5@5h1>@PHaEI6Jw;g7%$~P1=VP`wnVP|e}qn(YTA`^S} zA1cO(7*hph!@tK&@~je8PtCU0S1n_*$$o<}+v$R3>eoj*8%Kql#fy%w>hxR@U_(1_ z)H$~|_@i*_+S`2A5uT(o)Hyt`%v5l+Gg3B&igY$_SOtr0P{V~kn`O>BKc0Bzc;0Mf zab?Dm1qGD(;of@4#c_zF`SSlH%IW7#ot1v>=kCiUEjGJlyvh^WyMN;1@AZQVyig|{hk;Bbs9{D2 zU3kZC!%0=u&n3j96o1R_l6PTKHj-h`dmtk>4uiC@ipO9Seyu8Vl@W0@TNLo@Sk=Kp z9y>iz_az6xUi?OfwrJPrdW@qY?IzoBZ?H43I@s!s2EjNCG6^PTX!PDHJYA(3Q4@M=U8ykV+M}n( z(58qC*sB$`cB4Ts4uiDu6f;t0%cW0|`IDOw`aQ2FS!bP9_|i!@`ovApUW6{gSS+)~ zB`Jj^&=}upX!$1o+)(htV2V+Mt1h&BbXorVl2;XZZc;8>*SFnZ+OCCiUMdchstl`y zl}D@Q83?}eQk}{}hpq;&H50)k2mt`~|v(Yl4iOVOH7eq&I5E+b+Z2r?D zxL=d%xJh^iPuu6s&Z0{u9h)YAa!+Ex2L@=@Wn!P`IDK`ceZ=YNH=91+V$)C62T;!N z(*R9AZM3t0r!vmndwR0!fi!!+Y!l%(v9HW_o)XeP&XQBor9ANNsH)=GmNgqRHrOo~ zG+KN{u!(zZC3?ytCjd(mmrMk0IY*Aaf2SI&Vxvkn+S#^S9n#l}jTiQmuPFb}+HU46 zQidMM$R)h$G!9W>(J!mW#PGXT8mWSkMnFPorbrfiy>`&xedUtUT!&?KUdHG(6t4!k z*nx^KlrTb5Jhs|+*|NinyFF6WuH_47P1-R5z4hY}sZFdPF*=gDTdI>1PGv1rS-a%t z#p->l3QZfk$oNyzwW#>-vv>*G1CsSfj=;_YwvKitpt&5* zC;H7-{rw_vy?~y4S7zo1N>zbX(g~wgL%TCG8AcV_df$#LFWHf5zbd~ju9lLuW0~E= zv1R|eiak5eDrUZl=VzTogB4QW|EQ6?REc~sP-bkhgP{MreIobRBieG1ar(sl5+4sGT^CbifFu$ zzR>$hE%Zx;{7f<0<2FmBzBTB$xW+UEt2@(9SFHC&GS)}2w~u%-pwNqDzn~%$BgX|Y zpwP=wES6NVl!OsZ29g<_ySFpyXrbq}Jw*M0wBD)CP9vhCzK zMK`G_!KP%UXqz-rmff%IV4=(}U2$2(SKQFhT5QUs5XzYw>54}Gv0}7p^AVo|>uT@V2)s}wlxMusE(1o~GZ(or1 zY2ZT~%aJKL4v(xA>Nl8&pB#Gi_qL?_x-31c^7NZoOH+%gO(6E2qQ@f&z9TmuBg&)5 zx#A5nZ9`xDVbvsJ(P>+>U+6idLM;Ftd`hTVBUOc(lQ&`S>vw|5#%+=kDv-x1!K<2H7xDPVrRo zi_rQ+(;ne$`NVboHa?5k968%izUTCWFDKQ>hd3mI=uVl(93=9K3e4TlK8jmH|qIciT$p zF7Tx|U4#FG@UsBeQV3*Vd?An`m-UQQymQ94$4erUeO@gpoACZnZ_+ho&%PdeY~#ci zo@bgMxt5IZd?&)nH|;)jG{~`$_Y7H3y=3J_3#`fx!z$@9X3rR^Rlgwnl@Q^O3@^=> zs!&UoE(6nxfHeOJxl@(+v{{CZPK94Sg55)VW-)JwA`vMMfrCz&o6L}3r6!MLq`{6w8({LByT3XJA6eG>bvrc@ R&MJB1#jAo$eijZtPtAT?%@ZFWE762pudS4kxGN6d_dk)eno zVG`7lUFB>e!!p=_1qsV#+-1R9r)k(AiwnP6oc^&i4uv#z9cY{q&lBGtU*5gvJKs6y zJNMMUs(%*J2HZA`$gQ^5A%^TOx%%OcH97R?tR{xmMC5q~pLVYI8!}u_2ATUrKg3VY zUeUhME0;E=LX;G`L|vgzE*APBtBV4f*u14i2Dj8hR=7(c&Tmb~a7n!(Uh&xEXC9X> zr=DhJ#WrSUl>203d9kKBZ&GUjkJESn-ptup;*e!#?+Ww7rgXZ}fpv7R14Wq^O3GuQ zwsXr7&pT9k76)kbEPV7*Cu%b1Q~YQA*6ihQMK?LS;GnBr*hQXm@XGLx8|_&kBX6K( z=JQ+o{$pv|O zU7rZv^@_Y?k$f$grHM}$>>{`j68(!2+5ShVA>;Q|3VjXORUB|Y{#1BXe=1TzH`4fb^D2}YsxAYEHL_SyMEk7x%i^@IfUT1{$9d!j zCSarICwT6$365t^!cN;KkxTDRqL_v!S=2oR7rj5lc24{4PgU*s^?%d4NuS1Z)Hcno z{5{Q__ae+TqAci+A~);k_Ho%GkVorGrT(OIgUeg z^*D#pTQCCBPas6e6D+k(V1uSAzraVJ`>iZfPNtc)Cry;6Ku|~9;4tX+mt5_|0KQdw zI?!UFWH)=<&;ysKe)gKSq4(li|4O15GU)VIC{YXv3j(Tn5C@>{U1Hl+jxy1G882w6 z>x$Wz5kw311(8sL-JZZsW00u01%) zX~Sdi>B{{BPC~_QB1==eGzZKz`4n#`qEla#cbAOfReEcNb7LmF{3DLgL)rXxj9Z*a Wc{zNU^E1k{0&BtS(e?|ou#8#kd7i&kQf^(*sy_A6$8?&iBXI-RuJXbg_x*O zqiBp+k_e_~5{($6#u5chqDd^MF)_yIbLK9qW|N&EpV#yK{yx9wHGk}jVdj0$?Pu=X z`#$F!zL)#{YtxF|D4!DD>vF}xEp~RBUoTk|BUgQ1XJ@x}ZprEOdOJHic1n>QHM1LD z+1Z(XKWs<6w)Nc#s#H(OOZys{OFX7!mrgW{Exm8>qxP3lMus~1E@g}hZgy`?#JEXm z5gFm*CQnMwZJRfxV0=b+F-kpql zZQ&tx7v)hpv5%+p;&0V+&X<1sK07P6&%D?^6#ne-C&8Zs{-pSm;ZKe~1^yiI=Y&5c z{!~@5eVlV{HQ#b%O=R14S(7Fdj4dn}o0gU~IxICmd{Wrt$gvTbja6?~MNf06p{iy~ zXA10L@j02wbcTB0R`E(_*7$i*)U2G$k0~BZKxzL&&g^kMNCf=;0i|cZvWbD1fm9T% zO`uHdIbKjSRBdK`*#=%vf1oeQ6j1E1>doHbMJCctJ1OdeFMq9Ya@%*vbo{~$@TsQS zvDbN1#gwH<)R1*v)s4Mm8|X8X>JI9`R15a7y{+a01Pz3g8Yewewp1mtk9oxngy22~ zS7@kl8pb~4N0!!}qakIIav=K~KM)=p)Jm8UXGmb52p}8kWm@pw!47AC=SP+Ws+^&? zuW1nbCogaiT>ivqEc+2}^JGJHf5S-j0k5RNP6n^@|4jDs^iqD>}LYV>OqEl_7ee6e33GX zt>FcwLC8#H8heTtlnk|Jj0@P40-%un>|8-me1XIr@`f44u}1~ORSq|3p!S?Goqc4x z7qD%Up!P#)5_`us2&d2hpueVMu(pG$51+VQFHv!2Nd^Y0M;a3OhC2e|p?IZ39^W)0 zpy=!NqxnWT0fnq~&_nS(97jA8$Bg{Ro`&4Z#zE|D+Z9ISPzb3r_TXz31jRx5&-N;) zy)Wy-{=_S;PigfP1>Ih)?R;Z-?rSYoGoa|)rAi2WMH0sTXgeh;?HGt3PiY`0Mis-} z5I}n8QLYd_QvISJvOJG+D@#(pz&E@}=JX?(JH%gBC|xD=h9?&q_Qk@&(~87t^3f$5 zL33Hro2|Az4xf`yCUrsy0VP@YdM;%8pIzuVeg1^jpF;i%s$P7P)gmDn6SHR)OznH> z(x=}wjeLNKkm7mJER(W&!NM=;qH@h9kE}8LR*07gAGsZN4JtEL5b$`kN zG4c05T^P6I7q2aDhZ?*NbZKq^pB}1s!A^YjWm(Nb}D*wIy?p~t3l9Vl6o z_}ha1DPy8m&1wmn*^&+rKZ%lZ&s_xjC4QD9NF$-&Xx+87-3K{KBmb-#d$QdNu*;J% zEW73h<&!B!urx(eDX-G%n`(O0-eHs8TC&Ue^Y>!{PqdPUbA{w`6mlE?V8 z|7z|Z2@jN2NssEeKIi}qlw8lZS|H7V5drcpunn!P#h&waB>SKofOYwdxKzd^#w zR1fxJfl<&Pk+;T1$Q(DnzG6W1{cXire~c}C40X4R{e$ zsH^UemAkv`?Kxs4RNO+v#`A(8<*&|_(op0em(%{+K6}&Q$pX9JzzGLKJ<`|0oimg# zSe{GS3%q)}1KX>=ML9JTx9no5IrUYi4)gXc>=u|0GfO1;(xP&yuLX0SUlvs6spSxx ztM+4m5k@Ln$L8kbq>j(%mYtja(!dG11*u~)EoFJwqhD6l{cS_~ZTcoQ<>Ej{l`;%h z6fO%zxXpw&n~-hUH>@k1$W`}Xzp;(3d6nu~YP{#e-s8o@fcGQ4hHVyVDR;uwC1;A5 zE}xoylqHy)Ya*e3g#=gsLsTBV77)*32Xl>qx^pap8`-#9h>*UNSLwLRZ0 zExBZZ=;@T40NC$&%M}fEj?OB0$I-bf-zy&Ti-y{%CK~ECDr5P+iwN>WKZOg-7>XZ8 zJnc-7bfVD>Zoh2m!Cn<8=QNeG6ZBF!cV;izMB=AvTTsU<)No1V+)fBc4>PK<{ani( zj$WZJwVB^GaA4uwOQWGd<*WzpA^SCdPI)(RwK;F6g9rPqAkYPRILiHbMhPip zD3VFBo7@?g2C5lb&)*Qyj}%&%RL8Vt?+XH3fKlt*pJ!=LvPOw+;OXXUg1mQ>Lxnnm zphJFSX>5|ti_cHhopl-W!nOUKzwK)G2Gk@fID zU_CLjou92b@^Dy6gM8+~k*j{1_ckQ_#Ky8e@Ml(=qAZ)>J8#i%p!4-5U4!Xu(HJqW!CDavrWe(+N)AVh1<5EM=pOTv3L6 z7}GIf&Ge53yLY^?s`-5d)ZE6R*xpmHst~PWG|=FRZ}KlT{qf6yk^{eMGc^^hH$F~< zgr&I2V*5=>1eJR@`-867)LAG;1cpI~gQF5UEl`hc0=hG_T8Kc~pCgw1!@!GDIs*L* zo58amWvO;6m|tp&=Gkko0Apd_Vs&;CfbF43qEte^6{c+Vgy0Bh579YBN7%AdJ&tGl zkQAcG$Lc$VlEAc|Ew>frV=^(Y~Y zT3f32f&HcG_Uz{Zjlcn;$0}$j*K4@1NdK1MU4|t+e&JUqMRMCs9r1H&t*Iw@DAfBT zPfpZdzH*N{1l&pdZpx=l?5h{uo+xia{$^b7<_%N0f_J{k@Z7gs9qvP1IbfVIux}o<@m|o zhb6mvRG(Q1pC7k_)EvbSo^5;5!11(%2G&{Gf@f)L0h(aJLn=&Y{?l?(B>cQyogy?6 zFzXP=n`#^;w6scjL){!&Z#H!j>QMv+L-fZED(F|C7VLjScZA3{5Ge3|B4Ues>m8wP zx4jgKrm02?sx@yW7^>YQQjoo=P7<0y#mAN2a7QPJSKoq)jRr6I>vFyk%t@TP!) z)+<#|d`vl<=R4dYIvfHvt4H!|wGv=;lB*vWjFJvQ0|9|y;5}0%g`Hb)G}zwcCt@r2 zxP?K{38fT@-$u0UZ97XL0?MQYHPE|EX+rC;>M<@}kmfFFC$z>zU~8z{g1vTgn|i1a z+FNc2h7Y$R(DreXxv*ng{9viKqZ)$tn1%~3M*SQO@L--XSV)f-H>cY`-s?u1p4ZyL zt>uL618S=COoK|FOo2OF@sP{*elRH&CJ%(_xhgrmH)zVX^9dfK*B(3k;qeVOF2bFi z>Rv*Xs`Ya;LiI~7TBxj4%jpJv<`wEyw@074ML3S%bNdn8nWpH;bL>QlfHDGMWGCk# zLK=YHZ)y%}D-C^lPAP~)1bRWy423hO-&GG5(rNU0r3qf44PioEjX-x?psTb%A2baW zYFR{k!jkvYZjif3HA1Kn5FH4{z3L%C??MFn!l69|E!^6xP8QOxi1vUDv+y(U))IWr z+CFYIw;NZ%OZ%|MtM;jf@f_YfLwB8n8z4)IReVRE&(Ke*u-qZO*m$_ca+TNBTXzwtI6IAS$_^_AxKk*Q#0lBZFrI30LNs=5g2M=XRA zydxye(EcNJw9q~Rhc0XD=m=*%QU~yym|EHo3leDZ0Qw7)f}YsPGpcr4kZ5gmYQv(kk9DTOQBw9=0sec^+cn699C8}{C84_rc1!v!?}y=~0OsbZ=0eLT}2HeHs=*+9CSj zHDz}mf{MAhj!G2BMo>2w>@p7Mu|`G1&DE-C!QsH6KUkwuI!+Kfvz&Wa3Rncv) zjdFmPZZrel7C@8oOl_rEoc`{q0>K`Lwz-&!8lAFB>A)z}n3? zdh2*cQb%~)3qM1)-&b}J5(&}GDGiSCJ`0Sae{piF4L@*m!+iaiJy8FxDUj!5*4!7> z%_$Y+ysc`-qq-WA0r(~NYfF1xf*l#(bPVo3c*QAs33dcoC(-D@`xI)gb?4OGg$5+V z50|r0USsSjFdRBUbpUc@OzF5aTw}YPIff;`PRbej_E);I_WODE%)MLFLDTnSluww_ zKB>}cc~U~@5?<=2CzQ!idn+!f1B?f4$%S3-;bCjadz3dnWS(bHN{tKMnp|@u$U~TUGpZ-Ic25r}_!fNbT=b?fOwORn}aX2%C|a2Ss|R zFPxt$?+0)0anVr0kn*LzCH^THs_})I&3Y|#dqtjU9~BI#OC51%>8>OYYA?9pC+{S! zmii9z7t13+A7t->X=+}ewDRDvpva(*V4ylVadPT)P9fCKP_ka$6F@KiKL=HSqt zL)7ebX$e$EsW-$gbY|i7H2KiR2j9AoB21*)Q@?OYlg#%^7CE!wq#Bd78Kk_dWx;obe55%=%&(f3rS*fn;0dksq)u?MVnOX8m75>WOx9pVD=J(9 zpr}IL$9%K=H|{1>c)A)Og>un0CR2`2n-Clk6iSL-UQTlupO!8Lc;c$la#;>4r4tp6 za#J)d;bgYV4;psrS-3k>-q#_Nt8(aB(zvM-7hmcK)X$ZN8*(Ry1*I0|1miC-9S_uV z#}@`i1+~TY?%{}elue`ksbd^8*drq|XhK2c%aen%CS>LWVtE2n^RlqLL$O%cVl~!jER@SMVZ zd70^1qo)Rsvs_@SaqX7xq2s_OV>Qj8=5_fXX+$`8d)Ez8hlDwFYpRCSLHB%fPAJIC zG`HoQvl1@-ojJ6h{cjZbERl!N&mqkq&5INVs_d@uq|S0fB?>}D$y9JTRGJC$74o;J zhQAE=lQER>>2UY?mgF$gN7s(J^bBSQ&Pz>yDRV;RB;>6ZHue{pANA`={S)@7+@YGr zsfXL_Tz75v)j2_4AE`+iW*%xA2yRy;?F;dT^jhj`Gp=Zx++ZJub<&t8OP+>-_+u{G zD&0c)N{YJ7p$5sYnYf0kC~|8FcNWXtprJtH2qBAUEmg-g*o)A$jZ_MY1EgcCPA!qY zF13F`L4CQrJ??_ZUW6hOc4yZzc^_Fj6oiAUcsUsm8h4jgRaeU7Z%A6UZ<3xto#+*d ze5Wg`|4GyM!S*lpw0*UN%Q~#|W_;-16pp@Wy2b!lFg)1uM-qKD+*6{*vJ&l~bVP}f=7s2ZMX zq^|xb#z-@mQ%|EK8R{R>N6_;hB{+s!{B9fQ2!lpS&hEM6CMAyHlq=dJ$-c3|V zP~K0{>|g1ml5bG$HAx+X?ssnfoH|N|o^Sm{6CLt%^?pth71&Zj&7SI?k!6$YHC2Z< z%g@=H>mL3OTB!cAzB%BpcNoc` z_PAURy?@pB=Goxm#6?2hDjD~2{L0OrI<4H=#ldG)SWKs>*GB^Nn?989!{OwY@*c3{ zH+?kU7Zk~%`h;8yfe-Ytf=Fa_JC~MkkiiKKZF~C*VsfQ@>7ReF-=r9ODO#U6+b_7J zI0jNbrrWW#wwL-?nh+!M5Xa#I&6Ib8bJuKMs$w!yAjktJw2<+Odd`3MCvz5*^w}*v14tM zbIfSM^FwN(9(#9MZD8!$_p^Pr_KaTkOKJ1$()!blL_iL}@^CUq#wbe0u>s{U|B;^8 zr0E$PDS=9G3)J_tNkTFgv-Sc@!@)oG-Fdbo&B&BE7b7G;*1yQJyZyB4G6;BrCyx&v z>pQV0Z4Zh1_6`9YEzl8OeWH)zL+0O-cc5489hB7|@!gz%C9<|}S49I<%+>huZ8n6C zbZ7(Rx6w){WuC@u#abqg?@Rs}8u@z~=tVKT*lV^6&%w*)<2TZMis9RH2O_uVGujKv z?U|l}B|s$7iQ-*cpqB(oaGk#d=IYT4G;TQ1#{Btu^zf*HHC_t`-ATFBssc21QV%Hq zQtQDtoh|h?!FmU5As$l{f~<3M)c2ySmg=y>n3kc4q%%rX%^=_772IAqjLp%qeccX3T6n;iMlyWc_|+4 z5t1Cz-tg2>P0Q%(Y`EWqtv%0a7SG(#e)EwF9A~W$gv?dTxaST$XHC{Jkp$;j_LWxV z@=pIgc^CeyPn$JYAG0W{A5^$1y`XrpDGZ-FG`*0>htops5{(0nr4y|(em;L7D(LVn z{r9hIzxtuMl=OToC5PZSPP8s1^5ZzB$CD&~hl|?X+k=8{NIp4WI%g)0)HSePkf*_F z+dlbyPGD^oHa~LUu&G?NGT5kM(wm&n01f4C4)B^YlgYQ2Yn~2Vzmd$|S2GGoRbxIp z_Jfu*Eule%WUrt%kn7abg~@7Kn)r=wMhK^wmv|0mp0+_tY%PWXPYp`vF)}43A|tkF zyvhi_Yp?=5rl!x}@ef%&eBsKZ@SLZb3uBS4;23T+tkp7UJm>FE(}ZxmG#Om(?<%7= zH`4<1+kI2Q+%{1^%*^rnE6>c6OiSB`z|TaQi1C$qmYX_#HR_@M$<0OAQh(D>`cf-) znBP;VZ34tI!GN?iCp^*_q%_%$n&X!|`^YP6UisV`cFm9&z`{vGOI}3roQgh`7lB7I zuH5W$o%yXpVw|MYjFuhSe6_Z?Jn5~f+NLDU4&dj`j1ufFlBW2Ev$ibJb*NxlmX}MKz;dUB_oJV-}1^ zPbd#yR4_Dv86?=)m0i(P1SwN$(6_-6CDd>TZv&UwFv)@ytb5dH_i z5ax#Bcov$oL@-jNT%k`GPDP&YXi|>ikC51M=(1b}>Tvvc68Ij25CtksBq}M&A{YnQ zAC6yULPaM@Du?SCD2TxF^BBgF0u`OcL$I*6OoCva$3JlnfZJ`E!F=}gL?#x&54N?dM{DF8xCD(xai?h z6!W4$Rl+%fjbr1-+_>yx=MH4P^xE>wdu(;9nul=bvLgc_pQ&k*Cy&uA$+L4uhTC`x zi@fb$ttk58u0MUI&6OwhaL32q6S#+b+cHE#WhC}O?Flz2ywQ>AA^?x2ukL^OO0O~f zibA^ztihls3vjPJCOf`GAM{ev~3EUxvZXV>2M7a%R~vu2qS(rZw00M7**7E1Me%qh|>-c`7p-koX7w?#!72H7PHlyB1Y6&gH%>=Gl4 zco9br&#pX?>Agi~gz+n#dI)wk-9zgFtR9&@)J~hUj?hd8hA#QxhG+ZhU5bBM-zz#W z{Vf(Yysc|#w8=4nipA3Quz^uWO0O(Nq6YNesxvXx9;mKzkHTidgPx3^^b)~&_hO=@ z$I0cbt-5xNm(_D!!=%T^-IQ&*;KsWpz39ls%i3+a(8kNWOKxqYUl45cc3sEDWJTL` zA&r0-Rvp@SH^EU8-gtRujZ-*OUv{G#Q70WWT^cWUyrYY0yu8zg?kxS12rsX5i)y^g zkE1)n&K)|s@&B4QItu=L_P>wbtykltl%2Zh#>-F-b%cV*3j0L?novp)}4*$uscTZF}mqtLp(z(%(qsn;7$d4 z@jA}k`i7v6>tN;&NFu3=*E%!S&S}mN$b0As1oETIya=7OR~YgTLTOEe&f4dT`H^M-9i+4N z=wg0U^MhL(gSEdFb8b5wA>UVReCBtBvHP26ma|+fKr@hOtF!X9^0Qg~9g|y_&f0~F zIaPJBYlP0)L5lg&On0{coweH&^P?Jfw@{sxQa3lS zm><;+(gx_Py^N6F0eY}DL}%?o#Qcb=(uL@(J%pGaRsQG}q_cMWVSZFTNAEtv+M@@l zSJ>eIi5<<3@Si#_+HRz`JV8RI?v0{ex47o8qpjU{d`tWFlh8}vJ+}i4d1(`lArSsJ@3%b4Xj%3}WeU#L0?u4m% znT6Dv=dip8zHcUA( zReAast{K}MDf#hsUH9|#d)oJ18qo>7hBCfkWhBGJ47JqaB+~5F!|rdsBvugQIe)mb z%ryYk4@IqTi`@F7Ic%7R-vY;nGA`1&OSrLLKa^>sxJ#bi^#^*hlMY%BLzVX{cMG}> zV_GTZbHK2@z7TUjXEa#5Q&>%&ezgNChB5x8`$W=I(I22*JC&n*5ffrxyG3)Nklg*-3f)rQYC8-1#`G$VGqWj+$+hQM_wuY48Lbu$K7o6YYaPhU&N zg5?zc?*2}7)`pY}uTQV!tj&S=_jSIobtE=_9fyKLBbh+yx8(B9NE|`mkV~H=rk~;@ z5wkfN>d$ICV0jYu@o94Fon)9XQfq=EcuV>vNpLZVG1A9-Uwt_{dC4cAZ7p8g{rFFl zL7L2jOIHv;=VYd}bS=5ePG;IjH;~J9$xN5N*2dS@xJO|PnNMeHUvyWRo?0;BnTIet z+S0cqHolw4)8B(p_USKg zT-h2(LcUV49U*H7aXwj2Ca zYQvz~Z3K8djd{`O00HjqLoJ62R-%PI>5Qx5=8m;X&W9~f;w@YWPfy1jt4Na4bf$}9 z4@q*kH{`c+)4~nz=~XTPP#H{1X(a&$WiW{bYh%gJNy6;|!26=cLuYLZNuI)q491JK zHgT*W+GpVaY6XP8u5ttSOf2LPa&u)1sIPGGc6x_v$FN;0tVe!xM=ca0#Ps+KcXMtF z?2Ukd16F*jO8np*r@o)tEmcNBc_tI?RPhZ5IORYAguaJ<6z*Xv=|`jnPNSKYP9KmG zf9Q`xhPr?R)xo3j`Jbe+*`v9ENiNrNm!FVS$3`>$PBkRe+!whvEkR30mobd1)2y9b zn?9temB`mpFwhNe2HhnMJzI?h{C-`RSCdv2EIqJl|HQm;De-t~>}R?c46f?x$mD?F z)=pDj1x3j$5%Uhlr{>YActJnB(nb6Jw z`_KO+EYwfj$V4M|r&f@`{r@xh|6~TOho$)cj2mR+UJa#+2W4hw=44JNm}<$^+9qiS z$##7UYRQn)^mNqP7GTiU4wFW#p?mizjtTEL>*RY2$l~Nv?$!BC#FwTO=YPCVI4wf@ z&1WB+vYR_`LRNO6-TaKK!o2L%soxt@x3Z9NgU318B&*2(+VPlZH@WOG9@R@J5%tn|#!s=9yBoGcx1v&~>tb!i zvV_Bhos6Is4$7R;=m^t}Ui_8x`%M#O>PH281%=M{xL!<9mBCOx-8odcjAWyk$hbqt zTwF=bB6;=C#o1j<9_3GDd}iDxn&vkpHe=^eyH9Mm=(6%_SFc}92k1h)^@IHk!<=CO zuR9I!ebi^({d0J0-Mb^t>*TwuTwIqrek_aMfw%4!ztJ)5OznXoo2J|_ZHzPFt`vuP)Q5BVJqIkA&(6mGg zbzjQ@yuwedE00}Np54lI{cCrZobFsj8f(pADQ5?AY6RRlZ_>_NRmkAafIlPt*s4{9 zrdD%9!qK;S1^!l{zjfuBsbey8CPx;8MW&@^rj5-RJu!W1L3UVfL`0U^g}CJBs>&&> z#HmG7Iob8g7;lb3yAX;?8BJ69zw{23e^cn#-R=1|t2Sd_JqOX5bEjydTlby5>FkGN_gt zwN2pSqLDyzs9u1w{-sqwU`usVdyz2fukWH(H?;!^fxAhzJB@t$e`A0`$lm{ViO^=6 zyp3KolnrwvomlZ4ojwkV21x$afB$m~YM(cmPGE91>cy#tb4#3hw#>QAwiDviGaC&4 zlLghkHeSG~*TZy543r@T$`Au(SOv>52QU!>Wst~0fblAlAw&$6@t1f?u#1bPAqL9$ z?+BCu4Huk7@-a7w^LTSy4UCEb)tQD7JRSzQz)dlL@L=fLUB%~J@F}tx2~7bZ%aj>B z4(-fvE)E5_^iZYo87+_^a6u>FRu9!kKK8E}i4i6sc_`X!m5d}68GRgnOq>b?bJ(Zs2>q zc@g^-T#gcBC5W*Sf{6QE!8e>6>T=4fG%oauapF}QzoYm;Q-gb!KXR^iM z1f(s*-~_0)i^SjrAOa00k5iC7=t5clEY8LxI3c(Rum zoIngt@XzN9#L>JMoIngtU=CyOUlp7{DC9tke*LHK`t@DOCqu^;UdiltT_EJZrdU%) zzJLcDCtO}QHZ>!6a&Ne`n+j?&IO$y~1lGJ@(u-8Los>v*6RB=i-@hW&Ei*OV^5G{^ z-7zbl-`Rrla?*CkTj{!0GF z_K1cP{V;OhjG?M-d~V|il1?_K8%tXUja&fDIzmO{BWn=5(9S1ydA8E_ML}(F;t&c&51p?FE^$)$MpZ z;97v*=I{oJn;7WvxVTj|`*ketVg5eJslp<2y{^3)HG`L80 zP- z9$N;2CNTx}m#W)0ou7u`6qGm(tldy?@ilsKu@;m~7bQ~N{=R8o?iNme){awPBz4Nf zXGZl5U8!;O6{&9KH*-8KF~Nd|RG82l6sc|^)s0Lck?JN=-T1#7YelMCTaoJa^u&W` z4P^d_RJZgABGt{-7m-MHlUGgd-th7ROT0WW8`qJ~S zEgKX!g3d`=zH$HO3*y*$N1vdx^sQgqqR`hcHPa9bKHo|>_qYFvo5fD>AC@bDF8KcK zz3U%6+EoAI@X_`!EV%jLr}=X+WX2kExnA3ZevW4!!QUb7w{R z8y%LKA3iB;a^%>E%tk-Q7eznE!Jv7O;(Ahawkm3;|6T1Q`Z*S-Uhd?uAaP;vzwBbX zhv~HF=U6oU5RE@Xy=@|I$F#bTol6A~g&|FsZ7Ib6#)b%k+ zu#U+J`7fw?HOU(?CbkaO3JKj+0|cE2WA@G5p9S)h{4I{_iUfHF6799wGt_6crz+_Z zPAKuZjK#7J{R)~frU8OJh&e~hnAkcvEcow zP_f;%NoRS^iFJ{KTuxhu4~9EuC|@8>uJO54baHL>UBfjSF-=nZk6Qj%$2|Veom^YT zM;4u2|2xYc*K&uWSLjP^=C=(TSUC66Xwk{FX!-O1pydy~L99b{L&8sNtdMUe(eE|3 zoOK*>(eJf|4JaA|iN-*jF_7(#Q9|`121wCk74%=ea;?MFL;hx5@8%6txPo`S%JAH` z?&mhYb^j_JL|dc(yS7HC{Cq!&pA+!D|FR2jAJTn)^k|~Fmntb!OQ<{Kpb~vJRiqnF5OaRlV6;yy6mR zC(+}!S}Bq)t@W(WP--BQ52sw9=m*&l_6wU^zZ@em9U2B0CbFLjfGSTJF{*u>I+bUm z+AOYmkRhLs(?jx%FH&alkv9;O1|c(*Y3wQ9f+a)k8RG(0BwaprxM6mtUA)pEk9}xc zee8OPiihay_M_Q91wbLI9rRFq5698(Ldc)&X~?~79K^?|!HkDONS(0C)T|BI!~jUG`kQddKX!{Iu>PQ|Vv1jP`8YKfu3su8-Y`UJIi#C|tq00tS zGS6<}GxWcA@Gg=ros}(kcE3p5ilj@CbV&vRI(7F0gHh6fXWQNa3t(@eTI=dQ2+@VNr_HSv0LK9Ugn>J2-JYwSJIN_B*`MtwXC^G1Lc>M zec0~=rMg0%pTq$sexME%`Y0w0%8>SQDfItP?F{dHY>E?VJCSrLk}f66vbK)yxJaFR zuFc4|2Rg)x-rpq(_+X7lx)i;?1AW%i46=u^1Ni7q9C@xRTC0QTD3cV{pEdR5F~c_d z{4Vm8xk7b=GMdM$z8N`V8>N988&w?zk#t9s3;c0M*_r3H!pZCe@hkD&>V4kSg}rX` z1=NV_WYR*1d&+KXy#TWKf=LDE?kR=KDUvQ5eUtq6kuHV2zgv?oMepyTe`SN{;#zt2AQe*mLlZ^{4w delta 6789 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 DfLnlo diff --git a/.gradle/8.5/executionHistory/executionHistory.lock b/.gradle/8.5/executionHistory/executionHistory.lock index b275fcc14d8c2bb402a0a79e97f0c297d4d47a89..ecd5a413fe4cff611790a853fca3e148614f49c2 100644 GIT binary patch literal 17 VcmZSnD(U&&^z=?{1~3p=4gfUG1jzsZ literal 17 VcmZSnD(U&&^z=?{1~3qo0su5V1Umo# diff --git a/.gradle/8.5/fileHashes/fileHashes.bin b/.gradle/8.5/fileHashes/fileHashes.bin index 61e328fd3aed3ed0ebcfc074329755a06a721950..b5263b961a37ae504bae5f0499e6a1d7b24460db 100644 GIT binary patch delta 14637 zcmZu%30zLi_wU}PNXf09_WeOSB}tM>DwX!4gi5b93E7KwBq?%ZWJyVi&_-9Kl}ai~ zDrt`pZ;>TJ{O3M%^Yi9^Kc6t)Ip@rov(1^w^WG`dEkxEFDRq-jzSF5|MW>GPa6gae zU(6ReC*P`2Fu<7$v+DhGEu^mCMCvtOO?KWe0{fJ6`(zAQM8WHo%@XID5LjM8iYpip z3Kxz#yznSTSl4yZLV-iL&c89^>{>~LJ#ESLP<5W@OX z$W#rE>g{<`*N}m`5Y{X^=CJrK^)8bIB7z9NFGvZJvU8`3Qt1VEgN#37KhlgSoy~C`eO)t zK0*d*ISMB{dt^8i`)m>doBDGrw4e(smmHq(N%k@V`-R9+?Rmmyer~of=`8%05!pk| zoYg}}$I0t%iZDXhy-1Qz$ADNkFD)r*@)LyRSCe`=3B-c+=d^pb2_fut8(E{z)J)y~V9PNvd1g^S`t*bmnKwS~7jNr8Si^a;r)Qop_F=&C+`{482&9BakzOYv z>p;X0SsP1)J(MQhdpX1#-{nsd9G)Yrz>DY)05i#ax}|0XX6Q#%!4VeVfUBALXv^NtJ2 zdF4bzUbWm5dan@C<#^=!yn>lBba&U@^rW{4ym>1(|1|_xl%Bui_-q3LyCicby`gie z{`AcpwIvLtQ?C2lNfU^t5#7&SZ{P^5v?kq$IK;Hn{r*p57b2|f3+X>pO+0Q&oA&b+ zW1QD}a& zwk7ve4981L1|iz!Y_jZgHZh^(-He9?Aqaa6a(#yLF(Rt#pqKdSOAPp(l>TZ!bXXTG zA6jCJu)FG{_1A2|;Yo}{)Q$NFdt^Zlea#Y<{TB7lD&DCo2+YUGgpozK;!tkTkG)%^ zHy9~eMX^H%oX~8+-7(#$VPDJuBc^R_-+aSuF`Dgkpj7-Ye@xX_e7I$=S1LkVFO2b+ zm~;<#CyDMuc-KBEb_=8?s_}TA)AnTuZKA;NRxF#?l0K)Xz2hJP^L0VuHjG2u9psg- z_GWUu<`|W?4NJy|tRg~Bd)ID6TJBBRZ^zUyVe@xU#XX5Zfe5Bjs4Y7%It4xqkue7u z^R-R__Pa18wZ$Vl6V7H?F{?dTGp6w39~Dwf-^g!BMyVgQeisGsTJh_oyL%-xGs6D<>!pU*J5iNKC!)bL(f zYER0!*vYBN2(6F@ivzJR!rUpr;hz011ojPp;y~=HuxDpU5EN^)q)I~W$qppo?~-e_dGE*0J`tc53e~?Ss91*wvA^ zg)VJGU}r8E48=x>%VpP6h9{3Xs|s|6L7R3T+&5!+JVW`$7bJ$mZK@as%Wo=9MC`r4 zD2WIR7QB#9{a1@L;}H7t6_|SfYX2;2A_uEwj*N#vVEqB8{czg*U|fmG%U3}l_yBb2 ztR7oyxs*)Ao~sNhY03&)w|{mnUBM{Y2RaWxGfAIWWt$C{)T#Lhwj9Jbss=Oeg*D{Y zBFgraW1;456&q@_`RjRv7kmbX4#Hh2F6kB^1(Xoj@CYnE1Qj^%7I^t(KjWvHid5_& zY#X+4<@4*0-MP!wpbKc7av2cVx-7&V{D!t+m-$IWHgtv5n;YcX^p9H}x z1GcjfSdj~i55vu!z8U>p$IKpqcZY!MVeB!^$)~QyVEbh-7oeJs=@2KBH~YqP`XZux zZ-GNP<|thH+02hw==Z(=YOfF|NLEL}jL9Q7|)YUGGUaUPGjLY)bno zbe3v^l6 z2!Xex!Ml8z2NGMv78jO@Bd{=$)xK892kd?|wKXYa2qox6k4zrP8%6+;7_U)pis;tSKFyk>(}#c=-} zW-Bsgr~iZ4b2Nc=3G_u8Sc0nIuL6t;ToLX6@9B zMiL&3fDNTEky@4Rc=UHMomEQ!rCLUx5zoFg{$sXD*#*(o-2#^7Si+Q+Am0?@b?X=v zsOEC)BZe!sQs1jEf&17&FyT0^LwL$24M&v>BBI7PpmiM2Cgh5qMJoraMBwu^RP%8h z2DZKStgF81%Mkj)1Gt{RImD57Tcg|-EJR?P6WDSBcOqWP*MAXAu|Z(79mu0Ww*9&7 zkMq7FuqPS(Jb|AUUL7#=rV@MaYlPl50I?))K!`{BcS{GXLSRQeC?;_qVrQa%U`GLy zS5;|1As$x}_P&|svOfCu9z#&YnYsPp$R+_@J&-TJUsLxnhEo}B~)<&u7;^?OqI|6*ioj31e9x! zdCEsFy2n&+zCATJK)*o4gT-OJi}5!fS0XZW$p9*8_z2;+!&oVPEz{yZI0+=uVO+6V*VU96zCx5uF2IupojTu@rCk|7 zU|%5!K81w}?`f59IRDf~8=*znK;a71L3xqymxqI?2+a9N*fks zdB=rtz9R{*Y=CD5o}hYHcgXX(cNPLmexGdn>H-d$WNtc+@FH0-m;oc`T))2bH*+fl zR<%-<*JySBDfIcAwutGld$v*!uj8;4+f(>#%j)Y)VSTm%C}hIq2=q419em1<1mv#+ zo@ZfTZOw$?(*`!%kF-mmnc@u4nTc!Q)1$IEhXp~&S9}VkREnSA$5v3e_wjz5I9%^o zJA276BELfcvIdq8zNcwMy6vI}tUe12Yw-la`cayJlm%0^pXq^`T4;)ZLHSJ|4=}xc z*#@9lhli=2w|JCOo|?z7*Nqvo_qN9u$*g;;5nlA1N~xpwSJ&T{eD(vAIFGJSk`?d( z2K(z_lI)FtfAzQs698qS;7cWp;tQ%94lV0pE@}rpf*v>8p#|DV z&~hJ3pxPVp(-#5gfW(ai{8*#hO;_ThU?c)YAOg_oQ_9B&?taeR|m zA@umzbEX0Avi^5u&povRz>@_PH(47|AKkzd?K=la(@_H={*JxGOaaDsmHSBl zQ4XkLu~rj`YctkA@$g2HYu$hXFUyJ8bE?|UR5uoZclt@}r-4b%#GC4aPE6`IamG|o zznvs(I59Q}(Uwm7jaTX{{va9Hg76m~Nu8fQ;1@4z7Gd*%&)l~=9Zl4|BCq`9P?mfw zC3wGcJowiC@MSd9UIIKPuwW_Gm#*(0WVT)~lNy}Bf`u^Yg6g)y9iE8vSpvW?SUltPoT;%EN1Kb~STi7h3`jA1tA-gjioO!c^ws#|m97B=3O^ke&!r#YC9( zp71T5vwK=N(`V#H0Rw)zml0uE5hqsdS2ukXiFj^hf-rtqAM{0`CoZ(ymTz5Tgl2k- zs8~@JeCRwYkPQO0vOUr`ezSU84rM3On&^qNBL zRcCz=!8|BmLly@kG<<$p=M*nQ;-9>uA{MaVF?#&XMz(2bK0+(6gJ>g`qj1vO=h2x@ zZ>&RTaR=3C#PY^f5BDGO{p@j&K?BA-opQEx)%DcvjR?=X0yG!0II8>s7fV%_97bT3 z$QbdZDYfzvRyG<4e+x*xIR@1Jg{*Fj!2Qb=v=1_eMYkE*r`JQ=sQ=`W#>_sLj*7m`Br z48RL>n6kr?hf_+gaS$=pMEcF+fSnf5;}JE3;@f4J$A|lk;H(8JOttOB>xsS^yokE1 zYivIm9a?v?bbOf3`&kXeUd)13J^Q_b%l%H~KG5n146R@!yGz*_Q$x%FP~Suv89D-4 zYnBedSEHlxeP;%eom)sAG<2fktyz<>DVmsXL=JQLxqY6DSzrKam%u2(k58T3vGq3M zxVw@ZSdb;0-B76K8L_|(f!%53Nuv>P%@#)1YRLpizB=YDtKXlzWz3JB~4L=1+>o_D^lnPq(MSm~8Hd zB>hZ%zzauM$X6{siz&kBAj(gM9Ra)FuTA&>oR>lm>CMMdF*@xLqJoN_@?u30_Z<})P?#RFH z|J4PjkHB$K>mK;c6&ErJ7YWg6`zWHY@xM}uyP(VyXNP<>8KEu@zh0KEoDjrby=xp% z#NT2ghdCO#LKC-tGI+GRZdByiIF8o8IkNX&>|DI{UA`N+%`D)@P|u&L1HB+zLduZ; zpwnv;UXEkg@g-+oD#Nkh~U&~ zAG)v2SK6Mn@7z;U2jn9;D;KB+`~>LR*f_hKYH{QBUzj-AylE7#P;Cnv6USxX!Rb0a zj$_KC$Rl4SPM+X6qxe9*n&HJiIP@Vkr~Pl1`!M28{RkZNC1T|4LO@`ZvdS zsc)J2!4{;!it?(ya4)$(Ip*^4@X_7nxZUc8Q}~D0LB$mb)3OWVHShe%!o$OR3?whY z*&y~3>(7jtxCdBYVsUfT86?NEWSk{mPP+TTb^a``VcVpRPxFIlG0D5aQ9d5E#}^xSbQki++j zuTH1=VafCS|Lk0ru5^Y`i&=zE5%|A^b_!oo>mE09j9i=SSJUG(lPmVUIBGa}#DG&8 z#tzX-$jQM?3%*(^MqCg3vKLkmZuT>hlobA6`;6#=nlF=_7p_aqk92ikV!&`PnIrz~ zZ;oesvZIVmo$Y1Le)>SEsKOHiRTxMa0)M3GJpR)raD(>_-&|HT_s0EU*O3fMvrn_R zy6*}{_cWgA^r173L#5a}J!9RuJG^%q)^F{Ng55nG^0)qhl<*_y4UOX{4Y2aJ;}83^ zqrBkZT;YEXc`Kp1y>*<&TVcGD`v4z4H{1?y*Mp0J!Ove2$$%BD|!Ux zQG^~`0uKm7-shLV`s+zc3z|0@RG<8>f^xo4;y4BKeZ<2mS{lQSh;H0-`sSpuV$N(y z?#SO92gE0CyZTx7yXS(Njn+-m=lr<_JhtTM&T8Bu^Jd5jH*Q%csauw_sX%2B?0x=> z8M(=SbKGAXvxRsUwa_`ut~YC25ITi^Cxx6T8Mm1o;=OdX=*ozWEtxtCKbsUOkCO^R zV7wkh8{cXcRX2XST)R_t^bGls=15Mz*8(Zwr?3tFF;4SK*K6wsUQCf$F){K(^Ztx4 zT#gH_bQJQyo?Ee=KJ67to@n;U)m(UZEwK0Ag(Z8qil~GN&_p~uYKb6jJ>GvV!3_gkNi|Wh7}4L1y)Oka*nAJC>3MT|ycs?acAtKB~WdC&OWf#PS30a~NCk?GL_N146+^Qc7~A zA6q1j#R5Y#g-ZkiCZVR%4#Z8uwdn539toKR3;y_J^}n<&7CZAz+fnH2(Ux{ic$`2N z^|PiRjv2+(sSm0Y;>xb z*?ViQcZl16wdh68-FQKq~Wk{4G}2{?IB}-_u>L2x)F zdSGCEZX~z7!ToW2Z`c{|&^NAey4UhxSG5uzO}}AmFD?}4C&abnzYmfjCH&@h0&gLB z?AaKwxRzqN_$k~X4}!~(RT-53G%!_yr>%V=iabZ430N{qQjHT6FFiYX*ygxP8K3B! zS&VhYnCLslIGyx9Z~ed?xl|wI8K?PTsu|RpYfl&O)&iGtHcVEJ_+EMPbd$t-!?t#( zvFK;cU%AD9%e&+-sUywa*T4QX_!iW=4rU~bmiOXe^bTQCT>)W7aJFP&pQM-Y*=sQs zXP#dBcg20~qF?Q*No{Kx=MVe&+*%Xo^BmyGw=kykU_Xg8LJ}s!5Z9cef``+Ha?h3;Oi;FlDfu#u*1 z4(ZU+tOTIb4IT7z8?yOQU_XZiZ)pCg!&!=`_$iQk*s<6aJV19GP zHruNUUVmWJW9U`j2{e8~^WR>Ly=*a)3y+!3VimXF!|em?uBzH$leAw;ls6ooYhhgwiwPTN}&R= z6v)%V=DTR<3I^}O;U#9c6x^YQ{P)IYmF~e|kTlNbgZI4Sj{#>2CZi;p#CyM2xO2u~ zy_qgM4t2uvN=L+EcoNbd+a36%L32Um6!>)#sxoOh6bFaHfHNJsBl*20Kk#*if{UEc z#2_e|28aF5X!sdmE^xR)izQ|Zi6Sq zI+C(9Ujc%iSQ(8b)WLaQIMiN_hVWP48gMx3hK5>TR2>c@kD}o$aN#H%+HFR|Iq-0y zwWyL@1N`WT7)I!P@W>ZU_yKG)QdA#^1VUSe&x2lu9NWQ}d2pz}9lp0T1#{=Iv?jv| z`hp5S{y?x>pY}qPWDrQwhot}Nv&00UaV!wQB*3YF<>td&I%=|JaDV_!(;|230Z$~eh>CX^7Kt#fp@b7WX z|9CYrGeyfp{QK#gMR`^$Pcs}%0OLSg5|LaZFYW+^>v0(=c7av=nHvWVCC$;YYhtI? z{w6lv4iz?lPZD&PbO5Ifkaxfu4IctUIXK+6Yit;I2o8@~p`R9T5yb(7;+Ht)FB>LEs#|}Vf+Ya z+Cl#hT^>Hd{7(a3V5$utVceK4)`6|okVj~Y zhkSHI24wgl33cGn0XRu)`L8}}fxi@eQXNE_r~{)rAyu2q*s}7HaM_OEGO&Yiw($3Q z1F|qAHm){tVV!mRa>QZv27`nMHO3vVF47fsi8ff8U$7O|hR8)5S{OD;5|l!y6wr2AjbnC0s^P z0RG>iqKCuD0%EsIWb4{s>&cyEuI2*V7d@#RQ=-F!JyzV`l-Kg~VfKzxhUf`!vWGiT z$*8`>`|Ajg_RO7yI`V7IjcJ4Q7cU28-WXdlZD z-KnwJLJ#hk&U~akQJ0x*0hzRi-YFuLp8`c_Krmv7hMhpf3QAtL>2Oc7^TN*=$9=wU zB!3Q#=?G>{g|pTN(ria>N04XU<(kwN7=HOJ+50PuJ$FWO7x1Tje<^sCq?g{MMFpGV zLW+Id;SG)PJ-s8Pc~jU%|Hf|(SDPFHLR&ktm{}(9CM^W#rBU+r0zXkGyy&;^0XwL6 zn=Mki1GFi?wM;$YrIuCqIk_mzIBO7~I{14nx+qCq_|^9Qwi$?S;&QJsrY{)sfWG~nBBJMk9}6(kZC?>)o3?{l;Rd2-CvqRP!Dc=B zfh$XM6DWa9V3!B-XflYQhgGYQA>VviBtzz zInI{K)7qQy%t?Ozx!UsLx(oldj}d{ld!R(?Ron$E*8uNTxJ(G?l)rk%=^5%0Th_M~ znVOm~#Ppp8eiLCa8gGU@tut0#ap5(aNbRxaO+3$3K1)w71JAn1zd3d-P%x<9uQipt z{P|l6zrpi*JIHJ z;4fJHAn`tC7!17Vp}ISo-40A?Q8COg5X5NG#LVW`fqhzVIIs`)+OXk(!A^htJs@m3 zyvWBq3lMK1HkiN4H|j9qxyYz{5E1Eq1$N;q6}mSJM#u#ECO$4!GOH7+zPYb{KQSzN zRl=J{?t=*Ah(;Vr_CPU%n=qE=fOQ6JU*OyYurC8{@7GnN6VuSnk|pC3*$VCLQ_Uiy zjXSFbc~w1X;~14LA#t<0-Bif=)pZR^%2_#CJG^G0g{mbizSZI@;JN zFq96rV-tjY84a}4VAdr!&s*iSbj`H4o7ealn(^c@{MBg1*_wZ^c-8jj{q1)p-SD8Z ze&5bCGkI#6gE;hcfZJ8HU;_xd3Ky({_M^@D5DXoI!?Vn=36#)7K4#bfjrPHuw)cf)O3yAJ${-G3-EjDT{dJ|1j|>9o*?W3#&({UmVK1}_)+(==Y6&O zeJ@`yn}Ihn`ZE%Qe~b$Yr75nQn9=3Q1^O^wGaQhU{$?)#LDyI+bVU9{h-eb9 zfe5{yVGtGv_0+tE^!y4O;81C5h2ufZJ1QsIu9S%P-M{a~2>gNAW!XTVz6~X1&BTE> zv_EJATzAam1*4bYuw*+LvOp+(_dKsh-%zt=3Lti+#Xvfcr6LFsJ%mg`{hM_cM&HS@ zW(ouEE_f=ffmMo?{N4%#uEu47q7MCwx2&0>h({32yo;;CO<6HwO{G8(6iY7zO9B0f zWB?n8upk0z=CNeKFE4J>XC;j0C9?pB7dCyHytuV;=4=q=1q-ar5$W}x1jOof3kEJ6 zme_C;ZQg1MVv=zJQC{4`6F#XW&w}aB2c<$&TJZpR$PT14(dOlWPA1&^`U*G;HIM_r z6>zpH0L@x}t^jVNSi#>T(3hY@`hE(fNP&Iy1mrLW*8`w0bQ{knnH^2qtS*`u{vo!> zh*1ZgUGT%aWy!KrY%s%{-h!N!E6|zEz0(=CgI(U}U;Q8puB`OLQC7iYoM%xPK(uWK-R%_{b_*fW*lcVjsUvhsTmpni*E}K>YxyFTI z9k1q)+l#%G0y07EV!s5LPSA3Iy8@5?|8w2>a^Ef@wu&;~kzFLQByS&vbXwdDIVQm$ J-Usv|{|Az8+Iau~ delta 1906 zcmYjSeNa?Y6z`lXvZ(<&>OM>mEXB{*O%pKUBh6jSL@Lw%FqRodZSn)tfz>jIGK`EC zFLKC8tk7K*LwxFW)MPai`~)3iw=Y3#M9s+^bp#2c^Ju2&&Ai{-d*8YDoZs)9d)_Hu z=+)McJrlM55qUvn+ZGHM{_v5WXZ|Ppd~FBnk&j!1To`GQ-$rWs<;Z5xe~d~1IdXWc zzI1px=uJ;VfeaZDkb_6MEekj9rc#>y!t^dS%f#O*Zk{ zd%UJk8sCUA(&CYqEt(9m+VrJXKKSUy6Jaok= zXSBx3;@0W%POBz|U-jypSF=#&UvtRLYff3%=8-e5+x4B-XM;ZACc~OPX2UnFw|dLY zJDMDG*Q?{bc_`n$@08f?m3P`b`reK`AlGzST4P%ZK_Scrgrv#alNh*Kd;AOTdyQdkVCeHkO=WPyj`R)D4!t$;F6 z=9MrE)T~u-1;~~SW>u68>1><>@w~7a?BrezUX{BBW&^do15V0$7jDygYavs$uZ3Ko zqFh)_!}36*v^;Q8y8#vN1IP6~ubK1NpD!OAl=cBb-3Jh$h7Z9@?sec)aUVex(DDMd zRt0R`R=^Hz>v@#3o{t&!F^?8~%+|?;U{@7|un1_@2AEFO8~97}1~`Qg3G{U-j-cjJ zv{N!MR1jLI+W=b`gWG`H2BdwC8s!>L{W;obxqjQJ8Z zr9Oq_L%>Qg9)>Ir*r>q+;Rkj=9;mXN9Jji@%g}Z4ZpcCvsD=(G-3uP&-OD3t+y^e@ z+z+FW4l0PHZpA9O4l;|tL4)(CL*OIxVF)MtVb;ZanE3<_gGI$3F_d5b9r%^=DAWVZ z^YZsbFU#RP#_ITwv7lwgc{=ZVPK@OQ=Z_Zs0L3coB-{WTx`W~xaE_{Mz&xPD3oJR=A_cA=HhTgcVWR|vKW@)ZuZ#k7LzPl2+SYx)c;}8kqs%<;6lC>pZrlcJhPQDVzp=-v-yb}X7 zY!`3LHK3rBKP;<4t5Q{H0`l!P+?H_(g;q1Ex-xcCQO#@J)hxj6J%){KGpA+lUSpV} zvyodz4>f-Q(HQk;O|&VDYCAAQMR{?fiSLwNk2jF^onf0*XPLy%bIh*f96D9vd7KS~ z@L|{^w_>?D>Xv#gg|nBi49IyJGU%5kt~=8iR_9e82NHjVv)bk3lRAvGBYr-3>se0n zlRtBW1Aj4u=s3?|_xla)YRPZN)#GS{0G(*&-HR`9C6u?IpRz9UrLMff&Q;VUFoC-C zdymT|HW=5&v`juebmnz*Qkb8)MI_Wj`v(}@wm>4i@qmM>eZbNOcVY^vj4q=P+8^>l z%-?7glm){4U%&D8v>L^&#su z5-$B>H;)SX2#xvxb8YA&T(lWDsYQK7fJP%#?(~NQ z_13c@np2c$wDl9u37dK%NxY6!l_E5CFGX$rV79OC{Ck+FNj#VKdzX1O%wdj zh}WoU8V7ZcXR6#LMxi`E*-Uq*i`8=4vo<<2Ls-?o7ezWqN{pqdnZmB$PvpcOnI#W>S@5!PWU7OfydpHcDV1wy=p2?x9++aMeXj{Ot(+^YGJC35Rm~Gy bQD)n?-Mv{8?fI7!Z5b=-)k{mn;1~V@IJx)$u#($|EFMNF3H6Ww7J-nd{?EI3S%Lr$jjDF7RQ`@&7K*0 zX&_Ll8DF8t#I}W9owOL!{Efl>e|Uj&dI+SA-x;{0weuki>_h$0b-n`Lt`x$e$?_lBq=j8=>nW@{=;Ejhf{#{@y*HN4vF zEPy3em>d68z5W}7{BArkrxrV_;-ICKbqQeuUO4A*CU&6uC@;WFL&%fH6>~XQRhC}L z>#noEF&ga#9+aDjJ<>N(QWUom@}}YA^D?nB8td;!IlK^J<-FWH7Q{A(=gW<;n}v+& z1g|llie~dsHD$&8S3+<*kNZp!=vu^9`ZCgZ$h^mANccBy^)hSYciRmSS>iRGT)@R1 z8ZE!EMRs_MN;vCzFBYD;=tkPxWyrpp&tn!^0W9$OrP5z=QV1MO=DEGl0~l@-JEu`@ zB2tw0;q7`!Ex@Lsb62+%qg8=@`|+&TPMBCh#Kb7~8OXYije{aCb~Uy7#J%?&LN^32 zv8WVa#m$~NFPnytB9P#@l{5mZ%SpOqFPo2;U1_-48xEGapvdQnStcS%y~Me1lBQ~> z>o|Nr>4p{-@_EA}*B(51H9>yEI7F6_=joME!LuYQcN&@&TD@3f@EPOktMync`059h#6k%mEdXLvUJS;T9R8KWFmPTtKa| zp1VT(%*)>qS2~(lU;((;+5Gd#=^G~?kVYdaEWmR{`J?a2gjLHAB+ws3o??S`h{=6% z{`l{Y9>f*jMeMZ(wb;)gL%-rn!e#E{k}KzfMNlDJL53ZiT85}%SE2oCB6Tq+#I^;^&9s)ghCpzP z@LK}Ru%b)@o&3q05GZ|xIJJZt)z6pu4Ux8`zd_iK6FN(QHJq$SPOt}?#sY0JW*x|- zVN0Ysi1MAnjU=s3u>C+L_HEJX3|Rn>9zBcv?gx?qW~Xp_>@82>-W)V1BLW2Te8jQN zCPm2)xf%2zE^q)xcN)*6@r)~)X4wC3c(Ir1dtWa#K0Y6jySI}=96%kANz>W-PDN{w zJFyESu~{%D4{e9eFWn_v6>y96+6tU#*jnwt3`wnzhzm9nSAr>b3G1&e-Q?+sWcv+< z-L1Twu(vq>LOsH}n50w)pw7B=?w6~q44V)-Fppfd9Vm)pA%zi@C5PJ)I<$oJ+696D zCi{Tf7Gn;7qcjOX7KDKh0ICI%M`IwLMz%!)mPRw1wm&c}NZJ+Wr{%j}fXubP7@A08B{> zMaThIN`&0$1%6&gd#H>&KL*AE-N}9N_|Y8>2<>_~Jn$~fIZsmKjBW_odc@?h&pbnM51UpNFoNuLT}8tXbhKq&s#{(Bs`sg85utg_5kd0)Uwr^qg;N!qREit zUKI!+MwZI1{?1NB1_Mt?$yI{M3X!kq-619{A~M0mW>?_I;I}ovgx2LXNP*T)>Zn0# zBi60(i!~XG7J(*~OU!l$nhK4ByBurx2vz?k?A?JA=6|uJ=ajFonuxC=XHSAt0R!-j z4}G#s5N-}Si#Wd;B#|wX;b*{*D6MV0RaAp^E{)D7K6y~P$->9M-n?i!67O{)KWV_z zR5fE}GTX)wfh|yhv^0h1CBZ_X#sV(Fl+@*k30Ad;-cv}bv!NxJdZ}3~&PB}$O)L%k z8lLdKjgrmV&&(F?g05y_l_f01W}mh`jG0%WkxTiKDppVtV9^sD3A2mnxP^V*gt|2} z!z>O>lsT+^54js0PdHdZCbl-s@`!H10%6D`>2EEVxvb58?{aCze%0<(p? zupMm8pJSp>U7^8qWP~luq+!e3!WV{xC!-)FwD4nQOzfDcW51C}3&Q9h3HBn$#UAy1 z-Z||P$}pOQKT*7h%H9p#Qf!kOsL6rdEd0hCM}k;P&5yduwm7M*E)-j5DDiGFti{%k z;bvUhbXgd%Jb7ygYy?X{>%p?Eyd#tc(PJncvOwHC3Z zV(@b|Ty{}N!;7}}V-Y4foH;`|ReP;;vhE1?hwP-`7dbT-Z?QGXZX>>gEpA}T)b04( zX~0|@^BaR*zW;H%5ALm;aRarou=hQgvl3nejNDAMef%u$#{Kz7p1*~7d;pp<*7clzvgqN5QwZ%*C)y5BNxz|ZkK+Un zc_dERYed5y#`=>A$))U$rwE0$1mhr;hr#h;6UWXJjzmm_Y~2m+0ZYGEd()!RrU`3w z&z0e^_*E?}p>LdX6}ju0{~!KkPgDCt%^-vid?zmK1(Dc^&UQ(IEvU_fohyipL(q}o z-*>J&D2sDm7+fjo6(Q(A=I*#@);vA}q2k$h4?|VR*!b#!g29RJb_kUWB^gHrdEYU6 zWKRG}(3xQq@QMOKSZ6A6f|Qn?zd13pRCn$}`>OX=9KA3@6(X;8wucRu^(^ z^%|BVB6n63uE(jlI2>ITyd+JS;ZkWti2%HE_QJhHdXG>bhqS{XRarNF$Y0vaQb4G* z75SNf)IB4tJvLb7pR`5<%NUX~xPpV}mx0FAd7i?vO>!pb5DzQ?x$_J(f>2Gc++)#l z%iMu0WxzPX`w>jTs_KpnviyYoOwSLTSssa9%E`;P5l2T+_XhL0{mXApS&XdvWeMgJ>SVEdR-<-mpF3ho7m~eC zV6qf;s;!!*oPG#V`wGa!myqh3Gu}(BNm4;qIGXeiJh7gOb+u|7&}%9}%pNNq^9$9` zs8!u9N&PIG-L4q2w-~OKU|6-plAQj+3&@uglW;1ha&=7nqcvNSglRqagorGMOkIzv z>+4tS(M3#|lf!9deO$Wygr2PM9PGw$s~;K+>z)dxN#=TCGt&8h$SkK0-Pn2av?^23 zr2+O`CafypB{)GklSx-5siO2ul1T4>3asye3Vm;FuDU%{E66S-51ghvPM%3U##0|f z!G{sv+%z}onuR!%Yh*QdR|Ana2PzpcH<-mJm?2rW_13}sfdH~!rg?H2@wFVxA`&B^Ch?CX^(bzo9(uh%lQ6FU znv9KaG})!cD(QN4Dq+{{@A8OX8k}|V0n}u4dCqVNiF3WKyxPF5Z(-RcV!|Cbi&3n% z{>O*=X4CUpl)r{wuYZIGxhfITcOjGX1<(jEs`(3cHtF+Ifb?Pd6n&0A5!3;c2>cFY z5+5P7A+FFU_jm44-@*r|7*?F79!L1pgJVcNZt#H(aYXQ~U4Zr6q2jQ5T6TP?l&wk- zhkq)vg0`Dr6;T5^Ge|Mhil|4;AjR|%$g+y5e{F|=4MTi^iR+lHDSur)Vs3M_I@pO& zlcZulK0d(w_?Fhoaq3s5TREmYB<2aw`mC_JMgI2tSnO@r_IiU1;^ikw-%y+qB&mnd z%r$>IPkKJNk>Bj&AKlP2D7p|gkC{cf6rRO%);q|awtAvUD7HhT2%RUecRK&P^PHcD zS%*Weh?=>`s)>0;Yk3|h2tO14LSwZkqc$P@th497(B11(&rNI9n=8s#us2HkZNWY9 z{=f%sf|%VoBMjoqZ6Fg>hkLyBL$pQ!eP)fzMz zi896oPCeQXebE^|zT#w2jQIvpMu69D10vs8wdKHgoqeG;D@PdAy#S^7RmW5~Zsh{q z!z$0!>fX&R8=(v{lzSAO)xSOZGQ^axkuJ{_+|NgClhgNX;`|$4#Jk%CUtN~dFBV}eZ3wqM{-V~N7<7uz5+OPDnRdd`tu%%5$ z1)Fn}YvTA>RlAvoehx-^EwA|e?#&2;cr_2wU1YvA=v8+#t~_He%U@fyzIEjN@bH+S zy0wRLwhjapeA)WOPVSi~BWzJQ{~h6Z_+!;FjfmdZrFgr8Qbgd+?6;*-Qzu}jA8ywC zENE0lMTW&cUX#!=^##XT*Jnumu%Ja5#VBUCT)W?_xZKCcU`5_jT<6fgIwy`WaM@t#h{guLP%-}Pjhh25wdtG`v{9q%x1Vmg?PfT6=*g$_)m5!4 zFNrdmr(BO@-(UANdfm#M=?fkSt`4I#X2jb3sF`?ZTwQ)HD1H2D)d+(s*eHi=pLVGJ z6QEJbSC#vEWH2Chgfh%fTAu!PO=$2Gw)b7l$Y;O)kY%nbv?>^z+#3Ub#+8Mqz7Z7o zQGsmO^y>Y6-;Gfxe1A=Nte4v-#uT9hXoq!h$Rz6Lx4EG^8n;IgEC7{Q1qTg?`B`hz z8@+4KYtNl3syw;1SN6R9PQB^zelOm0!?)n={Yo=5Z6mg>Q*7z~`j`S4>!)1xhS_tfjlzwlnHST-w)u(n}U~M>M`YV?UcU z^Y16Gm?~M`4n+F^P>LWEiqfqkE#($E-+$Moc~sQQH7ywn-Ckn4VRm=a0vTyR6(2QH z`k2;^78T=vrOG}Ve5zTJDaugKG`bcQKh&mQy!gTA*XDw*MW}%v*&rQJ`Fy%zY*fOc zwe%L{y!FN-Z-=L0AMq~I)xUc7Cgc5ZoJF9OD5HA9EU%%QyzdDMW~?|WA1Am0kBU;e zEa|Tw_I8ooTYMxJt`AokVf;7S)h*+f{4FDKaAs4@M2DR(uiz{fr3kNHobIBipXzVz zqr!h{iW3pKH=+V|IvYDA~JqatsfdJz!|xNKomYKf)lw_d@9i@rI?_W@$}{ zO7)3M-)#Jc!>eJ!Os$Y>cJCFBMcy>MQXu&KYScKZGLD1ToT0Dccj}3}v!|J&jNF_1 zEG=Er-HFa9k9dpQ|CerpR8i1YB2>CN)tnCMn|+H6Bx=`EDV&#`^>eRF$Kd@}m0sOn zfS@HG<)Ji?s*|{3fd>cJHKH0r9y;PxO1H01 zH~g3O^uC(lCWX9E$FA_)9TQnCzG^9LZq21flP*-BJ@6mpKZZ#y;rQmna$m!R?E3?( zZGY%w+~aSlYh`f?9OB!Pz&P-S>b@^jF6x(}Y{5Cl$JQrrZuQX=brU7MI$C~6vaJ7< zefdS^?RuhpJ5+KWvq9D2r$d!xv*Y}SmJbA7{-|jho2o1)_Sfs{b}OAqSF6zf5oJs> z{uRG+3P(2dSIW}}a$X*hyp=l8lYaKaONrC7sx$LUFLrSLVCJK1moY`3kt?%j^YL$= z!U^v>pfrOXa-;t75Pi+J7*@VV)L~IGv4}4|TKuaPnT8(eo_2-#TCR$x^wvS7-jP3>g8> zld2!D{5wiBW$8@%Wx?fg^lHz4>6W@Asi840d(Oq?{M~;@sv_3%a|f(;-#NR1wIbC% zX4iir)E88#Pg;D~5W3TYHSxZlNpLjl4{5hmenDmBYn6!y#k<_57Cl|~ht>Y^4W`lq zK3OWSI-q5km|#U%X;DR@#9;o}hntw!>@1IW;8Qjc$@xIZ*+rJM5-Y?js7;-;Z{M|>_?cWl#xlR&SUz%Z$EVy75a5UwM38vR8l&?aZ1^t Q6&==bs;8nX`gh~;KO{6LfB*mh delta 984 zcmZ9LT}<0$6vlI&GFF*(&LI242(&N-=LX$$d}I;IhqE}4j|d26vGs!DlIdm?>S88l zz<@a0@g|&R$>y?)t?pt2U*jQ0TVQ@5YG7n)y&7O5IuocjThtrh#S5cXZ*r3Nch2*i z=fubE)wec{KW@=Zz1O-9owKcKgYI>{McR>PCtZ}^P3p?_ux@)OlA=;~Sh{3KBdN2G z`c=lxq{`WqP>v#>8Y~LwUB%6$$J`$1wLQNB*}M6#8Vqox7H(du#X7G(3BT?t$r>b5 zZ+|kaIzJ67#4`0`v8$y1j3?Bssi?X$diXmRshRJaVLieFF*8eE z96|XYjs@_U8CZHGaiX1y#Nnpp6v^F7laTdNvXg8srv=EJedHu5euBn{?^IBj->am! z^myo3aBmGIBv4BpNPIuJZL;xcnk8O6Ko)PUrzu{qCpVWim|r&=s2tgT9=Q*PrNDtE zl7eUGg>=f0lP}V=jeT7d6=#4VBn#c9CEE{?Pa=mXk|N$$3|0z{lGnyVubHy-IvGG; z57|@X{4we#alT2{30W@gA4IF%a}rlcE*+=i!;~)%+tC5$G^4v3<4y3YixtVS7V}=I3`?@* zF6Z4UFHb&;AbVTkm1rxRq}pn{;%PU%bx*C?=KY;$BGS|6PvA#h?nf~f4q!JQ9WV(F4&se}4X(5C>30y4vQtbaZfos!y{?615_ybibzWJ}L1+%~}hIo&xru~zkqNgY8DmDl(zE?Q>Z-+HZ zf8ge&ijIu@jy|V8+}s!eRjE1ou2P18=ymV^|JC;{hYA)>wpDKApIxY5D&!If6^q}z zRk?#vK>t}&f=%=Jn^4i^lTB3>1We|OEH7+o-2o8{+q>CURg;l_=U2J)DJ!d>Dp@Dz zss#vm^xq2D+Ih$us`BCFt!gv*Q+Qir7I<<I#Ro}(TB=U2k;t%nS1`5oZ9X$m2 OCdd1wZ~ow6&Ika? RcFCy88x7*3ZoBDWxeq`|1`}`@sC8@q;X}*YbA13*2 zj&ho_FwJ*yGkpFHzQvio#j)NZ-og_V@UndVjlNd~JS7X=oaSZw{5ihGxxT0Rz1tT) z9(OBm3op;--{gB{vv27Z-?FW~h;2SBlYEY{fK$kMxb!x0w)^}$e6QsD78Upw7y2TK zd{_|WSkx~(?7xjy?DOyREiR#V`0BzdS-Q*T-|bsk;QeOsRerhD=ilR7y4Sa)jGjZO zTgbn_DfjvJ`4(6BUh4O%g0D_2xXa;f;G}b-xonWL-{-INEk59T<)Cj#zgMYTm@w~E z`TW(sB{ja~wZ4ct>T@jzOC)%=Qtlz2zux!aVc#o9e9uRs)%As`Kf^of^EddG*7z0% z5YK7!`H%UQH2I!e>s_)y?!MLR^SAhxOnMgj+s8TX^SAn5nf=F0ZREB2{3m>i+kMMQ zd@a0%r*7hP`23x|=RWo=I_X;+>7}331V>cI?eh6g`4)HkFip!IPQN|e9-sfTZ^;?o zi)Vciy*@0l&nv(1#Xu9?qsyO5;O*dT=hyJ>aANo)yc~WG_rFFtS2(wLrMx)aBYFH@ zZZfye=RfCLe%^;Qxb7e8el7Qc&)@HRwZ=QW@Zh(313v$t@5PHgP~U63hwo#QH{|mV z`<}n#TR!4@_Wx_@+@E_bQrZQ)#}*Vt0K0dR_YD$&P|086%~XY1s*#qPxi2NEq7$km7PEOTLmnLh?Ve_7RLj08MAqo#uJV6<& zRh#ILl_!2ip#w_Kyk={J_&J4NP*Jm5?2m>f>m~6h#a~glfieXTTrgh~zae*wN;?C` z3t(!K{5|Y8#9L6W&pIK003U~a0$!`s>SL1P zNAO7$rD@Hy0!OV=;2hl3DDDh=sB6D%{sjIR_8Ann;IuZ!Rrw3}SH!ym9sbL;o4xv8*pyIy@hBuBDYcA zp|<}|ptRWozJpK`X&>apUGP0Dxew<7Jop#1{PZFC5u9GQeTepB*#PDb!W-b;F11DI zD4CNd<+ywdeuD5*#D-8jjH0YJ7akzBQ6<%*AI56u;FQr3xu ShLN&__{=bRW(kq94C6n};nryY diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/me/trouper/sentinel/Sentinel.java b/src/main/java/me/trouper/sentinel/Sentinel.java index 652b774..fbb3888 100644 --- a/src/main/java/me/trouper/sentinel/Sentinel.java +++ b/src/main/java/me/trouper/sentinel/Sentinel.java @@ -1,10 +1,12 @@ package me.trouper.sentinel; import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListenerPriority; import de.tr7zw.changeme.nbtapi.NBT; import io.github.itzispyder.pdk.PDK; import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; import me.trouper.sentinel.server.events.extras.ShadowRealmEvents; +import me.trouper.sentinel.server.events.violations.blocks.command.CommandBlockEdit; import me.trouper.sentinel.server.events.violations.players.PluginCloakingPacket; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; @@ -56,10 +58,6 @@ public final class Sentinel extends JavaPlugin { getLogger().info("Loading PacketEvents"); PacketEvents.getAPI().load(); - - getLogger().info("Registering PacketEvents"); - PacketEvents.getAPI().getEventManager().registerListener(new PluginCloakingPacket()); - PacketEvents.getAPI().getEventManager().registerListener(new ShadowRealmEvents()); } @Override diff --git a/src/main/java/me/trouper/sentinel/data/config/ViolationConfig.java b/src/main/java/me/trouper/sentinel/data/config/ViolationConfig.java index 122ebb7..11e4f72 100644 --- a/src/main/java/me/trouper/sentinel/data/config/ViolationConfig.java +++ b/src/main/java/me/trouper/sentinel/data/config/ViolationConfig.java @@ -15,13 +15,15 @@ public class ViolationConfig implements JsonSerializable { return file; } - public CommandBlockEdit commandBlockEdit = new CommandBlockEdit(); + public CommandBlockWhitelist commandBlockWhitelist = new CommandBlockWhitelist(); public CommandBlockMinecartPlace commandBlockMinecartPlace = new CommandBlockMinecartPlace(); public CommandBlockMinecartUse commandBlockMinecartUse = new CommandBlockMinecartUse(); + public CommandBlockMinecartEdit commandBlockMinecartEdit = new CommandBlockMinecartEdit(); public CommandBlockMinecartBreak commandBlockMinecartBreak = new CommandBlockMinecartBreak(); public CommandBlockPlace commandBlockPlace = new CommandBlockPlace(); public CommandBlockUse commandBlockUse = new CommandBlockUse(); + public CommandBlockEdit commandBlockEdit = new CommandBlockEdit(); public CommandBlockBreak commandBlockBreak = new CommandBlockBreak(); public CommandExecute commandExecute = new CommandExecute(); public CreativeHotbarAction creativeHotbarAction = new CreativeHotbarAction(); @@ -122,6 +124,16 @@ public class ViolationConfig implements JsonSerializable { ); } + public class CommandBlockMinecartEdit { + public boolean enabled = true; + public boolean deop = true; + public boolean logToDiscord = true; + public boolean punish = false; + public List punishmentCommands = Arrays.asList( + "ban %player% ]=- Sentinel -=[ \nYou have been banned for attempting a dangerous action. \nIf you believe this to be a mistake, please contact the server owner." + ); + } + public class CommandBlockMinecartPlace { public boolean enabled = true; public boolean deop = true; diff --git a/src/main/java/me/trouper/sentinel/data/storage/CommandBlockStorage.java b/src/main/java/me/trouper/sentinel/data/storage/CommandBlockStorage.java index 8390fdb..e9af207 100644 --- a/src/main/java/me/trouper/sentinel/data/storage/CommandBlockStorage.java +++ b/src/main/java/me/trouper/sentinel/data/storage/CommandBlockStorage.java @@ -19,17 +19,15 @@ public class CommandBlockStorage implements JsonSerializable holders = new ArrayList<>() { - @Override - public boolean add(CommandBlockHolder holder) { - for (CommandBlockHolder existing : holders) { - if (existing.loc().isSameLocation(holder.loc())) { - super.remove(existing); - } - } - - return super.add(holder); - } - }; + + public List holders = new ArrayList<>(); + + public synchronized boolean remove(CommandBlockHolder holder) { + return holders.removeIf(existing -> existing.loc().isSameLocation(holder.loc())); + } + public synchronized boolean add(CommandBlockHolder holder) { + holders.removeIf(existing -> existing.loc().isSameLocation(holder.loc())); + return holders.add(holder); + } } diff --git a/src/main/java/me/trouper/sentinel/data/types/CommandBlockHolder.java b/src/main/java/me/trouper/sentinel/data/types/CommandBlockHolder.java index 878db3c..227f81a 100644 --- a/src/main/java/me/trouper/sentinel/data/types/CommandBlockHolder.java +++ b/src/main/java/me/trouper/sentinel/data/types/CommandBlockHolder.java @@ -1,29 +1,36 @@ package me.trouper.sentinel.data.types; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUpdateCommandBlock; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUpdateCommandBlockMinecart; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.startup.drm.Auth; +import me.trouper.sentinel.utils.DisplayUtils; import me.trouper.sentinel.utils.ServerUtils; import me.trouper.sentinel.utils.Text; +import me.trouper.sentinel.utils.display.BlockDisplayRaytracer; import org.bukkit.Bukkit; +import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.CommandBlock; -import org.bukkit.command.Command; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.CommandMinecart; import org.bukkit.persistence.PersistentDataType; +import java.util.List; + public class CommandBlockHolder { - private final String owner; - private final SerialLocation loc; - private final String facing; - private final String type; - private final boolean auto; - private final boolean conditional; - private final String command; + private String owner; + private SerialLocation loc; + private String facing; + private String type; + private boolean auto; + private boolean conditional; + private String command; private boolean whitelisted; public CommandBlockHolder(String owner, SerialLocation loc, String facing, String type, boolean auto, boolean conditional, String command) { @@ -41,28 +48,34 @@ public class CommandBlockHolder { return owner; } + public CommandBlockHolder setOwner(String owner) { + this.owner = owner; + Sentinel.getInstance().getDirector().io.commandBlocks.save(); + return this; + } + public SerialLocation loc() { - return loc; + return this.loc; } public String facing() { - return facing; + return this.facing; } public String type() { - return type; + return this.type; } - public boolean auto() { - return auto; + public boolean isAuto() { + return this.auto; } - public boolean conditional() { - return conditional; + public boolean isConditional() { + return this.conditional; } public String command() { - return command; + return this.command; } public boolean present() { @@ -76,28 +89,33 @@ public class CommandBlockHolder { where.getChunk().load(false); Block b = where.getBlock(); if (!(b.getState() instanceof CommandBlock c) || !(b.getBlockData() instanceof org.bukkit.block.data.type.CommandBlock cb)) { - ServerUtils.verbose("Block is not present due to not being a command block."); - if (!this.whitelisted) this.delete(); + ServerUtils.verbose(1,"Block is not present due to not being a command block. Whitelisted: %s",this.isWhitelisted()); + if (!this.isWhitelisted()) this.delete(); return false; } - if (!this.command.equals(c.getCommand())) { - ServerUtils.verbose("Block is not present due to command mismatch. Should be '%s', is '%s'",this.command,c.getCommand()); - if (!this.whitelisted) this.delete(); - return false; - } - if (this.conditional != cb.isConditional()) { - ServerUtils.verbose("Block is not present due to conditional mismatch."); - if (!this.whitelisted) this.delete(); + if (!this.getDirection().equals(cb.getFacing())) { + ServerUtils.verbose("Block is not present due to facing mismatch. Should be '%s', is '%s'",this.facing(),cb.getFacing()); + if (!this.isWhitelisted()) this.delete(); return false; } if (!this.getType().equals(c.getType())) { - ServerUtils.verbose("Block is not present due to type mismatch. Should be '%s', is '%s'",this.type,c.getType()); - if (!this.whitelisted) this.delete(); + ServerUtils.verbose("Block is not present due to type mismatch. Should be '%s', is '%s'",this.type(),c.getType()); + if (!this.isWhitelisted()) this.delete(); return false; } - if (this.auto != (c.getPersistentDataContainer().getOrDefault(Sentinel.getInstance().getNamespace("auto"), PersistentDataType.BYTE,(byte) 0) == (byte) 1)) { + if (!this.command().equals(c.getCommand())) { + ServerUtils.verbose("Block is not present due to command mismatch. Should be '%s', is '%s'",this.command(),c.getCommand()); + if (!this.isWhitelisted()) this.delete(); + return false; + } + if (this.isConditional() != cb.isConditional()) { + ServerUtils.verbose("Block is not present due to conditional mismatch."); + if (!this.isWhitelisted()) this.delete(); + return false; + } + if (this.isAuto() != (c.getPersistentDataContainer().getOrDefault(Sentinel.getInstance().getNamespace("auto"), PersistentDataType.BYTE,(byte) 0) == (byte) 1)) { ServerUtils.verbose("Block is not present due to auto mismatch."); - if (!this.whitelisted) this.delete(); + if (!this.isWhitelisted()) this.delete(); return false; } if (!preLoaded) where.getChunk().unload(); @@ -105,12 +123,13 @@ public class CommandBlockHolder { } } - public boolean whitelisted() { - return whitelisted; + public boolean isWhitelisted() { + return this.whitelisted; } public CommandBlockHolder setWhitelisted(boolean whitelisted) { this.whitelisted = whitelisted; + Sentinel.getInstance().getDirector().io.commandBlocks.save(); return this; } @@ -119,40 +138,31 @@ public class CommandBlockHolder { } public BlockFace getDirection() { - try { - return BlockFace.valueOf(facing.toUpperCase()); - } catch (IllegalArgumentException e) { - return BlockFace.NORTH; - } + return BlockFace.valueOf(facing().toUpperCase()); } public Material getType() { - return switch (this.type) { - case "COMMAND_BLOCK" -> Material.COMMAND_BLOCK; - case "REPEATING_COMMAND_BLOCK" -> Material.REPEATING_COMMAND_BLOCK; - case "CHAIN_COMMAND_BLOCK" -> Material.CHAIN_COMMAND_BLOCK; - case "COMMAND_BLOCK_MINECART" -> Material.COMMAND_BLOCK_MINECART; - default -> throw new IllegalArgumentException("Unknown command block type: " + type); - }; + return Material.valueOf(type().toUpperCase()); } public void destroy() { + ServerUtils.verbose(1,"Destroying command block..."); SerialLocation.translate(this.loc).getBlock().setType(Material.AIR); - if (!whitelisted) delete(); + if (!this.isWhitelisted()) delete(); } public boolean restore() { if (Material.COMMAND_BLOCK_MINECART.equals(this.getType())) { - ServerUtils.verbose("Cannot restore minecarts yet."); + ServerUtils.verbose(1,"Cannot restore minecarts yet."); return false; } - if (this.present()) return false; + if (this.present() || !this.isWhitelisted()) return false; Block block = SerialLocation.translate(this.loc).getBlock(); block.setType(this.getType()); if (!ServerUtils.isCommandBlock(block)) { - ServerUtils.verbose("Block at the location was not a command block (You shouldn't be seeing this. Report it)."); + ServerUtils.verbose(1,"Block at the location was not a command block (You shouldn't be seeing this. Report it)."); return false; } @@ -163,8 +173,8 @@ public class CommandBlockHolder { block.getState().update(true, false); org.bukkit.block.data.type.CommandBlock conditional = (org.bukkit.block.data.type.CommandBlock) cb.getBlock().getBlockData(); - ServerUtils.verbose("Direction is " + this.getDirection()); - ServerUtils.verbose("Conditional is " + this.conditional); + //ServerUtils.verbose("Direction is " + this.getDirection()); + //ServerUtils.verbose("Conditional is " + this.conditional); conditional.setFacing(this.getDirection()); conditional.setConditional(this.conditional); @@ -177,7 +187,7 @@ public class CommandBlockHolder { this.auto ? (byte) 1 : (byte) 0 ); - cb.update(true,false); + cb.update(true,true); ServerUtils.verbose("Command block at " + this.loc.toString() + " has been restored."); return true; } @@ -187,14 +197,170 @@ public class CommandBlockHolder { } public CommandBlockHolder add() { - Sentinel.getInstance().getDirector().io.commandBlocks.holders.add(this); + ServerUtils.verbose(1,"Adding command block..."); + Sentinel.getInstance().getDirector().io.commandBlocks.add(this); Sentinel.getInstance().getDirector().io.commandBlocks.save(); return this; } public void delete() { - SerialLocation.translate(this.loc).getBlock().setType(Material.AIR); - Sentinel.getInstance().getDirector().io.commandBlocks.holders.removeIf(h->h.loc.isSameLocation(this.loc)); + ServerUtils.verbose(1,"Deleting & Destroying command block..."); + if (this.loc.isUUID() && Bukkit.getEntity(this.loc.toUIID()) != null) Bukkit.getEntity(this.loc.toUIID()).remove(); + else SerialLocation.translate(this.loc).getBlock().setType(Material.AIR); + Sentinel.getInstance().getDirector().io.commandBlocks.remove(this); Sentinel.getInstance().getDirector().io.commandBlocks.save(); } + + public void highlight(Player viewer, Material color) { + if (this.loc.isUUID()) { + Color c = switch (color) { + case RED_CONCRETE_POWDER -> Color.RED; + case LIME_CONCRETE_POWDER -> Color.LIME; + case MAGENTA_CONCRETE_POWDER, PURPLE_CONCRETE_POWDER -> Color.FUCHSIA; + default -> Color.BLACK; + }; + Entity cart = Bukkit.getEntity(this.loc.toUIID()); + if (cart == null) return; + for (int i = 0; i < 5; i++) { + DisplayUtils.ring(cart.getLocation().clone().add(0, (double) i /5,0),0.6, (location) -> { + DisplayUtils.PLAYER_DUST_PARTICLE_FACTORY.apply(c,1F).accept(viewer,location); + },((location, integer) -> { + return integer % 36 == 0; + })); + } + } else { + BlockDisplayRaytracer.outline(color, this.loc.translate(), 0.05, 2, List.of(viewer)); + } + } + + public boolean update(Player updater) { + ServerUtils.verbose(1,"Processing update requested by %s",updater.getName()); + if (this.isWhitelisted()) return false; + boolean changesMade = false; + if (!this.owner().equals(updater.getUniqueId().toString())) { + this.owner = updater.getUniqueId().toString(); + changesMade = true; + } + if (this.loc.isUUID()) { + Entity cart = Bukkit.getEntity(this.loc.toUIID()); + if (!(cart instanceof CommandMinecart cm)) return false; + if (!cm.getCommand().equals(this.command())) { + this.command = cm.getCommand(); + changesMade = true; + } + } else { + Location where = loc.translate(); + boolean preLoaded = where.isChunkLoaded(); + where.getChunk().load(false); + Block b = where.getBlock(); + if (!(b.getState() instanceof CommandBlock c) || !(b.getBlockData() instanceof org.bukkit.block.data.type.CommandBlock cb)) { + ServerUtils.verbose(1,"Block cannot be updated due to not being a command block. It will be deleted if it is not whitelisted. Whitelisted: %s",this.isWhitelisted()); + if (!this.isWhitelisted()) this.delete(); + return false; + } + if (!this.getDirection().equals(cb.getFacing())) { + ServerUtils.verbose("Block needs update due to facing mismatch. Should be '%s', is '%s'",this.facing(),cb.getFacing()); + this.facing = cb.getFacing().toString(); + changesMade = true; + } + if (!this.getType().equals(c.getType())) { + ServerUtils.verbose("Block needs update due to type mismatch. Should be '%s', is '%s'",this.type(),c.getType()); + this.type = c.getType().toString(); + changesMade = true; + + } + if (!this.command().equals(c.getCommand())) { + ServerUtils.verbose("Block needs update due to command mismatch. Should be '%s', is '%s'",this.command(),c.getCommand()); + this.command = c.getCommand(); + changesMade = true; + + } + if (this.isConditional() != cb.isConditional()) { + ServerUtils.verbose("Block needs update due to conditional mismatch."); + this.conditional = cb.isConditional(); + changesMade = true; + + } + if (this.isAuto() != (c.getPersistentDataContainer().getOrDefault(Sentinel.getInstance().getNamespace("auto"), PersistentDataType.BYTE,(byte) 0) == (byte) 1)) { + ServerUtils.verbose("Block needs update due to auto mismatch."); + this.auto = (c.getPersistentDataContainer().getOrDefault(Sentinel.getInstance().getNamespace("auto"), PersistentDataType.BYTE,(byte) 0) == (byte) 1); + changesMade = true; + } + if (!preLoaded) where.getChunk().unload(); + } + + if (changesMade) updater.sendMessage(Text.prefix("Successfully updated a &b%s&7.".formatted(Text.cleanName(this.type())))); + return changesMade; + } + + public boolean update(Player updater, WrapperPlayClientUpdateCommandBlockMinecart packet) { + ServerUtils.verbose(1,"Processing packet update requested by %s",updater.getName()); + if (this.isWhitelisted()) return false; + boolean changesMade = false; + if (!this.owner().equals(updater.getUniqueId().toString())) { + this.owner = updater.getUniqueId().toString(); + changesMade = true; + } + + if (!this.loc.isUUID()) { + throw new IllegalArgumentException("Cannot update block commands with this packet."); + } + + if (!this.command().equals(packet.getCommand())) { + ServerUtils.verbose("Block needs update due to command mismatch. Should be '%s', is '%s'",this.command(),packet.getCommand()); + this.command = packet.getCommand(); + changesMade = true; + + } + + if (changesMade) updater.sendMessage(Text.prefix("Successfully updated a &b%s&7.".formatted(Text.cleanName(this.type())))); + return changesMade; + } + + public boolean update(Player updater, WrapperPlayClientUpdateCommandBlock packet) { + ServerUtils.verbose(1,"Processing packet update requested by %s",updater.getName()); + if (this.isWhitelisted()) return false; + boolean changesMade = false; + if (!this.owner().equals(updater.getUniqueId().toString())) { + this.owner = updater.getUniqueId().toString(); + changesMade = true; + } + + if (this.loc.isUUID()) { + throw new IllegalArgumentException("Cannot update UUID command blocks with this packet."); + } + + Material t = switch (packet.getMode()) { + case AUTO -> Material.REPEATING_COMMAND_BLOCK; + case REDSTONE -> Material.COMMAND_BLOCK; + case SEQUENCE -> Material.CHAIN_COMMAND_BLOCK; + }; + + if (!this.getType().equals(t)) { + ServerUtils.verbose("Block needs update due to type mismatch. Should be '%s', is '%s'",this.type(),t.toString()); + this.type = t.toString(); + changesMade = true; + + } + if (!this.command().equals(packet.getCommand())) { + ServerUtils.verbose("Block needs update due to command mismatch. Should be '%s', is '%s'",this.command(),packet.getCommand()); + this.command = packet.getCommand(); + changesMade = true; + + } + if (this.isConditional() != packet.isConditional()) { + ServerUtils.verbose("Block needs update due to conditional mismatch."); + this.conditional = packet.isConditional(); + changesMade = true; + + } + if (this.isAuto() != packet.isAutomatic()) { + ServerUtils.verbose("Block needs update due to auto mismatch."); + this.auto = packet.isAutomatic(); + changesMade = true; + } + + if (changesMade) updater.sendMessage(Text.prefix("Successfully updated a &b%s&7.".formatted(Text.cleanName(this.type())))); + return changesMade; + } } diff --git a/src/main/java/me/trouper/sentinel/server/commands/SentinelCommand.java b/src/main/java/me/trouper/sentinel/server/commands/SentinelCommand.java index 64a4245..2337534 100644 --- a/src/main/java/me/trouper/sentinel/server/commands/SentinelCommand.java +++ b/src/main/java/me/trouper/sentinel/server/commands/SentinelCommand.java @@ -207,14 +207,14 @@ public class SentinelCommand implements CustomCommand { if (p.getTargetEntity(10) instanceof CommandMinecart cm) { Sentinel.getInstance().getDirector().whitelistManager - .generateHolder(p.getUniqueId(), cm).addToWhitelist(); + .generateHolder(p.getUniqueId(), cm).addAndWhitelist(); return; } Block target = p.getTargetBlock(Set.of(Material.AIR), 10); if (ServerUtils.isCommandBlock(target)) { CommandBlock cb = (CommandBlock) target.getState(); Sentinel.getInstance().getDirector().whitelistManager - .generateHolder(p.getUniqueId(), cb).addToWhitelist(); + .generateHolder(p.getUniqueId(), cb).addAndWhitelist(); } else { sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.notCommandBlock .formatted(Text.cleanName(target.getType().toString())))); @@ -228,8 +228,9 @@ public class SentinelCommand implements CustomCommand { if (p.getTargetEntity(10) instanceof CommandMinecart cm) { CommandBlockHolder wb = Sentinel.getInstance().getDirector().whitelistManager - .generateHolder(p.getUniqueId(), cm); - if (wb.removeFromWhitelist()) { + .getFromList(cm.getUniqueId()); + if (wb != null) { + wb.setWhitelisted(false); String cleanedType = Text.cleanName(SerialLocation.translate(wb.loc()).getBlock().getType().toString()); sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.removeSuccess .formatted(cleanedType, wb.command()))); @@ -242,8 +243,9 @@ public class SentinelCommand implements CustomCommand { Block target = p.getTargetBlock(Set.of(Material.AIR), 10); CommandBlockHolder wb = Sentinel.getInstance().getDirector().whitelistManager - .getFromWhitelist(target.getLocation()); - if (wb != null && wb.removeFromWhitelist()) { + .getFromList(target.getLocation()); + if (wb != null) { + wb.setWhitelisted(false); String cleanedType = Text.cleanName(SerialLocation.translate(wb.loc()).getBlock().getType().toString()); sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.removeSuccess .formatted(cleanedType, wb.command()))); @@ -261,10 +263,10 @@ public class SentinelCommand implements CustomCommand { var whitelistManager = Sentinel.getInstance().getDirector().whitelistManager; if (whitelistManager.autoWhitelist.contains(p.getUniqueId())) { whitelistManager.autoWhitelist.remove(p.getUniqueId()); - sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.autoWhitelistOn)); + sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.autoWhitelistOff)); } else { whitelistManager.autoWhitelist.add(p.getUniqueId()); - sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.autoWhitelistOff)); + sender.sendMessage(Text.prefix(Sentinel.getInstance().getDirector().io.lang.commandBlock.autoWhitelistOn)); } } diff --git a/src/main/java/me/trouper/sentinel/server/events/admin/WandEvents.java b/src/main/java/me/trouper/sentinel/server/events/admin/WandEvents.java index c82807a..f4944a8 100644 --- a/src/main/java/me/trouper/sentinel/server/events/admin/WandEvents.java +++ b/src/main/java/me/trouper/sentinel/server/events/admin/WandEvents.java @@ -6,14 +6,11 @@ import io.github.itzispyder.pdk.utils.misc.SoundPlayer; import me.trouper.sentinel.Sentinel; import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.data.types.Selection; -import me.trouper.sentinel.utils.DisplayUtils; +import me.trouper.sentinel.utils.Text; import me.trouper.sentinel.utils.PlayerUtils; import me.trouper.sentinel.utils.ServerUtils; -import me.trouper.sentinel.utils.Text; -import me.trouper.sentinel.utils.display.BlockDisplayRaytracer; import org.bukkit.*; import org.bukkit.block.CommandBlock; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.CommandMinecart; import org.bukkit.event.EventHandler; @@ -24,8 +21,6 @@ import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.inventory.ItemStack; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; public class WandEvents implements CustomListener { public static final ItemStack SELECTION_WAND = ItemBuilder.create() @@ -50,11 +45,7 @@ public class WandEvents implements CustomListener { .build(); public static final Map selections = new HashMap<>(); - private static final ConcurrentLinkedQueue blockHighlights = new ConcurrentLinkedQueue<>(); - private static final Map> playerBlockHighlights = new ConcurrentHashMap<>(); - private static final ConcurrentLinkedQueue entityHighlights = new ConcurrentLinkedQueue<>(); - private static final Map> playerEntityHighlights = new ConcurrentHashMap<>(); - + @EventHandler public void onClickEntity(PlayerInteractEntityEvent e) { Player p = e.getPlayer(); @@ -63,15 +54,15 @@ public class WandEvents implements CustomListener { if (!i.isSimilar(SELECTION_WAND)) return; if (!PlayerUtils.isTrusted(p)) return; - SoundPlayer add = new SoundPlayer(p.getLocation(),Sound.ENTITY_EXPERIENCE_ORB_PICKUP,100,1); + SoundPlayer add = new SoundPlayer(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 100, 1); if (!(e.getRightClicked() instanceof CommandMinecart cm)) return; - + e.setCancelled(true); - - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cm).addToWhitelist(); + + Sentinel.getInstance().getDirector().whitelistManager.getFromList(cm.getUniqueId()).setWhitelisted(true); add.play(p); } - + @EventHandler public void onDamage(VehicleDamageEvent e) { if (!(e.getAttacker() instanceof Player p)) return; @@ -80,15 +71,15 @@ public class WandEvents implements CustomListener { if (!i.isSimilar(SELECTION_WAND)) return; if (!PlayerUtils.isTrusted(p)) return; - SoundPlayer remove = new SoundPlayer(p.getLocation(),Sound.BLOCK_GLASS_BREAK,100,1); - + SoundPlayer remove = new SoundPlayer(p.getLocation(), Sound.BLOCK_GLASS_BREAK, 100, 1); + if (!(e.getVehicle() instanceof CommandMinecart cm)) return; e.setCancelled(true); - - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cm).removeFromWhitelist(); + + Sentinel.getInstance().getDirector().whitelistManager.getFromList(cm.getUniqueId()).setWhitelisted(false); remove.play(p); } - + @EventHandler public void onClick(PlayerInteractEvent e) { Player p = e.getPlayer(); @@ -97,166 +88,97 @@ public class WandEvents implements CustomListener { if (!i.isSimilar(SELECTION_WAND)) return; if (!PlayerUtils.isTrusted(p)) return; - SoundPlayer add = new SoundPlayer(p.getLocation(),Sound.ENTITY_EXPERIENCE_ORB_PICKUP,100,1); - SoundPlayer remove = new SoundPlayer(p.getLocation(),Sound.BLOCK_GLASS_BREAK,100,1); - SoundPlayer set1 = new SoundPlayer(p.getLocation(),Sound.UI_BUTTON_CLICK,100,1); - SoundPlayer set2 = new SoundPlayer(p.getLocation(),Sound.UI_BUTTON_CLICK,100,0.8F); - + SoundPlayer add = new SoundPlayer(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 100, 1); + SoundPlayer remove = new SoundPlayer(p.getLocation(), Sound.BLOCK_GLASS_BREAK, 100, 1); + SoundPlayer set1 = new SoundPlayer(p.getLocation(), Sound.UI_BUTTON_CLICK, 100, 1); + SoundPlayer set2 = new SoundPlayer(p.getLocation(), Sound.UI_BUTTON_CLICK, 100, 0.8F); + Selection selection = selections.computeIfAbsent(p.getUniqueId(), k -> new Selection()); if (p.getTargetBlockExact(10) == null) return; Location loc = p.getTargetBlockExact(10).getLocation(); - + if (e.getAction() == Action.LEFT_CLICK_BLOCK) { e.setCancelled(true); if (p.isSneaking() && ServerUtils.isCommandBlock(loc.getBlock())) { set1.play(p); - setPos1(p,selection,loc); + setPos1(p, selection, loc); } else if (ServerUtils.isCommandBlock(loc.getBlock())) { remove.play(p); - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),(CommandBlock) loc.getBlock().getState()).removeFromWhitelist(); + Sentinel.getInstance().getDirector().whitelistManager.getFromList(loc).setWhitelisted(false); } else { set1.play(p); - setPos1(p,selection,loc); + setPos1(p, selection, loc); } } else if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { - e.setCancelled(true); if (p.isSneaking() && ServerUtils.isCommandBlock(loc.getBlock())) { + e.setCancelled(true); set2.play(p); - setPos2(p,selection,loc); + setPos2(p, selection, loc); } else if (ServerUtils.isCommandBlock(loc.getBlock())) { add.play(p); - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),(CommandBlock) loc.getBlock().getState()).addToWhitelist(); + Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),(CommandBlock) loc.getBlock().getState()).addAndWhitelist(); + e.setCancelled(true); } else { + e.setCancelled(true); set2.play(p); - setPos2(p,selection,loc); + setPos2(p, selection, loc); } } } - - private record EntityHighlight(Player beholder, Entity ent, Color color) { - public void display() { - for (int i = 0; i < 5; i++) { - DisplayUtils.ring(ent.getLocation().clone().add(0, (double) i /5,0),0.6, (location) -> { - DisplayUtils.PLAYER_DUST_PARTICLE_FACTORY.apply(color,1F).accept(beholder,location); - },((location, integer) -> { - return integer % 36 == 0; - })); - } - } - } - - private record BlockHighlight(Player beholder, Location loc, Material color) { - public void display() { - BlockDisplayRaytracer.outline(color, loc, 0.05, 2, List.of(beholder)); - } - } private static void sortNear(Player p) { ItemStack i = p.getInventory().getItemInMainHand(); if (!i.isSimilar(SELECTION_WAND) || !PlayerUtils.isTrusted(p)) { - Set existingBlocks = playerBlockHighlights.remove(p.getUniqueId()); - Set existingEntities = playerEntityHighlights.remove(p.getUniqueId()); - if (existingBlocks != null) { - blockHighlights.removeAll(existingBlocks); - entityHighlights.removeAll(existingEntities); - } return; } - Set currentBlocks = new HashSet<>(); - Set currentEntities = new HashSet<>(); + // Highlight nearby command blocks Selection around = new Selection(); around.setPos1(p.getLocation().add(-10, -10, -10)); around.setPos2(p.getLocation().add(10, 10, 10)); around.getBlocks().stream() .filter(block -> ServerUtils.isCommandBlock(block) && block.getLocation().distance(p.getLocation()) <= 10) .forEach(block -> { - CommandBlock cb = (CommandBlock) block.getState(); - CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cb); - Material color = holder.isWhitelisted() - ? Material.LIME_CONCRETE_POWDER - : Material.RED_CONCRETE_POWDER; - if (holder.isUnknown()) { - color = Material.BLACK_CONCRETE_POWDER; - holder.addToExisting(); + if (!(block.getState() instanceof CommandBlock cb)) return; + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(block.getLocation()); + Material color = Material.BLACK_CONCRETE_POWDER; + if (holder == null) { + holder = Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(), cb); + holder.add(); + } else { + color = holder.isWhitelisted() ? Material.LIME_CONCRETE_POWDER : Material.RED_CONCRETE_POWDER; } - currentBlocks.add(new BlockHighlight(p, block.getLocation(), color)); + holder.highlight(p, color); }); - List carts = p.getNearbyEntities(10,10,10).stream().filter(entity -> entity instanceof CommandMinecart).toList(); + // Highlight nearby command minecarts + p.getNearbyEntities(10, 10, 10).stream() + .filter(entity -> entity instanceof CommandMinecart) + .forEach(entity -> { + CommandMinecart cm = (CommandMinecart) entity; + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cm.getUniqueId()); + Material color = Material.BLACK_CONCRETE_POWDER; + if (holder == null) { + holder = Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(), cm); + holder.add(); + } else { + color = holder.isWhitelisted() ? Material.LIME_CONCRETE_POWDER : Material.RED_CONCRETE_POWDER; + } + holder.highlight(p, color); + }); - for (Entity cart : carts) { - if (!(cart instanceof CommandMinecart cm)) continue; - CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cm); - Color color = holder.isWhitelisted() - ? Color.fromRGB(0x00FF00) - : Color.fromRGB(0xFF0000); - if (holder.isUnknown()) { - color = Color.fromRGB(0); - holder.addToExisting(); - } - currentEntities.add(new EntityHighlight(p, cart, color)); - } - - for (CommandBlockHolder wl : Sentinel.getInstance().getDirector().io.commandBlocks.whitelistedCMDBlocks) { - if (!wl.isPresent() && !wl.isCart()) { - currentBlocks.add(new BlockHighlight(p, wl.loc().translate(), Material.PURPLE_CONCRETE_POWDER)); - } - } - - Set previousBlocks = playerBlockHighlights.getOrDefault(p.getUniqueId(), new HashSet<>()); - Set blocksToAdd = new HashSet<>(currentBlocks); - blocksToAdd.removeAll(previousBlocks); - Set blocksToRemove = new HashSet<>(previousBlocks); - blocksToRemove.removeAll(currentBlocks); - - Set previousEntities = playerEntityHighlights.getOrDefault(p.getUniqueId(), new HashSet<>()); - Set entitiesToAdd = new HashSet<>(currentEntities); - entitiesToAdd.removeAll(previousEntities); - Set entitiesToRemove = new HashSet<>(previousEntities); - entitiesToRemove.removeAll(currentEntities); - - blockHighlights.addAll(blocksToAdd); - blockHighlights.removeAll(blocksToRemove); - playerBlockHighlights.put(p.getUniqueId(), currentBlocks); - - entityHighlights.addAll(entitiesToAdd); - entityHighlights.removeAll(entitiesToRemove); - playerEntityHighlights.put(p.getUniqueId(), currentEntities); + // Highlight missing command blocks + List holdersCopy = new ArrayList<>(Sentinel.getInstance().getDirector().io.commandBlocks.holders); + holdersCopy.forEach(holder -> { + if (!holder.present() && holder.isWhitelisted()) holder.highlight(p,Material.MAGENTA_CONCRETE_POWDER); + }); } public static void handleDisplay() { PlayerUtils.forEachTrusted(WandEvents::sortNear); - Iterator blockIterator = blockHighlights.iterator(); - while (blockIterator.hasNext()) { - BlockHighlight bh = blockIterator.next(); - if (bh.beholder == null || !bh.beholder.isOnline() || bh.loc.distance(bh.beholder.getLocation()) > 10) { - blockIterator.remove(); - playerBlockHighlights.computeIfPresent(bh.beholder.getUniqueId(), (uuid, set) -> { - set.remove(bh); - return set.isEmpty() ? null : set; - }); - } else { - bh.display(); - } - } - - Iterator entityIterator = entityHighlights.iterator(); - while (entityIterator.hasNext()) { - EntityHighlight eh = entityIterator.next(); - if (eh.beholder == null || !eh.beholder.isOnline() || eh.ent.getLocation().distance(eh.beholder.getLocation()) > 10) { - entityIterator.remove(); - playerEntityHighlights.computeIfPresent(eh.beholder.getUniqueId(), (uuid,set) -> { - set.remove(eh); - return set.isEmpty() ? null : set; - }); - } else { - eh.display(); - } - } - + // Display selections selections.forEach((uuid, selection) -> { Player p = Bukkit.getPlayer(uuid); if (p == null || !p.isOnline() || !p.getInventory().getItemInMainHand().isSimilar(SELECTION_WAND)) return; @@ -275,4 +197,4 @@ public class WandEvents implements CustomListener { selection.setPos1(loc); p.sendMessage(Text.prefix("Position 1 set to " + Text.formatLoc(loc))); } -} +} \ No newline at end of file diff --git a/src/main/java/me/trouper/sentinel/server/events/extras/ShadowRealmEvents.java b/src/main/java/me/trouper/sentinel/server/events/extras/ShadowRealmEvents.java index c2e6a30..c24785e 100644 --- a/src/main/java/me/trouper/sentinel/server/events/extras/ShadowRealmEvents.java +++ b/src/main/java/me/trouper/sentinel/server/events/extras/ShadowRealmEvents.java @@ -1,6 +1,7 @@ package me.trouper.sentinel.server.events.extras; import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListener; import com.github.retrooper.packetevents.event.PacketListenerAbstract; import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.event.PacketSendEvent; @@ -20,7 +21,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerJoinEvent; -public class ShadowRealmEvents extends PacketListenerAbstract implements CustomListener { +public class ShadowRealmEvents implements CustomListener, PacketListener { @EventHandler public void onJoin(PlayerJoinEvent e) { diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockBreak.java b/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockBreak.java index 2c8ed79..0c1cd0e 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockBreak.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockBreak.java @@ -33,9 +33,10 @@ public class CommandBlockBreak extends AbstractViolation{ ServerUtils.verbose("CommandBlockBreak: Block is a command block"); Player p = e.getPlayer(); CommandBlock cb = (CommandBlock) b.getState(); - CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cb); + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cb.getLocation()); if (PlayerUtils.isTrusted(e.getPlayer())) { - if (!Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) { + if (Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) { + ServerUtils.verbose("Auto Whitelist is on, un-whitelisting the command block."); holder.setWhitelisted(false); holder.delete(); } @@ -43,6 +44,7 @@ public class CommandBlockBreak extends AbstractViolation{ } if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockBreak.enabled) { + ServerUtils.verbose("Not enabled, deletion allowed."); holder.delete(); return; } diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockEdit.java b/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockEdit.java index 1ba0f16..7386dae 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockEdit.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockEdit.java @@ -1,7 +1,16 @@ package me.trouper.sentinel.server.events.violations.blocks.command; +import com.github.retrooper.packetevents.event.PacketListener; +import com.github.retrooper.packetevents.event.PacketListenerAbstract; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientTabComplete; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUpdateCommandBlock; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.gui.Items; @@ -10,6 +19,8 @@ import me.trouper.sentinel.server.gui.config.AntiNukeGUI; import me.trouper.sentinel.utils.PlayerUtils; import me.trouper.sentinel.utils.ServerUtils; import me.trouper.sentinel.utils.Text; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.CommandBlock; @@ -22,44 +33,54 @@ import org.bukkit.inventory.ItemStack; import java.util.List; -public class CommandBlockEdit extends AbstractViolation { +public class CommandBlockEdit extends AbstractViolation implements PacketListener { - @EventHandler - private void onCMDBlockChange(EntityChangeBlockEvent e) { - //ServerUtils.verbose("CommandBlockChange: Detected the event"); - if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockEdit.enabled) return; - //ServerUtils.verbose("CommandBlockChange: Enabled"); - if (!(e.getEntity() instanceof Player p)) return; - //ServerUtils.verbose("CommandBlockChange: Changer is a player"); - Block b = e.getBlock(); - if (!(ServerUtils.isCommandBlock(b))) - return; - ServerUtils.verbose("CommandBlockChange: Block is a command block"); - CommandBlock cb = (CommandBlock) b.getState(); + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (event.getPacketType() != PacketType.Play.Client.UPDATE_COMMAND_BLOCK) return; + ServerUtils.verbose("Packet is a command block update packet"); + + WrapperPlayClientUpdateCommandBlock wrapper = new WrapperPlayClientUpdateCommandBlock(event); + User user = event.getUser(); + Player p = Bukkit.getPlayer(user.getUUID()); + if (p == null) return; + + Vector3i pos = wrapper.getPosition(); + Location loc = new Location(p.getWorld(), pos.x, pos.y, pos.z); + + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(loc); if (PlayerUtils.isTrusted(p)) { - if (!Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) return; - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cb).addToWhitelist(); + if (Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) holder.setWhitelisted(true); + holder.update(p,wrapper); return; } - ServerUtils.verbose("CommandBlockChange: Not trusted, performing action"); + + if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockEdit.enabled) { + holder.update(p,wrapper); + return; + } + + ServerUtils.verbose("Enabled, performing action"); + + event.setCancelled(true); ActionConfiguration.Builder config = new ActionConfiguration.Builder() - .setEvent(e) .setPlayer(p) .deop(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockEdit.deop) - .cancel(true) .punish(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockEdit.punish) .setPunishmentCommands(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockEdit.punishmentCommands) .logToDiscord(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockEdit.logToDiscord); + runActions( Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.edit, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandBlock), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.edit, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandBlock), - generateCommandBlockInfo(cb), + generateCommandBlockInfo((CommandBlock) holder.loc().translate().getBlock().getState()), config ); } + @Override public CustomGui getConfigGui() { return CustomGui.create() diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockUse.java b/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockUse.java index 8dba11f..224218f 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockUse.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/blocks/command/CommandBlockUse.java @@ -2,6 +2,7 @@ package me.trouper.sentinel.server.events.violations.blocks.command; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.gui.Items; @@ -26,22 +27,24 @@ public class CommandBlockUse extends AbstractViolation { @EventHandler private void onCMDBlockUse(PlayerInteractEvent e) { - //ServerUtils.verbose("CommandBlockUse: Detected Interaction"); - if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockUse.enabled) return; - //ServerUtils.verbose("CommandBlockUse: Enabled"); Player p = e.getPlayer(); if (e.getClickedBlock() == null) return; //ServerUtils.verbose("CommandBlockUse: Block isn't null"); Block b = e.getClickedBlock(); if (!(ServerUtils.isCommandBlock(b))) return; CommandBlock cb = (CommandBlock) b.getState(); + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cb.getLocation()); if (PlayerUtils.isTrusted(p)) { - if (!Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) return; - if (Sentinel.getInstance().getDirector().whitelistManager.isWhitelisted(cb)) return; - e.setCancelled(true); - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(), cb).addToWhitelist(); + if (Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) holder.setWhitelisted(true); + holder.update(p); return; } + + if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockUse.enabled) { + holder.update(p); + return; + } + ServerUtils.verbose("CommandBlockUse: Not trusted, performing action"); ActionConfiguration.Builder config = new ActionConfiguration.Builder() diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartBreak.java b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartBreak.java index 2cfe79d..31a14fc 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartBreak.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartBreak.java @@ -2,6 +2,7 @@ package me.trouper.sentinel.server.events.violations.entities; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.gui.Items; @@ -16,6 +17,7 @@ import org.bukkit.entity.minecart.CommandMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.vehicle.VehicleDamageEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -23,33 +25,39 @@ import java.util.List; public class CommandMinecartBreak extends AbstractViolation { @EventHandler - public void onBreak(EntityDamageEvent e) { - //ServerUtils.verbose("CommandBlockBreak: Detected the event"); - if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartBreak.enabled) return; - //ServerUtils.verbose("CommandBlockBreak: Changer is a player"); - if (!(e.getEntity() instanceof CommandMinecart s)) return; - if (e.getDamageSource() == null) { + public void onBreak(VehicleDamageEvent e) { + if (!(e.getVehicle() instanceof CommandMinecart cm)) return; + if (e.getAttacker() == null) { e.setCancelled(true); return; } - if (e.getDamageSource().getCausingEntity() == null) { - e.setCancelled(true); - return; - } - if (!(e.getDamageSource().getCausingEntity() instanceof Player p)) { + if (!(e.getAttacker() instanceof Player p)) { e.setCancelled(true); return; } + + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cm.getUniqueId()); if (PlayerUtils.isTrusted(p)) { - if (Sentinel.getInstance().getDirector().whitelistManager.getFromExisting(s.getLocation()).isWhitelisted()) return; - Sentinel.getInstance().getDirector().whitelistManager.getFromExisting(s.getLocation()).removeFromExisting(); + if (Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) { + ServerUtils.verbose("Auto Whitelist is on, un-whitelisting the command minecart."); + holder.setWhitelisted(false); + holder.delete(); + } return; } + + if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartBreak.enabled) { + ServerUtils.verbose("Not enabled, deletion allowed."); + holder.delete(); + return; + } + + ServerUtils.verbose("Not trusted, performing action"); ActionConfiguration.Builder config = new ActionConfiguration.Builder() .setEvent(e) - .setEntity(s) + .setEntity(cm) .setPlayer(p) .deop(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockBreak.deop) .cancel(true) @@ -60,7 +68,7 @@ public class CommandMinecartBreak extends AbstractViolation { runActions( Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.brake, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandMinecart), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.brake, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandMinecart), - generateMinecartInfo(s), + generateMinecartInfo(cm), config ); } diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartEdit.java b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartEdit.java new file mode 100644 index 0000000..47bdf2c --- /dev/null +++ b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartEdit.java @@ -0,0 +1,172 @@ +package me.trouper.sentinel.server.events.violations.entities; + +import com.github.retrooper.packetevents.event.PacketListener; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUpdateCommandBlock; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUpdateCommandBlockMinecart; +import io.github.itzispyder.pdk.plugin.gui.CustomGui; +import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; +import me.trouper.sentinel.server.events.violations.AbstractViolation; +import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; +import me.trouper.sentinel.server.gui.Items; +import me.trouper.sentinel.server.gui.MainGUI; +import me.trouper.sentinel.server.gui.config.AntiNukeGUI; +import me.trouper.sentinel.utils.PlayerUtils; +import me.trouper.sentinel.utils.ServerUtils; +import me.trouper.sentinel.utils.Text; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.CommandBlock; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.minecart.CommandMinecart; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.UUID; + +public class CommandMinecartEdit extends AbstractViolation implements PacketListener { + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (event.getPacketType() != PacketType.Play.Client.UPDATE_COMMAND_BLOCK_MINECART) return; + ServerUtils.verbose("Packet is a command block update packet"); + + WrapperPlayClientUpdateCommandBlockMinecart wrapper = new WrapperPlayClientUpdateCommandBlockMinecart(event); + User user = event.getUser(); + Player p = Bukkit.getPlayer(user.getUUID()); + if (p == null) return; + + if (!(getEntityById(p.getWorld(),wrapper.getEntityId()) instanceof CommandMinecart cart)) { + ServerUtils.verbose("Packet is a canceled due to bad entity UUID"); + event.setCancelled(true); + return; + } + + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cart.getUniqueId()); + if (PlayerUtils.isTrusted(p)) { + if (Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) holder.setWhitelisted(true); + holder.update(p,wrapper); + return; + } + + if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.enabled) { + holder.update(p,wrapper); + return; + } + + ServerUtils.verbose("Enabled, performing action"); + + event.setCancelled(true); + + ActionConfiguration.Builder config = new ActionConfiguration.Builder() + .setPlayer(p) + .deop(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.deop) + .punish(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punish) + .setPunishmentCommands(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punishmentCommands) + .logToDiscord(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.logToDiscord); + + + runActions( + Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.edit, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandBlock), + Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.edit, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandBlock), + generateMinecartInfo(cart), + config + ); + } + + private Entity getEntityById(World world, int entityId) { + for (Entity entity : world.getEntities()) { + if (entity.getEntityId() == entityId) { + return entity; + } + } + return null; // Entity with the given ID not found + } + + @Override + public CustomGui getConfigGui() { + return CustomGui.create() + .title(Text.color("&6&lSentinel &8»&0 Command Block Edit")) + .size(27) + .onDefine(this::getMainPage) + .defineMain(this::onClick) + .define(26, Items.BACK, e->{ + e.getWhoClicked().openInventory(new AntiNukeGUI().home.getInventory()); + }) + .build(); + } + + @Override + public void getMainPage(Inventory inv) { + for (int i = 0; i < inv.getSize(); i++) { + inv.setItem(i,Items.BLANK); + } + + ItemStack ring = Items.RED; + if (Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.enabled) { + ring = Items.GREEN; + } + + List ringList = List.of(3,4,5,12,14,21,22,23); + + for (Integer i : ringList) { + inv.setItem(i,ring); + } + + inv.setItem(26,Items.BACK); + inv.setItem(13,Items.booleanItem(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.enabled,Items.configItem("Check Toggle", Material.CLOCK,"Enable/Disable this check entirely"))); + inv.setItem(2,Items.booleanItem(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.deop,Items.configItem("De-Op",Material.END_CRYSTAL,"Remove the user's operator privileges"))); + inv.setItem(20,Items.booleanItem(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.logToDiscord,Items.configItem("Log",Material.OAK_LOG,"If this check will produce a log to discord"))); + inv.setItem(6,Items.booleanItem(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punish,Items.configItem("Punish",Material.REDSTONE_TORCH,"Run the punishment commands"))); + inv.setItem(24,Items.stringListItem(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punishmentCommands,Material.DIAMOND_AXE,"Punishment Commands","Commands that will be ran \nif this check is flagged.")); + } + + @Override + public void onClick(InventoryClickEvent e) { + e.setCancelled(true); + if (!MainGUI.verify((Player) e.getWhoClicked())) return; + switch (e.getSlot()) { + case 13 -> { + Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.enabled = !Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.enabled; + getMainPage(e.getInventory()); + Sentinel.getInstance().getDirector().io.violationConfig.save(); + } + case 2 -> { + Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.deop = !Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.deop; + getMainPage(e.getInventory()); + Sentinel.getInstance().getDirector().io.violationConfig.save(); + } + case 20 -> { + Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.logToDiscord = !Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.logToDiscord; + getMainPage(e.getInventory()); + Sentinel.getInstance().getDirector().io.violationConfig.save(); + } + case 6 -> { + Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punish = !Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punish; + getMainPage(e.getInventory()); + Sentinel.getInstance().getDirector().io.violationConfig.save(); + } + + case 24 -> { + if (e.isLeftClick()) { + queuePlayer((Player) e.getWhoClicked(), (cfg, args) -> { + cfg.commandBlockMinecartEdit.punishmentCommands.add(args.getAll().toString()); + },"" + Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punishmentCommands); + return; + } + Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartEdit.punishmentCommands.clear(); + getMainPage(e.getInventory()); + Sentinel.getInstance().getDirector().io.violationConfig.save(); + } + + } + } +} diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartPlace.java b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartPlace.java index 6fea190..ba80c9d 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartPlace.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartPlace.java @@ -2,6 +2,7 @@ package me.trouper.sentinel.server.events.violations.entities; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.gui.Items; @@ -51,7 +52,7 @@ public class CommandMinecartPlace extends AbstractViolation { @EventHandler private void onVehicleCreate(VehicleCreateEvent e) { //ServerUtils.verbose("Vehicle Creation Event"); - if (!(e.getVehicle() instanceof CommandMinecart commandMinecart)) return; + if (!(e.getVehicle() instanceof CommandMinecart cm)) return; if (queuedInteractions.isEmpty()) { ServerUtils.verbose("Queue is empty, preventing"); e.setCancelled(true); @@ -69,14 +70,17 @@ public class CommandMinecartPlace extends AbstractViolation { e.setCancelled(true); return; } - + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),cm); if (PlayerUtils.isTrusted(p)) { - ServerUtils.verbose("Player is trusted, allowing."); - Sentinel.getInstance().getDirector().whitelistManager.generateHolder(p.getUniqueId(),commandMinecart) - .addToExisting(); + if (!Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) holder.addAndWhitelist(); + holder.add(); + return; + } + + if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartPlace.enabled) { + holder.add(); return; } - ActionConfiguration.Builder config = new ActionConfiguration.Builder() .setEvent(e) @@ -93,7 +97,7 @@ public class CommandMinecartPlace extends AbstractViolation { runActions( Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.place, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandMinecart), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.place, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandMinecart), - generateMinecartInfo(commandMinecart), + generateMinecartInfo(cm), config ); } @@ -101,7 +105,6 @@ public class CommandMinecartPlace extends AbstractViolation { @EventHandler private void onIneteract(PlayerInteractEvent e) { //ServerUtils.verbose("Player Interaction Event"); - if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartPlace.enabled) return; //ServerUtils.verbose("MinecartCommandPlace: Check is enabled"); Player p = e.getPlayer(); if (e.getItem() == null) return; diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartUse.java b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartUse.java index 9264f9c..7c57eab 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartUse.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/entities/CommandMinecartUse.java @@ -2,6 +2,7 @@ package me.trouper.sentinel.server.events.violations.entities; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.gui.Items; @@ -11,6 +12,7 @@ import me.trouper.sentinel.utils.PlayerUtils; import me.trouper.sentinel.utils.ServerUtils; import me.trouper.sentinel.utils.Text; import org.bukkit.Material; +import org.bukkit.block.CommandBlock; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.CommandMinecart; import org.bukkit.event.EventHandler; @@ -25,19 +27,27 @@ public class CommandMinecartUse extends AbstractViolation { @EventHandler private void onCMDBlockMinecartUse(PlayerInteractEntityEvent e) { - //ServerUtils.verbose("MinecartCommandUse: Detected Interaction with entity"); - if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartUse.enabled) return; - //ServerUtils.verbose("MinecartCommandUse: Enabled"); Player p = e.getPlayer(); - if (!(e.getRightClicked() instanceof CommandMinecart s)) return; + if (!(e.getRightClicked() instanceof CommandMinecart cm)) return; ServerUtils.verbose("MinecartCommandUse: Entity is minecart command"); - if (PlayerUtils.isTrusted(p)) return; - ServerUtils.verbose("MinecartCommandUse: Not trusted, performing action"); + + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cm.getUniqueId()); + if (PlayerUtils.isTrusted(p)) { + if (Sentinel.getInstance().getDirector().whitelistManager.autoWhitelist.contains(p.getUniqueId())) holder.setWhitelisted(true); + holder.update(p); + e.setCancelled(true); + return; + } + + if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockUse.enabled) { + holder.update(p); + return; + } ActionConfiguration.Builder config = new ActionConfiguration.Builder() .setEvent(e) .setPlayer(p) - .setEntity(s) + .setEntity(cm) .cancel(true) .punish(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartUse.punish) .deop(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockMinecartUse.deop) @@ -47,7 +57,7 @@ public class CommandMinecartUse extends AbstractViolation { runActions( Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.use, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandMinecart), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.use, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandMinecart), - generateMinecartInfo(s), + generateMinecartInfo(cm), config ); } diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java b/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java index d791205..e26aa55 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java @@ -1,9 +1,6 @@ package me.trouper.sentinel.server.events.violations.players; -import com.github.retrooper.packetevents.event.PacketListenerAbstract; -import com.github.retrooper.packetevents.event.PacketListenerPriority; -import com.github.retrooper.packetevents.event.PacketReceiveEvent; -import com.github.retrooper.packetevents.event.PacketSendEvent; +import com.github.retrooper.packetevents.event.*; import com.github.retrooper.packetevents.protocol.chat.Node; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientTabComplete; @@ -19,14 +16,10 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -public class PluginCloakingPacket extends PacketListenerAbstract { +public class PluginCloakingPacket implements PacketListener { public static final List tabReplaceQueue = new ArrayList<>(); - public PluginCloakingPacket() { - super(PacketListenerPriority.NORMAL); - } - @Override public void onPacketReceive(PacketReceiveEvent event) { if (!Sentinel.getInstance().getDirector().io.mainConfig.plugin.pluginHider) return; diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandBlockExecute.java b/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandBlockExecute.java index f4acc19..72da81a 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandBlockExecute.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandBlockExecute.java @@ -2,11 +2,13 @@ package me.trouper.sentinel.server.events.violations.whitelist; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.gui.Items; import me.trouper.sentinel.server.gui.MainGUI; import me.trouper.sentinel.server.gui.config.AntiNukeGUI; +import me.trouper.sentinel.utils.PlayerUtils; import me.trouper.sentinel.utils.ServerUtils; import me.trouper.sentinel.utils.Text; import org.bukkit.Material; @@ -20,6 +22,8 @@ import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import java.util.UUID; + public class CommandBlockExecute extends AbstractViolation { @EventHandler @@ -32,6 +36,7 @@ public class CommandBlockExecute extends AbstractViolation { Block block = s.getBlock(); CommandBlock cb = (CommandBlock) block.getState(); + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(cb.getLocation()); String label = cb.getCommand(); ServerUtils.verbose("Command block is set to %s.".formatted(label)); @@ -39,9 +44,6 @@ public class CommandBlockExecute extends AbstractViolation { if (label.startsWith("/")) label = label.substring(1); ServerUtils.verbose("It's label is %s.".formatted(label)); - boolean isRestricted = Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.disabledCommands.contains(label); - boolean canRun = Sentinel.getInstance().getDirector().whitelistManager.isWhitelisted(cb); - ActionConfiguration.Builder config = new ActionConfiguration.Builder() .setEvent(e) .setBlock(block) @@ -50,7 +52,7 @@ public class CommandBlockExecute extends AbstractViolation { .restoreBlock(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.attemptRestore) .logToDiscord(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.logToDiscord); - if (isRestricted) { + if (Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.disabledCommands.contains(label)) { ServerUtils.verbose("Command block is using a restricted command."); runActions( @@ -59,8 +61,8 @@ public class CommandBlockExecute extends AbstractViolation { generateCommandBlockInfo(cb), config ); - } else if (!canRun) { - ServerUtils.verbose("Command block can't run."); + } else if (holder == null || !holder.isWhitelisted() || !holder.present()|| !PlayerUtils.isTrusted(UUID.fromString(holder.owner()))) { + ServerUtils.verbose("Command block can't run. Not whitelisted and/or trusted."); runActions( Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormat.formatted(Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandBlockWhitelist), diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandMinecartExecute.java b/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandMinecartExecute.java index fdb3b77..09bcbd8 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandMinecartExecute.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/whitelist/CommandMinecartExecute.java @@ -2,8 +2,10 @@ package me.trouper.sentinel.server.events.violations.whitelist; import io.github.itzispyder.pdk.plugin.gui.CustomGui; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; +import me.trouper.sentinel.utils.PlayerUtils; import me.trouper.sentinel.utils.ServerUtils; import org.bukkit.entity.minecart.CommandMinecart; import org.bukkit.event.EventHandler; @@ -11,6 +13,8 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.server.ServerCommandEvent; import org.bukkit.inventory.Inventory; +import java.util.UUID; + public class CommandMinecartExecute extends AbstractViolation { @EventHandler @@ -19,8 +23,7 @@ public class CommandMinecartExecute extends AbstractViolation { if (!Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.enabled) return; //ServerUtils.verbose("Whitelist not disabled"); if (!(e.getSender() instanceof CommandMinecart s)) return; - //ServerUtils.verbose("Sender is command block"); - + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(s.getUniqueId()); String label = s.getCommand(); ServerUtils.verbose("Command block is set to %s.".formatted(label)); @@ -28,9 +31,6 @@ public class CommandMinecartExecute extends AbstractViolation { if (label.startsWith("/")) label = label.substring(1); ServerUtils.verbose("It's label is %s.".formatted(label)); - boolean isRestricted = Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.disabledCommands.contains(label); - boolean canRun = Sentinel.getInstance().getDirector().whitelistManager.isWhitelisted(s); - ActionConfiguration.Builder config = new ActionConfiguration.Builder() .setEvent(e) .setEntity(s) @@ -38,7 +38,7 @@ public class CommandMinecartExecute extends AbstractViolation { .removeEntity(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.destroyCart) .logToDiscord(Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.logToDiscord); - if (isRestricted) { + if (Sentinel.getInstance().getDirector().io.violationConfig.commandBlockWhitelist.disabledCommands.contains(label)) { ServerUtils.verbose("Command cart is using a restricted command."); runActions( @@ -47,11 +47,8 @@ public class CommandMinecartExecute extends AbstractViolation { generateMinecartInfo(s), config ); - return; - } - - if (!canRun) { - ServerUtils.verbose("Command cart can't run."); + } else if (holder == null || !holder.isWhitelisted() || !holder.present() || !PlayerUtils.isTrusted(UUID.fromString(holder.owner()))) { + ServerUtils.verbose("Command cart can't run. Block is not whitelisted, and/or not trusted."); runActions( Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormat.formatted(Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.commandBlockWhitelist), diff --git a/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java b/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java index fc2eb15..fb0bb3d 100644 --- a/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java +++ b/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java @@ -1,6 +1,8 @@ package me.trouper.sentinel.server.functions.helpers; +import com.github.retrooper.packetevents.wrapper.PacketWrapper; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.data.types.SerialLocation; import me.trouper.sentinel.utils.ServerUtils; import me.trouper.sentinel.utils.trees.Node; @@ -226,7 +228,8 @@ public class ActionConfiguration { actions.add(config -> { config.restoreBlock = restoreBlock; if (config.block != null) { - if (Sentinel.getInstance().getDirector().whitelistManager.getFromWhitelist(config.block.getLocation()) != null && Sentinel.getInstance().getDirector().whitelistManager.getFromWhitelist(config.block.getLocation()).restore()) { + CommandBlockHolder holder = Sentinel.getInstance().getDirector().whitelistManager.getFromList(config.block.getLocation()); + if (holder != null && holder.restore()) { config.actionNode.addTextLine(Sentinel.getInstance().getDirector().io.lang.violations.protections.actionNode.restore); } else { config.actionNode.addTextLine(Sentinel.getInstance().getDirector().io.lang.violations.protections.actionNode.restoreFailed); diff --git a/src/main/java/me/trouper/sentinel/server/functions/helpers/CBWhitelistManager.java b/src/main/java/me/trouper/sentinel/server/functions/helpers/CBWhitelistManager.java index b8edf1a..e9c7ded 100644 --- a/src/main/java/me/trouper/sentinel/server/functions/helpers/CBWhitelistManager.java +++ b/src/main/java/me/trouper/sentinel/server/functions/helpers/CBWhitelistManager.java @@ -5,7 +5,6 @@ import me.trouper.sentinel.data.types.SerialLocation; import me.trouper.sentinel.data.types.CommandBlockHolder; import me.trouper.sentinel.server.events.admin.WandEvents; import me.trouper.sentinel.data.types.Selection; -import me.trouper.sentinel.utils.PlayerUtils; import me.trouper.sentinel.utils.ServerUtils; import me.trouper.sentinel.utils.Text; import org.bukkit.Location; @@ -57,7 +56,7 @@ public class CBWhitelistManager { AtomicInteger number = new AtomicInteger(); selection.forEachBlock(block -> { if (block.getType().equals(Material.COMMAND_BLOCK) || block.getType().equals(Material.REPEATING_COMMAND_BLOCK) || block.getType().equals(Material.CHAIN_COMMAND_BLOCK)) { - generateHolder(player.getUniqueId(),(CommandBlock) block.getState()).removeFromWhitelist(); + getFromList(block.getLocation()).setWhitelisted(false); number.getAndIncrement(); } }); @@ -74,7 +73,7 @@ public class CBWhitelistManager { AtomicInteger number = new AtomicInteger(); selection.forEachBlock(block -> { if (block.getType().equals(Material.COMMAND_BLOCK) || block.getType().equals(Material.REPEATING_COMMAND_BLOCK) || block.getType().equals(Material.CHAIN_COMMAND_BLOCK)) { - generateHolder(player.getUniqueId(),(CommandBlock) block.getState()).destroy(); + getFromList(block.getLocation()).delete(); number.getAndIncrement(); } }); @@ -92,8 +91,7 @@ public class CBWhitelistManager { AtomicInteger number = new AtomicInteger(); selection.forEachBlock(block -> { if (ServerUtils.isCommandBlock(block)) { - CommandBlock cb = (CommandBlock) block.getState(); - generateHolder(player.getUniqueId(),cb).addToWhitelist(); + getFromList(block.getLocation()).addAndWhitelist(); number.getAndIncrement(); } }); @@ -104,10 +102,11 @@ public class CBWhitelistManager { public int clearAll() { int total = 0; for (CommandBlockHolder cb : Sentinel.getInstance().getDirector().io.commandBlocks.holders) { - cb.removeFromWhitelist(); + cb.destroy(); + cb.delete(); total++; - if (cb.loc().isUUID()) continue; + if (cb.isCart()) continue; Location remove = SerialLocation.translate(cb.loc()); remove.getBlock().setType(Material.AIR); } @@ -118,10 +117,11 @@ public class CBWhitelistManager { int total = 0; for (CommandBlockHolder cb : Sentinel.getInstance().getDirector().io.commandBlocks.holders) { if (!cb.owner().equals(who.toString())) continue; - cb.removeFromWhitelist(); + cb.destroy(); + cb.delete(); total++; - if (cb.loc().isUUID()) continue; + if (cb.isCart()) continue; Location remove = SerialLocation.translate(cb.loc()); remove.getBlock().setType(Material.AIR); } @@ -163,4 +163,32 @@ public class CBWhitelistManager { public boolean isConditional(CommandBlock cb) { return cb.getBlock().getBlockData() instanceof org.bukkit.block.data.type.CommandBlock cbs && cbs.isConditional(); } + + public CommandBlockHolder getFromList(UUID entityUUID) { + for (CommandBlockHolder existing : Sentinel.getInstance().getDirector().io.commandBlocks.holders) { + if (existing.loc().isUUID() && existing.loc().toUIID().equals(entityUUID)) { + return existing; + } + } + return null; + } + + public CommandBlockHolder getFromList(Location loc) { + for (CommandBlockHolder existing : Sentinel.getInstance().getDirector().io.commandBlocks.holders) { + if (existing.loc().isSameLocation(loc)) { + return existing; + } + } + return null; + } + + public CommandBlockHolder getFromList(SerialLocation loc) { + for (CommandBlockHolder existing : Sentinel.getInstance().getDirector().io.commandBlocks.holders) { + if (existing.loc().isSameLocation(loc)) { + return existing; + } + } + return null; + } + } diff --git a/src/main/java/me/trouper/sentinel/server/gui/whitelist/WhitelistGUI.java b/src/main/java/me/trouper/sentinel/server/gui/whitelist/WhitelistGUI.java index f0c6f03..16d1cec 100644 --- a/src/main/java/me/trouper/sentinel/server/gui/whitelist/WhitelistGUI.java +++ b/src/main/java/me/trouper/sentinel/server/gui/whitelist/WhitelistGUI.java @@ -104,7 +104,7 @@ public class WhitelistGUI { //ServerUtils.verbose("Type material is %s", type.name()); - String name = holder.loc().isUUID() ? + String name = holder.isCart() ? "Minecart: " + holder.loc().toUIID() : String.format("X: %d, Y: %d, Z: %d", (int) holder.loc().x(), @@ -122,7 +122,7 @@ public class WhitelistGUI { //ServerUtils.verbose("Got type"); lore.add(Text.color("&7Whitelisted: " + (holder.isWhitelisted() ? "&aYes" : "&cNo"))); //ServerUtils.verbose("Got whitelist status"); - lore.add(Text.color("&7Present: " + (holder.isPresent() ? "&aYes" : "&cNo"))); + lore.add(Text.color("&7Present: " + (holder.present() ? "&aYes" : "&cNo"))); //ServerUtils.verbose("Got Present Status"); lore.add(""); lore.add(Text.color("&eClick to manage!")); @@ -146,8 +146,7 @@ public class WhitelistGUI { .defineMain(e -> e.setCancelled(true)) .define(0,createDisplayItem(holder)) .define(2, createActionItem(whitelisted ? "Un-Whitelist" : "Whitelist", whitelisted ? Material.BARRIER : Material.PAPER), e -> { - if (whitelisted) holder.removeFromWhitelist(); - else holder.addToWhitelist(); + holder.setWhitelisted(!whitelisted); player.playSound(player.getLocation(),Sound.BLOCK_NOTE_BLOCK_PLING,1,1F); openManagementMenu(player,holder); }) @@ -183,18 +182,7 @@ public class WhitelistGUI { player.openInventory(createGUI(player).getInventory()); }) .define(6,createActionItem("Take Ownership",Material.NAME_TAG), e -> { - CommandBlockHolder updated = new CommandBlockHolder( - player.getUniqueId().toString(), - holder.loc(), - holder.facing(), - holder.type(), - holder.auto(), - holder.conditional(), - holder.command() - ); - holder.destroy(); - if (whitelisted) updated.addToWhitelist(); - updated.restore(); + holder.setOwner(player.getUniqueId().toString()); player.playSound(player.getLocation(),Sound.ENTITY_VILLAGER_TRADE,1,1F); openManagementMenu(player,holder); }) @@ -241,7 +229,7 @@ public class WhitelistGUI { private List filterEntries(Player player, FilterOperator operator) { Set filters = activeFilters.computeIfAbsent(player.getUniqueId(), v -> new HashSet<>()); ServerUtils.verbose("Filtering entries for %s. Current: ", player,filters.toString()); - return Sentinel.getInstance().getDirector().io.commandBlocks.existing.stream() + return Sentinel.getInstance().getDirector().io.commandBlocks.holders.stream() .filter(holder -> { if (filters.isEmpty()) return true; @@ -258,7 +246,7 @@ public class WhitelistGUI { case IMPULSE -> holder.getType().equals(Material.COMMAND_BLOCK); case WHITELISTED -> holder.isWhitelisted(); case NOT_WHITELISTED -> !holder.isWhitelisted(); - case NOT_PRESENT -> !holder.isPresent(); + case NOT_PRESENT -> !holder.present(); }; result = operator.apply(result, conditionMet); diff --git a/src/main/java/me/trouper/sentinel/startup/drm/Auth.java b/src/main/java/me/trouper/sentinel/startup/drm/Auth.java index 8dc55db..745f292 100644 --- a/src/main/java/me/trouper/sentinel/startup/drm/Auth.java +++ b/src/main/java/me/trouper/sentinel/startup/drm/Auth.java @@ -19,9 +19,6 @@ import java.util.Map; public final class Auth { public String authorize(String license, String identifier) { - if (true) { - return "AUTHORIZED"; - } Map> licenses = getLicenseList(); if (licenses == null) return "ERROR"; diff --git a/src/main/java/me/trouper/sentinel/startup/drm/Loader.java b/src/main/java/me/trouper/sentinel/startup/drm/Loader.java index ecfd659..55a4742 100644 --- a/src/main/java/me/trouper/sentinel/startup/drm/Loader.java +++ b/src/main/java/me/trouper/sentinel/startup/drm/Loader.java @@ -1,5 +1,7 @@ package me.trouper.sentinel.startup.drm; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.event.PacketListenerPriority; import io.github.itzispyder.pdk.utils.SchedulerUtils; import me.trouper.sentinel.Sentinel; import me.trouper.sentinel.data.config.MainConfig; @@ -38,7 +40,6 @@ public final class Loader { &7Your License Key is &a%s&7. &7Your server ID is &6%s&7. - &7You are &6%s&7. &fIf you have just &apurchased&f the plugin: &8- &7Join the &b&ndiscord&r&7 and open a ticket. @@ -55,7 +56,7 @@ public final class Loader { &fWoah! You read quite far! &8- &7Want the plugin for cheaper, &nor even for free&r&7? &8- &7DM &b@obvwolf&7 on discord and lets make a deal! - """.formatted(Sentinel.getInstance().license,Sentinel.getInstance().identifier, MainConfig.username)); + """.formatted(Sentinel.getInstance().license,Sentinel.getInstance().identifier)); public boolean load(String license, String identifier, boolean coldStart) { Sentinel.getInstance().getLogger().info("\n]====---- Requesting Authentication ----====[ \n- License Key: %s\n- Server ID: %s\n".formatted(license,identifier)); @@ -145,9 +146,13 @@ public final class Loader { new CallbackCommand().register(); new ExtraCommand().register(); + // Packets + PacketEvents.getAPI().getEventManager().registerListener(new PluginCloakingPacket(), PacketListenerPriority.NORMAL); + PacketEvents.getAPI().getEventManager().registerListener(new ShadowRealmEvents(), PacketListenerPriority.HIGHEST); + PacketEvents.getAPI().getEventManager().registerListener(new CommandBlockEdit(), PacketListenerPriority.NORMAL); + // Events new AntiBanEvents().register(); - new CommandBlockEdit().register(); new CommandBlockExecute().register(); new CommandMinecartPlace().register(); new CommandMinecartUse().register(); diff --git a/src/main/java/me/trouper/sentinel/utils/ServerUtils.java b/src/main/java/me/trouper/sentinel/utils/ServerUtils.java index 3eca709..56e2fe4 100644 --- a/src/main/java/me/trouper/sentinel/utils/ServerUtils.java +++ b/src/main/java/me/trouper/sentinel/utils/ServerUtils.java @@ -37,6 +37,38 @@ public final class ServerUtils { },1); } + public static void verbose(int backtrace, String message, Object... args) { + if (!Sentinel.getInstance().getDirector().io.mainConfig.debugMode) return; + String callerInfo = "Unknown Caller"; + + // Capture the stack trace to determine the caller + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (stackTrace.length > 2 + backtrace) { // Ensure we have enough depth + StackTraceElement caller = stackTrace[2 + backtrace]; // The method that called `verbose()` + + String className = caller.getClassName(); + className = className.substring(className.lastIndexOf(".") + 1); + if (className.contains("-")) { + callerInfo = "Protected"; + } else { + callerInfo = className + "." + caller.getMethodName(); + } + + + } + + String formattedMessage = message.formatted(args); + String log = "[Sentinel] [DEBUG ^ %s] [%s]: %s".formatted(backtrace, callerInfo, formattedMessage); + Sentinel.getInstance().getLogger().info(log); + + for (Player trustedPlayer : Bukkit.getOnlinePlayers()) { + if (PlayerUtils.isTrusted(trustedPlayer)) { + trustedPlayer.sendMessage("§d§lSentinel §7[§bDEBUG§7] §7[§e%s§7] §8» §7%s" + .formatted(callerInfo, formattedMessage)); + } + } + } + public static void verbose(String message, Object... args) { if (!Sentinel.getInstance().getDirector().io.mainConfig.debugMode) return; String callerInfo = "Unknown Caller";