From 8df8c6add868cb1b0bf1446c46ab0f5d6272ee41 Mon Sep 17 00:00:00 2001 From: GiantLuigi4 <49770992+GiantLuigi4@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:19:48 -0400 Subject: [PATCH] ssr prototyping, no network logic hooked up yet --- build.gradle | 7 +- libs/mcef-2.x.jar | Bin 200767 -> 216637 bytes src/main/java/net/montoyo/wd/WebDisplays.java | 108 ++-- .../wd/client/renderers/ScreenRenderer.java | 5 +- .../montoyo/wd/entity/TileEntityScreen.java | 503 +++++++++--------- .../net/client_bound/S2CMessageEnableSSR.java | 14 + .../net/montoyo/wd/remote/BrowserGen.java | 69 +++ .../net/montoyo/wd/remote/IRemoteBrowser.java | 7 + .../net/montoyo/wd/remote/IWDBrowser.java | 24 + .../net/montoyo/wd/remote/VirtualBrowser.java | 275 ++++++++++ .../net/montoyo/wd/remote/WDMCEFBrowser.java | 10 + .../montoyo/wd/remote/client/ImageReader.java | 53 ++ .../wd/remote/client/RemoteBrowser.java | 137 +++++ .../wd/remote/server/BlankBrowser.java | 78 +++ .../wd/remote/server/ServerBrowser.java | 61 +++ .../wd/remote/server/ServerRenderer.java | 57 ++ src/test/java/NetworkedBrowserTest.java | 43 ++ 17 files changed, 1168 insertions(+), 283 deletions(-) create mode 100644 src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java create mode 100644 src/main/java/net/montoyo/wd/remote/BrowserGen.java create mode 100644 src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/IWDBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/VirtualBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/client/ImageReader.java create mode 100644 src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java create mode 100644 src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java create mode 100644 src/test/java/NetworkedBrowserTest.java diff --git a/build.gradle b/build.gradle index 3498e50..7b37f32 100644 --- a/build.gradle +++ b/build.gradle @@ -83,9 +83,10 @@ dependencies { // implementation fg.deobf("curse.maven:spark-361579:4381167") compileOnly fg.deobf("curse.maven:vivecraft-667903:4794431") - implementation fg.deobf("com.cinemamod:mcef-forge:2.0.1-1.20.1") { - transitive = false - } +// implementation fg.deobf("com.cinemamod:mcef-forge:2.0.1-1.20.1") { +// transitive = false +// } + implementation fg.deobf("flatdir.lib:mcef:2.x") } sourceSets { diff --git a/libs/mcef-2.x.jar b/libs/mcef-2.x.jar index f4316ec7d38f6c72d24a70947eabf1a591628581..412932a4129ae6757b8ebf0636581c9f00a06dcc 100644 GIT binary patch delta 57456 zcmZ6Rb8K%$z&!w`5DtboRxV!3WHI-_xsmF*V&{*t*^Jj^$rX&_-()TN}_8y@b? zBxt`-wk6Qwm9hK?gYMc~jC#M9)uY6Po;mY*6Pz|4CysX@_6)A1dos}{Oj2#$KMndwM=-=B`3qNG((n3xYO$(BK9-R7N3$Ti&!H zf_{YY!Hx^B>xrg1m8_;l?BMl+kY~*U@ou^G!hv&L(lYvmyu`H9qVi$9N>O}IH#JTf_z z4)*)j(3V97{Nsdrpn*H760WQX^PGL0zuulc5&eCuQBG9wpiQe|P0%<*i(RBuTrU?h z99)+`kwbYN)FKr&THhAVERK1fP{elUn`&Dck*yGYgY%K>O-_R|em1=+0jN@=IO1oz z+IcUp+3n9hLv4RREE7TdShMH(w(W6YCOnH(qxUt!7wqca@pNL z1pFwIioWKLQ*GDWhMu-PFTF+_{b1z2(Z-bue%tGnkm(aRcw@928ZlX!3Kb1U&A4nX zP6;D}nkz**9oK60P8*)38ugCT*N?!|Rw|AAY z8%jBRjD;aj2f436K3-0u9p8hLJt>_}{$1lYdV$>24gttbjXf?8as#j;9cbgagBqbL zK7$X;lbs_!Kj|=Azi;DxbPTc^ibI9j$=(t?e1jdQn|(vFI#2*G{(tuDOO@S3{Ikam zc~_E7*4L4zNfmVPi`womtj2G(s*tI&wb6CFS-SDlZHQ~-(0TWPGiuDz+XcHg#J zvHN4|erld z72Uk@D5f)G7pI;%L61$j6NbXNEW}@sAkMUjSgzAGAH@^s1{tUxXXT=1{CJWgpS}}D z<(!ji6KTAHSaPg3kv0=5$*EtO;wX_3a0wX1BlZIWd+CgZt!HY;9r(Yk&+ zuwc$*1hAl5*?9YUPsR;^oC12v3Bf@!X&CmJ5>rWtYSFNSa4|}hq!Y0zGNT=3i%R26 z@u)Ddq*OBMr;C?p)YjCe@r#Su<*jlV_UPy}v4^>^u>BEFokYu4CLJ!VbIhu@BWh(G3JONpCfTN9RkWd-x=qM10kXt4 zr75_VW8lEiY2?Y34W#C5%JQTaoR-$MUB+Um8`xFK7kXBuo7FO6R9Tcr<@VK8TC2^~ zRtqH{`t$2TH}jsg3cb05Llo)am8!A!PDrNqRH<1P7) zfc3;zhQSfw zX;IJD-j`@;{OD|$0A${DJiiAK2I|?8=F+^m<1$STUhq$=vSsU%t>5zfn^wEEW@x|1%@4MjqkQT?7VxrhhX3y5vc|;AEKzEa+JoAi z|536fw6!gFX6y8HGa84IxN+a`NV&SswJeLW$p+!R%4us# zSye?0H3DLa>D(Ju3)CW1HQz7?AW|jlJN#l0<vIXP-aTM+M$aC}+1Jbg-9apNfB!PUBKXjBGp;G4QgM0n-5siY?rGYOGz;4mo zHN?*nLRw6_2Yt9ZziOA;xW^3KcUN)eN0gH%@H#IRoiG~`S)|9sw#;lqjQ4K|ZVhZP=FIc4+e*K|3!1Gyg%kXPWL z_+9O7^UHob9`bXjyJ8CIXVG?rc-*?}e*?K41OP!yVER3Y(O|c6Fr%AfE?TKm{Wx_(Jlw6n2!2ovihN z{@MDx*8qC=$TsgzjnkFhu#+FnGHN_JEK0Ptvfmze&F%L)A51C(_d*^0 zb&$dN>h|vKIX)Z#bPQ6S&ZfXsiwAiiGfXIWbA=mX<<6(&fhMd*&L%2Iv+?&67 z^0R%rBK^YFC+ME|ort;Bv%6CH8=HErtn_o;?XsbuM`+p#P`g#Hmzx!+Q!0RjpaUi5NQ5eZ%Aw^q9v2pk#EU)TaKsW6ss}G+YD~bQ$xwpx@$-}-NZsOgX{|j z>OlSB{b7&q!)G$%?H+gu_EBYWw*KlsjKb zO-sg#WmiolAn9FA&9S*WMIR)^ZA+zPGm+>Ul@PohzK_DrHT_X;JU*9P6RaUEh zPO%0HV1in`npszcQVm-ZmQ*^HPKowhtt8Q?Ov?42fSiZLVi$#MX7<<$Rcoy(C5+9P zUAETUo5kf`wrwl~XYH`+PbqBb%+3iI1uOO3nxW~{dOuyK6tfL;t(x|*9IEgv6HiWf zB*=h~nz}-0paH&i|5!F+ofHx>Hpau@@YxjL3XVlW1Xj2~Ks_|N9&6<9I<^0>SAgQ* zcW3r6PZDKIep8Pr*7$>Bw{NPb%^gE(Bj?s^gkr%jZS8-uQXuD=i! z^mwvkS~Um+z|xbja@eNJS~nbmYifu=^lt%*9+5hbuRj~g*|D4K@72hUHSGKZ$mk~k z?YHdq`IvkAXSgbfAND>PopBEa)dD}~*K^7z{8{yIs)a)JO-_Lg0`rc_z@M&Qd|KD5 z@W`**=V)pV9t!gUm|Gt22IdpHgPU~}Kp$k+=+0))cKFshCJX)TZ4`D6;)?-?nER_n z8Syh@&*-Gb-(QFuA++*Fm-r`~y1W|zlA^E8F{IMhXfKWx^nd%M+S*>*x-_lpR=s>Q zRuq-y9YiwQ6hUowt7Sn21M?ZvXe9w3ie8d@#-&6?>ERlRb?y2*{k&Ibl&tmIB-@4c z*iX&TM*)9#R!f;aZvc)QK-d@F4K*&F*yHchp*m%rULzOb%!nfN6brmem15KCOq_+ESIc4 z@n_^q7uzLfHRJX)y_zZgqJ06-593WMLd2CQnzHucq*1A#7%`<%-j*HWmKWU1n{GRC zrB&%cwbW|oMu^}Li4lkn9kY5J%SH2rc2o(e!|Ih(4&yYJWb^Bep>(-3^u9l&${5N zdK1s=^gDiH_r$~Q z(Gd3)&h8X>yI_L`QSe4#OLQ(%ggu^#^sWc>2?j5(@PhI_b~vN^?rXS%+m6V+S3GlI zcTXu_ioQv&=9o6(y!!zCAOZ+)OcMCoC`E7HOo&B+$LBt|k04^JLIj({(?ewC!epy^ zN`9W|hBSy*Y6RpwkIJ{wTN=EH~DQRa-;^afTqW3BIi&HoZ8`b2W}`ctts zhYOLe+;a$j$E6|yr11l*sz-M#^zh@f9(c2PFadM9X_s=tn^%DMb}y)(emw#bN6wuA zhH^`Rxp%^bAm_Z}Y`|bw_9skjzrbNycGaQ2SM8#atGGY1`2DvWnpcM;W>x9s&n z*v+w*(zdK(v0G_44X`DIR_F^i%L?`N@njhu=2NXxUil zmPaeH0b^Zy83C41wmJ~n-G!$Q5{FV)eok9S`x$ijranpDH6LO8k8`+gG}0VK_-6#4 zy%6u4^bN}9w!32P*EV9kZhvalSy#?=7hc*m^g$ib3q63`FO#l8{UC0_Japp|@_;m-35AyyC$Efym zkmDoBB&jPs#XKr9@IQ$kyez6wVL zXB0P_I|mo(%}@S9^;M7{|Ha=>ddDN?|3+t|yMH)JbdVMS3geC~cmO&q81jk}6!D6L zsOS;K6#Rx@B)nfE27C`c{40b9E{Ic^7(QS*%#QRG`VbJ@M~q1rUi^`S z=`+rjrv1yH_SVi6sqrBox=(M+`fp9;pm)q;c*)Na+KyqiJni4S+B-F4xPLE72k}QW zjLv^(L$YEV|3z4bsbi%lO8uTwZQsBG{8RR6{7VGvpMp1p0vk1=;^NV2scixhxWy zpe1oB=>sVe3TS4M{(}+O0tq9Uy)0uzRzpH2A*mFZOwLt|phRKGa?ZW@T#PHhk!VM3 zOSUc4nc{+L$#agds9WqU!6WgS;Fdr@#3!;L?3MlmWyx^f0DACSer7S!x);g*vq2V4 z4Im6N`@rCVLec#bC-B^OW0V17^T%vo&X)dW;bdRW7Vyi>GVK7^mWg1|s$N_>GOXp; zL!}Gkvw6isM?2`R7M{Fi4QD=?gM@T}wDVo|Y6NEUyf*ywFhPBXb%}vYU zfg@g$s$zhLma41X_>Ww&@wd$|f?)_V8p#U3S3E_oVu%~Tsxo=4A4kZ`R|If(()Z!<6+{N z$ms^f2^|3q%^7PP5?nw0YlMlNGr_iASf?pjuDGE_WHCJf4R!T56&mGyBif*xrKZ** zH*`u)I@xx9Npo|EkhJvY%b&Je*t+D`ULKZ2GHv#|vCqFoGt)h%x!yNiyX$s(fA_|t zfMyc~knwO^4+a1ZtcIg8?dZ^>LuP>bqLLQbASh~goURyyX$d&6F1IVzAu9NiKqt1{ z@=`Dwa>~Mp?Nxe~HN#SfnsHAtVicC7DdE%>8ouHJnR*j5C3ru<4Cy+(&RM>;7HtLe z#;X7W=2gJOtid=A7UMN}fPiJG(!%g>B?lpkOwcC}Qc?f|Tj;pcy-V_purMh#>{y{0 zF`34{qimG$Z>4Q4_QFU{Z{kVtvEb~a66FPr`t{Zrz5)xSgcfw%U5#k(XjHSo=A>Ev zNb7N2%MB5+6})a6?LVfcp&MQd)*ba-WmR2aMV?o4EQvw*q1Y+*_=gO))=WExdKQ+6b`z;c5(*`6=O8!W8fq;o z^Oxwdulxz#M?{hMQbePWqn<^S$yerMa)AK!{jD9Q?FtS`+bXG|Eq`Kms7nLnnP=rg zTSKG5%Pk6bGmi{4{ahBL9W24=N6Wxqqteq9PlP_IL*;p*3#ssV=JEUk9PYvONLLLy zbHyQMsBP3eb5EI62eih3AkeEQear10s&O0Lz~|%AV>`-KYLk)h1dKKQ^!K>9?_vNL zQ1O)8TQGlI*CZ;pB^&Z>SI02l|ZgC$W?6XhE;h*r^9c2)#qpopaXoHiEclV|z|*aJrMm#l&z!CPEe=#@&l z@!iX1oXQN;IL&ZcV;L$rJMrA1uh6g8A*^?ls3&+jNr9jYB!*b#lG6d zL|eRl@b#5|ZpM70>%oc$Kf&$}1qW(S!#51Iha{KO#y75d+0nC{*=fzXXl((5GE|;u zwr!rC8#0w1i8**KV2#@o#VM^rr8Gs`DiL8(#7WQ>ioDhF?^w2X?bJCoOA-tAxDR;b zGEvtCH;UQb8mPlfK0OZ7LA(KVDhX5S;Lb3<8biv}e{7t8lNoodSq*@T4lcq;y(u`V zm)T-mD?s0dohMy14xTA`{@&+W9Z^bf`bEn^kziL{X| zQHZzE=9rUOo~nF;rMj4)eM>Q3fJ-rG?svN;Yv}G=;8A^cBf{6ZcB@v{P8djm-cgk{KHKxO;DB zdu7U`HknpBd;s%0E50_}ink4OdD!g45P%tnr> z+6w6BELno%u1K$8zZ}lNYK`UcJsGKm0?t)KTiHZJmh=EIJ3!wsePtyS<=RFf-JyM^ z3J(1BTIi1%u*#ns;!gmG z2O?lxia8@CRG{&TJ@J}~&0xK!!LK9fGSS)ib&hMHRW~vuI~vH(Aw62QIAcd_X<9 z^%|9I^df4+%h8DoYw6eTRcgnxJ|}7xY381wfasWReK}_|v*Ja`zHC8rRXaIv8nLSL z7r*uVcrSn>a++d(S`4rzh55o*<6kpvRx_&yXeV16xQ9IbOi-s*7^dTda$7Ix-d_6T zzWlxBki^I3Q;DjLl9-0%#0|ZBep6toxgnNouA-7U+qQ@jX;0j?a;f9! zLs_@55@C~uezg(7>3%L9!Lm${45}Q2ZmhLardE4sh9u-Ie(MD)tZ2#$pGQWDwc*&V z5#bdYPe~)xeT>SMtz2j1ghtWZ-QoH8Z9CEiuBD-1Qw&xHgyF+m*2K3vGBqI_-Min6WlauoT zX=vVytr$IEgPhI*S-@CGBxtLo?)UtY$?vZUX`xrYIXVYbbJivBy-7N5j6IE)awq&G zsWqu$toS5t+0c#46|xO)r=SnZ>Cw6^-2wo4UJ0L(&!*k*;*IG7hOS-e9QYA=N61*N zpSG->vZ_Nhpq;O9>e{18ho8ay4$Ml@#^dxiZ8>?+1#6~HK$vbijDJ}pa zlYN+Tn6mo}i-nG=(gh>9m=dt%H&}c(F_jf_y$=_Wd^s*ZAK_|dy)8^U<^CwFZmtsY zv81Qg7&f&H?=Stw3u^Qtb?=f4-YPS@{8%V-bT!xaQW!+}$?%QiS87+@qhQoOQx;D` z_6McHT*G(r|$*6B##^wpI z51rbu%6+8Nl}r-jMxh_13FnHag(>2My0Ni$u^5I3a7FJE_FS&!ApLu5#e>(0RvBvG%^df#}ht^%ugoo z|6B}b6eHd-wrRI6nEe5R5p&qwtC;NvRrbIW1TR0(m!gzCQir^#`T*Es(}+^?46|lU zezMfKbWUg3r*I(WSpVG^L;3|KIajE}?pVu3aVJ#igL<0WZ(g*HdI&#;)W5^j99_T{ z6iadjig4J768)hK&B3r=~kZd?PXm zX^`9H0ZW$?^7OGLOV)v55REG!Dt>UJ9V6||e{NrU2Uz}zwlKypT1ggbgzchCQ3~Uj z_b#Cjwl=%F|0vLp9&7AzCr~g_r?P$${LrHzF6sg^=3o;a9vk2j2dHC>K8kPlZ|;{{ zye(`n8g|R3l;aeubnV!Kd@CU77wV2Ae7k^ENTRAYlIq#hb!xra{W^C5GrruxGOCRM1KR{ zmFn>8B}tD9>;a?*tIRLu$wlaeo_NFbf?TG8&Cb3?R<*xfXG6AmL{*L6Rr#i~gzCcv zj0#--juoXc(aUA7BPr@JapD_r0yXVSxR^Y`vU5m=nI;*6Wc0?neNp{uhX(ji0`8Lv z+r5R063=U{LJEL624=pMT{xN`#ounc;Y^;^{ z7L+@usY!k2^X;|b>M65VYsp?;Qdgss*)fzBO1 zb=NJT$sfq^jx3ljr%)s8YvPUg4VnxCuX(B*DgvfcgEi#7=EmdKs1c0%NXcYBZ$;Ct zQVoT!30No48^g>zaVVxzoX`u{152FP2DqgCu7BqbA3A!*ibt0I#BgJJ)`aNSCRp<^ z!a2)iwBP4n)y$2XzZm9zx*aNZP?5CV)B)sf@RGde)QY6XO!UPFZge~K4N0St5Aj4* zxB!ZejJmH-+iuzWKc1%!uy{Wf$>K+fV+E(<#t)qP=nAiNxkz)4HCjD>I=N@KDRnNa z)sy2a*zaD#Emw2n954InUa@!%0*C~u!X;;>+-Ev7E)M?Y56)c%^qA9P=bskyM-ej} z(e=@!{XJhjah$z*z4dX@1Sf@%8GZzXaR7k>32k|hNLP*cfyagz)P6FZZ{J{f?UwQvr%77L|sz<(5EW{w@d^&bW4Ao<@EprUTQD*fbp3A-T9aT#4L=+{NzfwC zhiRx^-Ns{G_`(O^-2pMOM&0{Z1z2N_N$lE9tlFELHQU3wiKUiVC3+}hH-H=OM>8an ziM)6InXQi3T<5v>Ij`F>cYd#DS7NOZeWOeiR&S62h2gUW@9Egz@__j>IM&r#?LKy^ zm$B&bwDgLtXg6?^g6&MWWt^Osk5sHy6XxTx`RU!F+ocS%33Z}s@lKF}aen7IWDC}V zZL={`uVNCLVKeshlbv2Y22Y~lGwq(_k)wte7E{p}i7;fgLYXC8vH0GJoyJ6}GLlO7 zmCS_p27y^tXVS>dBLF>;EYypru~~B~E-^NIfyP$&xaZWOV%YU@`hjv*C=%SG&Dl|S zId&}5y9TT&hwgHxt}B@O5`6#x(Y1GuMLOs z@L_d!(ts_DxKLtU8h152bFB}mv)|J%7U>mtgG+|39x8FeUeD!5s{GUx47y;nUD55w3^JnVDe%yFA$1$4maI``pBCgZ zhF0YgO~2q_;4O9fCfZBq!bebdjP_N|Nig3t%%ZRL^(q4|&@r}s2xp4?#SSSsa3%Ds z6@k$wEfT~IEdhbisTL|GF{zfU3ph3YqSO~OZKtU9i-TVB-eLSKMo)7(`3uxqRAN;V z&P!HME`l~}<=f$mJ<5oqGiUy3^x%7Z6VRo%YOFqmcd|k262UW|J)>pX&9Qhntq+4M zT6}mr7?HS)K6ESZ7IZaJkMmxoIxLAer7|@nqvC@#Gynn9BWoAz6-?rFyJgC_hY=U} z@MxyR)HL=e-)p3;iy}Ig4l%A`=)deoQ{l*nPyYN^D?Eq5Gy`v5Apx>nOF+z633P;UY8ao)G+jDX* zNLFVbrvV1?md#nU4-52GQ)Tbtj)_x3UYsud^X0l<09f$-O8oE$3wd&H;|o{FmY^v;wAOa}ed-^q0q5^d`z`N?pZ$i?20tN~h0WgO9*{i)Bs_r2u~ z+rr-9Sd`fUTYq}MJexYDzy2uqPqPkH4#GLbRE zE!=e7dHxk>iO{}GSGb<27zjjD)} zF%pvJm@iZ`?s}G*I=69{|KYCUx@=p(RJ(lOYr zgR;YZ;_jx02(o8}Xcbf4xmQ`XSCNZC9V(5j(MMGu&#%))=v-p;#)%tpRY$)UlBr zY|~;OFs`BT1dd34X|aO{6Vv?N6HvHb(-Kl7F;Zc}#x=|skoW4axADhfsOwD&TNGOi z2DKQrl;3fdyfU5mv1?^vxJiMM0^wVa1U(FN$B^+_@5((f>v_IU^u89-X(r*R{P3I; zuHbXxPfU9O&E-2x6MeenAOIZmaM4qyT0n=Khb>z|bk9{P3F7D4ke&gI=P`MaV=L*X z=aq17g0*H7K?~|nsxNgsHO{QLNs0EVM|O4|EXO7Cgc~WuncAdy3cu>ENxcR(!98wV zR{7rhEXJ5gM~;QE(YB;6MaA0TLl9LwAdO1kD)sh$V%=o4d?4y#wa`|vuFBhER5-!VZIr5M@=F}l1&fJ-d zJW!0X`d-JnaI%S<26=615wxPYIXe!Mh!(7ud#b{tAdcZg^lto-se#XWRU$WKi}Yqh z+M~jt3yvHZ#8+st98khCTAI2Sak=0zC?{hrb`6rL2mUo7&a+E;cSZ@Z2-3VkHOg|TnxPGe7o|Z-`16^S-hR=pHrm7auqrf zT4|_dgSF)7LjXMSv&e@R+l^uxaVUy3ziQddv0jH;?exAiR(vr-$H+ z$)v>%MD3!eSBJpdjM*<3!?w{+5g1H;8p0alCT>Zo^pzIUYJN6o7kNqyl{u{x^-FKu$dogxA}17|71SoWB9^ z@R!=%UtEs%Ut4Cwmn1#D_hKs|9@Okw{I5U)XNPl-!h=DMs<^-YdcT*eUW8PDiyrt| z!%hFXDgu0^#}V$2B@o$4qP#e%+iX!P;T;|lga4~FGhe={xJ7DxPAS6O2NSpsLe5=y z6YG`S!h`peV{DM5|4m#Y)l3+1@_8*(eq-z z+xb_pNSzofM5`*UKg)W^i`Hzf%sy)!6CUSMxDmhvsIeS1zAxXg?g1XL;NDsd;XWx5 z@}wFlS0Grq%lXB?aM9&|L^Vo=XnoNo;cGr#gwrdn2uiVFV#FV!uO6>`KlL(KLp{eU zT~cOBBM>HUi4L6>O}jBUXMEJgu7sUZrIabFY(bqqVUdX~DH!>lW@%R8Pn{BP+5MrmCFT~65v?s^h3sl; zPR%PRWm)DG;rUyq;CzXsyZt~N0@W6so0^n-Y)%__m+=L{A4Pd+ydi7HVCrF6nkLgu zB&73{iMSWuy6=olQ~^$eYk|S`yU$EZW)olu-ZIa<0eq3OEZ3EM6GaD)w;dFFBPjlw zU*sL{#pGo87bkLq5-fbhuTA175wuZUwzWc-)jG%*N;&$#G8{Jy6>k6fz@51((c;AqcpBTp=TWDXwH#9m7f;>=&Rdjn(mk^w!bj};)3 z=u31VpD=tWaiB_1N1-qMrzF8--$9~QmS*DFB}jBSXN^M6p;BTzjew+PJZRv7!lE+0 zY)$ou{sC@r=9{?f%`^cr)-%yAxkd0}TG1(|HB8tgjji()$}{xr8|3;`^yb&cSGSdY z!|QX2W`=ZARAW?QZt;eZz2eKDl|CTgWz1-o=4l)WpJx7>XsC>_89^1+N{T->A&voI z(U}s8Lcovid;|Q5iuPIdD;%chiRkgOSV+5u^R7#^HH$Z`Qqk;3Lrx&qNB-X5SJAY90A9#_9_16x(F`rmU33zlJ zug&{C^2pb%yz!*5?BD6kkFurHvl=yZ%kzEWKbjw+EnRDT*KmKc`qua)`s6Z~#Wj

q3xV+D+JT1eYe;HqhXh9NNLHVESw$h>J# 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

855rf=b8A#D*RS@j z5kK7Gs3^a`j?CX zCElgxRk+$k9LN8*j=K>6QMAF^-N**Vl_(jjlPm8;74&fh1tzfO5CvNYLhk0eie?K4 z6zIYrxDv9))vkO1mHLZ7X>NMR0f)JXQVDJVozyT0E{!~C+&LBp%u2zVv#-=aSxJi=ZOCG!xZn1-fA_8_}|2B%@mU@a}BL z`2e;5XhwyLIwdA0qtF@xg^kfe{Pt0thA5fj35d!z*q96I!mn2GlFf@&~2b_w+b>;1;De%h$k&{1%=+ zQdKpJ7;DenpfzrXsY}D`CaQM5FU8F;+#Oou*8@bRw*r9a@lrh#+ru0rREHIMD(r>= z#JtJ^0rFhWpyS73{Y?rKbqytu#m5XP4CFm%8Xg4P>?niStKlv6{nrx08bh7!B(fJi z!B>KK7hDp=+pz0Vk{7CF@r~Y_^C1IY!4L>DWDh}8j)5)$C7h{ zZP!Ikq()U4-hU79QGRw;+Fnm{KoA( z>2BfaH-ukBE@|1K{CWgX5C?>bYN0<7nORXTXhzh|t_g|xv!q`1&thMpwaeMt|o z61CXeDJ4hwD$pcdXqR1PtUWDDGo05G+p;K7_4yBa+@D7LJE0zIpwx|d$Qy@8i@8XQ z2ITye9>fxtAY_J5M~d(U0zbXV)Ik^13O7WH4~}*Mox5fPLDQ3o(y{Y#&FEv_fa?t~ z7o?{(izdC0nvNanof&4y% z;VGTaTbhmdp@nKRKF6-!IOZ$}nVWH3f@IW>*D)bzn+9RKeq#6@{lI=vn;vq(v-*iO zTS1`wJ9@|#=SKBxbuw>%MvC?aL@sr$=?^1*XwI@d{0FPtl=s1#%2x`l`-}0Db}fc> ztx;vx#DabAdwQHboWs3`rFX&TtR2R2p^^YcK47f0g{$#Ec7~B%{AlZ^W5Ko@{FQFa z4ytf=EYxFe3h<9%tp z)8e4sQ}8gNABaj^5-0NS2Lx?$WBkxEw!-})#H@D_$lXH^u|sLjhD&edLIAN4fOKlU z?S-?GcqgojhrJ>+nT--y6%VRBw0fL9;OnORw&zDkTn?y{Ch)i)W!*FpnUXfNhNhxQ zvu^4Sz)HUEB1)3)Hh&2_?M}0DPYKU!Co98Y7`gowS+U=I?E@~=yz)@D zizck`RCYrW$d<+sf*Y<+;@RWy0imvsWz_Mx$i`rirfA&C)crF-cOvNamV|pRp4AcB z<94q!3IBO<>oM7|nL&1?HQkfM#GsAtKvybirgCTe(L=x+1_=p~m^n0n@k5V28J!%2 z^9J%-dZ0h?{6DFKKqLgb>n#zHB_n)r0dm0&1I1i>x;0;~HRrf!+n3Mr>jvYBL1L$+ zaS-I9A`9Oi#D~z7xh2DM+F>ZdUlpMUJUdxjSC$a+lWc@b)*?UJXrtVXKt|I?;rq#a z93=z~214;TLeSvc!MrOCHDJfFa~cS<2IikMqxdbt>VZ!ti}U4IqClR*sHzlk>lmB@ z)a$u8Erk!F8uQYu|4T9v2$@hzGSM*;m#6UTMyMLZ_`=x;yMKw?@beTt5UKO9&kzwF zIt1Xi^AOG!kdpTQ@#-O>AKM64H_w2vO+Y&#yDl)0>3x8bR_9r2PK^tUnXS;R@RazJ zkxvC&$rXV;<-IXttD3a8ZD2}Ci+^+}{)<=c@4D%{qIUi^|Kru88q*M5R*AE93Yqp7O_)}$eub%gIcz!YSTwh=I@})(i6PnHc_DGV#0|Exb>g%_)7>u zYw_}P=ids009&};fdA>hL1z6OULMa~pJ81_PvrTHi+qTXRi`OgOOL2DMp+h+o6gg0DfQPI?9nimV2>{vV;i$Xb^8dm|B?ZNMha|c pUjW%>?~jO^n=^T4=g2x{G;|6eCKm!a0?*AY0|6~9F diff --git a/src/main/java/net/montoyo/wd/WebDisplays.java b/src/main/java/net/montoyo/wd/WebDisplays.java index 8473021..2175895 100644 --- a/src/main/java/net/montoyo/wd/WebDisplays.java +++ b/src/main/java/net/montoyo/wd/WebDisplays.java @@ -4,6 +4,8 @@ package net.montoyo.wd; +import com.cinemamod.mcef.MCEF; +import com.cinemamod.mcef.MCEFBrowser; import com.google.gson.Gson; import net.minecraft.ChatFormatting; import net.minecraft.advancements.Advancement; @@ -48,10 +50,16 @@ import net.montoyo.wd.init.TabInit; import net.montoyo.wd.init.TileInit; import net.montoyo.wd.miniserv.server.Server; import net.montoyo.wd.net.WDNetworkRegistry; +import net.montoyo.wd.net.client_bound.S2CMessageEnableSSR; import net.montoyo.wd.net.client_bound.S2CMessageServerInfo; +import net.montoyo.wd.remote.IWDBrowser; +import net.montoyo.wd.remote.client.RemoteBrowser; +import net.montoyo.wd.remote.server.BlankBrowser; +import net.montoyo.wd.remote.server.ServerBrowser; import net.montoyo.wd.utilities.DistSafety; import net.montoyo.wd.utilities.Log; import net.montoyo.wd.utilities.Util; +import org.cef.browser.CefBrowser; import java.io.*; import java.net.MalformedURLException; @@ -65,7 +73,7 @@ public class WebDisplays { public static WebDisplays INSTANCE; public static SharedProxy PROXY = null; - + public static final ResourceLocation ADV_PAD_BREAK = new ResourceLocation("webdisplays", "webdisplays/pad_break"); public static final String BLACKLIST_URL = "mod://webdisplays/blacklisted.html"; public static final Gson GSON = new Gson(); @@ -96,29 +104,32 @@ public class WebDisplays { public long miniservQuota; public float ytVolume; public float avDist100; + public float avDist0; - // mod detection private boolean hasOC; + private boolean hasCC; + private static boolean SSR = false; + public WebDisplays() { INSTANCE = this; - if(FMLEnvironment.dist.isClient()) { + if (FMLEnvironment.dist.isClient()) { PROXY = DistSafety.createProxy(); } else { PROXY = new SharedProxy(); } - + if (FMLEnvironment.dist.isClient()) { // proxies are annoying, so from now on, I'mma be just registering stuff in here FMLJavaModLoadingContext.get().getModEventBus().addListener(ClientProxy::onKeybindRegistry); MinecraftForge.EVENT_BUS.addListener(ClientProxy::onDrawSelection); ClientConfig.init(); } - + CommonConfig.init(); - + //Criterions criterionPadBreak = new Criterion("pad_break"); criterionUpgradeScreen = new Criterion("upgrade_screen"); @@ -134,9 +145,9 @@ public class WebDisplays { BlockInit.init(bus); ItemInit.init(bus); TileInit.init(bus); - + PROXY.preInit(); - + MinecraftForge.EVENT_BUS.register(this); //Other things @@ -155,7 +166,7 @@ public class WebDisplays { t.printStackTrace(); } } */ - + if (!FMLEnvironment.production) { ScreenControlRegistry.init(); } @@ -210,7 +221,7 @@ public class WebDisplays { if (miniservPort != 0) { Server sv = Server.getInstance(); - if(!serverStartedDimensions.contains(level.dimension())) { + if (!serverStartedDimensions.contains(level.dimension())) { sv.setPort(miniservPort); sv.setDirectory(new File(worldDir, "wd_filehost")); sv.start(); @@ -222,7 +233,7 @@ public class WebDisplays { @SubscribeEvent public void onWorldLeave(LevelEvent.Unload ev) throws IOException { - if(ev.getLevel() instanceof Level level) { + if (ev.getLevel() instanceof Level level) { if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD) return; Server sw = Server.getInstance(); @@ -233,7 +244,7 @@ public class WebDisplays { @SubscribeEvent public void onWorldSave(LevelEvent.Save ev) { - if(ev.getLevel() instanceof Level level) { + if (ev.getLevel() instanceof Level level) { if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD) return; File f = new File(Objects.requireNonNull(ev.getLevel().getServer()).getServerDirectory(), "wd_next.txt"); @@ -250,13 +261,13 @@ public class WebDisplays { @SubscribeEvent public void onToss(ItemTossEvent ev) { - if(!ev.getEntity().level().isClientSide) { + if (!ev.getEntity().level().isClientSide) { ItemStack is = ev.getEntity().getItem(); - if(is.getItem() == ItemInit.MINEPAD.get()) { + if (is.getItem() == ItemInit.MINEPAD.get()) { CompoundTag tag = is.getTag(); - if(tag == null) { + if (tag == null) { tag = new CompoundTag(); is.setTag(tag); } @@ -271,11 +282,11 @@ public class WebDisplays { @SubscribeEvent public void onPlayerCraft(PlayerEvent.ItemCraftedEvent ev) { - if(CommonConfig.hardRecipes && ItemInit.isCompCraftItem(ev.getCrafting().getItem()) && (CraftComponent.EXTCARD.makeItemStack().is(ev.getCrafting().getItem()))) { - if((ev.getEntity() instanceof ServerPlayer && !hasPlayerAdvancement((ServerPlayer) ev.getEntity(), ADV_PAD_BREAK)) || PROXY.hasClientPlayerAdvancement(ADV_PAD_BREAK) != HasAdvancement.YES) { + if (CommonConfig.hardRecipes && ItemInit.isCompCraftItem(ev.getCrafting().getItem()) && (CraftComponent.EXTCARD.makeItemStack().is(ev.getCrafting().getItem()))) { + if ((ev.getEntity() instanceof ServerPlayer && !hasPlayerAdvancement((ServerPlayer) ev.getEntity(), ADV_PAD_BREAK)) || PROXY.hasClientPlayerAdvancement(ADV_PAD_BREAK) != HasAdvancement.YES) { ev.getCrafting().setDamageValue(CraftComponent.BADEXTCARD.ordinal()); - if(!ev.getEntity().level().isClientSide) + if (!ev.getEntity().level().isClientSide) ev.getEntity().level().playSound(null, ev.getEntity().getX(), ev.getEntity().getY(), ev.getEntity().getZ(), SoundEvents.ITEM_BREAK, SoundSource.MASTER, 1.0f, 1.0f); } } @@ -288,10 +299,10 @@ public class WebDisplays { @SubscribeEvent public void onLogIn(PlayerEvent.PlayerLoggedInEvent ev) { - if(!ev.getEntity().level().isClientSide && ev.getEntity() instanceof ServerPlayer) { + if (!ev.getEntity().level().isClientSide && ev.getEntity() instanceof ServerPlayer) { IWDDCapability cap = ev.getEntity().getCapability(WDDCapability.Provider.cap, null).orElseThrow(RuntimeException::new); - if(cap.isFirstRun()) { + if (cap.isFirstRun()) { Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome1"); Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome2"); Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome3"); @@ -311,27 +322,27 @@ public class WebDisplays { @SubscribeEvent public void onLogOut(PlayerEvent.PlayerLoggedOutEvent ev) { - if(!ev.getEntity().level().isClientSide) + if (!ev.getEntity().level().isClientSide) Server.getInstance().getClientManager().revokeClientKey(ev.getEntity().getGameProfile().getId()); } @SubscribeEvent public void attachEntityCaps(AttachCapabilitiesEvent ev) { - if(ev.getObject() instanceof Player) + if (ev.getObject() instanceof Player) ev.addCapability(CAPABILITY, new WDDCapability.Provider()); } @SubscribeEvent public void onPlayerClone(PlayerEvent.Clone ev) { - IWDDCapability src = ev.getOriginal().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); - IWDDCapability dst = ev.getEntity().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); + IWDDCapability src = ev.getOriginal().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); + IWDDCapability dst = ev.getEntity().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); - if(src == null) { + if (src == null) { Log.error("src is null"); return; } - if(dst == null) { + if (dst == null) { Log.error("dst is null"); return; } @@ -343,14 +354,14 @@ public class WebDisplays { public void onServerChat(ServerChatEvent ev) { String msg = ev.getMessage().getString().replaceAll("\\s+", " ").toLowerCase(); StringBuilder sb = new StringBuilder(msg.length()); - for(int i = 0; i < msg.length(); i++) { + for (int i = 0; i < msg.length(); i++) { char chr = msg.charAt(i); - if(chr != '.' && chr != ',' && chr != ';' && chr != '!' && chr != '?' && chr != ':' && chr != '\'' && chr != '\"' && chr != '`') + if (chr != '.' && chr != ',' && chr != ';' && chr != '!' && chr != '?' && chr != ':' && chr != '\'' && chr != '\"' && chr != '`') sb.append(chr); } - if(sb.toString().equals("ironic he could save others from death but not himself")) { + if (sb.toString().equals("ironic he could save others from death but not himself")) { Player ply = ev.getPlayer(); ply.level().playSound(null, ply.getX(), ply.getY(), ply.getZ(), soundIronic, SoundSource.PLAYERS, 1.0f, 1.0f); } @@ -358,13 +369,13 @@ public class WebDisplays { @SubscribeEvent public void onClientChat(ClientChatEvent ev) { - if(ev.getMessage().equals("!WD render recipes")) + if (ev.getMessage().equals("!WD render recipes")) PROXY.renderRecipes(); } private boolean hasPlayerAdvancement(ServerPlayer ply, ResourceLocation rl) { MinecraftServer server = PROXY.getServer(); - if(server == null) + if (server == null) return false; Advancement adv = server.getAdvancements().getAdvancement(rl); @@ -385,18 +396,18 @@ public class WebDisplays { return ret; } - private static void registerTrigger(Criterion ... criteria) { - for(Criterion c: criteria) + private static void registerTrigger(Criterion... criteria) { + for (Criterion c : criteria) CriteriaTriggers.register(c); } - // public static boolean isOpenComputersAvailable() { - // return INSTANCE.hasOC; - // } + // public static boolean isOpenComputersAvailable() { + // return INSTANCE.hasOC; + // } - // public static boolean isComputerCraftAvailable() { - // return INSTANCE.hasCC; - // } + // public static boolean isComputerCraftAvailable() { + // return INSTANCE.hasCC; + // } public static boolean isSiteBlacklisted(String url) { try { @@ -404,7 +415,7 @@ public class WebDisplays { for (String str : CommonConfig.Browser.blacklist) if (str.equalsIgnoreCase(url2.getHost())) return true; return false; - } catch(MalformedURLException ex) { + } catch (MalformedURLException ex) { return false; } } @@ -412,5 +423,20 @@ public class WebDisplays { public static String applyBlacklist(String url) { return isSiteBlacklisted(url) ? BLACKLIST_URL : url; } -} + public static boolean isSSR() { + return SSR; + } + + public static void markSSR() { + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + StackTraceElement caller = elements[3]; + try { + Class c = Class.forName(caller.getClassName()); + if (c == S2CMessageEnableSSR.class || c == WebDisplays.class) { + SSR = true; + } + } catch (Throwable err) { + } + } +} diff --git a/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java b/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java index 5bf19c8..fa7036e 100644 --- a/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java +++ b/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java @@ -12,6 +12,7 @@ import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.remote.IWDBrowser; import net.montoyo.wd.utilities.Vector3f; import net.montoyo.wd.utilities.Vector3i; import org.jetbrains.annotations.NotNull; @@ -47,7 +48,7 @@ public class ScreenRenderer implements BlockEntityRenderer { for (int i = 0; i < te.screenCount(); i++) { TileEntityScreen.Screen scr = te.getScreen(i); if (scr.browser == null) { - scr.createBrowser(true); + scr.createBrowser(false, true); } // TODO: manually backface cull the screens @@ -120,7 +121,7 @@ public class ScreenRenderer implements BlockEntityRenderer { //TODO: don't use tesselator RenderSystem.enableDepthTest(); RenderSystem.setShader(GameRenderer::getPositionTexColorShader); - RenderSystem._setShaderTexture(0, ((MCEFBrowser) scr.browser).getRenderer().getTextureID()); + RenderSystem._setShaderTexture(0, ((IWDBrowser) scr.browser).getRenderer().getTextureID()); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); builder.vertex(poseStack.last().pose(), -sw, -sh, 0.505f).uv(0.f, 1.f).color(1.f, 1.f, 1.f, 1.f).endVertex(); diff --git a/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java b/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java index 451736e..b935aa4 100644 --- a/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java +++ b/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java @@ -43,6 +43,10 @@ import net.montoyo.wd.net.client_bound.S2CMessageAddScreen; import net.montoyo.wd.net.client_bound.S2CMessageCloseGui; import net.montoyo.wd.net.client_bound.S2CMessageJSResponse; import net.montoyo.wd.net.client_bound.S2CMessageScreenUpdate; +import net.montoyo.wd.remote.BrowserGen; +import net.montoyo.wd.remote.IRemoteBrowser; +import net.montoyo.wd.remote.IWDBrowser; +import net.montoyo.wd.remote.client.RemoteBrowser; import net.montoyo.wd.utilities.*; import org.cef.browser.CefBrowser; import org.lwjgl.opengl.GL11; @@ -59,13 +63,13 @@ import java.util.function.Consumer; import static net.montoyo.wd.block.BlockPeripheral.point; public class TileEntityScreen extends BlockEntity { - + public TileEntityScreen(BlockPos arg2, BlockState arg3) { super(TileInit.SCREEN_BLOCK_ENTITY.get(), arg2, arg3); } public static class Screen { - + public BlockSide side; public Vector2i size; public Vector2i resolution; @@ -76,7 +80,7 @@ public class TileEntityScreen extends BlockEntity { public ArrayList friends; public int friendRights; public int otherRights; - public CefBrowser browser; + public IWDBrowser browser; public ArrayList upgrades; public boolean doTurnOnAnim; public long turnOnTime; @@ -84,9 +88,9 @@ public class TileEntityScreen extends BlockEntity { public final Vector2i lastMousePos = new Vector2i(); public NibbleArray redstoneStatus; //null on client public boolean autoVolume = true; - + public int mouseType; - + public static Screen deserialize(CompoundTag tag) { Screen ret = new Screen(); ret.side = BlockSide.values()[tag.getByte("Side")]; @@ -95,47 +99,47 @@ public class TileEntityScreen extends BlockEntity { ret.rotation = Rotation.values()[tag.getByte("Rotation")]; ret.url = tag.getString("URL"); ret.videoType = VideoType.getTypeFromURL(ret.url); - + if (ret.resolution.x <= 0 || ret.resolution.y <= 0) { float psx = ((float) ret.size.x) * 16.f - 4.f; float psy = ((float) ret.size.y) * 16.f - 4.f; psx *= 8.f; //TODO: Use ratio in config file psy *= 8.f; - + ret.resolution.x = (int) psx; ret.resolution.y = (int) psy; } - + if (tag.contains("OwnerName")) { String name = tag.getString("OwnerName"); UUID uuid = tag.getUUID("OwnerUUID"); ret.owner = new NameUUIDPair(name, uuid); } - + ListTag friends = tag.getList("Friends", 10); ret.friends = new ArrayList<>(friends.size()); - + for (int i = 0; i < friends.size(); i++) { CompoundTag nf = friends.getCompound(i); NameUUIDPair pair = new NameUUIDPair(nf.getString("Name"), nf.getUUID("UUID")); ret.friends.add(pair); } - + ret.friendRights = tag.getByte("FriendRights"); ret.otherRights = tag.getByte("OtherRights"); - + ListTag upgrades = tag.getList("Upgrades", 10); ret.upgrades = new ArrayList<>(); - + for (int i = 0; i < upgrades.size(); i++) ret.upgrades.add(ItemStack.of(upgrades.getCompound(i))); - + if (tag.contains("AutoVolume")) ret.autoVolume = tag.getBoolean("AutoVolume"); - + return ret; } - + public CompoundTag serialize() { CompoundTag tag = new CompoundTag(); tag.putByte("Side", (byte) side.ordinal()); @@ -145,131 +149,133 @@ public class TileEntityScreen extends BlockEntity { tag.putInt("ResolutionY", resolution.y); tag.putByte("Rotation", (byte) rotation.ordinal()); tag.putString("URL", url); - + if (owner == null) Log.warning("Found TES with NO OWNER!!"); else { tag.putString("OwnerName", owner.name); tag.putUUID("OwnerUUID", owner.uuid); } - + ListTag list = new ListTag(); for (NameUUIDPair f : friends) { CompoundTag nf = new CompoundTag(); nf.putString("Name", f.name); nf.putUUID("UUID", f.uuid); - + list.add(nf); } - + tag.put("Friends", list); tag.putByte("FriendRights", (byte) friendRights); tag.putByte("OtherRights", (byte) otherRights); - + list = new ListTag(); for (ItemStack is : upgrades) list.add(is.save(new CompoundTag())); - + tag.put("Upgrades", list); tag.putBoolean("AutoVolume", autoVolume); return tag; } - + public int rightsFor(Player ply) { return rightsFor(ply.getGameProfile().getId()); } - + public int rightsFor(UUID uuid) { if (owner.uuid.equals(uuid)) return ScreenRights.ALL; - + return friends.stream().anyMatch(f -> f.uuid.equals(uuid)) ? friendRights : otherRights; } - + public void setupRedstoneStatus(Level world, BlockPos start) { if (world.isClientSide()) { Log.warning("Called Screen.setupRedstoneStatus() on client."); return; } - + if (redstoneStatus != null) { Log.warning("Called Screen.setupRedstoneStatus() on server, but redstone status is non-null"); return; } - + Direction[] VALUES = Direction.values(); redstoneStatus = new NibbleArray(size.x * size.y); final Direction facing = VALUES[side.reverse().ordinal()]; final ScreenIterator it = new ScreenIterator(start, side, size); - + while (it.hasNext()) { int idx = it.getIndex(); redstoneStatus.set(idx, world.getSignal(it.next(), facing)); } } - - + + public void clampResolution() { if (resolution.x > CommonConfig.Screen.maxResolutionX) { float newY = ((float) resolution.y) * ((float) CommonConfig.Screen.maxResolutionX) / ((float) resolution.x); resolution.x = CommonConfig.Screen.maxResolutionX; resolution.y = (int) newY; } - + if (resolution.y > CommonConfig.Screen.maxResolutionY) { float newX = ((float) resolution.x) * ((float) CommonConfig.Screen.maxResolutionY) / ((float) resolution.y); resolution.x = (int) newX; resolution.y = CommonConfig.Screen.maxResolutionY; } } - - public void createBrowser(boolean doAnim) { - if (WebDisplays.PROXY instanceof ClientProxy clientProxy) { - browser = MCEF.createBrowser(WebDisplays.applyBlacklist(url != null ? url : "https://www.google.com"), false); - - if (browser instanceof MCEFBrowser mcefBrowser) { - if (rotation.isVertical) - mcefBrowser.resize(resolution.y, resolution.x); - else - mcefBrowser.resize(resolution.x, resolution.y); - - // uh yes this is intentional - // basically: on my laptop, this line caused an error inexplicably - // reason: the compiler didn't update this file, so it stayed as a Consumer in the bytecode - //noinspection RedundantCast - mcefBrowser.setCursorChangeListener((MCEFCursorChangeListener) (type) -> mouseType = type); - } - + + public void createBrowser(boolean server, boolean doAnim) { + browser = BrowserGen.createBrowser( + server, + WebDisplays.applyBlacklist(url != null ? url : "https://www.google.com"), + false + ); + + if (browser != null) { + if (rotation.isVertical) + browser.resize(resolution.y, resolution.x); + else + browser.resize(resolution.x, resolution.y); + + // uh yes this is intentional + // basically: on my laptop, this line caused an error inexplicably + // reason: the compiler didn't update this file, so it stayed as a Consumer in the bytecode + //noinspection RedundantCast + browser.setCursorChangeListener((MCEFCursorChangeListener) (type) -> mouseType = type); + doTurnOnAnim = doAnim; turnOnTime = System.currentTimeMillis(); } } } - + public void forEachScreenBlocks(BlockSide side, Consumer func) { Screen scr = getScreen(side); - + if (scr != null) { ScreenIterator it = new ScreenIterator(getBlockPos(), side, scr.size); - + while (it.hasNext()) func.accept(it.next()); } } - + private final ArrayList screens = new ArrayList<>(); private net.minecraft.world.phys.AABB renderBB = new net.minecraft.world.phys.AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); private boolean loaded = true; public float ytVolume = Float.POSITIVE_INFINITY; - + public boolean isLoaded() { return loaded; } - + public void load() { loaded = true; } - + public void unload() { for (Screen scr : screens) { if (scr.browser != null) { @@ -277,57 +283,33 @@ public class TileEntityScreen extends BlockEntity { scr.browser = null; } } - + loaded = false; } - - @Override - public void load(CompoundTag tag) { - super.load(tag); - - ListTag list = tag.getList("WDScreens", Tag.TAG_COMPOUND); - if (list.isEmpty()) - return; - - screens.clear(); - for (int i = 0; i < list.size(); i++) - screens.add(Screen.deserialize(list.getCompound(i))); - } - + @Override public CompoundTag getUpdateTag() { CompoundTag tag = new CompoundTag(); - saveAdditional(tag); + saveAdditional(true, tag); return tag; } - + @Override public void handleUpdateTag(CompoundTag tag) { - load(tag); + load(true, tag); for (Screen screen : screens) { - if (screen.browser == null) screen.createBrowser(false); + if (screen.browser == null) screen.createBrowser(false, false); if (screen.browser != null) screen.browser.loadURL(screen.url); } updateAABB(); } - - @Override - protected void saveAdditional(CompoundTag tag) { - super.saveAdditional(tag); - - ListTag list = new ListTag(); - for (Screen scr : screens) - list.add(scr.serialize()); - - tag.put("WDScreens", list); - } - + public Screen addScreen(BlockSide side, Vector2i size, @Nullable Vector2i resolution, @Nullable Player owner, boolean sendUpdate) { for (Screen scr : screens) { if (scr.side == side) return scr; } - + Screen ret = new Screen(); ret.side = side; ret.size = size; @@ -336,84 +318,87 @@ public class TileEntityScreen extends BlockEntity { ret.friendRights = ScreenRights.DEFAULTS; ret.otherRights = ScreenRights.DEFAULTS; ret.upgrades = new ArrayList<>(); - + if (owner != null) { ret.owner = new NameUUIDPair(owner.getGameProfile()); - + if (side == BlockSide.TOP || side == BlockSide.BOTTOM) { int rot = (int) Math.floor(((double) (owner.getYRot() * 4.0f / 360.0f)) + 2.5) & 3; - + if (side == BlockSide.TOP) { if (rot == 1) rot = 3; else if (rot == 3) rot = 1; } - + ret.rotation = Rotation.values()[rot]; } } - + if (resolution == null || resolution.x < 1 || resolution.y < 1) { float psx = ((float) size.x) * 16.f - 4.f; float psy = ((float) size.y) * 16.f - 4.f; psx *= 8.f; //TODO: Use ratio in config file psy *= 8.f; - + ret.resolution = new Vector2i((int) psx, (int) psy); } else ret.resolution = resolution; - + ret.clampResolution(); - + if (!level.isClientSide) { ret.setupRedstoneStatus(level, getBlockPos()); - + if (sendUpdate) WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), new S2CMessageAddScreen(this, ret)); } - + screens.add(ret); - + if (level.isClientSide) updateAABB(); else setChanged(); // level.blockEntityChanged(worldPosition); - + + if (!level.isClientSide && WebDisplays.isSSR()) + ret.createBrowser(true, false); + return ret; } - + public Screen getScreen(BlockSide side) { for (Screen scr : screens) { if (scr.side == side) return scr; } - + return null; } - + public int screenCount() { return screens.size(); } - + public Screen getScreen(int idx) { return screens.get(idx); } - + public void clear() { screens.clear(); - + if (!level.isClientSide) setChanged(); } - + public void requestData(ServerPlayer ep) { if (!level.isClientSide) WDNetworkRegistry.INSTANCE.send(PacketDistributor.PLAYER.with(() -> ep), new S2CMessageAddScreen(this)); } - + public static String url(String url) throws IOException { System.out.println("URL received: " + url); if (!(WebDisplays.PROXY instanceof ClientProxy)) { @@ -427,20 +412,20 @@ public class TileEntityScreen extends BlockEntity { return url; // TODO: ? } } - + public void setScreenURL(BlockSide side, String url) throws IOException { Screen scr = getScreen(side); if (scr == null) { Log.error("Attempt to change URL of non-existing screen on side %s", side.toString()); return; } - + String weburl = url(url); - + weburl = WebDisplays.applyBlacklist(weburl); scr.url = weburl; scr.videoType = VideoType.getTypeFromURL(weburl); - + if (level.isClientSide) { if (scr.browser != null) scr.browser.loadURL(weburl); @@ -449,7 +434,7 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + public void removeScreen(BlockSide side) { int idx = -1; for (int i = 0; i < screens.size(); i++) { @@ -458,12 +443,12 @@ public class TileEntityScreen extends BlockEntity { break; } } - + if (idx < 0) { Log.error("Tried to delete non-existing screen on side %s", side.toString()); return; } - + if (level.isClientSide) { if (screens.get(idx).browser != null) { screens.get(idx).browser.close(true); @@ -471,9 +456,9 @@ public class TileEntityScreen extends BlockEntity { } } else WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), new S2CMessageScreenUpdate(this.getBlockPos(), side)); //Delete the screen - + screens.remove(idx); - + if (!level.isClientSide) { if (screens.isEmpty()) //No more screens: remove tile entity level.setBlockAndUpdate(getBlockPos(), BlockInit.blockScreen.get().defaultBlockState().setValue(BlockScreen.hasTE, false)); @@ -481,25 +466,25 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + public void setResolution(BlockSide side, Vector2i res) { if (res.x < 1 || res.y < 1) { Log.warning("Call to TileEntityScreen.setResolution(%s) with suspicious values X=%d and Y=%d", side.toString(), res.x, res.y); return; } - + Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to change resolution of non-existing screen on side %s", side.toString()); return; } - + scr.resolution = res; scr.clampResolution(); - + if (level.isClientSide) { WebDisplays.PROXY.screenUpdateResolutionInGui(new Vector3i(getBlockPos()), side, res); - + if (scr.browser != null) { scr.browser.close(true); scr.browser = null; //Will be re-created by renderer @@ -509,82 +494,81 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + private static Player getLaserUser(Screen scr) { if (scr.laserUser != null) { if (scr.laserUser.isRemoved() || !scr.laserUser.getItemInHand(InteractionHand.MAIN_HAND).getItem().equals(ItemInit.LASER_POINTER.get())) scr.laserUser = null; } - + return scr.laserUser; } - + private static void checkLaserUserRights(Screen scr) { if (scr.laserUser != null && (scr.rightsFor(scr.laserUser) & ScreenRights.INTERACT) == 0) scr.laserUser = null; } - + public void clearLaserUser(BlockSide side) { Screen scr = getScreen(side); - + if (scr != null) scr.laserUser = null; } - + public void click(BlockSide side, Vector2i vec) { Screen scr = getScreen(side); if (scr == null) { Log.error("Attempt click non-existing screen of side %s", side.toString()); return; } - + if (level.isClientSide) Log.warning("TileEntityScreen.click() from client side is useless..."); else if (getLaserUser(scr) == null) WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, ClickControl.ControlType.CLICK, vec)); } - + void clickUnsafe(BlockSide side, ClickControl.ControlType action, int x, int y) { if (level.isClientSide) { Vector2i vec = (action == ClickControl.ControlType.UP) ? null : new Vector2i(x, y); WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, action, vec)); } } - + public void handleMouseEvent(BlockSide side, ClickControl.ControlType event, @Nullable Vector2i vec, int button) { if (button > 1) return; // buttons above 1 crash the game - + Screen scr = getScreen(side); if (scr == null) { Log.error("Attempt inject mouse events on non-existing screen of side %s", side.toString()); return; } - - if (scr.browser instanceof MCEFBrowser mcefBrowser) { - if (button == 1) button = 0; - else if (button == 0) button = 1; - - if (event == ClickControl.ControlType.CLICK) { - mcefBrowser.sendMouseMove(vec.x, vec.y); //Move to target - mcefBrowser.sendMousePress(vec.x, vec.y, button); //Press - mcefBrowser.sendMouseRelease(vec.x, vec.y, button); //Release - } else if (event == ClickControl.ControlType.DOWN) { - mcefBrowser.sendMouseMove(vec.x, vec.y); //Move to target - mcefBrowser.sendMousePress(vec.x, vec.y, button); //Press - } else if (event == ClickControl.ControlType.MOVE) - mcefBrowser.sendMouseMove(vec.x, vec.y); //Move - else if (event == ClickControl.ControlType.UP) - mcefBrowser.sendMouseRelease(scr.lastMousePos.x, scr.lastMousePos.y, button); //Release - - mcefBrowser.setFocus(true); - - if (vec != null) { - scr.lastMousePos.x = vec.x; - scr.lastMousePos.y = vec.y; - } + + if (button == 1) button = 0; + else if (button == 0) button = 1; + + IWDBrowser browser = scr.browser; + if (event == ClickControl.ControlType.CLICK) { + browser.sendMouseMove(vec.x, vec.y); //Move to target + browser.sendMousePress(vec.x, vec.y, button); //Press + browser.sendMouseRelease(vec.x, vec.y, button); //Release + } else if (event == ClickControl.ControlType.DOWN) { + browser.sendMouseMove(vec.x, vec.y); //Move to target + browser.sendMousePress(vec.x, vec.y, button); //Press + } else if (event == ClickControl.ControlType.MOVE) + browser.sendMouseMove(vec.x, vec.y); //Move + else if (event == ClickControl.ControlType.UP) + browser.sendMouseRelease(scr.lastMousePos.x, scr.lastMousePos.y, button); //Release + + browser.setFocus(true); + + if (vec != null) { + scr.lastMousePos.x = vec.x; + scr.lastMousePos.y = vec.y; } } - + // public void updateJSRedstone(BlockSide side, Vector2i vec, int redstoneLevel) { // Screen scr = getScreen(side); // if (scr == null) { @@ -680,19 +664,19 @@ public class TileEntityScreen extends BlockEntity { // } else // WDNetworkRegistry.INSTANCE.send(PacketDistributor.PLAYER.with(() -> src), new S2CMessageJSResponse(reqId, req, 400, "Invalid request")); // } - + @Override public void onLoad() { if (level.isClientSide) { WebDisplays.PROXY.trackScreen(this, true); } } - + @Override public void onChunkUnloaded() { if (level.isClientSide) { WebDisplays.PROXY.trackScreen(this, false); - + for (Screen scr : screens) { if (scr.browser != null) { scr.browser.close(true); @@ -701,14 +685,14 @@ public class TileEntityScreen extends BlockEntity { } } } - + private void updateAABB() { Vector3i origin = new Vector3i(getBlockPos()); MutableAABB box = null; - + for (Screen scr : screens) { Vector3i f = scr.side.forward; - + int fx = Math.max(f.x, 0); int fy = Math.max(f.y, 0); int fz = Math.max(f.z, 0); @@ -720,13 +704,13 @@ public class TileEntityScreen extends BlockEntity { scr.side.equals(BlockSide.TOP) || scr.side.equals(BlockSide.BOTTOM) ) oz = 1; - + if (box == null) { box = new MutableAABB( origin.x + fx + ox, origin.y + fy, origin.z + fz + oz, - + origin.x + ox + scr.side.right.x * scr.size.x + fx + scr.side.up.x * scr.size.y, origin.y + scr.side.right.y * scr.size.x + fy + scr.side.up.y * scr.size.y, origin.z + oz + scr.side.right.z * scr.size.x + fz + scr.side.up.z * scr.size.y @@ -736,24 +720,24 @@ public class TileEntityScreen extends BlockEntity { origin.x + fx + ox, origin.y + fy, origin.z + fz + oz, - + origin.x + ox + scr.side.right.x * scr.size.x + fx + scr.side.up.x * scr.size.y, origin.y + scr.side.right.y * scr.size.x + fy + scr.side.up.y * scr.size.y, origin.z + oz + scr.side.right.z * scr.size.x + fz + scr.side.up.z * scr.size.y ); } } - + if (box == null) renderBB = new AABB(worldPosition); else renderBB = box.toMc(); } - + @Override @Nonnull public net.minecraft.world.phys.AABB getRenderBoundingBox() { return renderBB; } - + // //FIXME: Not called if enableSoundDistance is false // public void updateTrackDistance(double d, float masterVolume) { // final WebDisplays wd = WebDisplays.INSTANCE; @@ -787,7 +771,7 @@ public class TileEntityScreen extends BlockEntity { // } // } // } - + public void updateClientSideURL(CefBrowser target, String url) { for (Screen scr : screens) { if (scr.browser == target) { @@ -801,23 +785,23 @@ public class TileEntityScreen extends BlockEntity { scr.url = blacklisted ? WebDisplays.BLACKLIST_URL : url; //FIXME: This is an invalid fix for something that CANNOT be fixed scr.videoType = VideoType.getTypeFromURL(scr.url); ytVolume = Float.POSITIVE_INFINITY; //Force volume update - + if (blacklisted && scr.browser != null) scr.browser.loadURL(WebDisplays.BLACKLIST_URL); - + break; } } } - + @Override public void invalidateCaps() { super.invalidateCaps(); - + if (level.isClientSide) onChunkUnloaded(); } - + public void addFriend(ServerPlayer ply, BlockSide side, NameUUIDPair pair) { if (!level.isClientSide) { Screen scr = getScreen(side); @@ -825,7 +809,7 @@ public class TileEntityScreen extends BlockEntity { Log.error("Tried to add friend to invalid screen side %s", side.toString()); return; } - + if (!scr.friends.contains(pair)) { scr.friends.add(pair); (new ScreenConfigData(new Vector3i(getBlockPos()), side, scr)).updateOnly().sendTo(point(level, getBlockPos())); @@ -833,7 +817,7 @@ public class TileEntityScreen extends BlockEntity { } } } - + public void removeFriend(ServerPlayer ply, BlockSide side, NameUUIDPair pair) { if (!level.isClientSide) { Screen scr = getScreen(side); @@ -841,7 +825,7 @@ public class TileEntityScreen extends BlockEntity { Log.error("Tried to remove friend from invalid screen side %s", side.toString()); return; } - + if (scr.friends.remove(pair)) { checkLaserUserRights(scr); (new ScreenConfigData(new Vector3i(getBlockPos()), side, scr)).updateOnly().sendTo(point(level, getBlockPos())); @@ -849,7 +833,7 @@ public class TileEntityScreen extends BlockEntity { } } } - + public void setRights(ServerPlayer ply, BlockSide side, int fr, int or) { if (!level.isClientSide) { Screen scr = getScreen(side); @@ -857,27 +841,27 @@ public class TileEntityScreen extends BlockEntity { Log.error("Tried to change rights of invalid screen on side %s", side.toString()); return; } - + scr.friendRights = fr; scr.otherRights = or; - + checkLaserUserRights(scr); (new ScreenConfigData(new Vector3i(getBlockPos()), side, scr)).updateOnly().sendTo(point(level, getBlockPos())); setChanged(); } } - + public void type(BlockSide side, String text, BlockPos soundPos) { type(side, text, soundPos, null); } - + public void type(BlockSide side, String text, BlockPos soundPos, @Nullable ServerPlayer sender) { Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to type on invalid screen on side %s", side.toString()); return; } - + if (level.isClientSide) { if (scr.browser instanceof MCEFBrowser mcefBrowser) { try { @@ -886,12 +870,12 @@ public class TileEntityScreen extends BlockEntity { char chr = text.charAt(i); if (chr == 1) break; - + mcefBrowser.sendKeyTyped(chr, 0); } } else { TypeData[] data = WebDisplays.GSON.fromJson(text, TypeData[].class); - + for (TypeData ev : data) { if (ev.getKeyCode() == 257) { ev = new TypeData( @@ -900,7 +884,7 @@ public class TileEntityScreen extends BlockEntity { ev.getScanCode() ); } - + switch (ev.getAction()) { case PRESS -> { mcefBrowser.sendKeyPress(ev.getKeyCode(), ev.getScanCode(), ev.getModifier()); @@ -910,7 +894,7 @@ public class TileEntityScreen extends BlockEntity { case RELEASE -> mcefBrowser.sendKeyRelease(ev.getKeyCode(), ev.getScanCode(), ev.getModifier()); case TYPE -> mcefBrowser.sendKeyTyped((char) ev.getKeyCode(), ev.getModifier()); // TODO: check - + default -> throw new RuntimeException("Invalid type action '" + ev.getAction() + '\''); } } @@ -925,20 +909,20 @@ public class TileEntityScreen extends BlockEntity { () -> point(sender, level, getBlockPos()) : () -> point(level, getBlockPos()) ), S2CMessageScreenUpdate.type(this, side, text)); - + if (soundPos != null) playSoundAt(WebDisplays.INSTANCE.soundTyping, soundPos, 0.25f, 1.f); } } - + private void playSoundAt(SoundEvent snd, BlockPos at, float vol, float pitch) { double x = at.getX(); double y = at.getY(); double z = at.getZ(); - + level.playSound(null, x + 0.5, y + 0.5, z + 0.5, snd, SoundSource.BLOCKS, vol, pitch); } - + // public void updateUpgrades(BlockSide side, ItemStack[] upgrades) { // if (!level.isClientSide) { // Log.error("Tried to call TileEntityScreen.updateUpgrades() from server side..."); @@ -957,11 +941,11 @@ public class TileEntityScreen extends BlockEntity { // if (scr.browser != null) // scr.browser.runJS("if(typeof webdisplaysUpgradesChanged == \"function\") webdisplaysUpgradesChanged();", ""); // } - + private static String safeName(ItemStack is) { return is.getItem().getName(is).getString(); } - + //If equal is null, no duplicate check is preformed public boolean addUpgrade(BlockSide side, ItemStack is, @Nullable Player player, boolean abortIfExisting) { if (level.isClientSide) { @@ -974,30 +958,30 @@ public class TileEntityScreen extends BlockEntity { itemAsUpgrade.onInstall(this, side, player, isCopy); return false; } - + Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to add an upgrade on invalid screen on side %s", side.toString()); return false; } - + if (!(is.getItem() instanceof IUpgrade)) { Log.error("Tried to add a non-upgrade item %s to screen (%s does not implement IUpgrade)", safeName(is), is.getItem().getClass().getCanonicalName()); return false; } - + if (scr.upgrades.size() >= 16) { Log.error("Can't insert upgrade %s in screen %s at %s: too many upgrades already!", safeName(is), side.toString(), getBlockPos().toString()); return false; } - + IUpgrade itemAsUpgrade = (IUpgrade) is.getItem(); if (abortIfExisting && scr.upgrades.stream().anyMatch(otherStack -> itemAsUpgrade.isSameUpgrade(is, otherStack))) return false; //Upgrade already exists - + ItemStack isCopy = is.copy(); //FIXME: Duct tape fix, because the original stack will be shrinked isCopy.setCount(1); - + scr.upgrades.add(isCopy); if (player != null && !player.level().isClientSide) { WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.upgrade(this, side, true, is)); @@ -1007,19 +991,19 @@ public class TileEntityScreen extends BlockEntity { setChanged(); return true; } - + public boolean hasUpgrade(BlockSide side, ItemStack is) { Screen scr = getScreen(side); if (scr == null) return false; - + if (!(is.getItem() instanceof IUpgrade)) return false; - + IUpgrade itemAsUpgrade = (IUpgrade) is.getItem(); return scr.upgrades.stream().anyMatch(otherStack -> itemAsUpgrade.isSameUpgrade(is, otherStack)); } - + public boolean hasUpgrade(BlockSide side, DefaultUpgrade du) { Screen scr = getScreen(side); if (du == DefaultUpgrade.LASERMOUSE) { @@ -1034,32 +1018,32 @@ public class TileEntityScreen extends BlockEntity { return false; } } - + public void removeUpgrade(BlockSide side, ItemStack is, @Nullable Player player) { if (level.isClientSide) return; - + Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to remove an upgrade on invalid screen on side %s", side.toString()); return; } - + if (!(is.getItem() instanceof IUpgrade)) { Log.error("Tried to remove a non-upgrade item %s to screen (%s does not implement IUpgrade)", safeName(is), is.getItem().getClass().getCanonicalName()); return; } - + int idxToRemove = -1; IUpgrade itemAsUpgrade = (IUpgrade) is.getItem(); - + for (int i = 0; i < scr.upgrades.size(); i++) { if (itemAsUpgrade.isSameUpgrade(is, scr.upgrades.get(i))) { idxToRemove = i; break; } } - + if (idxToRemove >= 0) { dropUpgrade(scr.upgrades.get(idxToRemove), side, player); scr.upgrades.remove(idxToRemove); @@ -1071,51 +1055,51 @@ public class TileEntityScreen extends BlockEntity { } else Log.warning("Tried to remove non-existing upgrade %s to screen %s at %s", safeName(is), side.toString(), getBlockPos().toString()); } - + private void dropUpgrade(ItemStack is, BlockSide side, @Nullable Player ply) { if (!((IUpgrade) is.getItem()).onRemove(this, side, ply, is)) { //Drop upgrade item boolean spawnDrop = true; - + if (ply != null) { if (ply.isCreative() || ply.addItem(is)) spawnDrop = false; //If in creative or if the item was added to the player's inventory, don't spawn drop entity } - + if (spawnDrop) { Vector3f pos = new Vector3f((float) this.getBlockPos().getX(), (float) this.getBlockPos().getY(), (float) this.getBlockPos().getZ()); pos.addMul(side.backward.toFloat(), 1.5f); - + if (level != null) { level.addFreshEntity(new ItemEntity(level, pos.x, pos.y, pos.z, is)); } } } } - + private Screen getScreenForLaserOp(BlockSide side, Player ply) { if (level.isClientSide) return null; - + Screen scr = getScreen(side); if (scr == null) { Log.error("Called laser operation on invalid screen on side %s", side.toString()); return null; } - + if ((scr.rightsFor(ply) & ScreenRights.INTERACT) == 0) return null; //Don't output an error, it can 'legally' happen - + if (scr.upgrades.stream().noneMatch(DefaultUpgrade.LASERMOUSE::matchesLaserMouse)) { Log.error("Called laser operation on side %s, but it's missing the laser sensor upgrade", side.toString()); return null; } - + return scr; //Okay, go for it... } - + public void laserDownMove(BlockSide side, Player ply, Vector2i pos, boolean down, int button) { Screen scr = getScreenForLaserOp(side, ply); - + if (scr != null) { if (button == -1) WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(ply, level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, ClickControl.ControlType.MOVE, pos)); @@ -1125,10 +1109,10 @@ public class TileEntityScreen extends BlockEntity { WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(ply, level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, ClickControl.ControlType.UP, pos)); } } - + public void laserUp(BlockSide side, Player ply, int button) { Screen scr = getScreenForLaserOp(side, ply); - + if (scr != null) { if (getLaserUser(scr) == ply) { scr.laserUser = null; @@ -1136,13 +1120,13 @@ public class TileEntityScreen extends BlockEntity { } } } - + public void onDestroy(@Nullable Player ply) { for (Screen scr : screens) { scr.upgrades.forEach(is -> dropUpgrade(is, scr.side, ply)); scr.upgrades.clear(); } - + WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.turnOff(getBlockPos(), null)); } @@ -1165,43 +1149,43 @@ public class TileEntityScreen extends BlockEntity { remove.upgrades.clear(); screens.remove(remove); } - + public void setOwner(BlockSide side, Player newOwner) { if (level.isClientSide) { Log.error("Called TileEntityScreen.setOwner() on client..."); return; } - + if (newOwner == null) { Log.error("Called TileEntityScreen.setOwner() with null owner"); return; } - + Screen scr = getScreen(side); if (scr == null) { Log.error("Called TileEntityScreen.setOwner() on invalid screen on side %s", side.toString()); return; } - + scr.owner = new NameUUIDPair(newOwner.getGameProfile()); WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.owner(this, side, scr.owner)); checkLaserUserRights(scr); setChanged(); } - + public void setRotation(BlockSide side, Rotation rot) { Screen scr = getScreen(side); if (scr == null) { Log.error("Trying to change rotation of invalid screen on side %s", side.toString()); return; } - + if (level.isClientSide) { boolean oldWasVertical = scr.rotation.isVertical; scr.rotation = rot; - + WebDisplays.PROXY.screenUpdateRotationInGui(new Vector3i(getBlockPos()), side, rot); - + if (scr.browser != null && oldWasVertical != rot.isVertical) { scr.browser.close(true); scr.browser = null; //Will be re-created by renderer @@ -1212,7 +1196,7 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + // public void evalJS(BlockSide side, String code) { // Screen scr = getScreen(side); // if (scr == null) { @@ -1226,16 +1210,16 @@ public class TileEntityScreen extends BlockEntity { // } //// else WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.js(this, side, code)); // } - + public void setAutoVolume(BlockSide side, boolean av) { Screen scr = getScreen(side); if (scr == null) { Log.error("Trying to toggle auto-volume on invalid screen (side %s)", side.toString()); return; } - + scr.autoVolume = av; - + if (level.isClientSide) WebDisplays.PROXY.screenUpdateAutoVolumeInGui(new Vector3i(getBlockPos()), side, av); else { @@ -1253,4 +1237,49 @@ public class TileEntityScreen extends BlockEntity { // return oldState.getValue(BlockScreen.hasTE) != newState.getValue(BlockScreen.hasTE); // } + + @Override + public void load(CompoundTag tag) { + load(false, tag); + } + + public void load(boolean syncing, CompoundTag tag) { + super.load(tag); + + ListTag list = tag.getList("WDScreens", Tag.TAG_COMPOUND); + if (list.isEmpty()) + return; + + screens.clear(); + for (int i = 0; i < list.size(); i++) { + CompoundTag tg = list.getCompound(i); + screens.add(Screen.deserialize(tg)); + if (WebDisplays.isSSR()) { + if (!syncing) { + screens.get(i).createBrowser(true, false); + } else { + screens.get(i).createBrowser(false, false); + ((RemoteBrowser) screens.get(i).browser).setUUID(tg.getUUID("uuid")); + } + } + } + } + + @Override + protected void saveAdditional(CompoundTag tag) { + saveAdditional(false, tag); + } + + protected void saveAdditional(boolean syncing, CompoundTag tag) { + super.saveAdditional(tag); + + ListTag list = new ListTag(); + for (Screen scr : screens) { + CompoundTag tg = scr.serialize(); + if (syncing) tg.putUUID("uuid", ((IRemoteBrowser) scr.browser).getUUID()); + list.add(tg); + } + + tag.put("WDScreens", list); + } } diff --git a/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java b/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java new file mode 100644 index 0000000..f4b71ba --- /dev/null +++ b/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java @@ -0,0 +1,14 @@ +package net.montoyo.wd.net.client_bound; + +import net.minecraftforge.network.NetworkEvent; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.net.Packet; + +public class S2CMessageEnableSSR extends Packet { + @Override + public void handle(NetworkEvent.Context ctx) { + if (checkClient(ctx)) { + WebDisplays.markSSR(); + } + } +} diff --git a/src/main/java/net/montoyo/wd/remote/BrowserGen.java b/src/main/java/net/montoyo/wd/remote/BrowserGen.java new file mode 100644 index 0000000..dc14056 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/BrowserGen.java @@ -0,0 +1,69 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEF; +import com.cinemamod.mcef.MCEFBrowser; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.remote.client.RemoteBrowser; +import net.montoyo.wd.remote.server.BlankBrowser; +import net.montoyo.wd.remote.server.ServerBrowser; +import org.cef.browser.CefBrowser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.UUID; + +public class BrowserGen { + private static ArrayList browsers = new ArrayList<>(); + private static HashMap clientBrowsers = new HashMap<>(); + private static HashMap serverBrowsers = new HashMap<>(); + + public static IWDBrowser createBrowser(boolean server, String url, boolean transparent) { + CefBrowser browser; + if (!server) { + if (WebDisplays.isSSR()) { + browser = new RemoteBrowser() { + @Override + protected void finalize() throws Throwable { + synchronized (browsers) { + browsers.remove(this); + clientBrowsers.remove(getUUID()); + } + super.finalize(); + } + }; + } else + browser = new WDMCEFBrowser(MCEF.getClient(), url, transparent) { + @Override + public void close() { + synchronized (browsers) { + browsers.remove(this); + super.close(); + } + } + }; + } else { + if (WebDisplays.isSSR()) { + browser = new ServerBrowser(MCEF.getClient(), url, transparent) { + @Override + public void close() { + browsers.remove(this); + synchronized (browsers) { + serverBrowsers.remove(getUUID()); + } + super.close(); + } + }; + serverBrowsers.put(((IRemoteBrowser) browser).getUUID(), (IRemoteBrowser) browser); + } else return null; + } + browser.setCloseAllowed(); + browser.createImmediately(); + browsers.add((IWDBrowser) browser); + return (IWDBrowser) browser; + } + + public static void onUUIDAcquired(RemoteBrowser browser) { + clientBrowsers.put(((IRemoteBrowser) browser).getUUID(), browser); + } +} diff --git a/src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java b/src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java new file mode 100644 index 0000000..0ca88f5 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java @@ -0,0 +1,7 @@ +package net.montoyo.wd.remote; + +import java.util.UUID; + +public interface IRemoteBrowser { + UUID getUUID(); +} diff --git a/src/main/java/net/montoyo/wd/remote/IWDBrowser.java b/src/main/java/net/montoyo/wd/remote/IWDBrowser.java new file mode 100644 index 0000000..23ec226 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/IWDBrowser.java @@ -0,0 +1,24 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEFRenderer; +import com.cinemamod.mcef.listeners.MCEFCursorChangeListener; + +public interface IWDBrowser { + void close(boolean b); + + void loadURL(String url); + + void resize(int y, int x); + + void setCursorChangeListener(MCEFCursorChangeListener mcefCursorChangeListener); + + MCEFRenderer getRenderer(); + + void setFocus(boolean b); + + void sendMouseRelease(int x, int y, int button); + + void sendMouseMove(int x, int y); + + void sendMousePress(int x, int y, int button); +} diff --git a/src/main/java/net/montoyo/wd/remote/VirtualBrowser.java b/src/main/java/net/montoyo/wd/remote/VirtualBrowser.java new file mode 100644 index 0000000..6be03d4 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/VirtualBrowser.java @@ -0,0 +1,275 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEF; +import com.cinemamod.mcef.MCEFRenderer; +import com.cinemamod.mcef.listeners.MCEFCursorChangeListener; +import org.cef.CefClient; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefPdfPrintCallback; +import org.cef.callback.CefRunFileDialogCallback; +import org.cef.callback.CefStringVisitor; +import org.cef.handler.CefDialogHandler; +import org.cef.handler.CefRenderHandler; +import org.cef.handler.CefWindowHandler; +import org.cef.misc.CefPdfPrintSettings; +import org.cef.network.CefRequest; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Vector; +import java.util.concurrent.CompletableFuture; + +public abstract class VirtualBrowser implements CefBrowser, CefRenderHandler, CefWindowHandler, IWDBrowser { + String url; + + @Override + public void createImmediately() { + + } + + @Override + public CefClient getClient() { + return MCEF.getClient().getHandle(); + } + + @Override + public CefRenderHandler getRenderHandler() { + return this; + } + + @Override + public CefWindowHandler getWindowHandler() { + return this; + } + + @Override + public boolean canGoBack() { + return false; + } + + @Override + public void goBack() { + + } + + @Override + public boolean canGoForward() { + return false; + } + + @Override + public void goForward() { + + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public void reload() { + + } + + @Override + public void reloadIgnoreCache() { + + } + + @Override + public void stopLoad() { + + } + + @Override + public int getIdentifier() { + return 0; + } + + @Override + public CefFrame getMainFrame() { + return null; + } + + @Override + public CefFrame getFocusedFrame() { + return null; + } + + @Override + public CefFrame getFrame(long l) { + return null; + } + + @Override + public CefFrame getFrame(String s) { + return null; + } + + @Override + public Vector getFrameIdentifiers() { + return null; + } + + @Override + public Vector getFrameNames() { + return null; + } + + @Override + public int getFrameCount() { + return 0; + } + + @Override + public boolean isPopup() { + return false; + } + + @Override + public boolean hasDocument() { + return false; + } + + @Override + public void viewSource() { + + } + + @Override + public void getSource(CefStringVisitor cefStringVisitor) { + + } + + @Override + public void getText(CefStringVisitor cefStringVisitor) { + + } + + @Override + public void loadRequest(CefRequest cefRequest) { + + } + + @Override + public void loadURL(String s) { + this.url = url; + } + + @Override + public void executeJavaScript(String s, String s1, int i) { + + } + + @Override + public String getURL() { + return url; + } + + @Override + public void close(boolean b) { + + } + + @Override + public void setCloseAllowed() { + + } + + @Override + public boolean doClose() { + return true; + } + + @Override + public void onBeforeClose() { + + } + + @Override + public void setFocus(boolean b) { + + } + + @Override + public void setWindowVisibility(boolean b) { + + } + + @Override + public double getZoomLevel() { + return 0; + } + + @Override + public void setZoomLevel(double v) { + + } + + @Override + public void runFileDialog(CefDialogHandler.FileDialogMode fileDialogMode, String s, String s1, Vector vector, int i, CefRunFileDialogCallback cefRunFileDialogCallback) { + + } + + @Override + public void startDownload(String s) { + + } + + @Override + public void print() { + + } + + @Override + public void printToPDF(String s, CefPdfPrintSettings cefPdfPrintSettings, CefPdfPrintCallback cefPdfPrintCallback) { + + } + + @Override + public void find(String s, boolean b, boolean b1, boolean b2) { + + } + + @Override + public void stopFinding(boolean b) { + + } + + @Override + public CefBrowser getDevTools() { + return this; + } + + @Override + public CefBrowser getDevTools(Point point) { + return this; + } + + @Override + public void replaceMisspelling(String s) { + + } + + @Override + public CompletableFuture createScreenshot(boolean b) { + return null; + } + + @Override + public void resize(int y, int x) { + + } + + @Override + public void setCursorChangeListener(MCEFCursorChangeListener mcefCursorChangeListener) { + + } + + @Override + public MCEFRenderer getRenderer() { + return null; + } +} diff --git a/src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java b/src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java new file mode 100644 index 0000000..f4820dc --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java @@ -0,0 +1,10 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEFBrowser; +import com.cinemamod.mcef.MCEFClient; + +public class WDMCEFBrowser extends MCEFBrowser implements IWDBrowser { + public WDMCEFBrowser(MCEFClient client, String url, boolean transparent) { + super(client, url, transparent); + } +} diff --git a/src/main/java/net/montoyo/wd/remote/client/ImageReader.java b/src/main/java/net/montoyo/wd/remote/client/ImageReader.java new file mode 100644 index 0000000..0bf26b8 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/client/ImageReader.java @@ -0,0 +1,53 @@ +package net.montoyo.wd.remote.client; + +import net.minecraft.network.FriendlyByteBuf; + +import java.nio.ByteBuffer; + +public class ImageReader { + ByteBuffer target; + + public ImageReader(ByteBuffer target) { + this.target = target; + } + + public void read(ByteBuffer buf, int bWidth) { + int lim = buf.limit(); + + int count = buf.getInt(); + for (int i = 0; i < count; i++) { + int x = buf.getInt(); + int y = buf.getInt(); + int width = buf.getInt(); + int height = buf.getInt(); + + for (int y1 = 0; y1 < height; y1++) { + buf.limit(buf.position() + width * 4); + target.position( + x * 4 + (y + y1) * 4 * bWidth + ); + target.put(buf); + buf.limit(lim); + } + } + } + + public void read(FriendlyByteBuf buf, int bWidth) { + int count = buf.readInt(); + for (int i = 0; i < count; i++) { + int x = buf.readInt(); + int y = buf.readInt(); + int width = buf.readInt(); + int height = buf.readInt(); + + for (int y1 = 0; y1 < height; y1++) { + ByteBuffer buf1 = buf.internalNioBuffer(buf.readerIndex(), width * 4); + target.position( + x * 4 + (y + y1) * 4 * bWidth + ); + target.put(buf1); + buf.skipBytes(width * 4); + } + } + } +} diff --git a/src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java b/src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java new file mode 100644 index 0000000..923ff13 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java @@ -0,0 +1,137 @@ +package net.montoyo.wd.remote.client; + +import com.cinemamod.mcef.MCEFRenderer; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.montoyo.wd.remote.IRemoteBrowser; +import net.montoyo.wd.remote.IWDBrowser; +import net.montoyo.wd.remote.VirtualBrowser; +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; +import org.cef.handler.CefScreenInfo; + +import java.awt.*; +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; +import java.util.UUID; + +import static org.lwjgl.opengl.GL11C.*; + +public class RemoteBrowser extends VirtualBrowser implements IRemoteBrowser, IWDBrowser { + UUID myId; + + MCEFRenderer renderer; + + public RemoteBrowser() { + try { + Constructor ctor = MCEFRenderer.class.getDeclaredConstructor(boolean.class); + ctor.setAccessible(true); + renderer = ctor.newInstance(false); + Minecraft.getInstance().submit(renderer::initialize); + } catch (Throwable err) { + throw new RuntimeException(err); + } + } + + @Override + public MCEFRenderer getRenderer() { + return renderer; + } + + @Override + public UUID getUUID() { + return myId; + } + + public void setUUID(UUID uuid) { + myId = uuid; + } + + @Override + public Rectangle getViewRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public boolean getScreenInfo(CefBrowser cefBrowser, CefScreenInfo cefScreenInfo) { + return false; + } + + @Override + public Point getScreenPoint(CefBrowser cefBrowser, Point point) { + return null; + } + + @Override + public void onPopupShow(CefBrowser cefBrowser, boolean b) { + + } + + @Override + public void onPopupSize(CefBrowser cefBrowser, Rectangle rectangle) { + + } + + int lastWidth, lastHeight; + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height) { + if (width != lastWidth || height != lastHeight) { + renderer.onPaint(buffer, width, height); + lastWidth = width; + lastHeight = height; + } else { + if (renderer.getTextureID() == 0) return; + + RenderSystem.bindTexture(renderer.getTextureID()); + if (renderer.isTransparent()) RenderSystem.enableBlend(); + + RenderSystem.pixelStore(GL_UNPACK_ROW_LENGTH, width); + for (Rectangle dirtyRect : dirtyRects) { + GlStateManager._pixelStore(GL_UNPACK_SKIP_PIXELS, dirtyRect.x); + GlStateManager._pixelStore(GL_UNPACK_SKIP_ROWS, dirtyRect.y); + renderer.onPaint(buffer, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); + } + } + } + + @Override + public boolean onCursorChange(CefBrowser cefBrowser, int i) { + return false; + } + + @Override + public boolean startDragging(CefBrowser cefBrowser, CefDragData cefDragData, int i, int i1, int i2) { + return false; + } + + @Override + public void updateDragCursor(CefBrowser cefBrowser, int i) { + + } + + @Override + public Rectangle getRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public void onMouseEvent(CefBrowser cefBrowser, int i, int i1, int i2, int i3, int i4) { + + } + + @Override + public void sendMouseRelease(int x, int y, int button) { + } + + @Override + public void sendMouseMove(int x, int y) { + + } + + @Override + public void sendMousePress(int x, int y, int button) { + + } +} diff --git a/src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java b/src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java new file mode 100644 index 0000000..4251ae7 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java @@ -0,0 +1,78 @@ +package net.montoyo.wd.remote.server; + +import net.montoyo.wd.remote.VirtualBrowser; +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; +import org.cef.handler.CefScreenInfo; + +import java.awt.*; +import java.nio.ByteBuffer; + +public class BlankBrowser extends VirtualBrowser { + @Override + public Rectangle getViewRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public boolean getScreenInfo(CefBrowser cefBrowser, CefScreenInfo cefScreenInfo) { + return false; + } + + @Override + public Point getScreenPoint(CefBrowser cefBrowser, Point point) { + return null; + } + + @Override + public void onPopupShow(CefBrowser cefBrowser, boolean b) { + + } + + @Override + public void onPopupSize(CefBrowser cefBrowser, Rectangle rectangle) { + + } + + @Override + public void onPaint(CefBrowser cefBrowser, boolean b, Rectangle[] rectangles, ByteBuffer byteBuffer, int i, int i1) { + + } + + @Override + public boolean onCursorChange(CefBrowser cefBrowser, int i) { + return false; + } + + @Override + public boolean startDragging(CefBrowser cefBrowser, CefDragData cefDragData, int i, int i1, int i2) { + return false; + } + + @Override + public void updateDragCursor(CefBrowser cefBrowser, int i) { + + } + + @Override + public Rectangle getRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public void onMouseEvent(CefBrowser cefBrowser, int i, int i1, int i2, int i3, int i4) { + + } + + @Override + public void sendMouseRelease(int x, int y, int button) { + } + + @Override + public void sendMouseMove(int x, int y) { + } + + @Override + public void sendMousePress(int x, int y, int button) { + } +} diff --git a/src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java b/src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java new file mode 100644 index 0000000..223b6b5 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java @@ -0,0 +1,61 @@ +package net.montoyo.wd.remote.server; + +import com.cinemamod.mcef.MCEFHeadlessBrowser; +import com.cinemamod.mcef.MCEFClient; +import com.cinemamod.mcef.MCEFRenderer; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.montoyo.wd.remote.IRemoteBrowser; +import net.montoyo.wd.remote.IWDBrowser; +import org.cef.browser.CefBrowser; + +import java.awt.*; +import java.nio.ByteBuffer; +import java.util.UUID; + +public class ServerBrowser extends MCEFHeadlessBrowser implements IRemoteBrowser, IWDBrowser { + private int lastWidth = 0; + private int lastHeight = 0; + + UUID myId; + + ServerRenderer renderer = new ServerRenderer(); + + public ServerBrowser(MCEFClient client, String url, boolean transparent) { + super(client, url, transparent); + this.myId = UUID.randomUUID(); + } + + @Override + public UUID getUUID() { + return myId; + } + + ByteBuffer graphics; + + void store(ByteBuffer srcBuffer, ByteBuffer dstBuffer, Rectangle dirty, int width, int height) { + for (int y = dirty.y; y < dirty.height + dirty.y; y++) { + dstBuffer.position((y * width + dirty.x) * 4); + srcBuffer.position((y * width + dirty.x) * 4); + srcBuffer.limit(dirty.width * 4 + (y * width + dirty.x) * 4); + dstBuffer.put(srcBuffer); + srcBuffer.position(0).limit(srcBuffer.capacity()); + } + dstBuffer.position(0).limit(dstBuffer.capacity()); + } + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height) { + if ((width != this.lastWidth && height != this.lastHeight)) { + renderer.size(width, height); + + renderer.put(buffer, new Rectangle[]{ + new Rectangle(0, 0, width, height) + }, width); + this.lastWidth = width; + this.lastHeight = height; + } else { + renderer.put(buffer, dirtyRects, width); + } + } +} diff --git a/src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java b/src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java new file mode 100644 index 0000000..2713595 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java @@ -0,0 +1,57 @@ +package net.montoyo.wd.remote.server; + +import java.awt.*; +import java.nio.ByteBuffer; + +public class ServerRenderer { + ByteBuffer contentChange; + + public ServerRenderer() { + } + + public void size(int width, int height) { + contentChange = ByteBuffer.allocate(width * height * 4); + } + + protected void checkGrow(int cap) { + if (contentChange.position() + cap > contentChange.capacity()) { + ByteBuffer copy = ByteBuffer.allocate((int) (contentChange.capacity() * 1.5)); + copy.position(0); + contentChange.flip(); + copy.put(contentChange); + contentChange = copy; + } + } + + public void put(ByteBuffer buffer, Rectangle[] dirtyRects, int width) { + contentChange.position(0); + contentChange.limit(contentChange.capacity()); + checkGrow(4); + contentChange.putInt(dirtyRects.length); + for (Rectangle dirtyRect : dirtyRects) { + checkGrow(4 * 4); + contentChange.putInt(dirtyRect.x); + contentChange.putInt(dirtyRect.y); + contentChange.putInt(dirtyRect.width); + contentChange.putInt(dirtyRect.height); + + for (int y = dirtyRect.y; y < dirtyRect.height + dirtyRect.y; y++) { + checkGrow(dirtyRect.width * 8); + buffer.position( + dirtyRect.x * 4 + + (y * width * 4) + ).limit( + (dirtyRect.x + dirtyRect.width) * 4 + + (y * width * 4) + ); + contentChange.put(buffer); + buffer.position(0).limit(buffer.capacity()); + } + } + contentChange.flip(); + } + + public ByteBuffer get() { + return contentChange; + } +} diff --git a/src/test/java/NetworkedBrowserTest.java b/src/test/java/NetworkedBrowserTest.java new file mode 100644 index 0000000..0f30176 --- /dev/null +++ b/src/test/java/NetworkedBrowserTest.java @@ -0,0 +1,43 @@ +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufHolder; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.EmptyByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.montoyo.wd.remote.client.ImageReader; +import net.montoyo.wd.remote.server.ServerRenderer; + +import java.awt.*; +import java.nio.ByteBuffer; +import java.util.Random; + +public class NetworkedBrowserTest { + public static void main(String[] args) { + int w = 45; + ByteBuffer src = ByteBuffer.allocate((w * w) * 4); + Random rng = new Random(); + byte[] bbyte = new byte[1]; + for (int i = 0; i < (w * w) * 4; i++) { + rng.nextBytes(bbyte); + src.put(bbyte[0]); + } + + ServerRenderer renderer = new ServerRenderer(); + renderer.size(w, w); + renderer.put(src, new Rectangle[]{ + new Rectangle(0, 0, w, w) + }, w); + + ByteBuffer buffer = renderer.get(); + + ByteBuffer dst = ByteBuffer.allocate((w * w) * 4); + ImageReader reader = new ImageReader(dst); + reader.read(buffer, w); + + dst.position(0).limit(dst.capacity()); + src.position(0).limit(src.capacity()); + for (int i = 0; i < src.capacity(); i++) { + if (src.get(i) != dst.get(i)) System.err.println("Mismatch at " + i); +// else System.out.println(" Correct at " + i); + } + } +}