From 006066f66c00022e7a0ddff5219b0a8f3a304fc7 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 12 Apr 2024 22:36:07 +0400 Subject: [PATCH 01/64] prepare for 0.12.0 --- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 5 ++++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5440772..f1c10e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group = com.imaginarycode.minecraft -version = 0.11.4-SNAPSHOT +version = 0.12.0-SNAPSHOT diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 16170 zcmZv@1C%B~(=OPyZQHhOo71+Qo{!^7;G&o^S)+pkaqdJWHm~1r7od1qA}a4m7bN0H~O_TWh$Qcv`r+nb?b4TbS8d zxH6g9o4C29YUpd@YhrwdLs-IyGpjd3(n_D1EQ+2>M}EC_Qd^DMB&z+Y-R@$d*<|Y<~_L?8O}c#13DZ`CI-je^V*!p27iTh zVF^v_sc+#ATfG`o!(m-#)8OIgpcJaaK&dTtcz~bzH_spvFh(X~Nd=l%)i95)K-yk?O~JY-q9yJKyNwGpuUo601UzzZnZP2>f~C7ET%*JQ`7U^c%Ay= z*VXGhB(=zePs-uvej`1AV`+URCzI7opL{ct^|Lg3`JRQ#N2liRT0J3kn2{O5?+)Xh zg+2W4_vVGeL^tu5mNC*w+M@qOsA?i7Q5Y!W}0%`WElV9J|}=8*@{O1`1(!wCebWJz&EbIE09Ar_<&ldhsD}pR(~NfS=IJb>x%X z{2ulD!5`cb!w+v^IGu~jd3D$fUs>e3cW|v_Cm{8={NL)ZoxNQqikAB&nbiz7mbKz( zWjH73t*#;8Rv5%^+JhrK!zDSutNaUZF#xIcX-J?XTXJMUzc0+Q{3)Xt)KYbRR4)MYT4?1fDz4 z0NVFLz!!^q(*mC;cfO~%{B}A^V3|1aPPqpOYCO4o^)?p?Hn17_0AbdX$f;k!9sL^g z{n_Q5yM!yp{oU))sbp&r6v}Au6R`9Z#h@0oM&1n0>wAP27GtH zG#~tyCu38r+Xh)31z*ShTdXWfb`4h!sraW8_kR1VGraUOtA9}O2g{N$S+1{3q>z*< zDEs&xo6@|O7lJlzn%!gmnJL@mh6XY?H2^>+tYwAp2aD&ve*;dNlFRUUD4uJsz0s{jA0wM|`g_Bk- z2nGTI4FLio^iSgCYQ<~?w6VhgXuFy?J6pI)*tog7+L(H{+c-IDy4s67IsWSv-2ZoX zkgKk*j4q1tU51^udPJsziAoFE%s5Wgi({t%V=JasWm6hHcE*-AVByK0i}t9!4^NT& zYJ1?sHp;I5vxtJi@z=?8N5Bc2Rp96QJ7Pawo_W$pO{f?a?6fX`?dHe8J+yAg-F$LU zXmTjqP`_JciO)bHLs}L><&(2CORPpITFZ5y{Ha$rW};;c-n)RcD`TyHnL?)Fx{0?I zqQ|D4T`xLJy`A}h{D57UR@bD8{Bw{9rlPt&U?{4 zTbO4-nHnPS!as<)ecV@VpH~W*$zoPr8f09_MZBPjoU zamA5hmU=F0q4v*u)BvEyDNo)GJxs9tiPkp2uhlGLR2bUD{NSjGGCixR9?$LKAlsip zUIa{WQs#68GH3NL{(FUyk-k=lrtx{V24k>kq~uc+St1uH0Yf3s547xvD5T*@n^+VN zKO~$H#RFW+Sd*M?`&+A$L<%DwNmIW&h>4j}vyxu3PmHrGwp?hXJp!{^>$Ax2WY&9} z5fJvDKBT&~%2QWqTGf{=6Pv2U+0HUQRv9%RZLR`G^XNdKRZt`Zs z)vuUr#7C#oQ00KL7$M$(yHa*C4XZ~*t9NPMJU`fACD3v+wvLzMJipnOfRmh_kN5oD zZ;)G|-j$^OF~-yWW*p1m#1)%%tWgg_?ps;<cvxwa&b=_7Iu)xM#KIHR~gWVSQGmujR;bCgI%H#(_~8O`LAHbJ%9L?R(Dt zq%5@6HsP4(%%tF4t#7v$y&h*i|KihD+E^Q7n~`1KzELK>5I8-`H|JF2Cq9CgniYyS z_4op2_>b9Il(p8PquZ{h8Gy$%WA+8t)o_gCdb75|9NJ&}Y*D~a6)VE@eT3!qvvSPz z4-A4Vw^rS17uWVctor@Gky4eiT6nF=PVY~8jzjKM-GlQzF5I-V&Z7d^G3?o9`C9gHU5GOAMLIZIOBw|s--tIy=R#b8@3;?-9Y8jeFt`AhO z8tTwGxksHRNk>;%uqWW&Q!^M?CwVDvX-*wTji*J^X%}1`6Z(#9OsQQfUI9x&CAj=W z-tDF7TYPVS7zfx~aje8Z@J>er!E<@63gEY)W{b!AF%?j%VG;B3b;Kt6VVH0qxBLrC z*82l$taUKcm}zRM=K+>H%w7(10hX25ud7r}c#sEK;mnBsVbD;$qu_|UEarcuS7aYi zcMjgkjmj=#d&K?NX=qgouhsLh{iYTe8qtsU~kLwg4&&Q1YGyz6D@(-w< zl~tx6ulu}VfKZ@_gt2aL@E`A`ULme@K+ zek2hch6FNgHdbowNo)mBs0da-}bhPw|R1u{4 zEZ?T!7j&^lNPs1je%@Em^CPp$cX%GrCBn66>D{`Ugf%+~@)w+gX2xGJ1qCy6|1f8m zkW@0=CvkEuR0$mn*wuIvn?-qRMNjtj*c5Z_P}N^he{2=<@XK4^ zC{Zs89DIB6QjEE2PRx9Le^?_kvTpBWr~%L249F}8N&xTV?+_;?oyfV?V^T(ioIxw@ zYNZUlBAc=A{A709=R`$--jqG{jPQj-7f_Sr1$o&kapsFL3jBVIE*Z4&L}1ve?@wh=%eda^BRYm=>pJ z{p#Gotpa1aH^l+Oclp_+$Whjp_q3(G8zS<1;!#*67K0Du1}RQPo&G8mVeftaJ&a++ zYlh?j&;3LJA5Q4fDBsWauFn>VvG_9Tcrr2Yt-#+%rO0ST1GFitK8f10=rq|6lf1q? zZgVH$pWLo_(3QZ@KH}q%V;KT>r!K|?t?LSBWRUoPcv3to`%wC6ZRPF|G1tKl`(7G_xblMQANQ+j&NIeH&TK6-$u*4Uh&0t&ePU zPJkhRuh#-@_X+0}aV*Jb0Bfa+LZNqQVWJ0#=KA~Bqt%4}(36~^U)lvrj$CQX%P=?D ziHvZYaHPO6-Q>+|s~lNFW0?Bv%tzi)3M>X`;!RfF3<~0HjHc|}*l~bKATK4IXdR!B zMf+A}Up#I+)T8aogDs8)j}J)JK!%rH9&J59H~Q@Ntd^EV{~c7kTX%dQB_?kfOR-tn zA=NR@abtm5k{N9NS^G$1>>Td<278}g(`E7_k5+?RgoT&-Nqa5AjkAAn7s8#Vc=*sd zmyzfjfeIp0Fehg1gbSQ(_~qXV=y0ShN7ck^V@6t(5C%IxDmYn-~2#bGniWG#vS zWlnC*Dbfin3QX!ZI-YRxCO7uBG+d>=s@*c0sPmByGDc2mN&24$GkoH0oitsFTV0_} z4iATfIz{jBODQY1t{lpUS%Q1Hzdel~82P1N#Cura_7k&{mUoI@q?W7&Jzo61$}3G7 zl`3shFi_Vnoh`5OIKHqV;wTULz2GkZgW0zNjk3t#5aH8tz(R^=;i?c~(3-;#WM50snq>qF)cu>}tWC*wTO7r93>;1Cbif%d{o% zC1Eyo7UwX41o7QLvdU_to(vzDD`*KK^3HBZvx@j@i1Nbt-w8Z5`>?)c;rXTjdt#k# zOfJED_)awGGGg*Z0Rgo!JN?rDkpZFr6pE4%K}BPXJ>0O@93hgvCGJz?oUweJQjnVi zNQKWhxNpSd36=ip(-D4iOtMG99MY(y86GtXS~1%=jipBb#D;tZpKmMRZ_t=10TL%p z21RJ%0X=&&WUDYBbTcwsof1(CDGDD)eW`d#Y*Z87@k z^{dy_GcUp~J?qJ=i#H#EeSsp^TSr@dt$%q>c3_o1F9sr_ta1PLWYBdi1BNUNu0`v` zvgB;K@#gLmv#tD2Mf21LHU0Hq2~Ro}Upex$#h~)93nAvxcS6wkM&UVy#4RnSG6QX9 zQ;r$p=AKnBnUe=hZPH*u-Q4Ta4COuQ7TQGIqbUi4&eot$D2GHljdSdbc-MK-t1R86opRwDuUN+ zw(1^ybD7grBO>ySm29}i&+s{~7uz?*?K;N9?Yw~zd6 z*Xfoqv-*O~(QBAVpOqwZ``Qmd5qbL#d`>U7rT&?h?FN=iYu*vFfck~?6h=b48;n}$ zQrzUxWJ{eaR2!*MSX=+F*)ECE#91?SmduzuZwQ! z!ydL4;ljZ(9R_<=q z!=`&+*DUw>CsM8xVDT-;zFYUu%hn$rxPXhKztEb98>7ow#=fdMWJ!i$jJ=MIBspC; zvoJ2R96iz*(%23uM#WtAe661ynV`4t?K~eV&7!-r+tg^aw3Jiql zX^)V(pEN2WfQOL4!JgVGIoQ~a8}Gy_4l92Wst~iEI zANmgs#tUnQcv2E7>g!{jjC+X-g)LH8&8VQNoBvicmuID9WQoa^S-h?S(POL5f({Fs zWfe|-nRh@hz|Ck@iKm0C75R&`CWwUy<05TSN_IH3aMaO_Kw>0#Pv&-Dfl7b}3qfofON-WA!AB)QpF2FTnvu;s>T;lA1&Fh0 zBl$6%ODbhP1gIh2T%!8 zZ%&Q`_{;znmFQruzy3PWP@echTsS*JR65#1s^Yda=tWMNX?a%+u|@dSu2I$CfK@Jn zawQv>0i4QnlbtbIr{`+ihYt_GdJHR=O@6{5LHt~olXhcS{M}I*a8tl}U4uzgBx*jp zRji6=dfc!=jHsx4K9~%u9#`zIn~cO6$jl}Nco#8;2pDgqvpvO#S|Y1K4rie3vqVCS zI#QhtFED4h{9VA1j=@RcVQaORXzjNxK8$SAK4wPeIC%aePdZXEx8yE+0I;$3%avkwY+41*ee; z&@xvi6UvJOhfU)RKMMK5Ge)~VT{PNe>z_T^X7?!+cO%0O9;nBI39kOtN@7LUz)ZmX zVkxf)8QPZBxVNXV%s6vVeKr}hCJ=hY`pM{cihwK~6q{=~trr;R=dFS{Nx9;4Zr!`7 zG7^c|#x2=Z`)Um#l$|b#-4ZUow`yGvfCXce%qd#AG~sxuJ6eX@lQ?Gjjp4vuTv(to zGf_0z8b@Z3BzdaEB6`wXLwFwkyA*4$k{>ml#wj!^5x4DqDUFA|FW+@VD-FJyK3ynY z+{Gi9YbWOrqc_u1`$TYn+)Y1`=FhpVDRPdVzJ(>N;7R=OCBBghMVep-7atEDV6AsR zbPurLbCNf;oXDMCcEh;jgbeA|IE5ZbQ52ds%s}TJ-6?8~*qMF3@X8c=bL@w}r$Eeo zYUC@E6+viob;vjUn;z&lgCas{XLW zcxyK?xbJRX+WU9|%5bsaPbm!Tu)E}a&!br8FTR3?Cb%vZ7|$~!=Ixn55uZS#3NRZZ zs<82Gtkto2fzIEbE1T5-++IkANc74_ zARU;|ap|KEBu3}J?H?y>a845^ydr)R0F1K65>38_s0!GY|0t(o^g;aU(_1BuV33!b zi%`3stu>SZm%sRQ;lF#YPI4YIjsAv*0wm?LyvmEf2gKw__$W9yX+jR-P0o&>kaw+` zGf&tUrybKn0W_!YI0F{}d-V@ih~H2E^+PAzPlxaLf!!ly_BXZb`x{oX?}Ft-Yf}M7 zL{95Z!O*@rVV2j3Pjafo*D)wz$d3nQ2r{c~F-B4MlK60ouc3wU3}PEHhb{(moORi; zz5Hl)0M*Q# zOMmV8+5Oqz@+KiFk}x13`>Sg5)om(PI7B*n7hy<%)eZ%l1W=X?1Jtm2HUs`O#YFrj z9oFV(XD8)A{GK75(qMrd3jxUxPO`+Y7MVo#OtQX}E3fEqAVqj*?6JOOe$$5fn+5s? zx6moNC@o%1rwax68*VH@V-ANJ;x0GK{o3~V@1MKuiCN^IycAo;ZVc_;2O7q6eCH1I zoe1{_eg#}yXybiKf2$)I+FsNMa7IrsH~HZ|$A{s0LJf%{UQD;+jsdG?0>7hBQV)4Z z9Aj3a;Zp^Un5Ljqh`L5U{X*^*a6hqP--eRfh0}0|6M_IUiNtOni5Fk^t?onDM*MD^ zJegBUHkuv4>|8kN#xJYTzk`=4HR0PzpzJwG>KT()`#P3VF~fM5zGtG$RvQ|WmyaWj zqa&<4PU$5f921)o=e5(&Jm@$x-k);(lbnuD;XVQ&-lY< z+qf+FM4LeIsrObq4%f816^m|}8*00qF5^nxMS|H$dd#|s?}S(ciSghkJ(SJ=5y+twusP{MwkwIq zG2jBiouA4dgIuopX4Fp~UOni({ADA{&bB1_SYl{Q1wI*BTif%ee(N*7Z#OJCY z`He1l4dzecQ4W@TWAOkMgb_`GjENXd#_HoZ02Mr-Do>Xl9w;r*JD0R$si9tO6>US| zW|-ViVwqmhC1e{PTM51QN-HWn*EaOG$)PA8f8Q$HRNa&V^1`9Dp(-VE<`-cJRki~l zeQ) zV@HnYenHV4B4{V-j?tY(Fc2FsQ|x6Gw;Our*EHIetWC6h>UX4AD|F*5bjP5T z@3kaY0O%|F3o`0WTWlQP;ddr(jcn4KyY(k|Jxi~yT38Bltin0O;H6rTSn6Vcdf`n& z3VU99zPfSZtoV`jNq@?f5~?~6My$>J%7mhCr9$Go0cVO)?rpbQDqH4OAWGC zt!B23yF^#B>^~P@O$qgThx4S#JI`u=3Vb8kfuoSrCVyU3+I_TDPtMd zh77hUa;@t9$3OrpW1;dq;7e|B=27+?L&)R206N7fz6u?Vpo*g6vIY5v1DKt|AK$2M zJi?{ZR|-bTbSdNw@;C%KmF)oF@02bTYv#S(-3CkWy`T4^;;km9dfr10T|IR>C-<0| zdFuPGMJ!X;7kkg1rSdU~d23f8Z6O>Wa7!Q!!DKWHYFT(lU)%HbfN|7|CApdi!p6M* zZmPd41(qS*oGsEeT8dw)S%!yhgr&Tky+y^toYWPz1+9)DO8jzecE{}r$;iVGY{|@p zrp?%)e$c+T^FP36!i|qrv2(?@HIV=2NN1;L5puOPYfUZcG0NMuFx0O6`UePVOQ79wGgMj)l5<4?a<`Yl_RhY_C7U=0zKBC2$EhP^_G|S) zwv*z48K19@_pT*WUhAAZmlp){uf+E+7CcPp@0fe!wZ0R-R5-^z@HriduQz zZow5@W~ILN%8FlEM2p$(xE>5I81*!?MyluZ_h+)_1Ug0r&e(>Yv0M~3hqW5MAzFyu zT~rkx=9&{Z2Vck0$yI7kx_X*?*}kLE$UCA?X#yX}J5mqJIW0vPm&dE7bya_O96Z%~ zl$ilJ>NzFyNQyi0rMf#i6p;Rs2}#%Va%#q3X3af9vR@Gu^|I*Uw9XEY{t`plKE}Dw z8XFLZIremOfC4J$_eo{BWTsF}V-fd#;9O9P@gDn1IpW}EqCsR)gC7BFD#!|v9*h%1 z*&6syZPLg3GRsaVn+HT0jx{p1-AFJ$!XJPR;zEERi4XWy8F%Ob0bCHy{|+cVgt zxUeBR@Fg+_?_9G>{k)>Pg*RYkst}Ve&Yr9ku!oPKAT5$zr_hh$bio?MkK~VXg<}A0 z(xHUlM(j$|fxDCvX(ON*g)b7>LKCWPKjS0%J1wRdl;<;+3;S1WAQF7)9UG>EBPO4+ z+60A8s;x%l0#{t#>M3qq-pVQOPavJPiz)V?3tAxyIwpNpQ#BQ7cUn49TfXdRMw84e znq4y_=;tRzm6)Uu*a@=Cyn@(7`XL|*GokZSuV40Fdtg?L=UjQd71V&Il|4)T&J8z^ zX>1PZv)eLcn%pp%s3)`~`Cg;oBWcd_nBp_R7 z(cbpAAxWQ&^ZmRDkLbO=Jfb(k(=z$y_Dzc|sd{p_6S+9#Fbr7HEPqyXNdaJ3`3u6( zWDF@;ybOj>Le%rvVTGL7*S;P6;T6lI#?Yp@KX&- zeXq*<7IsOCb=uS5s0Mmf25>+hk)wj?se_5MedT~~WtEfn%Dxk#_W?Lj?3>GwN46fK z!IYgVw^_>#<=3oy;69J;(4rMSQ*bk#e z*O9H2VyX^(Rhj_h2~RKjRb;#jfWoVR_7xu0|7d;#jJeOlwzc=%h&6f;S#I99}wvxDNo zQFoYVq&-Mp!>+&et%Z3e-=EL?u?LUtia5D*zj}rztU#KX9V6C7;j7Q8S0 zlB*6q%yF@-Yf+q;a1)&^0$8&K{HXDYS&Ed)vJ!l6r$n9U8P`MUQZI)eK-^u6*Kdpf zzNar-y5wx;ZtRJpbYCGEd0*84PVL8&+BWu$y*{?sk&bhCehjZArP1SSX2_6(z{nE6M^R*|f6 z$ynra_U-VwV*BF1^ho4}C9XiaVprNH`hGFmgiUX%Pv*@VcTI~^;m|JEntHi&{_L&; zNnO;cWA4aJODk4op9K>jC_D0@eyJFuB2hh`Cwo{)#83w{6&Ky2xe7(Qnzks)2SH`f z9MmfjA!;HpQ_Q@C+Q5Zs>7ASx!lG`27XazRsQ1uR^eWQATS z(PqV@o6r#!swbqh-w^cNgLo54+nw2GAw@~>UnR!SfLMDZrFXJ!$OoPmtDTp_b;9`K z6tL5XDPoLt$~OS+O>IkYa^+oW@Jfg_g4g+JCAzGU4dsZ-rcx~ZL}!pigv95Pq3LG} zPEIepL$%a4dNpm5R9%Wqxwu3dl8$7pq4pjr{XIuHbFK8kLrI(}DqKPN12YQ2t3qzdnN!ez3Fd zp@($04skG7>K4pGr(&g2KJoRf`ea1&(??Wp<%O(8*U+X0RR*C;2`Ok6Xl&E2*5VdI zwm9bdWnitI-|PHYdRgj21CFGr*CO^yY1 zJkS;V*|!ymL(H~{Vz-foW=m%#Bb9256n3?)QAHTMGkd{94WY{Y;*C_3_M$LA@*1`k zcOc;KRtbu3LZZcSJ$Y@4f9q(6`;*$pPvvNuPTT!YP)11=@3hLs*qSRmT&kfVB_E~J`wO&l5No9Hxys8+F-y1{*16v=L0gph z26scBjUWa-_NHH!@XYfp&9h5bno!vSYX-@^Wni0>qJlmngFgNZ=RDuIzHu6Ja}IZ- zz~}h(TRXn514hbq<};7Yp!(msmGT0$WLE$i%+~T+S)Z&w;Z3dPlWkfIw!BJ{{~Rcq z;&sxPHBu7o@hrM#E2pGw2J~6gLR;dze8@5(Xd~jE(gF~%!U~&-tl;CBXIrbO$!#%# z7Wnm3NH%VXo`JPuS>tD|@@o51t zvF6hSTV`=L1picH03CEV53d&h8m~F=xI^xq$^KQg$S?s!Y>X4C8px}6>=*DKtGGqORX z>@+KMD)Z8^xQbawX$BD?6-3UNB<=xuVC8wB+3{ z$(6jJF;?=cj{Vw_x`S}-Rt)sM&?wC`WeCKUYuI|Su&3BBDm>S9B?@}*DAYqI@VH5J zx@#>WGMvy{SU5}Z-ds4VIzM&)$RV?;m6yYnO)4jn1+66*NN(r@8i51e)@X?XxljW& z!Mqh9S&j$#%jy30)1H zmLPP5mM-sO3a)B03I-**B$D}Mg=LNdyPsRNgzN$c%7l1~0s5sGk5LwCFlp`b1}{tY z`Ax$;Fh0h_WqU?!RsMi?(oU6P#~_3MRFz6_$2S%Y&}kOb(M&MiPm~{! zI`z;?7q`8^+qCNSK{t`or*wkUEAx){Js`RRh|P9E(`1{cvg-PRvg+x{^u&;j#m+6UDx{Mo^f1Zw);JI=wvFcnuMO()EMgA1m%4ZN)t=+tTUo{-mt26* z+YtnDP|`%#Mc4r*9=JNUppLb2m|;RLP_~8+D>BB^VX@~;nM(ASLh@oz5vUeD^CYnE z%sZ0<+!;U4eDkEZZ{0f~Z`$qI8Kw{pGxP)o=!I`)$0qyhKYNP`j1A-|^8Q z(IE~i2!?diQoAET^xIFq^XF(^gAzEOveZ#&@hY^0Wsx#jKD!&*f^7=zg?p!e4zYCx zm`g2=4;L3|Jv~$BIf>zyPp4%@okJzf`yPuSHMH7A&2cKN05YV1W^!P1%kc4LP+B=1 z_v)WD&+J|8+5u@+^?n)Tl-y?P6@xH|G0q5VL4U@?0e!W-O=L>!?VrBX+I?s$~ z+R^j|7)h>Gl(Pq9{aK<-m@9xaP!=*m9OgP;S(LE4#j`zVvSzF=uH6#r*@8;YNf6h? zM?C0=;hrzuLP9<(sJ`tcn#1=oI}cKoBNT{G4h~EsKbQ$)+upOKO24nXjex~C@DYjI z^H-KT^YiY_{qyYHG3Y~NID^UJ%(tUUUwxScD9C&CqBy=;?RY2TQ!LL8zEHK#JA-4h zjyvrS%@N-z=x&oyw-C1sVCr+(u(?A&MbAjX;!_=O(G+RJ=S%0kDY{G5j7R%f*!3Lu z4g14hdT%|ONka2%Mt^)pzcR6H!Ci>hDIGNc zI{I>=8v><;f>XvXd#l3P8Sj{536jWYa>{EhzwaYB%d0E%34 zs;&Z4pI+PJX=`lcUrsKkWLbX_E%z}twRY>ZWZ*ayyQpMM6JFI513Q{C3N3tqjZF3}4n~f@ z1^DS=&vW?GO_0n2{*g|QW&^Pcv|^Nh{_vAra`IX=Q)i-TJ>vbBs9PT;-Zf8d37A(w z!a&fT*gXFS6Cl`Ms(4TK0AUu%bg;1yNP>Qg`Kw6&A z+==jRb-{oPy?$sWM+5q(TH6-Hfq2}yOJs1A)gEt5iq_r(A0M%haJb?CJEE%{9MDb_ z?k8%7DL9hlwp;KtwOhovV+jatf2)5LG6%b3u;fgv&Cg)q9kg70Pa;_(Dp@-f085&lb{lrqjJ8XBwmAHz2ZU?>J&&Qt_utVGrOC;QXfP8-` z4(gvV_VMBckHXq0&CBQV*-Eb~g%i_xDBsc{u4VJ4V# z)zc`WeInwd{2}6{tnH<*T%#<~5YXqUVk1X0kyKV;V?B|?2qvfZWWJ%1d`v`{qzb8V z0%GqJ)!KpL8n(^YXvhTEPbM&N*Par2=zIcS*g*o-ew6NnE^4gHYxS2%ry#CtVr*@z zwt5j^SX@|L!FP+QdTwr(_G}*BfVwZnBq>D@EX6A;D}&V7K($g}Tv*OMQeQ4@(&KM| z2s5;`v-L$^DpBPqp^j)l1@*YY?SXH7bfVx?iP_RDr0jm5SQh>h;Fr&o!O%Lp_!MyQ(3)9E>d8DS=Y4e zX)UA3i+h_{j7JFweESq*VAY`P6_?Kr-?5{BV5qBo;43bLHH`A=dgd&kl&zpM)0G~- zkYP(@b$G@?HAcPDoRnK_YmTf}Ws}xe`c;l-nL+x$=@8O8&cTz-?T`>Xcq?7!eD(4w3I*^4gr*Mix$f6~Eu zL$d6&d$SyJiHzaTS(jn`-^OdoV(+^g%*5}4xiC2Aak%H8E}-9`mywb6OE#R#DUKP0 zdVGquO}fc|BHvLQwJS8k9BrC71m+*>?CBUI*L5bKEk5sD9UG+hR$T?L*a!IL8`Y<} z&x+sOGNWy`IELU&chBa@Wn5*JQwk!Xhw9c?0vrmnKecLQ>fuH_$bg-=YRIa%TxyLo zrXGl{;J`Zv|A^Xvbl*h*J0&R$R$Rl=v^#;vag}wz+Rgq4TQ~~#9XPJ=@F5%1fwVd6 zwJpeIYBSy8SmYE>Y_|F5&zWOuclzUs*!*9kb2>WvSW?oMoqvilS#gEiSRGUE;I)7W z)|E64QMUT8l=6U7@`hl*Ovr9SK?>h|yCXrQs?Za{(SF-2A^8r&;ma$yVXAv`?iY{Ruo_RpDc?$_mYe{$)!^{E%qV{M2lfi_`V{uh1LEo>ktW3KNwUB-O7WqdeNMZ^^ls8k6M-)JZs71vu_ddp;A!#g zw=wtYZZm1OVjZP72UQC)kLNf_2zE52^+~SYDd|&iCX;n0jA1Nw6}NY_8G`LN)DBhy zlWWng+oB7p6uXX_xHm4%EQ_n-YYtYEm)n7Ire#_8@fetEqAR^npHzl3SwWn01Ob3= z!A_Q3z;1)Bo}q*_D{yf z0m3N7l%x{&a?jd;^375PLG6R;IOpFh&DIHCqCl1a+`{_Se9*!4zMNmwTXL?t-{>jE z$Xie}xGj0iG^@ABlUF;!?(uq#xzp6Mx6Ul| z3hNeNoe5K6q?JwT%srU~F1bBLqFO8mC)Wd7Dz-`Q%l1u3F$h{!@}CpLAq!dM@jwH~ zzHhAgn;pmsF?>(7CxarmhWJxMrq1YZGA3Wz1@87!l!Y$CN7tfF!$-OzeglAe#;Fqa zb|lGe83*!xm~EW<$fAy1pN?N+1jh^7N;Fv(sOA#NdztDyHWHT705>9F7bCiiL`lba zuDrfhCqn3b@|o;We}3e5IwV1`^#tA^5N0csa*5^|Uaps2XI>j8J}+D#EV;>^A;+$G z{+Fs8c|#Tpo@yv3lRlyn4l|&^Jq!=;RL~3`^STI9=)eF$xiBRN8|}78od%veM~uY) z0C)8CXU0XqVAmNhW(c_;_7qO7P9Tn+s_`f9{trxKU`5_w6P2pjL)u0+J>yQ3gVFf0 zp=6XES5&pbv1@k6pqhcrgVuVtUW~TY!ys3EARHo4$Ke6b!DtC%RRM6oORchPV{wJY zZ}*hbvZAiz_e>FnKS<7#U`cJvJ>LqprgBT)h+^0Ho6q_}){b232RhdecEVytoPMp0 zb}X+S_}3#I8U0T`m*iv^+k>vWbCBpy_!MNYRb=0pTRjiRFc832V;`7x*oAZ;SCur1 z_GrOqO9Zi1Ne1W4*j)f`>&H2fMn&F+oRYW*b=kx34~c^V9_qgv*6_HFZ~iiEJits& zJgk4!dkVNb_Yt7=p~7YNNtUeMg9d6_pr;P4dJhBf@Gx$7RFGT^gE5s7moU@iGu znT^V@qS_zWer=95u@i1Gc?UB|gCk{NS3gMhr#ad8(I`@qG)aZ|UUS{}148nldRpo!`)^i0VQ@Qq^g+rJ?5f==gq7w{|_pWO}2l;^b=O{q0k^lGSE1USIAOou2v4CCA|EEaC9V5YiIo|(O)%OZ;|4x|Tf4Ktx n;|ctiLEZX40|KDl3KEuzJmfzPJO~KSzcU9N1Z4a0|3?28SkL|f delta 14892 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..9f4197d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cb..fcb6fca 100755 --- a/gradlew +++ b/gradlew @@ -130,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. From 8aaae6702ea92f2ac1b0726c5b03547aa2ffe6c3 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 12 Apr 2024 22:37:02 +0400 Subject: [PATCH 02/64] new data system --- README.md | 77 +++- RedisBungee-API/build.gradle.kts | 5 +- .../redisbungee/AbstractRedisBungeeAPI.java | 67 ++- .../redisbungee/api/AbstractDataManager.java | 310 -------------- .../api/AbstractRedisBungeeListener.java | 50 --- .../redisbungee/api/JedisPubSubHandler.java | 36 -- .../redisbungee/api/PlayerDataManager.java | 253 ++++++++++++ .../redisbungee/api/ProxyDataManager.java | 383 ++++++++++++++++++ .../redisbungee/api/PubSubListener.java | 71 ---- .../redisbungee/api/RedisBungeePlugin.java | 245 ++--------- .../api/config/RedisBungeeConfiguration.java | 3 +- .../api/events/EventsPlatform.java | 1 - .../api/payloads/AbstractPayload.java | 24 ++ .../gson/AbstractPayloadSerializer.java | 34 ++ .../api/payloads/proxy/DeathPayload.java | 19 + .../api/payloads/proxy/HeartbeatPayload.java | 31 ++ .../api/payloads/proxy/PubSubPayload.java | 34 ++ .../api/payloads/proxy/RunCommandPayload.java | 36 ++ .../proxy/gson/DeathPayloadSerializer.java | 36 ++ .../gson/HeartbeatPayloadSerializer.java | 38 ++ .../proxy/gson/PubSubPayloadSerializer.java | 40 ++ .../gson/RunCommandPayloadSerializer.java | 38 ++ .../api/summoners/JedisClusterSummoner.java | 6 +- .../summoners/NotClosableJedisCluster.java | 2 - .../redisbungee/api/summoners/Summoner.java | 5 +- .../redisbungee/api/tasks/HeartbeatTask.java | 57 --- .../redisbungee/api/tasks/InitialUtils.java | 86 ---- .../api/tasks/IntegrityCheckTask.java | 91 ----- .../api/tasks/RedisPipelineTask.java | 49 +++ .../redisbungee/api/tasks/RedisTask.java | 27 +- .../redisbungee/api/tasks/ShutdownUtils.java | 39 -- .../redisbungee/api/util/InitialUtils.java | 48 +++ .../redisbungee/api/util/RedisUtil.java | 1 + .../redisbungee/api/util/io/IOUtil.java | 20 - .../api/util/payload/PayloadUtils.java | 41 -- .../api/util/player/PlayerUtils.java | 57 --- ...ations.java => MultiMapSerialization.java} | 3 +- .../api/util/uuid/NameFetcher.java | 58 +-- .../api/util/uuid/UUIDTranslator.java | 6 +- .../src/main/resources/messages.yml | 3 +- RedisBungee-Bungee/build.gradle.kts | 5 +- .../redisbungee/BungeeDataManager.java | 58 --- .../redisbungee/BungeePlayerDataManager.java | 98 +++++ .../redisbungee/BungeePlayerUtils.java | 28 -- .../minecraft/redisbungee/RedisBungee.java | 230 +++++------ .../RedisBungeeBungeeListener.java | 252 ------------ .../redisbungee/RedisBungeeListener.java | 154 +++++++ .../commands/RedisBungeeCommands.java | 9 +- RedisBungee-Velocity/build.gradle.kts | 3 +- .../redisbungee/RedisBungeeListener.java | 156 +++++++ .../RedisBungeeVelocityListener.java | 263 ------------ .../RedisBungeeVelocityPlugin.java | 184 ++++----- .../redisbungee/VelocityDataManager.java | 61 --- .../VelocityPlayerDataManager.java | 100 +++++ .../redisbungee/VelocityPlayerUtils.java | 31 -- .../commands/RedisBungeeCommands.java | 17 +- gradle.properties | 4 +- 57 files changed, 1967 insertions(+), 2116 deletions(-) delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java delete mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java rename RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/{Serializations.java => MultiMapSerialization.java} (97%) delete mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java create mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java delete mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java delete mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeBungeeListener.java create mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java create mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java delete mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java delete mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java create mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java delete mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java diff --git a/README.md b/README.md index c9f1789..97ba182 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # RedisBungee fork By Limework -*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*. +*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do +it, tell mojang to implement transfer packet*. [Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) The original project of RedisBungee is no longer maintained, so we have forked the plugin. -RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) -to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) or [Velocity*](https://github.com/PaperMC/Velocity) proxies +RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) +to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) +or [Velocity*](https://github.com/PaperMC/Velocity) proxies -Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40) +Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be +unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40) + +## Downloads -## Downloads [![](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/plugin/redisbungee) or from github releases @@ -17,12 +21,17 @@ or from github releases https://github.com/ProxioDev/RedisBungee/releases ## notes + If you are looking to use Original RedisBungee without a change to internals, -with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/) -as its last version before internal changes. please note that you will not get support for any old builds unless critical bugs effecting both 0.6.5 and 0.7.0 or above. +with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and +java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/) +as its last version before internal changes. please note that you will not get support for any old builds unless +critical bugs effecting both 0.6.5 and 0.7.0 or above. SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.87700/) + ## Supported Redis versions + | Redis version | Supported | |:-------------:|:---------:| | 1.x.x | ✖ | @@ -33,7 +42,6 @@ SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.8 | 6.x.x | ✔ | | 7.x.x | ✔ | - ## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) RedisBungee is distributed as a [Gradle](https://gradle.org/) project. @@ -41,7 +49,9 @@ RedisBungee is distributed as a [Gradle](https://gradle.org/) project. By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) # Setup jitpack repository + ## maven + ```xml @@ -50,7 +60,9 @@ By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://j ``` + ## gradle (kotlin dsl) + ```kotlin repositories { maven("https://jitpack.io/") @@ -58,8 +70,11 @@ repositories { ``` # [BungeeCord](https://github.com/SpigotMC/BungeeCord) -add this in your project dependencies + +add this in your project dependencies + ## maven + ```xml com.github.proxiodev.redisbungee @@ -70,7 +85,9 @@ add this in your project dependencies ``` + ## gradle (kotlin dsl) + ``` implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0") @@ -79,7 +96,9 @@ implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0:all") exclude("redis.clients", "jedis") } ``` + then in your project plugin.yml add `RedisBungee` to `depends` like this + ```yaml name: "yourplugin" main: your.main.class @@ -88,9 +107,10 @@ author: idk depends: [ RedisBungee ] ``` - ## [Velocity](https://github.com/PaperMC/Velocity) + ## maven + ```xml com.github.proxiodev.redisbungee @@ -100,7 +120,9 @@ depends: [ RedisBungee ] ``` + ## gradle (kotlin dsl) + ``` implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0") @@ -110,7 +132,9 @@ implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0:all } ``` -then to make your plugin depends on RedisBungee, make sure your plugin class Annotation have `@Dependency(id = "redisbungee")` like this +then to make your plugin depends on RedisBungee, make sure your plugin class Annotation +have `@Dependency(id = "redisbungee")` like this + ```java @Plugin( id = "myplugin", @@ -124,15 +148,20 @@ public class PluginMainClass { } ``` + ## Getting the latest commits to your code + If you want to use the latest commits without waiting for releases. first, install it to your maven local repo + ```bash git clone https://github.com/ProxioDev/RedisBungee.git cd RedisBungee ./gradlew publishToMavenLocal ``` + then use any of these in your project. + ```xml com.imaginarycode.minecraft @@ -142,6 +171,7 @@ then use any of these in your project. ``` + ```xml com.imaginarycode.minecraft @@ -151,6 +181,7 @@ then use any of these in your project. ``` + ## Javadocs * API: https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc/ @@ -159,20 +190,25 @@ then use any of these in your project. ## Configuration -**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is saved when the plugin first starts. - +**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The +default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is +saved when the plugin first starts. ## compatibility with original RedisBungee in Bungeecord ecosystem + This fork ensures compatibility with old plugins, so it should work as drop replacement, -but since Api has been split from the platform there some changes that have to be done, so your plugin might not work if: +but since Api has been split from the platform there some changes that have to be done, so your plugin might not work +if: * there is none at the moment, please report any findings at the issue page. Cluster mode compatibility in version 0.8.0: If you are using static legacy method `RedisBungee#getPool()` it might fail in: + * if Cluster mode is enabled, due fact its Uses different classes -* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than Jedis +* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than + Jedis ## Support @@ -184,12 +220,15 @@ This project is distributed under Eclipse Public License 1.0 You can find it [here](https://github.com/proxiodev/RedisBungee/blob/master/LICENSE) -You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be found [here](https://github.com/minecrafter/RedisBungee) or spigot page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/) - - +You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be +found [here](https://github.com/minecrafter/RedisBungee) or spigot +page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/) ## YourKit -YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). +YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET +applications. YourKit is the creator +of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) +and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/). ![YourKit](https://www.yourkit.com/images/yklogo.png) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 3af847e..933d566 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { api("redis.clients:jedis:$jedisVersion") api("com.squareup.okhttp:okhttp:2.7.5") api("org.spongepowered:configurate-yaml:$configurateVersion") - + api("com.github.ben-manes.caffeine:caffeine:3.1.8") // tests testImplementation("junit:junit:4.13.2") } @@ -68,11 +68,10 @@ tasks { compileJava { options.encoding = Charsets.UTF_8.name() - options.release.set(8) + options.release.set(17) } javadoc { options.encoding = Charsets.UTF_8.name() - } processResources { filteringCharset = Charsets.UTF_8.name() diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java index ccbb95b..70e3e7e 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java @@ -10,8 +10,6 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; @@ -40,21 +38,13 @@ import java.util.*; public abstract class AbstractRedisBungeeAPI { protected final RedisBungeePlugin plugin; private static AbstractRedisBungeeAPI abstractRedisBungeeAPI; - protected final List reservedChannels; - AbstractRedisBungeeAPI(RedisBungeePlugin plugin) { - // this does make sure that no one can place first initiated API class. + public AbstractRedisBungeeAPI(RedisBungeePlugin plugin) { + // this does make sure that no one can replace first initiated API class. if (abstractRedisBungeeAPI == null) { abstractRedisBungeeAPI = this; } - this.reservedChannels = ImmutableList.of( - "redisbungee-allservers", - "redisbungee-" + plugin.getConfiguration().getProxyId(), - "redisbungee-data" - ); - this.plugin = plugin; - } /** @@ -63,7 +53,7 @@ public abstract class AbstractRedisBungeeAPI { * @return a count of all players found */ public final int getPlayerCount() { - return plugin.getCount(); + return plugin.proxyDataManager().totalNetworkPlayers(); } /** @@ -74,7 +64,7 @@ public abstract class AbstractRedisBungeeAPI { * @return the last time a player was on, if online returns a 0 */ public final long getLastOnline(@NonNull UUID player) { - return plugin.getDataManager().getLastOnline(player); + return plugin.playerDataManager().getLastOnline(player); } /** @@ -86,7 +76,7 @@ public abstract class AbstractRedisBungeeAPI { */ @Nullable public final String getServerNameFor(@NonNull UUID player) { - return plugin.getDataManager().getServer(player); + return plugin.playerDataManager().getServerFor(player); } /** @@ -97,7 +87,7 @@ public abstract class AbstractRedisBungeeAPI { * @return a Set with all players found */ public final Set getPlayersOnline() { - return plugin.getPlayers(); + return plugin.proxyDataManager().networkPlayers(); } /** @@ -118,11 +108,11 @@ public abstract class AbstractRedisBungeeAPI { /** * Get a full list of players on all servers. * - * @return a immutable Multimap with all players found on this server + * @return a immutable Multimap with all players found on this network * @since 0.2.5 */ public final Multimap getServerToPlayers() { - return plugin.serverToPlayersCache(); + return plugin.playerDataManager().serversToPlayers(); } /** @@ -138,11 +128,11 @@ public abstract class AbstractRedisBungeeAPI { /** * Get a list of players on the specified proxy. * - * @param server a server name + * @param proxyID proxy id * @return a Set with all UUIDs found on this proxy */ - public final Set getPlayersOnProxy(@NonNull String server) { - return plugin.getPlayersOnProxy(server); + public final Set getPlayersOnProxy(@NonNull String proxyID) { + return plugin.proxyDataManager().getPlayersOn(proxyID); } /** @@ -163,7 +153,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.4 */ public final InetAddress getPlayerIp(@NonNull UUID player) { - return plugin.getDataManager().getIp(player); + return plugin.playerDataManager().getIpFor(player); } /** @@ -174,7 +164,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.3.3 */ public final String getProxy(@NonNull UUID player) { - return plugin.getDataManager().getProxy(player); + return plugin.playerDataManager().getProxyFor(player); } /** @@ -185,7 +175,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.5 */ public final void sendProxyCommand(@NonNull String command) { - plugin.sendProxyCommand("allservers", command); + sendProxyCommand("allservers", command); } /** @@ -198,19 +188,20 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.2.5 */ public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) { - plugin.sendProxyCommand(proxyId, command); + plugin.proxyDataManager().sendCommandTo(proxyId, command); } /** - * Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for - * PubSubMessageEvent to fire. + * Sends a message to a PubSub channel which makes PubSubMessageEvent fire. + *

+ * Note: Since 0.12.0 registering a channel api is no longer required * * @param channel The PubSub channel * @param message the message body to send * @since 0.3.3 */ public final void sendChannelMessage(@NonNull String channel, @NonNull String message) { - plugin.sendChannelMessage(channel, message); + plugin.proxyDataManager().sendChannelMessage(channel, message); } /** @@ -221,7 +212,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public final String getProxyId() { - return plugin.getConfiguration().getProxyId(); + return plugin.proxyDataManager().proxyId(); } /** @@ -245,7 +236,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public final List getAllProxies() { - return plugin.getProxiesIds(); + return plugin.proxyDataManager().proxiesIds(); } /** @@ -266,9 +257,10 @@ public abstract class AbstractRedisBungeeAPI { * * @param channels the channels to register * @since 0.3 + * @deprecated No longer required */ + @Deprecated public final void registerPubSubChannels(String... channels) { - plugin.getPubSubListener().addChannel(channels); } /** @@ -276,13 +268,10 @@ public abstract class AbstractRedisBungeeAPI { * * @param channels the channels to unregister * @since 0.3 + * @deprecated No longer required */ + @Deprecated public final void unregisterPubSubChannels(String... channels) { - for (String channel : channels) { - Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel"); - } - - plugin.getPubSubListener().removeChannel(channels); } /** @@ -355,6 +344,7 @@ public abstract class AbstractRedisBungeeAPI { /** * Kicks a player from the network + * calls {@link #getUuidFromName(String)} to get uuid * * @param playerName player name * @param message kick message that player will see on kick @@ -362,7 +352,7 @@ public abstract class AbstractRedisBungeeAPI { */ public void kickPlayer(String playerName, String message) { - plugin.kickPlayer(playerName, message); + kickPlayer(getUuidFromName(playerName), message); } /** @@ -373,7 +363,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 */ public void kickPlayer(UUID playerUUID, String message) { - plugin.kickPlayer(playerUUID, message); + this.plugin.playerDataManager().kickPlayer(playerUUID, message); } /** @@ -457,6 +447,7 @@ public abstract class AbstractRedisBungeeAPI { /** * shows what mode is RedisBungee is on + * Basically what every redis mode is used like cluster or single instance. * * @return {@link RedisBungeeMode} * @since 0.8.0 diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java deleted file mode 100644 index 1bff5b5..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractDataManager.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.net.InetAddresses; -import com.google.common.reflect.TypeToken; -import com.google.common.util.concurrent.UncheckedExecutionException; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -/** - * This class manages all the data that RedisBungee fetches from Redis, along with updates to that data. - * - * @since 0.3.3 - */ -public abstract class AbstractDataManager { - protected final RedisBungeePlugin

plugin; - private final Cache serverCache = createCache(); - private final Cache proxyCache = createCache(); - private final Cache ipCache = createCache(); - private final Cache lastOnlineCache = createCache(); - private final Gson gson = new Gson(); - - public AbstractDataManager(RedisBungeePlugin

plugin) { - this.plugin = plugin; - } - - private static Cache createCache() { - // TODO: Allow customization via cache specification, ala ServerListPlus - return CacheBuilder.newBuilder() - .maximumSize(1000) - .expireAfterWrite(1, TimeUnit.HOURS) - .build(); - } - - public String getServer(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null; - - try { - return serverCache.get(uuid, new RedisTask(plugin) { - @Override - public String unifiedJedisTask(UnifiedJedis unifiedJedis) { - return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "server"), "user not found"); - - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get server"); - throw new RuntimeException("Unable to get server for " + uuid, e); - } - } - - - public String getProxy(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.getConfiguration().getProxyId(); - - try { - return proxyCache.get(uuid, new RedisTask(plugin) { - @Override - public String unifiedJedisTask(UnifiedJedis unifiedJedis) { - return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "proxy"), "user not found"); - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get proxy"); - throw new RuntimeException("Unable to get proxy for " + uuid, e); - } - } - - public InetAddress getIp(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return plugin.getPlayerIp(player); - - try { - return ipCache.get(uuid, new RedisTask(plugin) { - @Override - public InetAddress unifiedJedisTask(UnifiedJedis unifiedJedis) { - String result = unifiedJedis.hget("player:" + uuid, "ip"); - if (result == null) - throw new NullPointerException("user not found"); - return InetAddresses.forString(result); - } - }); - } catch (ExecutionException | UncheckedExecutionException e) { - if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found")) - return null; // HACK - plugin.logFatal("Unable to get IP"); - throw new RuntimeException("Unable to get IP for " + uuid, e); - } - } - - public long getLastOnline(final UUID uuid) { - P player = plugin.getPlayer(uuid); - - if (player != null) - return 0; - - try { - return lastOnlineCache.get(uuid, new RedisTask(plugin) { - - @Override - public Long unifiedJedisTask(UnifiedJedis unifiedJedis) { - String result = unifiedJedis.hget("player:" + uuid, "online"); - return result == null ? -1 : Long.parseLong(result); - } - }); - } catch (ExecutionException e) { - plugin.logFatal("Unable to get last time online"); - throw new RuntimeException("Unable to get last time online for " + uuid, e); - } - } - - protected void invalidate(UUID uuid) { - ipCache.invalidate(uuid); - lastOnlineCache.invalidate(uuid); - serverCache.invalidate(uuid); - proxyCache.invalidate(uuid); - } - - // Invalidate all entries related to this player, since they now lie. (call invalidate(uuid)) - public abstract void onPostLogin(PL event); - - // Invalidate all entries related to this player, since they now lie. (call invalidate(uuid)) - public abstract void onPlayerDisconnect(PD event); - - public abstract void onPubSubMessage(PS event); - - public abstract boolean handleKick(UUID target, String message); - - protected void handlePubSubMessage(String channel, String message) { - if (!channel.equals("redisbungee-data")) - return; - - // Partially deserialize the message so we can look at the action - JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject(); - - final String source = jsonObject.get("source").getAsString(); - - if (source.equals(plugin.getConfiguration().getProxyId())) - return; - - DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString()); - - switch (action) { - case JOIN: - final DataManagerMessage message1 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - proxyCache.put(message1.getTarget(), message1.getSource()); - lastOnlineCache.put(message1.getTarget(), (long) 0); - ipCache.put(message1.getTarget(), message1.getPayload().getAddress()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerJoinedNetworkEvent(message1.getTarget()); - plugin.fireEvent(event); - }); - break; - case LEAVE: - final DataManagerMessage message2 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - invalidate(message2.getTarget()); - lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerLeftNetworkEvent(message2.getTarget()); - plugin.fireEvent(event); - }); - break; - case SERVER_CHANGE: - final DataManagerMessage message3 = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - serverCache.put(message3.getTarget(), message3.getPayload().getServer()); - plugin.executeAsync(() -> { - Object event = plugin.createPlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer()); - plugin.fireEvent(event); - }); - break; - case KICK: - final DataManagerMessage kickPayload = gson.fromJson(jsonObject, new TypeToken>() { - }.getType()); - plugin.executeAsync(() -> handleKick(kickPayload.target, kickPayload.payload.message)); - break; - - } - } - - public static class DataManagerMessage { - private final UUID target; - private final String source; - private final Action action; // for future use! - private final T payload; - - public DataManagerMessage(UUID target, String source, Action action, T payload) { - this.target = target; - this.source = source; - this.action = action; - this.payload = payload; - } - - public UUID getTarget() { - return target; - } - - public String getSource() { - return source; - } - - public Action getAction() { - return action; - } - - public T getPayload() { - return payload; - } - - public enum Action { - JOIN, - LEAVE, - KICK, - SERVER_CHANGE - } - } - - public static abstract class Payload { - } - - public static class KickPayload extends Payload { - - private final String message; - - public KickPayload(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - } - - public static class LoginPayload extends Payload { - private final InetAddress address; - - public LoginPayload(InetAddress address) { - this.address = address; - } - - public InetAddress getAddress() { - return address; - } - } - - public static class ServerChangePayload extends Payload { - private final String server; - private final String oldServer; - - public ServerChangePayload(String server, String oldServer) { - this.server = server; - this.oldServer = oldServer; - } - - public String getServer() { - return server; - } - - public String getOldServer() { - return oldServer; - } - } - - - public static class LogoutPayload extends Payload { - private final long timestamp; - - public LogoutPayload(long timestamp) { - this.timestamp = timestamp; - } - - public long getTimestamp() { - return timestamp; - } - } -} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java deleted file mode 100644 index 64b42ab..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/AbstractRedisBungeeListener.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - - -import com.google.common.collect.Multimap; -import com.google.common.collect.Multiset; -import com.google.common.io.ByteArrayDataOutput; -import com.google.gson.Gson; - -import java.net.InetAddress; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public abstract class AbstractRedisBungeeListener { - - protected final RedisBungeePlugin plugin; - protected final List exemptAddresses; - protected final Gson gson = new Gson(); - - public AbstractRedisBungeeListener(RedisBungeePlugin plugin, List exemptAddresses) { - this.plugin = plugin; - this.exemptAddresses = exemptAddresses; - } - - public void onLogin(LE event) {} - - public abstract void onPostLogin(PLE event); - - public abstract void onPlayerDisconnect(PD event); - - public abstract void onServerChange(SC event); - - public abstract void onPing(PP event); - - public abstract void onPluginMessage(PM event); - - public abstract void onPubSubMessage(PS event); - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java deleted file mode 100644 index d3974a5..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/JedisPubSubHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - - -import redis.clients.jedis.JedisPubSub; - - -public class JedisPubSubHandler extends JedisPubSub { - - private final RedisBungeePlugin plugin; - - public JedisPubSubHandler(RedisBungeePlugin plugin) { - this.plugin = plugin; - } - - @Override - public void onMessage(final String s, final String s2) { - if (s2.trim().length() == 0) return; - plugin.executeAsync(new Runnable() { - @Override - public void run() { - Object event = plugin.createPubSubEvent(s, s2); - plugin.fireEvent(event); - } - }); - } -} \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java new file mode 100644 index 0000000..c6ccbb6 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.net.InetAddresses; +import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import org.json.JSONObject; +import redis.clients.jedis.ClusterPipeline; +import redis.clients.jedis.Pipeline; +import redis.clients.jedis.Response; +import redis.clients.jedis.UnifiedJedis; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public abstract class PlayerDataManager { + + protected final RedisBungeePlugin

plugin; + private final LoadingCache serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis); + private final LoadingCache proxyCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis); + private final LoadingCache ipCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis); + private final LoadingCache lastOnlineCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis); + private final Object SERVERS_TO_PLAYERS_KEY = new Object(); + private final LoadingCache> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder); + private final UnifiedJedis unifiedJedis; + + public PlayerDataManager(RedisBungeePlugin

plugin) { + this.plugin = plugin; + this.unifiedJedis = plugin.proxyDataManager().unifiedJedis(); + } + + // handle network wide + // server change + public abstract void onPlayerChangedServerNetworkEvent(SC event); + + public abstract void onNetworkPlayerQuit(NJE event); + + // local events + public abstract void onPubSubMessageEvent(PS event); + + public abstract void onServerConnectedEvent(CE event); + + public abstract void onLoginEvent(LE event); + + public abstract void onDisconnectEvent(DE event); + + + protected void handleNetworkPlayerServerChange(IPlayerChangedServerNetworkEvent event) { + this.serverCache.invalidate(event.getUuid()); + } + + protected void handleNetworkPlayerQuit(IPlayerLeftNetworkEvent event) { + this.proxyCache.invalidate(event.getUuid()); + this.serverCache.invalidate(event.getUuid()); + this.ipCache.invalidate(event.getUuid()); + this.lastOnlineCache.invalidate(event.getUuid()); + } + + protected void handlePubSubMessageEvent(IPubSubMessageEvent event) { + // kick api + if (event.getChannel().equals("redisbungee-kick")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + String message = data.getString("message"); + plugin.handlePlatformKick(uuid, message); + return; + } + if (event.getChannel().equals("redisbungee-serverchange")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + String from = data.getString("from"); + String to = data.getString("to"); + plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to)); + return; + } + if (event.getChannel().equals("redisbungee-player-join")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); + return; + } + if (event.getChannel().equals("redisbungee-player-leave")) { + JSONObject data = new JSONObject(event.getMessage()); + String proxy = data.getString("proxy"); + if (proxy.equals(plugin.configuration().getProxyId())) { + return; + } + UUID uuid = UUID.fromString(data.getString("uuid")); + plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + } + + } + + protected void playerChangedServer(UUID uuid, String from, String to) { + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + data.put("from", from); + data.put("to", to); + plugin.proxyDataManager().sendChannelMessage("redisbungee-serverchange", data.toString()); + plugin.fireEvent(plugin.createPlayerChangedServerNetworkEvent(uuid, from, to)); + handleServerChangeRedis(uuid, to); + } + + public void kickPlayer(UUID uuid, String message) { + if (!plugin.handlePlatformKick(uuid, message)) { // handle locally before SENDING a message + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + data.put("message", message); + plugin.proxyDataManager().sendChannelMessage("redisbungee-kick", data.toString()); + } + } + + private void handleServerChangeRedis(UUID uuid, String server) { + Map data = new HashMap<>(); + data.put("server", server); + data.put("last-server", server); + unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", data); + } + + protected void addPlayer(final UUID uuid, final InetAddress inetAddress) { + Map redisData = new HashMap<>(); + redisData.put("last-online", String.valueOf(0)); + redisData.put("proxy", plugin.configuration().getProxyId()); + redisData.put("ip", inetAddress.toString()); + unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", redisData); + + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + plugin.proxyDataManager().sendChannelMessage("redisbungee-player-join", data.toString()); + plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); + this.plugin.proxyDataManager().addPlayer(uuid); + } + + protected void removePlayer(UUID uuid) { + unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", "last-online", String.valueOf(System.currentTimeMillis())); + unifiedJedis.hdel("redis-bungee::player::" + uuid + "::data", "server", "proxy", "ip"); + JSONObject data = new JSONObject(); + data.put("proxy", plugin.configuration().getProxyId()); + data.put("uuid", uuid); + plugin.proxyDataManager().sendChannelMessage("redisbungee-player-leave", data.toString()); + plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + this.plugin.proxyDataManager().removePlayer(uuid); + } + + + protected String getProxyFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "proxy"); + } + + protected String getServerFromRedis(UUID uuid) { + return unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "server"); + } + + protected InetAddress getIpAddressFromRedis(UUID uuid) { + String ip = unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "ip"); + if (ip == null) return null; + return InetAddresses.forString(ip); + } + + protected long getLastOnlineFromRedis(UUID uuid) { + String unixString = unifiedJedis.hget("redis-bungee::player::" + uuid + "::data", "last-online"); + if (unixString == null) return -1; + return Long.parseLong(unixString); + } + + public String getServerFor(UUID uuid) { + return this.serverCache.get(uuid); + } + + public String getProxyFor(UUID uuid) { + return this.proxyCache.get(uuid); + } + + public InetAddress getIpFor(UUID uuid) { + return this.ipCache.get(uuid); + } + + public long getLastOnline(UUID uuid) { + return this.lastOnlineCache.get(uuid); + } + + public Multimap serversToPlayers() { + return this.serverToPlayersCache.get(SERVERS_TO_PLAYERS_KEY); + } + + protected Multimap serversToPlayersBuilder(Object o) { + try { + return new RedisPipelineTask>(plugin) { + private final Set uuids = plugin.proxyDataManager().networkPlayers(); + private final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + + @Override + public Multimap doPooledPipeline(Pipeline pipeline) { + HashMap> responses = new HashMap<>(); + for (UUID uuid : uuids) { + responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server")); + } + pipeline.sync(); + responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); + return builder.build(); + } + + @Override + public Multimap clusterPipeline(ClusterPipeline pipeline) { + HashMap> responses = new HashMap<>(); + for (UUID uuid : uuids) { + responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server")); + } + pipeline.sync(); + responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); + return builder.build(); + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java new file mode 100644 index 0000000..73a7c14 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.gson.AbstractPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.DeathPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.HeartbeatPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import redis.clients.jedis.*; +import redis.clients.jedis.params.XAddParams; +import redis.clients.jedis.params.XReadParams; +import redis.clients.jedis.resps.StreamEntry; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.common.base.Preconditions.checkArgument; + +public abstract class ProxyDataManager implements Runnable, AutoCloseable { + + private static final String STREAM_ID = "redisbungee-stream"; + private static final int MAX_ENTRIES = 10000; + + private final AtomicBoolean closed = new AtomicBoolean(false); + + private final UnifiedJedis unifiedJedis; + + // data: + // Proxy id, heartbeat (unix epoch from instant), players as int + private final ConcurrentHashMap heartbeats = new ConcurrentHashMap<>(); + + private final String proxyId; + + // This different from proxy id, just to detect if there is duplicate proxy using same proxy id + private final UUID dataManagerUUID = UUID.randomUUID(); + + protected final RedisBungeePlugin plugin; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(AbstractPayload.class, new AbstractPayloadSerializer()).registerTypeAdapter(HeartbeatPayload.class, new HeartbeatPayloadSerializer()).registerTypeAdapter(DeathPayload.class, new DeathPayloadSerializer()).registerTypeAdapter(PubSubPayload.class, new PubSubPayloadSerializer()).registerTypeAdapter(RunCommandPayload.class, new RunCommandPayloadSerializer()).create(); + + public ProxyDataManager(RedisBungeePlugin plugin) { + this.plugin = plugin; + this.proxyId = this.plugin.configuration().getProxyId(); + this.unifiedJedis = plugin.getSummoner().obtainResource(); + this.destroyProxyMembers(); + } + + public abstract Set getLocalOnlineUUIDs(); + + public Set getPlayersOn(String proxyId) { + checkArgument(proxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); + if (proxyId.equals(this.proxyId)) return this.getLocalOnlineUUIDs(); + if (!this.heartbeats.containsKey(proxyId)) { + return new HashSet<>(); // return empty hashset or null? + } + return getProxyMembers(proxyId); + } + + public List proxiesIds() { + return Collections.list(this.heartbeats.keys()); + } + + public synchronized void sendCommandTo(String proxyToRun, String command) { + if (isClosed()) return; + publishPayload(new RunCommandPayload(this.proxyId, proxyToRun, command)); + } + + public synchronized void sendChannelMessage(String channel, String message) { + if (isClosed()) return; + this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message)); + publishPayload(new PubSubPayload(this.proxyId, channel, message)); + } + + // call every 1 second + public synchronized void publishHeartbeat() { + if (isClosed()) return; + HeartbeatPayload.HeartbeatData heartbeatData = new HeartbeatPayload.HeartbeatData(Instant.now().getEpochSecond(), this.getLocalOnlineUUIDs().size()); + this.heartbeats.put(this.proxyId(), heartbeatData); + publishPayload(new HeartbeatPayload(this.proxyId, heartbeatData)); + } + + public Set networkPlayers() { + try { + return new RedisPipelineTask>(this.plugin) { + @Override + public Set doPooledPipeline(Pipeline pipeline) { + HashSet>> responses = new HashSet<>(); + for (String proxyId : proxiesIds()) { + responses.add(pipeline.smembers("redisbungee::proxies::" + proxyId + "::online-players")); + } + pipeline.sync(); + HashSet uuids = new HashSet<>(); + for (Response> response : responses) { + for (String stringUUID : response.get()) { + uuids.add(UUID.fromString(stringUUID)); + } + } + return uuids; + } + + @Override + public Set clusterPipeline(ClusterPipeline pipeline) { + HashSet>> responses = new HashSet<>(); + for (String proxyId : proxiesIds()) { + responses.add(pipeline.smembers("redisbungee::proxies::" + proxyId + "::online-players")); + } + pipeline.sync(); + HashSet uuids = new HashSet<>(); + for (Response> response : responses) { + for (String stringUUID : response.get()) { + uuids.add(UUID.fromString(stringUUID)); + } + } + return uuids; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException("unable to get network players", e); + } + + } + + public int totalNetworkPlayers() { + int players = 0; + for (HeartbeatPayload.HeartbeatData value : this.heartbeats.values()) { + players += value.players(); + } + return players; + } + + // Call on close + private synchronized void publishDeath() { + publishPayload(new DeathPayload(this.proxyId)); + } + + private void publishPayload(AbstractPayload payload) { + Map data = new HashMap<>(); + data.put("payload", gson.toJson(payload)); + data.put("data-manager-uuid", this.dataManagerUUID.toString()); + data.put("class", payload.getClassName()); + this.unifiedJedis.xadd(STREAM_ID, XAddParams.xAddParams().maxLen(MAX_ENTRIES).id(StreamEntryID.NEW_ENTRY), data); + } + + + private void handleHeartBeat(HeartbeatPayload payload) { + String id = payload.senderProxy(); + if (!heartbeats.containsKey(id)) { + plugin.logInfo("Proxy {} has connected", id); + } + heartbeats.put(id, payload.data()); + } + + + // call every 1 minutes + public void correctionTask() { + // let's check this proxy players + Set localOnlineUUIDs = getLocalOnlineUUIDs(); + Set storedRedisUuids = getProxyMembers(this.proxyId); + + if (!localOnlineUUIDs.equals(storedRedisUuids)) { + plugin.logWarn("De-synced playerS set detected correcting...."); + Set add = new HashSet<>(localOnlineUUIDs); + Set remove = new HashSet<>(storedRedisUuids); + add.removeAll(storedRedisUuids); + remove.removeAll(localOnlineUUIDs); + for (UUID uuid : add) { + plugin.logWarn("found {} that isn't in the set, adding it to the Corrected set", uuid); + } + for (UUID uuid : remove) { + plugin.logWarn("found {} that does not belong to this proxy removing it from the corrected set", uuid); + } + try { + new RedisPipelineTask(plugin) { + @Override + public Void doPooledPipeline(Pipeline pipeline) { + Set removeString = new HashSet<>(); + for (UUID uuid : remove) { + removeString.add(uuid.toString()); + } + Set addString = new HashSet<>(); + for (UUID uuid : add) { + addString.add(uuid.toString()); + } + pipeline.srem("redisbungee::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::proxies::" + proxyId() + "::online-players", addString.toArray(new String[]{})); + pipeline.sync(); + return null; + } + + @Override + public Void clusterPipeline(ClusterPipeline pipeline) { + Set removeString = new HashSet<>(); + for (UUID uuid : remove) { + removeString.add(uuid.toString()); + } + Set addString = new HashSet<>(); + for (UUID uuid : add) { + addString.add(uuid.toString()); + } + pipeline.srem("redisbungee::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::proxies::" + proxyId() + "::online-players", addString.toArray(new String[]{})); + pipeline.sync(); + return null; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + plugin.logInfo("Player set has been corrected!"); + } + + + // handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~10 seconds + final Set deadProxies = new HashSet<>(); + for (Map.Entry stringHeartbeatDataEntry : this.heartbeats.entrySet()) { + String id = stringHeartbeatDataEntry.getKey(); + long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat(); + if (Instant.now().getEpochSecond() - heartbeat > 10) { + deadProxies.add(id); + cleanProxy(id); + } + } + try { + new RedisPipelineTask(plugin) { + @Override + public Void doPooledPipeline(Pipeline pipeline) { + for (String deadProxy : deadProxies) { + pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players"); + } + pipeline.sync(); + return null; + } + + @Override + public Void clusterPipeline(ClusterPipeline pipeline) { + for (String deadProxy : deadProxies) { + pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players"); + } + pipeline.sync(); + return null; + } + }.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void handleProxyDeath(DeathPayload payload) { + cleanProxy(payload.senderProxy()); + } + + private void cleanProxy(String id) { + if (id.equals(this.proxyId())) { + return; + } + for (UUID uuid : getProxyMembers(id)) plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + plugin.logInfo("Proxy {} has disconnected", id); + } + + private void handleChannelMessage(PubSubPayload payload) { + String channel = payload.channel(); + String message = payload.message(); + this.plugin.fireEvent(this.plugin.createPubSubEvent(channel, message)); + } + + protected abstract void handlePlatformCommandExecution(String command); + + private void handleCommand(RunCommandPayload payload) { + String proxyToRun = payload.proxyToRun(); + String command = payload.command(); + if (proxyToRun.equals("allservers") || proxyToRun.equals(this.proxyId())) { + handlePlatformCommandExecution(command); + } + } + + + public void addPlayer(UUID uuid) { + this.unifiedJedis.sadd("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString()); + } + + public void removePlayer(UUID uuid) { + this.unifiedJedis.srem("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString()); + } + + private void destroyProxyMembers() { + unifiedJedis.del("redisbungee::proxies::" + this.proxyId + "::online-players"); + } + + private Set getProxyMembers(String proxyId) { + Set uuidsStrings = unifiedJedis.smembers("redisbungee::proxies::" + proxyId + "::online-players"); + HashSet uuids = new HashSet<>(); + for (String proxyMember : uuidsStrings) { + uuids.add(UUID.fromString(proxyMember)); + } + return uuids; + } + + private StreamEntryID lastStreamEntryID; + + // polling from stream + @Override + public void run() { + while (!isClosed()) { + try { + List>> data = unifiedJedis.xread(XReadParams.xReadParams().block(0), Collections.singletonMap(STREAM_ID, lastStreamEntryID != null ? lastStreamEntryID : StreamEntryID.LAST_ENTRY)); + for (Map.Entry> datum : data) { + for (StreamEntry streamEntry : datum.getValue()) { + this.lastStreamEntryID = streamEntry.getID(); + String payloadData = streamEntry.getFields().get("payload"); + String clazz = streamEntry.getFields().get("class"); + UUID payloadDataManagerUUID = UUID.fromString(streamEntry.getFields().get("data-manager-uuid")); + + AbstractPayload unknownPayload = (AbstractPayload) gson.fromJson(payloadData, Class.forName(clazz)); + + if (unknownPayload.senderProxy().equals(this.proxyId)) { + if (!payloadDataManagerUUID.equals(this.dataManagerUUID)) { + plugin.logWarn("detected other proxy is using same ID! {} this can cause issues, please shutdown this proxy and change the id!", this.proxyId); + } + break; + } + if (unknownPayload instanceof HeartbeatPayload payload) { + handleHeartBeat(payload); + } else if (unknownPayload instanceof DeathPayload payload) { + handleProxyDeath(payload); + } else if (unknownPayload instanceof RunCommandPayload payload) { + handleCommand(payload); + } else if (unknownPayload instanceof PubSubPayload payload) { + handleChannelMessage(payload); + } else { + plugin.logWarn("got unknown data manager payload: {}", unknownPayload.getClassName()); + } + } + } + } catch (Exception e) { + this.plugin.logFatal("an error has occurred in the stream", e); + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { + } + } + } + } + + @Override + public void close() throws Exception { + closed.set(true); + this.publishDeath(); + this.heartbeats.clear(); + this.destroyProxyMembers(); + } + + public boolean isClosed() { + return closed.get(); + } + + public String proxyId() { + return proxyId; + } + + public UnifiedJedis unifiedJedis() { + return unifiedJedis; + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java deleted file mode 100644 index cd19d71..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PubSubListener.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api; - -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class PubSubListener implements Runnable { - private JedisPubSubHandler jpsh; - private final Set addedChannels = new HashSet(); - - private final RedisBungeePlugin plugin; - - public PubSubListener(RedisBungeePlugin plugin) { - this.plugin = plugin; - } - - @Override - public void run() { - RedisTask subTask = new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - jpsh = new JedisPubSubHandler(plugin); - addedChannels.add("redisbungee-" + plugin.getConfiguration().getProxyId()); - addedChannels.add("redisbungee-allservers"); - addedChannels.add("redisbungee-data"); - unifiedJedis.subscribe(jpsh, addedChannels.toArray(new String[0])); - return null; - } - }; - - try { - subTask.execute(); - } catch (Exception e) { - plugin.logWarn("PubSub error, attempting to recover in 5 secs."); - plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5); - } - } - - public void addChannel(String... channel) { - addedChannels.addAll(Arrays.asList(channel)); - jpsh.subscribe(channel); - } - - public void removeChannel(String... channel) { - Arrays.asList(channel).forEach(addedChannels::remove); - jpsh.unsubscribe(channel); - } - - public void poison() { - addedChannels.clear(); - jpsh.unsubscribe(); - } -} - diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java index a0e2471..3c827a0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java @@ -10,28 +10,16 @@ package com.imaginarycode.minecraft.redisbungee.api; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; -import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; -import redis.clients.jedis.Protocol; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; import java.net.InetAddress; -import java.util.*; +import java.util.UUID; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Preconditions.checkArgument; - /** * This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee @@ -51,225 +39,54 @@ public interface RedisBungeePlugin

extends EventsPlatform { } + void logInfo(String msg); + + void logInfo(String format, Object... object); + + void logWarn(String msg); + + void logWarn(String format, Object... object); + + void logFatal(String msg); + + void logFatal(String format, Throwable throwable); + + RedisBungeeConfiguration configuration(); + Summoner getSummoner(); - RedisBungeeConfiguration getConfiguration(); - - int getCount(); - - default int getCurrentCount() { - return new RedisTask(this) { - @Override - public Long unifiedJedisTask(UnifiedJedis unifiedJedis) { - long total = 0; - long redisTime = getRedisTime(unifiedJedis); - Map heartBeats = unifiedJedis.hgetAll("heartbeats"); - for (Map.Entry stringStringEntry : heartBeats.entrySet()) { - String k = stringStringEntry.getKey(); - String v = stringStringEntry.getValue(); - - long heartbeatTime = Long.parseLong(v); - if (heartbeatTime + RedisUtil.PROXY_TIMEOUT >= redisTime) { - total = total + unifiedJedis.scard("proxy:" + k + ":usersOnline"); - } - } - return total; - } - }.execute().intValue(); - } - - Set getLocalPlayersAsUuidStrings(); - - AbstractDataManager getDataManager(); - - default Set getPlayers() { - return new RedisTask>(this) { - @Override - public Set unifiedJedisTask(UnifiedJedis unifiedJedis) { - ImmutableSet.Builder setBuilder = ImmutableSet.builder(); - try { - List keys = new ArrayList<>(); - for (String i : getProxiesIds()) { - keys.add("proxy:" + i + ":usersOnline"); - } - if (!keys.isEmpty()) { - Set users = unifiedJedis.sunion(keys.toArray(new String[0])); - if (users != null && !users.isEmpty()) { - for (String user : users) { - try { - setBuilder = setBuilder.add(UUID.fromString(user)); - } catch (IllegalArgumentException ignored) { - } - } - } - } - } catch (JedisConnectionException e) { - // Redis server has disappeared! - logFatal("Unable to get connection from pool - did your Redis server go away?"); - throw new RuntimeException("Unable to get all players online", e); - } - return setBuilder.build(); - } - }.execute(); - } + RedisBungeeMode getRedisBungeeMode(); AbstractRedisBungeeAPI getAbstractRedisBungeeApi(); + ProxyDataManager proxyDataManager(); + + PlayerDataManager playerDataManager(); + UUIDTranslator getUuidTranslator(); - Multimap serverToPlayersCache(); + boolean isOnlineMode(); - default Multimap serversToPlayers() { - return new RedisTask>(this) { - @Override - public Multimap unifiedJedisTask(UnifiedJedis unifiedJedis) { - ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - for (String serverId : getProxiesIds()) { - Set players = unifiedJedis.smembers("proxy:" + serverId + ":usersOnline"); - for (String player : players) { - String playerServer = unifiedJedis.hget("player:" + player, "server"); - if (playerServer == null) { - continue; - } - builder.put(playerServer, UUID.fromString(player)); - } - } - return builder.build(); - } - }.execute(); - } + public P getPlayer(UUID uuid); - default Set getPlayersOnProxy(String proxyId) { - checkArgument(getProxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID"); - return new RedisTask>(this) { - @Override - public Set unifiedJedisTask(UnifiedJedis unifiedJedis) { - Set users = unifiedJedis.smembers("proxy:" + proxyId + ":usersOnline"); - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (String user : users) { - builder.add(UUID.fromString(user)); - } - return builder.build(); - } - }.execute(); - } + public P getPlayer(String name); - default void sendProxyCommand(String proxyId, String command) { - checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid"); - sendChannelMessage("redisbungee-" + proxyId, command); - } + public UUID getPlayerUUID(String player); - List getProxiesIds(); - default List getCurrentProxiesIds(boolean lagged) { - return new RedisTask>(this) { - @Override - public List unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - long time = getRedisTime(unifiedJedis); - ImmutableList.Builder servers = ImmutableList.builder(); - Map heartbeats = unifiedJedis.hgetAll("heartbeats"); - for (Map.Entry entry : heartbeats.entrySet()) { - try { - long stamp = Long.parseLong(entry.getValue()); - if (lagged ? time >= stamp + RedisUtil.PROXY_TIMEOUT : time <= stamp + RedisUtil.PROXY_TIMEOUT) { - servers.add(entry.getKey()); - } else if (time > stamp + RedisUtil.PROXY_TIMEOUT) { - logWarn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat."); - unifiedJedis.hdel("heartbeats", entry.getKey()); - } - } catch (NumberFormatException ignored) { - } - } - return servers.build(); - } catch (JedisConnectionException e) { - logFatal("Unable to fetch server IDs"); - e.printStackTrace(); - return Collections.singletonList(getConfiguration().getProxyId()); - } - } - }.execute(); - } + public String getPlayerName(UUID player); - PubSubListener getPubSubListener(); + boolean handlePlatformKick(UUID uuid, String message); - default void sendChannelMessage(String channel, String message) { - new RedisTask(this) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - unifiedJedis.publish(channel, message); - } catch (JedisConnectionException e) { - // Redis server has disappeared! - logFatal("Unable to get connection from pool - did your Redis server go away?"); - throw new RuntimeException("Unable to publish channel message", e); - } - return null; - } - }.execute(); - } + public String getPlayerServerName(P player); + + public boolean isPlayerOnAServer(P player); + + public InetAddress getPlayerIp(P player); void executeAsync(Runnable runnable); void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time); - boolean isOnlineMode(); - - void logInfo(String msg); - - void logWarn(String msg); - - void logFatal(String msg); - - P getPlayer(UUID uuid); - - P getPlayer(String name); - - UUID getPlayerUUID(String player); - - String getPlayerName(UUID player); - - String getPlayerServerName(P player); - - boolean isPlayerOnAServer(P player); - - InetAddress getPlayerIp(P player); - - default void sendProxyCommand(String cmd) { - sendProxyCommand(getConfiguration().getProxyId(), cmd); - } - - default Long getRedisTime(UnifiedJedis unifiedJedis) { - List data = (List) unifiedJedis.sendCommand(Protocol.Command.TIME); - List times = new ArrayList<>(); - data.forEach((o) -> times.add(new String((byte[])o))); - return getRedisTime(times); - } - default long getRedisTime(List timeRes) { - return Long.parseLong(timeRes.get(0)); - } - - default void kickPlayer(UUID playerUniqueId, String message) { - // first handle on origin proxy if player not found publish the payload - if (!getDataManager().handleKick(playerUniqueId, message)) { - new RedisTask(this) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PayloadUtils.kickPlayerPayload(playerUniqueId, message, unifiedJedis); - return null; - } - }.execute(); - } - } - - default void kickPlayer(String playerName, String message) { - // fetch the uuid from name - UUID playerUUID = getUuidTranslator().getTranslatedUuid(playerName, true); - kickPlayer(playerUUID, message); - } - - RedisBungeeMode getRedisBungeeMode(); - - void updateProxiesIds(); } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index 2e595f4..e10c9d0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -12,11 +12,9 @@ package com.imaginarycode.minecraft.redisbungee.api.config; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; import com.google.common.net.InetAddresses; import java.net.InetAddress; -import java.util.HashMap; import java.util.List; public class RedisBungeeConfiguration { @@ -47,6 +45,7 @@ public class RedisBungeeConfiguration { this.overrideBungeeCommands = overrideBungeeCommands; this.restoreOldKickBehavior = restoreOldKickBehavior; } + public String getProxyId() { return proxyId; } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java index 099c075..79dabfa 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/events/EventsPlatform.java @@ -17,7 +17,6 @@ import java.util.UUID; * * @author Ham1255 * @since 0.7.0 - * */ public interface EventsPlatform { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java new file mode 100644 index 0000000..e41ee5f --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/AbstractPayload.java @@ -0,0 +1,24 @@ + +package com.imaginarycode.minecraft.redisbungee.api.payloads; + +public abstract class AbstractPayload { + + private final String senderProxy; + + public AbstractPayload(String proxyId) { + this.senderProxy = proxyId; + } + + public AbstractPayload(String senderProxy, String className) { + this.senderProxy = senderProxy; + } + + public String senderProxy() { + return senderProxy; + } + + public String getClassName() { + return getClass().getName(); + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java new file mode 100644 index 0000000..6769ff2 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/gson/AbstractPayloadSerializer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +import java.lang.reflect.Type; + +public class AbstractPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public AbstractPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + return new AbstractPayload(jsonObject.get("proxy").getAsString(), jsonObject.get("class").getAsString()) { + }; + } + + @Override + public JsonElement serialize(AbstractPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java new file mode 100644 index 0000000..399071a --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/DeathPayload.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class DeathPayload extends AbstractPayload { + public DeathPayload(String proxyId) { + super(proxyId); + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java new file mode 100644 index 0000000..02268fd --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/HeartbeatPayload.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class HeartbeatPayload extends AbstractPayload { + + public record HeartbeatData(long heartbeat, int players) { + + } + + private final HeartbeatData data; + + public HeartbeatPayload(String proxyId, HeartbeatData data) { + super(proxyId); + this.data = data; + } + + public HeartbeatData data() { + return data; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java new file mode 100644 index 0000000..eaa9092 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/PubSubPayload.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class PubSubPayload extends AbstractPayload { + + private final String channel; + private final String message; + + + public PubSubPayload(String proxyId, String channel, String message) { + super(proxyId); + this.channel = channel; + this.message = message; + } + + public String channel() { + return channel; + } + + public String message() { + return message; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java new file mode 100644 index 0000000..6374e5c --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/RunCommandPayload.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy; + +import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; + +public class RunCommandPayload extends AbstractPayload { + + + private final String proxyToRun; + + private final String command; + + + public RunCommandPayload(String proxyId, String proxyToRun, String command) { + super(proxyId); + this.proxyToRun = proxyToRun; + this.command = command; + } + + public String proxyToRun() { + return proxyToRun; + } + + public String command() { + return command; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java new file mode 100644 index 0000000..d77dd51 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/DeathPayloadSerializer.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.DeathPayload; + +import java.lang.reflect.Type; + +public class DeathPayloadSerializer implements JsonSerializer, JsonDeserializer { + + private static final Gson gson = new Gson(); + + + @Override + public DeathPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + return new DeathPayload(senderProxy); + } + + @Override + public JsonElement serialize(DeathPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java new file mode 100644 index 0000000..1f301f2 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/HeartbeatPayloadSerializer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.HeartbeatPayload; + +import java.lang.reflect.Type; + +public class HeartbeatPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public HeartbeatPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + long heartbeat = jsonObject.get("heartbeat").getAsLong(); + int players = jsonObject.get("players").getAsInt(); + return new HeartbeatPayload(senderProxy, new HeartbeatPayload.HeartbeatData(heartbeat, players)); + } + + @Override + public JsonElement serialize(HeartbeatPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("heartbeat", new JsonPrimitive(src.data().heartbeat())); + jsonObject.add("players", new JsonPrimitive(src.data().players())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java new file mode 100644 index 0000000..01d66a5 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/PubSubPayloadSerializer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.PubSubPayload; + +import java.lang.reflect.Type; + +public class PubSubPayloadSerializer implements JsonSerializer, JsonDeserializer { + + private static final Gson gson = new Gson(); + + + @Override + public PubSubPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + String channel = jsonObject.get("channel").getAsString(); + String message = jsonObject.get("message").getAsString(); + return new PubSubPayload(senderProxy, channel, message); + } + + @Override + public JsonElement serialize(PubSubPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("channel", new JsonPrimitive(src.channel())); + jsonObject.add("message", context.serialize(src.message())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java new file mode 100644 index 0000000..2a7de33 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/payloads/proxy/gson/RunCommandPayloadSerializer.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson; + +import com.google.gson.*; +import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.RunCommandPayload; + +import java.lang.reflect.Type; + +public class RunCommandPayloadSerializer implements JsonSerializer, JsonDeserializer { + + + @Override + public RunCommandPayload deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String senderProxy = jsonObject.get("proxy").getAsString(); + String proxyToRun = jsonObject.get("proxy-to-run").getAsString(); + String command = jsonObject.get("command").getAsString(); + return new RunCommandPayload(senderProxy, proxyToRun, command); + } + + @Override + public JsonElement serialize(RunCommandPayload src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject jsonObject = new JsonObject(); + jsonObject.add("proxy", new JsonPrimitive(src.senderProxy())); + jsonObject.add("proxy-to-run", new JsonPrimitive(src.proxyToRun())); + jsonObject.add("command", context.serialize(src.command())); + return jsonObject; + } +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java index 99d8e19..14c2514 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/JedisClusterSummoner.java @@ -17,7 +17,7 @@ import java.io.IOException; import java.time.Duration; public class JedisClusterSummoner implements Summoner { - public final ClusterConnectionProvider clusterConnectionProvider; + private final ClusterConnectionProvider clusterConnectionProvider; public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) { this.clusterConnectionProvider = clusterConnectionProvider; @@ -35,6 +35,8 @@ public class JedisClusterSummoner implements Summoner { @Override public JedisCluster obtainResource() { - return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(30000)); + return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(10)); } + + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java index 84eb85a..5e09859 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/NotClosableJedisCluster.java @@ -11,9 +11,7 @@ package com.imaginarycode.minecraft.redisbungee.api.summoners; import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.JedisPooled; import redis.clients.jedis.providers.ClusterConnectionProvider; -import redis.clients.jedis.providers.PooledConnectionProvider; import java.time.Duration; diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java index 6b511e7..36beac5 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/summoners/Summoner.java @@ -10,6 +10,8 @@ package com.imaginarycode.minecraft.redisbungee.api.summoners; +import redis.clients.jedis.UnifiedJedis; + import java.io.Closeable; @@ -18,9 +20,8 @@ import java.io.Closeable; * * @author Ham1255 * @since 0.7.0 - * */ -public interface Summoner

extends Closeable { +public interface Summoner

extends Closeable { P obtainResource(); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java deleted file mode 100644 index 669ba8c..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/HeartbeatTask.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; -import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class HeartbeatTask extends RedisTask{ - - public final static TimeUnit REPEAT_INTERVAL_TIME_UNIT = TimeUnit.SECONDS; - public final static int INTERVAL = 1; - private final AtomicInteger globalPlayerCount; - - public HeartbeatTask(RedisBungeePlugin plugin, AtomicInteger globalPlayerCount) { - super(plugin); - this.globalPlayerCount = globalPlayerCount; - } - - - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - long redisTime = plugin.getRedisTime(unifiedJedis); - unifiedJedis.hset("heartbeats", plugin.getConfiguration().getProxyId(), String.valueOf(redisTime)); - } catch (JedisConnectionException e) { - // Redis server has disappeared! - plugin.logFatal("Unable to update heartbeat - did your Redis server go away?"); - e.printStackTrace(); - return null; - } - try { - plugin.updateProxiesIds(); - globalPlayerCount.set(plugin.getCurrentCount()); - } catch (Throwable e) { - plugin.logFatal("Unable to update data - did your Redis server go away?"); - e.printStackTrace(); - } - return null; - } - - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java deleted file mode 100644 index 8a2986f..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/InitialUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; -import redis.clients.jedis.Protocol; -import redis.clients.jedis.UnifiedJedis; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -public class InitialUtils { - - public static void checkRedisVersion(RedisBungeePlugin plugin) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - // This is more portable than INFO

- String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); - for (String s : info.split("\r\n")) { - if (s.startsWith("redis_version:")) { - String version = s.split(":")[1]; - plugin.logInfo("Redis server version: " + version); - if (!RedisUtil.isRedisVersionRight(version)) { - plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis."); - throw new RuntimeException("Unsupported Redis version detected"); - } - long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); - if (uuidCacheSize > 750000) { - plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains"); - } - break; - } - } - return null; - } - }.execute(); - } - - - public static void checkIfRecovering(RedisBungeePlugin plugin, Path dataFolder) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - Path crashFile = dataFolder.resolve("restarted_from_crash.txt"); - if (Files.exists(crashFile)) { - try { - Files.delete(crashFile); - } catch (IOException e) { - throw new RuntimeException(e); - } - plugin.logInfo("crash file was deleted continuing RedisBungee startup "); - } else if (unifiedJedis.hexists("heartbeats", plugin.getConfiguration().getProxyId())) { - try { - long value = Long.parseLong(unifiedJedis.hget("heartbeats", plugin.getConfiguration().getProxyId())); - long redisTime = plugin.getRedisTime(unifiedJedis); - - if (redisTime < value + RedisUtil.PROXY_TIMEOUT) { - logImposter(plugin); - throw new RuntimeException("Possible impostor instance!"); - } - } catch (NumberFormatException ignored) { - } - } - return null; - } - }.execute(); - } - - private static void logImposter(RedisBungeePlugin plugin) { - plugin.logFatal("You have launched a possible impostor Velocity / Bungeecord instance. Another instance is already running."); - plugin.logFatal("For data consistency reasons, RedisBungee will now disable itself."); - plugin.logFatal("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check."); - } - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java deleted file mode 100644 index c13742e..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/IntegrityCheckTask.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.UnifiedJedis; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public abstract class IntegrityCheckTask extends RedisTask { - - public static int INTERVAL = 30; - public static TimeUnit TIMEUNIT = TimeUnit.SECONDS; - - - public IntegrityCheckTask(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - Set players = plugin.getLocalPlayersAsUuidStrings(); - Set playersInRedis = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline"); - List lagged = plugin.getCurrentProxiesIds(true); - - // Clean up lagged players. - for (String s : lagged) { - Set laggedPlayers = unifiedJedis.smembers("proxy:" + s + ":usersOnline"); - unifiedJedis.del("proxy:" + s + ":usersOnline"); - if (!laggedPlayers.isEmpty()) { - plugin.logInfo("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)..."); - for (String laggedPlayer : laggedPlayers) { - PlayerUtils.cleanUpPlayer(laggedPlayer, unifiedJedis, true); - } - } - } - - Set absentLocally = new HashSet<>(playersInRedis); - absentLocally.removeAll(players); - Set absentInRedis = new HashSet<>(players); - absentInRedis.removeAll(playersInRedis); - - for (String member : absentLocally) { - boolean found = false; - for (String proxyId : plugin.getProxiesIds()) { - if (proxyId.equals(plugin.getConfiguration().getProxyId())) continue; - if (unifiedJedis.sismember("proxy:" + proxyId + ":usersOnline", member)) { - // Just clean up the set. - found = true; - break; - } - } - if (!found) { - PlayerUtils.cleanUpPlayer(member, unifiedJedis, false); - plugin.logWarn("Player found in set that was not found locally and globally: " + member); - } else { - unifiedJedis.srem("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline", member); - plugin.logWarn("Player found in set that was not found locally, but is on another proxy: " + member); - } - } - // due unifiedJedis does not support pipelined. - //Pipeline pipeline = jedis.pipelined(); - - for (String player : absentInRedis) { - // Player not online according to Redis but not BungeeCord. - handlePlatformPlayer(player, unifiedJedis); - } - } catch (Throwable e) { - plugin.logFatal("Unable to fix up stored player data"); - e.printStackTrace(); - } - return null; - } - - - public abstract void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis); - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java new file mode 100644 index 0000000..21a5d29 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisPipelineTask.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.tasks; + +import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import redis.clients.jedis.*; + +public abstract class RedisPipelineTask extends RedisTask { + + + public RedisPipelineTask(AbstractRedisBungeeAPI api) { + super(api); + } + + public RedisPipelineTask(RedisBungeePlugin plugin) { + super(plugin); + } + + + @Override + public T unifiedJedisTask(UnifiedJedis unifiedJedis) { + if (unifiedJedis instanceof JedisPooled pooled) { + try (Pipeline pipeline = pooled.pipelined()) { + return doPooledPipeline(pipeline); + } + } else if (unifiedJedis instanceof JedisCluster jedisCluster) { + try (ClusterPipeline pipeline = jedisCluster.pipelined()) { + return clusterPipeline(pipeline); + } + } + + return null; + } + + public abstract T doPooledPipeline(Pipeline pipeline); + + public abstract T clusterPipeline(ClusterPipeline pipeline); + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java index eb1b416..9a6da17 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/RedisTask.java @@ -11,11 +11,11 @@ package com.imaginarycode.minecraft.redisbungee.api.tasks; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import redis.clients.jedis.UnifiedJedis; import java.util.concurrent.Callable; @@ -27,23 +27,22 @@ import java.util.concurrent.Callable; public abstract class RedisTask implements Runnable, Callable { protected final Summoner summoner; - protected final AbstractRedisBungeeAPI api; - protected RedisBungeePlugin plugin; + + protected final RedisBungeeMode mode; @Override public V call() throws Exception { - return execute(); + return this.execute(); } public RedisTask(AbstractRedisBungeeAPI api) { - this.api = api; this.summoner = api.getSummoner(); + this.mode = api.getMode(); } public RedisTask(RedisBungeePlugin plugin) { - this.plugin = plugin; - this.api = plugin.getAbstractRedisBungeeApi(); - this.summoner = api.getSummoner(); + this.summoner = plugin.getSummoner(); + this.mode = plugin.getRedisBungeeMode(); } public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis); @@ -53,22 +52,16 @@ public abstract class RedisTask implements Runnable, Callable { this.execute(); } - public V execute(){ + public V execute() { // JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway. - if (api.getMode() == RedisBungeeMode.SINGLE) { + if (mode == RedisBungeeMode.SINGLE) { JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner; return this.unifiedJedisTask(jedisSummoner.obtainResource()); - } else if (api.getMode() == RedisBungeeMode.CLUSTER) { + } else if (mode == RedisBungeeMode.CLUSTER) { JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner; return this.unifiedJedisTask(jedisClusterSummoner.obtainResource()); } return null; } - public RedisBungeePlugin getPlugin() { - if (plugin == null) { - throw new NullPointerException("Plugin is null in the task"); - } - return plugin; - } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java deleted file mode 100644 index a3fdbcc..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/ShutdownUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.api.tasks; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisCluster; -import redis.clients.jedis.UnifiedJedis; - -import java.util.Set; - -public class ShutdownUtils { - - public static void shutdownCleanup(RedisBungeePlugin plugin) { - new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hdel("heartbeats", plugin.getConfiguration().getProxyId()); - if (unifiedJedis.scard("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline") > 0) { - Set players = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline"); - for (String member : players) - PlayerUtils.cleanUpPlayer(member, unifiedJedis, true); - } - return null; - } - }.execute(); - } - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java new file mode 100644 index 0000000..6815770 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.util; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; +import redis.clients.jedis.Protocol; +import redis.clients.jedis.UnifiedJedis; + + +public class InitialUtils { + + public static void checkRedisVersion(RedisBungeePlugin plugin) { + new RedisTask(plugin) { + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + // This is more portable than INFO
+ String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO)); + for (String s : info.split("\r\n")) { + if (s.startsWith("redis_version:")) { + String version = s.split(":")[1]; + plugin.logInfo("Redis server version: " + version); + if (!RedisUtil.isRedisVersionRight(version)) { + plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis."); + throw new RuntimeException("Unsupported Redis version detected"); + } + long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); + if (uuidCacheSize > 750000) { + plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains"); + } + break; + } + } + return null; + } + }.execute(); + } + + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java index 9e4bd92..2e00c1e 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java @@ -5,6 +5,7 @@ import com.google.common.annotations.VisibleForTesting; @VisibleForTesting public class RedisUtil { public final static int PROXY_TIMEOUT = 30; + public static boolean isRedisVersionRight(String redisVersion) { String[] args = redisVersion.split("\\."); if (args.length < 2) { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java deleted file mode 100644 index fa4290e..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/io/IOUtil.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.io; - -import com.google.common.io.ByteStreams; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - - -public class IOUtil { - public static String readInputStreamAsString(InputStream is) { - String string; - try { - string = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new AssertionError(e); - } - return string; - } -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java deleted file mode 100644 index 36e9b78..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/payload/PayloadUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.payload; - -import com.google.gson.Gson; -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.UUID; - -public class PayloadUtils { - private static final Gson gson = new Gson(); - - public static void playerJoinPayload(UUID uuid, UnifiedJedis unifiedJedis, InetAddress inetAddress) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.JOIN, - new AbstractDataManager.LoginPayload(inetAddress)))); - } - - - public static void playerQuitPayload(String uuid, UnifiedJedis unifiedJedis, long timestamp) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - UUID.fromString(uuid), AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.LEAVE, - new AbstractDataManager.LogoutPayload(timestamp)))); - } - - - - public static void playerServerChangePayload(UUID uuid, UnifiedJedis unifiedJedis, String newServer, String oldServer) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.SERVER_CHANGE, - new AbstractDataManager.ServerChangePayload(newServer, oldServer)))); - } - - - public static void kickPlayerPayload(UUID uuid, String message, UnifiedJedis unifiedJedis) { - unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>( - uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.KICK, - new AbstractDataManager.KickPayload(message)))); - } -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java deleted file mode 100644 index 820ad34..0000000 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/player/PlayerUtils.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.imaginarycode.minecraft.redisbungee.api.util.player; - -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerJoinPayload; -import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerQuitPayload; - -public class PlayerUtils { - - public static void cleanUpPlayer(String uuid, UnifiedJedis rsc, boolean firePayload) { - final long timestamp = System.currentTimeMillis(); - final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid, rsc); - rsc.srem("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid); - if (!isKickedFromOtherLocation) { - rsc.hdel("player:" + uuid, "server", "ip", "proxy"); - rsc.hset("player:" + uuid, "online", String.valueOf(timestamp)); - } - if (firePayload && !isKickedFromOtherLocation) { - playerQuitPayload(uuid, rsc, timestamp); - } - } - - public static void setKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) { - // set anything for sake of exists check. then expire it after 2 seconds. should be great? - unifiedJedis.set("kicked-other-location::" + uuid, "0"); - unifiedJedis.expire("kicked-other-location::" + uuid, 2); - } - - public static boolean isKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) { - return unifiedJedis.exists("kicked-other-location::" + uuid); - } - - - public static void createPlayer(UUID uuid, UnifiedJedis unifiedJedis, String currentServer, InetAddress hostname, boolean fireEvent) { - final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid.toString(), unifiedJedis); - Map playerData = new HashMap<>(4); - playerData.put("online", "0"); - playerData.put("ip", hostname.getHostName()); - playerData.put("proxy", AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId()); - if (currentServer != null) { - playerData.put("server", currentServer); - } - unifiedJedis.sadd("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid.toString()); - unifiedJedis.hset("player:" + uuid, playerData); - if (fireEvent && !isKickedFromOtherLocation) { - playerJoinPayload(uuid, unifiedJedis, hostname); - } - } - - -} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java similarity index 97% rename from RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java rename to RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java index 7ee9cc5..71df20b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/Serializations.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/serialize/MultiMapSerialization.java @@ -7,7 +7,7 @@ import com.google.common.io.ByteArrayDataOutput; import java.util.Collection; import java.util.Map; -public class Serializations { +public class MultiMapSerialization { public static void serializeMultiset(Multiset collection, ByteArrayDataOutput output) { output.writeInt(collection.elementSet().size()); @@ -36,4 +36,5 @@ public class Serializations { output.writeUTF(o.toString()); } } + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java index 69eb689..3bcd38c 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/NameFetcher.java @@ -22,38 +22,38 @@ import java.util.List; import java.util.UUID; public class NameFetcher { - private static OkHttpClient httpClient; - private static final Gson gson = new Gson(); + private static OkHttpClient httpClient; + private static final Gson gson = new Gson(); - public static void setHttpClient(OkHttpClient httpClient) { - NameFetcher.httpClient = httpClient; - } + public static void setHttpClient(OkHttpClient httpClient) { + NameFetcher.httpClient = httpClient; + } - public static List nameHistoryFromUuid(UUID uuid) throws IOException { - String name = getName(uuid); - if (name == null) return Collections.emptyList(); - return Collections.singletonList(name); - } + public static List nameHistoryFromUuid(UUID uuid) throws IOException { + String name = getName(uuid); + if (name == null) return Collections.emptyList(); + return Collections.singletonList(name); + } - public static String getName(UUID uuid) throws IOException { - String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); - Request request = new Request.Builder() - .addHeader("User-Agent", "RedisBungee-ProxioDev") - .url(url) - .get() - .build(); - ResponseBody body = httpClient.newCall(request).execute().body(); - String response = body.string(); - body.close(); + public static String getName(UUID uuid) throws IOException { + String url = "https://playerdb.co/api/player/minecraft/" + uuid.toString(); + Request request = new Request.Builder() + .addHeader("User-Agent", "RedisBungee-ProxioDev") + .url(url) + .get() + .build(); + ResponseBody body = httpClient.newCall(request).execute().body(); + String response = body.string(); + body.close(); - JsonObject json = gson.fromJson(response, JsonObject.class); - if (!json.has("success") || !json.get("success").getAsBoolean()) return null; - if (!json.has("data")) return null; - JsonObject data = json.getAsJsonObject("data"); - if (!data.has("player")) return null; - JsonObject player = data.getAsJsonObject("player"); - if (!player.has("username")) return null; + JsonObject json = gson.fromJson(response, JsonObject.class); + if (!json.has("success") || !json.get("success").getAsBoolean()) return null; + if (!json.has("data")) return null; + JsonObject data = json.getAsJsonObject("data"); + if (!data.has("player")) return null; + JsonObject player = data.getAsJsonObject("player"); + if (!player.has("username")) return null; - return player.get("username").getAsString(); - } + return player.get("username").getAsString(); + } } \ No newline at end of file diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java index 7453074..acccf40 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/uuid/UUIDTranslator.java @@ -14,13 +14,15 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; - import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; import org.checkerframework.checker.nullness.qual.NonNull; import redis.clients.jedis.UnifiedJedis; import redis.clients.jedis.exceptions.JedisException; -import java.util.*; +import java.util.Calendar; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml index a1b1853..737b52c 100644 --- a/RedisBungee-API/src/main/resources/messages.yml +++ b/RedisBungee-API/src/main/resources/messages.yml @@ -1,2 +1,3 @@ logged-in-other-location: "§cYou logged in from another location!" -already-logged-in: "§cYou are already logged in!" \ No newline at end of file +already-logged-in: "§cYou are already logged in!" +error: "§cError has occurred" \ No newline at end of file diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 78cf66f..07e1cdb 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -40,11 +40,11 @@ tasks { options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) } runWaterfall { - waterfallVersion("1.19") + waterfallVersion("1.20") } compileJava { options.encoding = Charsets.UTF_8.name() - options.release.set(8) + options.release.set(17) } javadoc { options.encoding = Charsets.UTF_8.name() @@ -73,6 +73,7 @@ tasks { relocate("com.google.gson", "com.imaginarycode.minecraft.redisbungee.internal.com.google.gson") relocate("com.google.j2objc", "com.imaginarycode.minecraft.redisbungee.internal.com.google.j2objc") relocate("com.google.thirdparty", "com.imaginarycode.minecraft.redisbungee.internal.com.google.thirdparty") + relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") } } diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java deleted file mode 100644 index dea9185..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeDataManager.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.PlayerDisconnectEvent; -import net.md_5.bungee.api.event.PostLoginEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; - -import java.util.UUID; - -public class BungeeDataManager extends AbstractDataManager implements Listener { - - public BungeeDataManager(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - @EventHandler - public void onPostLogin(PostLoginEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @EventHandler - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @EventHandler - public void onPubSubMessage(PubSubMessageEvent event) { - handlePubSubMessage(event.getChannel(), event.getMessage()); - } - - @Override - public boolean handleKick(UUID target, String message) { - // check if the player is online on this proxy - ProxiedPlayer player = plugin.getPlayer(target); - if (player == null) return false; - player.disconnect(TextComponent.fromLegacyText(message)); - return true; - } -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java new file mode 100644 index 0000000..e312b7c --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + + +public class BungeePlayerDataManager extends PlayerDataManager implements Listener { + public BungeePlayerDataManager(RedisBungeePlugin plugin) { + super(plugin); + } + + @Override + @EventHandler + public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { + super.handleNetworkPlayerServerChange(event); + } + + @Override + @EventHandler + public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { + super.handleNetworkPlayerQuit(event); + } + + @Override + @EventHandler + public void onPubSubMessageEvent(PubSubMessageEvent event) { + super.handlePubSubMessageEvent(event); + } + + @Override + @EventHandler + public void onServerConnectedEvent(ServerConnectedEvent event) { + final String currentServer = event.getServer().getInfo().getName(); + final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); + super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); + } + + @EventHandler + public void onLoginEvent(LoginEvent event) { + event.registerIntent((Plugin) plugin); + // check if online + if (getLastOnline(event.getConnection().getUniqueId()) == 0) { + if (!plugin.configuration().restoreOldKickBehavior()) { + kickPlayer(event.getConnection().getUniqueId(), plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); + // wait 3 seconds before releasing the event + plugin.executeAsyncAfter(() -> event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); + } else { + event.setCancelled(true); + event.setCancelReason(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))))); + event.completeIntent((Plugin) plugin); + } + } else { + event.completeIntent((Plugin) plugin); + } + + } + + @Override + @EventHandler + public void onLoginEvent(PostLoginEvent event) { + super.addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getAddress().getAddress()); + } + + @Override + @EventHandler + public void onDisconnectEvent(PlayerDisconnectEvent event) { + super.removePlayer(event.getPlayer().getUniqueId()); + } + + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java deleted file mode 100644 index f1a46c6..0000000 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import net.md_5.bungee.api.connection.PendingConnection; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import redis.clients.jedis.UnifiedJedis; -public class BungeePlayerUtils { - - public static void createBungeePlayer(ProxiedPlayer player, UnifiedJedis unifiedJedis, boolean fireEvent) { - String serverName = null; - if (player.getServer() != null) { - serverName = player.getServer().getInfo().getName(); - } - PendingConnection pendingConnection = player.getPendingConnection(); - PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, pendingConnection.getAddress().getAddress(), fireEvent); - } - -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index f540ab6..81988c4 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -10,131 +10,91 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.tasks.*; +import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; +import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.*; -import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; -import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; import com.squareup.okhttp.Dispatcher; import com.squareup.okhttp.OkHttpClient; import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Plugin; -import redis.clients.jedis.*; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.JedisPool; -import java.io.*; +import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; -import java.util.*; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; public class RedisBungee extends Plugin implements RedisBungeePlugin, ConfigLoader { private static RedisBungeeAPI apiStatic; - private AbstractRedisBungeeAPI api; private RedisBungeeMode redisBungeeMode; - private PubSubListener psl = null; + private ProxyDataManager proxyDataManager; + private BungeePlayerDataManager playerDataManager; + private ScheduledTask heartbeatTask; + private ScheduledTask cleanupTask; private Summoner summoner; private UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; - private BungeeDataManager dataManager; private OkHttpClient httpClient; - private volatile List proxiesIds; - private final AtomicInteger globalPlayerCount = new AtomicInteger(); - private Future integrityCheck; - private Future heartbeatTask; - private static final Object SERVER_TO_PLAYERS_KEY = new Object(); - private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); + + private final Logger logger = LoggerFactory.getLogger("RedisBungee"); @Override - public RedisBungeeConfiguration getConfiguration() { + public RedisBungeeConfiguration configuration() { return this.configuration; } - @Override - public int getCount() { - return this.globalPlayerCount.get(); - } - - @Override - public Set getLocalPlayersAsUuidStrings() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (ProxiedPlayer player : getProxy().getPlayers()) { - builder.add(player.getUniqueId().toString()); - } - return builder.build(); - } - - @Override - public AbstractDataManager getDataManager() { - return this.dataManager; - } - @Override public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() { return this.api; } + @Override + public ProxyDataManager proxyDataManager() { + return this.proxyDataManager; + } + + @Override + public PlayerDataManager playerDataManager() { + return this.playerDataManager; + } + @Override public UUIDTranslator getUuidTranslator() { return this.uuidTranslator; } - @Override - public Multimap serverToPlayersCache() { - try { - return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getProxiesIds() { - return proxiesIds; - } - - @Override - public PubSubListener getPubSubListener() { - return this.psl; - } - - @Override - public void executeAsync(Runnable runnable) { - this.getProxy().getScheduler().runAsync(this, runnable); - } - - @Override - public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) { - this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit); - } - @Override public void fireEvent(Object event) { this.getProxy().getPluginManager().callEvent((Event) event); @@ -147,17 +107,32 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin getLocalOnlineUUIDs() { + HashSet uuids = new HashSet<>(); + ProxyServer.getInstance().getPlayers().forEach((proxiedPlayer) -> uuids.add(proxiedPlayer.getUniqueId())); + return uuids; + } + + @Override + protected void handlePlatformCommandExecution(String command) { + logInfo("Dispatching {}", command); + ProxyServer.getInstance().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), command); + } + }; + this.playerDataManager = new BungeePlayerDataManager(this); + + getProxy().getPluginManager().registerListener(this, this.playerDataManager); + getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this)); + // start listening + getProxy().getScheduler().runAsync(this, proxyDataManager); + // heartbeat + this.heartbeatTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.publishHeartbeat(), 0, 1, TimeUnit.SECONDS); + // cleanup + this.cleanupTask = getProxy().getScheduler().schedule(this, () -> this.proxyDataManager.correctionTask(), 0, 60, TimeUnit.SECONDS); // init the http lib httpClient = new OkHttpClient(); Dispatcher dispatcher = new Dispatcher(getExecutorService()); @@ -226,29 +232,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin implements Listener { - - - public RedisBungeeBungeeListener(RedisBungeePlugin plugin, List exemptAddresses) { - super(plugin, exemptAddresses); - } - - @Override - @EventHandler(priority = HIGHEST) - public void onLogin(LoginEvent event) { - event.registerIntent((Plugin) plugin); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - if (event.isCancelled()) { - return null; - } - if (plugin.getConfiguration().restoreOldKickBehavior()) { - for (String s : plugin.getProxiesIds()) { - if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) { - event.setCancelled(true); - event.setCancelReason(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)); - return null; - } - } - } else if (api.isPlayerOnline(event.getConnection().getUniqueId())) { - PlayerUtils.setKickedOtherLocation(event.getConnection().getUniqueId().toString(), unifiedJedis); - api.kickPlayer(event.getConnection().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); - } - return null; - } finally { - event.completeIntent((Plugin) plugin); - } - } - }); - } - - @Override - @EventHandler - public void onPostLogin(PostLoginEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), unifiedJedis); - BungeePlayerUtils.createBungeePlayer(event.getPlayer(), unifiedJedis, true); - return null; - } - }); - } - - @Override - @EventHandler - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true); - return null; - } - }); - - } - - @Override - @EventHandler - public void onServerChange(ServerConnectedEvent event) { - final String currentServer = event.getServer().getInfo().getName(); - final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName(); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName()); - PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer); - return null; - } - }); - } - - @Override - @EventHandler - public void onPing(ProxyPingEvent event) { - if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) { - return; - } - ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); - - if (forced != null && event.getConnection().getListener().isPingPassthrough()) { - return; - } - event.getResponse().getPlayers().setOnline(plugin.getCount()); - } - - @Override - @SuppressWarnings("UnstableApiUsage") - @EventHandler - public void onPluginMessage(PluginMessageEvent event) { - if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) { - final String currentChannel = event.getTag(); - final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); - plugin.executeAsync(() -> { - ByteArrayDataInput in = ByteStreams.newDataInput(data); - - String subchannel = in.readUTF(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - String type; - - switch (subchannel) { - case "PlayerList": - out.writeUTF("PlayerList"); - Set original = Collections.emptySet(); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - original = plugin.getPlayers(); - } else { - out.writeUTF(type); - try { - original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); - } catch (IllegalArgumentException ignored) { - } - } - Set players = new HashSet<>(); - for (UUID uuid : original) - players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false)); - out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": - out.writeUTF("PlayerCount"); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - out.writeInt(plugin.getCount()); - } else { - out.writeUTF(type); - try { - out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); - } catch (IllegalArgumentException e) { - out.writeInt(0); - } - } - break; - case "LastOnline": - String user = in.readUTF(); - out.writeUTF("LastOnline"); - out.writeUTF(user); - out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": - String type1 = in.readUTF(); - out.writeUTF("ServerPlayers"); - Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - - boolean includesUsers; - - switch (type1) { - case "COUNT": - includesUsers = false; - break; - case "PLAYERS": - includesUsers = true; - break; - default: - // TODO: Should I raise an error? - return; - } - - out.writeUTF(type1); - - if (includesUsers) { - Multimap human = HashMultimap.create(); - for (Map.Entry entry : multimap.entries()) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - serializeMultimap(human, true, out); - } else { - serializeMultiset(multimap.keys(), out); - } - break; - case "Proxy": - out.writeUTF("Proxy"); - out.writeUTF(plugin.getConfiguration().getProxyId()); - break; - case "PlayerProxy": - String username = in.readUTF(); - out.writeUTF("PlayerProxy"); - out.writeUTF(username); - out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: - return; - } - - ((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); - }); - } - } - - @Override - @EventHandler - public void onPubSubMessage(PubSubMessageEvent event) { - if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) { - String message = event.getMessage(); - if (message.startsWith("/")) - message = message.substring(1); - plugin.logInfo("Invoking command via PubSub: /" + message); - ((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message); - } - } -} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java new file mode 100644 index 0000000..6d7b6a7 --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import net.md_5.bungee.api.AbstractReconnectHandler; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +import java.util.*; + +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap; +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset; + +public class RedisBungeeListener implements Listener { + + private final RedisBungeePlugin plugin; + + public RedisBungeeListener(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPing(ProxyPingEvent event) { + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) { + return; + } + ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); + + if (forced != null && event.getConnection().getListener().isPingPassthrough()) { + return; + } + event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers()); + } + + @SuppressWarnings("UnstableApiUsage") + @EventHandler + public void onPluginMessage(PluginMessageEvent event) { + if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) { + final String currentChannel = event.getTag(); + final byte[] data = Arrays.copyOf(event.getData(), event.getData().length); + plugin.executeAsync(() -> { + ByteArrayDataInput in = ByteStreams.newDataInput(data); + + String subchannel = in.readUTF(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + String type; + + switch (subchannel) { + case "PlayerList" -> { + out.writeUTF("PlayerList"); + Set original = Collections.emptySet(); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + original = plugin.proxyDataManager().networkPlayers(); + } else { + out.writeUTF(type); + try { + original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); + } catch (IllegalArgumentException ignored) { + } + } + Set players = new HashSet<>(); + for (UUID uuid : original) + players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false)); + out.writeUTF(Joiner.on(',').join(players)); + } + case "PlayerCount" -> { + out.writeUTF("PlayerCount"); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); + } else { + out.writeUTF(type); + try { + out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); + } catch (IllegalArgumentException e) { + out.writeInt(0); + } + } + } + case "LastOnline" -> { + String user = in.readUTF(); + out.writeUTF("LastOnline"); + out.writeUTF(user); + out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); + } + case "ServerPlayers" -> { + String type1 = in.readUTF(); + out.writeUTF("ServerPlayers"); + Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + boolean includesUsers; + switch (type1) { + case "COUNT" -> includesUsers = false; + case "PLAYERS" -> includesUsers = true; + default -> { + // TODO: Should I raise an error? + return; + } + } + out.writeUTF(type1); + if (includesUsers) { + Multimap human = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); + } + serializeMultimap(human, true, out); + } else { + serializeMultiset(multimap.keys(), out); + } + } + case "Proxy" -> { + out.writeUTF("Proxy"); + out.writeUTF(plugin.configuration().getProxyId()); + } + case "PlayerProxy" -> { + String username = in.readUTF(); + out.writeUTF("PlayerProxy"); + out.writeUTF(username); + out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); + } + default -> { + return; + } + } + + ((Server) event.getSender()).sendData(currentChannel, out.toByteArray()); + }); + } + } + + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java index e5a9ea3..e6a7264 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java @@ -13,8 +13,8 @@ package com.imaginarycode.minecraft.redisbungee.commands; import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -import com.imaginarycode.minecraft.redisbungee.RedisBungee; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.RedisBungee; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.BaseComponent; @@ -286,9 +286,10 @@ public class RedisBungeeCommands { public static class ServerIds extends Command { private final RedisBungee plugin; + public ServerIds(RedisBungee plugin) { super("serverids", "redisbungee.command.serverids"); - this.plugin =plugin; + this.plugin = plugin; } @Override @@ -313,8 +314,8 @@ public class RedisBungeeCommands { plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { @Override public void run() { - String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId(); - if (!plugin.getProxiesIds().contains(proxy)) { + String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); + if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create()); return; } diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 4c2bbb4..2415121 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -39,7 +39,7 @@ tasks { "https://jd.papermc.io/velocity/3.0.0/", // velocity api ) val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc") - options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) + options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) } runVelocity { velocityVersion("3.3.0-SNAPSHOT") @@ -61,6 +61,7 @@ tasks { relocate("com.squareup.okhttp", "com.imaginarycode.minecraft.redisbungee.internal.okhttp") relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio") relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json") + relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") } } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java new file mode 100644 index 0000000..3d7d6cf --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.proxy.ProxyPingEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.ServerPing; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap; +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset; + +public class RedisBungeeListener { + + private final RedisBungeePlugin plugin; + + public RedisBungeeListener(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last + public void onPing(ProxyPingEvent event) { + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) { + return; + } + ServerPing.Builder ping = event.getPing().asBuilder(); + ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers()); + event.setPing(ping.build()); + } + + @Subscribe + public void onPluginMessage(PluginMessageEvent event) { + if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { + return; + } + + event.setResult(PluginMessageEvent.ForwardResult.handled()); + + plugin.executeAsync(() -> { + ByteArrayDataInput in = event.dataAsDataStream(); + + String subchannel = in.readUTF(); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + String type; + + switch (subchannel) { + case "PlayerList": + out.writeUTF("PlayerList"); + Set original = Collections.emptySet(); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + original = plugin.proxyDataManager().networkPlayers(); + } else { + out.writeUTF(type); + try { + original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); + } catch (IllegalArgumentException ignored) { + } + } + Set players = original.stream() + .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) + .collect(Collectors.toSet()); + out.writeUTF(Joiner.on(',').join(players)); + break; + case "PlayerCount": + out.writeUTF("PlayerCount"); + type = in.readUTF(); + if (type.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(plugin.proxyDataManager().totalNetworkPlayers()); + } else { + out.writeUTF(type); + try { + out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); + } catch (IllegalArgumentException e) { + out.writeInt(0); + } + } + break; + case "LastOnline": + String user = in.readUTF(); + out.writeUTF("LastOnline"); + out.writeUTF(user); + out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); + break; + case "ServerPlayers": + String type1 = in.readUTF(); + out.writeUTF("ServerPlayers"); + Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + + boolean includesUsers; + + switch (type1) { + case "COUNT" -> includesUsers = false; + case "PLAYERS" -> includesUsers = true; + default -> { + // TODO: Should I raise an error? + return; + } + } + + out.writeUTF(type1); + + if (includesUsers) { + Multimap human = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); + } + serializeMultimap(human, true, out); + } else { + serializeMultiset(multimap.keys(), out); + } + break; + case "Proxy": + out.writeUTF("Proxy"); + out.writeUTF(plugin.configuration().getProxyId()); + break; + case "PlayerProxy": + String username = in.readUTF(); + out.writeUTF("PlayerProxy"); + out.writeUTF(username); + out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); + break; + default: + return; + } + + ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); + }); + + } + + +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java deleted file mode 100644 index f73bf88..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityListener.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.google.common.base.Joiner; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.io.ByteArrayDataInput; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; -import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener; -import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask; -import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils; -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.velocitypowered.api.event.Continuation; -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.ResultedEvent; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.LoginEvent; -import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; -import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult; -import com.velocitypowered.api.event.player.ServerConnectedEvent; -import com.velocitypowered.api.event.proxy.ProxyPingEvent; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import com.velocitypowered.api.proxy.server.ServerPing; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import redis.clients.jedis.UnifiedJedis; - -import java.net.InetAddress; -import java.util.*; -import java.util.stream.Collectors; - -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap; -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset; - -public class RedisBungeeVelocityListener extends AbstractRedisBungeeListener { - // Some messages are using legacy characters - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); - - public RedisBungeeVelocityListener(RedisBungeePlugin plugin, List exemptAddresses) { - super(plugin, exemptAddresses); - } - - @Subscribe(order = PostOrder.LAST) - public void onLogin(LoginEvent event, Continuation continuation) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - try { - if (!event.getResult().isAllowed()) { - return null; - } - if (plugin.getConfiguration().restoreOldKickBehavior()) { - - for (String s : plugin.getProxiesIds()) { - if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) { - event.setResult(ResultedEvent.ComponentResult.denied(serializer.deserialize(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))); - return null; - } - } - - } else if (api.isPlayerOnline(event.getPlayer().getUniqueId())) { - PlayerUtils.setKickedOtherLocation(event.getPlayer().getUniqueId().toString(), unifiedJedis); - api.kickPlayer(event.getPlayer().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); - } - return null; - } finally { - continuation.resume(); - } - } - - }); - } - - @Override - @Subscribe - public void onPostLogin(PostLoginEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), unifiedJedis); - VelocityPlayerUtils.createVelocityPlayer(event.getPlayer(), unifiedJedis, true); - return null; - } - }); - } - - @Override - @Subscribe - public void onPlayerDisconnect(DisconnectEvent event) { - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true); - return null; - } - - }); - - } - - @Override - @Subscribe - public void onServerChange(ServerConnectedEvent event) { - final String currentServer = event.getServer().getServerInfo().getName(); - final String oldServer = event.getPreviousServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null); - plugin.executeAsync(new RedisTask(plugin) { - @Override - public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { - unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", currentServer); - PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer); - return null; - } - }); - } - - @Override - @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last - public void onPing(ProxyPingEvent event) { - if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) { - return; - } - ServerPing.Builder ping = event.getPing().asBuilder(); - ping.onlinePlayers(plugin.getCount()); - event.setPing(ping.build()); - } - - @Override - @Subscribe - public void onPluginMessage(PluginMessageEvent event) { - if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) { - return; - } - - event.setResult(ForwardResult.handled()); - - plugin.executeAsync(() -> { - ByteArrayDataInput in = event.dataAsDataStream(); - - String subchannel = in.readUTF(); - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - String type; - - switch (subchannel) { - case "PlayerList": - out.writeUTF("PlayerList"); - Set original = Collections.emptySet(); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - original = plugin.getPlayers(); - } else { - out.writeUTF(type); - try { - original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type); - } catch (IllegalArgumentException ignored) { - } - } - Set players = original.stream() - .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) - .collect(Collectors.toSet()); - out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": - out.writeUTF("PlayerCount"); - type = in.readUTF(); - if (type.equals("ALL")) { - out.writeUTF("ALL"); - out.writeInt(plugin.getCount()); - } else { - out.writeUTF(type); - try { - out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size()); - } catch (IllegalArgumentException e) { - out.writeInt(0); - } - } - break; - case "LastOnline": - String user = in.readUTF(); - out.writeUTF("LastOnline"); - out.writeUTF(user); - out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": - String type1 = in.readUTF(); - out.writeUTF("ServerPlayers"); - Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - - boolean includesUsers; - - switch (type1) { - case "COUNT": - includesUsers = false; - break; - case "PLAYERS": - includesUsers = true; - break; - default: - // TODO: Should I raise an error? - return; - } - - out.writeUTF(type1); - - if (includesUsers) { - Multimap human = HashMultimap.create(); - for (Map.Entry entry : multimap.entries()) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - serializeMultimap(human, true, out); - } else { - serializeMultiset(multimap.keys(), out); - } - break; - case "Proxy": - out.writeUTF("Proxy"); - out.writeUTF(plugin.getConfiguration().getProxyId()); - break; - case "PlayerProxy": - String username = in.readUTF(); - out.writeUTF("PlayerProxy"); - out.writeUTF(username); - out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: - return; - } - - ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); - }); - - } - - - @Override - @Subscribe - public void onPubSubMessage(PubSubMessageEvent event) { - if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) { - String message = event.getMessage(); - if (message.startsWith("/")) - message = message.substring(1); - plugin.logInfo("Invoking command via PubSub: /" + message); - ((RedisBungeeVelocityPlugin) plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message); - - } - } -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index fe0d6c6..db2c870 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -10,12 +10,11 @@ package com.imaginarycode.minecraft.redisbungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; import com.google.inject.Inject; -import com.imaginarycode.minecraft.redisbungee.api.*; +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; @@ -23,7 +22,7 @@ import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEv import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; -import com.imaginarycode.minecraft.redisbungee.api.tasks.*; +import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; @@ -46,17 +45,21 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.scheduler.ScheduledTask; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.slf4j.Logger; -import redis.clients.jedis.*; import redis.clients.jedis.exceptions.JedisConnectionException; - -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.nio.file.Path; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; @Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"}) public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, ConfigLoader { @@ -64,29 +67,25 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con private final Logger logger; private final Path dataFolder; private final AbstractRedisBungeeAPI api; - private final PubSubListener psl; private Summoner jedisSummoner; private RedisBungeeMode redisBungeeMode; private final UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; - private final VelocityDataManager dataManager; private final OkHttpClient httpClient; - private volatile List proxiesIds; - private final AtomicInteger globalPlayerCount = new AtomicInteger(); - private ScheduledTask integrityCheck; + + private final ProxyDataManager proxyDataManager; + + private final VelocityPlayerDataManager playerDataManager; + + private ScheduledTask cleanUpTask; private ScheduledTask heartbeatTask; - private static final Object SERVER_TO_PLAYERS_KEY = new Object(); public static final List IDENTIFIERS = List.of( MinecraftChannelIdentifier.create("legacy", "redisbungee"), new LegacyChannelIdentifier("RedisBungee"), // This is needed for clients before 1.13 new LegacyChannelIdentifier("legacy:redisbungee") ); - private final Cache> serverToPlayersCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.SECONDS) - .build(); - @Inject public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { @@ -102,11 +101,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } this.api = new RedisBungeeAPI(this); InitialUtils.checkRedisVersion(this); - // check if this proxy is recovering from a crash and start heart the beat. - InitialUtils.checkIfRecovering(this, getDataFolder()); + this.proxyDataManager = new ProxyDataManager(this) { + @Override + public Set getLocalOnlineUUIDs() { + HashSet players = new HashSet<>(); + server.getAllPlayers().forEach(player -> players.add(player.getUniqueId())); + return players; + } + + @Override + protected void handlePlatformCommandExecution(String command) { + server.getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), command); + } + }; + this.playerDataManager = new VelocityPlayerDataManager(this); uuidTranslator = new UUIDTranslator(this); - dataManager = new VelocityDataManager(this); - psl = new PubSubListener(this); this.httpClient = new OkHttpClient(); Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(6)); this.httpClient.setDispatcher(dispatcher); @@ -115,31 +124,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } - @Override - public RedisBungeeConfiguration getConfiguration() { - return this.configuration; - } - - @Override - public int getCount() { - return this.globalPlayerCount.get(); - } - - - @Override - public Set getLocalPlayersAsUuidStrings() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Player player : getProxy().getAllPlayers()) { - builder.add(player.getUniqueId().toString()); - } - return builder.build(); - } - - @Override - public AbstractDataManager getDataManager() { - return this.dataManager; - } - @Override public Summoner getSummoner() { return this.jedisSummoner; @@ -150,29 +134,21 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.api; } + @Override + public ProxyDataManager proxyDataManager() { + return this.proxyDataManager; + } + + @Override + public PlayerDataManager playerDataManager() { + return this.playerDataManager; + } + @Override public UUIDTranslator getUuidTranslator() { return this.uuidTranslator; } - @Override - public Multimap serverToPlayersCache() { - try { - return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getProxiesIds() { - return proxiesIds; - } - - @Override - public PubSubListener getPubSubListener() { - return this.psl; - } @Override public void executeAsync(Runnable runnable) { @@ -199,16 +175,36 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con this.getLogger().info(msg); } + @Override + public void logInfo(String format, Object... object) { + logger.info(format, object); + } + @Override public void logWarn(String msg) { this.getLogger().warn(msg); } + @Override + public void logWarn(String format, Object... object) { + logger.warn(format, object); + } + @Override public void logFatal(String msg) { this.getLogger().error(msg); } + @Override + public void logFatal(String format, Throwable throwable) { + logger.error(format, throwable); + } + + @Override + public RedisBungeeConfiguration configuration() { + return this.configuration; + } + @Override public Player getPlayer(UUID uuid) { return this.getProxy().getPlayer(uuid).orElse(null); @@ -229,6 +225,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null); } + private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); + + @Override + public boolean handlePlatformKick(UUID uuid, String message) { + Player player = getPlayer(uuid); + if (player == null) return false; + player.disconnect(serializer.deserialize(message)); + return true; + } + @Override public String getPlayerServerName(Player player) { return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null); @@ -247,25 +253,16 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con @Override public void initialize() { logInfo("Initializing RedisBungee....."); - updateProxiesIds(); // start heartbeat task - heartbeatTask = getProxy().getScheduler().buildTask(this, new HeartbeatTask(this, this.globalPlayerCount)).repeat(HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT).schedule(); + // heartbeat and clean up + this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule(); + this.cleanUpTask = server.getScheduler().buildTask(this, this.proxyDataManager::correctionTask).repeat(Duration.ofSeconds(60)).schedule(); - getProxy().getEventManager().register(this, new RedisBungeeVelocityListener(this, configuration.getExemptAddresses())); - getProxy().getEventManager().register(this, dataManager); - getProxy().getScheduler().buildTask(this, psl).schedule(); - - IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) { - @Override - public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) { - Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null); - if (playerProxied == null) - return; // We'll deal with it later. - VelocityPlayerUtils.createVelocityPlayer(playerProxied, unifiedJedis, false); - } - }; - integrityCheck = getProxy().getScheduler().buildTask(this, integrityCheckTask::execute).repeat(30, TimeUnit.SECONDS).schedule(); + server.getEventManager().register(this, this.playerDataManager); + server.getEventManager().register(this, new RedisBungeeListener(this)); + // subscribe + server.getScheduler().buildTask(this, this.proxyDataManager).schedule(); // register plugin messages IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register); @@ -292,19 +289,18 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con public void stop() { logInfo("Turning off redis connections....."); // Poison the PubSub listener - if (psl != null) { - psl.poison(); - } - if (integrityCheck != null) { - integrityCheck.cancel(); + if (cleanUpTask != null) { + cleanUpTask.cancel(); } if (heartbeatTask != null) { heartbeatTask.cancel(); } - ShutdownUtils.shutdownCleanup(this); + + try { + this.proxyDataManager.close(); this.jedisSummoner.close(); - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException(e); } @@ -331,10 +327,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.redisBungeeMode; } - @Override - public void updateProxiesIds() { - this.proxiesIds = this.getCurrentProxiesIds(false); - } @Subscribe(order = PostOrder.FIRST) public void onProxyInitializeEvent(ProxyInitializeEvent event) { diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java deleted file mode 100644 index 4ad9ef3..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityDataManager.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; -import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.PostLoginEvent; -import com.velocitypowered.api.proxy.Player; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - -import java.util.UUID; - - -public class VelocityDataManager extends AbstractDataManager { - - public VelocityDataManager(RedisBungeePlugin plugin) { - super(plugin); - } - - @Override - @Subscribe - public void onPostLogin(PostLoginEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @Subscribe - public void onPlayerDisconnect(DisconnectEvent event) { - invalidate(event.getPlayer().getUniqueId()); - } - - @Override - @Subscribe - public void onPubSubMessage(PubSubMessageEvent event) { - handlePubSubMessage(event.getChannel(), event.getMessage()); - } - - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); - @Override - public boolean handleKick(UUID target, String message) { - Player player = plugin.getPlayer(target); - if (player == null) { - return false; - } - player.disconnect(serializer.deserialize(message)); - return true; - } -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java new file mode 100644 index 0000000..a2b6735 --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; +import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class VelocityPlayerDataManager extends PlayerDataManager { + public VelocityPlayerDataManager(RedisBungeePlugin plugin) { + super(plugin); + } + + @Override + @Subscribe + public void onPlayerChangedServerNetworkEvent(PlayerChangedServerNetworkEvent event) { + handleNetworkPlayerServerChange(event); + } + + @Override + @Subscribe + public void onNetworkPlayerQuit(PlayerLeftNetworkEvent event) { + handleNetworkPlayerQuit(event); + } + + @Override + @Subscribe + public void onPubSubMessageEvent(PubSubMessageEvent event) { + handlePubSubMessageEvent(event); + } + + @Override + @Subscribe + public void onServerConnectedEvent(ServerConnectedEvent event) { + final String currentServer = event.getServer().getServerInfo().getName(); + final String oldServer; + if (event.getPreviousServer().isPresent()) { + oldServer = event.getPreviousServer().get().getServerInfo().getName(); + } else { + oldServer = null; + } + super.playerChangedServer(event.getPlayer().getUniqueId(), oldServer, currentServer); + } + + private static final LegacyComponentSerializer LEGACY_COMPONENT_SERIALIZER = LegacyComponentSerializer.builder().build(); + + @Subscribe + public void onLoginEvent(LoginEvent event, Continuation continuation) { + // check if online + if (getLastOnline(event.getPlayer().getUniqueId()) == 0) { + if (!plugin.configuration().restoreOldKickBehavior()) { + kickPlayer(event.getPlayer().getUniqueId(), plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION)); + // wait 3 seconds before releasing the event + plugin.executeAsyncAfter(continuation::resume, TimeUnit.SECONDS, 3); + } else { + event.setResult(ResultedEvent.ComponentResult.denied(LEGACY_COMPONENT_SERIALIZER.deserialize(Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))))); + continuation.resume(); + } + } else { + continuation.resume(); + } + } + + @Override + @Subscribe + public void onLoginEvent(PostLoginEvent event) { + addPlayer(event.getPlayer().getUniqueId(), event.getPlayer().getRemoteAddress().getAddress()); + } + + @Override + @Subscribe + public void onDisconnectEvent(DisconnectEvent event) { + if (event.getLoginStatus() == DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN || event.getLoginStatus() == DisconnectEvent.LoginStatus.PRE_SERVER_JOIN) { + removePlayer(event.getPlayer().getUniqueId()); + } + } +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java deleted file mode 100644 index 2f43fc8..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee; - -import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ServerConnection; -import redis.clients.jedis.UnifiedJedis; - -import java.util.Optional; - -public class VelocityPlayerUtils { - protected static void createVelocityPlayer(Player player, UnifiedJedis unifiedJedis, boolean fireEvent) { - Optional optionalServerConnection = player.getCurrentServer(); - String serverName = null; - if (optionalServerConnection.isPresent()) { - serverName = optionalServerConnection.get().getServerInfo().getName(); - } - PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, player.getRemoteAddress().getAddress(), fireEvent); - } - - -} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java index 3537981..bb9823e 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java @@ -10,12 +10,6 @@ package com.imaginarycode.minecraft.redisbungee.commands; -import java.net.InetAddress; -import java.text.SimpleDateFormat; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - import com.google.common.base.Joiner; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -25,11 +19,16 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; - import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; +import java.net.InetAddress; +import java.text.SimpleDateFormat; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + /** * This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen. @@ -324,8 +323,8 @@ public class RedisBungeeCommands { CommandSource sender = invocation.source(); String[] args = invocation.arguments(); plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId(); - if (!plugin.getProxiesIds().contains(proxy)) { + String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); + if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); return; } diff --git a/gradle.properties b/gradle.properties index f1c10e0..016d2f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -group = com.imaginarycode.minecraft -version = 0.12.0-SNAPSHOT +group=com.imaginarycode.minecraft +version=0.12.0-SNAPSHOT From f96c5759a299da24163194a1790ce412f6c7274e Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 31 Aug 2023 13:00:19 +0400 Subject: [PATCH 03/64] config changes jedispool is now disabled by default, some minor config changes --- .../minecraft/redisbungee/api/config/ConfigLoader.java | 2 +- RedisBungee-API/src/main/resources/config.yml | 9 +++++---- RedisBungee-API/src/main/resources/messages.yml | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java index b92365e..428382c 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java @@ -115,7 +115,7 @@ public interface ConfigLoader { throw new RuntimeException("No redis server specified"); } JedisPool jedisPool = null; - if (node.getNode("enable-jedis-pool-compatibility").getBoolean(true)) { + if (node.getNode("enable-jedis-pool-compatibility").getBoolean(false)) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3)); config.setBlockWhenExhausted(true); diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 85ef226..93756ee 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -1,7 +1,7 @@ # RedisBungee configuration file. -# Get Redis from http://redis.io/ +# you need Redis so get Redis from http://redis.io/ or install it from your package manager -# The Redis server you use. +# The Redis server you will use. # these settings are ignored when cluster mode is enabled. redis-server: 127.0.0.1 redis-port: 6379 @@ -48,9 +48,10 @@ proxy-id: "test-1" # since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool. # which will break compatibility with old plugins that uses RedisBungee JedisPool # so to mitigate this issue, we will instruct RedisBungee to init an JedisPool for compatibility reasons. -# enabled by default +# disabled by default # ignored when cluster mode is enabled -enable-jedis-pool-compatibility: true +enable-jedis-pool-compatibility: false + # max connections for the compatibility pool compatibility-max-connections: 3 diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml index 737b52c..a1b1853 100644 --- a/RedisBungee-API/src/main/resources/messages.yml +++ b/RedisBungee-API/src/main/resources/messages.yml @@ -1,3 +1,2 @@ logged-in-other-location: "§cYou logged in from another location!" -already-logged-in: "§cYou are already logged in!" -error: "§cError has occurred" \ No newline at end of file +already-logged-in: "§cYou are already logged in!" \ No newline at end of file From 16576ab4c22e0695798b08468918c460735d87ad Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 31 Aug 2023 14:26:21 +0400 Subject: [PATCH 04/64] new config options: handle-motd, reconnect to last server --- .../redisbungee/api/config/ConfigLoader.java | 7 +++-- .../api/config/RedisBungeeConfiguration.java | 27 ++++++++++++++----- RedisBungee-API/src/main/resources/config.yml | 18 ++++++++++--- .../src/main/resources/messages.yml | 3 +++ .../redisbungee/BungeePlayerDataManager.java | 2 +- .../redisbungee/RedisBungeeListener.java | 12 +++------ .../redisbungee/RedisBungeeListener.java | 6 ++--- .../VelocityPlayerDataManager.java | 2 +- 8 files changed, 53 insertions(+), 24 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java index 428382c..e9b49c4 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java @@ -51,7 +51,7 @@ public interface ConfigLoader { final boolean useSSL = node.getNode("useSSL").getBoolean(false); final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false); final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false); - final boolean restoreOldKickBehavior = node.getNode("disable-kick-when-online").getBoolean(false); + final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(false); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); String proxyId = node.getNode("proxy-id").getString("test-1"); @@ -86,7 +86,10 @@ public interface ConfigLoader { } else { plugin.logInfo("Loaded proxy id " + proxyId); } - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), restoreOldKickBehavior); + boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean(); + boolean handleMotd = node.getNode("handle-motd").getBoolean(true); + + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), kickWhenOnline, reconnectToLastServer, handleMotd); Summoner summoner; RedisBungeeMode redisBungeeMode; if (node.getNode("cluster-mode-enabled").getBoolean(false)) { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index e10c9d0..aeedb08 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -25,15 +25,18 @@ public class RedisBungeeConfiguration { } private final ImmutableMap messages; - public static final int CONFIG_VERSION = 1; + public static final int CONFIG_VERSION = 2; private final String proxyId; private final List exemptAddresses; private final boolean registerLegacyCommands; private final boolean overrideBungeeCommands; + private final boolean kickWhenOnline; - private final boolean restoreOldKickBehavior; + private final boolean handleReconnectToLastServer; + private final boolean handleMotd; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap messages, boolean restoreOldKickBehavior) { + + public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap messages, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { this.proxyId = proxyId; this.messages = messages; ImmutableList.Builder addressBuilder = ImmutableList.builder(); @@ -43,7 +46,9 @@ public class RedisBungeeConfiguration { this.exemptAddresses = addressBuilder.build(); this.registerLegacyCommands = registerLegacyCommands; this.overrideBungeeCommands = overrideBungeeCommands; - this.restoreOldKickBehavior = restoreOldKickBehavior; + this.kickWhenOnline = kickWhenOnline; + this.handleReconnectToLastServer = handleReconnectToLastServer; + this.handleMotd = handleMotd; } public String getProxyId() { @@ -66,7 +71,17 @@ public class RedisBungeeConfiguration { return messages; } - public boolean restoreOldKickBehavior() { - return restoreOldKickBehavior; + public boolean kickWhenOnline() { + return kickWhenOnline; } + + public boolean handleMotd() { + return this.handleMotd; + } + + public boolean handleReconnectToLastServer() { + return this.handleReconnectToLastServer; + } + + } diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 93756ee..d8453a7 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -74,8 +74,20 @@ override-bungee-commands: false # restart scripts. exempt-ip-addresses: [] -# restore old login when online behavior before 0.9.0 update -disable-kick-when-online: false +# restore old login behavior before 0.9.0 update +# enabled by default +# when true: when player login and there is old player with same uuid it will get disconnected as result and new player will login +# when false: when a player login but login will fail because old player is still connected. +kick-when-online: true + +# enabled by default +# this option tells redis-bungee handle motd and set online count, when motd is requested +# you can disable this when you want to handle motd yourself, use RedisBungee api to get total players when needed :) +handle-motd: true + +# disabled by default +# Redis-bungee will attempt to connect player to last server that was stored. +reconnect-to-last-server: false # Config version DO NOT CHANGE!!!! -config-version: 1 +config-version: 2 diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml index a1b1853..200cb4a 100644 --- a/RedisBungee-API/src/main/resources/messages.yml +++ b/RedisBungee-API/src/main/resources/messages.yml @@ -1,2 +1,5 @@ +# this config file is for messages for players and command messages +# Note this uses legacy formating which § for color codes +# this might get replaced soon with mini message for both, velocity and bungeecord logged-in-other-location: "§cYou logged in from another location!" already-logged-in: "§cYou are already logged in!" \ No newline at end of file diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java index e312b7c..7ccfa02 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -67,7 +67,7 @@ public class BungeePlayerDataManager extends PlayerDataManager event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java index 6d7b6a7..ce298ff 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -28,8 +28,7 @@ import net.md_5.bungee.event.EventHandler; import java.util.*; -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultimap; -import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.serializeMultiset; +import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.MultiMapSerialization.*; public class RedisBungeeListener implements Listener { @@ -41,14 +40,11 @@ public class RedisBungeeListener implements Listener { @EventHandler public void onPing(ProxyPingEvent event) { - if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) { - return; - } + if (!plugin.configuration().handleMotd()) return; + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getAddress().getAddress())) return; ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection()); - if (forced != null && event.getConnection().getListener().isPingPassthrough()) { - return; - } + if (forced != null && event.getConnection().getListener().isPingPassthrough()) return; event.getResponse().getPlayers().setOnline(plugin.proxyDataManager().totalNetworkPlayers()); } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java index 3d7d6cf..35a9807 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -41,9 +41,9 @@ public class RedisBungeeListener { @Subscribe(order = PostOrder.LAST) // some plugins changes it online players so we need to be executed as last public void onPing(ProxyPingEvent event) { - if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) { - return; - } + if (!plugin.configuration().handleMotd()) return; + if (plugin.configuration().getExemptAddresses().contains(event.getConnection().getRemoteAddress().getAddress())) return; + ServerPing.Builder ping = event.getPing().asBuilder(); ping.onlinePlayers(plugin.proxyDataManager().totalNetworkPlayers()); event.setPing(ping.build()); diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java index a2b6735..d374a04 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -71,7 +71,7 @@ public class VelocityPlayerDataManager extends PlayerDataManager Date: Thu, 31 Aug 2023 14:30:02 +0400 Subject: [PATCH 05/64] remove autoclosable interface from proxy data manager --- .../minecraft/redisbungee/api/ProxyDataManager.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java index 73a7c14..34b7be0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.google.common.base.Preconditions.checkArgument; -public abstract class ProxyDataManager implements Runnable, AutoCloseable { +public abstract class ProxyDataManager implements Runnable { private static final String STREAM_ID = "redisbungee-stream"; private static final int MAX_ENTRIES = 10000; @@ -360,8 +360,7 @@ public abstract class ProxyDataManager implements Runnable, AutoCloseable { } } - @Override - public void close() throws Exception { + public void close() { closed.set(true); this.publishDeath(); this.heartbeats.clear(); From 6d40c1902ae5c6564909af587d79e765e0b886a5 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 31 Aug 2023 14:32:51 +0400 Subject: [PATCH 06/64] change false to true as default for kick-when-online option --- .../minecraft/redisbungee/api/config/ConfigLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java index e9b49c4..2662dfe 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java @@ -51,7 +51,7 @@ public interface ConfigLoader { final boolean useSSL = node.getNode("useSSL").getBoolean(false); final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false); final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false); - final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(false); + final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); String proxyId = node.getNode("proxy-id").getString("test-1"); From 3c4f0d8c933668bc914cdbb303fee9a9067c5359 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 31 Aug 2023 14:35:21 +0400 Subject: [PATCH 07/64] oops forgotten to change something for kick when online --- .../minecraft/redisbungee/BungeePlayerDataManager.java | 2 +- .../minecraft/redisbungee/VelocityPlayerDataManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java index 7ccfa02..360d642 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -67,7 +67,7 @@ public class BungeePlayerDataManager extends PlayerDataManager event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java index d374a04..ebd2511 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -71,7 +71,7 @@ public class VelocityPlayerDataManager extends PlayerDataManager Date: Sun, 10 Sep 2023 18:15:01 +0400 Subject: [PATCH 08/64] welcome adventure api MiniMessage is now used for Messages.yml fix bug when from is null when server change --- RedisBungee-API/build.gradle.kts | 7 +++++ .../redisbungee/AbstractRedisBungeeAPI.java | 31 ++++++++++++++++++- .../redisbungee/api/PlayerDataManager.java | 13 +++++--- .../redisbungee/api/RedisBungeePlugin.java | 3 +- .../api/config/RedisBungeeConfiguration.java | 4 +-- .../src/main/resources/messages.yml | 8 ++--- RedisBungee-Bungee/build.gradle.kts | 4 +++ .../redisbungee/BungeePlayerDataManager.java | 9 ++++-- .../minecraft/redisbungee/RedisBungee.java | 6 ++-- RedisBungee-Velocity/build.gradle.kts | 8 +++++ .../RedisBungeeVelocityPlugin.java | 6 ++-- .../VelocityPlayerDataManager.java | 8 ++--- 12 files changed, 84 insertions(+), 23 deletions(-) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 933d566..98eb1ff 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -23,6 +23,13 @@ dependencies { api("com.squareup.okhttp:okhttp:2.7.5") api("org.spongepowered:configurate-yaml:$configurateVersion") api("com.github.ben-manes.caffeine:caffeine:3.1.8") + + api("net.kyori:adventure-api:4.14.0") + api("net.kyori:adventure-text-serializer-gson:4.14.0") + api("net.kyori:adventure-text-serializer-legacy:4.14.0") + api("net.kyori:adventure-text-serializer-plain:4.14.0") + api("net.kyori:adventure-text-minimessage:4.14.0") + // tests testImplementation("junit:junit:4.13.2") } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java index 70e3e7e..1514918 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java @@ -17,6 +17,7 @@ import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import redis.clients.jedis.Jedis; @@ -347,8 +348,9 @@ public abstract class AbstractRedisBungeeAPI { * calls {@link #getUuidFromName(String)} to get uuid * * @param playerName player name - * @param message kick message that player will see on kick + * @param message kick message that player will see on kick * @since 0.8.0 + * @deprecated */ public void kickPlayer(String playerName, String message) { @@ -361,11 +363,38 @@ public abstract class AbstractRedisBungeeAPI { * @param playerUUID player name * @param message kick message that player will see on kick * @since 0.8.0 + * @deprecated */ + @Deprecated public void kickPlayer(UUID playerUUID, String message) { + kickPlayer(playerUUID, Component.text(message)); + } + + /** + * Kicks a player from the network + * calls {@link #getUuidFromName(String)} to get uuid + * + * @param playerName player name + * @param message kick message that player will see on kick + * @since 0.12.0 + */ + + public void kickPlayer(String playerName, Component message) { + kickPlayer(getUuidFromName(playerName), message); + } + + /** + * Kicks a player from the network + * + * @param playerUUID player name + * @param message kick message that player will see on kick + * @since 0.12.0 + */ + public void kickPlayer(UUID playerUUID, Component message) { this.plugin.playerDataManager().kickPlayer(playerUUID, message); } + /** * This gives you instance of Jedis * diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java index c6ccbb6..7d544df 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -19,6 +19,8 @@ import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNe import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent; import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; import org.json.JSONObject; import redis.clients.jedis.ClusterPipeline; import redis.clients.jedis.Pipeline; @@ -85,7 +87,7 @@ public abstract class PlayerDataManager extends EventsPlatform { public String getPlayerName(UUID player); - boolean handlePlatformKick(UUID uuid, String message); + boolean handlePlatformKick(UUID uuid, Component message); public String getPlayerServerName(P player); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index aeedb08..ec61bae 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -67,8 +67,8 @@ public class RedisBungeeConfiguration { return overrideBungeeCommands; } - public ImmutableMap getMessages() { - return messages; + public String getMessage(MessageType messageType) { + return this.messages.get(messageType); } public boolean kickWhenOnline() { diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml index 200cb4a..a92689b 100644 --- a/RedisBungee-API/src/main/resources/messages.yml +++ b/RedisBungee-API/src/main/resources/messages.yml @@ -1,5 +1,5 @@ # this config file is for messages for players and command messages -# Note this uses legacy formating which § for color codes -# this might get replaced soon with mini message for both, velocity and bungeecord -logged-in-other-location: "§cYou logged in from another location!" -already-logged-in: "§cYou are already logged in!" \ No newline at end of file +# Note this uses MiniMessage format https://docs.advntr.dev/minimessage/format.html + +logged-in-other-location: "You logged in from another location!" +already-logged-in: "You are already logged in!" \ No newline at end of file diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 07e1cdb..5ddd158 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -16,7 +16,10 @@ dependencies { compileOnly("net.md-5:bungeecord-api:$bungeecordApiVersion") { exclude("com.google.guava", "guava") exclude("com.google.code.gson", "gson") + exclude("net.kyori","adventure-api") } + implementation("net.kyori:adventure-platform-bungeecord:4.3.0") + implementation("net.kyori:adventure-text-serializer-gson:4.14.0") } description = "RedisBungee Bungeecord implementation" @@ -35,6 +38,7 @@ tasks { options.isDocFilesSubDirs = true options.links( "https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/", // bungeecord api + "https://jd.advntr.dev/api/4.14.0" ) val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc") options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java index 360d642..f21d703 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -16,6 +16,8 @@ import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfigurati import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -32,6 +34,9 @@ import java.util.concurrent.TimeUnit; public class BungeePlayerDataManager extends PlayerDataManager implements Listener { + + private final BungeeComponentSerializer BUNGEECORD_SERIALIZER = BungeeComponentSerializer.get(); + public BungeePlayerDataManager(RedisBungeePlugin plugin) { super(plugin); } @@ -68,12 +73,12 @@ public class BungeePlayerDataManager extends PlayerDataManager event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); } else { event.setCancelled(true); - event.setCancelReason(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', Objects.requireNonNull(plugin.configuration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))))); + event.setCancelReason(BungeeComponentSerializer.get().serialize(MiniMessage.miniMessage().deserialize( plugin.configuration().getMessage(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))); event.completeIntent((Plugin) plugin); } } else { diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 81988c4..74f5d9b 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -32,6 +32,8 @@ import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import com.squareup.okhttp.Dispatcher; import com.squareup.okhttp.OkHttpClient; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -156,11 +158,11 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin, Con return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null); } - private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); @Override - public boolean handlePlatformKick(UUID uuid, String message) { + public boolean handlePlatformKick(UUID uuid, Component message) { Player player = getPlayer(uuid); if (player == null) return false; - player.disconnect(serializer.deserialize(message)); + player.disconnect(message); return true; } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java index ebd2511..109cdff 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -24,6 +24,7 @@ import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.Objects; @@ -65,18 +66,17 @@ public class VelocityPlayerDataManager extends PlayerDataManager Date: Sun, 10 Sep 2023 19:18:43 +0400 Subject: [PATCH 09/64] remove weird javadocs --- .../com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java index 712ebba..2345f25 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java @@ -23,7 +23,7 @@ import java.util.UUID; * or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}. * * @author tuxed - * @since 0.2.3 | updated 0.8.0 + * @since 0.2.3 */ public class RedisBungeeAPI extends AbstractRedisBungeeAPI { From f6e1ca65bf18c17172074daeca9310752754c845 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 10 Sep 2023 19:29:11 +0400 Subject: [PATCH 10/64] make varable for depeneds, remove javadocs for adventure from implementations javadocs --- RedisBungee-API/build.gradle.kts | 25 +++++++++++++++---------- RedisBungee-Bungee/build.gradle.kts | 1 - RedisBungee-Velocity/build.gradle.kts | 1 - 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 98eb1ff..8a8e48f 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -15,26 +15,28 @@ repositories { val jedisVersion = "5.1.2" val configurateVersion = "3.7.3" val guavaVersion = "31.1-jre" - +val okHttpVersion = "2.7.5" +val caffeineVersion = "3.1.8" +val adventureVersion = "4.14.0" dependencies { api("com.google.guava:guava:$guavaVersion") api("redis.clients:jedis:$jedisVersion") - api("com.squareup.okhttp:okhttp:2.7.5") + api("com.squareup.okhttp:okhttp:$okHttpVersion") api("org.spongepowered:configurate-yaml:$configurateVersion") - api("com.github.ben-manes.caffeine:caffeine:3.1.8") + api("com.github.ben-manes.caffeine:caffeine:$caffeineVersion") - api("net.kyori:adventure-api:4.14.0") - api("net.kyori:adventure-text-serializer-gson:4.14.0") - api("net.kyori:adventure-text-serializer-legacy:4.14.0") - api("net.kyori:adventure-text-serializer-plain:4.14.0") - api("net.kyori:adventure-text-minimessage:4.14.0") + api("net.kyori:adventure-api:$adventureVersion") + api("net.kyori:adventure-text-serializer-gson:$adventureVersion") + api("net.kyori:adventure-text-serializer-legacy:$adventureVersion") + api("net.kyori:adventure-text-serializer-plain:$adventureVersion") + api("net.kyori:adventure-text-minimessage:$adventureVersion") // tests testImplementation("junit:junit:4.13.2") } -description = "RedisBungee interafaces" +description = "RedisBungee interfaces" blossom { replaceToken("@version@", "$version") @@ -64,7 +66,10 @@ tasks { options.links( "https://configurate.aoeu.xyz/$configurateVersion/apidocs/", // configurate "https://javadoc.io/doc/redis.clients/jedis/$jedisVersion/", // jedis - "https://guava.dev/releases/$guavaVersion/api/docs/" // guava + "https://guava.dev/releases/$guavaVersion/api/docs/", // guava + "https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine", + "https://jd.advntr.dev/api/$adventureVersion" + ) } diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 5ddd158..08988ab 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -38,7 +38,6 @@ tasks { options.isDocFilesSubDirs = true options.links( "https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/", // bungeecord api - "https://jd.advntr.dev/api/4.14.0" ) val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc") options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 36994d2..d6ffe4d 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -44,7 +44,6 @@ tasks { options.isDocFilesSubDirs = true options.links( "https://jd.papermc.io/velocity/3.0.0/", // velocity api - "https://jd.advntr.dev/api/4.14.0" ) val apiDocs = File(rootProject.projectDir, "RedisBungee-API/build/docs/javadoc") options.linksOffline("https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc", apiDocs.path) From e897a60976ea05119ff00bdbfcde7fd62d0b949a Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 10 Sep 2023 20:39:45 +0400 Subject: [PATCH 11/64] remove all old messages code, new lang file --- .../redisbungee/AbstractRedisBungeeAPI.java | 2 +- .../redisbungee/api/config/ConfigLoader.java | 26 +------------- .../api/config/RedisBungeeConfiguration.java | 14 ++------ RedisBungee-API/src/main/resources/lang.yml | 34 +++++++++++++++++++ .../src/main/resources/messages.yml | 5 --- .../redisbungee/BungeePlayerDataManager.java | 5 +-- .../VelocityPlayerDataManager.java | 7 ++-- 7 files changed, 45 insertions(+), 48 deletions(-) create mode 100644 RedisBungee-API/src/main/resources/lang.yml delete mode 100644 RedisBungee-API/src/main/resources/messages.yml diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java index 1514918..690fe5b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/AbstractRedisBungeeAPI.java @@ -352,7 +352,7 @@ public abstract class AbstractRedisBungeeAPI { * @since 0.8.0 * @deprecated */ - + @Deprecated public void kickPlayer(String playerName, String message) { kickPlayer(getUuidFromName(playerName), message); } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java index 2662dfe..5d9391b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java @@ -89,7 +89,7 @@ public interface ConfigLoader { boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean(); boolean handleMotd = node.getNode("handle-motd").getBoolean(true); - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), kickWhenOnline, reconnectToLastServer, handleMotd); + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, kickWhenOnline, reconnectToLastServer, handleMotd); Summoner summoner; RedisBungeeMode redisBungeeMode; if (node.getNode("cluster-mode-enabled").getBoolean(false)) { @@ -137,30 +137,6 @@ public interface ConfigLoader { void onConfigLoad(RedisBungeeConfiguration configuration, Summoner summoner, RedisBungeeMode mode); - default ImmutableMap getMessagesFromPath(Path path) throws IOException { - final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(path).build(); - ConfigurationNode node = yamlConfigurationFileLoader.load(); - HashMap messages = new HashMap<>(); - messages.put(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION, node.getNode("logged-in-other-location").getString("§cLogged in from another location.")); - messages.put(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN, node.getNode("already-logged-in").getString("§cYou are already logged in!")); - return ImmutableMap.copyOf(messages); - } - - default Path createMessagesFile(Path dataFolder) throws IOException { - if (Files.notExists(dataFolder)) { - Files.createDirectory(dataFolder); - } - Path file = dataFolder.resolve("messages.yml"); - if (Files.notExists(file)) { - try (InputStream in = getClass().getClassLoader().getResourceAsStream("messages.yml")) { - Files.createFile(file); - assert in != null; - Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); - } - } - return file; - } - default Path createConfigFile(Path dataFolder) throws IOException { if (Files.notExists(dataFolder)) { Files.createDirectory(dataFolder); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index ec61bae..5b991fa 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -13,18 +13,13 @@ package com.imaginarycode.minecraft.redisbungee.api.config; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.net.InetAddresses; +import net.kyori.adventure.text.Component; import java.net.InetAddress; import java.util.List; public class RedisBungeeConfiguration { - public enum MessageType { - LOGGED_IN_OTHER_LOCATION, - ALREADY_LOGGED_IN - } - - private final ImmutableMap messages; public static final int CONFIG_VERSION = 2; private final String proxyId; private final List exemptAddresses; @@ -36,9 +31,8 @@ public class RedisBungeeConfiguration { private final boolean handleMotd; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap messages, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { + public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { this.proxyId = proxyId; - this.messages = messages; ImmutableList.Builder addressBuilder = ImmutableList.builder(); for (String s : exemptAddresses) { addressBuilder.add(InetAddresses.forString(s)); @@ -67,10 +61,6 @@ public class RedisBungeeConfiguration { return overrideBungeeCommands; } - public String getMessage(MessageType messageType) { - return this.messages.get(messageType); - } - public boolean kickWhenOnline() { return kickWhenOnline; } diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml new file mode 100644 index 0000000..f1b6367 --- /dev/null +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -0,0 +1,34 @@ +# this config file is for messages / Languages +# Note 1: use MiniMessage format https://docs.advntr.dev/minimessage/format.html for colors etc... Legacy chat color is not supported. +# Mote 2: Language codes that are used is ISO 639-1, here is full list https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + + +# example: +# Format: +# message_id: +# <2 char language code>: "MiniMessage color and your text." +# lets assume we want to add arabic language. +# logged-in-other-location: +# en: "You logged in from another location!" +# ar: "لقد اتصلت من مكان اخر" + + +# Prefix if ever used. +redis-bungee-prefix: "[RedisBungee]" + +# en is English, Which is the default language used when a language for a message isn't defined. +# Warning: if en or set default language wasn't defined in the config for all messages, plugin will not load. + +# set the default language +default-language: en + +# send language based on client sent settings +# https://minecraft.fandom.com/wiki/Language +use-client-language: true + + +logged-in-other-location: + en: "You logged in from another location!" + +already-logged-in: + en: "You are already logged in!" \ No newline at end of file diff --git a/RedisBungee-API/src/main/resources/messages.yml b/RedisBungee-API/src/main/resources/messages.yml deleted file mode 100644 index a92689b..0000000 --- a/RedisBungee-API/src/main/resources/messages.yml +++ /dev/null @@ -1,5 +0,0 @@ -# this config file is for messages for players and command messages -# Note this uses MiniMessage format https://docs.advntr.dev/minimessage/format.html - -logged-in-other-location: "You logged in from another location!" -already-logged-in: "You are already logged in!" \ No newline at end of file diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java index f21d703..9a50019 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -16,6 +16,7 @@ import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfigurati import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ChatColor; @@ -73,12 +74,12 @@ public class BungeePlayerDataManager extends PlayerDataManager event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); } else { event.setCancelled(true); - event.setCancelReason(BungeeComponentSerializer.get().serialize(MiniMessage.miniMessage().deserialize( plugin.configuration().getMessage(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN)))); + event.setCancelReason(BUNGEECORD_SERIALIZER.serialize(Component.empty())); event.completeIntent((Plugin) plugin); } } else { diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java index 109cdff..b4e42fc 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -24,6 +24,7 @@ import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -68,15 +69,15 @@ public class VelocityPlayerDataManager extends PlayerDataManager Date: Sun, 10 Sep 2023 21:13:53 +0400 Subject: [PATCH 12/64] implement last server connect on join, closes #84 --- .../redisbungee/api/PlayerDataManager.java | 9 ++++ RedisBungee-API/src/main/resources/lang.yml | 25 ++++++---- .../redisbungee/RedisBungeeListener.java | 18 ++++++- .../redisbungee/RedisBungeeListener.java | 50 ++++++++++++------- 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java index 7d544df..2324d26 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -38,6 +38,7 @@ public abstract class PlayerDataManager plugin; private final LoadingCache serverCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getServerFromRedis); + private final LoadingCache lastServerCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastServerFromRedis); private final LoadingCache proxyCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getProxyFromRedis); private final LoadingCache ipCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getIpAddressFromRedis); private final LoadingCache lastOnlineCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build(this::getLastOnlineFromRedis); @@ -68,6 +69,7 @@ public abstract class PlayerDataManager: "MiniMessage color and your text." + # lets assume we want to add arabic language. -# logged-in-other-location: -# en: "You logged in from another location!" -# ar: "لقد اتصلت من مكان اخر" +# errors: +# logged-in-other-location: +# en: "You logged in from another location!" +# ar: "لقد اتصلت من مكان اخر" # Prefix if ever used. @@ -26,9 +25,15 @@ default-language: en # https://minecraft.fandom.com/wiki/Language use-client-language: true +messages: + logged-in-other-location: + en: "You logged in from another location!" + already-logged-in: + en: "You are already logged in!" + server-not-found: + en: "unable to connect you to the last server, because server %s was not found." + server-found: + en: "Connecting you to %s..." -logged-in-other-location: - en: "You logged in from another location!" -already-logged-in: - en: "You are already logged in!" \ No newline at end of file +# commands: diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java index ce298ff..faf447e 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -17,12 +17,15 @@ import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import net.kyori.adventure.text.Component; import net.md_5.bungee.api.AbstractReconnectHandler; +import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; @@ -146,5 +149,18 @@ public class RedisBungeeListener implements Listener { } } - + @EventHandler + public void onServerConnectEvent(ServerConnectEvent event) { + if (event.getReason() == ServerConnectEvent.Reason.JOIN_PROXY && plugin.configuration().handleReconnectToLastServer()) { + String lastServer = plugin.playerDataManager().getLastServerFor(event.getPlayer().getUniqueId()); + if (lastServer == null) return; + // sending connect message, todo: IMPLEMENT once lang system is finalized + ServerInfo serverInfo = ProxyServer.getInstance().getServerInfo(lastServer); + if (serverInfo == null) { + // sending failure message, todo: IMPLEMENT once lang system is finalized + return; + } + event.setTarget(serverInfo); + } + } } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java index 35a9807..0e1003f 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java @@ -20,10 +20,13 @@ import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; +import net.kyori.adventure.text.Component; import java.util.*; import java.util.stream.Collectors; @@ -65,7 +68,7 @@ public class RedisBungeeListener { String type; switch (subchannel) { - case "PlayerList": + case "PlayerList" -> { out.writeUTF("PlayerList"); Set original = Collections.emptySet(); type = in.readUTF(); @@ -83,8 +86,8 @@ public class RedisBungeeListener { .map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false)) .collect(Collectors.toSet()); out.writeUTF(Joiner.on(',').join(players)); - break; - case "PlayerCount": + } + case "PlayerCount" -> { out.writeUTF("PlayerCount"); type = in.readUTF(); if (type.equals("ALL")) { @@ -98,20 +101,18 @@ public class RedisBungeeListener { out.writeInt(0); } } - break; - case "LastOnline": + } + case "LastOnline" -> { String user = in.readUTF(); out.writeUTF("LastOnline"); out.writeUTF(user); out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true)))); - break; - case "ServerPlayers": + } + case "ServerPlayers" -> { String type1 = in.readUTF(); out.writeUTF("ServerPlayers"); Multimap multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - boolean includesUsers; - switch (type1) { case "COUNT" -> includesUsers = false; case "PLAYERS" -> includesUsers = true; @@ -120,9 +121,7 @@ public class RedisBungeeListener { return; } } - out.writeUTF(type1); - if (includesUsers) { Multimap human = HashMultimap.create(); for (Map.Entry entry : multimap.entries()) { @@ -132,19 +131,20 @@ public class RedisBungeeListener { } else { serializeMultiset(multimap.keys(), out); } - break; - case "Proxy": + } + case "Proxy" -> { out.writeUTF("Proxy"); out.writeUTF(plugin.configuration().getProxyId()); - break; - case "PlayerProxy": + } + case "PlayerProxy" -> { String username = in.readUTF(); out.writeUTF("PlayerProxy"); out.writeUTF(username); out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true)))); - break; - default: + } + default -> { return; + } } ((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray()); @@ -152,5 +152,21 @@ public class RedisBungeeListener { } + @Subscribe + public void onPlayerChooseInitialServerEvent(PlayerChooseInitialServerEvent event) { + if (plugin.configuration().handleReconnectToLastServer()) { + String lastServer = plugin.playerDataManager().getLastServerFor(event.getPlayer().getUniqueId()); + if (lastServer == null) return; + // sending connect message, todo: IMPLEMENT once lang system is finalized + Optional optionalRegisteredServer = ((RedisBungeeVelocityPlugin) plugin).getProxy().getServer(lastServer); + if (optionalRegisteredServer.isEmpty()) { + // sending failure message, todo: IMPLEMENT once lang system is finalized + return; + } + RegisteredServer server = optionalRegisteredServer.get(); + event.setInitialServer(server); + } + } + } From d1d848fa8cfdd9988708c61c93498af4c3190c62 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 12 Sep 2023 12:49:44 +0400 Subject: [PATCH 13/64] remove tests as its no longer used --- RedisBungee-API/build.gradle.kts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 8a8e48f..a12cfc6 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -32,8 +32,6 @@ dependencies { api("net.kyori:adventure-text-serializer-plain:$adventureVersion") api("net.kyori:adventure-text-minimessage:$adventureVersion") - // tests - testImplementation("junit:junit:4.13.2") } description = "RedisBungee interfaces" @@ -74,10 +72,6 @@ tasks { } - test { - useJUnitPlatform() - } - compileJava { options.encoding = Charsets.UTF_8.name() options.release.set(17) From 7183e809d0ff538219752f1216f4909c01e74da1 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 12 Sep 2023 12:57:40 +0400 Subject: [PATCH 14/64] bungeecord version --- RedisBungee-Bungee/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 08988ab..bcae98e 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -10,7 +10,7 @@ repositories { mavenCentral() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // bungeecord } -val bungeecordApiVersion = "1.19-R0.1-SNAPSHOT" +val bungeecordApiVersion = "1.20-R0.1-SNAPSHOT" dependencies { api(project(":RedisBungee-API")) compileOnly("net.md-5:bungeecord-api:$bungeecordApiVersion") { From dd38532501ec194d6794d21deca3d65bb499a4e7 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 12 Apr 2024 22:01:10 +0400 Subject: [PATCH 15/64] config log changes, introduction of new env REDISBUNGEE_PROXY_ID --- .../redisbungee/api/config/ConfigLoader.java | 16 ++++++++++++---- RedisBungee-API/src/main/resources/config.yml | 4 +++- RedisBungee-Bungee/build.gradle.kts | 1 + RedisBungee-Velocity/build.gradle.kts | 1 + 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java index 5d9391b..81c02c6 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java @@ -54,7 +54,8 @@ public interface ConfigLoader { final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); - String proxyId = node.getNode("proxy-id").getString("test-1"); + String proxyId = node.getNode("proxy-id").getString("proxy-1"); + final int maxConnections = node.getNode("max-redis-connections").getInt(10); List exemptAddresses; try { @@ -71,9 +72,11 @@ public interface ConfigLoader { if ((redisUsername.isEmpty() || redisUsername.equals("none"))) { redisUsername = null; } - - if (useSSL) { - plugin.logInfo("Using ssl"); + // env var + String proxyIdFromEnv = System.getenv("REDISBUNGEE_PROXY_ID"); + if (proxyIdFromEnv != null) { + plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv); + proxyId = proxyIdFromEnv; } // Configuration sanity checks. if (proxyId == null || proxyId.isEmpty()) { @@ -88,10 +91,15 @@ public interface ConfigLoader { } boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean(); boolean handleMotd = node.getNode("handle-motd").getBoolean(true); + plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); + plugin.logInfo("handle motd: {}", handleMotd); RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, kickWhenOnline, reconnectToLastServer, handleMotd); Summoner summoner; RedisBungeeMode redisBungeeMode; + if (useSSL) { + plugin.logInfo("Using ssl"); + } if (node.getNode("cluster-mode-enabled").getBoolean(false)) { plugin.logInfo("RedisBungee MODE: CLUSTER"); Set hostAndPortSet = new HashSet<>(); diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index d8453a7..8c5eb99 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -43,7 +43,9 @@ max-redis-connections: 10 useSSL: false # An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank. -proxy-id: "test-1" +# Tip: you can set proxy id using Environment variable REDISBUNGEE_PROXY_ID which will override this config option +# before launch +proxy-id: "proxy-1" # since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool. # which will break compatibility with old plugins that uses RedisBungee JedisPool diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index bcae98e..8a22c40 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -44,6 +44,7 @@ tasks { } runWaterfall { waterfallVersion("1.20") + environment.put("REDISBUNGEE_PROXY_ID", "bungeecord-1") } compileJava { options.encoding = Charsets.UTF_8.name() diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index d6ffe4d..7d1a260 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -50,6 +50,7 @@ tasks { } runVelocity { velocityVersion("3.3.0-SNAPSHOT") + environment.put("REDISBUNGEE_PROXY_ID", "velocity-1") } compileJava { options.encoding = Charsets.UTF_8.name() From 7ba54ebfe21996bc605766b9458cee4d379f8872 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 12 Sep 2023 16:04:27 +0400 Subject: [PATCH 16/64] include build date --- RedisBungee-API/build.gradle.kts | 2 ++ .../java/com/imaginarycode/minecraft/redisbungee/Constants.java | 1 + 2 files changed, 3 insertions(+) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index a12cfc6..dfd81cf 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -1,3 +1,4 @@ +import java.time.Instant import java.io.ByteArrayOutputStream plugins { @@ -48,6 +49,7 @@ blossom { commit = "$commitStdout".replace("\n", "") // for some reason it adds new line so remove it. commitStdout.close() replaceToken("@git_commit@", commit) + replaceToken("@build_date@", "${Instant.now().epochSecond}") } java { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/Constants.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/Constants.java index 4471b0f..23201b0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/Constants.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/Constants.java @@ -15,6 +15,7 @@ public class Constants { public final static String VERSION = "@version@"; public final static String GIT_COMMIT = "@git_commit@"; + public final static long BUILD_DATE = Long.parseLong("@build_date@"); public static String getGithubCommitLink() { return "https://github.com/ProxioDev/RedisBungee/commit/" + GIT_COMMIT; From b7433bc9a3aebb685cd4c26d4febb51e9a7fcbc4 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 12 Sep 2023 16:06:36 +0400 Subject: [PATCH 17/64] Remove unnecessary public in some methods in plugin interface --- .../redisbungee/api/RedisBungeePlugin.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java index 136291e..f94df72 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java @@ -68,22 +68,22 @@ public interface RedisBungeePlugin

extends EventsPlatform { boolean isOnlineMode(); - public P getPlayer(UUID uuid); + P getPlayer(UUID uuid); - public P getPlayer(String name); + P getPlayer(String name); - public UUID getPlayerUUID(String player); + UUID getPlayerUUID(String player); - public String getPlayerName(UUID player); + String getPlayerName(UUID player); boolean handlePlatformKick(UUID uuid, Component message); - public String getPlayerServerName(P player); + String getPlayerServerName(P player); - public boolean isPlayerOnAServer(P player); + boolean isPlayerOnAServer(P player); - public InetAddress getPlayerIp(P player); + InetAddress getPlayerIp(P player); void executeAsync(Runnable runnable); From 76c362cf66fa40575a979d8ab23d5fd453815df6 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 14 Sep 2023 10:34:41 +0400 Subject: [PATCH 18/64] log version / build date --- RedisBungee-API/src/main/resources/config.yml | 2 +- RedisBungee-API/src/main/resources/lang.yml | 17 ++++++++++++++++- .../minecraft/redisbungee/RedisBungee.java | 2 ++ .../redisbungee/RedisBungeeVelocityPlugin.java | 4 +++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 8c5eb99..81f93c5 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -49,7 +49,7 @@ proxy-id: "proxy-1" # since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool. # which will break compatibility with old plugins that uses RedisBungee JedisPool -# so to mitigate this issue, we will instruct RedisBungee to init an JedisPool for compatibility reasons. +# so to mitigate this issue, RedisBungee will create an JedisPool for compatibility reasons. # disabled by default # ignored when cluster mode is enabled enable-jedis-pool-compatibility: false diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index e5013f6..2d6e76e 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -25,6 +25,7 @@ default-language: en # https://minecraft.fandom.com/wiki/Language use-client-language: true +# messages messages: logged-in-other-location: en: "You logged in from another location!" @@ -36,4 +37,18 @@ messages: en: "Connecting you to %s..." -# commands: +# commands common messages: +commands-common: + player-not-found: + en: "Player not found." + player-not-specified: + en: "You must specify a player name." + command-not-specified: + en: "You must specify a command to be run." + +# commands +#commands: +# + + + diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 74f5d9b..7790b37 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -185,6 +185,8 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin, Con this.server = server; this.logger = logger; this.dataFolder = dataDirectory; + logInfo("Version: {}", Constants.VERSION); + logInfo("Build date: {}", Constants.BUILD_DATE); try { loadConfig(this, dataDirectory); } catch (IOException e) { @@ -252,7 +254,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con @Override public void initialize() { - logInfo("Initializing RedisBungee....."); + logInfo("Initializing RedisBungee.....");; // start heartbeat task // heartbeat and clean up this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule(); From 6bcba06f7aa0f66ed2759c59e50b84f67053d6a4 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 19 Sep 2023 15:04:32 +0400 Subject: [PATCH 19/64] ignore bungeecord commands override on velocity --- RedisBungee-API/src/main/resources/config.yml | 1 + .../minecraft/redisbungee/RedisBungeeVelocityPlugin.java | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 81f93c5..5d6c62c 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -70,6 +70,7 @@ register-legacy-commands: false # # Please note that with build 787+, most commands overridden by RedisBungee were moved to # modules, and these must be disabled or overridden yourself. +# ignored on velocity override-bungee-commands: false # A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index 2403bc5..874799b 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -254,7 +254,8 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con @Override public void initialize() { - logInfo("Initializing RedisBungee.....");; + logInfo("Initializing RedisBungee....."); + ; // start heartbeat task // heartbeat and clean up this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule(); @@ -272,9 +273,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con // register legacy commands if (configuration.doRegisterLegacyCommands()) { // Override Velocity commands - if (configuration.doOverrideBungeeCommands()) { - getProxy().getCommandManager().register("glist", new RedisBungeeCommands.GlistCommand(this), "redisbungee", "rglist"); - } + getProxy().getCommandManager().register("glist", new RedisBungeeCommands.GlistCommand(this), "redisbungee", "rglist"); getProxy().getCommandManager().register("sendtoall", new RedisBungeeCommands.SendToAll(this), "rsendtoall"); getProxy().getCommandManager().register("serverid", new RedisBungeeCommands.ServerId(this), "rserverid"); getProxy().getCommandManager().register("serverids", new RedisBungeeCommands.ServerIds(this)); From 20932d894bcdd055b42086e04142482073001e86 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 19 Sep 2023 15:08:14 +0400 Subject: [PATCH 20/64] addtional stuff for lang config file --- RedisBungee-API/src/main/resources/lang.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 2d6e76e..824f49a 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -23,6 +23,8 @@ default-language: en # send language based on client sent settings # https://minecraft.fandom.com/wiki/Language +# if you don't have languages configured for client it will +# default to language that has been set above use-client-language: true # messages @@ -47,8 +49,9 @@ commands-common: en: "You must specify a command to be run." # commands -#commands: -# +commands: + + From 5ea8932ac4753450d0ff391830a490f5ba7d92c8 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 28 Sep 2023 15:00:14 +0400 Subject: [PATCH 21/64] lang file changes --- RedisBungee-API/src/main/resources/lang.yml | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 824f49a..5154beb 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -1,6 +1,8 @@ # this config file is for messages / Languages # Note 1: use MiniMessage format https://docs.advntr.dev/minimessage/format.html for colors etc... Legacy chat color is not supported. -# Mote 2: Language codes that are used is ISO 639-1, here is full list https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +# note 2: Language codes used in minecraft from the minecraft wiki +# Locale Code -> In-game +# https://minecraft.wiki/w/Language # example: @@ -8,18 +10,18 @@ # lets assume we want to add arabic language. # errors: # logged-in-other-location: -# en: "You logged in from another location!" -# ar: "لقد اتصلت من مكان اخر" +# en_us: "You logged in from another location!" +# ar_sa: "لقد اتصلت من مكان اخر" # Prefix if ever used. redis-bungee-prefix: "[RedisBungee]" # en is English, Which is the default language used when a language for a message isn't defined. -# Warning: if en or set default language wasn't defined in the config for all messages, plugin will not load. +# Warning: IF THE set default language wasn't defined in the config for all messages, plugin will not load. # set the default language -default-language: en +default-language: en_us # send language based on client sent settings # https://minecraft.fandom.com/wiki/Language @@ -30,23 +32,23 @@ use-client-language: true # messages messages: logged-in-other-location: - en: "You logged in from another location!" + en_us: "You logged in from another location!" already-logged-in: - en: "You are already logged in!" + en_us: "You are already logged in!" server-not-found: - en: "unable to connect you to the last server, because server %s was not found." + en_us: "unable to connect you to the last server, because server %s was not found." server-found: - en: "Connecting you to %s..." + en_us: "Connecting you to %s..." # commands common messages: commands-common: player-not-found: - en: "Player not found." + en_US: "Player not found." player-not-specified: - en: "You must specify a player name." + en_us: "You must specify a player name." command-not-specified: - en: "You must specify a command to be run." + en_us: "You must specify a command to be run." # commands commands: From 97cdf31cfc82cf8626fbcb20e6c239998211c2f8 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 28 Sep 2023 17:23:19 +0400 Subject: [PATCH 22/64] provide better info from the wiki --- RedisBungee-API/src/main/resources/lang.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 5154beb..89bcc4c 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -2,11 +2,11 @@ # Note 1: use MiniMessage format https://docs.advntr.dev/minimessage/format.html for colors etc... Legacy chat color is not supported. # note 2: Language codes used in minecraft from the minecraft wiki # Locale Code -> In-game +# example: en_us for english and ar_sa for arabic +# all codes can be obtained from lik below # https://minecraft.wiki/w/Language - # example: - # lets assume we want to add arabic language. # errors: # logged-in-other-location: @@ -14,10 +14,10 @@ # ar_sa: "لقد اتصلت من مكان اخر" -# Prefix if ever used. +# RedisBungee Prefix if ever used. redis-bungee-prefix: "[RedisBungee]" -# en is English, Which is the default language used when a language for a message isn't defined. +# us_en is american English, Which is the default language used when a language for a message isn't defined. # Warning: IF THE set default language wasn't defined in the config for all messages, plugin will not load. # set the default language From 4f6529b295a270a4e5d9d875fb9e6f9d082318c3 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 28 Sep 2023 17:37:11 +0400 Subject: [PATCH 23/64] provide arabic translation --- RedisBungee-API/src/main/resources/lang.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 89bcc4c..4b755dc 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -33,22 +33,28 @@ use-client-language: true messages: logged-in-other-location: en_us: "You logged in from another location!" + ar_sa: "لقد اتصلت من مكان اخر" already-logged-in: en_us: "You are already logged in!" + ar_sa: "انت متصل بالفعل" server-not-found: en_us: "unable to connect you to the last server, because server %s was not found." + ar_sa: " فشل الاتصال بالخادم السابق لان الخادم غير موجود (%s) " server-found: en_us: "Connecting you to %s..." - + ar_sa: " جاري الاتصال بخادم %s" # commands common messages: commands-common: player-not-found: - en_US: "Player not found." + en_us: "Player not found." + ar_sa: "اللاعب غير موجود" player-not-specified: en_us: "You must specify a player name." + ar_sa: "أدخل اسم اللاعب مطلوب" command-not-specified: en_us: "You must specify a command to be run." + ar_sa: " ادخل الأمر المطلوب" # commands commands: From b76709c2910241553461c71bbe0dbd2a9b08c08a Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 28 Sep 2023 18:27:25 +0400 Subject: [PATCH 24/64] change %s to it placeholders --- RedisBungee-API/src/main/resources/lang.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 4b755dc..513d28b 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -38,11 +38,11 @@ messages: en_us: "You are already logged in!" ar_sa: "انت متصل بالفعل" server-not-found: - en_us: "unable to connect you to the last server, because server %s was not found." - ar_sa: " فشل الاتصال بالخادم السابق لان الخادم غير موجود (%s) " + en_us: "unable to connect you to the last server, because server was not found." + ar_sa: "فشل الاتصال بالخادم السابق لان الخادم غير موجود ()" server-found: - en_us: "Connecting you to %s..." - ar_sa: " جاري الاتصال بخادم %s" + en_us: "Connecting you to ..." + ar_sa: "جاري الاتصال بخادم " # commands common messages: commands-common: @@ -54,7 +54,7 @@ commands-common: ar_sa: "أدخل اسم اللاعب مطلوب" command-not-specified: en_us: "You must specify a command to be run." - ar_sa: " ادخل الأمر المطلوب" + ar_sa: "ادخل الأمر المطلوب" # commands commands: From eed91dd73de299b2863bab13edfdadedd9bd2349 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 30 Sep 2023 12:48:32 +0400 Subject: [PATCH 25/64] fix some issues on lang file --- RedisBungee-API/src/main/resources/lang.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 513d28b..7070106 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -1,9 +1,14 @@ # this config file is for messages / Languages -# Note 1: use MiniMessage format https://docs.advntr.dev/minimessage/format.html for colors etc... Legacy chat color is not supported. -# note 2: Language codes used in minecraft from the minecraft wiki -# Locale Code -> In-game -# example: en_us for english and ar_sa for arabic -# all codes can be obtained from lik below +# Note 1: use MiniMessage format https://docs.advntr.dev/minimessage/format.html +# for colors etc... Legacy chat color is not supported. + +# Note 2: +# Language codes used in minecraft from the minecraft wiki + +# example: en_us for american english and ar_sa for arabic + +# all codes can be obtained from link below +# from the colum Locale Code -> In-game # https://minecraft.wiki/w/Language # example: @@ -24,7 +29,6 @@ redis-bungee-prefix: "[RedisBungee]" default-language: en_us # send language based on client sent settings -# https://minecraft.fandom.com/wiki/Language # if you don't have languages configured for client it will # default to language that has been set above use-client-language: true @@ -38,9 +42,11 @@ messages: en_us: "You are already logged in!" ar_sa: "انت متصل بالفعل" server-not-found: + # placeholder displays server name in the message. en_us: "unable to connect you to the last server, because server was not found." ar_sa: "فشل الاتصال بالخادم السابق لان الخادم غير موجود ()" server-found: + # placeholder displays server name in the message. en_us: "Connecting you to ..." ar_sa: "جاري الاتصال بخادم " From 9b54ca93db1318ec8cf9918cb43d0eef53d4949d Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 30 Sep 2023 12:50:32 +0400 Subject: [PATCH 26/64] remove old commands system, rename register leagacy command to register commands. --- .../redisbungee/api/config/ConfigLoader.java | 4 +- .../api/config/RedisBungeeConfiguration.java | 12 +- RedisBungee-API/src/main/resources/config.yml | 6 +- .../minecraft/redisbungee/RedisBungee.java | 14 +- .../commands/RedisBungeeCommands.java | 354 ----------------- .../RedisBungeeVelocityPlugin.java | 17 +- .../commands/RedisBungeeCommands.java | 361 ------------------ 7 files changed, 16 insertions(+), 752 deletions(-) delete mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java delete mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java index 81c02c6..9bf84e2 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java @@ -50,7 +50,7 @@ public interface ConfigLoader { } final boolean useSSL = node.getNode("useSSL").getBoolean(false); final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false); - final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false); + final boolean registerCommands = node.getNode("register-commands").getBoolean(false); final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); @@ -94,7 +94,7 @@ public interface ConfigLoader { plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); plugin.logInfo("handle motd: {}", handleMotd); - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, kickWhenOnline, reconnectToLastServer, handleMotd); + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerCommands, overrideBungeeCommands, kickWhenOnline, reconnectToLastServer, handleMotd); Summoner summoner; RedisBungeeMode redisBungeeMode; if (useSSL) { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index 5b991fa..ffb44c5 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -11,9 +11,7 @@ package com.imaginarycode.minecraft.redisbungee.api.config; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.net.InetAddresses; -import net.kyori.adventure.text.Component; import java.net.InetAddress; import java.util.List; @@ -23,7 +21,7 @@ public class RedisBungeeConfiguration { public static final int CONFIG_VERSION = 2; private final String proxyId; private final List exemptAddresses; - private final boolean registerLegacyCommands; + private final boolean registerCommands; private final boolean overrideBungeeCommands; private final boolean kickWhenOnline; @@ -31,14 +29,14 @@ public class RedisBungeeConfiguration { private final boolean handleMotd; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { + public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerCommands, boolean overrideBungeeCommands, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { this.proxyId = proxyId; ImmutableList.Builder addressBuilder = ImmutableList.builder(); for (String s : exemptAddresses) { addressBuilder.add(InetAddresses.forString(s)); } this.exemptAddresses = addressBuilder.build(); - this.registerLegacyCommands = registerLegacyCommands; + this.registerCommands = registerCommands; this.overrideBungeeCommands = overrideBungeeCommands; this.kickWhenOnline = kickWhenOnline; this.handleReconnectToLastServer = handleReconnectToLastServer; @@ -53,8 +51,8 @@ public class RedisBungeeConfiguration { return exemptAddresses; } - public boolean doRegisterLegacyCommands() { - return registerLegacyCommands; + public boolean doRegisterCommands() { + return registerCommands; } public boolean doOverrideBungeeCommands() { diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 5d6c62c..1f18a09 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -57,10 +57,12 @@ enable-jedis-pool-compatibility: false # max connections for the compatibility pool compatibility-max-connections: 3 -# Register redis bungee legacy commands +# Register RedisBungee commands # if this disabled override-bungee-commands will be ignored -register-legacy-commands: false +register-commands: false + +# THIS IS BUNGEECORD ONLY OPTION! # Whether or not RedisBungee should install its version of regular BungeeCord commands. # Often, the RedisBungee commands are desired, but in some cases someone may wish to # override the commands using another plugin. diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 7790b37..8b8d263 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -25,7 +25,6 @@ import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; -import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; @@ -35,7 +34,6 @@ import com.squareup.okhttp.OkHttpClient; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Plugin; @@ -241,19 +239,11 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin - * All classes use the {@link AbstractRedisBungeeAPI}. - * - * @author tuxed - * @since 0.2.3 - */ -public class RedisBungeeCommands { - private static final BaseComponent[] NO_PLAYER_SPECIFIED = - new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create(); - private static final BaseComponent[] PLAYER_NOT_FOUND = - new ComponentBuilder("No such player found.").color(ChatColor.RED).create(); - private static final BaseComponent[] NO_COMMAND_SPECIFIED = - new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create(); - - private static String playerPlural(int num) { - return num == 1 ? num + " player is" : num + " players are"; - } - - public static class GlistCommand extends Command { - private final RedisBungee plugin; - - public GlistCommand(RedisBungee plugin) { - super("glist", "bungeecord.command.list", "redisbungee", "rglist"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); - BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW) - .append(playerPlural(count) + " currently online.").create(); - if (args.length > 0 && args[0].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - for (Map.Entry entry : serverToPlayers.entries()) { - // if for any reason UUID translation fails just return the uuid as name, to make command finish executing. - String playerName = plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false); - human.put(entry.getKey(), playerName != null ? playerName : entry.getValue().toString()); - } - for (String server : new TreeSet<>(serverToPlayers.keySet())) { - TextComponent serverName = new TextComponent(); - serverName.setColor(ChatColor.GREEN); - serverName.setText("[" + server + "] "); - TextComponent serverCount = new TextComponent(); - serverCount.setColor(ChatColor.YELLOW); - serverCount.setText("(" + serverToPlayers.get(server).size() + "): "); - TextComponent serverPlayers = new TextComponent(); - serverPlayers.setColor(ChatColor.WHITE); - serverPlayers.setText(Joiner.on(", ").join(human.get(server))); - sender.sendMessage(serverName, serverCount, serverPlayers); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create()); - } - } - }); - } - } - - public static class FindCommand extends Command { - private final RedisBungee plugin; - - public FindCommand(RedisBungee plugin) { - super("find", "bungeecord.command.find", "rfind"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - ServerInfo si = plugin.getProxy().getServerInfo(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)); - if (si != null) { - TextComponent message = new TextComponent(); - message.setColor(ChatColor.BLUE); - message.setText(args[0] + " is on " + si.getName() + "."); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class LastSeenCommand extends Command { - private final RedisBungee plugin; - - public LastSeenCommand(RedisBungee plugin) { - super("lastseen", "redisbungee.command.lastseen", "rlastseen"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); - TextComponent message = new TextComponent(); - if (secs == 0) { - message.setColor(ChatColor.GREEN); - message.setText(args[0] + " is currently online."); - } else if (secs != -1) { - message.setColor(ChatColor.BLUE); - message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); - } else { - message.setColor(ChatColor.RED); - message.setText(args[0] + " has never been online."); - } - sender.sendMessage(message); - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class IpCommand extends Command { - private final RedisBungee plugin; - - public IpCommand(RedisBungee plugin) { - super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); - if (ia != null) { - TextComponent message = new TextComponent(); - message.setColor(ChatColor.GREEN); - message.setText(args[0] + " is connected from " + ia.toString() + "."); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class PlayerProxyCommand extends Command { - private final RedisBungee plugin; - - public PlayerProxyCommand(RedisBungee plugin) { - super("pproxy", "redisbungee.command.pproxy"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); - if (proxy != null) { - TextComponent message = new TextComponent(); - message.setColor(ChatColor.GREEN); - message.setText(args[0] + " is connected to " + proxy + "."); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - } - }); - } - } - - public static class SendToAll extends Command { - private final RedisBungee plugin; - - public SendToAll(RedisBungee plugin) { - super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall"); - this.plugin = plugin; - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (args.length > 0) { - String command = Joiner.on(" ").skipNulls().join(args); - plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); - TextComponent message = new TextComponent(); - message.setColor(ChatColor.GREEN); - message.setText("Sent the command /" + command + " to all proxies."); - sender.sendMessage(message); - } else { - sender.sendMessage(NO_COMMAND_SPECIFIED); - } - } - } - - public static class ServerId extends Command { - private final RedisBungee plugin; - - public ServerId(RedisBungee plugin) { - super("serverid", "redisbungee.command.serverid", "rserverid"); - this.plugin = plugin; - } - - @Override - public void execute(CommandSender sender, String[] args) { - TextComponent textComponent = new TextComponent(); - textComponent.setText("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + "."); - textComponent.setColor(ChatColor.YELLOW); - sender.sendMessage(textComponent); - } - } - - public static class ServerIds extends Command { - private final RedisBungee plugin; - - public ServerIds(RedisBungee plugin) { - super("serverids", "redisbungee.command.serverids"); - this.plugin = plugin; - } - - @Override - public void execute(CommandSender sender, String[] strings) { - TextComponent textComponent = new TextComponent(); - textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies())); - textComponent.setColor(ChatColor.YELLOW); - sender.sendMessage(textComponent); - } - } - - public static class PlistCommand extends Command { - private final RedisBungee plugin; - - public PlistCommand(RedisBungee plugin) { - super("plist", "redisbungee.command.plist", "rplist"); - this.plugin = plugin; - } - - @Override - public void execute(final CommandSender sender, final String[] args) { - plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() { - @Override - public void run() { - String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); - if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { - sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create()); - return; - } - Set players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); - BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW) - .append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create(); - if (args.length >= 2 && args[1].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - for (Map.Entry entry : serverToPlayers.entries()) { - if (players.contains(entry.getValue())) { - human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false)); - } - } - for (String server : new TreeSet<>(human.keySet())) { - TextComponent serverName = new TextComponent(); - serverName.setColor(ChatColor.RED); - serverName.setText("[" + server + "] "); - TextComponent serverCount = new TextComponent(); - serverCount.setColor(ChatColor.YELLOW); - serverCount.setText("(" + human.get(server).size() + "): "); - TextComponent serverPlayers = new TextComponent(); - serverPlayers.setColor(ChatColor.WHITE); - serverPlayers.setText(Joiner.on(", ").join(human.get(server))); - sender.sendMessage(serverName, serverCount, serverPlayers); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create()); - } - } - }); - } - } -} \ No newline at end of file diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index 874799b..cdad87e 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -26,7 +26,6 @@ import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; -import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; @@ -46,7 +45,6 @@ import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.scheduler.ScheduledTask; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.slf4j.Logger; import redis.clients.jedis.exceptions.JedisConnectionException; @@ -270,18 +268,9 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con // register plugin messages IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register); - // register legacy commands - if (configuration.doRegisterLegacyCommands()) { - // Override Velocity commands - getProxy().getCommandManager().register("glist", new RedisBungeeCommands.GlistCommand(this), "redisbungee", "rglist"); - getProxy().getCommandManager().register("sendtoall", new RedisBungeeCommands.SendToAll(this), "rsendtoall"); - getProxy().getCommandManager().register("serverid", new RedisBungeeCommands.ServerId(this), "rserverid"); - getProxy().getCommandManager().register("serverids", new RedisBungeeCommands.ServerIds(this)); - getProxy().getCommandManager().register("pproxy", new RedisBungeeCommands.PlayerProxyCommand(this)); - getProxy().getCommandManager().register("plist", new RedisBungeeCommands.PlistCommand(this), "rplist"); - getProxy().getCommandManager().register("lastseen", new RedisBungeeCommands.LastSeenCommand(this), "rlastseen"); - getProxy().getCommandManager().register("ip", new RedisBungeeCommands.IpCommand(this), "playerip", "rip", "rplayerip"); - getProxy().getCommandManager().register("find", new RedisBungeeCommands.FindCommand(this), "rfind"); + // register commands + if (configuration.doRegisterCommands()) { + } logInfo("RedisBungee initialized successfully "); } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java deleted file mode 100644 index bb9823e..0000000 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (c) 2013-present RedisBungee contributors - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * - * http://www.eclipse.org/legal/epl-v10.html - */ - -package com.imaginarycode.minecraft.redisbungee.commands; - -import com.google.common.base.Joiner; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; -import com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin; -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.command.SimpleCommand; -import com.velocitypowered.api.proxy.server.RegisteredServer; -import com.velocitypowered.api.proxy.server.ServerInfo; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.NamedTextColor; - -import java.net.InetAddress; -import java.text.SimpleDateFormat; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - - -/** - * This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen. - *

- * All classes use the {@link AbstractRedisBungeeAPI}. - * - * @author tuxed - * @since 0.2.3 - */ -public class RedisBungeeCommands { - - private static final Component NO_PLAYER_SPECIFIED = - Component.text("You must specify a player name.", NamedTextColor.RED); - private static final Component PLAYER_NOT_FOUND = - Component.text("No such player found.", NamedTextColor.RED); - private static final Component NO_COMMAND_SPECIFIED = - Component.text("You must specify a command to be run.", NamedTextColor.RED); - - private static String playerPlural(int num) { - return num == 1 ? num + " player is" : num + " players are"; - } - - public static class GlistCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public GlistCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); - Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW); - CommandSource sender = invocation.source(); - if (invocation.arguments().length > 0 && invocation.arguments()[0].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - serverToPlayers.forEach((key, value) -> { - // if for any reason UUID translation fails just return the uuid as name, to make command finish executing. - String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false); - human.put(key, playerName != null ? playerName : value.toString()); - }); - for (String server : new TreeSet<>(serverToPlayers.keySet())) { - Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN); - Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW); - Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); - sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers)); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW)); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("velocity.command.server"); - } - } - - public static class FindCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public FindCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String[] args = invocation.arguments(); - CommandSource sender = invocation.source(); - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - ServerInfo si = plugin.getProxy().getServer(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)).map(RegisteredServer::getServerInfo).orElse(null); - if (si != null) { - Component message = Component.text(args[0] + " is on " + si.getName() + ".", NamedTextColor.BLUE); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.find"); - } - } - - public static class LastSeenCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public LastSeenCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String[] args = invocation.arguments(); - CommandSource sender = invocation.source(); - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); - TextComponent.Builder message = Component.text(); - if (secs == 0) { - message.color(NamedTextColor.GREEN); - message.content(args[0] + " is currently online."); - } else if (secs != -1) { - message.color(NamedTextColor.BLUE); - message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); - } else { - message.color(NamedTextColor.RED); - message.content(args[0] + " has never been online."); - } - sender.sendMessage(message.build()); - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.lastseen"); - } - } - - public static class IpCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public IpCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - CommandSource sender = invocation.source(); - String[] args = invocation.arguments(); - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); - if (ia != null) { - TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.ip"); - } - } - - public static class PlayerProxyCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public PlayerProxyCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - CommandSource sender = invocation.source(); - String[] args = invocation.arguments(); - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - if (args.length > 0) { - UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); - if (uuid == null) { - sender.sendMessage(PLAYER_NOT_FOUND); - return; - } - String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); - if (proxy != null) { - TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN); - sender.sendMessage(message); - } else { - sender.sendMessage(PLAYER_NOT_FOUND); - } - } else { - sender.sendMessage(NO_PLAYER_SPECIFIED); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.pproxy"); - } - } - - public static class SendToAll implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public SendToAll(RedisBungeeVelocityPlugin plugin) { - //super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall"); - this.plugin = plugin; - } - - @Override - public void execute(final Invocation invocation) { - String[] args = invocation.arguments(); - CommandSource sender = invocation.source(); - if (args.length > 0) { - String command = Joiner.on(" ").skipNulls().join(args); - plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); - TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN); - sender.sendMessage(message); - } else { - sender.sendMessage(NO_COMMAND_SPECIFIED); - } - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.sendtoall"); - } - } - - public static class ServerId implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public ServerId(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - invocation.source().sendMessage(Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.serverid"); - } - } - - public static class ServerIds implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public ServerIds(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - invocation.source().sendMessage( - Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.serverids"); - } - } - - public static class PlistCommand implements SimpleCommand { - private final RedisBungeeVelocityPlugin plugin; - - public PlistCommand(RedisBungeeVelocityPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void execute(Invocation invocation) { - CommandSource sender = invocation.source(); - String[] args = invocation.arguments(); - plugin.getProxy().getScheduler().buildTask(plugin, () -> { - String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); - if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { - sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); - return; - } - Set players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); - Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW); - if (args.length >= 2 && args[1].equals("showall")) { - Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); - Multimap human = HashMultimap.create(); - serverToPlayers.forEach((key, value) -> { - if (players.contains(value)) { - human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false)); - } - }); - for (String server : new TreeSet<>(human.keySet())) { - TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED); - TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW); - TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); - sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers)); - } - sender.sendMessage(playersOnline); - } else { - sender.sendMessage(playersOnline); - sender.sendMessage(Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW)); - } - }).schedule(); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return invocation.source().hasPermission("redisbungee.command.plist"); - } - } - -} \ No newline at end of file From 383e647c871c56c5a2ad82e7bd422e5e7af5baee Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 30 Sep 2023 13:34:21 +0400 Subject: [PATCH 27/64] split some functions from config loader to be common --- README.md | 2 +- .../api/config/LangConfiguration.java | 4 ++ .../api/config/RedisBungeeConfiguration.java | 1 - .../config/{ => loaders}/ConfigLoader.java | 45 +++------------ .../config/loaders/GenericConfigLoader.java | 57 +++++++++++++++++++ .../api/config/loaders/LangConfigLoader.java | 4 ++ .../minecraft/redisbungee/RedisBungee.java | 2 +- .../RedisBungeeVelocityPlugin.java | 2 +- gradlew | 2 +- 9 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java rename RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/{ => loaders}/ConfigLoader.java (82%) create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java diff --git a/README.md b/README.md index 97ba182..bb0a4ee 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ then in your project plugin.yml add `RedisBungee` to `depends` like this ```yaml name: "yourplugin" -main: your.main.class +main: your.loaders.class version: 1.0.0-SNAPSHOT author: idk depends: [ RedisBungee ] diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java new file mode 100644 index 0000000..0d421e8 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java @@ -0,0 +1,4 @@ +package com.imaginarycode.minecraft.redisbungee.api.config; + +public class LangConfiguration { +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index ffb44c5..31a0aee 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -18,7 +18,6 @@ import java.util.List; public class RedisBungeeConfiguration { - public static final int CONFIG_VERSION = 2; private final String proxyId; private final List exemptAddresses; private final boolean registerCommands; diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java similarity index 82% rename from RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java rename to RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index 9bf84e2..346f3ee 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -8,13 +8,13 @@ * http://www.eclipse.org/legal/epl-v10.html */ -package com.imaginarycode.minecraft.redisbungee.api.config; +package com.imaginarycode.minecraft.redisbungee.api.config.loaders; -import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; @@ -26,26 +26,21 @@ import redis.clients.jedis.*; import redis.clients.jedis.providers.ClusterConnectionProvider; import redis.clients.jedis.providers.PooledConnectionProvider; -import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.*; -public interface ConfigLoader { +public interface ConfigLoader extends GenericConfigLoader { - default void loadConfig(RedisBungeePlugin plugin, File dataFolder) throws IOException { - loadConfig(plugin, dataFolder.toPath()); - } + int CONFIG_VERSION = 2; + @Override default void loadConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { - Path configFile = createConfigFile(dataFolder); + Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml"); final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); ConfigurationNode node = yamlConfigurationFileLoader.load(); - if (node.getNode("config-version").getInt(0) != RedisBungeeConfiguration.CONFIG_VERSION) { - handleOldConfig(dataFolder); + if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) { + handleOldConfig(dataFolder, "config.yml", "config.yml"); node = yamlConfigurationFileLoader.load(); } final boolean useSSL = node.getNode("useSSL").getBoolean(false); @@ -145,29 +140,5 @@ public interface ConfigLoader { void onConfigLoad(RedisBungeeConfiguration configuration, Summoner summoner, RedisBungeeMode mode); - default Path createConfigFile(Path dataFolder) throws IOException { - if (Files.notExists(dataFolder)) { - Files.createDirectory(dataFolder); - } - Path file = dataFolder.resolve("config.yml"); - if (Files.notExists(file)) { - try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) { - Files.createFile(file); - assert in != null; - Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); - } - } - return file; - } - - default void handleOldConfig(Path dataFolder) throws IOException { - Path oldConfigFolder = dataFolder.resolve("old_config"); - if (Files.notExists(oldConfigFolder)) { - Files.createDirectory(oldConfigFolder); - } - Path oldConfigPath = dataFolder.resolve("config.yml"); - Files.move(oldConfigPath, oldConfigFolder.resolve(UUID.randomUUID() + "_config.yml")); - createConfigFile(dataFolder); - } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java new file mode 100644 index 0000000..e5f845d --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java @@ -0,0 +1,57 @@ +package com.imaginarycode.minecraft.redisbungee.api.config.loaders; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.time.Instant; +import java.util.UUID; + +public interface GenericConfigLoader { + + // CHANGES on every reboot + String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond(); + + default void loadConfig(RedisBungeePlugin plugin, File dataFolder) throws IOException { + loadConfig(plugin, dataFolder.toPath()); + } + + + void loadConfig(RedisBungeePlugin plugin, Path path) throws IOException; + + default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { + if (Files.notExists(dataFolder)) { + Files.createDirectory(dataFolder); + } + Path file = dataFolder.resolve(configFile); + if (Files.notExists(file) && defaultResourceID != null) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream(defaultResourceID)) { + Files.createFile(file); + assert in != null; + Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING); + } + } + return file; + } + + default void handleOldConfig(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { + Path oldConfigFolder = dataFolder.resolve("old_config"); + if (Files.notExists(oldConfigFolder)) { + Files.createDirectory(oldConfigFolder); + } + Path randomStoreConfigDirectory = oldConfigFolder.resolve(RANDOM_OLD); + if (Files.notExists(randomStoreConfigDirectory)) { + Files.createDirectory(randomStoreConfigDirectory); + } + Path oldConfigPath = dataFolder.resolve(configFile); + + Files.move(oldConfigPath, randomStoreConfigDirectory.resolve(configFile)); + createConfigFile(dataFolder, configFile, defaultResourceID); + } + +} diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java new file mode 100644 index 0000000..edb41f6 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -0,0 +1,4 @@ +package com.imaginarycode.minecraft.redisbungee.api.config.loaders; + +public interface LangConfigLoader { +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 8b8d263..4db37df 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -14,7 +14,7 @@ import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index cdad87e..e63aadb 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -15,7 +15,7 @@ import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; diff --git a/gradlew b/gradlew index fcb6fca..c24bf85 100755 --- a/gradlew +++ b/gradlew @@ -160,7 +160,7 @@ fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line -# * the main class name +# * the loaders class name # * -classpath # * -D...appname settings # * --module-path (only if needed) From 32b5e829ba5e1b4163e03dca5b0b7a954ebcc174 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 1 Oct 2023 13:22:27 +0400 Subject: [PATCH 28/64] gradle update --- gradle/wrapper/gradle-wrapper.jar | Bin 63375 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4cdf41af1ab109bc7f253b2b887023340..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 28216 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d..ac72c34 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index c24bf85..0adc8e1 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -160,7 +161,7 @@ fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line -# * the loaders class name +# * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) From 983693b929bf47751dc6ef5c84da2b0780c9f6e3 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 1 Oct 2023 14:31:49 +0400 Subject: [PATCH 29/64] Language system implementation, commands still not translatable yet, finish up configs system --- .../redisbungee/api/RedisBungeePlugin.java | 3 + .../api/config/LangConfiguration.java | 138 ++++++++++++++++++ .../api/config/loaders/ConfigLoader.java | 1 - .../config/loaders/GenericConfigLoader.java | 22 +-- .../api/config/loaders/LangConfigLoader.java | 53 ++++++- RedisBungee-API/src/main/resources/config.yml | 18 +-- RedisBungee-API/src/main/resources/lang.yml | 79 +++++----- .../redisbungee/BungeePlayerDataManager.java | 11 +- .../minecraft/redisbungee/RedisBungee.java | 20 ++- .../redisbungee/RedisBungeeListener.java | 6 +- .../redisbungee/RedisBungeeListener.java | 7 +- .../RedisBungeeVelocityPlugin.java | 19 ++- .../VelocityPlayerDataManager.java | 8 +- 13 files changed, 304 insertions(+), 81 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java index f94df72..8dc6887 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/RedisBungeePlugin.java @@ -11,6 +11,7 @@ package com.imaginarycode.minecraft.redisbungee.api; import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform; import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner; @@ -54,6 +55,8 @@ public interface RedisBungeePlugin

extends EventsPlatform { RedisBungeeConfiguration configuration(); + LangConfiguration langConfiguration(); + Summoner getSummoner(); RedisBungeeMode getRedisBungeeMode(); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java index 0d421e8..0b97e9b 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java @@ -1,4 +1,142 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + package com.imaginarycode.minecraft.redisbungee.api.config; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + public class LangConfiguration { + + + public static class Messages { + + private final Map LOGGED_IN_FROM_OTHER_LOCATION; + private final Map ALREADY_LOGGED_IN; + private final Map SERVER_CONNECTING; + private final Map SERVER_NOT_FOUND; + + private final Locale defaultLocale; + + public Messages(Locale defaultLocale) { + LOGGED_IN_FROM_OTHER_LOCATION = new HashMap<>(); + ALREADY_LOGGED_IN = new HashMap<>(); + SERVER_CONNECTING = new HashMap<>(); + SERVER_NOT_FOUND = new HashMap<>(); + this.defaultLocale = defaultLocale; + } + + public void register(String id, Locale locale, String miniMessage) { + switch (id) { + case "server-not-found" -> SERVER_NOT_FOUND.put(locale, miniMessage); + case "server-connecting" -> SERVER_CONNECTING.put(locale, miniMessage); + case "logged-in-other-location" -> LOGGED_IN_FROM_OTHER_LOCATION.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + case "already-logged-in" -> ALREADY_LOGGED_IN.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + } + } + + public Component alreadyLoggedIn(Locale locale) { + if (ALREADY_LOGGED_IN.containsKey(locale)) return ALREADY_LOGGED_IN.get(locale); + return ALREADY_LOGGED_IN.get(defaultLocale); + } + + // there is no way to know whats client locale during login so just default to use default locale MESSAGES. + public Component alreadyLoggedIn() { + return this.alreadyLoggedIn(this.defaultLocale); + } + + public Component loggedInFromOtherLocation(Locale locale) { + if (LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale)) return LOGGED_IN_FROM_OTHER_LOCATION.get(locale); + return LOGGED_IN_FROM_OTHER_LOCATION.get(defaultLocale); + } + + // there is no way to know what's client locale during login so just default to use default locale MESSAGES. + public Component loggedInFromOtherLocation() { + return this.loggedInFromOtherLocation(this.defaultLocale); + } + + public Component serverConnecting(Locale locale, String server) { + String miniMessage; + if (SERVER_CONNECTING.containsKey(locale)) { + miniMessage = SERVER_CONNECTING.get(locale); + } else { + miniMessage = SERVER_CONNECTING.get(defaultLocale); + } + return MiniMessage.miniMessage().deserialize(miniMessage, Placeholder.parsed("server", server)); + } + + public Component serverConnecting(String server) { + return this.serverConnecting(this.defaultLocale, server); + } + + public Component serverNotFound(Locale locale, String server) { + String miniMessage; + if (SERVER_NOT_FOUND.containsKey(locale)) { + miniMessage = SERVER_NOT_FOUND.get(locale); + } else { + miniMessage = SERVER_NOT_FOUND.get(defaultLocale); + } + return MiniMessage.miniMessage().deserialize(miniMessage, Placeholder.parsed("server", server)); + } + + public Component serverNotFound(String server) { + return this.serverNotFound(this.defaultLocale, server); + } + + + // tests locale if set CORRECTLY or just throw if not + public void test(Locale locale) { + if (!(LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale) && ALREADY_LOGGED_IN.containsKey(locale) && SERVER_CONNECTING.containsKey(locale) && SERVER_NOT_FOUND.containsKey(locale))) { + throw new IllegalStateException("Language system in `messages` found missing entries for " + locale.toString()); + } + } + + } + + + private final Component redisBungeePrefix; + + private final Locale defaultLanguage; + + private final boolean useClientLanguage; + + private final Messages messages; + + + public LangConfiguration(Component redisBungeePrefix, Locale defaultLanguage, boolean useClientLanguage, Messages messages) { + this.redisBungeePrefix = redisBungeePrefix; + this.defaultLanguage = defaultLanguage; + this.useClientLanguage = useClientLanguage; + this.messages = messages; + } + + public Component redisBungeePrefix() { + return redisBungeePrefix; + } + + public Locale defaultLanguage() { + return defaultLanguage; + } + + public boolean useClientLanguage() { + return useClientLanguage; + } + + public Messages messages() { + return messages; + } + + } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index 346f3ee..acdc387 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -34,7 +34,6 @@ public interface ConfigLoader extends GenericConfigLoader { int CONFIG_VERSION = 2; - @Override default void loadConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { Path configFile = createConfigFile(dataFolder, "config.yml", "config.yml"); final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java index e5f845d..9acd720 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/GenericConfigLoader.java @@ -1,29 +1,31 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + package com.imaginarycode.minecraft.redisbungee.api.config.loaders; -import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import org.jetbrains.annotations.Nullable; -import java.io.File; +; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.time.Instant; -import java.util.UUID; + public interface GenericConfigLoader { // CHANGES on every reboot String RANDOM_OLD = "backup-" + Instant.now().getEpochSecond(); - default void loadConfig(RedisBungeePlugin plugin, File dataFolder) throws IOException { - loadConfig(plugin, dataFolder.toPath()); - } - - - void loadConfig(RedisBungeePlugin plugin, Path path) throws IOException; - default Path createConfigFile(Path dataFolder, String configFile, @Nullable String defaultResourceID) throws IOException { if (Files.notExists(dataFolder)) { Files.createDirectory(dataFolder); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java index edb41f6..8755ddc 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -1,4 +1,55 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + package com.imaginarycode.minecraft.redisbungee.api.config.loaders; -public interface LangConfigLoader { +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Locale; + +public interface LangConfigLoader extends GenericConfigLoader { + + int CONFIG_VERSION = 1; + + default void loadLangConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { + Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml"); + final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build(); + ConfigurationNode node = yamlConfigurationFileLoader.load(); + if (node.getNode("config-version").getInt(0) != CONFIG_VERSION) { + handleOldConfig(dataFolder, "lang.yml", "lang.yml"); + node = yamlConfigurationFileLoader.load(); + } + // MINI message serializer + MiniMessage miniMessage = MiniMessage.miniMessage(); + + Component prefix = miniMessage.deserialize(node.getNode("prefix").getString("[RedisBungee]")); + Locale defaultLocale = Locale.forLanguageTag(node.getNode("default-locale").getString("en-us")); + System.out.println(node.getNode("default-locale").getString()); + boolean useClientLocale = node.getNode("use-client-locale").getBoolean(true); + LangConfiguration.Messages messages = new LangConfiguration.Messages(defaultLocale); + node.getNode("messages").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> { + messages.register(key.toString(), Locale.forLanguageTag(childKey.toString()), childChildNode.getString()); + })); + messages.test(defaultLocale); + onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages)); + } + + + void onLangConfigLoad(LangConfiguration langConfiguration); + + } diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 1f18a09..25e25e7 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -61,9 +61,8 @@ compatibility-max-connections: 3 # if this disabled override-bungee-commands will be ignored register-commands: false - # THIS IS BUNGEECORD ONLY OPTION! -# Whether or not RedisBungee should install its version of regular BungeeCord commands. +# Whether RedisBungee should install its version of regular BungeeCord commands. # Often, the RedisBungee commands are desired, but in some cases someone may wish to # override the commands using another plugin. # @@ -75,23 +74,24 @@ register-commands: false # ignored on velocity override-bungee-commands: false -# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic -# restart scripts. -exempt-ip-addresses: [] - # restore old login behavior before 0.9.0 update # enabled by default -# when true: when player login and there is old player with same uuid it will get disconnected as result and new player will login +# when true: when player login and there is old player with same uuid it will get disconnected as result and new player will log in # when false: when a player login but login will fail because old player is still connected. kick-when-online: true # enabled by default -# this option tells redis-bungee handle motd and set online count, when motd is requested +# this option tells RedisBungee handle motd and set online count, when motd is requested # you can disable this when you want to handle motd yourself, use RedisBungee api to get total players when needed :) handle-motd: true +# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic +# restart scripts. +# Ignored if handle-motd is disabled. +exempt-ip-addresses: [] + # disabled by default -# Redis-bungee will attempt to connect player to last server that was stored. +# RedisBungee will attempt to connect player to last server that was stored. reconnect-to-last-server: false # Config version DO NOT CHANGE!!!! diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 7070106..4cf7fa7 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -1,71 +1,80 @@ # this config file is for messages / Languages -# Note 1: use MiniMessage format https://docs.advntr.dev/minimessage/format.html +# use MiniMessage format https://docs.advntr.dev/minimessage/format.html # for colors etc... Legacy chat color is not supported. -# Note 2: # Language codes used in minecraft from the minecraft wiki - -# example: en_us for american english and ar_sa for arabic +# example: en-us for american english and ar-sa for arabic # all codes can be obtained from link below # from the colum Locale Code -> In-game +# NOTE: minecraft wiki shows languages like this `en_us` in config it should be `en-us` # https://minecraft.wiki/w/Language # example: # lets assume we want to add arabic language. -# errors: +# messages: # logged-in-other-location: -# en_us: "You logged in from another location!" -# ar_sa: "لقد اتصلت من مكان اخر" +# en-us: "You logged in from another location!" +# ar-sa: "لقد اتصلت من مكان اخر" # RedisBungee Prefix if ever used. -redis-bungee-prefix: "[RedisBungee]" +prefix: "[RedisBungee]" -# us_en is american English, Which is the default language used when a language for a message isn't defined. -# Warning: IF THE set default language wasn't defined in the config for all messages, plugin will not load. - -# set the default language -default-language: en_us +# en-us is american English, Which is the default language used when a language for a message isn't defined. +# Warning: IF THE set default locale wasn't defined in the config for all messages, plugin will not load. +# set the Default locale +default-locale: en-us # send language based on client sent settings -# if you don't have languages configured for client it will -# default to language that has been set above -use-client-language: true +# if you don't have languages configured For client Language +# it will default to language that has been set above +# NOTE: due minecraft protocol not sending player settings during login, +# some of the messages like logged-in-other-location will +# skip translation and use default locale that has been set in default-locale. +use-client-locale: true -# messages +# messages that are used during login, and connecting to Last server messages: logged-in-other-location: - en_us: "You logged in from another location!" - ar_sa: "لقد اتصلت من مكان اخر" + en-us: "You logged in from another location!" + ar-sa: "لقد اتصلت من مكان اخر" already-logged-in: - en_us: "You are already logged in!" - ar_sa: "انت متصل بالفعل" + en-us: "You are already logged in!" + ar-sa: "انت متصل بالفعل" server-not-found: # placeholder displays server name in the message. - en_us: "unable to connect you to the last server, because server was not found." - ar_sa: "فشل الاتصال بالخادم السابق لان الخادم غير موجود ()" - server-found: + en-us: "unable to connect you to the last server, because server was not found." + ar-sa: "فشل الاتصال بالخادم السابق لان الخادم غير موجود ()" + server-connecting: # placeholder displays server name in the message. - en_us: "Connecting you to ..." - ar_sa: "جاري الاتصال بخادم " + en-us: "Connecting you to ..." + ar-sa: "جاري الاتصال بخادم " -# commands common messages: + +# commands common messages commands-common: player-not-found: - en_us: "Player not found." - ar_sa: "اللاعب غير موجود" + en-us: "Player not found." + ar-sa: "اللاعب غير موجود" player-not-specified: - en_us: "You must specify a player name." - ar_sa: "أدخل اسم اللاعب مطلوب" + en-us: "You must specify a player name." + ar-sa: "أدخل اسم اللاعب مطلوب" command-not-specified: - en_us: "You must specify a command to be run." - ar_sa: "ادخل الأمر المطلوب" + en-us: "You must specify a command to be run." + ar-sa: "ادخل الأمر المطلوب" # commands commands: + glist: + single-player: + en-us: " player is currently online" + players: + en-us: " players are currently online" + notice: + en-us: "To see all players online, use /glist showall." - - +# DO NOT CHANGE!!!!! +config-version: 1 \ No newline at end of file diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java index 9a50019..8540005 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeePlayerDataManager.java @@ -12,15 +12,11 @@ package com.imaginarycode.minecraft.redisbungee; import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; @@ -30,14 +26,11 @@ import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.event.EventHandler; -import java.util.Objects; import java.util.concurrent.TimeUnit; public class BungeePlayerDataManager extends PlayerDataManager implements Listener { - private final BungeeComponentSerializer BUNGEECORD_SERIALIZER = BungeeComponentSerializer.get(); - public BungeePlayerDataManager(RedisBungeePlugin plugin) { super(plugin); } @@ -74,12 +67,12 @@ public class BungeePlayerDataManager extends PlayerDataManager event.completeIntent((Plugin) plugin), TimeUnit.SECONDS, 3); } else { event.setCancelled(true); - event.setCancelReason(BUNGEECORD_SERIALIZER.serialize(Component.empty())); + event.setCancelReason(BungeeComponentSerializer.get().serialize(plugin.langConfiguration().messages().alreadyLoggedIn())); event.completeIntent((Plugin) plugin); } } else { diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 4db37df..2fd62f7 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -14,8 +14,10 @@ import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.LangConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; @@ -45,6 +47,8 @@ import redis.clients.jedis.JedisPool; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetAddress; +import java.sql.Date; +import java.time.Instant; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -52,7 +56,7 @@ import java.util.concurrent.*; import java.util.logging.Level; -public class RedisBungee extends Plugin implements RedisBungeePlugin, ConfigLoader { +public class RedisBungee extends Plugin implements RedisBungeePlugin, ConfigLoader, LangConfigLoader { private static RedisBungeeAPI apiStatic; private AbstractRedisBungeeAPI api; @@ -64,6 +68,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin summoner; private UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; + private LangConfiguration langConfiguration; private OkHttpClient httpClient; private final Logger logger = LoggerFactory.getLogger("RedisBungee"); @@ -74,6 +79,10 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin optionalRegisteredServer = ((RedisBungeeVelocityPlugin) plugin).getProxy().getServer(lastServer); if (optionalRegisteredServer.isEmpty()) { - // sending failure message, todo: IMPLEMENT once lang system is finalized + player.sendMessage(plugin.langConfiguration().messages().serverNotFound(player.getPlayerSettings().getLocale(), lastServer)); return; } RegisteredServer server = optionalRegisteredServer.get(); diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index e63aadb..8fdac6c 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -15,8 +15,10 @@ import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; +import com.imaginarycode.minecraft.redisbungee.api.config.loaders.LangConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent; @@ -52,7 +54,9 @@ import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.nio.file.Path; +import java.sql.Date; import java.time.Duration; +import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -61,7 +65,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @Plugin(id = "redisbungee", name = "RedisBungee", version = Constants.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"}) -public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, ConfigLoader { +public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, ConfigLoader, LangConfigLoader { private final ProxyServer server; private final Logger logger; private final Path dataFolder; @@ -70,6 +74,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con private RedisBungeeMode redisBungeeMode; private final UUIDTranslator uuidTranslator; private RedisBungeeConfiguration configuration; + private LangConfiguration langConfiguration; private final OkHttpClient httpClient; private final ProxyDataManager proxyDataManager; @@ -92,9 +97,10 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con this.logger = logger; this.dataFolder = dataDirectory; logInfo("Version: {}", Constants.VERSION); - logInfo("Build date: {}", Constants.BUILD_DATE); + logInfo("Build date: {}", Date.from(Instant.ofEpochSecond(Constants.BUILD_DATE))); try { loadConfig(this, dataDirectory); + loadLangConfig(this, dataDirectory); } catch (IOException e) { throw new RuntimeException("Unable to load/save config", e); } catch (JedisConnectionException e) { @@ -206,6 +212,11 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con return this.configuration; } + @Override + public LangConfiguration langConfiguration() { + return this.langConfiguration; + } + @Override public Player getPlayer(UUID uuid) { return this.getProxy().getPlayer(uuid).orElse(null); @@ -311,6 +322,10 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con this.redisBungeeMode = mode; } + @Override + public void onLangConfigLoad(LangConfiguration langConfiguration) { + this.langConfiguration = langConfiguration; + } @Override public RedisBungeeMode getRedisBungeeMode() { diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java index b4e42fc..ea89d90 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityPlayerDataManager.java @@ -25,10 +25,7 @@ import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import java.util.Objects; import java.util.concurrent.TimeUnit; public class VelocityPlayerDataManager extends PlayerDataManager { @@ -69,15 +66,14 @@ public class VelocityPlayerDataManager extends PlayerDataManager Date: Sun, 1 Oct 2023 14:32:37 +0400 Subject: [PATCH 30/64] remove debug message --- .../redisbungee/api/config/loaders/LangConfigLoader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java index 8755ddc..40d39b4 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -38,7 +38,6 @@ public interface LangConfigLoader extends GenericConfigLoader { Component prefix = miniMessage.deserialize(node.getNode("prefix").getString("[RedisBungee]")); Locale defaultLocale = Locale.forLanguageTag(node.getNode("default-locale").getString("en-us")); - System.out.println(node.getNode("default-locale").getString()); boolean useClientLocale = node.getNode("use-client-locale").getBoolean(true); LangConfiguration.Messages messages = new LangConfiguration.Messages(defaultLocale); node.getNode("messages").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> { From fa7ca2dacbd4a58e12177907bc9fcff5fcc50e29 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 12 Apr 2024 22:01:53 +0400 Subject: [PATCH 31/64] checkstyle --- RedisBungee-API/build.gradle.kts | 9 +- RedisBungee-Bungee/build.gradle.kts | 9 +- RedisBungee-Velocity/build.gradle.kts | 5 + gradle.build.kts => build.gradle.kts | 0 config/checkstyle/checkstyle.xml | 382 ++++++++++++++++++++++++++ 5 files changed, 402 insertions(+), 3 deletions(-) rename gradle.build.kts => build.gradle.kts (100%) create mode 100644 config/checkstyle/checkstyle.xml diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index dfd81cf..ffef2f2 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -4,10 +4,12 @@ import java.io.ByteArrayOutputStream plugins { `java-library` `maven-publish` + checkstyle id("net.kyori.blossom") version "1.2.0" } + repositories { mavenCentral() } @@ -40,7 +42,7 @@ description = "RedisBungee interfaces" blossom { replaceToken("@version@", "$version") // GIT - var commit: String = "" + val commit: String; val commitStdout = ByteArrayOutputStream() rootProject.exec { standardOutput = commitStdout @@ -52,6 +54,11 @@ blossom { replaceToken("@build_date@", "${Instant.now().epochSecond}") } +checkstyle { + toolVersion = "10.12.3" +} + + java { withJavadocJar() withSourcesJar() diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 8a22c40..bf4efe0 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -1,15 +1,20 @@ plugins { `java-library` `maven-publish` + checkstyle id("com.github.johnrengelman.shadow") version "8.1.1" id("xyz.jpenilla.run-waterfall") version "2.0.0" } - repositories { mavenCentral() maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // bungeecord } + +checkstyle { + toolVersion = "10.12.3" +} + val bungeecordApiVersion = "1.20-R0.1-SNAPSHOT" dependencies { api(project(":RedisBungee-API")) @@ -44,7 +49,7 @@ tasks { } runWaterfall { waterfallVersion("1.20") - environment.put("REDISBUNGEE_PROXY_ID", "bungeecord-1") + environment["REDISBUNGEE_PROXY_ID"] = "bungeecord-1" } compileJava { options.encoding = Charsets.UTF_8.name() diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 7d1a260..2cd3930 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -1,6 +1,7 @@ plugins { `java-library` `maven-publish` + checkstyle id("com.github.johnrengelman.shadow") version "8.1.1" id("xyz.jpenilla.run-velocity") version "2.0.0" @@ -35,6 +36,10 @@ java { withJavadocJar() withSourcesJar() } +checkstyle { + toolVersion = "10.12.3" +} + tasks { withType { diff --git a/gradle.build.kts b/build.gradle.kts similarity index 100% rename from gradle.build.kts rename to build.gradle.kts diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..bc47e7e --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a6c69161039086b22dc2ac13bd604c9d82b6c278 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 7 Oct 2023 10:33:03 +0400 Subject: [PATCH 32/64] remove arabic for now, and finish up the lang file --- RedisBungee-API/src/main/resources/lang.yml | 49 ++++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 4cf7fa7..1a7bccd 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -38,43 +38,70 @@ use-client-locale: true messages: logged-in-other-location: en-us: "You logged in from another location!" - ar-sa: "لقد اتصلت من مكان اخر" already-logged-in: en-us: "You are already logged in!" - ar-sa: "انت متصل بالفعل" server-not-found: # placeholder displays server name in the message. en-us: "unable to connect you to the last server, because server was not found." - ar-sa: "فشل الاتصال بالخادم السابق لان الخادم غير موجود ()" server-connecting: # placeholder displays server name in the message. en-us: "Connecting you to ..." - ar-sa: "جاري الاتصال بخادم " # commands common messages commands-common: player-not-found: en-us: "Player not found." - ar-sa: "اللاعب غير موجود" player-not-specified: en-us: "You must specify a player name." - ar-sa: "أدخل اسم اللاعب مطلوب" command-not-specified: en-us: "You must specify a command to be run." - ar-sa: "ادخل الأمر المطلوب" # commands commands: glist: single-player: - en-us: " player is currently online" + en-us: "1 player is currently online." players: - en-us: " players are currently online" + en-us: " players are currently online." notice: en-us: "To see all players online, use /glist showall." - - + find: + found: + en-us: " is on ." + last-seen: + online: + en-us: " is currently online." + last-seen: + en-us: " last seen on ." + never-seen: + en-us: " has never been online." + ip: + connected-from: + en-us: " is connected from ." + proxy: + connected-to: + en-us: " is connected to ." + send-to-all: + sent: + en-us: "Sent the command / to all proxies." + # note: server here means a proxy. + server-id: + message: + en-us: "You are on ." + # Same here too + server-ids: + message: + en-us: "All server IDs: " + plist: + notice: + en-us: "To see all players online, use /plist showall." + not-valid-proxy: + en-us: " is not valid proxy. use /serverids for valid proxies" + single-player: + en-us: "1 player is currently online on ." + players: + en-us: " players are currently online on ." # DO NOT CHANGE!!!!! config-version: 1 \ No newline at end of file From d70a5de8292d976f09994a3fe68cb37fea848c6b Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Wed, 18 Oct 2023 15:42:35 +0400 Subject: [PATCH 33/64] Only allow 6.2 or above of redis versions --- README.md | 9 +++++---- .../minecraft/redisbungee/api/util/RedisUtil.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bb0a4ee..82ff3e7 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,11 @@ SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.8 |:-------------:|:---------:| | 1.x.x | ✖ | | 2.x.x | ✖ | -| 3.x.x | ✔ | -| 4.x.x | ✔ | -| 5.x.x | ✔ | -| 6.x.x | ✔ | +| 3.x.x | ✖ | +| 4.x.x | ✖ | +| 5.x.x | ✖ | +| below 6.2 | ✖ | +| 6.2 or above | ✔ | | 7.x.x | ✔ | ## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java index 2e00c1e..3020a4a 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java @@ -13,7 +13,7 @@ public class RedisUtil { } int major = Integer.parseInt(args[0]); int minor = Integer.parseInt(args[1]); - return major >= 3 && minor >= 0; + return major >= 6 && minor >= 2; } // Ham1255: i am keeping this if some plugin uses this *IF* From 46d53fc0181049faa6391ec2f4b06dd79b3469a9 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Wed, 18 Oct 2023 15:50:47 +0400 Subject: [PATCH 34/64] config changes --- RedisBungee-API/src/main/resources/config.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 25e25e7..98e96c9 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -25,11 +25,10 @@ redis-cluster-servers: - host: 127.0.0.1 port: 6379 -# THIS FEATURE IS REDIS V6+ # OPTIONAL: if your redis uses acl usernames set the username here. leave empty for no username. redis-username: "" -# OPTIONAL but recommended: If your Redis server uses AUTH, set the password required. +# OPTIONAL but recommended: If your Redis server uses AUTH, set the required password. redis-password: "" # Maximum connections that will be maintained to the Redis server. @@ -37,7 +36,7 @@ redis-password: "" # inefficient plugins or a lot of players. max-redis-connections: 10 -# since redis can support ssl by version 6 you can use ssl / tls in redis bungee too! +# since redis can support ssl by version 6 you can use SSL/TLS in redis bungee too! # but there is more configuration needed to work see https://github.com/ProxioDev/RedisBungee/issues/18 # Keep note that SSL/TLS connections will decrease redis performance so use it when needed. useSSL: false @@ -47,7 +46,7 @@ useSSL: false # before launch proxy-id: "proxy-1" -# since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool. +# since RedisBungee Internally now uses JedisPooled instead of Jedis, JedisPool. # which will break compatibility with old plugins that uses RedisBungee JedisPool # so to mitigate this issue, RedisBungee will create an JedisPool for compatibility reasons. # disabled by default @@ -58,7 +57,7 @@ enable-jedis-pool-compatibility: false compatibility-max-connections: 3 # Register RedisBungee commands -# if this disabled override-bungee-commands will be ignored +# if this disabled override-bungee-commands will be also disabled automatically. register-commands: false # THIS IS BUNGEECORD ONLY OPTION! @@ -71,7 +70,7 @@ register-commands: false # # Please note that with build 787+, most commands overridden by RedisBungee were moved to # modules, and these must be disabled or overridden yourself. -# ignored on velocity +# not used on velocity override-bungee-commands: false # restore old login behavior before 0.9.0 update @@ -87,7 +86,7 @@ handle-motd: true # A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic # restart scripts. -# Ignored if handle-motd is disabled. +# ignored if handle-motd is disabled. exempt-ip-addresses: [] # disabled by default From 2429cc63d5bc44035e864ed2752681d0d7f61411 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 21 Oct 2023 13:20:33 +0400 Subject: [PATCH 35/64] load common command messages --- .../api/config/LangConfiguration.java | 96 ++++++++++++++++++- .../api/config/loaders/LangConfigLoader.java | 11 ++- 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java index 0b97e9b..df99059 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java @@ -14,14 +14,30 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; +/** + * This language support implementation is temporarily + * until I come up with better system but for now we will use Maps instead :/ + */ public class LangConfiguration { + private interface RegistrableMessages { - public static class Messages { + void register(String id, Locale locale, String miniMessage); + + void test(Locale locale); + + default void throwError(Locale locale, String where) { + throw new IllegalStateException("Language system in `" + where + "` found missing entries for " + locale.toString()); + } + + } + + public static class Messages implements RegistrableMessages{ private final Map LOGGED_IN_FROM_OTHER_LOCATION; private final Map ALREADY_LOGGED_IN; @@ -99,12 +115,79 @@ public class LangConfiguration { // tests locale if set CORRECTLY or just throw if not public void test(Locale locale) { if (!(LOGGED_IN_FROM_OTHER_LOCATION.containsKey(locale) && ALREADY_LOGGED_IN.containsKey(locale) && SERVER_CONNECTING.containsKey(locale) && SERVER_NOT_FOUND.containsKey(locale))) { - throw new IllegalStateException("Language system in `messages` found missing entries for " + locale.toString()); + throwError(locale, "messages"); } } } + public static class CommandMessages implements RegistrableMessages { + + private final Locale defaultLocale; + + // Common + private final Map COMMON_PLAYER_NOT_FOUND; + private final Map COMMON_PLAYER_NOT_SPECIFIED; + private final Map COMMON_COMMAND_NOT_SPECIFIED; + + public CommandMessages(Locale defaultLocale) { + this.defaultLocale = defaultLocale; + COMMON_PLAYER_NOT_FOUND = new HashMap<>(); + COMMON_COMMAND_NOT_SPECIFIED = new HashMap<>(); + COMMON_PLAYER_NOT_SPECIFIED = new HashMap<>(); + } + + // probably split using : + @Override + public void register(String id, Locale locale, String miniMessage) { + String[] splitId = id.split(":"); + //System.out.println(Arrays.toString(splitId) + " " + locale + miniMessage); + switch (splitId[0]) { + case "commands-common" -> { + switch (splitId[1]) { + case "player-not-found" -> COMMON_PLAYER_NOT_FOUND.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + case "player-not-specified" -> COMMON_PLAYER_NOT_SPECIFIED.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + case "command-not-specified" -> COMMON_COMMAND_NOT_SPECIFIED.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); + } + } + case "commands" -> { + switch (splitId[1]) { + + } + } + } + } + + public Component playerNotFound(Locale locale) { + if (COMMON_PLAYER_NOT_FOUND.containsKey(locale)) return COMMON_PLAYER_NOT_FOUND.get(locale); + return COMMON_PLAYER_NOT_FOUND.get(defaultLocale); + } + public Component playerNotFound() { + return playerNotFound(this.defaultLocale); + } + public Component commandNotSpecified(Locale locale) { + if (COMMON_COMMAND_NOT_SPECIFIED.containsKey(locale)) return COMMON_COMMAND_NOT_SPECIFIED.get(locale); + return COMMON_COMMAND_NOT_SPECIFIED.get(defaultLocale); + } + public Component commandNotSpecified() { + return commandNotSpecified(this.defaultLocale); + } + public Component playerNotSpecified(Locale locale) { + if (COMMON_PLAYER_NOT_SPECIFIED.containsKey(locale)) return COMMON_PLAYER_NOT_SPECIFIED.get(locale); + return COMMON_PLAYER_NOT_SPECIFIED.get(defaultLocale); + } + public Component playerNotSpecified() { + return playerNotSpecified(this.defaultLocale); + } + + + @Override + public void test(Locale locale) { + if (!(this.COMMON_PLAYER_NOT_FOUND.containsKey(locale) && this.COMMON_PLAYER_NOT_SPECIFIED.containsKey(locale) && this.COMMON_COMMAND_NOT_SPECIFIED.containsKey(locale))) { + throwError(locale, "commands messages"); + } + } + } private final Component redisBungeePrefix; @@ -114,12 +197,15 @@ public class LangConfiguration { private final Messages messages; + private final CommandMessages commandMessages; - public LangConfiguration(Component redisBungeePrefix, Locale defaultLanguage, boolean useClientLanguage, Messages messages) { + + public LangConfiguration(Component redisBungeePrefix, Locale defaultLanguage, boolean useClientLanguage, Messages messages, CommandMessages commandMessages) { this.redisBungeePrefix = redisBungeePrefix; this.defaultLanguage = defaultLanguage; this.useClientLanguage = useClientLanguage; this.messages = messages; + this.commandMessages = commandMessages; } public Component redisBungeePrefix() { @@ -138,5 +224,7 @@ public class LangConfiguration { return messages; } - + public CommandMessages commandMessages() { + return commandMessages; + } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java index 40d39b4..6ede5c6 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -44,7 +44,16 @@ public interface LangConfigLoader extends GenericConfigLoader { messages.register(key.toString(), Locale.forLanguageTag(childKey.toString()), childChildNode.getString()); })); messages.test(defaultLocale); - onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages)); + LangConfiguration.CommandMessages commandMessages = new LangConfiguration.CommandMessages(defaultLocale); + + node.getNode("commands-common").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> { + commandMessages.register("commands-common:" + key.toString(), Locale.forLanguageTag(childKey.toString()), childChildNode.getString()); + })); + node.getNode("commands").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> childChildNode.getChildrenMap().forEach((childChildKey, childChildChildNode) -> { + commandMessages.register("commands:" + key.toString() + ":" + childKey.toString(), Locale.forLanguageTag(childChildKey.toString()), childChildChildNode.getString()); + }))); + commandMessages.test(defaultLocale); + onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages, commandMessages)); } From c56a64bbc256d9af50dbcc58e0326fbff044d0de Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 12 Apr 2024 22:03:45 +0400 Subject: [PATCH 36/64] update depends & Gradle --- RedisBungee-API/build.gradle.kts | 2 +- RedisBungee-Bungee/build.gradle.kts | 4 ++-- RedisBungee-Velocity/build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 14 +++++++------- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index ffef2f2..640b74d 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -20,7 +20,7 @@ val configurateVersion = "3.7.3" val guavaVersion = "31.1-jre" val okHttpVersion = "2.7.5" val caffeineVersion = "3.1.8" -val adventureVersion = "4.14.0" +val adventureVersion = "4.15.0" dependencies { api("com.google.guava:guava:$guavaVersion") diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index bf4efe0..8569404 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -23,8 +23,8 @@ dependencies { exclude("com.google.code.gson", "gson") exclude("net.kyori","adventure-api") } - implementation("net.kyori:adventure-platform-bungeecord:4.3.0") - implementation("net.kyori:adventure-text-serializer-gson:4.14.0") + implementation("net.kyori:adventure-platform-bungeecord:4.3.2") + implementation("net.kyori:adventure-text-serializer-gson:4.15.0") } description = "RedisBungee Bungeecord implementation" diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 2cd3930..6aae865 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -55,7 +55,7 @@ tasks { } runVelocity { velocityVersion("3.3.0-SNAPSHOT") - environment.put("REDISBUNGEE_PROXY_ID", "velocity-1") + environment["REDISBUNGEE_PROXY_ID"] = "velocity-1" } compileJava { options.encoding = Charsets.UTF_8.name() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..1af9e09 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From 1c36aa541893c68bbdc9d10645b1b4109ebaa2b4 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 23 Feb 2024 21:48:07 +0400 Subject: [PATCH 37/64] remove lang system for commands commands will be included in seperate plugins for each platforms. NOTE: because of this commands `Modules` will be used as example to access the API --- .../api/config/LangConfiguration.java | 77 +------------------ .../api/config/RedisBungeeConfiguration.java | 14 +--- .../api/config/loaders/ConfigLoader.java | 4 +- .../api/config/loaders/LangConfigLoader.java | 10 +-- RedisBungee-API/src/main/resources/config.yml | 19 +---- RedisBungee-API/src/main/resources/lang.yml | 56 -------------- .../minecraft/redisbungee/RedisBungee.java | 5 -- .../RedisBungeeVelocityPlugin.java | 4 - 8 files changed, 5 insertions(+), 184 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java index df99059..59dd4c2 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java @@ -121,74 +121,6 @@ public class LangConfiguration { } - public static class CommandMessages implements RegistrableMessages { - - private final Locale defaultLocale; - - // Common - private final Map COMMON_PLAYER_NOT_FOUND; - private final Map COMMON_PLAYER_NOT_SPECIFIED; - private final Map COMMON_COMMAND_NOT_SPECIFIED; - - public CommandMessages(Locale defaultLocale) { - this.defaultLocale = defaultLocale; - COMMON_PLAYER_NOT_FOUND = new HashMap<>(); - COMMON_COMMAND_NOT_SPECIFIED = new HashMap<>(); - COMMON_PLAYER_NOT_SPECIFIED = new HashMap<>(); - } - - // probably split using : - @Override - public void register(String id, Locale locale, String miniMessage) { - String[] splitId = id.split(":"); - //System.out.println(Arrays.toString(splitId) + " " + locale + miniMessage); - switch (splitId[0]) { - case "commands-common" -> { - switch (splitId[1]) { - case "player-not-found" -> COMMON_PLAYER_NOT_FOUND.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); - case "player-not-specified" -> COMMON_PLAYER_NOT_SPECIFIED.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); - case "command-not-specified" -> COMMON_COMMAND_NOT_SPECIFIED.put(locale, MiniMessage.miniMessage().deserialize(miniMessage)); - } - } - case "commands" -> { - switch (splitId[1]) { - - } - } - } - } - - public Component playerNotFound(Locale locale) { - if (COMMON_PLAYER_NOT_FOUND.containsKey(locale)) return COMMON_PLAYER_NOT_FOUND.get(locale); - return COMMON_PLAYER_NOT_FOUND.get(defaultLocale); - } - public Component playerNotFound() { - return playerNotFound(this.defaultLocale); - } - public Component commandNotSpecified(Locale locale) { - if (COMMON_COMMAND_NOT_SPECIFIED.containsKey(locale)) return COMMON_COMMAND_NOT_SPECIFIED.get(locale); - return COMMON_COMMAND_NOT_SPECIFIED.get(defaultLocale); - } - public Component commandNotSpecified() { - return commandNotSpecified(this.defaultLocale); - } - public Component playerNotSpecified(Locale locale) { - if (COMMON_PLAYER_NOT_SPECIFIED.containsKey(locale)) return COMMON_PLAYER_NOT_SPECIFIED.get(locale); - return COMMON_PLAYER_NOT_SPECIFIED.get(defaultLocale); - } - public Component playerNotSpecified() { - return playerNotSpecified(this.defaultLocale); - } - - - @Override - public void test(Locale locale) { - if (!(this.COMMON_PLAYER_NOT_FOUND.containsKey(locale) && this.COMMON_PLAYER_NOT_SPECIFIED.containsKey(locale) && this.COMMON_COMMAND_NOT_SPECIFIED.containsKey(locale))) { - throwError(locale, "commands messages"); - } - } - } - private final Component redisBungeePrefix; private final Locale defaultLanguage; @@ -197,15 +129,11 @@ public class LangConfiguration { private final Messages messages; - private final CommandMessages commandMessages; - - - public LangConfiguration(Component redisBungeePrefix, Locale defaultLanguage, boolean useClientLanguage, Messages messages, CommandMessages commandMessages) { + public LangConfiguration(Component redisBungeePrefix, Locale defaultLanguage, boolean useClientLanguage, Messages messages) { this.redisBungeePrefix = redisBungeePrefix; this.defaultLanguage = defaultLanguage; this.useClientLanguage = useClientLanguage; this.messages = messages; - this.commandMessages = commandMessages; } public Component redisBungeePrefix() { @@ -224,7 +152,4 @@ public class LangConfiguration { return messages; } - public CommandMessages commandMessages() { - return commandMessages; - } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index 31a0aee..b162c6f 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -20,23 +20,19 @@ public class RedisBungeeConfiguration { private final String proxyId; private final List exemptAddresses; - private final boolean registerCommands; - private final boolean overrideBungeeCommands; private final boolean kickWhenOnline; private final boolean handleReconnectToLastServer; private final boolean handleMotd; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean registerCommands, boolean overrideBungeeCommands, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { + public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { this.proxyId = proxyId; ImmutableList.Builder addressBuilder = ImmutableList.builder(); for (String s : exemptAddresses) { addressBuilder.add(InetAddresses.forString(s)); } this.exemptAddresses = addressBuilder.build(); - this.registerCommands = registerCommands; - this.overrideBungeeCommands = overrideBungeeCommands; this.kickWhenOnline = kickWhenOnline; this.handleReconnectToLastServer = handleReconnectToLastServer; this.handleMotd = handleMotd; @@ -50,14 +46,6 @@ public class RedisBungeeConfiguration { return exemptAddresses; } - public boolean doRegisterCommands() { - return registerCommands; - } - - public boolean doOverrideBungeeCommands() { - return overrideBungeeCommands; - } - public boolean kickWhenOnline() { return kickWhenOnline; } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index acdc387..cba8576 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -43,8 +43,6 @@ public interface ConfigLoader extends GenericConfigLoader { node = yamlConfigurationFileLoader.load(); } final boolean useSSL = node.getNode("useSSL").getBoolean(false); - final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false); - final boolean registerCommands = node.getNode("register-commands").getBoolean(false); final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); @@ -88,7 +86,7 @@ public interface ConfigLoader extends GenericConfigLoader { plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); plugin.logInfo("handle motd: {}", handleMotd); - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerCommands, overrideBungeeCommands, kickWhenOnline, reconnectToLastServer, handleMotd); + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd); Summoner summoner; RedisBungeeMode redisBungeeMode; if (useSSL) { diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java index 6ede5c6..ad30c42 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -44,16 +44,8 @@ public interface LangConfigLoader extends GenericConfigLoader { messages.register(key.toString(), Locale.forLanguageTag(childKey.toString()), childChildNode.getString()); })); messages.test(defaultLocale); - LangConfiguration.CommandMessages commandMessages = new LangConfiguration.CommandMessages(defaultLocale); - node.getNode("commands-common").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> { - commandMessages.register("commands-common:" + key.toString(), Locale.forLanguageTag(childKey.toString()), childChildNode.getString()); - })); - node.getNode("commands").getChildrenMap().forEach((key, childNode) -> childNode.getChildrenMap().forEach((childKey, childChildNode) -> childChildNode.getChildrenMap().forEach((childChildKey, childChildChildNode) -> { - commandMessages.register("commands:" + key.toString() + ":" + childKey.toString(), Locale.forLanguageTag(childChildKey.toString()), childChildChildNode.getString()); - }))); - commandMessages.test(defaultLocale); - onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages, commandMessages)); + onLangConfigLoad(new LangConfiguration(prefix, defaultLocale, useClientLocale, messages)); } diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 98e96c9..7b1e204 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -56,23 +56,6 @@ enable-jedis-pool-compatibility: false # max connections for the compatibility pool compatibility-max-connections: 3 -# Register RedisBungee commands -# if this disabled override-bungee-commands will be also disabled automatically. -register-commands: false - -# THIS IS BUNGEECORD ONLY OPTION! -# Whether RedisBungee should install its version of regular BungeeCord commands. -# Often, the RedisBungee commands are desired, but in some cases someone may wish to -# override the commands using another plugin. -# -# If you are just denying access to the commands, RedisBungee uses the default BungeeCord -# permissions - just deny them and access will be denied. -# -# Please note that with build 787+, most commands overridden by RedisBungee were moved to -# modules, and these must be disabled or overridden yourself. -# not used on velocity -override-bungee-commands: false - # restore old login behavior before 0.9.0 update # enabled by default # when true: when player login and there is old player with same uuid it will get disconnected as result and new player will log in @@ -94,4 +77,4 @@ exempt-ip-addresses: [] reconnect-to-last-server: false # Config version DO NOT CHANGE!!!! -config-version: 2 +config-version: 3 diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index 1a7bccd..ab96cea 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -47,61 +47,5 @@ messages: # placeholder displays server name in the message. en-us: "Connecting you to ..." - -# commands common messages -commands-common: - player-not-found: - en-us: "Player not found." - player-not-specified: - en-us: "You must specify a player name." - command-not-specified: - en-us: "You must specify a command to be run." - -# commands -commands: - glist: - single-player: - en-us: "1 player is currently online." - players: - en-us: " players are currently online." - notice: - en-us: "To see all players online, use /glist showall." - find: - found: - en-us: " is on ." - last-seen: - online: - en-us: " is currently online." - last-seen: - en-us: " last seen on ." - never-seen: - en-us: " has never been online." - ip: - connected-from: - en-us: " is connected from ." - proxy: - connected-to: - en-us: " is connected to ." - send-to-all: - sent: - en-us: "Sent the command / to all proxies." - # note: server here means a proxy. - server-id: - message: - en-us: "You are on ." - # Same here too - server-ids: - message: - en-us: "All server IDs: " - plist: - notice: - en-us: "To see all players online, use /plist showall." - not-valid-proxy: - en-us: " is not valid proxy. use /serverids for valid proxies" - single-player: - en-us: "1 player is currently online on ." - players: - en-us: " players are currently online on ." - # DO NOT CHANGE!!!!! config-version: 1 \ No newline at end of file diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 2fd62f7..83e58af 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -249,12 +249,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin, Con // register plugin messages IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register); - // register commands - if (configuration.doRegisterCommands()) { - - } logInfo("RedisBungee initialized successfully "); } From 2eb7f3cf9d3860c8cf78490395128a37912f2d01 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 23 Feb 2024 23:06:58 +0400 Subject: [PATCH 38/64] Fix wrong config version --- .../redisbungee/api/config/loaders/LangConfigLoader.java | 2 +- RedisBungee-API/src/main/resources/config.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java index ad30c42..cc698f5 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -23,7 +23,7 @@ import java.util.Locale; public interface LangConfigLoader extends GenericConfigLoader { - int CONFIG_VERSION = 1; + int CONFIG_VERSION = 2; default void loadLangConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml"); diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 7b1e204..628f6be 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -77,4 +77,4 @@ exempt-ip-addresses: [] reconnect-to-last-server: false # Config version DO NOT CHANGE!!!! -config-version: 3 +config-version: 2 From 6c27228920719e61f258a90499a2fe6f40b839be Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Fri, 23 Feb 2024 23:10:48 +0400 Subject: [PATCH 39/64] fix for lang too --- .../redisbungee/api/config/loaders/LangConfigLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java index cc698f5..ad30c42 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/LangConfigLoader.java @@ -23,7 +23,7 @@ import java.util.Locale; public interface LangConfigLoader extends GenericConfigLoader { - int CONFIG_VERSION = 2; + int CONFIG_VERSION = 1; default void loadLangConfig(RedisBungeePlugin plugin, Path dataFolder) throws IOException { Path configFile = createConfigFile(dataFolder, "lang.yml", "lang.yml"); From 44175e8a6859e7d2a021bbed5c39ef4d88f556dd Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 24 Feb 2024 00:03:59 +0400 Subject: [PATCH 40/64] fix redis version detection --- .../minecraft/redisbungee/api/util/RedisUtil.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java index 3020a4a..9740fde 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java @@ -6,6 +6,9 @@ import com.google.common.annotations.VisibleForTesting; public class RedisUtil { public final static int PROXY_TIMEOUT = 30; + public static final int MAJOR_VERSION = 6; + public static final int MINOR_VERSION = 6; + public static boolean isRedisVersionRight(String redisVersion) { String[] args = redisVersion.split("\\."); if (args.length < 2) { @@ -13,7 +16,10 @@ public class RedisUtil { } int major = Integer.parseInt(args[0]); int minor = Integer.parseInt(args[1]); - return major >= 6 && minor >= 2; + + if (major > MAJOR_VERSION) return true; + return major == MAJOR_VERSION && minor >= MINOR_VERSION; + } // Ham1255: i am keeping this if some plugin uses this *IF* From 72b2d46dcd9e98598f5b17713bafe7049ebe6348 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 24 Feb 2024 00:08:58 +0400 Subject: [PATCH 41/64] update redis wrong version message --- .../minecraft/redisbungee/api/util/InitialUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java index 6815770..8ebf8d0 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/InitialUtils.java @@ -29,7 +29,7 @@ public class InitialUtils { String version = s.split(":")[1]; plugin.logInfo("Redis server version: " + version); if (!RedisUtil.isRedisVersionRight(version)) { - plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis."); + plugin.logFatal("Your version of Redis (" + version + ") is not at least version " + RedisUtil.MAJOR_VERSION + "." + RedisUtil.MINOR_VERSION + " RedisBungee requires a newer version of Redis."); throw new RuntimeException("Unsupported Redis version detected"); } long uuidCacheSize = unifiedJedis.hlen("uuid-cache"); From b3eacbd1c4963a026dfedc30540a92f36b35d397 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 27 Feb 2024 20:34:52 +0400 Subject: [PATCH 42/64] typo in redis minimal version --- .../imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java index 9740fde..7e337db 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/util/RedisUtil.java @@ -7,7 +7,7 @@ public class RedisUtil { public final static int PROXY_TIMEOUT = 30; public static final int MAJOR_VERSION = 6; - public static final int MINOR_VERSION = 6; + public static final int MINOR_VERSION = 2; public static boolean isRedisVersionRight(String redisVersion) { String[] args = redisVersion.split("\\."); From 69c3e30344f45d07d2a9e15e2b4daaf51c720d82 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 27 Feb 2024 20:38:58 +0400 Subject: [PATCH 43/64] [skip ci] Update readme about the transfer packets --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 82ff3e7..ee5841b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # RedisBungee fork By Limework -*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do -it, tell mojang to implement transfer packet*. +~~*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do +it, tell mojang to implement transfer packet*.~~ +minecraft snapshot 24W03A mojang introduced transfer packets in the protocol we have to wait and +see how Proxies developers implement the apis, additionally how to implement proxy transfers in redisbungee [Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) The original project of RedisBungee is no longer maintained, so we have forked the plugin. From ca8377ad4cc2a8a15c1877f8e9736c5abc58d99a Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 27 Feb 2024 20:39:34 +0400 Subject: [PATCH 44/64] [skip ci] Update readme about the transfer packets --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ee5841b..05a400c 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,10 @@ ~~*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*.~~ + minecraft snapshot 24W03A mojang introduced transfer packets in the protocol we have to wait and see how Proxies developers implement the apis, additionally how to implement proxy transfers in redisbungee + [Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) The original project of RedisBungee is no longer maintained, so we have forked the plugin. From 782f0994c2291a5b847bc51b9882e5618b1e1952 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Tue, 27 Feb 2024 20:41:52 +0400 Subject: [PATCH 45/64] [skip ci] Update readme about the transfer packets x3 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05a400c..bbe101c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ ~~*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*.~~ -minecraft snapshot 24W03A mojang introduced transfer packets in the protocol we have to wait and -see how Proxies developers implement the apis, additionally how to implement proxy transfers in redisbungee +In minecraft snapshot 24W03A, mojang introduced transfer packets in the protocol but, we have to wait and +see how Proxies developers will implement the apis, additionally how to implement proxy transfers in redisbungee [Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) From 0b8dc4bde64817e755ba165b267fdb4dd7fcf093 Mon Sep 17 00:00:00 2001 From: ThiagoROX <51332006+SrBedrock@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:52:19 +0400 Subject: [PATCH 46/64] provide pt-br translation Signed-off-by: mohammed jasem alaajel --- RedisBungee-API/src/main/resources/lang.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/resources/lang.yml b/RedisBungee-API/src/main/resources/lang.yml index ab96cea..817a053 100644 --- a/RedisBungee-API/src/main/resources/lang.yml +++ b/RedisBungee-API/src/main/resources/lang.yml @@ -38,14 +38,18 @@ use-client-locale: true messages: logged-in-other-location: en-us: "You logged in from another location!" + pt-br: "Você está logado em outra localização!" already-logged-in: en-us: "You are already logged in!" + pt-br: "Você já está logado!" server-not-found: # placeholder displays server name in the message. en-us: "unable to connect you to the last server, because server was not found." + pt-br: "falha ao conectar você ao último servidor, porque o servidor não foi encontrado." server-connecting: # placeholder displays server name in the message. en-us: "Connecting you to ..." + pt-br: "Conectando você a ..." # DO NOT CHANGE!!!!! -config-version: 1 \ No newline at end of file +config-version: 1 From e5b1f9d76e0ec3bacc416086694ee44cf37defea Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 13 Apr 2024 22:22:19 +0400 Subject: [PATCH 47/64] update adventure --- RedisBungee-API/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 640b74d..c959c34 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -20,7 +20,7 @@ val configurateVersion = "3.7.3" val guavaVersion = "31.1-jre" val okHttpVersion = "2.7.5" val caffeineVersion = "3.1.8" -val adventureVersion = "4.15.0" +val adventureVersion = "4.16.0" dependencies { api("com.google.guava:guava:$guavaVersion") From 41b5bde55c393aaf9773d4eb02d592d6fcf9617a Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 13 Apr 2024 22:27:26 +0400 Subject: [PATCH 48/64] insert todo into langauge system --- .../minecraft/redisbungee/api/config/LangConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java index 59dd4c2..87aaa11 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/LangConfiguration.java @@ -14,7 +14,6 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -22,6 +21,7 @@ import java.util.Map; /** * This language support implementation is temporarily * until I come up with better system but for now we will use Maps instead :/ + * Todo: possible usage of adventure api */ public class LangConfiguration { From 86f64ab019c8fa1615361a6bb66187af59ec488d Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sat, 13 Apr 2024 23:57:00 +0400 Subject: [PATCH 49/64] move all depeneds management into settings.gradle.kts and include acf lib --- RedisBungee-API/build.gradle.kts | 45 +-- RedisBungee-Bungee/build.gradle.kts | 21 +- RedisBungee-Velocity/build.gradle.kts | 18 +- config/checkstyle/checkstyle.xml | 382 -------------------------- settings.gradle.kts | 64 +++++ 5 files changed, 92 insertions(+), 438 deletions(-) delete mode 100644 config/checkstyle/checkstyle.xml diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index c959c34..5050b4b 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -4,37 +4,22 @@ import java.io.ByteArrayOutputStream plugins { `java-library` `maven-publish` - checkstyle id("net.kyori.blossom") version "1.2.0" } - -repositories { - mavenCentral() -} - - -val jedisVersion = "5.1.2" -val configurateVersion = "3.7.3" -val guavaVersion = "31.1-jre" -val okHttpVersion = "2.7.5" -val caffeineVersion = "3.1.8" -val adventureVersion = "4.16.0" - dependencies { - api("com.google.guava:guava:$guavaVersion") - api("redis.clients:jedis:$jedisVersion") - api("com.squareup.okhttp:okhttp:$okHttpVersion") - api("org.spongepowered:configurate-yaml:$configurateVersion") - api("com.github.ben-manes.caffeine:caffeine:$caffeineVersion") - - api("net.kyori:adventure-api:$adventureVersion") - api("net.kyori:adventure-text-serializer-gson:$adventureVersion") - api("net.kyori:adventure-text-serializer-legacy:$adventureVersion") - api("net.kyori:adventure-text-serializer-plain:$adventureVersion") - api("net.kyori:adventure-text-minimessage:$adventureVersion") - + api(libs.guava) + api(libs.jedis) + api(libs.okhttp) + api(libs.configurate) + api(libs.caffeine) + api(libs.adventure.api) + api(libs.adventure.gson) + api(libs.adventure.legacy) + api(libs.adventure.plain) + api(libs.adventure.miniMessage) + implementation(libs.acf.core) } description = "RedisBungee interfaces" @@ -54,10 +39,6 @@ blossom { replaceToken("@build_date@", "${Instant.now().epochSecond}") } -checkstyle { - toolVersion = "10.12.3" -} - java { withJavadocJar() @@ -70,6 +51,10 @@ tasks { val options = options as StandardJavadocDocletOptions options.use() options.isDocFilesSubDirs = true + val jedisVersion = libs.jedis.get().version + val configurateVersion = libs.configurate.get().version + val guavaVersion = libs.guava.get().version + val adventureVersion = libs.guava.get().version options.links( "https://configurate.aoeu.xyz/$configurateVersion/apidocs/", // configurate "https://javadoc.io/doc/redis.clients/jedis/$jedisVersion/", // jedis diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index 8569404..e11d326 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -1,30 +1,20 @@ plugins { `java-library` `maven-publish` - checkstyle id("com.github.johnrengelman.shadow") version "8.1.1" id("xyz.jpenilla.run-waterfall") version "2.0.0" } -repositories { - mavenCentral() - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // bungeecord -} - -checkstyle { - toolVersion = "10.12.3" -} - -val bungeecordApiVersion = "1.20-R0.1-SNAPSHOT" dependencies { api(project(":RedisBungee-API")) - compileOnly("net.md-5:bungeecord-api:$bungeecordApiVersion") { + compileOnly(libs.platform.bungeecord) { exclude("com.google.guava", "guava") exclude("com.google.code.gson", "gson") exclude("net.kyori","adventure-api") } - implementation("net.kyori:adventure-platform-bungeecord:4.3.2") - implementation("net.kyori:adventure-text-serializer-gson:4.15.0") + implementation(libs.adventure.platforms.bungeecord) + implementation(libs.adventure.gson) + implementation(libs.acf.bungeecord) } description = "RedisBungee Bungeecord implementation" @@ -83,6 +73,9 @@ tasks { relocate("com.google.j2objc", "com.imaginarycode.minecraft.redisbungee.internal.com.google.j2objc") relocate("com.google.thirdparty", "com.imaginarycode.minecraft.redisbungee.internal.com.google.thirdparty") relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") + // acf shade + relocate("co.aikar.commands", "com.imaginarycode.minecraft.redisbungee.internal.acf.commands") + } } diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 6aae865..f9d301f 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -1,16 +1,11 @@ plugins { `java-library` `maven-publish` - checkstyle id("com.github.johnrengelman.shadow") version "8.1.1" id("xyz.jpenilla.run-velocity") version "2.0.0" } -repositories { - mavenCentral() - maven { url = uri("https://repo.papermc.io/repository/maven-public/") } -} dependencies { api(project(":RedisBungee-API")) { @@ -24,10 +19,11 @@ dependencies { exclude("net.kyori", "adventure-text-serializer-legacy") exclude("net.kyori", "adventure-text-serializer-plain") exclude("net.kyori", "adventure-text-minimessage") - } - compileOnly("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") - annotationProcessor("com.velocitypowered:velocity-api:3.3.0-SNAPSHOT") + compileOnly(libs.platform.velocity) + annotationProcessor(libs.platform.velocity) + implementation(libs.acf.velocity) + } description = "RedisBungee Velocity implementation" @@ -36,10 +32,6 @@ java { withJavadocJar() withSourcesJar() } -checkstyle { - toolVersion = "10.12.3" -} - tasks { withType { @@ -75,6 +67,8 @@ tasks { relocate("okio", "com.imaginarycode.minecraft.redisbungee.internal.okio") relocate("org.json", "com.imaginarycode.minecraft.redisbungee.internal.json") relocate("com.github.benmanes.caffeine", "com.imaginarycode.minecraft.redisbungee.internal.caffeine") + // acf shade + relocate("co.aikar.commands", "com.imaginarycode.minecraft.redisbungee.internal.acf.commands") } } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index bc47e7e..0000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts index 181ac78..81024f8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,3 +9,67 @@ rootProject.name = "RedisBungee-Parent" include(":RedisBungee-Velocity") include(":RedisBungee-Bungee") include(":RedisBungee-API") + +dependencyResolutionManagement { + repositories { + mavenCentral() + maven { + name = "PaperMC" + url = uri("https://repo.papermc.io/repository/maven-public/") + } + maven { + // hosts the bungeecord apis + name = "sonatype" + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + } + maven { + name = "aikar repo" + url = uri("https://repo.aikar.co/content/groups/aikar/") + } + + } + versionCatalogs { + val jedisVersion = "5.1.2" + val configurateVersion = "3.7.3" + val guavaVersion = "31.1-jre" + val okHttpVersion = "2.7.5" + val caffeineVersion = "3.1.8" + val adventureVersion = "4.16.0" + val acf = "0.5.1-SNAPSHOT" + val bungeecordApiVersion = "1.20-R0.1-SNAPSHOT" + val velocityVersion = "3.3.0-SNAPSHOT"; + + + create("libs") { + + library("guava", "com.google.guava:guava:$guavaVersion") + library("jedis", "redis.clients:jedis:$jedisVersion") + library("okhttp", "com.squareup.okhttp:okhttp:$okHttpVersion") + library("configurate", "org.spongepowered:configurate-yaml:$configurateVersion") + library("caffeine", "com.github.ben-manes.caffeine:caffeine:$caffeineVersion") + + library("adventure-api", "net.kyori:adventure-api:$adventureVersion") + library("adventure-gson", "net.kyori:adventure-text-serializer-gson:$adventureVersion") + library("adventure-legacy", "net.kyori:adventure-text-serializer-legacy:$adventureVersion") + library("adventure-plain", "net.kyori:adventure-text-serializer-plain:$adventureVersion") + library("adventure-miniMessage", "net.kyori:adventure-text-minimessage:$adventureVersion") + + library("acf-core", "co.aikar:acf-core:$acf") + library("acf-bungeecord", "co.aikar:acf-bungee:$acf") + library("acf-velocity", "co.aikar:acf-velocity:$acf") + + library("platform-bungeecord","net.md-5:bungeecord-api:$bungeecordApiVersion") + library("adventure-platforms-bungeecord", "net.kyori:adventure-platform-bungeecord:4.3.2") + + library("platform-velocity", "com.velocitypowered:velocity-api:$velocityVersion") + + + + + } + + + } + + +} \ No newline at end of file From 40c542a50a4bf4bc2062f89b96c8ac3249c09993 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 14 Apr 2024 05:54:08 +0400 Subject: [PATCH 50/64] new command infrastructure --- RedisBungee-API/build.gradle.kts | 1 - RedisBungee-Bungee/build.gradle.kts | 1 + .../BungeeCommandPlatformHelper.java | 17 ++++++ .../minecraft/redisbungee/RedisBungee.java | 12 ++++ RedisBungee-Commands/build.gradle.kts | 24 ++++++++ .../redisbungee/commands/CommandLoader.java | 23 ++++++++ .../commands/CommandRedisBungee.java | 56 +++++++++++++++++++ .../commands/utils/AdventureBaseCommand.java | 26 +++++++++ .../commands/utils/CommandPlatformHelper.java | 34 +++++++++++ RedisBungee-Velocity/build.gradle.kts | 1 + .../RedisBungeeVelocityPlugin.java | 12 +++- .../VelocityCommandPlatformHelper.java | 26 +++++++++ settings.gradle.kts | 1 + 13 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java create mode 100644 RedisBungee-Commands/build.gradle.kts create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java create mode 100644 RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java diff --git a/RedisBungee-API/build.gradle.kts b/RedisBungee-API/build.gradle.kts index 5050b4b..3504124 100644 --- a/RedisBungee-API/build.gradle.kts +++ b/RedisBungee-API/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { api(libs.adventure.legacy) api(libs.adventure.plain) api(libs.adventure.miniMessage) - implementation(libs.acf.core) } description = "RedisBungee interfaces" diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index e11d326..a8a9721 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(libs.adventure.platforms.bungeecord) implementation(libs.adventure.gson) implementation(libs.acf.bungeecord) + implementation(project(":RedisBungee-Commands")) } description = "RedisBungee Bungeecord implementation" diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java new file mode 100644 index 0000000..019d06f --- /dev/null +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/BungeeCommandPlatformHelper.java @@ -0,0 +1,17 @@ +package com.imaginarycode.minecraft.redisbungee; + +import co.aikar.commands.BungeeCommandIssuer; +import co.aikar.commands.CommandIssuer; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; + +public class BungeeCommandPlatformHelper extends CommandPlatformHelper { + + @Override + public void sendMessage(CommandIssuer issuer, Component component) { + BungeeCommandIssuer bIssuer = (BungeeCommandIssuer) issuer; + bIssuer.getIssuer().sendMessage(BungeeComponentSerializer.get().serialize(component)); + } + +} diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 83e58af..4577ac1 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -10,6 +10,7 @@ package com.imaginarycode.minecraft.redisbungee; +import co.aikar.commands.BungeeCommandManager; import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; @@ -27,6 +28,8 @@ import com.imaginarycode.minecraft.redisbungee.api.util.InitialUtils; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher; import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator; +import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent; import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent; @@ -70,6 +73,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin commandManager, RedisBungeeConfiguration configuration) { + commandManager.registerCommand(new CommandRedisBungee()); + // todo: config options to disable each command + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java new file mode 100644 index 0000000..ce253ab --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.Constants; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.event.HoverEventSource; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; + +@CommandAlias("rb|redisbungee") +@CommandPermission("redisbungee.use") +public class CommandRedisBungee extends AdventureBaseCommand { + + private static final String message = """ + This proxy is running RedisBungee Limework's fork + ======================================== + RedisBungee version: + Build date: + Commit: + ========================================"""; + + @Default + public static void info(CommandIssuer issuer) { + sendMessage( + issuer, + MiniMessage.miniMessage() + .deserialize( + message, + Placeholder.component("version", Component.text(Constants.VERSION)), + Placeholder.component("build-date", Component.text(Constants.BUILD_DATE)), + Placeholder.component( + "commit", + Component.text(Constants.GIT_COMMIT) + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink())) + .hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink()))) + ))); + } + + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java new file mode 100644 index 0000000..873e8ee --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/AdventureBaseCommand.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.utils; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.CommandIssuer; +import net.kyori.adventure.text.Component; + +/** + * this just dumb class that wraps the adventure stuff into base command + */ +public abstract class AdventureBaseCommand extends BaseCommand { + + public static void sendMessage(CommandIssuer issuer, Component component) { + CommandPlatformHelper.getPlatformHelper().sendMessage(issuer, component); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java new file mode 100644 index 0000000..b5da19a --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.utils; + +import co.aikar.commands.CommandIssuer; +import net.kyori.adventure.text.Component; + + +public abstract class CommandPlatformHelper { + + private static CommandPlatformHelper SINGLETON; + + public abstract void sendMessage(CommandIssuer issuer, Component component); + + public static void init(CommandPlatformHelper platformHelper) { + if (SINGLETON != null) { + throw new IllegalStateException("tried to re init Platform Helper"); + } + SINGLETON = platformHelper; + } + + public static CommandPlatformHelper getPlatformHelper() { + return SINGLETON; + } + +} diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index f9d301f..390a9d7 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { } compileOnly(libs.platform.velocity) annotationProcessor(libs.platform.velocity) + implementation(project(":RedisBungee-Commands")) implementation(libs.acf.velocity) } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index e0b3129..2d250f0 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -10,11 +10,14 @@ package com.imaginarycode.minecraft.redisbungee; +import co.aikar.commands.VelocityCommandManager; import com.google.inject.Inject; import com.imaginarycode.minecraft.redisbungee.api.PlayerDataManager; import com.imaginarycode.minecraft.redisbungee.api.ProxyDataManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.commands.CommandLoader; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; import com.imaginarycode.minecraft.redisbungee.api.config.LangConfiguration; import com.imaginarycode.minecraft.redisbungee.api.config.loaders.ConfigLoader; import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; @@ -91,6 +94,8 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con new LegacyChannelIdentifier("legacy:redisbungee") ); + private VelocityCommandManager commandManager; + @Inject public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { this.server = server; @@ -264,7 +269,6 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con @Override public void initialize() { logInfo("Initializing RedisBungee....."); - ; // start heartbeat task // heartbeat and clean up this.heartbeatTask = server.getScheduler().buildTask(this, this.proxyDataManager::publishHeartbeat).repeat(Duration.ofSeconds(1)).schedule(); @@ -279,6 +283,11 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con // register plugin messages IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register); + // load commands + CommandPlatformHelper.init(new VelocityCommandPlatformHelper()); + this.commandManager = new VelocityCommandManager(this.getProxy(), this); + CommandLoader.initCommands(this.commandManager, configuration()); + logInfo("RedisBungee initialized successfully "); } @@ -308,6 +317,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con } catch (InterruptedException e) { throw new RuntimeException(e); } + if (commandManager != null) commandManager.unregisterCommands(); logInfo("RedisBungee shutdown complete"); } diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java new file mode 100644 index 0000000..07cd9de --- /dev/null +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/VelocityCommandPlatformHelper.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.VelocityCommandIssuer; +import com.imaginarycode.minecraft.redisbungee.commands.utils.CommandPlatformHelper; +import net.kyori.adventure.text.Component; + +public class VelocityCommandPlatformHelper extends CommandPlatformHelper { + + @Override + public void sendMessage(CommandIssuer issuer, Component component) { + VelocityCommandIssuer vIssuer = (VelocityCommandIssuer) issuer; + vIssuer.getIssuer().sendMessage(component); + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 81024f8..f3ff03f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,6 +7,7 @@ pluginManagement { rootProject.name = "RedisBungee-Parent" include(":RedisBungee-Velocity") +include(":RedisBungee-Commands") include(":RedisBungee-Bungee") include(":RedisBungee-API") From e76f0d0a00fb536f983fe3e59dc0ade729bdb3ef Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 14 Apr 2024 07:09:05 +0400 Subject: [PATCH 51/64] finish up the version command etc --- .../commands/CommandRedisBungee.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java index ce253ab..315fa3c 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -11,46 +11,58 @@ package com.imaginarycode.minecraft.redisbungee.commands; import co.aikar.commands.CommandIssuer; -import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.CommandPermission; -import co.aikar.commands.annotation.Default; +import co.aikar.commands.annotation.*; import com.imaginarycode.minecraft.redisbungee.Constants; import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.event.HoverEventSource; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import java.util.Date; + @CommandAlias("rb|redisbungee") @CommandPermission("redisbungee.use") public class CommandRedisBungee extends AdventureBaseCommand { - private static final String message = """ + + @Default + @Subcommand("info|version|git") + public static void info(CommandIssuer issuer) { + final String message = """ This proxy is running RedisBungee Limework's fork ======================================== RedisBungee version: Build date: Commit: - ========================================"""; - - @Default - public static void info(CommandIssuer issuer) { + ======================================== + run /rb help for more commands"""; sendMessage( issuer, MiniMessage.miniMessage() .deserialize( message, Placeholder.component("version", Component.text(Constants.VERSION)), - Placeholder.component("build-date", Component.text(Constants.BUILD_DATE)), + Placeholder.component("build-date", Component.text( new Date(Constants.BUILD_DATE * 1000).toString() )), Placeholder.component( "commit", - Component.text(Constants.GIT_COMMIT) + Component.text(Constants.GIT_COMMIT.substring(0, 8)) .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, Constants.getGithubCommitLink())) .hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink()))) ))); } + @HelpCommand + public static void help(CommandIssuer issuer) { + final String message = """ + ======================================== + /rb info: shows version, build date, git commit hash. + /rb help: shows this page. + ......: ...... + ======================================== + run /rb help for more commands"""; + sendMessage(issuer, MiniMessage.miniMessage().deserialize(message)); + } } From 19064e0a602597f3c9f7556de4adf6f55b26e3e4 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 14 Apr 2024 08:04:08 +0400 Subject: [PATCH 52/64] impl: redis clean up task, taken from brains impl. --- .../api/tasks/UUIDCleanupTask.java | 56 +++++++++++++++++++ .../minecraft/redisbungee/RedisBungee.java | 2 +- .../redisbungee/commands/CommandLoader.java | 5 +- .../commands/CommandRedisBungee.java | 33 +++++++++-- .../commands/utils/CommandPlatformHelper.java | 1 + .../utils/StopperUUIDCleanupTask.java | 25 +++++++++ .../RedisBungeeVelocityPlugin.java | 2 +- 7 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java new file mode 100644 index 0000000..6e080c4 --- /dev/null +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/tasks/UUIDCleanupTask.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.api.tasks; + +import com.google.gson.Gson; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.util.uuid.CachedUUIDEntry; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.exceptions.JedisException; + +import java.util.ArrayList; + + +public class UUIDCleanupTask extends RedisTask{ + + private final Gson gson = new Gson(); + private final RedisBungeePlugin plugin; + + public UUIDCleanupTask(RedisBungeePlugin plugin) { + super(plugin); + this.plugin = plugin; + } + + // this code is inspired from https://github.com/minecrafter/redisbungeeclean + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + try { + final long number = unifiedJedis.hlen("uuid-cache"); + plugin.logInfo("Found {} entries", number); + ArrayList fieldsToRemove = new ArrayList<>(); + unifiedJedis.hgetAll("uuid-cache").forEach((field, data) -> { + CachedUUIDEntry cachedUUIDEntry = gson.fromJson(data, CachedUUIDEntry.class); + if (cachedUUIDEntry.expired()) { + fieldsToRemove.add(field); + } + }); + if (!fieldsToRemove.isEmpty()) { + unifiedJedis.hdel("uuid-cache", fieldsToRemove.toArray(new String[0])); + } + plugin.logInfo("deleted {} entries", fieldsToRemove.size()); + } catch (JedisException e) { + plugin.logFatal("There was an error fetching information", e); + } + return null; + } + + +} \ No newline at end of file diff --git a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java index 4577ac1..852a87b 100644 --- a/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java +++ b/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungee.java @@ -261,7 +261,7 @@ public class RedisBungee extends Plugin implements RedisBungeePlugin commandManager, RedisBungeeConfiguration configuration) { - commandManager.registerCommand(new CommandRedisBungee()); + public static void initCommands(CommandManager commandManager, RedisBungeePlugin plugin) { + commandManager.registerCommand(new CommandRedisBungee(plugin)); // todo: config options to disable each command } diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java index 315fa3c..8025030 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -13,10 +13,13 @@ package com.imaginarycode.minecraft.redisbungee.commands; import co.aikar.commands.CommandIssuer; import co.aikar.commands.annotation.*; import com.imaginarycode.minecraft.redisbungee.Constants; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; +import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; @@ -26,10 +29,15 @@ import java.util.Date; @CommandPermission("redisbungee.use") public class CommandRedisBungee extends AdventureBaseCommand { + private final RedisBungeePlugin plugin; + + public CommandRedisBungee(RedisBungeePlugin plugin) { + this.plugin = plugin; + } @Default @Subcommand("info|version|git") - public static void info(CommandIssuer issuer) { + public void info(CommandIssuer issuer) { final String message = """ This proxy is running RedisBungee Limework's fork ======================================== @@ -52,17 +60,30 @@ public class CommandRedisBungee extends AdventureBaseCommand { .hoverEvent(HoverEvent.showText(Component.text("Click me to open: " + Constants.getGithubCommitLink()))) ))); } - + // ......: ...... @HelpCommand - public static void help(CommandIssuer issuer) { + public void help(CommandIssuer issuer) { final String message = """ ======================================== - /rb info: shows version, build date, git commit hash. - /rb help: shows this page. - ......: ...... + /rb info: shows info of this version. + /rb help: shows this page. + /rb clean: cleans up the uuid cache + WARNING... command above could cause performance issues ======================================== run /rb help for more commands"""; sendMessage(issuer, MiniMessage.miniMessage().deserialize(message)); } + @Subcommand("clean") + @Private + public void cleanUp(CommandIssuer issuer) { + if (StopperUUIDCleanupTask.isRunning) { + sendMessage(issuer, + Component.text("cleanup is currently running!").color(NamedTextColor.RED)); + return; + } + sendMessage(issuer, + Component.text("cleanup is Starting, you should see the output status in the proxy console").color(NamedTextColor.GOLD)); + plugin.executeAsync(new StopperUUIDCleanupTask(plugin)); + } } diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java index b5da19a..8f6a62f 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java @@ -11,6 +11,7 @@ package com.imaginarycode.minecraft.redisbungee.commands.utils; import co.aikar.commands.CommandIssuer; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import net.kyori.adventure.text.Component; diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java new file mode 100644 index 0000000..06dbe85 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/StopperUUIDCleanupTask.java @@ -0,0 +1,25 @@ +package com.imaginarycode.minecraft.redisbungee.commands.utils; + +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.api.tasks.UUIDCleanupTask; +import redis.clients.jedis.UnifiedJedis; + +public class StopperUUIDCleanupTask extends UUIDCleanupTask { + + public static boolean isRunning = false; + + public StopperUUIDCleanupTask(RedisBungeePlugin plugin) { + super(plugin); + } + + + @Override + public Void unifiedJedisTask(UnifiedJedis unifiedJedis) { + isRunning = true; + try { + super.unifiedJedisTask(unifiedJedis); + } catch (Exception ignored) {} + isRunning = false; + return null; + } +} diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java index 2d250f0..80babdc 100644 --- a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java +++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java @@ -286,7 +286,7 @@ public class RedisBungeeVelocityPlugin implements RedisBungeePlugin, Con // load commands CommandPlatformHelper.init(new VelocityCommandPlatformHelper()); this.commandManager = new VelocityCommandManager(this.getProxy(), this); - CommandLoader.initCommands(this.commandManager, configuration()); + CommandLoader.initCommands(this.commandManager, this); logInfo("RedisBungee initialized successfully "); } From 1e7f474a09cc1eb5744af6c9782aeadb1bfba765 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 14 Apr 2024 08:40:16 +0400 Subject: [PATCH 53/64] adding first batch of commands --- .../redisbungee/commands/CommandLoader.java | 3 +- .../commands/LegacyRedisBungeeCommands.java | 107 ++++++++++++++++++ .../commands/utils/CommandPlatformHelper.java | 1 + 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java index 1bd7922..d0e4ad2 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java @@ -18,7 +18,8 @@ public class CommandLoader { public static void initCommands(CommandManager commandManager, RedisBungeePlugin plugin) { commandManager.registerCommand(new CommandRedisBungee(plugin)); - // todo: config options to disable each command + // todo: config options to disable each command + commandManager.registerCommand(new LegacyRedisBungeeCommands(plugin)); } } diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java new file mode 100644 index 0000000..cbd64da --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java @@ -0,0 +1,107 @@ +package com.imaginarycode.minecraft.redisbungee.commands; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.base.Joiner; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.util.TreeSet; +import java.util.UUID; + +@CommandAlias("rbl|redisbungeeleagacy") +@CommandPermission("redisbungee.leagacy.use") +public class LegacyRedisBungeeCommands extends AdventureBaseCommand { + + + private final RedisBungeePlugin plugin; + + public LegacyRedisBungeeCommands(RedisBungeePlugin plugin) { + this.plugin = plugin; + } + + private static final Component NO_PLAYER_SPECIFIED = + Component.text("You must specify a player name.", NamedTextColor.RED); + private static final Component PLAYER_NOT_FOUND = + Component.text("No such player found.", NamedTextColor.RED); + private static final Component NO_COMMAND_SPECIFIED = + Component.text("You must specify a command to be run.", NamedTextColor.RED); + + private static String playerPlural(int num) { + return num == 1 ? num + " player is" : num + " players are"; + } + + @Subcommand("serverid") + @CommandAlias("serverid") + @CommandPermission("redisbungee.command.serverid") + public void serverId(CommandIssuer issuer) { + sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); + } + @Subcommand("serverids") + @CommandAlias("serverids") + @CommandPermission("redisbungee.command.serverids") + public void serverIds(CommandIssuer issuer) { + sendMessage(issuer, Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); + } + + @Subcommand("glist|rglist") + @CommandAlias("glist|rglist") + @CommandPermission("redisbungee.command.glist") + public void gList(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + int count = plugin.getAbstractRedisBungeeApi().getPlayerCount(); + Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW); + if (args.length > 0 && args[0].equals("showall")) { + Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + Multimap human = HashMultimap.create(); + serverToPlayers.forEach((key, value) -> { + // if for any reason UUID translation fails just return the uuid as name, to make command finish executing. + String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false); + human.put(key, playerName != null ? playerName : value.toString()); + }); + for (String server : new TreeSet<>(serverToPlayers.keySet())) { + Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN); + Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW); + Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); + sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers)); + } + sendMessage(issuer, playersOnline); + } else { + sendMessage(issuer, playersOnline); + sendMessage(issuer, Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW)); + } + + }); + } + @Subcommand("find") + @CommandAlias("find") + @CommandPermission("redisbungee.command.find") + public void find(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + String proxyId = plugin.playerDataManager().getProxyFor(uuid); + if (proxyId != null) { + String serverId = plugin.playerDataManager().getServerFor(uuid); + Component message = Component.text(args[0] + " is on proxy " + proxyId + " on server " + serverId +".", NamedTextColor.BLUE); + sendMessage(issuer, message); + } else { + sendMessage(issuer, PLAYER_NOT_FOUND); + } + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + }); + + } +} \ No newline at end of file diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java index 8f6a62f..a951e9d 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/utils/CommandPlatformHelper.java @@ -28,6 +28,7 @@ public abstract class CommandPlatformHelper { SINGLETON = platformHelper; } + public static CommandPlatformHelper getPlatformHelper() { return SINGLETON; } From de65b163e2f2da74c5466b6448302b8d9b065552 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 14 Apr 2024 11:58:04 +0400 Subject: [PATCH 54/64] finish up the commands --- .../redisbungee/commands/CommandLoader.java | 5 +- .../commands/LegacyRedisBungeeCommands.java | 178 ++++++++++++++++-- 2 files changed, 165 insertions(+), 18 deletions(-) diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java index d0e4ad2..f883839 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java @@ -12,14 +12,13 @@ package com.imaginarycode.minecraft.redisbungee.commands; import co.aikar.commands.CommandManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; -import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration; public class CommandLoader { public static void initCommands(CommandManager commandManager, RedisBungeePlugin plugin) { commandManager.registerCommand(new CommandRedisBungee(plugin)); - // todo: config options to disable each command - commandManager.registerCommand(new LegacyRedisBungeeCommands(plugin)); + // todo: config options to disable each command + commandManager.registerCommand(new LegacyRedisBungeeCommands(plugin)); } } diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java index cbd64da..930c7e9 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java @@ -1,3 +1,13 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + package com.imaginarycode.minecraft.redisbungee.commands; import co.aikar.commands.CommandIssuer; @@ -10,8 +20,12 @@ import com.google.common.collect.Multimap; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; +import java.net.InetAddress; +import java.text.SimpleDateFormat; +import java.util.Set; import java.util.TreeSet; import java.util.UUID; @@ -37,21 +51,8 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { return num == 1 ? num + " player is" : num + " players are"; } - @Subcommand("serverid") - @CommandAlias("serverid") - @CommandPermission("redisbungee.command.serverid") - public void serverId(CommandIssuer issuer) { - sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); - } - @Subcommand("serverids") - @CommandAlias("serverids") - @CommandPermission("redisbungee.command.serverids") - public void serverIds(CommandIssuer issuer) { - sendMessage(issuer, Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); - } - - @Subcommand("glist|rglist") - @CommandAlias("glist|rglist") + @Subcommand("glist") + @CommandAlias("glist") @CommandPermission("redisbungee.command.glist") public void gList(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { @@ -104,4 +105,151 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { }); } + + @Subcommand("lastseen") + @CommandAlias("lastseen") + @CommandPermission("redisbungee.command.lastseen") + public void lastSeen(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid); + TextComponent.Builder message = Component.text(); + if (secs == 0) { + message.color(NamedTextColor.GREEN); + message.content(args[0] + " is currently online."); + } else if (secs != -1) { + message.color(NamedTextColor.BLUE); + message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + "."); + } else { + message.color(NamedTextColor.RED); + message.content(args[0] + " has never been online."); + } + sendMessage(issuer, message.build()); + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + + + }); + } + + @Subcommand("ip") + @CommandAlias("ip") + @CommandPermission("redisbungee.command.ip") + public void ip(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid); + if (ia != null) { + TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN); + sendMessage(issuer, message); + } else { + sendMessage(issuer, PLAYER_NOT_FOUND); + } + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + }); + } + + @Subcommand("pproxy") + @CommandAlias("pproxy") + @CommandPermission("redisbungee.command.pproxy") + public void playerProxy(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + if (args.length > 0) { + UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true); + if (uuid == null) { + sendMessage(issuer, PLAYER_NOT_FOUND); + return; + } + String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid); + if (proxy != null) { + TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN); + sendMessage(issuer, message); + } else { + sendMessage(issuer, PLAYER_NOT_FOUND); + } + } else { + sendMessage(issuer, NO_PLAYER_SPECIFIED); + } + }); + + } + + @Subcommand("sendtoall") + @CommandAlias("sendtoall") + @CommandPermission("redisbungee.command.sendtoall") + public void sendToAll(CommandIssuer issuer, String[] args) { + if (args.length > 0) { + String command = Joiner.on(" ").skipNulls().join(args); + plugin.getAbstractRedisBungeeApi().sendProxyCommand(command); + TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN); + sendMessage(issuer, message); + } else { + sendMessage(issuer, NO_COMMAND_SPECIFIED); + } + + } + + @Subcommand("serverid") + @CommandAlias("serverid") + @CommandPermission("redisbungee.command.serverid") + public void serverId(CommandIssuer issuer) { + sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); + } + + @Subcommand("serverids") + @CommandAlias("serverids") + @CommandPermission("redisbungee.command.serverids") + public void serverIds(CommandIssuer issuer) { + sendMessage(issuer, Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); + } + + + @Subcommand("plist") + @CommandAlias("plist") + @CommandPermission("redisbungee.command.plist") + public void playerList(CommandIssuer issuer, String[] args) { + plugin.executeAsync(() -> { + String proxy = args.length >= 1 ? args[0] : plugin.configuration().getProxyId(); + if (!plugin.proxyDataManager().proxiesIds().contains(proxy)) { + sendMessage(issuer, Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED)); + return; + } + Set players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy); + Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW); + if (args.length >= 2 && args[1].equals("showall")) { + Multimap serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers(); + Multimap human = HashMultimap.create(); + serverToPlayers.forEach((key, value) -> { + if (players.contains(value)) { + human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false)); + } + }); + for (String server : new TreeSet<>(human.keySet())) { + TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED); + TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW); + TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE); + sendMessage(issuer, Component.textOfChildren(serverName, serverCount, serverPlayers)); + } + sendMessage(issuer, playersOnline); + } else { + sendMessage(issuer, playersOnline); + sendMessage(issuer, Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW)); + } + }); + + } + } \ No newline at end of file From 72025bc22c115f1725bb540127c219288b34ceec Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 14 Apr 2024 13:19:08 +0400 Subject: [PATCH 55/64] fix wrong use method of inetaddres in player data manager --- .../minecraft/redisbungee/api/PlayerDataManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java index 2324d26..84d46a7 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -161,7 +161,7 @@ public abstract class PlayerDataManager redisData = new HashMap<>(); redisData.put("last-online", String.valueOf(0)); redisData.put("proxy", plugin.configuration().getProxyId()); - redisData.put("ip", inetAddress.toString()); + redisData.put("ip", inetAddress.getHostAddress()); unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", redisData); JSONObject data = new JSONObject(); From 70aacc99c070048c841321c64172a87c00795701 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Wed, 28 Feb 2024 23:11:42 +0400 Subject: [PATCH 56/64] use continue instead of break --- .../minecraft/redisbungee/api/ProxyDataManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java index 34b7be0..587af30 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -335,7 +335,7 @@ public abstract class ProxyDataManager implements Runnable { if (!payloadDataManagerUUID.equals(this.dataManagerUUID)) { plugin.logWarn("detected other proxy is using same ID! {} this can cause issues, please shutdown this proxy and change the id!", this.proxyId); } - break; + continue; } if (unknownPayload instanceof HeartbeatPayload payload) { handleHeartBeat(payload); @@ -344,6 +344,8 @@ public abstract class ProxyDataManager implements Runnable { } else if (unknownPayload instanceof RunCommandPayload payload) { handleCommand(payload); } else if (unknownPayload instanceof PubSubPayload payload) { + System.out.println("HANDLED PUBSUB?"); + handleChannelMessage(payload); } else { plugin.logWarn("got unknown data manager payload: {}", unknownPayload.getClassName()); From 2015d1d0fd472bd4b8ffa9595e6aaf6fa1a6ccdf Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Mon, 15 Apr 2024 03:36:44 +0400 Subject: [PATCH 57/64] remove debug message --- .../minecraft/redisbungee/api/ProxyDataManager.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java index 587af30..5e9c269 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -344,8 +344,6 @@ public abstract class ProxyDataManager implements Runnable { } else if (unknownPayload instanceof RunCommandPayload payload) { handleCommand(payload); } else if (unknownPayload instanceof PubSubPayload payload) { - System.out.println("HANDLED PUBSUB?"); - handleChannelMessage(payload); } else { plugin.logWarn("got unknown data manager payload: {}", unknownPayload.getClassName()); From 51719f13e229cb17c973a89f9d32451802e528a1 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Mon, 15 Apr 2024 05:06:36 +0400 Subject: [PATCH 58/64] show command --- .../redisbungee/api/ProxyDataManager.java | 7 +++++++ .../commands/CommandRedisBungee.java | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java index 5e9c269..707b6fd 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -10,6 +10,7 @@ package com.imaginarycode.minecraft.redisbungee.api; +import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.imaginarycode.minecraft.redisbungee.api.payloads.AbstractPayload; @@ -147,6 +148,12 @@ public abstract class ProxyDataManager implements Runnable { return players; } + public Map eachProxyCount() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + heartbeats.forEach((proxy, data) -> builder.put(proxy, data.players())); + return builder.build(); + } + // Call on close private synchronized void publishDeath() { publishPayload(new DeathPayload(this.proxyId)); diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java index 8025030..65aabe9 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -17,6 +17,7 @@ import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; import com.imaginarycode.minecraft.redisbungee.commands.utils.StopperUUIDCleanupTask; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; @@ -69,6 +70,7 @@ public class CommandRedisBungee extends AdventureBaseCommand { /rb help: shows this page. /rb clean: cleans up the uuid cache WARNING... command above could cause performance issues + /rb show: shows list of proxies with player count ======================================== run /rb help for more commands"""; sendMessage(issuer, MiniMessage.miniMessage().deserialize(message)); @@ -86,4 +88,19 @@ public class CommandRedisBungee extends AdventureBaseCommand { plugin.executeAsync(new StopperUUIDCleanupTask(plugin)); } + + @Subcommand("show") + public void showProxies(CommandIssuer issuer) { + final String message = """ + ======================================== + ========================================"""; + TextComponent.Builder builder = Component.text(); + + plugin.proxyDataManager().eachProxyCount().forEach((proxy, players) + -> builder.append(Component.text(proxy + ": " + players)).appendNewline()); + + sendMessage(issuer, MiniMessage.miniMessage().deserialize(message, Placeholder.component("data", builder))); + + + } } From ab441503c7e6e0991d53f70c786d2d83825ed12b Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Mon, 15 Apr 2024 05:23:38 +0400 Subject: [PATCH 59/64] correctly remove heartbeat on cleanup --- .../minecraft/redisbungee/api/ProxyDataManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java index 707b6fd..5892244 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -280,6 +280,7 @@ public abstract class ProxyDataManager implements Runnable { return; } for (UUID uuid : getProxyMembers(id)) plugin.fireEvent(plugin.createPlayerLeftNetworkEvent(uuid)); + this.heartbeats.remove(id); plugin.logInfo("Proxy {} has disconnected", id); } From 78561fa4678ea6e7cf21ab8baa2ee8a16f3a5040 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Mon, 15 Apr 2024 05:36:52 +0400 Subject: [PATCH 60/64] improve rb ocmmand --- .../commands/CommandRedisBungee.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java index 65aabe9..4684970 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -41,12 +41,12 @@ public class CommandRedisBungee extends AdventureBaseCommand { public void info(CommandIssuer issuer) { final String message = """ This proxy is running RedisBungee Limework's fork - ======================================== + ======================================== RedisBungee version: Build date: Commit: - ======================================== - run /rb help for more commands"""; + ======================================== + run /rb help for more commands"""; sendMessage( issuer, MiniMessage.miniMessage() @@ -65,14 +65,14 @@ public class CommandRedisBungee extends AdventureBaseCommand { @HelpCommand public void help(CommandIssuer issuer) { final String message = """ - ======================================== + ======================================== /rb info: shows info of this version. /rb help: shows this page. /rb clean: cleans up the uuid cache WARNING... command above could cause performance issues /rb show: shows list of proxies with player count - ======================================== - run /rb help for more commands"""; + ======================================== + run /rb help for more commands"""; sendMessage(issuer, MiniMessage.miniMessage().deserialize(message)); } @Subcommand("clean") @@ -92,12 +92,23 @@ public class CommandRedisBungee extends AdventureBaseCommand { @Subcommand("show") public void showProxies(CommandIssuer issuer) { final String message = """ - ======================================== - ========================================"""; + ======================================== + ========================================"""; + + final String proxyPlayersMessage = " : online"; + + TextComponent.Builder builder = Component.text(); plugin.proxyDataManager().eachProxyCount().forEach((proxy, players) - -> builder.append(Component.text(proxy + ": " + players)).appendNewline()); + -> builder.append( + MiniMessage.miniMessage() + .deserialize(proxyPlayersMessage, + Placeholder.component("here", Component.text(plugin.proxyDataManager().proxyId().equals(proxy) ? " (#) " : "")), + Placeholder.component("proxy", Component.text(proxy)), + Placeholder.component("players", Component.text(players)) + ) + .appendNewline())); sendMessage(issuer, MiniMessage.miniMessage().deserialize(message, Placeholder.component("data", builder))); From 32826d843c4ab859b7addee20055c561429e0cfc Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 18 Apr 2024 12:48:00 +0400 Subject: [PATCH 61/64] finsh up command system --- .../api/config/RedisBungeeConfiguration.java | 22 +++++++- .../api/config/loaders/ConfigLoader.java | 37 +++++++++++- RedisBungee-API/src/main/resources/config.yml | 56 ++++++++++++++++++- .../redisbungee/commands/CommandLoader.java | 13 ++++- .../commands/CommandRedisBungee.java | 2 +- .../commands/legacy/CommandFind.java | 34 +++++++++++ .../commands/legacy/CommandGList.java | 34 +++++++++++ .../commands/legacy/CommandIp.java | 34 +++++++++++ .../commands/legacy/CommandLastSeen.java | 34 +++++++++++ .../commands/legacy/CommandPProxy.java | 33 +++++++++++ .../commands/legacy/CommandPlist.java | 35 ++++++++++++ .../commands/legacy/CommandSendToAll.java | 33 +++++++++++ .../commands/legacy/CommandServerId.java | 33 +++++++++++ .../commands/legacy/CommandServerIds.java | 36 ++++++++++++ .../LegacyRedisBungeeCommands.java | 35 +++++++----- 15 files changed, 448 insertions(+), 23 deletions(-) create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java create mode 100644 RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java rename RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/{ => legacy}/LegacyRedisBungeeCommands.java (86%) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index b162c6f..e93d2fb 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -13,6 +13,7 @@ package com.imaginarycode.minecraft.redisbungee.api.config; import com.google.common.collect.ImmutableList; import com.google.common.net.InetAddresses; +import javax.annotation.Nullable; import java.net.InetAddress; import java.util.List; @@ -25,8 +26,10 @@ public class RedisBungeeConfiguration { private final boolean handleReconnectToLastServer; private final boolean handleMotd; + private final CommandsConfiguration commandsConfiguration; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd) { + + public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, CommandsConfiguration commandsConfiguration) { this.proxyId = proxyId; ImmutableList.Builder addressBuilder = ImmutableList.builder(); for (String s : exemptAddresses) { @@ -36,6 +39,7 @@ public class RedisBungeeConfiguration { this.kickWhenOnline = kickWhenOnline; this.handleReconnectToLastServer = handleReconnectToLastServer; this.handleMotd = handleMotd; + this.commandsConfiguration = commandsConfiguration; } public String getProxyId() { @@ -58,5 +62,21 @@ public class RedisBungeeConfiguration { return this.handleReconnectToLastServer; } + public record CommandsConfiguration(boolean redisbungeeEnabled, boolean redisbungeeLegacyEnabled, + @Nullable LegacySubCommandsConfiguration legacySubCommandsConfiguration) { + } + + public record LegacySubCommandsConfiguration(boolean findEnabled, boolean glistEnabled, boolean ipEnabled, + boolean lastseenEnabled, boolean plistEnabled, boolean pproxyEnabled, + boolean sendtoallEnabled, boolean serveridEnabled, + boolean serveridsEnabled, boolean installFind, boolean installGlist, boolean installIp, + boolean installLastseen, boolean installPlist, boolean installPproxy, + boolean installSendtoall, boolean installServerid, + boolean installServerids) { + } + + public CommandsConfiguration commandsConfiguration() { + return commandsConfiguration; + } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index cba8576..fed44c8 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -86,7 +86,42 @@ public interface ConfigLoader extends GenericConfigLoader { plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); plugin.logInfo("handle motd: {}", handleMotd); - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd); + + // commands + boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(); + boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(); + + boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(); + boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(); + boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(); + boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(); + boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(); + boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(); + boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(); + boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(); + boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(); + + boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(); + boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(); + boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(); + boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(); + boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(); + boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(); + boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(); + boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(); + boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(); + + + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, new RedisBungeeConfiguration.CommandsConfiguration( + redisBungeeEnabled, redisBungeeLegacyEnabled, + new RedisBungeeConfiguration.LegacySubCommandsConfiguration( + findEnabled, glistEnabled, ipEnabled, + lastseenEnabled, pListEnabled, pproxyEnabled, + sendToAllEnabled, serverIdEnabled, serverIdsEnabled, + installFind, installGlist, installIp, + installLastseen, installPlist, installPproxy, + installSendToAll, installServerid, installServerIds) + )); Summoner summoner; RedisBungeeMode redisBungeeMode; if (useSSL) { diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index 628f6be..a7fea36 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -50,7 +50,7 @@ proxy-id: "proxy-1" # which will break compatibility with old plugins that uses RedisBungee JedisPool # so to mitigate this issue, RedisBungee will create an JedisPool for compatibility reasons. # disabled by default -# ignored when cluster mode is enabled +# Automatically disabled when cluster mode is enabled enable-jedis-pool-compatibility: false # max connections for the compatibility pool @@ -69,12 +69,64 @@ handle-motd: true # A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic # restart scripts. -# ignored if handle-motd is disabled. +# Automatically disabled if handle-motd is disabled. exempt-ip-addresses: [] # disabled by default # RedisBungee will attempt to connect player to last server that was stored. reconnect-to-last-server: false +# For redis bungee legacy commands +# either can be run using '/rbl glist' for example +# or if 'install' is set to true '/glist' can be used. +# 'install' also overrides the proxy installed commands +# +# In legacy commands each command got it own permissions since they had it own permission pre new command system, +# so it's also applied to subcommands in '/rbl'. +commands: + # Permission redisbungee.legacy.use + redisbungee-legacy: + enabled: false + subcommands: + # Permission redisbungee.command.glist + glist: + enabled: false + install: false + # Permission redisbungee.command.find + find: + enabled: false + install: false + # Permission redisbungee.command.lastseen + lastseen: + enabled: false + install: false + # Permission redisbungee.command.ip + ip: + enabled: false + install: false + # Permission redisbungee.command.pproxy + pproxy: + enabled: false + install: false + # Permission redisbungee.command.sendtoall + sendtoall: + enabled: false + install: false + # Permission redisbungee.command.serverid + serverid: + enabled: false + install: false + # Permission redisbungee.command.serverids + serverids: + enabled: false + install: false + # Permission redisbungee.command.plist + plist: + enabled: false + install: false + # Permission redisbungee.command.use + redisbungee: + enabled: true + # Config version DO NOT CHANGE!!!! config-version: 2 diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java index f883839..5346be9 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandLoader.java @@ -13,12 +13,19 @@ package com.imaginarycode.minecraft.redisbungee.commands; import co.aikar.commands.CommandManager; import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin; +import com.imaginarycode.minecraft.redisbungee.commands.legacy.LegacyRedisBungeeCommands; + public class CommandLoader { public static void initCommands(CommandManager commandManager, RedisBungeePlugin plugin) { - commandManager.registerCommand(new CommandRedisBungee(plugin)); - // todo: config options to disable each command - commandManager.registerCommand(new LegacyRedisBungeeCommands(plugin)); + var commandsConfiguration = plugin.configuration().commandsConfiguration(); + if (commandsConfiguration.redisbungeeEnabled()) { + commandManager.registerCommand(new CommandRedisBungee(plugin)); + } + if (commandsConfiguration.redisbungeeLegacyEnabled()) { + commandManager.registerCommand(new LegacyRedisBungeeCommands(commandManager,plugin)); + } + } } diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java index 4684970..a3cc3ac 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/CommandRedisBungee.java @@ -27,7 +27,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import java.util.Date; @CommandAlias("rb|redisbungee") -@CommandPermission("redisbungee.use") +@CommandPermission("redisbungee.command.use") public class CommandRedisBungee extends AdventureBaseCommand { private final RedisBungeePlugin plugin; diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java new file mode 100644 index 0000000..0a73d36 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandFind.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("find|rfind") +@CommandPermission("redisbungee.command.find") +public class CommandFind extends AdventureBaseCommand { + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandFind(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void find(CommandIssuer issuer, String[] args) { + rootCommand.find(issuer, args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java new file mode 100644 index 0000000..54cc985 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandGList.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("glist|rglist") +@CommandPermission("redisbungee.command.glist") +public class CommandGList extends AdventureBaseCommand { + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandGList(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void gList(CommandIssuer issuer, String[] args) { + rootCommand.gList(issuer, args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java new file mode 100644 index 0000000..410ae91 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandIp.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("ip|playerip|rip|rplayerip") +@CommandPermission("redisbungee.command.ip") +public class CommandIp extends AdventureBaseCommand { + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandIp(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + + @Default + public void ip(CommandIssuer issuer, String[] args) { + this.rootCommand.ip(issuer, args); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java new file mode 100644 index 0000000..f44ea8e --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandLastSeen.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("lastseen|rlastseend") +@CommandPermission("redisbungee.command.lastseen") +public class CommandLastSeen extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandLastSeen(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void lastSeen(CommandIssuer issuer, String[] args) { + this.rootCommand.lastSeen(issuer,args); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java new file mode 100644 index 0000000..70a949c --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPProxy.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("pproxy") +@CommandPermission("redisbungee.command.pproxy") +public class CommandPProxy extends AdventureBaseCommand { + private final LegacyRedisBungeeCommands rootCommand; + + public CommandPProxy(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void playerProxy(CommandIssuer issuer, String[] args) { + this.rootCommand.playerProxy(issuer,args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java new file mode 100644 index 0000000..04e1609 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandPlist.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("plist|rplist") +@CommandPermission("redisbungee.command.plist") +public class CommandPlist extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandPlist(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void playerList(CommandIssuer issuer, String[] args) { + this.rootCommand.playerList(issuer, args); + } + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java new file mode 100644 index 0000000..ad9e1da --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandSendToAll.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("sendtoall|rsendtoall") +@CommandPermission("redisbungee.command.sendtoall") +public class CommandSendToAll extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandSendToAll(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + @Default + public void sendToAll(CommandIssuer issuer, String[] args) { + this.rootCommand.sendToAll(issuer, args); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java new file mode 100644 index 0000000..c62c2f5 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerId.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("serverid|rserverid") +@CommandPermission("redisbungee.command.serverid") +public class CommandServerId extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandServerId(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + @Default + public void serverId(CommandIssuer issuer) { + this.rootCommand.serverId(issuer); + } +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java new file mode 100644 index 0000000..85b53b7 --- /dev/null +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/CommandServerIds.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013-present RedisBungee contributors + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ + +package com.imaginarycode.minecraft.redisbungee.commands.legacy; + +import co.aikar.commands.CommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import com.imaginarycode.minecraft.redisbungee.commands.utils.AdventureBaseCommand; + +@CommandAlias("serverids|rserverids") +@CommandPermission("redisbungee.command.serverids") +public class CommandServerIds extends AdventureBaseCommand { + + + private final LegacyRedisBungeeCommands rootCommand; + + public CommandServerIds(LegacyRedisBungeeCommands rootCommand) { + this.rootCommand = rootCommand; + } + + @Default + public void serverIds(CommandIssuer issuer) { + this.rootCommand.serverIds(issuer); + } + + +} diff --git a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/LegacyRedisBungeeCommands.java similarity index 86% rename from RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java rename to RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/LegacyRedisBungeeCommands.java index 930c7e9..6253e96 100644 --- a/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/LegacyRedisBungeeCommands.java +++ b/RedisBungee-Commands/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/legacy/LegacyRedisBungeeCommands.java @@ -8,9 +8,10 @@ * http://www.eclipse.org/legal/epl-v10.html */ -package com.imaginarycode.minecraft.redisbungee.commands; +package com.imaginarycode.minecraft.redisbungee.commands.legacy; import co.aikar.commands.CommandIssuer; +import co.aikar.commands.CommandManager; import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Subcommand; @@ -29,15 +30,27 @@ import java.util.Set; import java.util.TreeSet; import java.util.UUID; -@CommandAlias("rbl|redisbungeeleagacy") -@CommandPermission("redisbungee.leagacy.use") +@CommandAlias("rbl|redisbungeelegacy") +@CommandPermission("redisbungee.legacy.use") public class LegacyRedisBungeeCommands extends AdventureBaseCommand { - private final RedisBungeePlugin plugin; - public LegacyRedisBungeeCommands(RedisBungeePlugin plugin) { + public LegacyRedisBungeeCommands(CommandManager commandManager, RedisBungeePlugin plugin) { this.plugin = plugin; + var commands = plugin.configuration().commandsConfiguration().legacySubCommandsConfiguration(); + if (!plugin.configuration().commandsConfiguration().redisbungeeLegacyEnabled()) throw new IllegalStateException("someone tried to init me while disabled!"); + if (commands == null) throw new NullPointerException("commands config is null!!"); + + if (commands.installGlist()) commandManager.registerCommand(new CommandGList(this)); + if (commands.installFind()) commandManager.registerCommand(new CommandFind(this)); + if (commands.installIp()) commandManager.registerCommand(new CommandIp(this)); + if (commands.installLastseen()) commandManager.registerCommand(new CommandLastSeen(this)); + if (commands.installPlist()) commandManager.registerCommand(new CommandPlist(this)); + if (commands.installPproxy()) commandManager.registerCommand(new CommandPProxy(this)); + if (commands.installSendtoall()) commandManager.registerCommand(new CommandSendToAll(this)); + if (commands.installServerid()) commandManager.registerCommand(new CommandServerId(this)); + if (commands.installServerids()) commandManager.registerCommand(new CommandServerIds(this)); } private static final Component NO_PLAYER_SPECIFIED = @@ -52,7 +65,6 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { } @Subcommand("glist") - @CommandAlias("glist") @CommandPermission("redisbungee.command.glist") public void gList(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { @@ -80,8 +92,8 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { }); } + @Subcommand("find") - @CommandAlias("find") @CommandPermission("redisbungee.command.find") public void find(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { @@ -107,7 +119,6 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { } @Subcommand("lastseen") - @CommandAlias("lastseen") @CommandPermission("redisbungee.command.lastseen") public void lastSeen(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { @@ -139,7 +150,6 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { } @Subcommand("ip") - @CommandAlias("ip") @CommandPermission("redisbungee.command.ip") public void ip(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { @@ -163,7 +173,6 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { } @Subcommand("pproxy") - @CommandAlias("pproxy") @CommandPermission("redisbungee.command.pproxy") public void playerProxy(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { @@ -188,7 +197,6 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { } @Subcommand("sendtoall") - @CommandAlias("sendtoall") @CommandPermission("redisbungee.command.sendtoall") public void sendToAll(CommandIssuer issuer, String[] args) { if (args.length > 0) { @@ -203,22 +211,19 @@ public class LegacyRedisBungeeCommands extends AdventureBaseCommand { } @Subcommand("serverid") - @CommandAlias("serverid") @CommandPermission("redisbungee.command.serverid") public void serverId(CommandIssuer issuer) { sendMessage(issuer, Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW)); } @Subcommand("serverids") - @CommandAlias("serverids") @CommandPermission("redisbungee.command.serverids") public void serverIds(CommandIssuer issuer) { - sendMessage(issuer, Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); + sendMessage(issuer, Component.text("All Proxies IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW)); } @Subcommand("plist") - @CommandAlias("plist") @CommandPermission("redisbungee.command.plist") public void playerList(CommandIssuer issuer, String[] args) { plugin.executeAsync(() -> { From 65ac4659158c544981742d24d2124d2c0c569c0f Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Thu, 18 Apr 2024 14:23:17 +0400 Subject: [PATCH 62/64] add default values to commands config --- .../api/config/loaders/ConfigLoader.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index fed44c8..a2e3791 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -88,28 +88,28 @@ public interface ConfigLoader extends GenericConfigLoader { // commands - boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(); - boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(); + boolean redisBungeeEnabled = node.getNode("commands", "redisbungee", "enabled").getBoolean(true); + boolean redisBungeeLegacyEnabled =node.getNode("commands", "redisbungee-legacy", "enabled").getBoolean(false); - boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(); - boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(); - boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(); - boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(); - boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(); - boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(); - boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(); - boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(); - boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(); + boolean glistEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "enabled").getBoolean(false); + boolean findEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "enabled").getBoolean(false); + boolean lastseenEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "enabled").getBoolean(false); + boolean ipEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "enabled").getBoolean(false); + boolean pproxyEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "enabled").getBoolean(false); + boolean sendToAllEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "enabled").getBoolean(false); + boolean serverIdEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "enabled").getBoolean(false); + boolean serverIdsEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "enabled").getBoolean(false); + boolean pListEnabled = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "enabled").getBoolean(false); - boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(); - boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(); - boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(); - boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(); - boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(); - boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(); - boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(); - boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(); - boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(); + boolean installGlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "glist", "install").getBoolean(false); + boolean installFind = node.getNode("commands", "redisbungee-legacy", "subcommands", "find", "install").getBoolean(false); + boolean installLastseen = node.getNode("commands", "redisbungee-legacy", "subcommands", "lastseen", "install").getBoolean(false); + boolean installIp = node.getNode("commands", "redisbungee-legacy", "subcommands", "ip", "install").getBoolean(false); + boolean installPproxy = node.getNode("commands", "redisbungee-legacy", "subcommands", "pproxy", "install").getBoolean(false); + boolean installSendToAll = node.getNode("commands", "redisbungee-legacy", "subcommands", "sendtoall", "install").getBoolean(false); + boolean installServerid = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverid", "install").getBoolean(false); + boolean installServerIds = node.getNode("commands", "redisbungee-legacy", "subcommands", "serverids", "install").getBoolean(false); + boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false); RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, new RedisBungeeConfiguration.CommandsConfiguration( From 97e6b5c9449dba3fdecade1cfbd087a7f33a1096 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Sun, 21 Apr 2024 16:00:03 +0400 Subject: [PATCH 63/64] update config / readme --- README.md | 208 +----------------- RedisBungee-API/src/main/resources/config.yml | 16 +- 2 files changed, 14 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index bbe101c..9d64fe5 100644 --- a/README.md +++ b/README.md @@ -1,220 +1,14 @@ -# RedisBungee fork By Limework - -~~*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do -it, tell mojang to implement transfer packet*.~~ - -In minecraft snapshot 24W03A, mojang introduced transfer packets in the protocol but, we have to wait and -see how Proxies developers will implement the apis, additionally how to implement proxy transfers in redisbungee - -[Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/) +# RedisBungee Limework's Fork The original project of RedisBungee is no longer maintained, so we have forked the plugin. RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/) to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) or [Velocity*](https://github.com/PaperMC/Velocity) proxies -Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be -unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40) - ## Downloads [![](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/plugin/redisbungee) -or from github releases - -https://github.com/ProxioDev/RedisBungee/releases - -## notes - -If you are looking to use Original RedisBungee without a change to internals, -with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and -java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/) -as its last version before internal changes. please note that you will not get support for any old builds unless -critical bugs effecting both 0.6.5 and 0.7.0 or above. - -SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.87700/) - -## Supported Redis versions - -| Redis version | Supported | -|:-------------:|:---------:| -| 1.x.x | ✖ | -| 2.x.x | ✖ | -| 3.x.x | ✖ | -| 4.x.x | ✖ | -| 5.x.x | ✖ | -| below 6.2 | ✖ | -| 6.2 or above | ✔ | -| 7.x.x | ✔ | - -## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) - -RedisBungee is distributed as a [Gradle](https://gradle.org/) project. - -By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee) - -# Setup jitpack repository - -## maven - -```xml - - - jitpack.io - https://jitpack.io - - -``` - -## gradle (kotlin dsl) - -```kotlin -repositories { - maven("https://jitpack.io/") -} -``` - -# [BungeeCord](https://github.com/SpigotMC/BungeeCord) - -add this in your project dependencies - -## maven - -```xml - - com.github.proxiodev.redisbungee - RedisBungee-Bungee - VERSION - provided - - - -``` - -## gradle (kotlin dsl) - -``` -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0") - -// USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOACTION AND REMOVE THE ABOVE STATEMENT -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Bungee:0.11.0:all") { - exclude("redis.clients", "jedis") -} -``` - -then in your project plugin.yml add `RedisBungee` to `depends` like this - -```yaml -name: "yourplugin" -main: your.loaders.class -version: 1.0.0-SNAPSHOT -author: idk -depends: [ RedisBungee ] -``` - -## [Velocity](https://github.com/PaperMC/Velocity) - -## maven - -```xml - - com.github.proxiodev.redisbungee - RedisBungee-Velocity - VERSION - provided - - -``` - -## gradle (kotlin dsl) - -``` -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0") - -// USE THIS IF YOU WANT TO USE INCLUDED JEDIS LIB BECAUSE OF RELOACTION AND REMOVE THE ABOVE STATEMENT -implementation("com.github.ProxioDev.redisbungee:RedisBungee-Velocity:0.11.0:all") { - exclude("redis.clients", "jedis") -} -``` - -then to make your plugin depends on RedisBungee, make sure your plugin class Annotation -have `@Dependency(id = "redisbungee")` like this - -```java -@Plugin( - id = "myplugin", - name = "My Plugin", - version = "0.1.0-beta", - dependencies = { - @Dependency(id = "redisbungee") - } -) -public class PluginMainClass { - -} -``` - -## Getting the latest commits to your code - -If you want to use the latest commits without waiting for releases. -first, install it to your maven local repo - -```bash -git clone https://github.com/ProxioDev/RedisBungee.git -cd RedisBungee -./gradlew publishToMavenLocal -``` - -then use any of these in your project. - -```xml - - com.imaginarycode.minecraft - RedisBungee-Bungee - VERSION - provided - - -``` - -```xml - - com.imaginarycode.minecraft - RedisBungee-Velocity - VERSION - provided - - -``` - -## Javadocs - -* API: https://ci.limework.net/RedisBungee/RedisBungee-API/build/docs/javadoc/ -* Velocity: https://ci.limework.net/RedisBungee/RedisBungee-Velocity/build/docs/javadoc/ -* Bungeecord: https://ci.limework.net/RedisBungee/RedisBungee-Bungee/build/docs/javadoc/ - -## Configuration - -**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The -default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is -saved when the plugin first starts. - -## compatibility with original RedisBungee in Bungeecord ecosystem - -This fork ensures compatibility with old plugins, so it should work as drop replacement, -but since Api has been split from the platform there some changes that have to be done, so your plugin might not work -if: - -* there is none at the moment, please report any findings at the issue page. - -Cluster mode compatibility in version 0.8.0: - -If you are using static legacy method `RedisBungee#getPool()` it might fail in: - -* if Cluster mode is enabled, due fact its Uses different classes -* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than - Jedis - ## Support open an issue with question button diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index a7fea36..edbf81e 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -1,5 +1,12 @@ # RedisBungee configuration file. -# you need Redis so get Redis from http://redis.io/ or install it from your package manager +# Notice: +# Redis 7.2.4 is last free and open source Redis version after license change +# https://download.redis.io/releases/redis-7.2.4.tar.gz which you have to compile yourself, +# unless your package manager still provide it. +# Here is The alternatives +# - 'ValKey' By linux foundation https://valkey.io/download/ +# - 'KeyDB' by Snapchat inc https://docs.keydb.dev/docs/download/ + # The Redis server you will use. # these settings are ignored when cluster mode is enabled. @@ -41,9 +48,12 @@ max-redis-connections: 10 # Keep note that SSL/TLS connections will decrease redis performance so use it when needed. useSSL: false +# An identifier for this network, which helps to separate redisbungee instances on same redis instance. +# You can use environment variable 'REDISBUNGEE_NETWORK_ID' to override +network-id: "main" + # An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank. -# Tip: you can set proxy id using Environment variable REDISBUNGEE_PROXY_ID which will override this config option -# before launch +# You can set Environment variable 'REDISBUNGEE_PROXY_ID' to override proxy-id: "proxy-1" # since RedisBungee Internally now uses JedisPooled instead of Jedis, JedisPool. From 91c3845b2e1a72ca68779bbd26035e838f8b68f4 Mon Sep 17 00:00:00 2001 From: mohammed jasem alaajel Date: Mon, 22 Apr 2024 16:27:14 +0400 Subject: [PATCH 64/64] add network id --- .../redisbungee/api/PlayerDataManager.java | 44 ++++++++++--------- .../redisbungee/api/ProxyDataManager.java | 39 +++++++++------- .../api/config/RedisBungeeConfiguration.java | 8 +++- .../api/config/loaders/ConfigLoader.java | 20 ++++++++- RedisBungee-API/src/main/resources/config.yml | 4 +- RedisBungee-Bungee/build.gradle.kts | 1 + RedisBungee-Velocity/build.gradle.kts | 1 + 7 files changed, 78 insertions(+), 39 deletions(-) diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java index 84d46a7..a6d600a 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/PlayerDataManager.java @@ -45,10 +45,14 @@ public abstract class PlayerDataManager> serverToPlayersCache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(this::serversToPlayersBuilder); private final UnifiedJedis unifiedJedis; + private final String proxyId; + private final String networkId; public PlayerDataManager(RedisBungeePlugin

plugin) { this.plugin = plugin; this.unifiedJedis = plugin.proxyDataManager().unifiedJedis(); + this.proxyId = plugin.proxyDataManager().proxyId(); + this.networkId = plugin.proxyDataManager().networkId(); } // handle network wide @@ -84,7 +88,7 @@ public abstract class PlayerDataManager data = new HashMap<>(); data.put("server", server); data.put("last-server", server); - unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", data); + unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", data); } protected void addPlayer(final UUID uuid, final InetAddress inetAddress) { Map redisData = new HashMap<>(); redisData.put("last-online", String.valueOf(0)); - redisData.put("proxy", plugin.configuration().getProxyId()); + redisData.put("proxy", this.proxyId); redisData.put("ip", inetAddress.getHostAddress()); - unifiedJedis.hset("redis-bungee::player::" + uuid + "::data", redisData); + unifiedJedis.hset("redis-bungee::" + this.networkId + "::player::" + uuid + "::data", redisData); JSONObject data = new JSONObject(); - data.put("proxy", plugin.configuration().getProxyId()); + data.put("proxy", this.proxyId); data.put("uuid", uuid); plugin.proxyDataManager().sendChannelMessage("redisbungee-player-join", data.toString()); plugin.fireEvent(plugin.createPlayerJoinedNetworkEvent(uuid)); @@ -173,10 +177,10 @@ public abstract class PlayerDataManager doPooledPipeline(Pipeline pipeline) { HashMap> responses = new HashMap<>(); for (UUID uuid : uuids) { - responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server")); + responses.put(uuid, pipeline.hget("redis-bungee::" + networkId + "::player::" + uuid + "::data", "server")); } pipeline.sync(); responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); @@ -252,7 +256,7 @@ public abstract class PlayerDataManager clusterPipeline(ClusterPipeline pipeline) { HashMap> responses = new HashMap<>(); for (UUID uuid : uuids) { - responses.put(uuid, pipeline.hget("redis-bungee::player::" + uuid + "::data", "server")); + responses.put(uuid, pipeline.hget("redis-bungee::" + networkId + "::player::" + uuid + "::data", "server")); } pipeline.sync(); responses.forEach((uuid, response) -> builder.put(response.get(), uuid)); diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java index 5892244..05e608c 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/ProxyDataManager.java @@ -24,6 +24,7 @@ import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.Heartbeat import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.PubSubPayloadSerializer; import com.imaginarycode.minecraft.redisbungee.api.payloads.proxy.gson.RunCommandPayloadSerializer; import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisPipelineTask; +import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil; import redis.clients.jedis.*; import redis.clients.jedis.params.XAddParams; import redis.clients.jedis.params.XReadParams; @@ -38,7 +39,6 @@ import static com.google.common.base.Preconditions.checkArgument; public abstract class ProxyDataManager implements Runnable { - private static final String STREAM_ID = "redisbungee-stream"; private static final int MAX_ENTRIES = 10000; private final AtomicBoolean closed = new AtomicBoolean(false); @@ -49,8 +49,12 @@ public abstract class ProxyDataManager implements Runnable { // Proxy id, heartbeat (unix epoch from instant), players as int private final ConcurrentHashMap heartbeats = new ConcurrentHashMap<>(); + private final String networkId; + private final String proxyId; + private final String STREAM_ID; + // This different from proxy id, just to detect if there is duplicate proxy using same proxy id private final UUID dataManagerUUID = UUID.randomUUID(); @@ -63,6 +67,8 @@ public abstract class ProxyDataManager implements Runnable { this.proxyId = this.plugin.configuration().getProxyId(); this.unifiedJedis = plugin.getSummoner().obtainResource(); this.destroyProxyMembers(); + this.networkId = plugin.configuration().networkId(); + this.STREAM_ID = "network-" + this.networkId + "-redisbungee-stream"; } public abstract Set getLocalOnlineUUIDs(); @@ -106,7 +112,7 @@ public abstract class ProxyDataManager implements Runnable { public Set doPooledPipeline(Pipeline pipeline) { HashSet>> responses = new HashSet<>(); for (String proxyId : proxiesIds()) { - responses.add(pipeline.smembers("redisbungee::proxies::" + proxyId + "::online-players")); + responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players")); } pipeline.sync(); HashSet uuids = new HashSet<>(); @@ -122,7 +128,7 @@ public abstract class ProxyDataManager implements Runnable { public Set clusterPipeline(ClusterPipeline pipeline) { HashSet>> responses = new HashSet<>(); for (String proxyId : proxiesIds()) { - responses.add(pipeline.smembers("redisbungee::proxies::" + proxyId + "::online-players")); + responses.add(pipeline.smembers("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players")); } pipeline.sync(); HashSet uuids = new HashSet<>(); @@ -207,8 +213,8 @@ public abstract class ProxyDataManager implements Runnable { for (UUID uuid : add) { addString.add(uuid.toString()); } - pipeline.srem("redisbungee::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{})); - pipeline.sadd("redisbungee::proxies::" + proxyId() + "::online-players", addString.toArray(new String[]{})); + pipeline.srem("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", addString.toArray(new String[]{})); pipeline.sync(); return null; } @@ -223,8 +229,8 @@ public abstract class ProxyDataManager implements Runnable { for (UUID uuid : add) { addString.add(uuid.toString()); } - pipeline.srem("redisbungee::proxies::" + proxyId() + "::online-players", removeString.toArray(new String[]{})); - pipeline.sadd("redisbungee::proxies::" + proxyId() + "::online-players", addString.toArray(new String[]{})); + pipeline.srem("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", removeString.toArray(new String[]{})); + pipeline.sadd("redisbungee::" + networkId + "::proxies::" + proxyId + "::online-players", addString.toArray(new String[]{})); pipeline.sync(); return null; } @@ -236,12 +242,12 @@ public abstract class ProxyDataManager implements Runnable { } - // handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~10 seconds + // handle dead proxies "THAT" Didn't send death payload but considered dead due TIMEOUT ~30 seconds final Set deadProxies = new HashSet<>(); for (Map.Entry stringHeartbeatDataEntry : this.heartbeats.entrySet()) { String id = stringHeartbeatDataEntry.getKey(); long heartbeat = stringHeartbeatDataEntry.getValue().heartbeat(); - if (Instant.now().getEpochSecond() - heartbeat > 10) { + if (Instant.now().getEpochSecond() - heartbeat > RedisUtil.PROXY_TIMEOUT) { deadProxies.add(id); cleanProxy(id); } @@ -251,7 +257,7 @@ public abstract class ProxyDataManager implements Runnable { @Override public Void doPooledPipeline(Pipeline pipeline) { for (String deadProxy : deadProxies) { - pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players"); + pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players"); } pipeline.sync(); return null; @@ -260,7 +266,7 @@ public abstract class ProxyDataManager implements Runnable { @Override public Void clusterPipeline(ClusterPipeline pipeline) { for (String deadProxy : deadProxies) { - pipeline.del("redisbungee::proxies::" + deadProxy + "::online-players"); + pipeline.del("redisbungee::" + networkId + "::proxies::" + deadProxy + "::online-players"); } pipeline.sync(); return null; @@ -302,19 +308,19 @@ public abstract class ProxyDataManager implements Runnable { public void addPlayer(UUID uuid) { - this.unifiedJedis.sadd("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString()); + this.unifiedJedis.sadd("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players", uuid.toString()); } public void removePlayer(UUID uuid) { - this.unifiedJedis.srem("redisbungee::proxies::" + this.proxyId + "::online-players", uuid.toString()); + this.unifiedJedis.srem("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players", uuid.toString()); } private void destroyProxyMembers() { - unifiedJedis.del("redisbungee::proxies::" + this.proxyId + "::online-players"); + unifiedJedis.del("redisbungee::" + this.networkId + "::proxies::" + this.proxyId + "::online-players"); } private Set getProxyMembers(String proxyId) { - Set uuidsStrings = unifiedJedis.smembers("redisbungee::proxies::" + proxyId + "::online-players"); + Set uuidsStrings = unifiedJedis.smembers("redisbungee::" + this.networkId + "::proxies::" + proxyId + "::online-players"); HashSet uuids = new HashSet<>(); for (String proxyMember : uuidsStrings) { uuids.add(UUID.fromString(proxyMember)); @@ -387,4 +393,7 @@ public abstract class ProxyDataManager implements Runnable { return unifiedJedis; } + public String networkId() { + return networkId; + } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java index e93d2fb..f76fb39 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/RedisBungeeConfiguration.java @@ -27,9 +27,10 @@ public class RedisBungeeConfiguration { private final boolean handleMotd; private final CommandsConfiguration commandsConfiguration; + private final String networkId; - public RedisBungeeConfiguration(String proxyId, List exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, CommandsConfiguration commandsConfiguration) { + public RedisBungeeConfiguration(String networkId, String proxyId, List exemptAddresses, boolean kickWhenOnline, boolean handleReconnectToLastServer, boolean handleMotd, CommandsConfiguration commandsConfiguration) { this.proxyId = proxyId; ImmutableList.Builder addressBuilder = ImmutableList.builder(); for (String s : exemptAddresses) { @@ -40,6 +41,7 @@ public class RedisBungeeConfiguration { this.handleReconnectToLastServer = handleReconnectToLastServer; this.handleMotd = handleMotd; this.commandsConfiguration = commandsConfiguration; + this.networkId = networkId; } public String getProxyId() { @@ -79,4 +81,8 @@ public class RedisBungeeConfiguration { public CommandsConfiguration commandsConfiguration() { return commandsConfiguration; } + + public String networkId() { + return networkId; + } } diff --git a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java index a2e3791..a73b6ef 100644 --- a/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java +++ b/RedisBungee-API/src/main/java/com/imaginarycode/minecraft/redisbungee/api/config/loaders/ConfigLoader.java @@ -46,6 +46,7 @@ public interface ConfigLoader extends GenericConfigLoader { final boolean kickWhenOnline = node.getNode("kick-when-online").getBoolean(true); String redisPassword = node.getNode("redis-password").getString(""); String redisUsername = node.getNode("redis-username").getString(""); + String networkId = node.getNode("network-id").getString("main"); String proxyId = node.getNode("proxy-id").getString("proxy-1"); final int maxConnections = node.getNode("max-redis-connections").getInt(10); @@ -70,6 +71,13 @@ public interface ConfigLoader extends GenericConfigLoader { plugin.logInfo("Overriding current configured proxy id {} and been set to {} by Environment variable REDISBUNGEE_PROXY_ID", proxyId, proxyIdFromEnv); proxyId = proxyIdFromEnv; } + + String networkIdFromEnv = System.getenv("REDISBUNGEE_NETWORK_ID"); + if (networkIdFromEnv != null) { + plugin.logInfo("Overriding current configured network id {} and been set to {} by Environment variable REDISBUNGEE_NETWORK_ID", networkId, networkIdFromEnv); + networkId = networkIdFromEnv; + } + // Configuration sanity checks. if (proxyId == null || proxyId.isEmpty()) { String genId = UUID.randomUUID().toString(); @@ -81,6 +89,16 @@ public interface ConfigLoader extends GenericConfigLoader { } else { plugin.logInfo("Loaded proxy id " + proxyId); } + + if (networkId.isEmpty()) { + networkId = "main"; + plugin.logWarn("network id was empty and replaced with 'main'"); + } + + plugin.logInfo("Loaded network id " + networkId); + + + boolean reconnectToLastServer = node.getNode("reconnect-to-last-server").getBoolean(); boolean handleMotd = node.getNode("handle-motd").getBoolean(true); plugin.logInfo("handle reconnect to last server: {}", reconnectToLastServer); @@ -112,7 +130,7 @@ public interface ConfigLoader extends GenericConfigLoader { boolean installPlist = node.getNode("commands", "redisbungee-legacy", "subcommands", "plist", "install").getBoolean(false); - RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, new RedisBungeeConfiguration.CommandsConfiguration( + RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(networkId, proxyId, exemptAddresses, kickWhenOnline, reconnectToLastServer, handleMotd, new RedisBungeeConfiguration.CommandsConfiguration( redisBungeeEnabled, redisBungeeLegacyEnabled, new RedisBungeeConfiguration.LegacySubCommandsConfiguration( findEnabled, glistEnabled, ipEnabled, diff --git a/RedisBungee-API/src/main/resources/config.yml b/RedisBungee-API/src/main/resources/config.yml index edbf81e..2e97d89 100644 --- a/RedisBungee-API/src/main/resources/config.yml +++ b/RedisBungee-API/src/main/resources/config.yml @@ -8,7 +8,7 @@ # - 'KeyDB' by Snapchat inc https://docs.keydb.dev/docs/download/ -# The Redis server you will use. +# The 'Redis', 'ValKey', 'KeyDB' server you will use. # these settings are ignored when cluster mode is enabled. redis-server: 127.0.0.1 redis-port: 6379 @@ -19,7 +19,7 @@ cluster-mode-enabled: false # FORMAT: # redis-cluster-servers: -# - host: 127.0.0.1 +# - host: 127.0.0.1` # port: 2020 # - host: 127.0.0.1 # port: 2021 diff --git a/RedisBungee-Bungee/build.gradle.kts b/RedisBungee-Bungee/build.gradle.kts index a8a9721..217be17 100644 --- a/RedisBungee-Bungee/build.gradle.kts +++ b/RedisBungee-Bungee/build.gradle.kts @@ -41,6 +41,7 @@ tasks { runWaterfall { waterfallVersion("1.20") environment["REDISBUNGEE_PROXY_ID"] = "bungeecord-1" + environment["REDISBUNGEE_NETWORK_ID"] = "dev" } compileJava { options.encoding = Charsets.UTF_8.name() diff --git a/RedisBungee-Velocity/build.gradle.kts b/RedisBungee-Velocity/build.gradle.kts index 390a9d7..fb9bc22 100644 --- a/RedisBungee-Velocity/build.gradle.kts +++ b/RedisBungee-Velocity/build.gradle.kts @@ -49,6 +49,7 @@ tasks { runVelocity { velocityVersion("3.3.0-SNAPSHOT") environment["REDISBUNGEE_PROXY_ID"] = "velocity-1" + environment["REDISBUNGEE_NETWORK_ID"] = "dev" } compileJava { options.encoding = Charsets.UTF_8.name()