From 3f10881a4d368a92ce1bdc957731382667ab1ae4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 27 Mar 2021 00:00:06 -0400 Subject: [PATCH 01/87] Initial commit Commands don't work. Compiling will not work - only works in a development environment. --- .gitignore | 118 +++++++++++ LICENSE | 21 ++ build.gradle | 118 +++++++++++ gradle.properties | 14 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++++++++++++ gradlew.bat | 104 ++++++++++ settings.gradle | 10 + .../com/geysermc/floodgate/FabricMod.java | 46 +++++ .../geysermc/floodgate/FabricPlatform.java | 15 ++ .../floodgate/addon/data/FabricDataAddon.java | 59 ++++++ .../addon/data/FabricDataHandler.java | 117 +++++++++++ .../inject/fabric/FabricInjector.java | 54 +++++ .../listener/FabricEventListener.java | 12 ++ .../logger/Log4jFloodgateLogger.java | 70 +++++++ .../mixin/HandshakeC2SPacketMixin.java | 22 +++ .../mixin/ServerLoginNetworkHandlerMixin.java | 26 +++ .../floodgate/mixin/ServerNetworkIoMixin.java | 25 +++ .../HandshakeS2CPacketAddressGetter.java | 8 + .../ServerLoginNetworkHandlerSetter.java | 12 ++ .../floodgate/module/FabricAddonModule.java | 35 ++++ .../floodgate/module/FabricCommandModule.java | 30 +++ .../module/FabricListenerModule.java | 6 + .../module/FabricPlatformModule.java | 93 +++++++++ .../pluginmessage/FabricSkinApplier.java | 58 ++++++ .../floodgate/util/FabricCommandUtil.java | 160 +++++++++++++++ .../floodgate/util/FabricUserAudience.java | 91 +++++++++ src/main/resources/fabric.mod.json | 31 +++ src/main/resources/floodgate.accesswidener | 4 + src/main/resources/floodgate.mixins.json | 14 ++ 31 files changed, 1563 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/com/geysermc/floodgate/FabricMod.java create mode 100644 src/main/java/com/geysermc/floodgate/FabricPlatform.java create mode 100644 src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java create mode 100644 src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java create mode 100644 src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java create mode 100644 src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java create mode 100644 src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java create mode 100644 src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java create mode 100644 src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java create mode 100644 src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java create mode 100644 src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java create mode 100644 src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java create mode 100644 src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java create mode 100644 src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java create mode 100644 src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java create mode 100644 src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java create mode 100644 src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java create mode 100644 src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java create mode 100644 src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java create mode 100644 src/main/resources/fabric.mod.json create mode 100644 src/main/resources/floodgate.accesswidener create mode 100644 src/main/resources/floodgate.mixins.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3c37caf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,118 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +run/ + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..25d80aaa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 GeyserMC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..e8ce13f1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,118 @@ +plugins { + id 'fabric-loom' version '0.7-SNAPSHOT' + id 'maven-publish' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +archivesBaseName = project.archives_base_name +version = project.mod_version +group = project.maven_group + +minecraft { + accessWidener = file("src/main/resources/floodgate.accesswidener") +} + +dependencies { + //to change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. + + include(implementation("org.geysermc.floodgate:common:2.0-SNAPSHOT")) + + // Cloud - commands + // Snapshot in order to get Fabric version + include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0-SNAPSHOT')) + + // Fabric Adventure platform + // For translating from Adventure to Minecraft + include(modImplementation('net.kyori:adventure-platform-fabric:4.0.0-SNAPSHOT') { + exclude group: 'ca.stellardrift', module: "colonel" + }) + + // Lombok + compileOnly 'org.projectlombok:lombok:1.18.16' + annotationProcessor 'org.projectlombok:lombok:1.18.16' +} + +repositories { + //mavenLocal() + // For Cloud snapshots + maven { + url = 'https://oss.sonatype.org/content/repositories/snapshots' + } + maven { + url = "https://repo.incendo.org/content/repositories/snapshots" + } + // Standard OpenCollab repositories + maven { + name = 'opencollab-release-repo' + url = 'https://repo.opencollab.dev/maven-releases/' + //TODO set as releases + } + maven { + name = 'opencollab-snapshot-repo' + url = 'https://repo.opencollab.dev/maven-snapshots/' + } +} + +processResources { + inputs.property "version", project.version + + from(sourceSets.main.resources.srcDirs) { + include "fabric.mod.json" + expand "version": project.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude "fabric.mod.json" + } +} + +// ensure that the encoding is set to UTF-8, no matter what the system default is +// this fixes some edge cases with special characters not displaying correctly +// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task +// if it is present. +// If you remove this task, sources will not be generated. +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = "sources" + from sourceSets.main.allSource +} + +jar { + from "LICENSE" +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + // add all the jars that should be included when publishing to maven + artifact(remapJar) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } + } + } + + // select the repositories you want to publish to + repositories { + // uncomment to publish to the local maven + mavenLocal() + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..4026a377 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,14 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.16.5 +yarn_mappings=1.16.5+build.6 +loader_version=0.11.3 +# Mod Properties +mod_version=2.0-SNAPSHOT +maven_group=com.geysermc.floodgate +archives_base_name=floodgate-fabric +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.25.1+build.416-1.16 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 zcma&ObC74zk}X`WF59+k+qTVL*+!RbS9RI8Z5v&-ZFK4Nn|tqzcjwK__x+Iv5xL`> zj94dg?X`0sMHx^qXds{;KY)OMg#H>35XgTVfq6#vc9ww|9) z@UMfwUqk)B9p!}NrNqTlRO#i!ALOPcWo78-=iy}NsAr~T8T0X0%G{DhX~u-yEwc29WQ4D zuv2j{a&j?qB4wgCu`zOXj!~YpTNFg)TWoV>DhYlR^Gp^rkOEluvxkGLB?!{fD!T@( z%3cy>OkhbIKz*R%uoKqrg1%A?)uTZD&~ssOCUBlvZhx7XHQ4b7@`&sPdT475?*zWy z>xq*iK=5G&N6!HiZaD{NSNhWL;+>Quw_#ZqZbyglna!Fqn3N!$L`=;TFPrhodD-Q` z1l*=DP2gKJP@)cwI@-M}?M$$$%u~=vkeC%>cwR$~?y6cXx-M{=wdT4|3X(@)a|KkZ z`w$6CNS@5gWS7s7P86L<=vg$Mxv$?)vMj3`o*7W4U~*Nden}wz=y+QtuMmZ{(Ir1D zGp)ZsNiy{mS}Au5;(fYf93rs^xvi(H;|H8ECYdC`CiC&G`zw?@)#DjMc7j~daL_A$ z7e3nF2$TKlTi=mOftyFBt8*Xju-OY@2k@f3YBM)-v8+5_o}M?7pxlNn)C0Mcd@87?+AA4{Ti2ptnYYKGp`^FhcJLlT%RwP4k$ad!ho}-^vW;s{6hnjD0*c39k zrm@PkI8_p}mnT&5I@=O1^m?g}PN^8O8rB`;t`6H+?Su0IR?;8txBqwK1Au8O3BZAX zNdJB{bpQWR@J|e=Z>XSXV1DB{uhr3pGf_tb)(cAkp)fS7*Qv))&Vkbb+cvG!j}ukd zxt*C8&RN}5ck{jkw0=Q7ldUp0FQ&Pb_$M7a@^nf`8F%$ftu^jEz36d#^M8Ia{VaTy z5(h$I)*l3i!VpPMW+XGgzL~fcN?{~1QWu9!Gu0jOWWE zNW%&&by0DbXL&^)r-A*7R@;T$P}@3eOj#gqJ!uvTqBL5bupU91UK#d|IdxBUZAeh1 z>rAI#*Y4jv>uhOh7`S@mnsl0g@1C;k$Z%!d*n8#_$)l}-1&z2kr@M+xWoKR z!KySy-7h&Bf}02%JeXmQGjO3ntu={K$jy$rFwfSV8!zqAL_*&e2|CJ06`4&0+ceI026REfNT>JzAdwmIlKLEr2? zaZ#d*XFUN*gpzOxq)cysr&#6zNdDDPH% zd8_>3B}uA7;bP4fKVdd~Og@}dW#74ceETOE- zlZgQqQfEc?-5ly(Z5`L_CCM!&Uxk5#wgo=OLs-kFHFG*cTZ)$VE?c_gQUW&*!2@W2 z7Lq&_Kf88OCo?BHCtwe*&fu&8PQ(R5&lnYo8%+U73U)Ec2&|A)Y~m7(^bh299REPe zn#gyaJ4%o4>diN3z%P5&_aFUmlKytY$t21WGwx;3?UC}vlxi-vdEQgsKQ;=#sJ#ll zZeytjOad$kyON4XxC}frS|Ybh`Yq!<(IrlOXP3*q86ImyV*mJyBn$m~?#xp;EplcM z+6sez%+K}Xj3$YN6{}VL;BZ7Fi|iJj-ywlR+AP8lq~mnt5p_%VmN{Sq$L^z!otu_u znVCl@FgcVXo510e@5(wnko%Pv+^r^)GRh;>#Z(|#cLnu_Y$#_xG&nvuT+~gzJsoSi zBvX`|IS~xaold!`P!h(v|=>!5gk)Q+!0R1Ge7!WpRP{*Ajz$oGG$_?Ajvz6F0X?809o`L8prsJ*+LjlGfSziO;+ zv>fyRBVx#oC0jGK8$%$>Z;0+dfn8x;kHFQ?Rpi7(Rc{Uq{63Kgs{IwLV>pDK7yX-2 zls;?`h!I9YQVVbAj7Ok1%Y+F?CJa-Jl>1x#UVL(lpzBBH4(6v0^4 z3Tf`INjml5`F_kZc5M#^J|f%7Hgxg3#o}Zwx%4l9yYG!WaYUA>+dqpRE3nw#YXIX%= ziH3iYO~jr0nP5xp*VIa#-aa;H&%>{mfAPPlh5Fc!N7^{!z$;p-p38aW{gGx z)dFS62;V;%%fKp&i@+5x=Cn7Q>H`NofJGXmNeh{sOL+Nk>bQJJBw3K*H_$}%*xJM=Kh;s#$@RBR z|75|g85da@#qT=pD777m$wI!Q8SC4Yw3(PVU53bzzGq$IdGQoFb-c_(iA_~qD|eAy z@J+2!tc{|!8fF;%6rY9`Q!Kr>MFwEH%TY0y>Q(D}xGVJM{J{aGN0drG&|1xO!Ttdw z-1^gQ&y~KS5SeslMmoA$Wv$ly={f}f9<{Gm!8ycp*D9m*5Ef{ymIq!MU01*)#J1_! zM_i4{LYButqlQ>Q#o{~W!E_#(S=hR}kIrea_67Z5{W>8PD>g$f;dTvlD=X@T$8D0;BWkle@{VTd&D5^)U>(>g(jFt4lRV6A2(Te->ooI{nk-bZ(gwgh zaH4GT^wXPBq^Gcu%xW#S#p_&x)pNla5%S5;*OG_T^PhIIw1gXP&u5c;{^S(AC*+$> z)GuVq(FT@zq9;i{*9lEsNJZ)??BbSc5vF+Kdh-kL@`(`l5tB4P!9Okin2!-T?}(w% zEpbEU67|lU#@>DppToestmu8Ce=gz=e#V+o)v)#e=N`{$MI5P0O)_fHt1@aIC_QCv=FO`Qf=Ga%^_NhqGI)xtN*^1n{ z&vgl|TrKZ3Vam@wE0p{c3xCCAl+RqFEse@r*a<3}wmJl-hoJoN<|O2zcvMRl<#BtZ z#}-bPCv&OTw`GMp&n4tutf|er`@#d~7X+);##YFSJ)BitGALu}-N*DJdCzs(cQ?I- z6u(WAKH^NUCcOtpt5QTsQRJ$}jN28ZsYx+4CrJUQ%egH zo#tMoywhR*oeIkS%}%WUAIbM`D)R6Ya&@sZvvUEM7`fR0Ga03*=qaEGq4G7-+30Ck zRkje{6A{`ebq?2BTFFYnMM$xcQbz0nEGe!s%}O)m={`075R0N9KTZ>vbv2^eml>@}722%!r#6Wto}?vNst? zs`IasBtcROZG9+%rYaZe^=5y3chDzBf>;|5sP0!sP(t^= z^~go8msT@|rp8LJ8km?4l?Hb%o10h7(ixqV65~5Y>n_zG3AMqM3UxUNj6K-FUgMT7 z*Dy2Y8Ws+%`Z*~m9P zCWQ8L^kA2$rf-S@qHow$J86t)hoU#XZ2YK~9GXVR|*`f6`0&8j|ss_Ai-x=_;Df^*&=bW$1nc{Gplm zF}VF`w)`5A;W@KM`@<9Bw_7~?_@b{Z`n_A6c1AG#h#>Z$K>gX6reEZ*bZRjCup|0# zQ{XAb`n^}2cIwLTN%5Ix`PB*H^(|5S{j?BwItu+MS`1)VW=TnUtt6{3J!WR`4b`LW z?AD#ZmoyYpL=903q3LSM=&5eNP^dwTDRD~iP=}FXgZ@2WqfdyPYl$9do?wX{RU*$S zgQ{OqXK-Yuf4+}x6P#A*la&^G2c2TC;aNNZEYuB(f25|5eYi|rd$;i0qk7^3Ri8of ziP~PVT_|4$n!~F-B1_Et<0OJZ*e+MN;5FFH`iec(lHR+O%O%_RQhvbk-NBQ+$)w{D+dlA0jxI;z|P zEKW`!X)${xzi}Ww5G&@g0akBb_F`ziv$u^hs0W&FXuz=Ap>SUMw9=M?X$`lgPRq11 zqq+n44qL;pgGO+*DEc+Euv*j(#%;>p)yqdl`dT+Og zZH?FXXt`<0XL2@PWYp|7DWzFqxLK)yDXae&3P*#+f+E{I&h=$UPj;ey9b`H?qe*Oj zV|-qgI~v%&oh7rzICXfZmg$8$B|zkjliQ=e4jFgYCLR%yi!9gc7>N z&5G#KG&Hr+UEfB;M(M>$Eh}P$)<_IqC_WKOhO4(cY@Gn4XF(#aENkp&D{sMQgrhDT zXClOHrr9|POHqlmm+*L6CK=OENXbZ+kb}t>oRHE2xVW<;VKR@ykYq04LM9L-b;eo& zl!QQo!Sw{_$-qosixZJWhciN>Gbe8|vEVV2l)`#5vKyrXc6E`zmH(76nGRdL)pqLb@j<&&b!qJRLf>d`rdz}^ZSm7E;+XUJ ziy;xY&>LM?MA^v0Fu8{7hvh_ynOls6CI;kQkS2g^OZr70A}PU;i^~b_hUYN1*j-DD zn$lHQG9(lh&sDii)ip*{;Sb_-Anluh`=l~qhqbI+;=ZzpFrRp&T+UICO!OoqX@Xr_ z32iJ`xSpx=lDDB_IG}k+GTYG@K8{rhTS)aoN8D~Xfe?ul&;jv^E;w$nhu-ICs&Q)% zZ=~kPNZP0-A$pB8)!`TEqE`tY3Mx^`%O`?EDiWsZpoP`e-iQ#E>fIyUx8XN0L z@S-NQwc;0HjSZKWDL}Au_Zkbh!juuB&mGL0=nO5)tUd_4scpPy&O7SNS^aRxUy0^< zX}j*jPrLP4Pa0|PL+nrbd4G;YCxCK-=G7TG?dby~``AIHwxqFu^OJhyIUJkO0O<>_ zcpvg5Fk$Wpj}YE3;GxRK67P_Z@1V#+pu>pRj0!mFf(m_WR3w3*oQy$s39~U7Cb}p(N&8SEwt+)@%o-kW9Ck=^?tvC2$b9% ze9(Jn+H`;uAJE|;$Flha?!*lJ0@lKfZM>B|c)3lIAHb;5OEOT(2453m!LgH2AX=jK zQ93An1-#l@I@mwB#pLc;M7=u6V5IgLl>E%gvE|}Hvd4-bE1>gs(P^C}gTv*&t>W#+ zASLRX$y^DD3Jrht zwyt`yuA1j(TcP*0p*Xkv>gh+YTLrcN_HuaRMso~0AJg`^nL#52dGBzY+_7i)Ud#X) zVwg;6$WV20U2uyKt8<)jN#^1>PLg`I`@Mmut*Zy!c!zshSA!e^tWVoKJD%jN&ml#{ z@}B$j=U5J_#rc%T7(DGKF+WwIblEZ;Vq;CsG~OKxhWYGJx#g7fxb-_ya*D0=_Ys#f zhXktl=Vnw#Z_neW>Xe#EXT(4sT^3p6srKby4Ma5LLfh6XrHGFGgM;5Z}jv-T!f~=jT&n>Rk z4U0RT-#2fsYCQhwtW&wNp6T(im4dq>363H^ivz#>Sj;TEKY<)dOQU=g=XsLZhnR>e zd}@p1B;hMsL~QH2Wq>9Zb; zK`0`09fzuYg9MLJe~cdMS6oxoAD{kW3sFAqDxvFM#{GpP^NU@9$d5;w^WgLYknCTN z0)N425mjsJTI@#2kG-kB!({*+S(WZ-{SckG5^OiyP%(6DpRsx60$H8M$V65a_>oME z^T~>oG7r!ew>Y)&^MOBrgc-3PezgTZ2xIhXv%ExMFgSf5dQbD=Kj*!J4k^Xx!Z>AW ziZfvqJvtm|EXYsD%A|;>m1Md}j5f2>kt*gngL=enh<>#5iud0dS1P%u2o+>VQ{U%(nQ_WTySY(s#~~> zrTsvp{lTSup_7*Xq@qgjY@1#bisPCRMMHnOL48qi*jQ0xg~TSW%KMG9zN1(tjXix()2$N}}K$AJ@GUth+AyIhH6Aeh7qDgt#t*`iF5#A&g4+ zWr0$h9Zx6&Uo2!Ztcok($F>4NA<`dS&Js%L+67FT@WmI)z#fF~S75TUut%V($oUHw z$IJsL0X$KfGPZYjB9jaj-LaoDD$OMY4QxuQ&vOGo?-*9@O!Nj>QBSA6n$Lx|^ zky)4+sy{#6)FRqRt6nM9j2Lzba!U;aL%ZcG&ki1=3gFx6(&A3J-oo|S2_`*w9zT)W z4MBOVCp}?4nY)1))SOX#6Zu0fQQ7V{RJq{H)S#;sElY)S)lXTVyUXTepu4N)n85Xo zIpWPT&rgnw$D2Fsut#Xf-hO&6uA0n~a;a3!=_!Tq^TdGE&<*c?1b|PovU}3tfiIUu z){4W|@PY}zJOXkGviCw^x27%K_Fm9GuKVpd{P2>NJlnk^I|h2XW0IO~LTMj>2<;S* zZh2uRNSdJM$U$@=`zz}%;ucRx{aKVxxF7?0hdKh6&GxO6f`l2kFncS3xu0Ly{ew0& zeEP*#lk-8-B$LD(5yj>YFJ{yf5zb41PlW7S{D9zC4Aa4nVdkDNH{UsFJp)q-`9OYt zbOKkigbmm5hF?tttn;S4g^142AF^`kiLUC?e7=*JH%Qe>uW=dB24NQa`;lm5yL>Dyh@HbHy-f%6Vz^ zh&MgwYsh(z#_fhhqY$3*f>Ha}*^cU-r4uTHaT?)~LUj5``FcS46oyoI5F3ZRizVD% zPFY(_S&5GN8$Nl2=+YO6j4d|M6O7CmUyS&}m4LSn6}J`$M0ZzT&Ome)ZbJDFvM&}A zZdhDn(*viM-JHf84$!I(8eakl#zRjJH4qfw8=60 z11Ely^FyXjVvtv48-Fae7p=adlt9_F^j5#ZDf7)n!#j?{W?@j$Pi=k`>Ii>XxrJ?$ z^bhh|X6qC8d{NS4rX5P!%jXy=>(P+r9?W(2)|(=a^s^l~x*^$Enw$~u%WRuRHHFan{X|S;FD(Mr z@r@h^@Bs#C3G;~IJMrERd+D!o?HmFX&#i|~q(7QR3f8QDip?ms6|GV_$86aDb|5pc?_-jo6vmWqYi{P#?{m_AesA4xX zi&ki&lh0yvf*Yw~@jt|r-=zpj!bw<6zI3Aa^Wq{|*WEC}I=O!Re!l~&8|Vu<$yZ1p zs-SlwJD8K!$(WWyhZ+sOqa8cciwvyh%zd`r$u;;fsHn!hub0VU)bUv^QH?x30#;tH zTc_VbZj|prj7)d%ORU;Vs{#ERb>K8>GOLSImnF7JhR|g$7FQTU{(a7RHQ*ii-{U3X z^7+vM0R$8b3k1aSU&kxvVPfOz3~)0O2iTYinV9_5{pF18j4b{o`=@AZIOAwwedB2@ ztXI1F04mg{<>a-gdFoRjq$6#FaevDn$^06L)k%wYq03&ysdXE+LL1#w$rRS1Y;BoS zH1x}{ms>LHWmdtP(ydD!aRdAa(d@csEo z0EF9L>%tppp`CZ2)jVb8AuoYyu;d^wfje6^n6`A?6$&%$p>HcE_De-Zh)%3o5)LDa zskQ}%o7?bg$xUj|n8gN9YB)z!N&-K&!_hVQ?#SFj+MpQA4@4oq!UQ$Vm3B`W_Pq3J z=ngFP4h_y=`Iar<`EESF9){%YZVyJqLPGq07TP7&fSDmnYs2NZQKiR%>){imTBJth zPHr@p>8b+N@~%43rSeNuOz;rgEm?14hNtI|KC6Xz1d?|2J`QS#`OW7gTF_;TPPxu@ z)9J9>3Lx*bc>Ielg|F3cou$O0+<b34_*ZJhpS&$8DP>s%47a)4ZLw`|>s=P_J4u z?I_%AvR_z8of@UYWJV?~c4Yb|A!9n!LEUE6{sn@9+D=0w_-`szJ_T++x3MN$v-)0d zy`?1QG}C^KiNlnJBRZBLr4G~15V3$QqC%1G5b#CEB0VTr#z?Ug%Jyv@a`QqAYUV~^ zw)d|%0g&kl{j#FMdf$cn(~L@8s~6eQ)6{`ik(RI(o9s0g30Li{4YoxcVoYd+LpeLz zai?~r)UcbYr@lv*Z>E%BsvTNd`Sc?}*}>mzJ|cr0Y(6rA7H_6&t>F{{mJ^xovc2a@ zFGGDUcGgI-z6H#o@Gj29C=Uy{wv zQHY2`HZu8+sBQK*_~I-_>fOTKEAQ8_Q~YE$c?cSCxI;vs-JGO`RS464Ft06rpjn+a zqRS0Y3oN(9HCP@{J4mOWqIyD8PirA!pgU^Ne{LHBG;S*bZpx3|JyQDGO&(;Im8!ed zNdpE&?3U?E@O~>`@B;oY>#?gXEDl3pE@J30R1;?QNNxZ?YePc)3=NS>!STCrXu*lM z69WkLB_RBwb1^-zEm*tkcHz3H;?v z;q+x0Jg$|?5;e1-kbJnuT+^$bWnYc~1qnyVTKh*cvM+8yJT-HBs1X@cD;L$su65;i z2c1MxyL~NuZ9+)hF=^-#;dS#lFy^Idcb>AEDXu1!G4Kd8YPy~0lZz$2gbv?su}Zn} zGtIbeYz3X8OA9{sT(aleold_?UEV{hWRl(@)NH6GFH@$<8hUt=dNte%e#Jc>7u9xi zuqv!CRE@!fmZZ}3&@$D>p0z=*dfQ_=IE4bG0hLmT@OP>x$e`qaqf_=#baJ8XPtOpWi%$ep1Y)o2(sR=v)M zt(z*pGS$Z#j_xq_lnCr+x9fwiT?h{NEn#iK(o)G&Xw-#DK?=Ms6T;%&EE${Gq_%99 z6(;P~jPKq9llc+cmI(MKQ6*7PcL)BmoI}MYFO)b3-{j>9FhNdXLR<^mnMP`I7z0v` zj3wxcXAqi4Z0kpeSf>?V_+D}NULgU$DBvZ^=0G8Bypd7P2>;u`yW9`%4~&tzNJpgp zqB+iLIM~IkB;ts!)exn643mAJ8-WlgFE%Rpq!UMYtB?$5QAMm)%PT0$$2{>Yu7&U@ zh}gD^Qdgu){y3ANdB5{75P;lRxSJPSpQPMJOiwmpMdT|?=q;&$aTt|dl~kvS z+*i;6cEQJ1V`R4Fd>-Uzsc=DPQ7A7#VPCIf!R!KK%LM&G%MoZ0{-8&99H!|UW$Ejv zhDLX3ESS6CgWTm#1ZeS2HJb`=UM^gsQ84dQpX(ESWSkjn>O zVxg%`@mh(X9&&wN$lDIc*@>rf?C0AD_mge3f2KkT6kGySOhXqZjtA?5z`vKl_{(5g z&%Y~9p?_DL{+q@siT~*3Q*$nWXQfNN;%s_eHP_A;O`N`SaoB z6xYR;z_;HQ2xAa9xKgx~2f2xEKiEDpGPH1d@||v#f#_Ty6_gY>^oZ#xac?pc-F`@ z*}8sPV@xiz?efDMcmmezYVw~qw=vT;G1xh+xRVBkmN66!u(mRG3G6P#v|;w@anEh7 zCf94arw%YB*=&3=RTqX?z4mID$W*^+&d6qI*LA-yGme;F9+wTsNXNaX~zl2+qIK&D-aeN4lr0+yP;W>|Dh?ms_ogT{DT+ ztXFy*R7j4IX;w@@R9Oct5k2M%&j=c_rWvoul+` z<18FH5D@i$P38W9VU2(EnEvlJ(SHCqTNBa)brkIjGP|jCnK&Qi%97tikU}Y#3L?s! z2ujL%YiHO-#!|g5066V01hgT#>fzls7P>+%D~ogOT&!Whb4iF=CnCto82Yb#b`YoVsj zS2q^W0Rj!RrM@=_GuPQy5*_X@Zmu`TKSbqEOP@;Ga&Rrr>#H@L41@ZX)LAkbo{G8+ z;!5EH6vv-ip0`tLB)xUuOX(*YEDSWf?PIxXe`+_B8=KH#HFCfthu}QJylPMTNmoV; zC63g%?57(&osaH^sxCyI-+gwVB|Xs2TOf=mgUAq?V~N_5!4A=b{AXbDae+yABuuu3B_XSa4~c z1s-OW>!cIkjwJf4ZhvT|*IKaRTU)WAK=G|H#B5#NB9<{*kt?7`+G*-^<)7$Iup@Um z7u*ABkG3F*Foj)W9-I&@BrN8(#$7Hdi`BU#SR1Uz4rh&=Ey!b76Qo?RqBJ!U+rh(1 znw@xw5$)4D8OWtB_^pJO*d~2Mb-f~>I!U#*=Eh*xa6$LX?4Evp4%;ENQR!mF4`f7F zpG!NX=qnCwE8@NAbQV`*?!v0;NJ(| zBip8}VgFVsXFqslXUV>_Z>1gmD(7p#=WACXaB|Y`=Kxa=p@_ALsL&yAJ`*QW^`2@% zW7~Yp(Q@ihmkf{vMF?kqkY%SwG^t&CtfRWZ{syK@W$#DzegcQ1>~r7foTw3^V1)f2Tq_5f$igmfch;8 zT-<)?RKcCdQh6x^mMEOS;4IpQ@F2q-4IC4%*dU@jfHR4UdG>Usw4;7ESpORL|2^#jd+@zxz{(|RV*1WKrw-)ln*8LnxVkKDfGDHA%7`HaiuvhMu%*mY9*Ya{Ti#{DW?i0 zXXsp+Bb(_~wv(3t70QU3a$*<$1&zm1t++x#wDLCRI4K)kU?Vm9n2c0m@TyUV&&l9%}fulj!Z9)&@yIcQ3gX}l0b1LbIh4S z5C*IDrYxR%qm4LVzSk{0;*npO_SocYWbkAjA6(^IAwUnoAzw_Uo}xYFo?Y<-4Zqec z&k7HtVlFGyt_pA&kX%P8PaRD8y!Wsnv}NMLNLy-CHZf(ObmzV|t-iC#@Z9*d-zUsx zxcYWw{H)nYXVdnJu5o-U+fn~W z-$h1ax>h{NlWLA7;;6TcQHA>UJB$KNk74T1xNWh9)kwK~wX0m|Jo_Z;g;>^E4-k4R zRj#pQb-Hg&dAh}*=2;JY*aiNZzT=IU&v|lQY%Q|=^V5pvTR7^t9+@+ST&sr!J1Y9a z514dYZn5rg6@4Cy6P`-?!3Y& z?B*5zw!mTiD2)>f@3XYrW^9V-@%YFkE_;PCyCJ7*?_3cR%tHng9%ZpIU}LJM=a+0s z(SDDLvcVa~b9O!cVL8)Q{d^R^(bbG=Ia$)dVN_tGMee3PMssZ7Z;c^Vg_1CjZYTnq z)wnF8?=-MmqVOMX!iE?YDvHCN?%TQtKJMFHp$~kX4}jZ;EDqP$?jqJZjoa2PM@$uZ zF4}iab1b5ep)L;jdegC3{K4VnCH#OV;pRcSa(&Nm50ze-yZ8*cGv;@+N+A?ncc^2z9~|(xFhwOHmPW@ zR5&)E^YKQj@`g=;zJ_+CLamsPuvppUr$G1#9urUj+p-mPW_QSSHkPMS!52t>Hqy|g z_@Yu3z%|wE=uYq8G>4`Q!4zivS}+}{m5Zjr7kMRGn_p&hNf|pc&f9iQ`^%78rl#~8 z;os@rpMA{ZioY~(Rm!Wf#Wx##A0PthOI341QiJ=G*#}pDAkDm+{0kz&*NB?rC0-)glB{0_Tq*^o zVS1>3REsv*Qb;qg!G^9;VoK)P*?f<*H&4Su1=}bP^Y<2PwFpoqw#up4IgX3L z`w~8jsFCI3k~Y9g(Y9Km`y$0FS5vHb)kb)Jb6q-9MbO{Hbb zxg?IWQ1ZIGgE}wKm{axO6CCh~4DyoFU+i1xn#oyfe+<{>=^B5tm!!*1M?AW8c=6g+%2Ft97_Hq&ZmOGvqGQ!Bn<_Vw`0DRuDoB6q8ME<;oL4kocr8E$NGoLI zXWmI7Af-DR|KJw!vKp2SI4W*x%A%5BgDu%8%Iato+pWo5`vH@!XqC!yK}KLzvfS(q z{!y(S-PKbk!qHsgVyxKsQWk_8HUSSmslUA9nWOjkKn0%cwn%yxnkfxn?Y2rysXKS=t-TeI%DN$sQ{lcD!(s>(4y#CSxZ4R} zFDI^HPC_l?uh_)-^ppeYRkPTPu~V^0Mt}#jrTL1Q(M;qVt4zb(L|J~sxx7Lva9`mh zz!#A9tA*6?q)xThc7(gB2Ryam$YG4qlh00c}r&$y6u zIN#Qxn{7RKJ+_r|1G1KEv!&uKfXpOVZ8tK{M775ws%nDyoZ?bi3NufNbZs)zqXiqc zqOsK@^OnlFMAT&mO3`@3nZP$3lLF;ds|;Z{W(Q-STa2>;)tjhR17OD|G>Q#zJHb*> zMO<{WIgB%_4MG0SQi2;%f0J8l_FH)Lfaa>*GLobD#AeMttYh4Yfg22@q4|Itq};NB z8;o*+@APqy@fPgrc&PTbGEwdEK=(x5K!If@R$NiO^7{#j9{~w=RBG)ZkbOw@$7Nhl zyp{*&QoVBd5lo{iwl2gfyip@}IirZK;ia(&ozNl!-EEYc=QpYH_= zJkv7gA{!n4up6$CrzDJIBAdC7D5D<_VLH*;OYN>_Dx3AT`K4Wyx8Tm{I+xplKP6k7 z2sb!i7)~%R#J0$|hK?~=u~rnH7HCUpsQJujDDE*GD`qrWWog+C+E~GGy|Hp_t4--} zrxtrgnPh}r=9o}P6jpAQuDN}I*GI`8&%Lp-C0IOJt#op)}XSr!ova@w{jG2V=?GXl3zEJJFXg)U3N>BQP z*Lb@%Mx|Tu;|u>$-K(q^-HG!EQ3o93%w(A7@ngGU)HRWoO&&^}U$5x+T&#zri>6ct zXOB#EF-;z3j311K`jrYyv6pOPF=*`SOz!ack=DuEi({UnAkL5H)@R?YbRKAeP|06U z?-Ns0ZxD0h9D8)P66Sq$w-yF+1hEVTaul%&=kKDrQtF<$RnQPZ)ezm1`aHIjAY=!S z`%vboP`?7mItgEo4w50C*}Ycqp9_3ZEr^F1;cEhkb`BNhbc6PvnXu@wi=AoezF4~K zkxx%ps<8zb=wJ+9I8o#do)&{(=yAlNdduaDn!=xGSiuo~fLw~Edw$6;l-qaq#Z7?# zGrdU(Cf-V@$x>O%yRc6!C1Vf`b19ly;=mEu8u9|zitcG^O`lbNh}k=$%a)UHhDwTEKis2yc4rBGR>l*(B$AC7ung&ssaZGkY-h(fpwcPyJSx*9EIJMRKbMP9}$nVrh6$g-Q^5Cw)BeWqb-qi#37ZXKL!GR;ql)~ z@PP*-oP?T|ThqlGKR84zi^CN z4TZ1A)7vL>ivoL2EU_~xl-P{p+sE}9CRwGJDKy{>0KP+gj`H9C+4fUMPnIB1_D`A- z$1`G}g0lQmqMN{Y&8R*$xYUB*V}dQPxGVZQ+rH!DVohIoTbh%#z#Tru%Px@C<=|og zGDDwGq7yz`%^?r~6t&>x*^We^tZ4!E4dhwsht#Pb1kCY{q#Kv;z%Dp#Dq;$vH$-(9 z8S5tutZ}&JM2Iw&Y-7KY4h5BBvS=Ove0#+H2qPdR)WyI zYcj)vB=MA{7T|3Ij_PN@FM@w(C9ANBq&|NoW30ccr~i#)EcH)T^3St~rJ0HKKd4wr z@_+132;Bj+>UC@h)Ap*8B4r5A1lZ!Dh%H7&&hBnlFj@eayk=VD*i5AQc z$uN8YG#PL;cuQa)Hyt-}R?&NAE1QT>svJDKt*)AQOZAJ@ zyxJoBebiobHeFlcLwu_iI&NEZuipnOR;Tn;PbT1Mt-#5v5b*8ULo7m)L-eti=UcGf zRZXidmxeFgY!y80-*PH-*=(-W+fK%KyUKpg$X@tuv``tXj^*4qq@UkW$ZrAo%+hay zU@a?z&2_@y)o@D!_g>NVxFBO!EyB&6Z!nd4=KyDP^hl!*(k{dEF6@NkXztO7gIh zQ&PC+p-8WBv;N(rpfKdF^@Z~|E6pa)M1NBUrCZvLRW$%N%xIbv^uv?=C!=dDVq3%* zgvbEBnG*JB*@vXx8>)7XL*!{1Jh=#2UrByF7U?Rj_}VYw88BwqefT_cCTv8aTrRVjnn z1HNCF=44?*&gs2`vCGJVHX@kO z240eo#z+FhI0=yy6NHQwZs}a+J~4U-6X`@ zZ7j+tb##m`x%J66$a9qXDHG&^kp|GkFFMmjD(Y-k_ClY~N$H|n@NkSDz=gg?*2ga5 z)+f)MEY>2Lp15;~o`t`qj;S>BaE;%dv@Ux11yq}I(k|o&`5UZFUHn}1kE^gIK@qV& z!S2IhyU;->VfA4Qb}m7YnkIa9%z{l~iPWo2YPk-`hy2-Eg=6E$21plQA5W2qMZDFU z-a-@Dndf%#on6chT`dOKnU9}BJo|kJwgGC<^nfo34zOKH96LbWY7@Wc%EoFF=}`VU zksP@wd%@W;-p!e^&-)N7#oR331Q)@9cx=mOoU?_Kih2!Le*8fhsZ8Qvo6t2vt+UOZ zw|mCB*t2%z21YqL>whu!j?s~}-L`OS+jdg1(XnmYw$rg~r(?5Y+qTg`$F}q3J?GtL z@BN&8#`u2RqkdG4yGGTus@7U_%{6C{XAhFE!2SelH?KtMtX@B1GBhEIDL-Bj#~{4! zd}p7!#XE9Lt;sy@p5#Wj*jf8zGv6tTotCR2X$EVOOup;GnRPRVU5A6N@Lh8?eA7k? zn~hz&gY;B0ybSpF?qwQ|sv_yO=8}zeg2$0n3A8KpE@q26)?707pPw?H76lCpjp=5r z6jjp|auXJDnW}uLb6d7rsxekbET9(=zdTqC8(F5@NNqII2+~yB;X5iJNQSiv`#ozm zf&p!;>8xAlwoxUC3DQ#!31ylK%VrcwS<$WeCY4V63V!|221oj+5#r}fGFQ}|uwC0) zNl8(CF}PD`&Sj+p{d!B&&JtC+VuH z#>US`)YQrhb6lIAYb08H22y(?)&L8MIQsA{26X`R5Km{YU)s!x(&gIsjDvq63@X`{ z=7{SiH*_ZsPME#t2m|bS76Uz*z{cpp1m|s}HIX}Ntx#v7Eo!1%G9__4dGSGl`p+xi zZ!VK#Qe;Re=9bqXuW+0DSP{uZ5-QXrNn-7qW19K0qU}OhVru7}3vqsG?#D67 zb}crN;QwsH*vymw(maZr_o|w&@sQki(X+D)gc5Bt&@iXisFG;eH@5d43~Wxq|HO(@ zV-rip4n#PEkHCWCa5d?@cQp^B;I-PzOfag|t-cuvTapQ@MWLmh*41NH`<+A+JGyKX zyYL6Ba7qqa5j@3lOk~`OMO7f0!@FaOeZxkbG@vXP(t3#U*fq8=GAPqUAS>vW2uxMk{a(<0=IxB;# zMW;M+owrHaZBp`3{e@7gJCHP!I(EeyGFF;pdFPdeP+KphrulPSVidmg#!@W`GpD&d z9p6R`dpjaR2E1Eg)Ws{BVCBU9-aCgN57N~uLvQZH`@T+2eOBD%73rr&sV~m#2~IZx zY_8f8O;XLu2~E3JDXnGhFvsyb^>*!D>5EtlKPe%kOLv6*@=Jpci`8h0z?+fbBUg_7 zu6DjqO=$SjAv{|Om5)nz41ZkS4E_|fk%NDY509VV5yNeo%O|sb>7C#wj8mL9cEOFh z>nDz%?vb!h*!0dHdnxDA>97~EoT~!N40>+)G2CeYdOvJr5^VnkGz)et&T9hrD(VAgCAJjQ7V$O?csICB*HFd^k@$M5*v$PZJD-OVL?Ze(U=XGqZPVG8JQ z<~ukO%&%nNXYaaRibq#B1KfW4+XMliC*Tng2G(T1VvP;2K~;b$EAqthc${gjn_P!b zs62UT(->A>!ot}cJXMZHuy)^qfqW~xO-In2);e>Ta{LD6VG2u&UT&a@>r-;4<)cJ9 zjpQThb4^CY)Ev0KR7TBuT#-v}W?Xzj{c7$S5_zJA57Qf=$4^npEjl9clH0=jWO8sX z3Fuu0@S!WY>0XX7arjH`?)I<%2|8HfL!~#c+&!ZVmhbh`wbzy0Ux|Jpy9A{_7GGB0 zadZ48dW0oUwUAHl%|E-Q{gA{z6TXsvU#Hj09<7i)d}wa+Iya)S$CVwG{4LqtB>w%S zKZx(QbV7J9pYt`W4+0~f{hoo5ZG<0O&&5L57oF%hc0xGJ@Zrg_D&lNO=-I^0y#3mxCSZFxN2-tN_mU@7<@PnWG?L5OSqkm8TR!`| zRcTeWH~0z1JY^%!N<(TtxSP5^G9*Vw1wub`tC-F`=U)&sJVfvmh#Pi`*44kSdG};1 zJbHOmy4Ot|%_?@$N?RA9fF?|CywR8Sf(SCN_luM8>(u0NSEbKUy7C(Sk&OuWffj)f za`+mo+kM_8OLuCUiA*CNE|?jra$M=$F3t+h-)?pXz&r^F!ck;r##`)i)t?AWq-9A9 zSY{m~TC1w>HdEaiR*%j)L);H{IULw)uxDO>#+WcBUe^HU)~L|9#0D<*Ld459xTyew zbh5vCg$a>`RCVk)#~ByCv@Ce!nm<#EW|9j><#jQ8JfTmK#~jJ&o0Fs9jz0Ux{svdM4__<1 zrb>H(qBO;v(pXPf5_?XDq!*3KW^4>(XTo=6O2MJdM^N4IIcYn1sZZpnmMAEdt}4SU zPO54j2d|(xJtQ9EX-YrlXU1}6*h{zjn`in-N!Ls}IJsG@X&lfycsoCemt_Ym(PXhv zc*QTnkNIV=Ia%tg%pwJtT^+`v8ng>;2~ps~wdqZSNI7+}-3r+#r6p`8*G;~bVFzg= z!S3&y)#iNSUF6z;%o)%h!ORhE?CUs%g(k2a-d576uOP2@QwG-6LT*G!I$JQLpd`cz z-2=Brr_+z96a0*aIhY2%0(Sz=|D`_v_7h%Yqbw2)8@1DwH4s*A82krEk{ zoa`LbCdS)R?egRWNeHV8KJG0Ypy!#}kslun?67}^+J&02!D??lN~t@;h?GS8#WX`)6yC**~5YNhN_Hj}YG<%2ao^bpD8RpgV|V|GQwlL27B zEuah|)%m1s8C6>FLY0DFe9Ob66fo&b8%iUN=y_Qj;t3WGlNqP9^d#75ftCPA*R4E8 z)SWKBKkEzTr4JqRMEs`)0;x8C35yRAV++n(Cm5++?WB@ya=l8pFL`N0ag`lWhrYo3 zJJ$< zQ*_YAqIGR*;`VzAEx1Pd4b3_oWtdcs7LU2#1#Ls>Ynvd8k^M{Ef?8`RxA3!Th-?ui{_WJvhzY4FiPxA?E4+NFmaC-Uh*a zeLKkkECqy>Qx&1xxEhh8SzMML=8VP}?b*sgT9ypBLF)Zh#w&JzP>ymrM?nnvt!@$2 zh>N$Q>mbPAC2kNd&ab;FkBJ}39s*TYY0=@e?N7GX>wqaM>P=Y12lciUmve_jMF0lY zBfI3U2{33vWo(DiSOc}!5##TDr|dgX1Uojq9!vW3$m#zM_83EGsP6&O`@v-PDdO3P z>#!BEbqpOXd5s?QNnN!p+92SHy{sdpePXHL{d@c6UilT<#~I!tH$S(~o}c#(j<2%! zQvm}MvAj-95Ekx3D4+|e%!?lO(F+DFw9bxb-}rsWQl)b44###eUg4N?N-P(sFH2hF z`{zu?LmAxn2=2wCE8?;%ZDi#Y;Fzp+RnY8fWlzVz_*PDO6?Je&aEmuS>=uCXgdP6r zoc_JB^TA~rU5*geh{G*gl%_HnISMS~^@{@KVC;(aL^ZA-De+1zwUSXgT>OY)W?d6~ z72znET0m`53q%AVUcGraYxIcAB?OZA8AT!uK8jU+=t;WneL~|IeQ>$*dWa#x%rB(+ z5?xEkZ&b{HsZ4Ju9TQ|)c_SIp`7r2qMJgaglfSBHhl)QO1aNtkGr0LUn{@mvAt=}nd7#>7ru}&I)FNsa*x?Oe3-4G`HcaR zJ}c%iKlwh`x)yX1vBB;-Nr=7>$~(u=AuPX2#&Eh~IeFw%afU+U)td0KC!pHd zyn+X$L|(H3uNit-bpn7%G%{&LsAaEfEsD?yM<;U2}WtD4KuVKuX=ec9X zIe*ibp1?$gPL7<0uj*vmj2lWKe`U(f9E{KVbr&q*RsO;O>K{i-7W)8KG5~~uS++56 zm@XGrX@x+lGEjDQJp~XCkEyJG5Y57omJhGN{^2z5lj-()PVR&wWnDk2M?n_TYR(gM zw4kQ|+i}3z6YZq8gVUN}KiYre^sL{ynS}o{z$s&I z{(rWaLXxcQ=MB(Cz7W$??Tn*$1y(7XX)tv;I-{7F$fPB%6YC7>-Dk#=Y8o1=&|>t5 zV_VVts>Eb@)&4%m}!K*WfLoLl|3FW)V~E1Z!yu`Sn+bAP5sRDyu7NEbLt?khAyz-ZyL-}MYb&nQ zU16f@q7E1rh!)d%f^tTHE3cVoa%Xs%rKFc|temN1sa)aSlT*)*4k?Z>b3NP(IRXfq zlB^#G6BDA1%t9^Nw1BD>lBV(0XW5c?l%vyB3)q*;Z5V~SU;HkN;1kA3Nx!$!9wti= zB8>n`gt;VlBt%5xmDxjfl0>`K$fTU-C6_Z;!A_liu0@Os5reMLNk;jrlVF^FbLETI zW+Z_5m|ozNBn7AaQ<&7zk}(jmEdCsPgmo%^GXo>YYt82n&7I-uQ%A;k{nS~VYGDTn zlr3}HbWQG6xu8+bFu^9%%^PYCbkLf=*J|hr>Sw+#l(Y#ZGKDufa#f-f0k-{-XOb4i zwVG1Oa0L2+&(u$S7TvedS<1m45*>a~5tuOZ;3x%!f``{=2QQlJk|b4>NpD4&L+xI+ z+}S(m3}|8|Vv(KYAGyZK5x*sgwOOJklN0jsq|BomM>OuRDVFf_?cMq%B*iQ*&|vS9 zVH7Kh)SjrCBv+FYAE=$0V&NIW=xP>d-s7@wM*sdfjVx6-Y@=~>rz%2L*rKp|*WXIz z*vR^4tV&7MQpS9%{9b*>E9d_ls|toL7J|;srnW{l-}1gP_Qr-bBHt=}PL@WlE|&KH zCUmDLZb%J$ZzNii-5VeygOM?K8e$EcK=z-hIk63o4y63^_*RdaitO^THC{boKstphXZ2Z+&3ToeLQUG(0Frs?b zCxB+65h7R$+LsbmL51Kc)pz_`YpGEzFEclzb=?FJ=>rJwgcp0QH-UuKRS1*yCHsO) z-8t?Zw|6t($Eh&4K+u$I7HqVJBOOFCRcmMMH};RX_b?;rnk`rz@vxT_&|6V@q0~Uk z9ax|!pA@Lwn8h7syrEtDluZ6G!;@=GL> zse#PRQrdDs=qa_v@{Wv(3YjYD0|qocDC;-F~&{oaTP?@pi$n z1L6SlmFU2~%)M^$@C(^cD!y)-2SeHo3t?u3JiN7UBa7E2 z;<+_A$V084@>&u)*C<4h7jw9joHuSpVsy8GZVT;(>lZ(RAr!;)bwM~o__Gm~exd`K zKEgh2)w?ReH&syI`~;Uo4`x4$&X+dYKI{e`dS~bQuS|p zA`P_{QLV3r$*~lb=9vR^H0AxK9_+dmHX}Y} zIV*#65%jRWem5Z($ji{!6ug$En4O*=^CiG=K zp4S?+xE|6!cn$A%XutqNEgUqYY3fw&N(Z6=@W6*bxdp~i_yz5VcgSj=lf-6X1Nz75 z^DabwZ4*70$$8NsEy@U^W67tcy7^lNbu;|kOLcJ40A%J#pZe0d#n zC{)}+p+?8*ftUlxJE*!%$`h~|KZSaCb=jpK3byAcuHk7wk@?YxkT1!|r({P*KY^`u z!hw#`5$JJZGt@nkBK_nwWA31_Q9UGvv9r-{NU<&7HHMQsq=sn@O?e~fwl20tnSBG* zO%4?Ew6`aX=I5lqmy&OkmtU}bH-+zvJ_CFy z_nw#!8Rap5Wcex#5}Ldtqhr_Z$}@jPuYljTosS1+WG+TxZ>dGeT)?ZP3#3>sf#KOG z0)s%{cEHBkS)019}-1A2kd*it>y65-C zh7J9zogM74?PU)0c0YavY7g~%j%yiWEGDb+;Ew5g5Gq@MpVFFBNOpu0x)>Yn>G6uo zKE%z1EhkG_N5$a8f6SRm(25iH#FMeaJ1^TBcBy<04ID47(1(D)q}g=_6#^V@yI?Y&@HUf z`;ojGDdsvRCoTmasXndENqfWkOw=#cV-9*QClpI03)FWcx(m5(P1DW+2-{Hr-`5M{v##Zu-i-9Cvt;V|n)1pR^y ztp3IXzHjYWqabuPqnCY9^^;adc!a%Z35VN~TzwAxq{NU&Kp35m?fw_^D{wzB}4FVXX5Zk@#={6jRh%wx|!eu@Xp;%x+{2;}!&J4X*_SvtkqE#KDIPPn@ z5BE$3uRlb>N<2A$g_cuRQM1T#5ra9u2x9pQuqF1l2#N{Q!jVJ<>HlLeVW|fN|#vqSnRr<0 zTVs=)7d`=EsJXkZLJgv~9JB&ay16xDG6v(J2eZy;U%a@EbAB-=C?PpA9@}?_Yfb&) zBpsih5m1U9Px<+2$TBJ@7s9HW>W){i&XKLZ_{1Wzh-o!l5_S+f$j^RNYo85}uVhN# zq}_mN-d=n{>fZD2Lx$Twd2)}X2ceasu91}n&BS+4U9=Y{aZCgV5# z?z_Hq-knIbgIpnkGzJz-NW*=p?3l(}y3(aPCW=A({g9CpjJfYuZ%#Tz81Y)al?!S~ z9AS5#&nzm*NF?2tCR#|D-EjBWifFR=da6hW^PHTl&km-WI9*F4o>5J{LBSieVk`KO z2(^9R(zC$@g|i3}`mK-qFZ33PD34jd_qOAFj29687wCUy>;(Hwo%Me&c=~)V$ua)V zsaM(aThQ3{TiM~;gTckp)LFvN?%TlO-;$y+YX4i`SU0hbm<})t0zZ!t1=wY&j#N>q zONEHIB^RW6D5N*cq6^+?T}$3m|L{Fe+L!rxJ=KRjlJS~|z-&CC{#CU8`}2|lo~)<| zk?Wi1;Cr;`?02-C_3^gD{|Ryhw!8i?yx5i0v5?p)9wZxSkwn z3C;pz25KR&7{|rc4H)V~y8%+6lX&KN&=^$Wqu+}}n{Y~K4XpI-#O?L=(2qncYNePX zTsB6_3`7q&e0K67=Kg7G=j#?r!j0S^w7;0?CJbB3_C4_8X*Q%F1%cmB{g%XE&|IA7 z(#?AeG{l)s_orNJp!$Q~qGrj*YnuKlV`nVdg4vkTNS~w$4d^Oc3(dxi(W5jq0e>x} z(GN1?u2%Sy;GA|B%Sk)ukr#v*UJU%(BE9X54!&KL9A^&rR%v zIdYt0&D59ggM}CKWyxGS@ z>T#})2Bk8sZMGJYFJtc>D#k0+Rrrs)2DG;(u(DB_v-sVg=GFMlSCx<&RL;BH}d6AG3VqP!JpC0Gv6f8d|+7YRC@g|=N=C2 zo>^0CE0*RW?W))S(N)}NKA)aSwsR{1*rs$(cZIs?nF9)G*bSr%%SZo^YQ|TSz={jX z4Z+(~v_>RH0(|IZ-_D_h@~p_i%k^XEi+CJVC~B zsPir zA0Jm2yIdo4`&I`hd%$Bv=Rq#-#bh{Mxb_{PN%trcf(#J3S1UKDfC1QjH2E;>wUf5= ze8tY9QSYx0J;$JUR-0ar6fuiQTCQP#P|WEq;Ez|*@d?JHu-(?*tTpGHC+=Q%H>&I> z*jC7%nJIy+HeoURWN%3X47UUusY2h7nckRxh8-)J61Zvn@j-uPA@99|y48pO)0XcW zX^d&kW^p7xsvdX?2QZ8cEUbMZ7`&n{%Bo*xgFr4&fd#tHOEboQos~xm8q&W;fqrj} z%KYnnE%R`=`+?lu-O+J9r@+$%YnqYq!SVs>xp;%Q8p^$wA~oynhnvIFp^)Z2CvcyC zIN-_3EUHW}1^VQ0;Oj>q?mkPx$Wj-i7QoXgQ!HyRh6Gj8p~gH22k&nmEqUR^)9qni{%uNeV{&0-H60C zibHZtbV=8=aX!xFvkO}T@lJ_4&ki$d+0ns3FXb+iP-VAVN`B7f-hO)jyh#4#_$XG%Txk6M<+q6D~ zi*UcgRBOoP$7P6RmaPZ2%MG}CMfs=>*~(b97V4+2qdwvwA@>U3QQAA$hiN9zi%Mq{ z*#fH57zUmi)GEefh7@`Uy7?@@=BL7cXbd{O9)*lJh*v!@ z-6}p9u0AreiGauxn7JBEa-2w&d=!*TLJ49`U@D7%2ppIh)ynMaAE2Q4dl@47cNu{9 z&3vT#pG$#%hrXzXsj=&Ss*0;W`Jo^mcy4*L8b^sSi;H{*`zW9xX2HAtQ*sO|x$c6UbRA(7*9=;D~(%wfo(Z6#s$S zuFk`dr%DfVX5KC|Af8@AIr8@OAVj=6iX!~8D_P>p7>s!Hj+X0_t}Y*T4L5V->A@Zx zcm1wN;TNq=h`5W&>z5cNA99U1lY6+!!u$ib|41VMcJk8`+kP{PEOUvc@2@fW(bh5pp6>C3T55@XlpsAd#vn~__3H;Dz2w=t9v&{v*)1m4)vX;4 zX4YAjM66?Z7kD@XX{e`f1t_ZvYyi*puSNhVPq%jeyBteaOHo7vOr8!qqp7wV;)%jtD5>}-a?xavZ;i|2P3~7c)vP2O#Fb`Y&Kce zQNr7%fr4#S)OOV-1piOf7NgQvR{lcvZ*SNbLMq(olrdDC6su;ubp5un!&oT=jVTC3uTw7|r;@&y*s)a<{J zkzG(PApmMCpMmuh6GkM_`AsBE@t~)EDcq1AJ~N@7bqyW_i!mtHGnVgBA`Dxi^P93i z5R;}AQ60wy=Q2GUnSwz+W6C^}qn`S-lY7=J(3#BlOK%pCl=|RVWhC|IDj1E#+|M{TV0vE;vMZLy7KpD1$Yk zi0!9%qy8>CyrcRK`juQ)I};r)5|_<<9x)32b3DT1M`>v^ld!yabX6@ihf`3ZVTgME zfy(l-ocFuZ(L&OM4=1N#Mrrm_<>1DZpoWTO70U8+x4r3BpqH6z@(4~sqv!A9_L}@7 z7o~;|?~s-b?ud&Wx6==9{4uTcS|0-p@dKi0y#tPm2`A!^o3fZ8Uidxq|uz2vxf;wr zM^%#9)h^R&T;}cxVI(XX7kKPEVb);AQO?cFT-ub=%lZPwxefymBk+!H!W(o(>I{jW z$h;xuNUr#^0ivvSB-YEbUqe$GLSGrU$B3q28&oA55l)ChKOrwiTyI~e*uN;^V@g-Dm4d|MK!ol8hoaSB%iOQ#i_@`EYK_9ZEjFZ8Ho7P^er z^2U6ZNQ{*hcEm?R-lK)pD_r(e=Jfe?5VkJ$2~Oq^7YjE^5(6a6Il--j@6dBHx2Ulq z!%hz{d-S~i9Eo~WvQYDt7O7*G9CP#nrKE#DtIEbe_uxptcCSmYZMqT2F}7Kw0AWWC zPjwo0IYZ6klc(h9uL|NY$;{SGm4R8Bt^^q{e#foMxfCSY^-c&IVPl|A_ru!ebwR#7 z3<4+nZL(mEsU}O9e`^XB4^*m)73hd04HH%6ok^!;4|JAENnEr~%s6W~8KWD)3MD*+ zRc46yo<}8|!|yW-+KulE86aB_T4pDgL$XyiRW(OOcnP4|2;v!m2fB7Hw-IkY#wYfF zP4w;k-RInWr4fbz=X$J;z2E8pvAuy9kLJUSl8_USi;rW`kZGF?*Ur%%(t$^{Rg!=v zg;h3@!Q$eTa7S0#APEDHLvK%RCn^o0u!xC1Y0Jg!Baht*a4mmKHy~88md{YmN#x) zBOAp_i-z2h#V~*oO-9k(BizR^l#Vm%uSa^~3337d;f=AhVp?heJ)nlZGm`}D(U^2w z#vC}o1g1h?RAV^90N|Jd@M00PoNUPyA?@HeX0P7`TKSA=*4s@R;Ulo4Ih{W^CD{c8 ze(ipN{CAXP(KHJ7UvpOc@9SUAS^wKo3h-}BDZu}-qjdNlVtp^Z{|CxKOEo?tB}-4; zEXyDzGbXttJ3V$lLo-D?HYwZm7vvwdRo}P#KVF>F|M&eJ44n*ZO~0)#0e0Vy&j00I z{%IrnUvKp70P?>~J^$^0Wo%>le>re2ZSvRfes@dC-*e=DD1-j%<$^~4^4>Id5w^Fr z{RWL>EbUCcyC%1980kOYqZAcgdz5cS8c^7%vvrc@CSPIx;X=RuodO2dxk17|am?HJ@d~Mp_l8H?T;5l0&WGFoTKM{eP!L-a0O8?w zgBPhY78tqf^+xv4#OK2I#0L-cSbEUWH2z+sDur85*!hjEhFfD!i0Eyr-RRLFEm5(n z-RV6Zf_qMxN5S6#8fr9vDL01PxzHr7wgOn%0Htmvk9*gP^Um=n^+7GLs#GmU&a#U^4jr)BkIubQO7oUG!4CneO2Ixa`e~+Jp9m{l6apL8SOqA^ zvrfEUPwnHQ8;yBt!&(hAwASmL?Axitiqvx%KZRRP?tj2521wyxN3ZD9buj4e;2y6U zw=TKh$4%tt(eh|y#*{flUJ5t4VyP*@3af`hyY^YU3LCE3Z|22iRK7M7E;1SZVHbXF zKVw!L?2bS|kl7rN4(*4h2qxyLjWG0vR@`M~QFPsf^KParmCX;Gh4OX6Uy9#4e_%oK zv1DRnfvd$pu(kUoV(MmAc09ckDiuqS$a%!AQ1Z>@DM#}-yAP$l`oV`BDYpkqpk(I|+qk!yoo$TwWr6dRzLy(c zi+qbVlYGz0XUq@;Fm3r~_p%by)S&SVWS+wS0rC9bk^3K^_@6N5|2rtF)wI>WJ=;Fz zn8$h<|Dr%kN|nciMwJAv;_%3XG9sDnO@i&pKVNEfziH_gxKy{l zo`2m4rnUT(qenuq9B0<#Iy(RPxP8R)=5~9wBku=%&EBoZ82x1GlV<>R=hIqf0PK!V zw?{z9e^B`bGyg2nH!^x}06oE%J_JLk)^QyHLipoCs2MWIqc>vaxsJj(=gg1ZSa=u{ zt}od#V;e7sA4S(V9^<^TZ#InyVBFT(V#$fvI7Q+pgsr_2X`N~8)IOZtX}e(Bn(;eF zsNj#qOF_bHl$nw5!ULY{lNx@93Fj}%R@lewUuJ*X*1$K`DNAFpE z7_lPE+!}uZ6c?+6NY1!QREg#iFy=Z!OEW}CXBd~wW|r_9%zkUPR0A3m+@Nk%4p>)F zXVut7$aOZ6`w}%+WV$te6-IX7g2yms@aLygaTlIv3=Jl#Nr}nN zp|vH-3L03#%-1-!mY`1z?+K1E>8K09G~JcxfS)%DZbteGQnQhaCGE2Y<{ut#(k-DL zh&5PLpi9x3$HM82dS!M?(Z zEsqW?dx-K_GMQu5K54pYJD=5+Rn&@bGjB?3$xgYl-|`FElp}?zP&RAd<522c$Rv6} zcM%rYClU%JB#GuS>FNb{P2q*oHy}UcQ-pZ2UlT~zXt5*k-ZalE(`p7<`0n7i(r2k{ zb84&^LA7+aW1Gx5!wK!xTbw0slM?6-i32CaOcLC2B>ZRI16d{&-$QBEu1fKF0dVU>GTP05x2>Tmdy`75Qx! z^IG;HB9V1-D5&&)zjJ&~G}VU1-x7EUlT3QgNT<&eIDUPYey$M|RD6%mVkoDe|;2`8Z+_{0&scCq>Mh3hj|E*|W3;y@{$qhu77D)QJ` znD9C1AHCKSAHQqdWBiP`-cAjq7`V%~JFES1=i-s5h6xVT<50kiAH_dn0KQB4t*=ua zz}F@mcKjhB;^7ka@WbSJFZRPeYI&JFkpJ-!B z!ju#!6IzJ;D@$Qhvz9IGY5!%TD&(db3<*sCpZ?U#1^9RWQ zs*O-)j!E85SMKtoZzE^8{w%E0R0b2lwwSJ%@E}Lou)iLmPQyO=eirG8h#o&E4~eew z;h><=|4m0$`ANTOixHQOGpksXlF0yy17E&JksB4_(vKR5s$Ve+i;gco2}^RRJI+~R zWJ82WGigLIUwP!uSELh3AAs9HmY-kz=_EL-w|9}noKE#(a;QBpEx9 z4BT-zY=6dJT>72Hkz=9J1E=}*MC;zzzUWb@x(Ho8cU_aRZ?fxse5_Ru2YOvcr?kg&pt@v;{ai7G--k$LQtoYj+Wjk+nnZty;XzANsrhoH#7=xVqfPIW(p zX5{YF+5=k4_LBnhLUZxX*O?29olfPS?u*ybhM_y z*XHUqM6OLB#lyTB`v<BZ&YRs$N)S@5Kn_b3;gjz6>fh@^j%y2-ya({>Hd@kv{CZZ2e)tva7gxLLp z`HoGW);eRtov~Ro5tetU2y72~ zQh>D`@dt@s^csdfN-*U&o*)i3c4oBufCa0e|BwT2y%Y~=U7A^ny}tx zHwA>Wm|!SCko~UN?hporyQHRUWl3djIc722EKbTIXQ6>>iC!x+cq^sUxVSj~u)dsY zW8QgfZlE*2Os%=K;_vy3wx{0u!2%A)qEG-$R^`($%AOfnA^LpkB_}Dd7AymC)zSQr z>C&N8V57)aeX8ap!|7vWaK6=-3~ko9meugAlBKYGOjc#36+KJwQKRNa_`W@7;a>ot zdRiJkz?+QgC$b}-Owzuaw3zBVLEugOp6UeMHAKo2$m4w zpw?i%Lft^UtuLI}wd4(-9Z^*lVoa}11~+0|Hs6zAgJ01`dEA&^>Ai=mr0nC%eBd_B zzgv2G_~1c1wr*q@QqVW*Wi1zn=}KCtSwLjwT>ndXE_Xa22HHL_xCDhkM( zhbw+j4uZM|r&3h=Z#YrxGo}GX`)AZyv@7#7+nd-D?BZV>thtc|3jt30j$9{aIw9)v zDY)*fsSLPQTNa&>UL^RWH(vpNXT7HBv@9=*=(Q?3#H*crA2>KYx7Ab?-(HU~a275)MBp~`P)hhzSsbj|d`aBe(L*(;zif{iFJu**ZR zkL-tPyh!#*r-JVQJq>5b0?cCy!uSKef+R=$s3iA7*k*_l&*e!$F zYwGI;=S^0)b`mP8&Ry@{R(dPfykD&?H)na^ihVS7KXkxb36TbGm%X1!QSmbV9^#>A z-%X>wljnTMU0#d;tpw?O1W@{X-k*>aOImeG z#N^x?ehaaQd}ReQykp>i;92q@%$a!y1PNyPYDIvMm& zyYVwn;+0({W@3h(r&i#FuCDE)AC(y&Vu>4?1@j0|CWnhHUx4|zL7cdaA32RSk?wl% zMK^n42@i5AU>f70(huWfOwaucbaToxj%+)7hnG^CjH|O`A}+GHZyQ-X57(WuiyRXV zPf>0N3GJ<2Myg!sE4XJY?Z7@K3ZgHy8f7CS5ton0Eq)Cp`iLROAglnsiEXpnI+S8; zZn>g2VqLxi^p8#F#Laf3<00AcT}Qh&kQnd^28u!9l1m^`lfh9+5$VNv=?(~Gl2wAl zx(w$Z2!_oESg_3Kk0hUsBJ<;OTPyL(?z6xj6LG5|Ic4II*P+_=ac7KRJZ`(k2R$L# zv|oWM@116K7r3^EL*j2ktjEEOY9c!IhnyqD&oy7+645^+@z5Y|;0+dyR2X6^%7GD* zXrbPqTO}O={ z4cGaI#DdpP;5u?lcNb($V`l>H7k7otl_jQFu1hh>=(?CTPN#IPO%O_rlVX}_Nq;L< z@YNiY>-W~&E@=EC5%o_z<^3YEw)i_c|NXxHF{=7U7Ev&C`c^0Z4-LGKXu*Hkk&Av= zG&RAv{cR7o4${k~f{F~J48Ks&o(D@j-PQ2`LL@I~b=ifx3q!p6`d>~Y!<-^mMk3)e zhi1;(YLU5KH}zzZNhl^`0HT(r`5FfmDEzxa zk&J7WQ|!v~TyDWdXQ)!AN_Y%xM*!jv^`s)A`|F%;eGg27KYsrCE2H}7*r)zvum6B{ z$k5Har9pv!dcG%f|3hE(#hFH+12RZPycVi?2y`-9I7JHryMn3 z9Y8?==_(vOAJ7PnT<0&85`_jMD0#ipta~Q3M!q5H1D@Nj-YXI$W%OQplM(GWZ5Lpq z-He6ul|3<;ZQsqs!{Y7x`FV@pOQc4|N;)qgtRe(Uf?|YqZv^$k8On7DJ5>f2%M=TV zw~x}9o=mh$JVF{v4H5Su1pq66+mhTG6?F>Do}x{V(TgFwuLfvNP^ijkrp5#s4UT!~ zEU7pr8aA)2z1zb|X9IpmJykQcqI#(rS|A4&=TtWu@g^;JCN`2kL}%+K!KlgC z>P)v+uCeI{1KZpewf>C=?N7%1e10Y3pQCZST1GT5fVyB1`q)JqCLXM zSN0qlreH1=%Zg-5`(dlfSHI&2?^SQdbEE&W4#%Eve2-EnX>NfboD<2l((>>34lE%) zS6PWibEvuBG7)KQo_`?KHSPk+2P;`}#xEs}0!;yPaTrR#j(2H|#-CbVnTt_?9aG`o z(4IPU*n>`cw2V~HM#O`Z^bv|cK|K};buJ|#{reT8R)f+P2<3$0YGh!lqx3&a_wi2Q zN^U|U$w4NP!Z>5|O)>$GjS5wqL3T8jTn%Vfg3_KnyUM{M`?bm)9oqZP&1w1)o=@+(5eUF@=P~ zk2B5AKxQ96n-6lyjh&xD!gHCzD$}OOdKQQk7LXS-fk2uy#h{ktqDo{o&>O!6%B|)` zg?|JgcH{P*5SoE3(}QyGc=@hqlB5w;bnmF#pL4iH`TSuft$dE5j^qP2S)?)@pjRQZ zBfo6g>c!|bN-Y|(Wah2o61Vd|OtXS?1`Fu&mFZ^yzUd4lgu7V|MRdGj3e#V`=mnk- zZ@LHn?@dDi=I^}R?}mZwduik!hC%=Hcl56u{Wrk1|1SxlgnzG&e7Vzh*wNM(6Y!~m z`cm8Ygc1$@z9u9=m5vs1(XXvH;q16fxyX4&e5dP-{!Kd555FD6G^sOXHyaCLka|8j zKKW^E>}>URx736WWNf?U6Dbd37Va3wQkiE;5F!quSnVKnmaIRl)b5rM_ICu4txs+w zj}nsd0I_VG^<%DMR8Zf}vh}kk;heOQTbl ziEoE;9@FBIfR7OO9y4Pwyz02OeA$n)mESpj zdd=xPwA`nO06uGGsXr4n>Cjot7m^~2X~V4yH&- zv2llS{|und45}Pm1-_W@)a-`vFBpD~>eVP(-rVHIIA|HD@%7>k8JPI-O*<7X{L*Ik zh^K`aEN!BteiRaY82FVo6<^8_22=aDIa8P&2A3V<(BQ;;x8Zs-1WuLRWjQvKv1rd2 zt%+fZ!L|ISVKT?$3iCK#7whp|1ivz1rV*R>yc5dS3kIKy_0`)n*%bfNyw%e7Uo}Mnnf>QwDgeH$X5eg_)!pI4EJjh6?kkG2oc6Af0py z(txE}$ukD|Zn=c+R`Oq;m~CSY{ebu9?!is}01sOK_mB?{lSY33E=!KkKtMeI*FO2b z%95awv9;Z|UDp3xm+aP*5I!R-_M2;GxeCRx3ATS0iF<_Do2Mi)Hk2 zjBF35VB>(oamIYjunu?g0O-?LuOvtfs5F(iiIicbu$HMPPF%F>pE@hIRjzT)>aa=m zwe;H9&+2|S!m74!E3xfO{l3E_ab`Q^tZ4yH9=~o2DUEtEMDqG=&D*8!>?2uao%w`&)THr z^>=L3HJquY>6)>dW4pCWbzrIB+>rdr{s}}cL_?#!sOPztRwPm1B=!jP7lQG|Iy6rP zVqZDNA;xaUx&xUt?Ox|;`9?oz`C0#}mc<1Urs#vTW4wd{1_r`eX=BeSV z_9WV*9mz>PH6b^z{VYQJ1nSTSqOFHE9u>cY)m`Q>=w1NzUShxcHsAxasnF2BG;NQ; zqL1tjLjImz_`q=|bAOr_i5_NEijqYZ^;d5y3ZFj6kCYakJh**N_wbfH;ICXq?-p#r z{{ljNDPSytOaG#7=yPmA&5gyYI%^7pLnMOw-RK}#*dk=@usL;|4US?{@K%7esmc&n z5$D*+l&C9)Bo@$d;Nwipd!68&+NnOj^<~vRcKLX>e03E|;to;$ndgR;9~&S-ly5gf z{rzj+j-g$;O|u?;wwxrEpD=8iFzUHQfl{B>bLHqH(9P zI59SS2PEBE;{zJUlcmf(T4DrcO?XRWR}?fekN<($1&AJTRDyW+D*2(Gyi?Qx-i}gy z&BpIO!NeVdLReO!YgdUfnT}7?5Z#~t5rMWqG+$N2n%5o#Np6ccNly}#IZQsW4?|NV zR9hrcyP(l#A+U4XcQvT;4{#i)dU>HK>aS!k1<3s2LyAhm2(!Nu%vRC9T`_yn9D+r} z1i&U~IcQ?4xhZYyH6WL-f%}qIhZkc&}n2N0PM| z6|XA9d-y;!`D{p;xu*gv7a|zaZ*MiQ)}zPzW4GB0mr)}N-DmB&hl1&x`2@sxN572_ zS)RdJyR%<7kW0v3Q_|57JKy&9tUdbqz}|hwn84}U*0r^jt6Ssrp+#1y=JBcZ+F`f(N?O0XL1OFGN`1-r?S<#t4*C9|y~e)!UYZ zRQ3M8m%~M)VriIvn~XzoP;5qeu(ZI>Y#r zAd)J)G9)*BeE%gmm&M@Olg3DI_zokjh9NvdGbT z+u4(Y&uC6tBBefIg~e=J#8i1Zxr>RT)#rGaB2C71usdsT=}mm`<#WY^6V{L*J6v&l z1^Tkr6-+^PA)yC;s1O^3Q!)Reb=fxs)P~I*?i&j{Vbb(Juc?La;cA5(H7#FKIj0Or zgV0BO{DUs`I9HgQ{-!g@5P^Vr|C4}~w6b=#`Zx0XcVSd?(04HUHwK(gJNafgQNB9Z zCi3TgNXAeJ+x|X|b@27$RxuYYuNSUBqo#uyiH6H(b~K*#!@g__4i%HP5wb<+Q7GSb zTZjJw96htUaGZ89$K_iBo4xEOJ#DT#KRu9ozu!GH0cqR>hP$nk=KXM%Y!(%vWQ#}s zy=O#BZ>xjUejMH^F39Bf0}>D}yiAh^toa-ts#gt6Mk9h1D<9_mGMBhLT0Ce2O3d_U znaTkBaxd-8XgwSp5)x-pqX5=+{cSuk6kyl@k|5DQ!5zLUVV%1X9vjY0gerbuG6nwZu5KDMdq(&UMLZ zy?jW#F6joUtVyz`Y?-#Yc0=i*htOFwQ3`hk$8oq35D}0m$FAOp#UFTV3|U3F>@N?d zeXLZCZjRC($%?dz(41e~)CN10qjh^1CdAcY(<=GMGk@`b1ptA&L*{L@_M{%Vd5b*x#b1(qh=7((<_l%ZUaHtmgq} zjchBdiis{Afxf@3CjPR09E*2#X(`W#-n`~6PcbaL_(^3tfDLk?Nb6CkW9v!v#&pWJ3iV-9hz zngp#Q`w`r~2wt&cQ9#S7z0CA^>Mzm7fpt72g<0y-KT{G~l-@L#edmjZQ}7{*$mLgSdJfS$Ge{hrD=mr;GD)uYq8}xS zT>(w_;}894Kb}(P5~FOpFIEjadhmxD(PsZbKwa-qxVa7Oc7~ebPKMeN(pCRzq8s@l z`|l^*X1eK1+Spz--WkSW_nK`Cs@JmkY4+p=U91nJoy{tSH;TzuIyS)Q_(S@;Iakua zpuDo5W54Mo;jY@Ly1dY)j|+M%$FJ0`C=FW#%UvOd&?p}0QqL20Xt!#pr8ujy6CA-2 zFz6Ex5H1i)c9&HUNwG{8K%FRK7HL$RJwvGakleLLo}tsb>t_nBCIuABNo$G--_j!gV&t8L^4N6wC|aLC)l&w04CD6Vc#h^(YH@Zs4nwUGkhc_-yt{dK zMZ<%$swLmUl8`E~RLihGt@J5v;r;vT&*Q!Cx zZ55-zpb;W7_Q{tf$mQvF61(K>kwTq0x{#Din||)B{+6O#ArLi)kiHWVC4`fOT&B(h zw&YV`J1|^FLx~9Q%r-SFhYl4PywI7sF2Q$>4o50~dfp5nn}XHv-_DM?RGs#+4gM;% znU>k=81G~f6u%^Z{bcX&sUv*h|L+|mNq=W43y@{~C zpL-TW3hYPs0^*OqS#KQwA^CGG_A-6#`_{1LBCD&*3nY0UHWJj1D|VP%oQlFxLllaA zVI@2^)HZ%E*=RbQcFOKIP7?+|_xVK+2oG(t_EGl2y;Ovox zZb^qVpe!4^reKvpIBFzx;Ji=PmrV>uu-Hb>`s?k?YZQ?>av45>i(w0V!|n?AP|v5H zm`e&Tgli#lqGEt?=(?~fy<(%#nDU`O@}Vjib6^rfE2xn;qgU6{u36j_+Km%v*2RLnGpsvS+THbZ>p(B zgb{QvqE?~50pkLP^0(`~K& zjT=2Pt2nSnwmnDFi2>;*C|OM1dY|CAZ5R|%SAuU|5KkjRM!LW_)LC*A zf{f>XaD+;rl6Y>Umr>M8y>lF+=nSxZX_-Z7lkTXyuZ(O6?UHw^q; z&$Zsm4U~}KLWz8>_{p*WQ!OgxT1JC&B&>|+LE3Z2mFNTUho<0u?@r^d=2 z-av!n8r#5M|F%l;=D=S1mGLjgFsiYAOODAR}#e^a8 zfVt$k=_o}kt3PTz?EpLkt54dY}kyd$rU zVqc9SN>0c z753j-gdN~UiW*FUDMOpYEkVzP)}{Ds*3_)ZBi)4v26MQr140|QRqhFoP=a|;C{#KS zD^9b-9HM11W+cb1Y)HAuk<^GUUo(ut!5kILBzAe)Vaxwu4Up!7Ql*#DDu z>EB84&xSrh>0jT!*X81jJQq$CRHqNj29!V3FN9DCx)~bvZbLwSlo3l^zPb1sqBnp) zfZpo|amY^H*I==3#8D%x3>zh#_SBf?r2QrD(Y@El!wa;Ja6G9Y1947P*DC|{9~nO& z*vDnnU!8(cV%HevsraF%Y%2{Z>CL0?64eu9r^t#WjW4~3uw8d}WHzsV%oq-T)Y z0-c!FWX5j1{1##?{aTeCW2b$PEnwe;t`VPCm@sQ`+$$L2=3kBR%2XU1{_|__XJ$xt zibjY2QlDVs)RgHH*kl&+jn*JqquF)k_Ypibo00lcc<2RYqsi-G%}k0r(N97H7JEn7@E3ZTH0JK>d8)E~A-D z!B&z9zJw0Bi^fgQZI%LirYaBKnWBXgc`An*qvO^*$xymqKOp(+3}IsnVhu?YnN7qz zNJxDN-JWd7-vIiv2M9ih>x3gNVY%DzzY~dCnA}76IRl!`VM=6=TYQ=o&uuE8kHqZT zoUNod0v+s9D)7aLJ|hVqL0li1hg)%&MAciI(4YJ=%D4H$fGQ&Lu-?@>>@pEgC;ERrL= zI^cS&3q8fvEGTJZgZwL5j&jp%j9U^Of6pR{wA^u=tVt#yCQepXNIbynGnuWbsC_EE zRyMFq{5DK692-*kyGy~An>AdVR9u___fzmmJ4;^s0yAGgO^h{YFmqJ%ZJ_^0BgCET zE6(B*SzeZ4pAxear^B-YW<%BK->X&Cr`g9_;qH~pCle# zdY|UB5cS<}DFRMO;&czbmV(?vzikf)Ks`d$LL801@HTP5@r><}$xp}+Ip`u_AZ~!K zT}{+R9Wkj}DtC=4QIqJok5(~0Ll&_6PPVQ`hZ+2iX1H{YjI8axG_Bw#QJy`6T>1Nn z%u^l`>XJ{^vX`L0 z1%w-ie!dE|!SP<>#c%ma9)8K4gm=!inHn2U+GR+~ zqZVoa!#aS0SP(|**WfQSe?cA=1|Jwk`UDsny%_y{@AV??N>xWekf>_IZLUEK3{Ksi zWWW$if&Go~@Oz)`#=6t_bNtD$d9FMBN#&97+XKa+K2C@I9xWgTE{?Xnhc9_KKPcujj@NprM@e|KtV_SR+ zSpeJ!1FGJ=Te6={;;+;a46-*DW*FjTnBfeuzI_=I1yk8M(}IwEIGWV0Y~wia;}^dg z{BK#G7^J`SE10z4(_Me=kF&4ld*}wpNs91%2Ute>Om`byv9qgK4VfwPj$`axsiZ)wxS4k4KTLb-d~!7I@^Jq`>?TrixHk|9 zqCX7@sWcVfNP8N;(T>>PJgsklQ#GF>F;fz_Rogh3r!dy*0qMr#>hvSua;$d z3TCZ4tlkyWPTD<=5&*bUck~J;oaIzSQ0E03_2x{?weax^jL3o`ZP#uvK{Z5^%H4b6 z%Kbp6K?>{;8>BnQy64Jy$~DN?l(ufkcs6TpaO&i~dC>0fvi-I^7YT#h?m;TVG|nba%CKRG%}3P*wejg) zI(ow&(5X3HR_xk{jrnkA-hbwxEQh|$CET9Qv6UpM+-bY?E!XVorBvHoU59;q<9$hK z%w5K-SK zWT#1OX__$ceoq0cRt>9|)v}$7{PlfwN}%Wh3rwSl;%JD|k~@IBMd5}JD#TOvp=S57 zae=J#0%+oH`-Av}a(Jqhd4h5~eG5ASOD)DfuqujI6p!;xF_GFcc;hZ9k^a7c%%h(J zhY;n&SyJWxju<+r`;pmAAWJmHDs{)V-x7(0-;E?I9FWK@Z6G+?7Py8uLc2~Fh1^0K zzC*V#P88(6U$XBjLmnahi2C!a+|4a)5Ho5>owQw$jaBm<)H2fR=-B*AI8G@@P-8I8 zHios92Q6Nk-n0;;c|WV$Q);Hu4;+y%C@3alP`cJ2{z~*m-@de%OKVgiWp;4Q)qf9n zJ!vmx(C=_>{+??w{U^Bh|LFJ<6t}Er<-Tu{C{dv8eb(kVQ4!fOuopTo!^x1OrG}0D zR{A#SrmN`=7T29bzQ}bwX8OUufW9d9T4>WY2n15=k3_rfGOp6sK0oj7(0xGaEe+-C zVuWa;hS*MB{^$=0`bWF(h|{}?53{5Wf!1M%YxVw}io4u-G2AYN|FdmhI13HvnoK zNS2fStm=?8ZpKt}v1@Dmz0FD(9pu}N@aDG3BY8y`O*xFsSz9f+Y({hFx;P_h>ER_& z`~{z?_vCNS>agYZI?ry*V96_uh;|EFc0*-x*`$f4A$*==p`TUVG;YDO+I4{gJGrj^ zn?ud(B4BlQr;NN?vaz_7{&(D9mfd z8esj=a4tR-ybJjCMtqV8>zn`r{0g$hwoWRUI3}X5=dofN){;vNoftEwX>2t@nUJro z#%7rpie2eH1sRa9i6TbBA4hLE8SBK@blOs=ouBvk{zFCYn4xY;v3QSM%y6?_+FGDn z4A;m)W?JL!gw^*tRx$gqmBXk&VU=Nh$gYp+Swu!h!+e(26(6*3Q!(!MsrMiLri`S= zKItik^R9g!0q7y$lh+L4zBc-?Fsm8`CX1+f>4GK7^X2#*H|oK}reQnT{Mm|0ar<+S zRc_dM%M?a3bC2ILD`|;6vKA`a3*N~(cjw~Xy`zhuY2s{(7KLB{S>QtR3NBQ3>vd+= z#}Q)AJr7Y_-eV(sMN#x!uGX08oE*g=grB*|bBs}%^3!RVA4f%m3=1f0K=T^}iI&2K zuM2GG5_%+#v-&V>?x4W9wQ|jE2Q7Be8mOyJtZrqn#gXy-1fF1P$C8+We&B*-pi#q5 zETp%H6g+%#sH+L4=ww?-h;MRCd2J9zwQUe4gHAbCbH08gDJY;F6F)HtWCRW1fLR;)ysGZanlz*a+|V&@(ipWdB!tz=m_0 z6F}`d$r%33bw?G*azn*}Z;UMr{z4d9j~s`0*foZkUPwpJsGgoR0aF>&@DC;$A&(av z?b|oo;`_jd>_5nye`DVOcMLr-*Nw&nA z82E8Dw^$Lpso)gEMh?N|Uc^X*NIhg=U%enuzZOGi-xcZRUZmkmq~(cP{S|*+A6P;Q zprIkJkIl51@ng)8cR6QSXJtoa$AzT@*(zN3M+6`BTO~ZMo0`9$s;pg0HE3C;&;D@q zd^0zcpT+jC%&=cYJF+j&uzX87d(gP9&kB9|-zN=69ymQS9_K@h3ph&wD5_!4q@qI@ zBMbd`2JJ2%yNX?`3(u&+nUUJLZ=|{t7^Rpw#v-pqD2_3}UEz!QazhRty%|Q~WCo7$ z+sIugHA%Lmm{lBP#bnu_>G}Ja<*6YOvSC;89z67M%iG0dagOt1HDpDn$<&H0DWxMU zxOYaaks6%R@{`l~zlZ*~2}n53mn2|O&gE+j*^ypbrtBv{xd~G(NF?Z%F3>S6+qcry z?ZdF9R*a;3lqX_!rI(Cov8ER_mOqSn6g&ZU(I|DHo7Jj`GJ}mF;T(vax`2+B8)H_D zD0I;%I?*oGD616DsC#j0x*p+ZpBfd=9gR|TvB)832CRhsW_7g&WI@zp@r7dhg}{+4f=(cO2s+)jg0x(*6|^+6W_=YIfSH0lTcK* z%)LyaOL6em@*-_u)}Swe8rU)~#zT-vNiW(D*~?Zp3NWl1y#fo!3sK-5Ek6F$F5l3| zrFFD~WHz1}WHmzzZ!n&O8rTgfytJG*7iE~0`0;HGXgWTgx@2fD`oodipOM*MOWN-} zJY-^>VMEi8v23ZlOn0NXp{7!QV3F1FY_URZjRKMcY(2PV_ms}EIC^x z=EYB5UUQ{@R~$2Mwiw$_JAcF+szKB*n(`MYpDCl>~ss54uDQ%Xf-8|dgO zY)B_qju=IaShS|XsQo=nSYxV$_vQR@hd~;qW)TEfU|BA0&-JSwO}-a*T;^}l;MgLM zz}CjPlJX|W2vCzm3oHw3vqsRc3RY=2()}iw_k2#eKf&VEP7TQ;(DDzEAUgj!z_h2Br;Z3u=K~LqM6YOrlh)v9`!n|6M-s z?XvA~y<5?WJ{+yM~uPh7uVM&g-(;IC3>uA}ud?B3F zelSyc)Nx>(?F=H88O&_70%{ATsLVTAp88F-`+|egQ7C4rpIgOf;1tU1au+D3 zlz?k$jJtTOrl&B2%}D}8d=+$NINOZjY$lb{O<;oT<zXoAp01KYG$Y4*=)!&4g|FL(!54OhR-?)DXC&VS5E|1HGk8LY;)FRJqnz zb_rV2F7=BGwHgDK&4J3{%&IK~rQx<&Kea|qEre;%A~5YD6x`mo>mdR)l?Nd%T2(5U z_ciT02-zt_*C|vn?BYDuqSFrk3R(4B0M@CRFmG{5sovIq4%8AhjXA5UwRGo)MxZlI zI%vz`v8B+#ff*XtGnciczFG}l(I}{YuCco#2E6|+5WJ|>BSDfz0oT+F z%QI^ixD|^(AN`MS6J$ zXlKNTFhb>KDkJp*4*LaZ2WWA5YR~{`={F^hwXGG*rJYQA7kx|nwnC58!eogSIvy{F zm1C#9@$LhK^Tl>&iM0wsnbG7Y^MnQ=q))MgApj4)DQt!Q5S`h+5a%c7M!m%)?+h65 z0NHDiEM^`W+M4)=q^#sk(g!GTpB}edwIe>FJQ+jAbCo#b zXmtd3raGJNH8vnqMtjem<_)9`gU_-RF&ZK!aIenv7B2Y0rZhon=2yh&VsHzM|`y|0x$Zez$bUg5Nqj?@~^ zPN43MB}q0kF&^=#3C;2T*bDBTyO(+#nZnULkVy0JcGJ36or7yl1wt7HI_>V7>mdud zv2II9P61FyEXZuF$=69dn%Z6F;SOwyGL4D5mKfW)q4l$8yUhv7|>>h_-4T*_CwAyu7;DW}_H zo>N_7Gm6eed=UaiEp_7aZko@CC61@(E1be&5I9TUq%AOJW>s^9w%pR5g2{7HW9qyF zh+ZvX;5}PN0!B4q2FUy+C#w5J?0Tkd&S#~94(AP4%fRb^742pgH7Tb1))siXWXHUT z1Wn5CG&!mGtr#jq6(P#!ck@K+FNprcWP?^wA2>mHA03W?kj>5b|P0ErXS) zg2qDTjQ|grCgYhrH-RapWCvMq5vCaF?{R%*mu}1)UDll~6;}3Q*^QOfj!dlt02lSzK z?+P)02Rrq``NbU3j&s*;<%i4Y>y9NK&=&KsYwvEmf5jwTG6?+Pu1q9M8lLlx)uZZ7 zizhr~e0ktGs-=$li-2jz^_48-jk**y&5u0`B2gc#i$T1~t+AS*kEfR*b{^Ec>2-F~ zKYRl&uQ5yO@EtAZX8ZSqx;8+AKf+CqhlUSpp*VfyBMv+%wxN5GukZEi^_to%MFRc0 zdXqJ*jk?#uYT6EJe446@(f6G4vhnxQP|pGeJ?-#|Ksq?g*ky=}x+Qnx+!<>Y(XStN zQIND`{KU}&l)E*ntI^}kJ=ly8DML{!(58Xk4_bzIc@v~e;>wKl_`7G%pGz~4KH*CTp;_|52)d!+ximd$|8v@zzEq%j68QXkgf$7eM~xdM5q5i z{?qFx_W|eq@L03bWJfjy^z@()-iCjzjREuf zb_a(yTz)ZKWCF%Lp>^2-%Q?*t{06}x#DLN3cO=i>h6#-a`z;<5rBGGM6GA(WqvRcX%Pn?Uvs1#e|ePSNJEC%+X(YI$x)`s$%>O#%}D9dgqWfq4yfVz^%FglokdFR}uJQhx|}_w`9Ulx38Ha>ZslKs58c-@IFI&f;?xM zbK>rKNfPFsf>%+k6%(A6=7Aac^_qrOCNqb3ZVJ;8pt!?1DR*ynJb#@II9h?)xB)A~ zm9Kk)Hy}!Z+W}i6ZJDy+?yY_=#kWrzgV)2eZAx_E=}Nh7*#<&mQz`Umfe$+l^P(xd zN}PA2qII4}ddCU+PN+yxkH%y!Qe(;iH3W%bwM3NKbU_saBo<8x9fGNtTAc_SizU=o zC3n2;c%LoU^j90Sz>B_p--Fzqv7x7*?|~-x{haH8RP)p|^u$}S9pD-}5;88pu0J~9 zj}EC`Q^Fw}`^pvAs4qOIuxKvGN@DUdRQ8p-RXh=3S#<`3{+Qv6&nEm)uV|kRVnu6f zco{(rJaWw(T0PWim?kkj9pJ)ZsUk9)dSNLDHf`y&@wbd;_ita>6RXFJ+8XC*-wsiN z(HR|9IF283fn=DI#3Ze&#y3yS5;!yoIBAH(v}3p5_Zr+F99*%+)cp!Sy8e+lG?dOc zuEz<;3X9Z5kkpL_ZYQa`sioR_@_cG z8tT~GOSTWnO~#?$u)AcaBSaV7P~RT?Nn8(OSL1RmzPWRWQ$K2`6*)+&7^zZBeWzud z*xb3|Fc~|R9eH+lQ#4wF#c;)Gka6lL(63C;>(bZob!i8F-3EhYU3|6-JBC0*5`y0| zBs!Frs=s!Sy0qmQNgIH|F`6(SrD1js2prni_QbG9Sv@^Pu2szR9NZl8GU89gWWvVg z2^-b*t+F{Nt>v?js7hnlC`tRU(an0qQG7;h6T~ z-`vf#R-AE$pzk`M{gCaia}F`->O2)60AuGFAJg> z*O2IZqTx=AzDvC49?A92>bQLdb&32_4>0Bgp0ESXXnd4B)!$t$g{*FG%HYdt3b3a^J9#so%BJMyr2 z{y?rzW!>lr097b9(75#&4&@lkB1vT*w&0E>!dS+a|ZOu6t^zro2tiP)bhcNNxn zbJs3_Fz+?t;4bkd8GfDI7ccJ5zU`Bs~ zN~bci`c`a%DoCMel<-KUCBdZRmew`MbZEPYE|R#|*hhvhyhOL#9Yt7$g_)!X?fK^F z8UDz)(zpsvriJ5aro5>qy`Fnz%;IR$@Kg3Z3EE!fv9CAdrAym6QU82=_$_N5*({_1 z7!-=zy(R{xg9S519S6W{HpJZ8Is|kQ!0?`!vxDggmslD59)>iQ15f z7J8NqdR`9f8H|~iFGNsPV!N)(CC9JRmzL9S}7U-K@`X893f3f<8|8Ls!^eA^#(O6nA+ByFIXcz_WLbfeG|nHJ5_sJJ^gNJ%SI9#XEfNRbzV+!RkI zXS$MOVYb2!0vU}Gt7oUy*|WpF^*orBot~b2J@^be?Gq;U%#am8`PmH-UCFZ&uTJlnetYij0z{K1mmivk$bdPbLodu;-R@@#gAV!=d%(caz$E?r zURX0pqAn7UuF6dULnoF1dZ$WM)tHAM{eZK6DbU1J`V5Dw<;xk}Nl`h+nfMO_Rdv z3SyOMzAbYaD;mkxA7_I_DOs#Bk;e5D%gsS3q)hlmi1w{FsjKNJE22`AjmNiAPRnIc zcIkN25;rOn3FipAFd(PnlK9{03w6Q<(68#1Jw`{axEGQE{Ac>^U$h);h2ADICmaNxrfpb`Jdr*)Y1SicpYKCFv$3vf~;5aW>n^7QGa63MJ z;B1+Z>WQ615R2D8JmmT`T{QcgZ+Kz1hTu{9FOL}Q8+iFx-Vyi}ZVVcGjTe>QfA`7W zFoS__+;E_rQIQxd(Bq4$egKeKsk#-9=&A!)(|hBvydsr5ts0Zjp*%*C0lM2sIOx1s zg$xz?Fh?x!P^!vWa|}^+SY8oZHub7f;E!S&Q;F?dZmvBxuFEISC}$^B_x*N-xRRJh zn4W*ThEWaPD*$KBr8_?}XRhHY7h^U1aN6>m=n~?YJQd8+!Uyq_3^)~4>XjelM&!c9 zCo|0KsGq7!KsZ~9@%G?i>LaU7#uSTMpypocm*oqJHR|wOgVWc7_8PVuuw>x{kEG4T z$p^DV`}jUK39zqFc(d5;N+M!Zd3zhZN&?Ww(<@AV-&f!v$uV>%z+dg9((35o@4rqLvTC-se@hkn^6k7+xHiK-vTRvM8{bCejbU;1@U=*r}GTI?Oc$!b6NRcj83-zF; z=TB#ESDB`F`jf4)z=OS76Se}tQDDHh{VKJk#Ad6FDB_=afpK#pyRkGrk~OuzmQG)} z*$t!nZu$KN&B;|O-aD=H<|n6aGGJZ=K9QFLG0y=Jye_ElJFNZJT;fU8P8CZcLBERjioAOC0Vz_pIXIc};)8HjfPwNy zE!g|lkRv3qpmU?shz(BBt5%TbpJC3HzP9!t7k*Fh48!-HlJ4TTgdCr3rCU!iF}kgu z4Qs;K@XOY~4f~N}Jl8V_mGbwzvNLbl&0e9UG4W;kvjTK|5`-Ld+eQ6YRF`N0ct%u% z^3J_{7r#_W1zm|>IPN!yWCRrN)N!7v`~ptNkIXKipQ6ogFvcnI5ugxdoa{d;uD67g zgo^}QuZRkB540Vc!@c80(wFG=$ct}oHq(#W0+-XX(;Rrt`x=<45X}ficNtI2(&}=~ zb(!}tNz?s`wm{gK?2tdf+OEF;tzx<(3fMd7_tM@Ghs$Z(Os-H(kYq#qB|J-aC9Ku?fsWwJhB36c)A zu|a7ZF?V8X7l2g5~xqZf>2=6Dsi5lfo zKIRL&@MLJyaBE)V_9=pJYu%U2wxR*-(0MI5_|yqP`?h@cks(5LR@XUKLMI_xuVtiu zRvpDS8MyUMRFM6`P+Sjc!A_e^H38Qu7b{b7QZ>NHyA6k-YYygQuW&C_OGO(7V7?}r)zedSVpBI zuk29Z4GW3C0GpfozbZQya454sjt@ndQmsp=DA&@sWw&xmOlDk1JIcMNp~-ES$&A~k zG#W(6hBj?!Fu8Q4WYexoSBa8_5=v20xnx6H?e;$t)5|f&{7=vOye^&3_c-Ug?|a@e z=X`&qT_5B7N9vZoPBhXOTEDV;4&x2Je4}T(UB~O-$D#CjX77$R?RZ*`ed~$G;$4YS z4n*|Pop(!NN79Hk2}U#cfEEwdxM)xQm}$~rV03xc=#U@@Y*}qEmot5KvDb=8{!E-n zl4p?}&g2h^sUGyTcGh=0aQzQb*k;K;dvbeZUgmwEv>%#(EPtj=gHKdi|E8@w+|>KC zxEU>b>P+9Xf}pEyQK(}#QrBG4Jaf!iE!qpMbTu>gb!gtdq<`@xO+roQl+S_7)!G(% zdy)$iGmJ1cwP?F=IyyV1-$|kf|EKM3B@I&lZ%NI@VV;*mQdLWjc#t|Vbk_Q~>&O03 zIcSr$(qLAINj7a z;!||v&1D5SX#X@5jNd}jUsi-CH_Scjyht&}q2p*CJCC-`&NyXf)vD5{e!HO629D-O z%bZelTcq=DoRX>zeWCa^RmR3*{x9;3lZ75M#S)!W0bRIFH#P6b%{|HRSZ5!!I#s)W z_|XXZQ<0_`>b^^0Z>LU64Yg1w)8}#M^9se(OZ9~baZ7fsKFc;EtnB>kesci#>=icG zuHdjax2^=!_(9?0l7;G7^-}9>Y#M zm;9*GT~dBuYWdk49%mZM0=H#FY1)}7NE5DE_vsqrA0`?0R0q535qHjWXcl|gz9Fq$ zMKxgL;68l!gm3y0durIr3LHv~y*ABm` zYhQG0UW#hg@*A{&G!;$FS43}rIF$e6yRdGJWVR<}uuJ_5_8qa3xaHH^!VzUteVp;> z<0`M>3tnY$ZFb$(`0sg93TwGyP;`9UYUWxO&CvAnSzei&ap))NcW;R`tA=y^?mBmG+M*&bqW5kL$V(O;(p)aEk`^ci?2Jwxu>0sy>a7+Wa9t z5#I2o;+gr^9^&km^z7>xJWbN&Ft>Vna34E zI@BBzwX)R}K3SL?)enrDJ45QLt;-7CFJk{`cF3L4Z^CtG_r5)0)HV>BOYPIUh#D%| zYQAu31f{bm-D*`_k7DTTr?Nkw_gY%J1cb2&TdtibY?V=|SSIOlA;|5C!2@?YQ z-$?G0jj^mG|MP>DmbF7}T~C$H6=CpZ~hd zZ1C|xV@=h#^~`3LSCnmI(vZ|5r3>eq5*UB)dhdy``*gKY3Eg%jSK8I-`G+OWWlD)T zt$wSQ=||lSkiKy}YF-k}@W9EiS?)z`hK{R!dd-$BCJvBtAN-yXn3njU$MisEtp!?Q z%Vk-*(wy9dd15(-WFw_&^tT;;IpF?ox1`Qq3-0zVTk+$W_?q}GfAQlPcrB^?&tWSI z2BB!K=sH7FUYmXa_dcV^Z3>5z8}~W{S!$jVR_3hu_|wl2|gmRH8ftn^z@fW75*;-`;wU+fY+BR_yx6BZnE5_Hna({jrPiubRp$jZ=T=t$hx&NeCV1!vuCcl4PJ0p0Fjp>6K} zHkoD1gQk=P2hYcT%)cJ2Q5WuA|5_x+dX0%hnozfTF>$#Wz~X!MY>){H4#fB#7^ID* z1*o2Hzp}?WVs&gbS?Uq(CT0sP+F)u9{xfgg6o_{8J#m;|NeJqDHhb(Q8%z8aM_qeM zn83>d`uDd47WIuKp78JBYo2SYupGcNXIzeou^eMY`@%Bv8elZ>q~3uq#~IX)g%g;h zoUXymEd>|kVsMkyb&1l~lrE-`w(0PObapYa35DJ4Y03Jv_!DKp}0HTbOgZRM=;PSsuAJJJ1 zItc+tu9;ANG;qHaCI|T85!euhFK~VK^G2LZV1+cbzS?>ar@>emg;JTI5VAn1g5U~| zU=p&k0OlSzc$U=s#9_uL3&n|6A1X$XvrE9vFV@`A4G#!D1QcFCeE`F2N(deJx>)*A z$XIW0P~-NbAd=5i6`s<~(vAQX9t$dbVqc5|E|CHRtb$1(l&KSNh_t2#k_l95KnP86 z)ns_DGspv-M0z0#h2a+*oH|{5~j{ zXGD=}cLrBSESQ0u$XmQlFfWMCAWaS;wKK%#aSSYK=qljBiY(s zT$v;We24&$w=avIILsMt0%1fDyah|AlLNg#WL$Lu)tf}YfqO%+pH~QC*bZO4aM*i9 zrPFf|5!hv@XY8CzaFh*Dy9vH|2fKKr(@x}`L#9^*vOae|lk`adG#oZZAyk|TOV8`9L zc-sQu%y1MQes&J?)a1}Zc*>-P!6j-T#75V$lLC!TuMB(!G-+D2;XptUxymSPFI-K&0x}B1?h$ z3-9**-9!);fwyiWB5gS$i;P~c=^}5-6G@{4TWDBRDc6(M|%qa-mS`z`u9kWo{Xl_uc;hXOkRd literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..bb8b2fc2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..fbd7c515 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +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. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..a9f778a7 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..5b60df3d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/src/main/java/com/geysermc/floodgate/FabricMod.java b/src/main/java/com/geysermc/floodgate/FabricMod.java new file mode 100644 index 00000000..e828638a --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/FabricMod.java @@ -0,0 +1,46 @@ +package com.geysermc.floodgate; + +import com.geysermc.floodgate.inject.fabric.FabricInjector; +import com.geysermc.floodgate.module.FabricAddonModule; +import com.geysermc.floodgate.module.FabricCommandModule; +import com.geysermc.floodgate.module.FabricPlatformModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.kyori.adventure.platform.fabric.FabricServerAudiences; +import net.minecraft.server.MinecraftServer; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.module.ServerCommonModule; + +public class FabricMod implements ModInitializer { + private MinecraftServer server; + + @Override + public void onInitialize() { + FabricInjector.setInstance(new FabricInjector()); + + ServerLifecycleEvents.SERVER_STARTED.register((server) -> { + long ctm = System.currentTimeMillis(); + this.server = server; + + FabricServerAudiences adventure = FabricServerAudiences.of(server); + + Injector injector = Guice.createInjector( + new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate")), + new FabricPlatformModule(this.server, adventure) + ); + + injector.getInstance(FabricPlatform.class) + .enable( + new FabricAddonModule(), + new FabricCommandModule() + ); + + long endCtm = System.currentTimeMillis(); + injector.getInstance(FloodgateLogger.class) + .translatedInfo("floodgate.core.finish", endCtm - ctm); + }); + } +} diff --git a/src/main/java/com/geysermc/floodgate/FabricPlatform.java b/src/main/java/com/geysermc/floodgate/FabricPlatform.java new file mode 100644 index 00000000..fc73ca6d --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/FabricPlatform.java @@ -0,0 +1,15 @@ +package com.geysermc.floodgate; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.geysermc.floodgate.FloodgatePlatform; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.inject.PlatformInjector; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +public class FabricPlatform extends FloodgatePlatform { + @Inject + public FabricPlatform(FloodgateApi api, PlatformInjector platformInjector, FloodgateLogger logger, Injector guice) { + super(api, platformInjector, logger, guice); + } +} diff --git a/src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java new file mode 100644 index 00000000..b51250c6 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -0,0 +1,59 @@ +package com.geysermc.floodgate.addon.data; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.util.Utils; + +public class FabricDataAddon implements InjectorAddon { + @Inject private FloodgateHandshakeHandler handshakeHandler; + @Inject private FloodgateConfig config; + @Inject private SimpleFloodgateApi api; + @Inject private FloodgateLogger logger; + + @Inject + @Named("packetHandler") + private String packetHandlerName; + + @Inject + @Named("playerAttribute") + private AttributeKey playerAttribute; + + @Override + public void onInject(Channel channel, boolean toServer) { + channel.pipeline().addBefore( + packetHandlerName, "floodgate_data_handler", + new FabricDataHandler(config, handshakeHandler, logger) + ); + } + + @Override + public void onLoginDone(Channel channel) { + onRemoveInject(channel); + } + + @Override + public void onChannelClosed(Channel channel) { + FloodgatePlayer player = channel.attr(playerAttribute).get(); + if (player != null && api.removePlayer(player)) { + logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername()); + } + } + + @Override + public void onRemoveInject(Channel channel) { + Utils.removeHandler(channel.pipeline(), "floodgate_data_handler"); + } + + @Override + public boolean shouldInject() { + return true; + } +} diff --git a/src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java new file mode 100644 index 00000000..0d727655 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -0,0 +1,117 @@ +package com.geysermc.floodgate.addon.data; + +import com.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; +import com.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; +import com.mojang.authlib.GameProfile; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.ReferenceCountUtil; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import org.geysermc.floodgate.api.handshake.HandshakeData; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.floodgate.util.Constants; + +@RequiredArgsConstructor +public class FabricDataHandler extends ChannelInboundHandlerAdapter { + private final FloodgateConfig config; + private final FloodgateHandshakeHandler handshakeHandler; + private final FloodgateLogger logger; + private ClientConnection networkManager; + private FloodgatePlayer player; + private boolean done; + + @Override + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + ReferenceCountUtil.retain(packet); + if (done) { + super.channelRead(ctx, packet); + return; + } + + boolean isHandshake = packet instanceof HandshakeC2SPacket; + boolean isLogin = packet instanceof LoginHelloC2SPacket; + try { + if (isHandshake) { + networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); + + String handshakeValue = ((HandshakeS2CPacketAddressGetter) packet).getAddress(); + FloodgateHandshakeHandler.HandshakeResult result = handshakeHandler.handle(ctx.channel(), handshakeValue); + HandshakeData handshakeData = result.getHandshakeData(); + + ((HandshakeS2CPacketAddressGetter) packet).setAddress(handshakeData.getHostname()); + + if (handshakeData.getDisconnectReason() != null) { + ctx.close(); //todo disconnect with message + return; + } + + //todo use kickMessageAttribute and let this be common logic + + switch (result.getResultType()) { + case SUCCESS: + break; + case EXCEPTION: + logger.info(config.getDisconnect().getInvalidKey()); + ctx.close(); + return; + case INVALID_DATA_LENGTH: + int dataLength = result.getBedrockData().getDataLength(); + logger.info( + config.getDisconnect().getInvalidArgumentsLength(), + BedrockData.EXPECTED_LENGTH, dataLength + ); + ctx.close(); + return; + case TIMESTAMP_DENIED: + logger.info(Constants.TIMESTAMP_DENIED_MESSAGE); + ctx.close(); + return; + default: // only continue when SUCCESS + return; + } + + player = result.getFloodgatePlayer(); + } else if (isLogin) { + // we have to fake the offline player (login) cycle + if (!(networkManager.getPacketListener() instanceof ServerLoginNetworkHandler)) { + // player is not in the login state, abort + return; + } + + GameProfile gameProfile = new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername()); + + ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); + ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setLoginState(); + } + } finally { + // don't let the packet through if the packet is the login packet + // because we want to skip the login cycle + if (isLogin) { + ReferenceCountUtil.release(packet, 2); + } else { + ctx.fireChannelRead(packet); + } + + if (isLogin || player == null) { + // we're done, we'll just wait for the loginSuccessCall + done = true; + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + if (config.isDebug()) { + cause.printStackTrace(); + } + } +} diff --git a/src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java b/src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java new file mode 100644 index 00000000..44291cd9 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java @@ -0,0 +1,54 @@ +package com.geysermc.floodgate.inject.fabric; + +import io.netty.channel.*; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.minecraft.server.MinecraftServer; +import org.geysermc.floodgate.inject.CommonPlatformInjector; + +@RequiredArgsConstructor +public class FabricInjector extends CommonPlatformInjector { + private static FabricInjector instance; + + @Getter private final boolean injected = true; + + @Override + public boolean inject() throws Exception { + return true; + } + + public void injectClient(ChannelFuture future) { + future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + super.channelRead(ctx, msg); + + Channel channel = (Channel) msg; + channel.pipeline().addLast(new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) { + injectAddonsCall(channel, false); + addInjectedClient(channel); + channel.closeFuture().addListener(listener -> { + channelClosedCall(channel); + removeInjectedClient(channel); + }); + } + }); + } + }); + } + + @Override + public boolean removeInjection() throws Exception { + return true; + } + + public static FabricInjector getInstance() { + return instance; + } + + public static void setInstance(FabricInjector injector) { + instance = injector; + } +} diff --git a/src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java b/src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java new file mode 100644 index 00000000..a776107d --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java @@ -0,0 +1,12 @@ +package com.geysermc.floodgate.listener; + +public class FabricEventListener { + + public void onPlayerJoin() { + + } + + public void onPlayerQuit() { + //todo + } +} diff --git a/src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java b/src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java new file mode 100644 index 00000000..9f42a8d7 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java @@ -0,0 +1,70 @@ +package com.geysermc.floodgate.logger; + +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.util.LanguageManager; + +import static org.geysermc.floodgate.util.MessageFormatter.format; + +@RequiredArgsConstructor +public class Log4jFloodgateLogger implements FloodgateLogger { + private final Logger logger; + private final LanguageManager languageManager; + + @Override + public void error(String message, Object... args) { + logger.error(message, args); + } + + @Override + public void error(String message, Throwable throwable, Object... args) { + logger.error(format(message, args), throwable); + } + + @Override + public void warn(String message, Object... args) { + logger.warn(message, args); + } + + @Override + public void info(String message, Object... args) { + logger.info(message, args); + } + + @Override + public void translatedInfo(String message, Object... args) { + logger.info(languageManager.getLogString(message, args)); + } + + @Override + public void debug(String message, Object... args) { + logger.debug(message, args); + } + + @Override + public void trace(String message, Object... args) { + logger.trace(message, args); + } + + @Override + public void enableDebug() { + if (!logger.isDebugEnabled()) { + Configurator.setLevel(logger.getName(), Level.DEBUG); + } + } + + @Override + public void disableDebug() { + if (logger.isDebugEnabled()) { + Configurator.setLevel(logger.getName(), Level.INFO); + } + } + + @Override + public boolean isDebug() { + return logger.isDebugEnabled(); + } +} diff --git a/src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java b/src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java new file mode 100644 index 00000000..be2f750b --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java @@ -0,0 +1,22 @@ +package com.geysermc.floodgate.mixin; + +import com.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(HandshakeC2SPacket.class) +public abstract class HandshakeC2SPacketMixin implements HandshakeS2CPacketAddressGetter { + + @Shadow private String address; + + @Override + public String getAddress() { + return this.address; + } + + @Override + public void setAddress(String address) { + this.address = address; + } +} diff --git a/src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java b/src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java new file mode 100644 index 00000000..77f221b1 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java @@ -0,0 +1,26 @@ +package com.geysermc.floodgate.mixin; + +import com.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; +import com.mojang.authlib.GameProfile; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ServerLoginNetworkHandler.class) +public abstract class ServerLoginNetworkHandlerMixin implements ServerLoginNetworkHandlerSetter { + @Shadow + private GameProfile profile; + + @Shadow + private ServerLoginNetworkHandler.State state; + + @Override + public void setGameProfile(GameProfile profile) { + this.profile = profile; + } + + @Override + public void setLoginState() { + this.state = ServerLoginNetworkHandler.State.READY_TO_ACCEPT; + } +} diff --git a/src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java b/src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java new file mode 100644 index 00000000..93444875 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java @@ -0,0 +1,25 @@ +package com.geysermc.floodgate.mixin; + +import com.geysermc.floodgate.inject.fabric.FabricInjector; +import io.netty.channel.ChannelFuture; +import net.minecraft.server.ServerNetworkIo; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.net.InetAddress; +import java.util.List; + +@Mixin(ServerNetworkIo.class) +public class ServerNetworkIoMixin { + + @Shadow @Final private List channels; + + @Inject(method = "bind", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) + public void onChannelAdd(InetAddress address, int port, CallbackInfo ci) { + FabricInjector.getInstance().injectClient(this.channels.get(this.channels.size() - 1)); + } +} diff --git a/src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java b/src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java new file mode 100644 index 00000000..f2f0d836 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java @@ -0,0 +1,8 @@ +package com.geysermc.floodgate.mixin_interface; + +public interface HandshakeS2CPacketAddressGetter { + String getAddress(); + + //TODO packet fields will become final in 1.17 + void setAddress(String address); +} diff --git a/src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java b/src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java new file mode 100644 index 00000000..83191f44 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java @@ -0,0 +1,12 @@ +package com.geysermc.floodgate.mixin_interface; + +import com.mojang.authlib.GameProfile; +import net.minecraft.server.network.ServerLoginNetworkHandler; + +import java.util.UUID; + +public interface ServerLoginNetworkHandlerSetter { + void setGameProfile(GameProfile profile); + + void setLoginState(); +} diff --git a/src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java b/src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java new file mode 100644 index 00000000..64df3925 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java @@ -0,0 +1,35 @@ +package com.geysermc.floodgate.module; + +import com.geysermc.floodgate.addon.data.FabricDataAddon; +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.addon.AddonManagerAddon; +import org.geysermc.floodgate.addon.DebugAddon; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.register.AddonRegister; + +public class FabricAddonModule extends AbstractModule { + @Override + protected void configure() { + bind(AddonRegister.class).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon managerAddon() { + return new AddonManagerAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon dataAddon() { + return new FabricDataAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon debugAddon() { + return new DebugAddon(); + } +} diff --git a/src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java b/src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java new file mode 100644 index 00000000..ee1a1a89 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java @@ -0,0 +1,30 @@ +package com.geysermc.floodgate.module; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.fabric.FabricCommandManager; +import cloud.commandframework.fabric.FabricServerCommandManager; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import lombok.SneakyThrows; +import net.minecraft.server.command.ServerCommandSource; +import org.geysermc.floodgate.module.CommandModule; +import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; +import org.geysermc.floodgate.player.UserAudience; + +public class FabricCommandModule extends CommandModule { + @Provides + @Singleton + @SneakyThrows + public CommandManager commandManager(CommandUtil commandUtil) { + FabricCommandManager commandManager = new FabricServerCommandManager<>( + CommandExecutionCoordinator.simpleCoordinator(), + commandUtil::getAudience, + audience -> (ServerCommandSource) audience.source() + ); + commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + return commandManager; + } + +} diff --git a/src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java b/src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java new file mode 100644 index 00000000..8a4152d4 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java @@ -0,0 +1,6 @@ +package com.geysermc.floodgate.module; + +import com.google.inject.AbstractModule; + +public class FabricListenerModule extends AbstractModule { +} diff --git a/src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java new file mode 100644 index 00000000..61ece880 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java @@ -0,0 +1,93 @@ +package com.geysermc.floodgate.module; + +import com.geysermc.floodgate.inject.fabric.FabricInjector; +import com.geysermc.floodgate.logger.Log4jFloodgateLogger; +import com.geysermc.floodgate.pluginmessage.FabricSkinApplier; +import com.geysermc.floodgate.util.FabricCommandUtil; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.platform.fabric.FabricServerAudiences; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.LogManager; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.skin.SkinApplier; +import org.geysermc.floodgate.util.LanguageManager; + +@RequiredArgsConstructor +public class FabricPlatformModule extends AbstractModule { + private final MinecraftServer server; + private final FabricServerAudiences adventure; + + @Provides + @Singleton + public MinecraftServer server() { + return server; + } + + @Provides + @Singleton + public FabricServerAudiences adventure() { + return adventure; + } + + @Provides + @Singleton + public FloodgateLogger floodgateLogger(LanguageManager languageManager) { + return new Log4jFloodgateLogger(LogManager.getLogger("floodgate"), languageManager); + } + + @Provides + @Singleton + public CommandUtil commandUtil( + FloodgateApi api, + FloodgateLogger logger, + LanguageManager languageManager) { + return new FabricCommandUtil(adventure, api, logger, languageManager, server); + } + + /* + DebugAddon / PlatformInjector + */ + + @Provides + @Singleton + public CommonPlatformInjector platformInjector() { + return FabricInjector.getInstance(); + } + + @Provides + @Named("packetEncoder") + public String packetEncoder() { + return "encoder"; + } + + @Provides + @Named("packetDecoder") + public String packetDecoder() { + return "decoder"; + } + + @Provides + @Named("packetHandler") + public String packetHandler() { + return "packet_handler"; + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "Fabric"; + } + + @Provides + @Singleton + public SkinApplier skinApplier(MinecraftServer server) { + return new FabricSkinApplier(server); + } +} diff --git a/src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java new file mode 100644 index 00000000..3c6d2b81 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -0,0 +1,58 @@ +package com.geysermc.floodgate.pluginmessage; + +import com.google.gson.JsonObject; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.packet.s2c.play.EntitiesDestroyS2CPacket; +import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; +import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.skin.SkinApplier; + +@RequiredArgsConstructor +public class FabricSkinApplier implements SkinApplier { + private final MinecraftServer server; + + @Override + public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) { + this.server.execute(() -> { + ServerPlayerEntity bedrockPlayer = this.server.getPlayerManager().getPlayer(floodgatePlayer.getCorrectUniqueId()); + if (bedrockPlayer == null) { + // Disconnected probably? + return; + } + + // Apply the new skin internally + PropertyMap properties = bedrockPlayer.getGameProfile().getProperties(); + + properties.removeAll("textures"); + Property property = new Property( + "textures", + skinResult.get("value").getAsString(), + skinResult.get("signature").getAsString()); + properties.put("textures", property); + + // Skin is applied - now it's time to refresh the player for everyone. Oof. + for (ServerPlayerEntity otherPlayer : this.server.getPlayerManager().getPlayerList()) { + if (otherPlayer == bedrockPlayer) { + continue; + } + + boolean loadedInWorld = otherPlayer.getEntityWorld().getEntityById(bedrockPlayer.getEntityId()) != null; + if (loadedInWorld) { + // Player is loaded in this world + otherPlayer.networkHandler.sendPacket(new EntitiesDestroyS2CPacket(bedrockPlayer.getEntityId())); + } + otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.REMOVE_PLAYER, bedrockPlayer)); + + otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.ADD_PLAYER, bedrockPlayer)); + if (loadedInWorld) { + otherPlayer.networkHandler.sendPacket(new PlayerSpawnS2CPacket(bedrockPlayer)); + } + } + }); + } +} diff --git a/src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java new file mode 100644 index 00000000..fb3e62e1 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java @@ -0,0 +1,160 @@ +package com.geysermc.floodgate.util; + +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.platform.fabric.FabricServerAudiences; +import net.kyori.adventure.platform.fabric.impl.accessor.ConnectionAccess; +import net.kyori.adventure.platform.fabric.impl.server.FriendlyByteBufBridge; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.WhitelistEntry; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.player.UserAudience; +import org.geysermc.floodgate.player.UserAudienceArgument; +import org.geysermc.floodgate.util.LanguageManager; +import org.geysermc.floodgate.util.Utils; + +import java.util.*; + +@RequiredArgsConstructor +public class FabricCommandUtil implements CommandUtil { + @Getter private final FabricServerAudiences adventure; + @Getter private final FloodgateApi api; + @Getter private final FloodgateLogger logger; + @Getter private final LanguageManager manager; + @Getter private final MinecraftServer server; + + @Override + public @NonNull UserAudience getAudience(@NonNull Object source) { + if (!(source instanceof ServerCommandSource)) { + throw new RuntimeException(); + } + + ServerCommandSource commandSource = (ServerCommandSource) source; + if (commandSource.getEntity() instanceof ServerPlayerEntity) { + return getAudience0((ServerPlayerEntity) commandSource.getEntity()); + } + + return new FabricUserAudience(null, manager.getDefaultLocale(), commandSource, this); + } + + @Override + public @Nullable UserAudience getAudienceByUuid(@NonNull UUID uuid) { + ServerPlayerEntity player = this.server.getPlayerManager().getPlayer(uuid); + if (player != null) { + return getAudience0(player); + } + return getOfflineAudienceByUuid(uuid); + } + + @Override + public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { + ServerPlayerEntity player = this.server.getPlayerManager().getPlayer(username); + if (player != null) { + return getAudience0(player); + } + return getOfflineAudienceByUsername(username); + } + + private FabricUserAudience getAudience0(ServerPlayerEntity player) { + // Marked as internal??? Should probably find a better way to get this. + Locale locale = ((ConnectionAccess) player.networkHandler.getConnection()).getChannel().attr(FriendlyByteBufBridge.CHANNEL_LOCALE).get(); + return new FabricUserAudience( + player.getUuid(), locale != null ? + locale.getLanguage().toLowerCase(Locale.ROOT) + "_" + locale.getCountry().toUpperCase(Locale.ROOT) : + manager.getDefaultLocale(), player.getCommandSource(), this); + } + + @Override + public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { + return new FabricUserAudience(uuid, null, null, this); + } + + @Override + public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { + UUID uuid = null; + GameProfile profile = this.server.getUserCache().findByName(username); + if (profile != null) { + uuid = profile.getId(); + } + return new FabricUserAudience(uuid, username, null, this); + } + + @Override + public @NonNull Collection getOnlineUsernames(UserAudienceArgument.@NonNull PlayerType limitTo) { + List players = this.server.getPlayerManager().getPlayerList(); + + Collection usernames = new ArrayList<>(); + switch (limitTo) { + case ALL_PLAYERS: + for (ServerPlayerEntity player : players) { + usernames.add(player.getName().asString()); + } + break; + case ONLY_JAVA: + for (ServerPlayerEntity player : players) { + if (!api.isFloodgatePlayer(player.getUuid())) { + usernames.add(player.getName().asString()); + } + } + break; + case ONLY_BEDROCK: + for (ServerPlayerEntity player : players) { + if (api.isFloodgatePlayer(player.getUuid())) { + usernames.add(player.getName().asString()); + } + } + break; + default: + throw new IllegalStateException("Unknown PlayerType"); + } + return usernames; + } + + @Override + public void sendMessage(Object player, String locale, CommandMessage message, Object... args) { + getPlayer(player).sendMessage(translateAndTransform(locale, message, args), false); + } + + @Override + public void kickPlayer(Object player, String locale, CommandMessage message, Object... args) { + getPlayer(player).networkHandler.disconnect(translateAndTransform(locale, message, args)); + } + + @Override + public boolean whitelistPlayer(String xuid, String username) { + GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username); + this.server.getPlayerManager().getWhitelist().add(new WhitelistEntry(profile)); + return true; + } + + @Override + public boolean removePlayerFromWhitelist(String xuid, String username) { + GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username); + this.server.getPlayerManager().getWhitelist().remove(profile); + return true; + } + + protected ServerPlayerEntity getPlayer(Object instance) { + try { + ServerCommandSource source = (ServerCommandSource) instance; + return source.getPlayer(); + } catch (ClassCastException | CommandSyntaxException exception) { + logger.error("Failed to cast {} to Player", instance.getClass().getName()); + throw new RuntimeException(); + } + } + + public Text translateAndTransform(String locale, CommandMessage message, Object... args) { + return Text.of(message.translateMessage(manager, locale, args)); + } +} diff --git a/src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java new file mode 100644 index 00000000..dba7a888 --- /dev/null +++ b/src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java @@ -0,0 +1,91 @@ +package com.geysermc.floodgate.util; + +import lombok.RequiredArgsConstructor; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.player.UserAudience; + +import java.util.UUID; + +@RequiredArgsConstructor +public class FabricUserAudience implements UserAudience, ForwardingAudience.Single { + private final UUID uuid; + private final String locale; + private final ServerCommandSource source; + private final FabricCommandUtil commandUtil; + + @Override + public @NonNull Audience audience() { + return commandUtil.getAdventure().audience(source); + } + + @Override + public @NonNull UUID uuid() { + return uuid; + } + + @Override + public @NonNull String username() { + return source.getName(); + } + + @Override + public @NonNull String locale() { + return locale; + } + + @Override + public @NonNull ServerCommandSource source() { + return source; + } + + @Override + public boolean hasPermission(@NonNull String permission) { + //TODO + return true; + } + + @Override + public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { + if (this.source.getEntity() instanceof ServerPlayerEntity) { + ((ServerPlayerEntity) this.source.getEntity()).sendMessage( + commandUtil.getAdventure().toNative(message), false + ); + } + } + + @Override + public void sendMessage(CommandMessage message, Object... args) { + commandUtil.sendMessage(this.source, this.locale, message, args); + } + + @Override + public void disconnect(@NonNull Component reason) { + if (source.getEntity() instanceof ServerPlayerEntity) { + ((ServerPlayerEntity) source.getEntity()).networkHandler.disconnect( + commandUtil.getAdventure().toNative(reason) + ); + } + } + + @Override + public void disconnect(CommandMessage message, Object... args) { + if (source.getEntity() instanceof ServerPlayerEntity) { + ((ServerPlayerEntity) source.getEntity()).networkHandler.disconnect( + commandUtil.translateAndTransform(this.locale, message, args) + ); + } + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..1f351e41 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "floodgate", + "version": "${version}", + "name": "Floodgate-Fabric", + "description": "", + "authors": [ + "GeyserMC" + ], + "contact": { + "website": "https://geysermc.org", + "repo": "https://github.com/GeyserMC/Floodgate-Fabric" + }, + "license": "MIT", + "icon": "assets/floodgate/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.geysermc.floodgate.FabricMod" + ] + }, + "accessWidener": "floodgate.accesswidener", + "mixins": [ + "floodgate.mixins.json" + ], + "depends": { + "fabricloader": ">=0.11.3", + "fabric": "*", + "minecraft": "1.16.5" + } +} diff --git a/src/main/resources/floodgate.accesswidener b/src/main/resources/floodgate.accesswidener new file mode 100644 index 00000000..932b0de1 --- /dev/null +++ b/src/main/resources/floodgate.accesswidener @@ -0,0 +1,4 @@ +accessWidener v1 named + +# To change login state +accessible class net/minecraft/server/network/ServerLoginNetworkHandler$State \ No newline at end of file diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json new file mode 100644 index 00000000..e1ed9b5f --- /dev/null +++ b/src/main/resources/floodgate.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.geysermc.floodgate.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "HandshakeC2SPacketMixin", + "ServerLoginNetworkHandlerMixin", + "ServerNetworkIoMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} From 064184a9d229e9031a6db1b11415bc7ce36eee62 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 27 Mar 2021 10:38:42 -0400 Subject: [PATCH 02/87] Fix package name --- .../{com => org}/geysermc/floodgate/FabricMod.java | 10 +++++----- .../geysermc/floodgate/FabricPlatform.java | 2 +- .../geysermc/floodgate/addon/data/FabricDataAddon.java | 2 +- .../floodgate/addon/data/FabricDataHandler.java | 6 +++--- .../floodgate/inject/fabric/FabricInjector.java | 3 +-- .../floodgate/listener/FabricEventListener.java | 2 +- .../floodgate/logger/Log4jFloodgateLogger.java | 2 +- .../floodgate/mixin/HandshakeC2SPacketMixin.java | 4 ++-- .../mixin/ServerLoginNetworkHandlerMixin.java | 4 ++-- .../geysermc/floodgate/mixin/ServerNetworkIoMixin.java | 4 ++-- .../HandshakeS2CPacketAddressGetter.java | 2 +- .../ServerLoginNetworkHandlerSetter.java | 2 +- .../geysermc/floodgate/module/FabricAddonModule.java | 4 ++-- .../geysermc/floodgate/module/FabricCommandModule.java | 2 +- .../floodgate/module/FabricListenerModule.java | 2 +- .../floodgate/module/FabricPlatformModule.java | 10 +++++----- .../floodgate/pluginmessage/FabricSkinApplier.java | 2 +- .../geysermc/floodgate/util/FabricCommandUtil.java | 4 +--- .../geysermc/floodgate/util/FabricUserAudience.java | 7 +------ src/main/resources/fabric.mod.json | 2 +- src/main/resources/floodgate.mixins.json | 6 +++--- 21 files changed, 37 insertions(+), 45 deletions(-) rename src/main/java/{com => org}/geysermc/floodgate/FabricMod.java (85%) rename src/main/java/{com => org}/geysermc/floodgate/FabricPlatform.java (94%) rename src/main/java/{com => org}/geysermc/floodgate/addon/data/FabricDataAddon.java (97%) rename src/main/java/{com => org}/geysermc/floodgate/addon/data/FabricDataHandler.java (96%) rename src/main/java/{com => org}/geysermc/floodgate/inject/fabric/FabricInjector.java (94%) rename src/main/java/{com => org}/geysermc/floodgate/listener/FabricEventListener.java (76%) rename src/main/java/{com => org}/geysermc/floodgate/logger/Log4jFloodgateLogger.java (97%) rename src/main/java/{com => org}/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java (83%) rename src/main/java/{com => org}/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java (87%) rename src/main/java/{com => org}/geysermc/floodgate/mixin/ServerNetworkIoMixin.java (89%) rename src/main/java/{com => org}/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java (77%) rename src/main/java/{com => org}/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java (84%) rename src/main/java/{com => org}/geysermc/floodgate/module/FabricAddonModule.java (90%) rename src/main/java/{com => org}/geysermc/floodgate/module/FabricCommandModule.java (96%) rename src/main/java/{com => org}/geysermc/floodgate/module/FabricListenerModule.java (72%) rename src/main/java/{com => org}/geysermc/floodgate/module/FabricPlatformModule.java (89%) rename src/main/java/{com => org}/geysermc/floodgate/pluginmessage/FabricSkinApplier.java (98%) rename src/main/java/{com => org}/geysermc/floodgate/util/FabricCommandUtil.java (98%) rename src/main/java/{com => org}/geysermc/floodgate/util/FabricUserAudience.java (89%) diff --git a/src/main/java/com/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java similarity index 85% rename from src/main/java/com/geysermc/floodgate/FabricMod.java rename to src/main/java/org/geysermc/floodgate/FabricMod.java index e828638a..76230d79 100644 --- a/src/main/java/com/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -1,9 +1,9 @@ -package com.geysermc.floodgate; +package org.geysermc.floodgate; -import com.geysermc.floodgate.inject.fabric.FabricInjector; -import com.geysermc.floodgate.module.FabricAddonModule; -import com.geysermc.floodgate.module.FabricCommandModule; -import com.geysermc.floodgate.module.FabricPlatformModule; +import org.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.floodgate.module.FabricAddonModule; +import org.geysermc.floodgate.module.FabricCommandModule; +import org.geysermc.floodgate.module.FabricPlatformModule; import com.google.inject.Guice; import com.google.inject.Injector; import net.fabricmc.api.ModInitializer; diff --git a/src/main/java/com/geysermc/floodgate/FabricPlatform.java b/src/main/java/org/geysermc/floodgate/FabricPlatform.java similarity index 94% rename from src/main/java/com/geysermc/floodgate/FabricPlatform.java rename to src/main/java/org/geysermc/floodgate/FabricPlatform.java index fc73ca6d..cb8165c9 100644 --- a/src/main/java/com/geysermc/floodgate/FabricPlatform.java +++ b/src/main/java/org/geysermc/floodgate/FabricPlatform.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate; +package org.geysermc.floodgate; import com.google.inject.Inject; import com.google.inject.Injector; diff --git a/src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java similarity index 97% rename from src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java rename to src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java index b51250c6..86a96d96 100644 --- a/src/main/java/com/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.addon.data; +package org.geysermc.floodgate.addon.data; import com.google.inject.Inject; import com.google.inject.name.Named; diff --git a/src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java similarity index 96% rename from src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java rename to src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 0d727655..3ae717da 100644 --- a/src/main/java/com/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -1,7 +1,7 @@ -package com.geysermc.floodgate.addon.data; +package org.geysermc.floodgate.addon.data; -import com.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; -import com.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; +import org.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; +import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; diff --git a/src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java similarity index 94% rename from src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java rename to src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java index 44291cd9..783d585e 100644 --- a/src/main/java/com/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java @@ -1,9 +1,8 @@ -package com.geysermc.floodgate.inject.fabric; +package org.geysermc.floodgate.inject.fabric; import io.netty.channel.*; import lombok.Getter; import lombok.RequiredArgsConstructor; -import net.minecraft.server.MinecraftServer; import org.geysermc.floodgate.inject.CommonPlatformInjector; @RequiredArgsConstructor diff --git a/src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java similarity index 76% rename from src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java rename to src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java index a776107d..ecedc785 100644 --- a/src/main/java/com/geysermc/floodgate/listener/FabricEventListener.java +++ b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.listener; +package org.geysermc.floodgate.listener; public class FabricEventListener { diff --git a/src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java b/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java similarity index 97% rename from src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java rename to src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java index 9f42a8d7..232d93a3 100644 --- a/src/main/java/com/geysermc/floodgate/logger/Log4jFloodgateLogger.java +++ b/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.logger; +package org.geysermc.floodgate.logger; import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.Level; diff --git a/src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java b/src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java similarity index 83% rename from src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java rename to src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java index be2f750b..f930268d 100644 --- a/src/main/java/com/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java @@ -1,6 +1,6 @@ -package com.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mixin; -import com.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; +import org.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java similarity index 87% rename from src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java rename to src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java index 77f221b1..71049150 100644 --- a/src/main/java/com/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java @@ -1,6 +1,6 @@ -package com.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mixin; -import com.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; +import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; import com.mojang.authlib.GameProfile; import net.minecraft.server.network.ServerLoginNetworkHandler; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java similarity index 89% rename from src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java rename to src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java index 93444875..50dc5a05 100644 --- a/src/main/java/com/geysermc/floodgate/mixin/ServerNetworkIoMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java @@ -1,6 +1,6 @@ -package com.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mixin; -import com.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.floodgate.inject.fabric.FabricInjector; import io.netty.channel.ChannelFuture; import net.minecraft.server.ServerNetworkIo; import org.spongepowered.asm.mixin.Final; diff --git a/src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java b/src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java similarity index 77% rename from src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java rename to src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java index f2f0d836..8f5fb568 100644 --- a/src/main/java/com/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java +++ b/src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.mixin_interface; +package org.geysermc.floodgate.mixin_interface; public interface HandshakeS2CPacketAddressGetter { String getAddress(); diff --git a/src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java similarity index 84% rename from src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java rename to src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java index 83191f44..f9e8c2be 100644 --- a/src/main/java/com/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java +++ b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.mixin_interface; +package org.geysermc.floodgate.mixin_interface; import com.mojang.authlib.GameProfile; import net.minecraft.server.network.ServerLoginNetworkHandler; diff --git a/src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java b/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java similarity index 90% rename from src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java rename to src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java index 64df3925..9e7c8410 100644 --- a/src/main/java/com/geysermc/floodgate/module/FabricAddonModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java @@ -1,6 +1,6 @@ -package com.geysermc.floodgate.module; +package org.geysermc.floodgate.module; -import com.geysermc.floodgate.addon.data.FabricDataAddon; +import org.geysermc.floodgate.addon.data.FabricDataAddon; import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.multibindings.ProvidesIntoSet; diff --git a/src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java similarity index 96% rename from src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java rename to src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java index ee1a1a89..0b74ab2b 100644 --- a/src/main/java/com/geysermc/floodgate/module/FabricCommandModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.module; +package org.geysermc.floodgate.module; import cloud.commandframework.CommandManager; import cloud.commandframework.execution.CommandExecutionCoordinator; diff --git a/src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java b/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java similarity index 72% rename from src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java rename to src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java index 8a4152d4..f00592bc 100644 --- a/src/main/java/com/geysermc/floodgate/module/FabricListenerModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.module; +package org.geysermc.floodgate.module; import com.google.inject.AbstractModule; diff --git a/src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java similarity index 89% rename from src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java rename to src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index 61ece880..dd696b97 100644 --- a/src/main/java/com/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -1,9 +1,9 @@ -package com.geysermc.floodgate.module; +package org.geysermc.floodgate.module; -import com.geysermc.floodgate.inject.fabric.FabricInjector; -import com.geysermc.floodgate.logger.Log4jFloodgateLogger; -import com.geysermc.floodgate.pluginmessage.FabricSkinApplier; -import com.geysermc.floodgate.util.FabricCommandUtil; +import org.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.floodgate.logger.Log4jFloodgateLogger; +import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; +import org.geysermc.floodgate.util.FabricCommandUtil; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.Singleton; diff --git a/src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java similarity index 98% rename from src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java rename to src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 3c6d2b81..3ce8ce89 100644 --- a/src/main/java/com/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.pluginmessage; import com.google.gson.JsonObject; import com.mojang.authlib.properties.Property; diff --git a/src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java similarity index 98% rename from src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java rename to src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index fb3e62e1..4dd57eb0 100644 --- a/src/main/java/com/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -1,4 +1,4 @@ -package com.geysermc.floodgate.util; +package org.geysermc.floodgate.util; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -20,8 +20,6 @@ import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudienceArgument; -import org.geysermc.floodgate.util.LanguageManager; -import org.geysermc.floodgate.util.Utils; import java.util.*; diff --git a/src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java similarity index 89% rename from src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java rename to src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java index dba7a888..ad833648 100644 --- a/src/main/java/com/geysermc/floodgate/util/FabricUserAudience.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java @@ -1,20 +1,15 @@ -package com.geysermc.floodgate.util; +package org.geysermc.floodgate.util; import lombok.RequiredArgsConstructor; -import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.platform.command.CommandMessage; -import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.UserAudience; import java.util.UUID; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 1f351e41..ba4832b4 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ "environment": "*", "entrypoints": { "main": [ - "com.geysermc.floodgate.FabricMod" + "org.geysermc.floodgate.FabricMod" ] }, "accessWidener": "floodgate.accesswidener", diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index e1ed9b5f..e6a9ffc4 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -4,9 +4,9 @@ "package": "com.geysermc.floodgate.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ - "HandshakeC2SPacketMixin", - "ServerLoginNetworkHandlerMixin", - "ServerNetworkIoMixin" + "org.geysermc.floodgate.mixin.HandshakeC2SPacketMixin", + "org.geysermc.floodgate.mixin.ServerLoginNetworkHandlerMixin", + "org.geysermc.floodgate.mixin.ServerNetworkIoMixin" ], "injectors": { "defaultRequire": 1 From be3bef4ce8bdc03b296abc8bb7e07b30695607e3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 27 Mar 2021 10:44:59 -0400 Subject: [PATCH 03/87] Fix mixins --- src/main/resources/floodgate.mixins.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index e6a9ffc4..66847086 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -1,12 +1,12 @@ { "required": true, "minVersion": "0.8", - "package": "com.geysermc.floodgate.mixin", + "package": "org.geysermc.floodgate.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ - "org.geysermc.floodgate.mixin.HandshakeC2SPacketMixin", - "org.geysermc.floodgate.mixin.ServerLoginNetworkHandlerMixin", - "org.geysermc.floodgate.mixin.ServerNetworkIoMixin" + "HandshakeC2SPacketMixin", + "ServerLoginNetworkHandlerMixin", + "ServerNetworkIoMixin" ], "injectors": { "defaultRequire": 1 From 312c59f67d87aba5e56f6af3e4fa099bbcca6d31 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 28 Mar 2021 18:27:40 -0400 Subject: [PATCH 04/87] Final classes; implement join message --- build.gradle | 3 ++ .../org/geysermc/floodgate/FabricMod.java | 10 ++++--- .../geysermc/floodgate/FabricPlatform.java | 2 +- .../floodgate/addon/data/FabricDataAddon.java | 2 +- .../addon/data/FabricDataHandler.java | 2 +- .../inject/fabric/FabricInjector.java | 2 +- .../listener/FabricEventListener.java | 29 +++++++++++++++---- .../listener/FabricEventRegistration.java | 14 +++++++++ .../logger/Log4jFloodgateLogger.java | 2 +- .../floodgate/mixin/ServerNetworkIoMixin.java | 2 +- .../floodgate/module/FabricAddonModule.java | 2 +- .../floodgate/module/FabricCommandModule.java | 3 +- .../module/FabricListenerModule.java | 17 ++++++++++- .../module/FabricPlatformModule.java | 11 ++++++- .../pluginmessage/FabricSkinApplier.java | 2 +- .../floodgate/util/FabricCommandUtil.java | 2 +- .../floodgate/util/FabricUserAudience.java | 2 +- 17 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java diff --git a/build.gradle b/build.gradle index e8ce13f1..98e511a8 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ dependencies { // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. + // Base Floodgate include(implementation("org.geysermc.floodgate:common:2.0-SNAPSHOT")) // Cloud - commands @@ -35,6 +36,8 @@ dependencies { // Fabric Adventure platform // For translating from Adventure to Minecraft include(modImplementation('net.kyori:adventure-platform-fabric:4.0.0-SNAPSHOT') { + // Thanks to zml for this fix + // The package modifies Brigadier which causes a LinkageError at runtime if included exclude group: 'ca.stellardrift', module: "colonel" }) diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 76230d79..7d12baa2 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -1,9 +1,6 @@ package org.geysermc.floodgate; import org.geysermc.floodgate.inject.fabric.FabricInjector; -import org.geysermc.floodgate.module.FabricAddonModule; -import org.geysermc.floodgate.module.FabricCommandModule; -import org.geysermc.floodgate.module.FabricPlatformModule; import com.google.inject.Guice; import com.google.inject.Injector; import net.fabricmc.api.ModInitializer; @@ -12,6 +9,10 @@ import net.fabricmc.loader.api.FabricLoader; import net.kyori.adventure.platform.fabric.FabricServerAudiences; import net.minecraft.server.MinecraftServer; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.module.FabricAddonModule; +import org.geysermc.floodgate.module.FabricCommandModule; +import org.geysermc.floodgate.module.FabricListenerModule; +import org.geysermc.floodgate.module.FabricPlatformModule; import org.geysermc.floodgate.module.ServerCommonModule; public class FabricMod implements ModInitializer { @@ -35,7 +36,8 @@ public class FabricMod implements ModInitializer { injector.getInstance(FabricPlatform.class) .enable( new FabricAddonModule(), - new FabricCommandModule() + new FabricCommandModule(), + new FabricListenerModule() ); long endCtm = System.currentTimeMillis(); diff --git a/src/main/java/org/geysermc/floodgate/FabricPlatform.java b/src/main/java/org/geysermc/floodgate/FabricPlatform.java index cb8165c9..3d29c2b0 100644 --- a/src/main/java/org/geysermc/floodgate/FabricPlatform.java +++ b/src/main/java/org/geysermc/floodgate/FabricPlatform.java @@ -7,7 +7,7 @@ import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.logger.FloodgateLogger; -public class FabricPlatform extends FloodgatePlatform { +public final class FabricPlatform extends FloodgatePlatform { @Inject public FabricPlatform(FloodgateApi api, PlatformInjector platformInjector, FloodgateLogger logger, Injector guice) { super(api, platformInjector, logger, guice); diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java index 86a96d96..9a40c865 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -12,7 +12,7 @@ import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.util.Utils; -public class FabricDataAddon implements InjectorAddon { +public final class FabricDataAddon implements InjectorAddon { @Inject private FloodgateHandshakeHandler handshakeHandler; @Inject private FloodgateConfig config; @Inject private SimpleFloodgateApi api; diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 3ae717da..128b16d9 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -20,7 +20,7 @@ import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.Constants; @RequiredArgsConstructor -public class FabricDataHandler extends ChannelInboundHandlerAdapter { +public final class FabricDataHandler extends ChannelInboundHandlerAdapter { private final FloodgateConfig config; private final FloodgateHandshakeHandler handshakeHandler; private final FloodgateLogger logger; diff --git a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java index 783d585e..7eaf9867 100644 --- a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.inject.CommonPlatformInjector; @RequiredArgsConstructor -public class FabricInjector extends CommonPlatformInjector { +public final class FabricInjector extends CommonPlatformInjector { private static FabricInjector instance; @Getter private final boolean injected = true; diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java index ecedc785..d0e1c832 100644 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java +++ b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java @@ -1,12 +1,29 @@ package org.geysermc.floodgate.listener; -public class FabricEventListener { +import com.google.inject.Inject; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.player.FloodgatePlayerImpl; +import org.geysermc.floodgate.util.LanguageManager; - public void onPlayerJoin() { +public final class FabricEventListener { + @Inject private FloodgateApi api; + @Inject private FloodgateLogger logger; + @Inject private LanguageManager languageManager; - } - - public void onPlayerQuit() { - //todo + public void onPlayerJoin(ServerPlayNetworkHandler networkHandler, PacketSender packetSender, MinecraftServer server) { + FloodgatePlayer player = api.getPlayer(networkHandler.player.getUuid()); + if (player != null) { + player.as(FloodgatePlayerImpl.class).setLogin(false); + logger.translatedInfo( + "floodgate.ingame.login_name", + player.getCorrectUsername(), player.getCorrectUniqueId() + ); + languageManager.loadLocale(player.getLanguageCode()); + } } } diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java new file mode 100644 index 00000000..43daf0fa --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.listener; + +import com.google.inject.Inject; +import lombok.RequiredArgsConstructor; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; + +@RequiredArgsConstructor(onConstructor = @__(@Inject)) +public final class FabricEventRegistration implements ListenerRegistration { + @Override + public void register(FabricEventListener listener) { + ServerPlayConnectionEvents.JOIN.register(listener::onPlayerJoin); + } +} diff --git a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java b/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java index 232d93a3..a4cb38ff 100644 --- a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java +++ b/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java @@ -10,7 +10,7 @@ import org.geysermc.floodgate.util.LanguageManager; import static org.geysermc.floodgate.util.MessageFormatter.format; @RequiredArgsConstructor -public class Log4jFloodgateLogger implements FloodgateLogger { +public final class Log4jFloodgateLogger implements FloodgateLogger { private final Logger logger; private final LanguageManager languageManager; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java index 50dc5a05..8e966d54 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java @@ -14,7 +14,7 @@ import java.net.InetAddress; import java.util.List; @Mixin(ServerNetworkIo.class) -public class ServerNetworkIoMixin { +public abstract class ServerNetworkIoMixin { @Shadow @Final private List channels; diff --git a/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java b/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java index 9e7c8410..2dd731ce 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java @@ -9,7 +9,7 @@ import org.geysermc.floodgate.addon.DebugAddon; import org.geysermc.floodgate.api.inject.InjectorAddon; import org.geysermc.floodgate.register.AddonRegister; -public class FabricAddonModule extends AbstractModule { +public final class FabricAddonModule extends AbstractModule { @Override protected void configure() { bind(AddonRegister.class).asEagerSingleton(); diff --git a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java index 0b74ab2b..f30a3b30 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java @@ -8,12 +8,11 @@ import com.google.inject.Provides; import com.google.inject.Singleton; import lombok.SneakyThrows; import net.minecraft.server.command.ServerCommandSource; -import org.geysermc.floodgate.module.CommandModule; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.UserAudience; -public class FabricCommandModule extends CommandModule { +public final class FabricCommandModule extends CommandModule { @Provides @Singleton @SneakyThrows diff --git a/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java b/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java index f00592bc..ec67abcf 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java @@ -1,6 +1,21 @@ package org.geysermc.floodgate.module; import com.google.inject.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.listener.FabricEventListener; +import org.geysermc.floodgate.register.ListenerRegister; -public class FabricListenerModule extends AbstractModule { +public final class FabricListenerModule extends AbstractModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public FabricEventListener fabricEventListener() { + return new FabricEventListener(); + } } diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index dd696b97..41982bc3 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -1,7 +1,10 @@ package org.geysermc.floodgate.module; import org.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.floodgate.listener.FabricEventListener; +import org.geysermc.floodgate.listener.FabricEventRegistration; import org.geysermc.floodgate.logger.Log4jFloodgateLogger; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; import org.geysermc.floodgate.util.FabricCommandUtil; import com.google.inject.AbstractModule; @@ -20,7 +23,7 @@ import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.util.LanguageManager; @RequiredArgsConstructor -public class FabricPlatformModule extends AbstractModule { +public final class FabricPlatformModule extends AbstractModule { private final MinecraftServer server; private final FabricServerAudiences adventure; @@ -51,6 +54,12 @@ public class FabricPlatformModule extends AbstractModule { return new FabricCommandUtil(adventure, api, logger, languageManager, server); } + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new FabricEventRegistration(); + } + /* DebugAddon / PlatformInjector */ diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 3ce8ce89..789b037b 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -13,7 +13,7 @@ import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.skin.SkinApplier; @RequiredArgsConstructor -public class FabricSkinApplier implements SkinApplier { +public final class FabricSkinApplier implements SkinApplier { private final MinecraftServer server; @Override diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 4dd57eb0..21e585d4 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -24,7 +24,7 @@ import org.geysermc.floodgate.player.UserAudienceArgument; import java.util.*; @RequiredArgsConstructor -public class FabricCommandUtil implements CommandUtil { +public final class FabricCommandUtil implements CommandUtil { @Getter private final FabricServerAudiences adventure; @Getter private final FloodgateApi api; @Getter private final FloodgateLogger logger; diff --git a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java index ad833648..548828b9 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java @@ -15,7 +15,7 @@ import org.geysermc.floodgate.player.UserAudience; import java.util.UUID; @RequiredArgsConstructor -public class FabricUserAudience implements UserAudience, ForwardingAudience.Single { +public final class FabricUserAudience implements UserAudience, ForwardingAudience.Single { private final UUID uuid; private final String locale; private final ServerCommandSource source; From 71395ed65923c4201e5f09fd833e29a2efbccf5c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 29 Mar 2021 13:09:57 -0400 Subject: [PATCH 05/87] Commands now work --- .../org/geysermc/floodgate/FabricMod.java | 33 ++++++------ .../module/FabricPlatformModule.java | 22 ++------ .../pluginmessage/FabricSkinApplier.java | 13 +++-- .../floodgate/util/FabricCommandUtil.java | 51 +++++++++++++------ .../floodgate/util/FabricUserAudience.java | 46 ++++++++++++++--- 5 files changed, 103 insertions(+), 62 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 7d12baa2..5085f596 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -7,36 +7,37 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; import net.kyori.adventure.platform.fabric.FabricServerAudiences; -import net.minecraft.server.MinecraftServer; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.module.FabricAddonModule; -import org.geysermc.floodgate.module.FabricCommandModule; -import org.geysermc.floodgate.module.FabricListenerModule; -import org.geysermc.floodgate.module.FabricPlatformModule; -import org.geysermc.floodgate.module.ServerCommonModule; +import org.geysermc.floodgate.module.*; +import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; +import org.geysermc.floodgate.util.FabricCommandUtil; public class FabricMod implements ModInitializer { - private MinecraftServer server; - @Override public void onInitialize() { FabricInjector.setInstance(new FabricInjector()); + Injector injector = Guice.createInjector( + new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate")), + new FabricPlatformModule() + ); + + FabricPlatform platform = injector.getInstance(FabricPlatform.class); + + platform.enable(new FabricCommandModule()); + ServerLifecycleEvents.SERVER_STARTED.register((server) -> { long ctm = System.currentTimeMillis(); - this.server = server; FabricServerAudiences adventure = FabricServerAudiences.of(server); - Injector injector = Guice.createInjector( - new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate")), - new FabricPlatformModule(this.server, adventure) - ); + // Stupid hack, see the class for more information + // This can probably be Guice-i-fied but that is beyond me + FabricCommandUtil.setLaterVariables(server, adventure); + FabricSkinApplier.setServer(server); - injector.getInstance(FabricPlatform.class) - .enable( + platform.enable( new FabricAddonModule(), - new FabricCommandModule(), new FabricListenerModule() ); diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index 41982bc3..87a7eea3 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -12,8 +12,6 @@ import com.google.inject.Provides; import com.google.inject.Singleton; import com.google.inject.name.Named; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.platform.fabric.FabricServerAudiences; -import net.minecraft.server.MinecraftServer; import org.apache.logging.log4j.LogManager; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; @@ -24,20 +22,6 @@ import org.geysermc.floodgate.util.LanguageManager; @RequiredArgsConstructor public final class FabricPlatformModule extends AbstractModule { - private final MinecraftServer server; - private final FabricServerAudiences adventure; - - @Provides - @Singleton - public MinecraftServer server() { - return server; - } - - @Provides - @Singleton - public FabricServerAudiences adventure() { - return adventure; - } @Provides @Singleton @@ -51,7 +35,7 @@ public final class FabricPlatformModule extends AbstractModule { FloodgateApi api, FloodgateLogger logger, LanguageManager languageManager) { - return new FabricCommandUtil(adventure, api, logger, languageManager, server); + return new FabricCommandUtil(api, logger, languageManager); } @Provides @@ -96,7 +80,7 @@ public final class FabricPlatformModule extends AbstractModule { @Provides @Singleton - public SkinApplier skinApplier(MinecraftServer server) { - return new FabricSkinApplier(server); + public SkinApplier skinApplier() { + return new FabricSkinApplier(); } } diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 789b037b..18381624 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -14,12 +14,13 @@ import org.geysermc.floodgate.skin.SkinApplier; @RequiredArgsConstructor public final class FabricSkinApplier implements SkinApplier { - private final MinecraftServer server; + // See FabricCommandUtil + private static MinecraftServer SERVER; @Override public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) { - this.server.execute(() -> { - ServerPlayerEntity bedrockPlayer = this.server.getPlayerManager().getPlayer(floodgatePlayer.getCorrectUniqueId()); + SERVER.execute(() -> { + ServerPlayerEntity bedrockPlayer = SERVER.getPlayerManager().getPlayer(floodgatePlayer.getCorrectUniqueId()); if (bedrockPlayer == null) { // Disconnected probably? return; @@ -36,7 +37,7 @@ public final class FabricSkinApplier implements SkinApplier { properties.put("textures", property); // Skin is applied - now it's time to refresh the player for everyone. Oof. - for (ServerPlayerEntity otherPlayer : this.server.getPlayerManager().getPlayerList()) { + for (ServerPlayerEntity otherPlayer : SERVER.getPlayerManager().getPlayerList()) { if (otherPlayer == bedrockPlayer) { continue; } @@ -55,4 +56,8 @@ public final class FabricSkinApplier implements SkinApplier { } }); } + + public static void setServer(MinecraftServer server) { + SERVER = server; + } } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 21e585d4..0fafebc8 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -25,11 +25,15 @@ import java.util.*; @RequiredArgsConstructor public final class FabricCommandUtil implements CommandUtil { - @Getter private final FabricServerAudiences adventure; + // Static because commands *need* to be initialized before the server is available + // Otherwise it would be a class variable + private static MinecraftServer SERVER; + // This one also requires the server so it's bundled in + private static FabricServerAudiences ADVENTURE; + @Getter private final FloodgateApi api; @Getter private final FloodgateLogger logger; @Getter private final LanguageManager manager; - @Getter private final MinecraftServer server; @Override public @NonNull UserAudience getAudience(@NonNull Object source) { @@ -46,8 +50,8 @@ public final class FabricCommandUtil implements CommandUtil { } @Override - public @Nullable UserAudience getAudienceByUuid(@NonNull UUID uuid) { - ServerPlayerEntity player = this.server.getPlayerManager().getPlayer(uuid); + public UserAudience getAudienceByUuid(@NonNull UUID uuid) { + ServerPlayerEntity player = SERVER.getPlayerManager().getPlayer(uuid); if (player != null) { return getAudience0(player); } @@ -55,8 +59,8 @@ public final class FabricCommandUtil implements CommandUtil { } @Override - public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { - ServerPlayerEntity player = this.server.getPlayerManager().getPlayer(username); + public UserAudience getAudienceByUsername(@NonNull String username) { + ServerPlayerEntity player = SERVER.getPlayerManager().getPlayer(username); if (player != null) { return getAudience0(player); } @@ -66,10 +70,11 @@ public final class FabricCommandUtil implements CommandUtil { private FabricUserAudience getAudience0(ServerPlayerEntity player) { // Marked as internal??? Should probably find a better way to get this. Locale locale = ((ConnectionAccess) player.networkHandler.getConnection()).getChannel().attr(FriendlyByteBufBridge.CHANNEL_LOCALE).get(); - return new FabricUserAudience( + return new FabricUserAudience.NamedFabricUserAudience( + player.getName().asString(), player.getUuid(), locale != null ? locale.getLanguage().toLowerCase(Locale.ROOT) + "_" + locale.getCountry().toUpperCase(Locale.ROOT) : - manager.getDefaultLocale(), player.getCommandSource(), this); + manager.getDefaultLocale(), player.getCommandSource(), this, true); } @Override @@ -80,16 +85,16 @@ public final class FabricCommandUtil implements CommandUtil { @Override public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { UUID uuid = null; - GameProfile profile = this.server.getUserCache().findByName(username); + GameProfile profile = SERVER.getUserCache().findByName(username); if (profile != null) { uuid = profile.getId(); } - return new FabricUserAudience(uuid, username, null, this); + return new FabricUserAudience.NamedFabricUserAudience(username, uuid, username, null, this, false); } @Override public @NonNull Collection getOnlineUsernames(UserAudienceArgument.@NonNull PlayerType limitTo) { - List players = this.server.getPlayerManager().getPlayerList(); + List players = SERVER.getPlayerManager().getPlayerList(); Collection usernames = new ArrayList<>(); switch (limitTo) { @@ -120,7 +125,14 @@ public final class FabricCommandUtil implements CommandUtil { @Override public void sendMessage(Object player, String locale, CommandMessage message, Object... args) { - getPlayer(player).sendMessage(translateAndTransform(locale, message, args), false); + ServerCommandSource commandSource = (ServerCommandSource) player; + if (commandSource.getEntity() instanceof ServerPlayerEntity) { + SERVER.execute(() -> ((ServerPlayerEntity) commandSource.getEntity()) + .sendMessage(translateAndTransform(locale, message, args), false)); + } else { + // Console? + logger.info(message.translateMessage(manager, locale, args)); + } } @Override @@ -131,14 +143,14 @@ public final class FabricCommandUtil implements CommandUtil { @Override public boolean whitelistPlayer(String xuid, String username) { GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username); - this.server.getPlayerManager().getWhitelist().add(new WhitelistEntry(profile)); + SERVER.getPlayerManager().getWhitelist().add(new WhitelistEntry(profile)); return true; } @Override public boolean removePlayerFromWhitelist(String xuid, String username) { GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username); - this.server.getPlayerManager().getWhitelist().remove(profile); + SERVER.getPlayerManager().getWhitelist().remove(profile); return true; } @@ -147,7 +159,7 @@ public final class FabricCommandUtil implements CommandUtil { ServerCommandSource source = (ServerCommandSource) instance; return source.getPlayer(); } catch (ClassCastException | CommandSyntaxException exception) { - logger.error("Failed to cast {} to Player", instance.getClass().getName()); + logger.error("Failed to cast {} as a player", instance.getClass().getName()); throw new RuntimeException(); } } @@ -155,4 +167,13 @@ public final class FabricCommandUtil implements CommandUtil { public Text translateAndTransform(String locale, CommandMessage message, Object... args) { return Text.of(message.translateMessage(manager, locale, args)); } + + public FabricServerAudiences getAdventure() { + return ADVENTURE; + } + + public static void setLaterVariables(MinecraftServer server, FabricServerAudiences adventure) { + SERVER = server; + ADVENTURE = adventure; + } } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java index 548828b9..171c242b 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java @@ -1,6 +1,7 @@ package org.geysermc.floodgate.util; import lombok.RequiredArgsConstructor; +import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.audience.MessageType; @@ -15,7 +16,7 @@ import org.geysermc.floodgate.player.UserAudience; import java.util.UUID; @RequiredArgsConstructor -public final class FabricUserAudience implements UserAudience, ForwardingAudience.Single { +public class FabricUserAudience implements UserAudience, ForwardingAudience.Single { private final UUID uuid; private final String locale; private final ServerCommandSource source; @@ -33,6 +34,10 @@ public final class FabricUserAudience implements UserAudience, ForwardingAudienc @Override public @NonNull String username() { + if (source == null) { + return ""; + } + return source.getName(); } @@ -48,17 +53,12 @@ public final class FabricUserAudience implements UserAudience, ForwardingAudienc @Override public boolean hasPermission(@NonNull String permission) { - //TODO - return true; + return Permissions.check(source, permission); } @Override public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { - if (this.source.getEntity() instanceof ServerPlayerEntity) { - ((ServerPlayerEntity) this.source.getEntity()).sendMessage( - commandUtil.getAdventure().toNative(message), false - ); - } + commandUtil.getAdventure().audience(this.source).sendMessage(message); } @Override @@ -83,4 +83,34 @@ public final class FabricUserAudience implements UserAudience, ForwardingAudienc ); } } + + /** + * Used whenever a name has been explicitly defined for us. Most helpful in offline players. + */ + public static final class NamedFabricUserAudience extends FabricUserAudience implements PlayerAudience { + private final String name; + private final boolean online; + + public NamedFabricUserAudience( + String name, + UUID uuid, + String locale, + ServerCommandSource source, + FabricCommandUtil commandUtil, + boolean online) { + super(uuid, locale, source, commandUtil); + this.name = name; + this.online = online; + } + + @Override + public @NonNull String username() { + return name; + } + + @Override + public boolean online() { + return online; + } + } } From 01134d2f6cfa09302faff6cf464b9e22ce7f9f6a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 29 Mar 2021 14:07:05 -0400 Subject: [PATCH 06/87] More proper player reloading --- .../pluginmessage/FabricSkinApplier.java | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 18381624..2a2285be 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -1,17 +1,30 @@ package org.geysermc.floodgate.pluginmessage; +import com.google.common.collect.Lists; import com.google.gson.JsonObject; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; +import com.mojang.datafixers.util.Pair; import lombok.RequiredArgsConstructor; -import net.minecraft.network.packet.s2c.play.EntitiesDestroyS2CPacket; -import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; -import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.attribute.EntityAttributeInstance; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.network.Packet; +import net.minecraft.network.packet.s2c.play.*; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.util.math.MathHelper; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.skin.SkinApplier; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + @RequiredArgsConstructor public final class FabricSkinApplier implements SkinApplier { // See FabricCommandUtil @@ -51,7 +64,45 @@ public final class FabricSkinApplier implements SkinApplier { otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.ADD_PLAYER, bedrockPlayer)); if (loadedInWorld) { - otherPlayer.networkHandler.sendPacket(new PlayerSpawnS2CPacket(bedrockPlayer)); + // Copied from EntityTrackerEntry + Packet spawnPacket = bedrockPlayer.createSpawnPacket(); + otherPlayer.networkHandler.sendPacket(spawnPacket); + if (!bedrockPlayer.getDataTracker().isEmpty()) { + otherPlayer.networkHandler.sendPacket(new EntityTrackerUpdateS2CPacket(bedrockPlayer.getEntityId(), bedrockPlayer.getDataTracker(), true)); + } + + Collection collection = bedrockPlayer.getAttributes().getAttributesToSend(); + if (!collection.isEmpty()) { + otherPlayer.networkHandler.sendPacket(new EntityAttributesS2CPacket(bedrockPlayer.getEntityId(), collection)); + } + + otherPlayer.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(bedrockPlayer.getEntityId(), bedrockPlayer.getVelocity())); + + List> equipmentList = Lists.newArrayList(); + EquipmentSlot[] slots = EquipmentSlot.values(); + + for (EquipmentSlot equipmentSlot : slots) { + ItemStack itemStack = bedrockPlayer.getEquippedStack(equipmentSlot); + if (!itemStack.isEmpty()) { + equipmentList.add(Pair.of(equipmentSlot, itemStack.copy())); + } + } + + if (!equipmentList.isEmpty()) { + otherPlayer.networkHandler.sendPacket(new EntityEquipmentUpdateS2CPacket(bedrockPlayer.getEntityId(), equipmentList)); + } + + for (StatusEffectInstance statusEffectInstance : bedrockPlayer.getStatusEffects()) { + otherPlayer.networkHandler.sendPacket(new EntityStatusEffectS2CPacket(bedrockPlayer.getEntityId(), statusEffectInstance)); + } + + if (!bedrockPlayer.getPassengerList().isEmpty()) { + otherPlayer.networkHandler.sendPacket(new EntityPassengersSetS2CPacket(bedrockPlayer)); + } + + if (bedrockPlayer.hasVehicle()) { + otherPlayer.networkHandler.sendPacket(new EntityPassengersSetS2CPacket(bedrockPlayer.getVehicle())); + } } } }); From c0ef59e8b0163e89ec9215b14fa47d1712634201 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 29 Mar 2021 19:56:33 -0400 Subject: [PATCH 07/87] Missed com -> org change --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4026a377..eb5ee751 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ yarn_mappings=1.16.5+build.6 loader_version=0.11.3 # Mod Properties mod_version=2.0-SNAPSHOT -maven_group=com.geysermc.floodgate +maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html From 7fe0d955bc1ca0982780cf76dacd740d718be40f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:11:10 -0400 Subject: [PATCH 08/87] It works. --- build.gradle | 53 ++++++++++++++++++++++++++++++++++++++--------- gradle.properties | 3 +++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 98e511a8..2b46914e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,9 @@ +import net.fabricmc.loom.task.RemapJarTask + plugins { + id 'com.github.johnrengelman.shadow' version '5.2.0' id 'fabric-loom' version '0.7-SNAPSHOT' + id 'java' id 'maven-publish' } @@ -27,23 +31,23 @@ dependencies { // You may need to force-disable transitiveness on them. // Base Floodgate - include(implementation("org.geysermc.floodgate:common:2.0-SNAPSHOT")) + implementation("org.geysermc.floodgate:common:${project.mod_version}") + shadow("org.geysermc.floodgate:common:${project.mod_version}") - // Cloud - commands - // Snapshot in order to get Fabric version - include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0-SNAPSHOT')) + include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0-SNAPSHOT') { + because "Commands library implementation for Fabric - only supported on the snapshot" + }) - // Fabric Adventure platform - // For translating from Adventure to Minecraft include(modImplementation('net.kyori:adventure-platform-fabric:4.0.0-SNAPSHOT') { + because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included exclude group: 'ca.stellardrift', module: "colonel" }) // Lombok - compileOnly 'org.projectlombok:lombok:1.18.16' - annotationProcessor 'org.projectlombok:lombok:1.18.16' + compileOnly "org.projectlombok:lombok:${project.lombok_version}" + annotationProcessor "org.projectlombok:lombok:${project.lombok_version}" } repositories { @@ -95,8 +99,37 @@ task sourcesJar(type: Jar, dependsOn: classes) { from sourceSets.main.allSource } -jar { - from "LICENSE" +shadowJar { +// dependencies { +// exclude('net.fabricmc:.*') +// //include(dependency('org.geysermc.floodgate:.*')) +// //include(dependency("org.geysermc.cumulus:.*")) +// //include(dependency('org.geysermc:.*')) +// exclude '/mappings/*' +// } + configurations = [project.configurations.shadow] + //dependencies { + //exclude(dependency('cloud.commandframework:cloud-fabric:.*')) + //exclude(dependency('net.kyori:adventure-platform-fabric:.*')) + //} + + //relocate 'net.kyori', 'org.geysermc.floodgate.relocations.kyori' + //relocate 'cloud.commandframework', 'org.geysermc.floodgate.relocations.cloud' +} + +task remappedShadowJar(type: RemapJarTask) { + dependsOn tasks.shadowJar + input = tasks.shadowJar.archivePath + addNestedDependencies = true + remapAccessWidener = true // Required for our access widener changes to go into effect and not crash on startup + archiveName = "Floodgate-Fabric.jar" +} + +tasks.assemble.dependsOn tasks.remappedShadowJar + +artifacts { + archives remappedShadowJar + shadow shadowJar } // configure the maven publication diff --git a/gradle.properties b/gradle.properties index eb5ee751..ce220694 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,3 +12,6 @@ archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html fabric_version=0.25.1+build.416-1.16 + +# Our stuff +lombok_version=1.18.16 From 0a668eb48563873c73998c3ee09ea9f3929f8d0e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:14:09 -0400 Subject: [PATCH 09/87] Add Jenkinsfile --- Jenkinsfile | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ build.gradle | 2 +- 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..ece59bde --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,103 @@ +pipeline { + agent any + tools { + gradle 'Gradle 6' + jdk 'Java 8' + } + + parameters { + booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD') + } + + options { + buildDiscarder(logRotator(artifactNumToKeepStr: '20')) + } + + stages { + stage ('Build') { + steps { + sh 'gradle clean build --refresh-dependencies' + } + post { + success { + archiveArtifacts artifacts: 'build/libs/*.jar', includes: 'build/libs/floodgate-fabric.jar', fingerprint: true + } + } + } + + stage ('Deploy') { + when { + branch "master" + } + + steps { + rtGradleDeployer( + id: "GRADLE_DEPLOYER", + serverId: "opencollab-artifactory", + releaseRepo: "maven-releases", + snapshotRepo: "maven-snapshots" + ) + rtGradleResolver( + id: "GRADLE_RESOLVER", + serverId: "opencollab-artifactory", + ) + rtGradleRun ( + usesPlugin: false, + tool: 'Gradle 6', + rootDir: "", + buildFile: 'build.gradle', + tasks: 'build artifactoryPublish', + deployerId: "GRADLE_DEPLOYER", + resolverId: "GRADLE_RESOLVER" + ) + rtPublishBuildInfo( + serverId: "opencollab-artifactory" + ) + } + } + } + + post { + always { + script { + def changeLogSets = currentBuild.changeSets + def message = "**Changes:**" + + if (changeLogSets.size() == 0) { + message += "\n*No changes.*" + } else { + def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") + def count = 0; + def extra = 0; + for (int i = 0; i < changeLogSets.size(); i++) { + def entries = changeLogSets[i].items + for (int j = 0; j < entries.length; j++) { + if (count <= 10) { + def entry = entries[j] + def commitId = entry.commitId.substring(0, 6) + message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" + count++ + } else { + extra++; + } + } + } + + if (extra != 0) { + message += "\n - ${extra} more commits" + } + } + + env.changes = message + } + deleteDir() + script { + if(!params.SKIP_DISCORD) { + withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Floodgate-Fabric)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + } + } + } + } + } +} diff --git a/build.gradle b/build.gradle index 2b46914e..a8ef3a78 100644 --- a/build.gradle +++ b/build.gradle @@ -122,7 +122,7 @@ task remappedShadowJar(type: RemapJarTask) { input = tasks.shadowJar.archivePath addNestedDependencies = true remapAccessWidener = true // Required for our access widener changes to go into effect and not crash on startup - archiveName = "Floodgate-Fabric.jar" + archiveName = "floodgate-fabric.jar" } tasks.assemble.dependsOn tasks.remappedShadowJar From 676ac621d95f1644d89de04d5548bcff1141e0bd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:24:17 -0400 Subject: [PATCH 10/87] It's tradition at this point for Jenkins to fail at least once --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index ece59bde..f88a1a15 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,7 +20,7 @@ pipeline { } post { success { - archiveArtifacts artifacts: 'build/libs/*.jar', includes: 'build/libs/floodgate-fabric.jar', fingerprint: true + archiveArtifacts artifacts: 'build/libs/floodgate-fabric.jar', fingerprint: true } } } From 15a1f36b3e2c59909522a14004ea740efc4658e2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 20 Apr 2021 14:09:42 -0400 Subject: [PATCH 11/87] Relocate Fastutil --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index a8ef3a78..be6abace 100644 --- a/build.gradle +++ b/build.gradle @@ -115,6 +115,7 @@ shadowJar { //relocate 'net.kyori', 'org.geysermc.floodgate.relocations.kyori' //relocate 'cloud.commandframework', 'org.geysermc.floodgate.relocations.cloud' + relocate 'it.unimi.dsi.fastutil', 'org.geysermc.floodgate.relocations.fastutil' } task remappedShadowJar(type: RemapJarTask) { From f0b292cdc8ab5f975a2f3d5032fa7012bc152c1f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 25 Apr 2021 10:57:15 -0400 Subject: [PATCH 12/87] Create README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..e01af311 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Floodgate-Fabric +Fabric port of the hybrid mode plugin to allow for connections from Geyser to join online mode servers. + +This is a Floodgate 2.0 port; you need to download the Floodgate 2.0 equivalent of your Geyser platform in order to use it. + +Download: https://ci.opencollab.dev/job/GeyserMC/job/Floodgate-Fabric/job/master/ From 84ddb0e59e45e0c449d07844513bbeeb4367a307 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 7 May 2021 15:02:58 -0400 Subject: [PATCH 13/87] Null check player channel --- .../org/geysermc/floodgate/util/FabricCommandUtil.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 0fafebc8..a1d2ab79 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -2,6 +2,7 @@ package org.geysermc.floodgate.util; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.netty.channel.Channel; import lombok.Getter; import lombok.RequiredArgsConstructor; import net.kyori.adventure.platform.fabric.FabricServerAudiences; @@ -13,7 +14,6 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.platform.command.CommandMessage; @@ -69,7 +69,13 @@ public final class FabricCommandUtil implements CommandUtil { private FabricUserAudience getAudience0(ServerPlayerEntity player) { // Marked as internal??? Should probably find a better way to get this. - Locale locale = ((ConnectionAccess) player.networkHandler.getConnection()).getChannel().attr(FriendlyByteBufBridge.CHANNEL_LOCALE).get(); + Locale locale; + Channel channel = ((ConnectionAccess) player.networkHandler.getConnection()).getChannel(); + if (channel != null) { + locale = channel.attr(FriendlyByteBufBridge.CHANNEL_LOCALE).get(); + } else { + locale = null; + } return new FabricUserAudience.NamedFabricUserAudience( player.getName().asString(), player.getUuid(), locale != null ? From 2720e0ca67fab2763979423c54418e10fb9de418 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 16 May 2021 22:44:25 -0400 Subject: [PATCH 14/87] Text.of doesn't exist on the server? --- .../java/org/geysermc/floodgate/util/FabricCommandUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index a1d2ab79..7ddddb0a 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -12,6 +12,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.WhitelistEntry; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.FloodgateApi; @@ -171,7 +172,7 @@ public final class FabricCommandUtil implements CommandUtil { } public Text translateAndTransform(String locale, CommandMessage message, Object... args) { - return Text.of(message.translateMessage(manager, locale, args)); + return new LiteralText(message.translateMessage(manager, locale, args)); } public FabricServerAudiences getAdventure() { From a0ceafe1d1e538aa0229bcaa3f6c5e56d4502574 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 10 Jun 2021 10:12:13 -0400 Subject: [PATCH 15/87] Update to 1.17 --- Jenkinsfile | 6 +-- build.gradle | 17 +++------ gradle.properties | 9 ++--- gradle/wrapper/gradle-wrapper.properties | 2 +- .../addon/data/FabricDataHandler.java | 8 ++-- .../mixin/HandshakeC2SPacketMixin.java | 22 ----------- .../HandshakeS2CPacketAddressGetter.java | 8 ---- .../pluginmessage/FabricSkinApplier.java | 19 ++++------ .../floodgate/util/FabricCommandUtil.java | 38 +++++++++++++++++-- .../floodgate/util/FabricUserAudience.java | 6 +-- src/main/resources/fabric.mod.json | 2 +- src/main/resources/floodgate.mixins.json | 3 +- 12 files changed, 64 insertions(+), 76 deletions(-) delete mode 100644 src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java delete mode 100644 src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java diff --git a/Jenkinsfile b/Jenkinsfile index f88a1a15..d8dcc0e1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,8 +1,8 @@ pipeline { agent any tools { - gradle 'Gradle 6' - jdk 'Java 8' + gradle 'Gradle 7' + jdk 'Java 16' } parameters { @@ -43,7 +43,7 @@ pipeline { ) rtGradleRun ( usesPlugin: false, - tool: 'Gradle 6', + tool: 'Gradle 7', rootDir: "", buildFile: 'build.gradle', tasks: 'build artifactoryPublish', diff --git a/build.gradle b/build.gradle index be6abace..78d1562d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ import net.fabricmc.loom.task.RemapJarTask plugins { - id 'com.github.johnrengelman.shadow' version '5.2.0' - id 'fabric-loom' version '0.7-SNAPSHOT' + id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'fabric-loom' version '0.8-SNAPSHOT' id 'java' id 'maven-publish' } -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +sourceCompatibility = JavaVersion.VERSION_16 +targetCompatibility = JavaVersion.VERSION_16 archivesBaseName = project.archives_base_name version = project.mod_version @@ -38,7 +38,7 @@ dependencies { because "Commands library implementation for Fabric - only supported on the snapshot" }) - include(modImplementation('net.kyori:adventure-platform-fabric:4.0.0-SNAPSHOT') { + include(modImplementation('net.kyori:adventure-platform-fabric:4.1.0-SNAPSHOT') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included @@ -74,14 +74,9 @@ repositories { processResources { inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" + filesMatching("fabric.mod.json") { expand "version": project.version } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } } // ensure that the encoding is set to UTF-8, no matter what the system default is diff --git a/gradle.properties b/gradle.properties index ce220694..52a12f79 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.16.5 -yarn_mappings=1.16.5+build.6 +minecraft_version=1.17 +yarn_mappings=1.17+build.6 loader_version=0.11.3 # Mod Properties mod_version=2.0-SNAPSHOT @@ -11,7 +11,6 @@ maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.25.1+build.416-1.16 - +fabric_version=0.34.10+1.17 # Our stuff -lombok_version=1.18.16 +lombok_version=1.18.20 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bb8b2fc2..0f80bbf5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 128b16d9..3fd76d80 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -1,6 +1,5 @@ package org.geysermc.floodgate.addon.data; -import org.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; @@ -42,11 +41,12 @@ public final class FabricDataHandler extends ChannelInboundHandlerAdapter { if (isHandshake) { networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); - String handshakeValue = ((HandshakeS2CPacketAddressGetter) packet).getAddress(); - FloodgateHandshakeHandler.HandshakeResult result = handshakeHandler.handle(ctx.channel(), handshakeValue); + HandshakeC2SPacket handshakePacket = (HandshakeC2SPacket) packet; + FloodgateHandshakeHandler.HandshakeResult result = handshakeHandler.handle(ctx.channel(), handshakePacket.getAddress()); HandshakeData handshakeData = result.getHandshakeData(); - ((HandshakeS2CPacketAddressGetter) packet).setAddress(handshakeData.getHostname()); + packet = new HandshakeC2SPacket(handshakeData.getHostname(), handshakePacket.getPort(), handshakePacket.getIntendedState()); + ReferenceCountUtil.retain(packet); if (handshakeData.getDisconnectReason() != null) { ctx.close(); //todo disconnect with message diff --git a/src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java b/src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java deleted file mode 100644 index f930268d..00000000 --- a/src/main/java/org/geysermc/floodgate/mixin/HandshakeC2SPacketMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.geysermc.floodgate.mixin; - -import org.geysermc.floodgate.mixin_interface.HandshakeS2CPacketAddressGetter; -import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(HandshakeC2SPacket.class) -public abstract class HandshakeC2SPacketMixin implements HandshakeS2CPacketAddressGetter { - - @Shadow private String address; - - @Override - public String getAddress() { - return this.address; - } - - @Override - public void setAddress(String address) { - this.address = address; - } -} diff --git a/src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java b/src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java deleted file mode 100644 index 8f5fb568..00000000 --- a/src/main/java/org/geysermc/floodgate/mixin_interface/HandshakeS2CPacketAddressGetter.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.geysermc.floodgate.mixin_interface; - -public interface HandshakeS2CPacketAddressGetter { - String getAddress(); - - //TODO packet fields will become final in 1.17 - void setAddress(String address); -} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 2a2285be..ad63321a 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -7,22 +7,17 @@ import com.mojang.authlib.properties.PropertyMap; import com.mojang.datafixers.util.Pair; import lombok.RequiredArgsConstructor; import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.LivingEntity; import net.minecraft.entity.attribute.EntityAttributeInstance; import net.minecraft.entity.effect.StatusEffectInstance; -import net.minecraft.entity.mob.MobEntity; import net.minecraft.item.ItemStack; import net.minecraft.network.Packet; import net.minecraft.network.packet.s2c.play.*; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerChunkManager; -import net.minecraft.util.math.MathHelper; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.skin.SkinApplier; import java.util.Collection; -import java.util.Iterator; import java.util.List; @RequiredArgsConstructor @@ -55,10 +50,10 @@ public final class FabricSkinApplier implements SkinApplier { continue; } - boolean loadedInWorld = otherPlayer.getEntityWorld().getEntityById(bedrockPlayer.getEntityId()) != null; + boolean loadedInWorld = otherPlayer.getEntityWorld().getEntityById(bedrockPlayer.getId()) != null; if (loadedInWorld) { // Player is loaded in this world - otherPlayer.networkHandler.sendPacket(new EntitiesDestroyS2CPacket(bedrockPlayer.getEntityId())); + otherPlayer.networkHandler.sendPacket(new EntityDestroyS2CPacket(bedrockPlayer.getId())); } otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.REMOVE_PLAYER, bedrockPlayer)); @@ -68,15 +63,15 @@ public final class FabricSkinApplier implements SkinApplier { Packet spawnPacket = bedrockPlayer.createSpawnPacket(); otherPlayer.networkHandler.sendPacket(spawnPacket); if (!bedrockPlayer.getDataTracker().isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityTrackerUpdateS2CPacket(bedrockPlayer.getEntityId(), bedrockPlayer.getDataTracker(), true)); + otherPlayer.networkHandler.sendPacket(new EntityTrackerUpdateS2CPacket(bedrockPlayer.getId(), bedrockPlayer.getDataTracker(), true)); } Collection collection = bedrockPlayer.getAttributes().getAttributesToSend(); if (!collection.isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityAttributesS2CPacket(bedrockPlayer.getEntityId(), collection)); + otherPlayer.networkHandler.sendPacket(new EntityAttributesS2CPacket(bedrockPlayer.getId(), collection)); } - otherPlayer.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(bedrockPlayer.getEntityId(), bedrockPlayer.getVelocity())); + otherPlayer.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(bedrockPlayer.getId(), bedrockPlayer.getVelocity())); List> equipmentList = Lists.newArrayList(); EquipmentSlot[] slots = EquipmentSlot.values(); @@ -89,11 +84,11 @@ public final class FabricSkinApplier implements SkinApplier { } if (!equipmentList.isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityEquipmentUpdateS2CPacket(bedrockPlayer.getEntityId(), equipmentList)); + otherPlayer.networkHandler.sendPacket(new EntityEquipmentUpdateS2CPacket(bedrockPlayer.getId(), equipmentList)); } for (StatusEffectInstance statusEffectInstance : bedrockPlayer.getStatusEffects()) { - otherPlayer.networkHandler.sendPacket(new EntityStatusEffectS2CPacket(bedrockPlayer.getEntityId(), statusEffectInstance)); + otherPlayer.networkHandler.sendPacket(new EntityStatusEffectS2CPacket(bedrockPlayer.getId(), statusEffectInstance)); } if (!bedrockPlayer.getPassengerList().isEmpty()) { diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 7ddddb0a..d75ca838 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -5,9 +5,11 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.netty.channel.Channel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.platform.fabric.FabricServerAudiences; import net.kyori.adventure.platform.fabric.impl.accessor.ConnectionAccess; import net.kyori.adventure.platform.fabric.impl.server.FriendlyByteBufBridge; +import net.minecraft.entity.Entity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.WhitelistEntry; import net.minecraft.server.command.ServerCommandSource; @@ -17,7 +19,7 @@ import net.minecraft.text.Text; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudienceArgument; @@ -131,7 +133,23 @@ public final class FabricCommandUtil implements CommandUtil { } @Override - public void sendMessage(Object player, String locale, CommandMessage message, Object... args) { + public boolean hasPermission(Object player, String permission) { + return Permissions.check((Entity) player, permission); + } + + @Override + public Collection getOnlinePlayersWithPermission(String permission) { + List players = new ArrayList<>(); + for (ServerPlayerEntity player : SERVER.getPlayerManager().getPlayerList()) { + if (hasPermission(player, permission)) { + players.add(player); + } + } + return players; + } + + @Override + public void sendMessage(Object player, String locale, TranslatableMessage message, Object... args) { ServerCommandSource commandSource = (ServerCommandSource) player; if (commandSource.getEntity() instanceof ServerPlayerEntity) { SERVER.execute(() -> ((ServerPlayerEntity) commandSource.getEntity()) @@ -143,7 +161,19 @@ public final class FabricCommandUtil implements CommandUtil { } @Override - public void kickPlayer(Object player, String locale, CommandMessage message, Object... args) { + public void sendMessage(Object target, String message) { + ServerCommandSource commandSource = (ServerCommandSource) target; + if (commandSource.getEntity() instanceof ServerPlayerEntity) { + SERVER.execute(() -> ((ServerPlayerEntity) commandSource.getEntity()) + .sendMessage(new LiteralText(message), false)); + } else { + // Console? + logger.info(message); + } + } + + @Override + public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { getPlayer(player).networkHandler.disconnect(translateAndTransform(locale, message, args)); } @@ -171,7 +201,7 @@ public final class FabricCommandUtil implements CommandUtil { } } - public Text translateAndTransform(String locale, CommandMessage message, Object... args) { + public Text translateAndTransform(String locale, TranslatableMessage message, Object... args) { return new LiteralText(message.translateMessage(manager, locale, args)); } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java index 171c242b..69298f4d 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java @@ -10,7 +10,7 @@ import net.kyori.adventure.text.Component; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import java.util.UUID; @@ -62,7 +62,7 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public void sendMessage(CommandMessage message, Object... args) { + public void sendMessage(TranslatableMessage message, Object... args) { commandUtil.sendMessage(this.source, this.locale, message, args); } @@ -76,7 +76,7 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public void disconnect(CommandMessage message, Object... args) { + public void disconnect(TranslatableMessage message, Object... args) { if (source.getEntity() instanceof ServerPlayerEntity) { ((ServerPlayerEntity) source.getEntity()).networkHandler.disconnect( commandUtil.translateAndTransform(this.locale, message, args) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index ba4832b4..a63c0401 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,6 +26,6 @@ "depends": { "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": "1.16.5" + "minecraft": "1.17.x" } } diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index 66847086..4fdb9935 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -2,9 +2,8 @@ "required": true, "minVersion": "0.8", "package": "org.geysermc.floodgate.mixin", - "compatibilityLevel": "JAVA_8", + "compatibilityLevel": "JAVA_16", "mixins": [ - "HandshakeC2SPacketMixin", "ServerLoginNetworkHandlerMixin", "ServerNetworkIoMixin" ], From c9fd77fa440349741a980ee7b21ed6ba95d2e1b9 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 5 Jul 2021 16:21:01 +0200 Subject: [PATCH 16/87] Made Floodgate-Fabric build again --- .../geysermc/floodgate/addon/data/FabricDataAddon.java | 5 ----- .../floodgate/pluginmessage/FabricSkinApplier.java | 10 +++------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java index 9a40c865..385403eb 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -34,11 +34,6 @@ public final class FabricDataAddon implements InjectorAddon { ); } - @Override - public void onLoginDone(Channel channel) { - onRemoveInject(channel); - } - @Override public void onChannelClosed(Channel channel) { FloodgatePlayer player = channel.attr(playerAttribute).get(); diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index ad63321a..549d9cb4 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -1,7 +1,6 @@ package org.geysermc.floodgate.pluginmessage; import com.google.common.collect.Lists; -import com.google.gson.JsonObject; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; import com.mojang.datafixers.util.Pair; @@ -16,6 +15,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.skin.SkinApplier; +import org.geysermc.floodgate.skin.SkinData; import java.util.Collection; import java.util.List; @@ -26,7 +26,7 @@ public final class FabricSkinApplier implements SkinApplier { private static MinecraftServer SERVER; @Override - public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) { + public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { SERVER.execute(() -> { ServerPlayerEntity bedrockPlayer = SERVER.getPlayerManager().getPlayer(floodgatePlayer.getCorrectUniqueId()); if (bedrockPlayer == null) { @@ -38,11 +38,7 @@ public final class FabricSkinApplier implements SkinApplier { PropertyMap properties = bedrockPlayer.getGameProfile().getProperties(); properties.removeAll("textures"); - Property property = new Property( - "textures", - skinResult.get("value").getAsString(), - skinResult.get("signature").getAsString()); - properties.put("textures", property); + properties.put("textures", new Property("textures", skinData.getValue(), skinData.getSignature())); // Skin is applied - now it's time to refresh the player for everyone. Oof. for (ServerPlayerEntity otherPlayer : SERVER.getPlayerManager().getPlayerList()) { From defa438b3998d75303f65f5a522d4a80303fc4ca Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 16 Jul 2021 11:53:23 -0400 Subject: [PATCH 17/87] Require 1.17.1 or greater --- gradle.properties | 8 ++++---- .../floodgate/pluginmessage/FabricSkinApplier.java | 2 +- .../org/geysermc/floodgate/util/FabricCommandUtil.java | 6 +++--- src/main/resources/fabric.mod.json | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index 52a12f79..531ef28e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,15 +2,15 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.17 -yarn_mappings=1.17+build.6 -loader_version=0.11.3 +minecraft_version=1.17.1 +yarn_mappings=1.17.1+build.14 +loader_version=0.11.6 # Mod Properties mod_version=2.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.34.10+1.17 +fabric_version=0.37.0+1.17 # Our stuff lombok_version=1.18.20 diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 549d9cb4..c5c07eb8 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -49,7 +49,7 @@ public final class FabricSkinApplier implements SkinApplier { boolean loadedInWorld = otherPlayer.getEntityWorld().getEntityById(bedrockPlayer.getId()) != null; if (loadedInWorld) { // Player is loaded in this world - otherPlayer.networkHandler.sendPacket(new EntityDestroyS2CPacket(bedrockPlayer.getId())); + otherPlayer.networkHandler.sendPacket(new EntitiesDestroyS2CPacket(bedrockPlayer.getId())); } otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.REMOVE_PLAYER, bedrockPlayer)); diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index d75ca838..ef001e9b 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -94,9 +94,9 @@ public final class FabricCommandUtil implements CommandUtil { @Override public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { UUID uuid = null; - GameProfile profile = SERVER.getUserCache().findByName(username); - if (profile != null) { - uuid = profile.getId(); + Optional profile = SERVER.getUserCache().findByName(username); + if (profile.isPresent()) { + uuid = profile.get().getId(); } return new FabricUserAudience.NamedFabricUserAudience(username, uuid, username, null, this, false); } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a63c0401..3cea4ae6 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,6 +26,6 @@ "depends": { "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": "1.17.x" + "minecraft": ">=1.17.1" } } From 8da762506b38c2d8753c652d5652f0a0c4edcef7 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 22 Jul 2021 23:04:36 -0400 Subject: [PATCH 18/87] relocate google common --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 78d1562d..29b6740f 100644 --- a/build.gradle +++ b/build.gradle @@ -111,6 +111,8 @@ shadowJar { //relocate 'net.kyori', 'org.geysermc.floodgate.relocations.kyori' //relocate 'cloud.commandframework', 'org.geysermc.floodgate.relocations.cloud' relocate 'it.unimi.dsi.fastutil', 'org.geysermc.floodgate.relocations.fastutil' + relocate 'com.google.common', 'org.geysermc.floodgate.relocations.google.common' + } task remappedShadowJar(type: RemapJarTask) { From 1bedce65389878c0e76ef4a0e10011723e77310e Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 23 Jul 2021 11:23:58 -0400 Subject: [PATCH 19/87] exclude gson and guava instead --- build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 29b6740f..736995ea 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,10 @@ dependencies { // Base Floodgate implementation("org.geysermc.floodgate:common:${project.mod_version}") - shadow("org.geysermc.floodgate:common:${project.mod_version}") + shadow("org.geysermc.floodgate:common:${project.mod_version}") { + exclude group: 'com.google.guava', module: "guava" + exclude group: 'com.google.code.gson', module: "gson" + } include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0-SNAPSHOT') { because "Commands library implementation for Fabric - only supported on the snapshot" @@ -111,7 +114,6 @@ shadowJar { //relocate 'net.kyori', 'org.geysermc.floodgate.relocations.kyori' //relocate 'cloud.commandframework', 'org.geysermc.floodgate.relocations.cloud' relocate 'it.unimi.dsi.fastutil', 'org.geysermc.floodgate.relocations.fastutil' - relocate 'com.google.common', 'org.geysermc.floodgate.relocations.google.common' } From 478202e9eacc10dedfaded03c603af8b0819952c Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 23 Jul 2021 12:29:19 -0400 Subject: [PATCH 20/87] whoops --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 736995ea..2945e98c 100644 --- a/build.gradle +++ b/build.gradle @@ -114,7 +114,6 @@ shadowJar { //relocate 'net.kyori', 'org.geysermc.floodgate.relocations.kyori' //relocate 'cloud.commandframework', 'org.geysermc.floodgate.relocations.cloud' relocate 'it.unimi.dsi.fastutil', 'org.geysermc.floodgate.relocations.fastutil' - } task remappedShadowJar(type: RemapJarTask) { From af3abbd6f00c3dd8b0824d60d53efb1f85d6e489 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:03:10 -0400 Subject: [PATCH 21/87] Update to latest Floodgate changes --- .../floodgate/addon/data/FabricDataAddon.java | 5 +- .../addon/data/FabricDataHandler.java | 128 ++++++++++++------ .../mixin/ClientConnectionMixin.java | 13 ++ .../mixin/ServerLoginNetworkHandlerMixin.java | 12 +- .../floodgate/util/FabricCommandUtil.java | 13 +- src/main/resources/floodgate.mixins.json | 1 + 6 files changed, 110 insertions(+), 62 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java index 385403eb..a59c3891 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -28,9 +28,12 @@ public final class FabricDataAddon implements InjectorAddon { @Override public void onInject(Channel channel, boolean toServer) { + PacketBlocker blocker = new PacketBlocker(); + channel.pipeline().addBefore("decoder", "floodgate_packet_blocker", blocker); + channel.pipeline().addBefore( packetHandlerName, "floodgate_data_handler", - new FabricDataHandler(config, handshakeHandler, logger) + new FabricDataHandler(config, handshakeHandler, blocker, logger) ); } diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 3fd76d80..3b064a25 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -1,5 +1,7 @@ package org.geysermc.floodgate.addon.data; +import com.google.common.collect.Queues; +import org.geysermc.floodgate.mixin.ClientConnectionMixin; import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; @@ -18,39 +20,59 @@ import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.Constants; +import java.net.InetSocketAddress; +import java.util.Queue; + @RequiredArgsConstructor public final class FabricDataHandler extends ChannelInboundHandlerAdapter { private final FloodgateConfig config; private final FloodgateHandshakeHandler handshakeHandler; + private final PacketBlocker blocker; private final FloodgateLogger logger; + + private final Queue packetQueue = Queues.newConcurrentLinkedQueue(); + private ClientConnection networkManager; private FloodgatePlayer player; - private boolean done; + + /** + * As a variable so we can change it without reflection + */ + HandshakeC2SPacket handshakePacket; + /** + * A boolean to compensate for the above + */ + private boolean packetsBlocked; @Override - public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { - ReferenceCountUtil.retain(packet); - if (done) { - super.channelRead(ctx, packet); + public void channelRead(ChannelHandlerContext ctx, Object packet) { + // prevent other packets from being handled while we handle the handshake packet + if (packetsBlocked) { + packetQueue.add(packet); return; } - boolean isHandshake = packet instanceof HandshakeC2SPacket; - boolean isLogin = packet instanceof LoginHelloC2SPacket; - try { - if (isHandshake) { - networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); + if (packet instanceof HandshakeC2SPacket handshakePacket) { + blocker.enable(); + packetsBlocked = true; + this.handshakePacket = handshakePacket; - HandshakeC2SPacket handshakePacket = (HandshakeC2SPacket) packet; - FloodgateHandshakeHandler.HandshakeResult result = handshakeHandler.handle(ctx.channel(), handshakePacket.getAddress()); + networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); + + handshakeHandler.handle(ctx.channel(), handshakePacket.getAddress()).thenApply(result -> { HandshakeData handshakeData = result.getHandshakeData(); - packet = new HandshakeC2SPacket(handshakeData.getHostname(), handshakePacket.getPort(), handshakePacket.getIntendedState()); - ReferenceCountUtil.retain(packet); + this.handshakePacket = new HandshakeC2SPacket(handshakeData.getHostname(), + handshakePacket.getPort(), handshakePacket.getIntendedState()); + + InetSocketAddress newIp = result.getNewIp(ctx.channel()); + if (newIp != null) { + ((ClientConnectionMixin) networkManager).setAddress(newIp); + } if (handshakeData.getDisconnectReason() != null) { ctx.close(); //todo disconnect with message - return; + return true; } //todo use kickMessageAttribute and let this be common logic @@ -59,9 +81,13 @@ public final class FabricDataHandler extends ChannelInboundHandlerAdapter { case SUCCESS: break; case EXCEPTION: + logger.info(Constants.INTERNAL_ERROR_MESSAGE); + ctx.close(); + return true; + case DECRYPT_ERROR: logger.info(config.getDisconnect().getInvalidKey()); ctx.close(); - return; + return true; case INVALID_DATA_LENGTH: int dataLength = result.getBedrockData().getDataLength(); logger.info( @@ -69,44 +95,58 @@ public final class FabricDataHandler extends ChannelInboundHandlerAdapter { BedrockData.EXPECTED_LENGTH, dataLength ); ctx.close(); - return; - case TIMESTAMP_DENIED: - logger.info(Constants.TIMESTAMP_DENIED_MESSAGE); - ctx.close(); - return; + return true; default: // only continue when SUCCESS - return; + return true; } player = result.getFloodgatePlayer(); - } else if (isLogin) { - // we have to fake the offline player (login) cycle - if (!(networkManager.getPacketListener() instanceof ServerLoginNetworkHandler)) { - // player is not in the login state, abort - return; + return player == null; + }).thenAccept(shouldRemove -> { + ctx.fireChannelRead(this.handshakePacket); + Object queuedPacket; + while ((queuedPacket = packetQueue.poll()) != null) { + if (checkLogin(ctx, packet)) { + break; + } + ctx.fireChannelRead(queuedPacket); } - GameProfile gameProfile = new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername()); + if (shouldRemove) { + ctx.pipeline().remove(FabricDataHandler.this); + } + blocker.disable(); + packetsBlocked = false; + }); + return; + } - ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); - ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setLoginState(); - } - } finally { - // don't let the packet through if the packet is the login packet - // because we want to skip the login cycle - if (isLogin) { - ReferenceCountUtil.release(packet, 2); - } else { - ctx.fireChannelRead(packet); - } - - if (isLogin || player == null) { - // we're done, we'll just wait for the loginSuccessCall - done = true; - } + if (!checkLogin(ctx, packet)) { + ctx.fireChannelRead(packet); } } + private boolean checkLogin(ChannelHandlerContext ctx, Object packet) { + if (packet instanceof LoginHelloC2SPacket) { + // we have to fake the offline player (login) cycle + if (!(networkManager.getPacketListener() instanceof ServerLoginNetworkHandler)) { + // player is not in the login state, abort + ctx.pipeline().remove(this); + return true; + } + + GameProfile gameProfile = new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername()); + + ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); + ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setLoginState(); + + ctx.pipeline().remove(this); + return true; + } + return false; + } + + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java new file mode 100644 index 00000000..7392653f --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java @@ -0,0 +1,13 @@ +package org.geysermc.floodgate.mixin; + +import net.minecraft.network.ClientConnection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.net.SocketAddress; + +@Mixin(ClientConnection.class) +public interface ClientConnectionMixin { + @Accessor("address") + void setAddress(SocketAddress address); +} diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java index 71049150..b99d0e2a 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java @@ -5,19 +5,15 @@ import com.mojang.authlib.GameProfile; import net.minecraft.server.network.ServerLoginNetworkHandler; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(ServerLoginNetworkHandler.class) public abstract class ServerLoginNetworkHandlerMixin implements ServerLoginNetworkHandlerSetter { @Shadow - private GameProfile profile; + ServerLoginNetworkHandler.State state; - @Shadow - private ServerLoginNetworkHandler.State state; - - @Override - public void setGameProfile(GameProfile profile) { - this.profile = profile; - } + @Accessor("profile") + public abstract void setGameProfile(GameProfile profile); @Override public void setLoginState() { diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index ef001e9b..9040e4a8 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.platform.fabric.FabricServerAudiences; +import net.kyori.adventure.platform.fabric.PlayerLocales; import net.kyori.adventure.platform.fabric.impl.accessor.ConnectionAccess; import net.kyori.adventure.platform.fabric.impl.server.FriendlyByteBufBridge; import net.minecraft.entity.Entity; @@ -71,14 +72,8 @@ public final class FabricCommandUtil implements CommandUtil { } private FabricUserAudience getAudience0(ServerPlayerEntity player) { - // Marked as internal??? Should probably find a better way to get this. - Locale locale; - Channel channel = ((ConnectionAccess) player.networkHandler.getConnection()).getChannel(); - if (channel != null) { - locale = channel.attr(FriendlyByteBufBridge.CHANNEL_LOCALE).get(); - } else { - locale = null; - } + // Apparently can be null even if Javadocs say otherwise + Locale locale = PlayerLocales.locale(player); return new FabricUserAudience.NamedFabricUserAudience( player.getName().asString(), player.getUuid(), locale != null ? @@ -191,7 +186,7 @@ public final class FabricCommandUtil implements CommandUtil { return true; } - protected ServerPlayerEntity getPlayer(Object instance) { + private ServerPlayerEntity getPlayer(Object instance) { try { ServerCommandSource source = (ServerCommandSource) instance; return source.getPlayer(); diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index 4fdb9935..6aa2ae26 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -4,6 +4,7 @@ "package": "org.geysermc.floodgate.mixin", "compatibilityLevel": "JAVA_16", "mixins": [ + "ClientConnectionMixin", "ServerLoginNetworkHandlerMixin", "ServerNetworkIoMixin" ], From 8b9c6b81abdbc324a43b08922ab72f5d613b012b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:40:15 -0400 Subject: [PATCH 22/87] Update for latest Floodgate usage --- .../geysermc/floodgate/util/FabricCommandUtil.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 9040e4a8..a6c0e509 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -2,14 +2,11 @@ package org.geysermc.floodgate.util; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import io.netty.channel.Channel; import lombok.Getter; import lombok.RequiredArgsConstructor; import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.platform.fabric.FabricServerAudiences; import net.kyori.adventure.platform.fabric.PlayerLocales; -import net.kyori.adventure.platform.fabric.impl.accessor.ConnectionAccess; -import net.kyori.adventure.platform.fabric.impl.server.FriendlyByteBufBridge; import net.minecraft.entity.Entity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.WhitelistEntry; @@ -173,15 +170,15 @@ public final class FabricCommandUtil implements CommandUtil { } @Override - public boolean whitelistPlayer(String xuid, String username) { - GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username); + public boolean whitelistPlayer(UUID uuid, String username) { + GameProfile profile = new GameProfile(uuid, username); SERVER.getPlayerManager().getWhitelist().add(new WhitelistEntry(profile)); return true; } @Override - public boolean removePlayerFromWhitelist(String xuid, String username) { - GameProfile profile = new GameProfile(Utils.getJavaUuid(xuid), username); + public boolean removePlayerFromWhitelist(UUID uuid, String username) { + GameProfile profile = new GameProfile(uuid, username); SERVER.getPlayerManager().getWhitelist().remove(profile); return true; } From 39a519b4841abf9408d8fa2179f24fccefe0ea0a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 1 Dec 2021 15:37:52 -0500 Subject: [PATCH 23/87] Update to latest Floodgate changes Co-Authored-By: byquanton <32410361+byquanton@users.noreply.github.com> --- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- gradlew.bat | 21 +-- .../floodgate/addon/data/FabricDataAddon.java | 9 +- .../addon/data/FabricDataHandler.java | 172 +++++++----------- .../mixin/ClientIntentionPacketMixin.java | 14 ++ .../ServerLoginNetworkHandlerSetter.java | 3 - src/main/resources/floodgate.mixins.json | 3 +- 10 files changed, 92 insertions(+), 136 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java diff --git a/gradle.properties b/gradle.properties index 531ef28e..8e7bf696 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.17.1 yarn_mappings=1.17.1+build.14 loader_version=0.11.6 # Mod Properties -mod_version=2.0-SNAPSHOT +mod_version=2.1.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 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 Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf5..e750102e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c515..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index a9f778a7..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java index a59c3891..6cab89d5 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -22,18 +22,19 @@ public final class FabricDataAddon implements InjectorAddon { @Named("packetHandler") private String packetHandlerName; + @Inject + @Named("kickMessageAttribute") + private AttributeKey kickMessageAttribute; + @Inject @Named("playerAttribute") private AttributeKey playerAttribute; @Override public void onInject(Channel channel, boolean toServer) { - PacketBlocker blocker = new PacketBlocker(); - channel.pipeline().addBefore("decoder", "floodgate_packet_blocker", blocker); - channel.pipeline().addBefore( packetHandlerName, "floodgate_data_handler", - new FabricDataHandler(config, handshakeHandler, blocker, logger) + new FabricDataHandler(handshakeHandler, config, kickMessageAttribute, logger) ); } diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 3b064a25..b0b769d9 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -1,133 +1,91 @@ package org.geysermc.floodgate.addon.data; -import com.google.common.collect.Queues; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import net.minecraft.text.Text; +import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.mixin.ClientConnectionMixin; +import org.geysermc.floodgate.mixin.ClientIntentionPacketMixin; import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.util.ReferenceCountUtil; -import lombok.RequiredArgsConstructor; import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket; import net.minecraft.server.network.ServerLoginNetworkHandler; -import org.geysermc.floodgate.api.handshake.HandshakeData; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; -import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.Constants; +import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; import java.net.InetSocketAddress; -import java.util.Queue; -@RequiredArgsConstructor -public final class FabricDataHandler extends ChannelInboundHandlerAdapter { - private final FloodgateConfig config; - private final FloodgateHandshakeHandler handshakeHandler; - private final PacketBlocker blocker; +public final class FabricDataHandler extends CommonDataHandler { private final FloodgateLogger logger; - - private final Queue packetQueue = Queues.newConcurrentLinkedQueue(); - private ClientConnection networkManager; private FloodgatePlayer player; - /** - * As a variable so we can change it without reflection - */ - HandshakeC2SPacket handshakePacket; - /** - * A boolean to compensate for the above - */ - private boolean packetsBlocked; - - @Override - public void channelRead(ChannelHandlerContext ctx, Object packet) { - // prevent other packets from being handled while we handle the handshake packet - if (packetsBlocked) { - packetQueue.add(packet); - return; - } - - if (packet instanceof HandshakeC2SPacket handshakePacket) { - blocker.enable(); - packetsBlocked = true; - this.handshakePacket = handshakePacket; - - networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); - - handshakeHandler.handle(ctx.channel(), handshakePacket.getAddress()).thenApply(result -> { - HandshakeData handshakeData = result.getHandshakeData(); - - this.handshakePacket = new HandshakeC2SPacket(handshakeData.getHostname(), - handshakePacket.getPort(), handshakePacket.getIntendedState()); - - InetSocketAddress newIp = result.getNewIp(ctx.channel()); - if (newIp != null) { - ((ClientConnectionMixin) networkManager).setAddress(newIp); - } - - if (handshakeData.getDisconnectReason() != null) { - ctx.close(); //todo disconnect with message - return true; - } - - //todo use kickMessageAttribute and let this be common logic - - switch (result.getResultType()) { - case SUCCESS: - break; - case EXCEPTION: - logger.info(Constants.INTERNAL_ERROR_MESSAGE); - ctx.close(); - return true; - case DECRYPT_ERROR: - logger.info(config.getDisconnect().getInvalidKey()); - ctx.close(); - return true; - case INVALID_DATA_LENGTH: - int dataLength = result.getBedrockData().getDataLength(); - logger.info( - config.getDisconnect().getInvalidArgumentsLength(), - BedrockData.EXPECTED_LENGTH, dataLength - ); - ctx.close(); - return true; - default: // only continue when SUCCESS - return true; - } - - player = result.getFloodgatePlayer(); - return player == null; - }).thenAccept(shouldRemove -> { - ctx.fireChannelRead(this.handshakePacket); - Object queuedPacket; - while ((queuedPacket = packetQueue.poll()) != null) { - if (checkLogin(ctx, packet)) { - break; - } - ctx.fireChannelRead(queuedPacket); - } - - if (shouldRemove) { - ctx.pipeline().remove(FabricDataHandler.this); - } - blocker.disable(); - packetsBlocked = false; - }); - return; - } - - if (!checkLogin(ctx, packet)) { - ctx.fireChannelRead(packet); - } + public FabricDataHandler( + FloodgateHandshakeHandler handshakeHandler, + FloodgateConfig config, + AttributeKey kickMessageAttribute, FloodgateLogger logger) { + super(handshakeHandler, config, kickMessageAttribute, new PacketBlocker()); + this.logger = logger; } - private boolean checkLogin(ChannelHandlerContext ctx, Object packet) { + @Override + protected void setNewIp(Channel channel, InetSocketAddress newIp) { + ((ClientConnectionMixin) this.networkManager).setAddress(newIp); + } + + @Override + protected Object setHostname(Object handshakePacket, String hostname) { + // While it would be ideal to simply create a new handshake packet, the packet constructor + // does not allow us to set the protocol version + ((ClientIntentionPacketMixin) handshakePacket).setAddress(hostname); + return handshakePacket; + } + + @Override + protected boolean shouldRemoveHandler(HandshakeResult result) { + player = result.getFloodgatePlayer(); + + if (getKickMessage() != null) { + // we also have to keep this handler if we want to kick then with a disconnect message + return false; + } else if (player == null) { + // player is not a Floodgate player + return true; + } + + if (result.getResultType() == FloodgateHandshakeHandler.ResultType.SUCCESS) { + logger.info("Floodgate player who is logged in as {} {} joined", + player.getCorrectUsername(), player.getCorrectUniqueId()); + } + + // Handler will be removed after the login hello packet is handled + return false; + } + + @Override + protected boolean channelRead(Object packet) { + if (packet instanceof HandshakeC2SPacket handshakePacket) { + ctx.pipeline().addAfter("splitter", "floodgate_packet_blocker", blocker); + networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); + handle(packet, handshakePacket.getAddress()); + return false; + } + return !checkAndHandleLogin(packet); + } + + private boolean checkAndHandleLogin(Object packet) { if (packet instanceof LoginHelloC2SPacket) { + String kickMessage = getKickMessage(); + if (kickMessage != null) { + networkManager.disconnect(Text.of(kickMessage)); + return true; + } + // we have to fake the offline player (login) cycle if (!(networkManager.getPacketListener() instanceof ServerLoginNetworkHandler)) { // player is not in the login state, abort diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java new file mode 100644 index 00000000..227729ce --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.mixin; + +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(HandshakeC2SPacket.class) +public interface ClientIntentionPacketMixin { + + @Accessor("address") + @Mutable + void setAddress(String address); +} diff --git a/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java index f9e8c2be..fb94a607 100644 --- a/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java +++ b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java @@ -1,9 +1,6 @@ package org.geysermc.floodgate.mixin_interface; import com.mojang.authlib.GameProfile; -import net.minecraft.server.network.ServerLoginNetworkHandler; - -import java.util.UUID; public interface ServerLoginNetworkHandlerSetter { void setGameProfile(GameProfile profile); diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index 6aa2ae26..a19fd613 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -6,7 +6,8 @@ "mixins": [ "ClientConnectionMixin", "ServerLoginNetworkHandlerMixin", - "ServerNetworkIoMixin" + "ServerNetworkIoMixin", + "ClientIntentionPacketMixin" ], "injectors": { "defaultRequire": 1 From 72be662bcad516fa928f2a1d5c7a42af0a3ffb11 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 1 Dec 2021 21:54:26 -0500 Subject: [PATCH 24/87] Update to Mojang mappings --- build.gradle | 2 +- .../geysermc/floodgate/FabricPlatform.java | 1 - .../addon/data/FabricDataHandler.java | 34 ++++---- .../listener/FabricEventListener.java | 6 +- .../mixin/ClientIntentionPacketMixin.java | 6 +- ...nectionMixin.java => ConnectionMixin.java} | 6 +- ...ava => ServerConnectionListenerMixin.java} | 8 +- .../mixin/ServerLoginNetworkHandlerMixin.java | 22 ----- .../ServerLoginPacketListenerImplMixin.java | 22 +++++ ...a => ServerLoginPacketListenerSetter.java} | 2 +- .../floodgate/module/FabricCommandModule.java | 6 +- .../pluginmessage/FabricSkinApplier.java | 56 ++++++------ .../floodgate/util/FabricCommandUtil.java | 85 +++++++++---------- .../floodgate/util/FabricUserAudience.java | 20 ++--- src/main/resources/floodgate.accesswidener | 2 +- src/main/resources/floodgate.mixins.json | 6 +- 16 files changed, 141 insertions(+), 143 deletions(-) rename src/main/java/org/geysermc/floodgate/mixin/{ClientConnectionMixin.java => ConnectionMixin.java} (66%) rename src/main/java/org/geysermc/floodgate/mixin/{ServerNetworkIoMixin.java => ServerConnectionListenerMixin.java} (71%) delete mode 100644 src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java create mode 100644 src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java rename src/main/java/org/geysermc/floodgate/mixin_interface/{ServerLoginNetworkHandlerSetter.java => ServerLoginPacketListenerSetter.java} (76%) diff --git a/build.gradle b/build.gradle index 2945e98c..794aea68 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ minecraft { dependencies { //to change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + mappings loom.officialMojangMappings() modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Fabric API. This is technically optional, but you probably want it anyway. diff --git a/src/main/java/org/geysermc/floodgate/FabricPlatform.java b/src/main/java/org/geysermc/floodgate/FabricPlatform.java index 3d29c2b0..af1a0a20 100644 --- a/src/main/java/org/geysermc/floodgate/FabricPlatform.java +++ b/src/main/java/org/geysermc/floodgate/FabricPlatform.java @@ -2,7 +2,6 @@ package org.geysermc.floodgate; import com.google.inject.Inject; import com.google.inject.Injector; -import org.geysermc.floodgate.FloodgatePlatform; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.logger.FloodgateLogger; diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index b0b769d9..71a5aa2e 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -2,17 +2,17 @@ package org.geysermc.floodgate.addon.data; import io.netty.channel.Channel; import io.netty.util.AttributeKey; -import net.minecraft.text.Text; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.handshake.ClientIntentionPacket; +import net.minecraft.network.protocol.login.ServerboundHelloPacket; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.mixin.ClientConnectionMixin; +import org.geysermc.floodgate.mixin.ConnectionMixin; import org.geysermc.floodgate.mixin.ClientIntentionPacketMixin; -import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; +import org.geysermc.floodgate.mixin_interface.ServerLoginPacketListenerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; -import net.minecraft.network.ClientConnection; -import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; -import net.minecraft.network.packet.c2s.login.LoginHelloC2SPacket; -import net.minecraft.server.network.ServerLoginNetworkHandler; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; @@ -22,7 +22,7 @@ import java.net.InetSocketAddress; public final class FabricDataHandler extends CommonDataHandler { private final FloodgateLogger logger; - private ClientConnection networkManager; + private Connection networkManager; private FloodgatePlayer player; public FabricDataHandler( @@ -35,7 +35,7 @@ public final class FabricDataHandler extends CommonDataHandler { @Override protected void setNewIp(Channel channel, InetSocketAddress newIp) { - ((ClientConnectionMixin) this.networkManager).setAddress(newIp); + ((ConnectionMixin) this.networkManager).setAddress(newIp); } @Override @@ -69,25 +69,25 @@ public final class FabricDataHandler extends CommonDataHandler { @Override protected boolean channelRead(Object packet) { - if (packet instanceof HandshakeC2SPacket handshakePacket) { + if (packet instanceof ClientIntentionPacket intentionPacket) { ctx.pipeline().addAfter("splitter", "floodgate_packet_blocker", blocker); - networkManager = (ClientConnection) ctx.channel().pipeline().get("packet_handler"); - handle(packet, handshakePacket.getAddress()); + networkManager = (Connection) ctx.channel().pipeline().get("packet_handler"); + handle(packet, intentionPacket.getHostName()); return false; } return !checkAndHandleLogin(packet); } private boolean checkAndHandleLogin(Object packet) { - if (packet instanceof LoginHelloC2SPacket) { + if (packet instanceof ServerboundHelloPacket) { String kickMessage = getKickMessage(); if (kickMessage != null) { - networkManager.disconnect(Text.of(kickMessage)); + networkManager.disconnect(Component.nullToEmpty(kickMessage)); return true; } // we have to fake the offline player (login) cycle - if (!(networkManager.getPacketListener() instanceof ServerLoginNetworkHandler)) { + if (!(networkManager.getPacketListener() instanceof ServerLoginPacketListenerImpl)) { // player is not in the login state, abort ctx.pipeline().remove(this); return true; @@ -95,8 +95,8 @@ public final class FabricDataHandler extends CommonDataHandler { GameProfile gameProfile = new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername()); - ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); - ((ServerLoginNetworkHandlerSetter) networkManager.getPacketListener()).setLoginState(); + ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); + ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setLoginState(); ctx.pipeline().remove(this); return true; diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java index d0e1c832..bb5cbe12 100644 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java +++ b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java @@ -3,7 +3,7 @@ package org.geysermc.floodgate.listener; import com.google.inject.Inject; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerGamePacketListenerImpl; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; @@ -15,8 +15,8 @@ public final class FabricEventListener { @Inject private FloodgateLogger logger; @Inject private LanguageManager languageManager; - public void onPlayerJoin(ServerPlayNetworkHandler networkHandler, PacketSender packetSender, MinecraftServer server) { - FloodgatePlayer player = api.getPlayer(networkHandler.player.getUuid()); + public void onPlayerJoin(ServerGamePacketListenerImpl networkHandler, PacketSender packetSender, MinecraftServer server) { + FloodgatePlayer player = api.getPlayer(networkHandler.player.getUUID()); if (player != null) { player.as(FloodgatePlayerImpl.class).setLogin(false); logger.translatedInfo( diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java index 227729ce..8cef9aca 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java @@ -1,14 +1,14 @@ package org.geysermc.floodgate.mixin; -import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.gen.Accessor; -@Mixin(HandshakeC2SPacket.class) +@Mixin(ClientIntentionPacket.class) public interface ClientIntentionPacketMixin { - @Accessor("address") + @Accessor("hostName") @Mutable void setAddress(String address); } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java similarity index 66% rename from src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java rename to src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java index 7392653f..64948225 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientConnectionMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java @@ -1,13 +1,13 @@ package org.geysermc.floodgate.mixin; -import net.minecraft.network.ClientConnection; +import net.minecraft.network.Connection; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import java.net.SocketAddress; -@Mixin(ClientConnection.class) -public interface ClientConnectionMixin { +@Mixin(Connection.class) +public interface ConnectionMixin { @Accessor("address") void setAddress(SocketAddress address); } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java similarity index 71% rename from src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java rename to src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java index 8e966d54..dd04e51d 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerNetworkIoMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java @@ -1,8 +1,8 @@ package org.geysermc.floodgate.mixin; +import net.minecraft.server.network.ServerConnectionListener; import org.geysermc.floodgate.inject.fabric.FabricInjector; import io.netty.channel.ChannelFuture; -import net.minecraft.server.ServerNetworkIo; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -13,12 +13,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.net.InetAddress; import java.util.List; -@Mixin(ServerNetworkIo.class) -public abstract class ServerNetworkIoMixin { +@Mixin(ServerConnectionListener.class) +public abstract class ServerConnectionListenerMixin { @Shadow @Final private List channels; - @Inject(method = "bind", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) + @Inject(method = "startTcpServerListener", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) public void onChannelAdd(InetAddress address, int port, CallbackInfo ci) { FabricInjector.getInstance().injectClient(this.channels.get(this.channels.size() - 1)); } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java deleted file mode 100644 index b99d0e2a..00000000 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginNetworkHandlerMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.geysermc.floodgate.mixin; - -import org.geysermc.floodgate.mixin_interface.ServerLoginNetworkHandlerSetter; -import com.mojang.authlib.GameProfile; -import net.minecraft.server.network.ServerLoginNetworkHandler; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(ServerLoginNetworkHandler.class) -public abstract class ServerLoginNetworkHandlerMixin implements ServerLoginNetworkHandlerSetter { - @Shadow - ServerLoginNetworkHandler.State state; - - @Accessor("profile") - public abstract void setGameProfile(GameProfile profile); - - @Override - public void setLoginState() { - this.state = ServerLoginNetworkHandler.State.READY_TO_ACCEPT; - } -} diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java new file mode 100644 index 00000000..afebed5b --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java @@ -0,0 +1,22 @@ +package org.geysermc.floodgate.mixin; + +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import org.geysermc.floodgate.mixin_interface.ServerLoginPacketListenerSetter; +import com.mojang.authlib.GameProfile; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerLoginPacketListenerImpl.class) +public abstract class ServerLoginPacketListenerImplMixin implements ServerLoginPacketListenerSetter { + @Shadow + ServerLoginPacketListenerImpl.State state; + + @Accessor("gameProfile") + public abstract void setGameProfile(GameProfile profile); + + @Override + public void setLoginState() { + this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; + } +} diff --git a/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java similarity index 76% rename from src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java rename to src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java index fb94a607..4861cc76 100644 --- a/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginNetworkHandlerSetter.java +++ b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java @@ -2,7 +2,7 @@ package org.geysermc.floodgate.mixin_interface; import com.mojang.authlib.GameProfile; -public interface ServerLoginNetworkHandlerSetter { +public interface ServerLoginPacketListenerSetter { void setGameProfile(GameProfile profile); void setLoginState(); diff --git a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java index f30a3b30..3d824010 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java @@ -7,7 +7,7 @@ import cloud.commandframework.fabric.FabricServerCommandManager; import com.google.inject.Provides; import com.google.inject.Singleton; import lombok.SneakyThrows; -import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.commands.CommandSourceStack; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.UserAudience; @@ -17,10 +17,10 @@ public final class FabricCommandModule extends CommandModule { @Singleton @SneakyThrows public CommandManager commandManager(CommandUtil commandUtil) { - FabricCommandManager commandManager = new FabricServerCommandManager<>( + FabricCommandManager commandManager = new FabricServerCommandManager<>( CommandExecutionCoordinator.simpleCoordinator(), commandUtil::getAudience, - audience -> (ServerCommandSource) audience.source() + audience -> (CommandSourceStack) audience.source() ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); return commandManager; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index c5c07eb8..b38be056 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -5,14 +5,14 @@ import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; import com.mojang.datafixers.util.Pair; import lombok.RequiredArgsConstructor; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.entity.attribute.EntityAttributeInstance; -import net.minecraft.entity.effect.StatusEffectInstance; -import net.minecraft.item.ItemStack; -import net.minecraft.network.Packet; -import net.minecraft.network.packet.s2c.play.*; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.item.ItemStack; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinData; @@ -28,7 +28,7 @@ public final class FabricSkinApplier implements SkinApplier { @Override public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { SERVER.execute(() -> { - ServerPlayerEntity bedrockPlayer = SERVER.getPlayerManager().getPlayer(floodgatePlayer.getCorrectUniqueId()); + ServerPlayer bedrockPlayer = SERVER.getPlayerList().getPlayer(floodgatePlayer.getCorrectUniqueId()); if (bedrockPlayer == null) { // Disconnected probably? return; @@ -41,58 +41,58 @@ public final class FabricSkinApplier implements SkinApplier { properties.put("textures", new Property("textures", skinData.getValue(), skinData.getSignature())); // Skin is applied - now it's time to refresh the player for everyone. Oof. - for (ServerPlayerEntity otherPlayer : SERVER.getPlayerManager().getPlayerList()) { + for (ServerPlayer otherPlayer : SERVER.getPlayerList().getPlayers()) { if (otherPlayer == bedrockPlayer) { continue; } - boolean loadedInWorld = otherPlayer.getEntityWorld().getEntityById(bedrockPlayer.getId()) != null; + boolean loadedInWorld = otherPlayer.getCommandSenderWorld().getEntity(bedrockPlayer.getId()) != null; if (loadedInWorld) { // Player is loaded in this world - otherPlayer.networkHandler.sendPacket(new EntitiesDestroyS2CPacket(bedrockPlayer.getId())); + otherPlayer.connection.send(new ClientboundRemoveEntitiesPacket(bedrockPlayer.getId())); } - otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.REMOVE_PLAYER, bedrockPlayer)); + otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, bedrockPlayer)); - otherPlayer.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.ADD_PLAYER, bedrockPlayer)); + otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, bedrockPlayer)); if (loadedInWorld) { // Copied from EntityTrackerEntry - Packet spawnPacket = bedrockPlayer.createSpawnPacket(); - otherPlayer.networkHandler.sendPacket(spawnPacket); - if (!bedrockPlayer.getDataTracker().isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityTrackerUpdateS2CPacket(bedrockPlayer.getId(), bedrockPlayer.getDataTracker(), true)); + Packet spawnPacket = bedrockPlayer.getAddEntityPacket(); + otherPlayer.connection.send(spawnPacket); + if (!bedrockPlayer.getEntityData().isEmpty()) { + otherPlayer.connection.send(new ClientboundSetEntityDataPacket(bedrockPlayer.getId(), bedrockPlayer.getEntityData(), true)); } - Collection collection = bedrockPlayer.getAttributes().getAttributesToSend(); + Collection collection = bedrockPlayer.getAttributes().getDirtyAttributes(); if (!collection.isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityAttributesS2CPacket(bedrockPlayer.getId(), collection)); + otherPlayer.connection.send(new ClientboundUpdateAttributesPacket(bedrockPlayer.getId(), collection)); } - otherPlayer.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(bedrockPlayer.getId(), bedrockPlayer.getVelocity())); + otherPlayer.connection.send(new ClientboundSetEntityMotionPacket(bedrockPlayer.getId(), bedrockPlayer.getDeltaMovement())); List> equipmentList = Lists.newArrayList(); EquipmentSlot[] slots = EquipmentSlot.values(); for (EquipmentSlot equipmentSlot : slots) { - ItemStack itemStack = bedrockPlayer.getEquippedStack(equipmentSlot); + ItemStack itemStack = bedrockPlayer.getItemBySlot(equipmentSlot); if (!itemStack.isEmpty()) { equipmentList.add(Pair.of(equipmentSlot, itemStack.copy())); } } if (!equipmentList.isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityEquipmentUpdateS2CPacket(bedrockPlayer.getId(), equipmentList)); + otherPlayer.connection.send(new ClientboundSetEquipmentPacket(bedrockPlayer.getId(), equipmentList)); } - for (StatusEffectInstance statusEffectInstance : bedrockPlayer.getStatusEffects()) { - otherPlayer.networkHandler.sendPacket(new EntityStatusEffectS2CPacket(bedrockPlayer.getId(), statusEffectInstance)); + for (MobEffectInstance mobEffectInstance : bedrockPlayer.getActiveEffects()) { + otherPlayer.connection.send(new ClientboundUpdateMobEffectPacket(bedrockPlayer.getId(), mobEffectInstance)); } - if (!bedrockPlayer.getPassengerList().isEmpty()) { - otherPlayer.networkHandler.sendPacket(new EntityPassengersSetS2CPacket(bedrockPlayer)); + if (!bedrockPlayer.getPassengers().isEmpty()) { + otherPlayer.connection.send(new ClientboundSetPassengersPacket(bedrockPlayer)); } - if (bedrockPlayer.hasVehicle()) { - otherPlayer.networkHandler.sendPacket(new EntityPassengersSetS2CPacket(bedrockPlayer.getVehicle())); + if (bedrockPlayer.getVehicle() != null) { + otherPlayer.connection.send(new ClientboundSetPassengersPacket(bedrockPlayer.getVehicle())); } } } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index a6c0e509..29758e42 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -7,13 +7,13 @@ import lombok.RequiredArgsConstructor; import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.platform.fabric.FabricServerAudiences; import net.kyori.adventure.platform.fabric.PlayerLocales; -import net.minecraft.entity.Entity; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.WhitelistEntry; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.LiteralText; -import net.minecraft.text.Text; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.UserWhiteListEntry; +import net.minecraft.world.entity.Entity; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; @@ -38,13 +38,12 @@ public final class FabricCommandUtil implements CommandUtil { @Override public @NonNull UserAudience getAudience(@NonNull Object source) { - if (!(source instanceof ServerCommandSource)) { + if (!(source instanceof CommandSourceStack commandSource)) { throw new RuntimeException(); } - ServerCommandSource commandSource = (ServerCommandSource) source; - if (commandSource.getEntity() instanceof ServerPlayerEntity) { - return getAudience0((ServerPlayerEntity) commandSource.getEntity()); + if (commandSource.getEntity() instanceof ServerPlayer) { + return getAudience0((ServerPlayer) commandSource.getEntity()); } return new FabricUserAudience(null, manager.getDefaultLocale(), commandSource, this); @@ -52,7 +51,7 @@ public final class FabricCommandUtil implements CommandUtil { @Override public UserAudience getAudienceByUuid(@NonNull UUID uuid) { - ServerPlayerEntity player = SERVER.getPlayerManager().getPlayer(uuid); + ServerPlayer player = SERVER.getPlayerList().getPlayer(uuid); if (player != null) { return getAudience0(player); } @@ -61,21 +60,21 @@ public final class FabricCommandUtil implements CommandUtil { @Override public UserAudience getAudienceByUsername(@NonNull String username) { - ServerPlayerEntity player = SERVER.getPlayerManager().getPlayer(username); + ServerPlayer player = SERVER.getPlayerList().getPlayerByName(username); if (player != null) { return getAudience0(player); } return getOfflineAudienceByUsername(username); } - private FabricUserAudience getAudience0(ServerPlayerEntity player) { + private FabricUserAudience getAudience0(ServerPlayer player) { // Apparently can be null even if Javadocs say otherwise Locale locale = PlayerLocales.locale(player); return new FabricUserAudience.NamedFabricUserAudience( - player.getName().asString(), - player.getUuid(), locale != null ? + player.getName().getString(), + player.getUUID(), locale != null ? locale.getLanguage().toLowerCase(Locale.ROOT) + "_" + locale.getCountry().toUpperCase(Locale.ROOT) : - manager.getDefaultLocale(), player.getCommandSource(), this, true); + manager.getDefaultLocale(), player.createCommandSourceStack(), this, true); } @Override @@ -86,7 +85,7 @@ public final class FabricCommandUtil implements CommandUtil { @Override public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { UUID uuid = null; - Optional profile = SERVER.getUserCache().findByName(username); + Optional profile = SERVER.getProfileCache().get(username); if (profile.isPresent()) { uuid = profile.get().getId(); } @@ -95,26 +94,26 @@ public final class FabricCommandUtil implements CommandUtil { @Override public @NonNull Collection getOnlineUsernames(UserAudienceArgument.@NonNull PlayerType limitTo) { - List players = SERVER.getPlayerManager().getPlayerList(); + List players = SERVER.getPlayerList().getPlayers(); Collection usernames = new ArrayList<>(); switch (limitTo) { case ALL_PLAYERS: - for (ServerPlayerEntity player : players) { - usernames.add(player.getName().asString()); + for (ServerPlayer player : players) { + usernames.add(player.getName().getString()); } break; case ONLY_JAVA: - for (ServerPlayerEntity player : players) { - if (!api.isFloodgatePlayer(player.getUuid())) { - usernames.add(player.getName().asString()); + for (ServerPlayer player : players) { + if (!api.isFloodgatePlayer(player.getUUID())) { + usernames.add(player.getName().getString()); } } break; case ONLY_BEDROCK: - for (ServerPlayerEntity player : players) { - if (api.isFloodgatePlayer(player.getUuid())) { - usernames.add(player.getName().asString()); + for (ServerPlayer player : players) { + if (api.isFloodgatePlayer(player.getUUID())) { + usernames.add(player.getName().getString()); } } break; @@ -132,7 +131,7 @@ public final class FabricCommandUtil implements CommandUtil { @Override public Collection getOnlinePlayersWithPermission(String permission) { List players = new ArrayList<>(); - for (ServerPlayerEntity player : SERVER.getPlayerManager().getPlayerList()) { + for (ServerPlayer player : SERVER.getPlayerList().getPlayers()) { if (hasPermission(player, permission)) { players.add(player); } @@ -142,10 +141,10 @@ public final class FabricCommandUtil implements CommandUtil { @Override public void sendMessage(Object player, String locale, TranslatableMessage message, Object... args) { - ServerCommandSource commandSource = (ServerCommandSource) player; - if (commandSource.getEntity() instanceof ServerPlayerEntity) { - SERVER.execute(() -> ((ServerPlayerEntity) commandSource.getEntity()) - .sendMessage(translateAndTransform(locale, message, args), false)); + CommandSourceStack commandSource = (CommandSourceStack) player; + if (commandSource.getEntity() instanceof ServerPlayer) { + SERVER.execute(() -> ((ServerPlayer) commandSource.getEntity()) + .displayClientMessage(translateAndTransform(locale, message, args), false)); } else { // Console? logger.info(message.translateMessage(manager, locale, args)); @@ -154,10 +153,10 @@ public final class FabricCommandUtil implements CommandUtil { @Override public void sendMessage(Object target, String message) { - ServerCommandSource commandSource = (ServerCommandSource) target; - if (commandSource.getEntity() instanceof ServerPlayerEntity) { - SERVER.execute(() -> ((ServerPlayerEntity) commandSource.getEntity()) - .sendMessage(new LiteralText(message), false)); + CommandSourceStack commandSource = (CommandSourceStack) target; + if (commandSource.getEntity() instanceof ServerPlayer) { + SERVER.execute(() -> ((ServerPlayer) commandSource.getEntity()) + .displayClientMessage(new TextComponent(message), false)); } else { // Console? logger.info(message); @@ -166,35 +165,35 @@ public final class FabricCommandUtil implements CommandUtil { @Override public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { - getPlayer(player).networkHandler.disconnect(translateAndTransform(locale, message, args)); + getPlayer(player).connection.disconnect(translateAndTransform(locale, message, args)); } @Override public boolean whitelistPlayer(UUID uuid, String username) { GameProfile profile = new GameProfile(uuid, username); - SERVER.getPlayerManager().getWhitelist().add(new WhitelistEntry(profile)); + SERVER.getPlayerList().getWhiteList().add(new UserWhiteListEntry(profile)); return true; } @Override public boolean removePlayerFromWhitelist(UUID uuid, String username) { GameProfile profile = new GameProfile(uuid, username); - SERVER.getPlayerManager().getWhitelist().remove(profile); + SERVER.getPlayerList().getWhiteList().remove(profile); return true; } - private ServerPlayerEntity getPlayer(Object instance) { + private ServerPlayer getPlayer(Object instance) { try { - ServerCommandSource source = (ServerCommandSource) instance; - return source.getPlayer(); + CommandSourceStack source = (CommandSourceStack) instance; + return source.getPlayerOrException(); } catch (ClassCastException | CommandSyntaxException exception) { logger.error("Failed to cast {} as a player", instance.getClass().getName()); throw new RuntimeException(); } } - public Text translateAndTransform(String locale, TranslatableMessage message, Object... args) { - return new LiteralText(message.translateMessage(manager, locale, args)); + public Component translateAndTransform(String locale, TranslatableMessage message, Object... args) { + return new TextComponent(message.translateMessage(manager, locale, args)); } public FabricServerAudiences getAdventure() { diff --git a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java index 69298f4d..ba780c13 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java @@ -7,8 +7,8 @@ import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; @@ -19,7 +19,7 @@ import java.util.UUID; public class FabricUserAudience implements UserAudience, ForwardingAudience.Single { private final UUID uuid; private final String locale; - private final ServerCommandSource source; + private final CommandSourceStack source; private final FabricCommandUtil commandUtil; @Override @@ -38,7 +38,7 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing return ""; } - return source.getName(); + return source.getTextName(); } @Override @@ -47,7 +47,7 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing } @Override - public @NonNull ServerCommandSource source() { + public @NonNull CommandSourceStack source() { return source; } @@ -68,8 +68,8 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing @Override public void disconnect(@NonNull Component reason) { - if (source.getEntity() instanceof ServerPlayerEntity) { - ((ServerPlayerEntity) source.getEntity()).networkHandler.disconnect( + if (source.getEntity() instanceof ServerPlayer) { + ((ServerPlayer) source.getEntity()).connection.disconnect( commandUtil.getAdventure().toNative(reason) ); } @@ -77,8 +77,8 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing @Override public void disconnect(TranslatableMessage message, Object... args) { - if (source.getEntity() instanceof ServerPlayerEntity) { - ((ServerPlayerEntity) source.getEntity()).networkHandler.disconnect( + if (source.getEntity() instanceof ServerPlayer) { + ((ServerPlayer) source.getEntity()).connection.disconnect( commandUtil.translateAndTransform(this.locale, message, args) ); } @@ -95,7 +95,7 @@ public class FabricUserAudience implements UserAudience, ForwardingAudience.Sing String name, UUID uuid, String locale, - ServerCommandSource source, + CommandSourceStack source, FabricCommandUtil commandUtil, boolean online) { super(uuid, locale, source, commandUtil); diff --git a/src/main/resources/floodgate.accesswidener b/src/main/resources/floodgate.accesswidener index 932b0de1..4e7dcb8e 100644 --- a/src/main/resources/floodgate.accesswidener +++ b/src/main/resources/floodgate.accesswidener @@ -1,4 +1,4 @@ accessWidener v1 named # To change login state -accessible class net/minecraft/server/network/ServerLoginNetworkHandler$State \ No newline at end of file +accessible class net/minecraft/server/network/ServerLoginPacketListenerImpl$State \ No newline at end of file diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index a19fd613..2b61e57f 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -4,9 +4,9 @@ "package": "org.geysermc.floodgate.mixin", "compatibilityLevel": "JAVA_16", "mixins": [ - "ClientConnectionMixin", - "ServerLoginNetworkHandlerMixin", - "ServerNetworkIoMixin", + "ConnectionMixin", + "ServerLoginPacketListenerImplMixin", + "ServerConnectionListenerMixin", "ClientIntentionPacketMixin" ], "injectors": { From 477096c493c556eebc5cac2b2e821fe191ea6abe Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 1 Dec 2021 22:29:23 -0500 Subject: [PATCH 25/87] Update to 1.18 --- build.gradle | 15 +- gradle.properties | 7 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradlew | 269 ++++++++++++++++++------------ 4 files changed, 169 insertions(+), 122 deletions(-) diff --git a/build.gradle b/build.gradle index 794aea68..e98c0825 100644 --- a/build.gradle +++ b/build.gradle @@ -2,13 +2,13 @@ import net.fabricmc.loom.task.RemapJarTask plugins { id 'com.github.johnrengelman.shadow' version '7.0.0' - id 'fabric-loom' version '0.8-SNAPSHOT' + id 'fabric-loom' version '0.10-SNAPSHOT' id 'java' id 'maven-publish' } -sourceCompatibility = JavaVersion.VERSION_16 -targetCompatibility = JavaVersion.VERSION_16 +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 archivesBaseName = project.archives_base_name version = project.mod_version @@ -37,11 +37,11 @@ dependencies { exclude group: 'com.google.code.gson', module: "gson" } - include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0-SNAPSHOT') { - because "Commands library implementation for Fabric - only supported on the snapshot" + include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0') { + because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:4.1.0-SNAPSHOT') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.0.0') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included @@ -55,12 +55,11 @@ dependencies { repositories { //mavenLocal() - // For Cloud snapshots maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } maven { - url = "https://repo.incendo.org/content/repositories/snapshots" + url = "https://repo.incendo.org/content/repositories/releases" } // Standard OpenCollab repositories maven { diff --git a/gradle.properties b/gradle.properties index 8e7bf696..89e73792 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,15 +2,14 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.17.1 -yarn_mappings=1.17.1+build.14 -loader_version=0.11.6 +minecraft_version=1.18 +loader_version=0.12.6 # Mod Properties mod_version=2.1.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.37.0+1.17 +fabric_version=0.43.1+1.18 # Our stuff lombok_version=1.18.20 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradlew b/gradlew index 4f906e0c..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # 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"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + 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. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# 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. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" From 7be00026962c0b49f5b7b4a7e5b0673b0d8d14f5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 1 Dec 2021 22:34:11 -0500 Subject: [PATCH 26/87] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d8dcc0e1..41833f05 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent any tools { gradle 'Gradle 7' - jdk 'Java 16' + jdk 'Java 17' } parameters { @@ -16,7 +16,7 @@ pipeline { stages { stage ('Build') { steps { - sh 'gradle clean build --refresh-dependencies' + sh './gradlew clean build --refresh-dependencies' } post { success { From 8579048dccf9eeda823a9947f9eb45d72c120211 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 5 Dec 2021 11:57:35 -0500 Subject: [PATCH 27/87] Remove mention of 2.0 Floodgate --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e01af311..15381f41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ # Floodgate-Fabric Fabric port of the hybrid mode plugin to allow for connections from Geyser to join online mode servers. -This is a Floodgate 2.0 port; you need to download the Floodgate 2.0 equivalent of your Geyser platform in order to use it. - Download: https://ci.opencollab.dev/job/GeyserMC/job/Floodgate-Fabric/job/master/ From cbafcb4030e67a514c52600eea2476c848982d29 Mon Sep 17 00:00:00 2001 From: Gadget <94780999+Gadget64@users.noreply.github.com> Date: Thu, 23 Dec 2021 05:49:30 +0100 Subject: [PATCH 28/87] Disable platform on shutdown (#40) --- src/main/java/org/geysermc/floodgate/FabricMod.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 5085f596..6b2a3bd0 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -45,5 +45,9 @@ public class FabricMod implements ModInitializer { injector.getInstance(FloodgateLogger.class) .translatedInfo("floodgate.core.finish", endCtm - ctm); }); + + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> { + platform.disable(); + }); } } From f7272e403cc45e46d31b7664e1bb89b6f4e23456 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:26:04 -0500 Subject: [PATCH 29/87] Update Floodgate-Fabric for 1.18.2 --- build.gradle | 3 ++- gradle.properties | 2 +- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index e98c0825..58fb7a34 100644 --- a/build.gradle +++ b/build.gradle @@ -35,13 +35,14 @@ dependencies { shadow("org.geysermc.floodgate:common:${project.mod_version}") { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" + exclude group: 'net.kyori', module: '*' // Let Adventure-Platform provide its desired Adventure version } include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0') { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.0.0') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.2.0') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included diff --git a/gradle.properties b/gradle.properties index 89e73792..4d8c440b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.18 +minecraft_version=1.18.2 loader_version=0.12.6 # Mod Properties mod_version=2.1.0-SNAPSHOT diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3cea4ae6..dd7781f5 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,6 +26,6 @@ "depends": { "fabricloader": ">=0.11.3", "fabric": "*", - "minecraft": ">=1.17.1" + "minecraft": ">=1.18.2" } } From 986b8b5303f9da7b1ed5453a8cff1ab23608e322 Mon Sep 17 00:00:00 2001 From: "Josiah (Gaming32) Glosson" Date: Fri, 25 Mar 2022 15:46:29 -0500 Subject: [PATCH 30/87] Exclude slf4j (#46) --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 58fb7a34..f1c51fc7 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,7 @@ dependencies { shadow("org.geysermc.floodgate:common:${project.mod_version}") { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" + exclude group: 'org.slf4j', module: "slf4j-api" exclude group: 'net.kyori', module: '*' // Let Adventure-Platform provide its desired Adventure version } From 1fa1270dc1dca0874f823a5ea210220e864eb02a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 25 Mar 2022 19:26:34 -0400 Subject: [PATCH 31/87] Attempt to fix building? --- Jenkinsfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 41833f05..526b3870 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -39,14 +39,15 @@ pipeline { ) rtGradleResolver( id: "GRADLE_RESOLVER", - serverId: "opencollab-artifactory", + serverId: "opencollab-artifactory" ) - rtGradleRun ( - usesPlugin: false, + rtGradleRun( + usesPlugin: true, tool: 'Gradle 7', rootDir: "", - buildFile: 'build.gradle', - tasks: 'build artifactoryPublish', + useWrapper: true, + buildFile: 'build.gradle.kts', + tasks: 'artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" ) From 3795a11581e72bdd306426b6348772fdb552bbc0 Mon Sep 17 00:00:00 2001 From: sschr15 Date: Thu, 14 Apr 2022 14:44:08 -0500 Subject: [PATCH 32/87] Remove maven that times out (#47) --- build.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/build.gradle b/build.gradle index f1c51fc7..893142a0 100644 --- a/build.gradle +++ b/build.gradle @@ -60,9 +60,6 @@ repositories { maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } - maven { - url = "https://repo.incendo.org/content/repositories/releases" - } // Standard OpenCollab repositories maven { name = 'opencollab-release-repo' From 6c6a9e0255c4d280647f776497b5cadc4d1d72d5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 4 May 2022 23:05:58 -0400 Subject: [PATCH 33/87] Update Adventure-Fabric for Fabric Loader compatibility Fixes #49 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 893142a0..610d1979 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ dependencies { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.2.0') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.2.1') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included From dbbb2b2ed9ebf9b59e65d869ffb03e07571d8d13 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 17 May 2022 11:12:19 -0400 Subject: [PATCH 34/87] Jenkinsfile: we don't use a Kotlin buildscript --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 526b3870..d6eb8206 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -46,7 +46,7 @@ pipeline { tool: 'Gradle 7', rootDir: "", useWrapper: true, - buildFile: 'build.gradle.kts', + buildFile: 'build.gradle', tasks: 'artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" From 5d5864db5a222b2dd72fbd5e2dbc6b44e532f462 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 17 May 2022 11:17:43 -0400 Subject: [PATCH 35/87] Jenkinsfile: wait why are we trying to deploy anyway? --- Jenkinsfile | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d6eb8206..667df30e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,38 +24,6 @@ pipeline { } } } - - stage ('Deploy') { - when { - branch "master" - } - - steps { - rtGradleDeployer( - id: "GRADLE_DEPLOYER", - serverId: "opencollab-artifactory", - releaseRepo: "maven-releases", - snapshotRepo: "maven-snapshots" - ) - rtGradleResolver( - id: "GRADLE_RESOLVER", - serverId: "opencollab-artifactory" - ) - rtGradleRun( - usesPlugin: true, - tool: 'Gradle 7', - rootDir: "", - useWrapper: true, - buildFile: 'build.gradle', - tasks: 'artifactoryPublish', - deployerId: "GRADLE_DEPLOYER", - resolverId: "GRADLE_RESOLVER" - ) - rtPublishBuildInfo( - serverId: "opencollab-artifactory" - ) - } - } } post { From 7482ac791b76b8299d036b369b6f0d97fd259cb6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 14 Jun 2022 16:09:01 -0400 Subject: [PATCH 36/87] Update for 1.19 With thanks to Kastle. --- build.gradle | 40 ++-- gradle.properties | 10 +- .../org/geysermc/floodgate/FabricMod.java | 7 +- .../floodgate/addon/data/FabricDataAddon.java | 2 +- .../listener/FabricEventListener.java | 2 - .../floodgate/mixin/ChunkMapMixin.java | 12 ++ .../floodgate/module/FabricCommandModule.java | 2 +- .../module/FabricPlatformModule.java | 9 +- .../pluginmessage/FabricSkinApplier.java | 71 ++----- .../floodgate/util/FabricCommandUtil.java | 180 +++++------------- .../floodgate/util/FabricPlatformUtils.java | 28 +++ .../floodgate/util/FabricUserAudience.java | 116 ----------- src/main/resources/fabric.mod.json | 4 +- src/main/resources/floodgate.accesswidener | 4 +- src/main/resources/floodgate.mixins.json | 5 +- 15 files changed, 138 insertions(+), 354 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java create mode 100644 src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java delete mode 100644 src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java diff --git a/build.gradle b/build.gradle index 610d1979..bfa7224f 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ import net.fabricmc.loom.task.RemapJarTask plugins { id 'com.github.johnrengelman.shadow' version '7.0.0' - id 'fabric-loom' version '0.10-SNAPSHOT' + id 'fabric-loom' version '0.12-SNAPSHOT' id 'java' id 'maven-publish' } @@ -14,8 +14,8 @@ archivesBaseName = project.archives_base_name version = project.mod_version group = project.maven_group -minecraft { - accessWidener = file("src/main/resources/floodgate.accesswidener") +loom { + accessWidenerPath = file("src/main/resources/floodgate.accesswidener") } dependencies { @@ -31,19 +31,20 @@ dependencies { // You may need to force-disable transitiveness on them. // Base Floodgate - implementation("org.geysermc.floodgate:common:${project.mod_version}") - shadow("org.geysermc.floodgate:common:${project.mod_version}") { + implementation("org.geysermc.floodgate:core:${project.mod_version}") + shadow("org.geysermc.floodgate:core:${project.mod_version}") { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j', module: "slf4j-api" exclude group: 'net.kyori', module: '*' // Let Adventure-Platform provide its desired Adventure version + exclude group: 'it.unimi.dsi.fastutil', module: "*" } - include(modImplementation('cloud.commandframework:cloud-fabric:1.5.0') { + include(modImplementation('cloud.commandframework:cloud-fabric:1.7.0-SNAPSHOT') { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.2.1') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.4.0-SNAPSHOT') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included @@ -60,6 +61,12 @@ repositories { maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } + // specifically for adventure-platform-fabric:5.4.0-SNAPSHOT + maven { + name = "sonatype-oss-snapshots1" + url = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + mavenContent { snapshotsOnly() } + } // Standard OpenCollab repositories maven { name = 'opencollab-release-repo' @@ -96,29 +103,14 @@ task sourcesJar(type: Jar, dependsOn: classes) { } shadowJar { -// dependencies { -// exclude('net.fabricmc:.*') -// //include(dependency('org.geysermc.floodgate:.*')) -// //include(dependency("org.geysermc.cumulus:.*")) -// //include(dependency('org.geysermc:.*')) -// exclude '/mappings/*' -// } configurations = [project.configurations.shadow] - //dependencies { - //exclude(dependency('cloud.commandframework:cloud-fabric:.*')) - //exclude(dependency('net.kyori:adventure-platform-fabric:.*')) - //} - - //relocate 'net.kyori', 'org.geysermc.floodgate.relocations.kyori' - //relocate 'cloud.commandframework', 'org.geysermc.floodgate.relocations.cloud' - relocate 'it.unimi.dsi.fastutil', 'org.geysermc.floodgate.relocations.fastutil' } task remappedShadowJar(type: RemapJarTask) { dependsOn tasks.shadowJar - input = tasks.shadowJar.archivePath + input = tasks.shadowJar.archiveFile addNestedDependencies = true - remapAccessWidener = true // Required for our access widener changes to go into effect and not crash on startup + //remapAccessWidener = true // Required for our access widener changes to go into effect and not crash on startup archiveName = "floodgate-fabric.jar" } diff --git a/gradle.properties b/gradle.properties index 4d8c440b..0c43ff6a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,15 @@ # Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G +org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.18.2 -loader_version=0.12.6 +minecraft_version=1.19 +loader_version=0.14.6 # Mod Properties -mod_version=2.1.0-SNAPSHOT +mod_version=2.2.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.43.1+1.18 +fabric_version=0.55.3+1.19 # Our stuff lombok_version=1.18.20 diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 6b2a3bd0..91a51de0 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -6,11 +6,11 @@ import com.google.inject.Injector; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; -import net.kyori.adventure.platform.fabric.FabricServerAudiences; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.module.*; import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; import org.geysermc.floodgate.util.FabricCommandUtil; +import org.geysermc.floodgate.util.FabricPlatformUtils; public class FabricMod implements ModInitializer { @Override @@ -29,12 +29,11 @@ public class FabricMod implements ModInitializer { ServerLifecycleEvents.SERVER_STARTED.register((server) -> { long ctm = System.currentTimeMillis(); - FabricServerAudiences adventure = FabricServerAudiences.of(server); - // Stupid hack, see the class for more information // This can probably be Guice-i-fied but that is beyond me - FabricCommandUtil.setLaterVariables(server, adventure); + FabricCommandUtil.setServer(server); FabricSkinApplier.setServer(server); + FabricPlatformUtils.setServer(server); platform.enable( new FabricAddonModule(), diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java index 6cab89d5..ca93aae4 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java @@ -41,7 +41,7 @@ public final class FabricDataAddon implements InjectorAddon { @Override public void onChannelClosed(Channel channel) { FloodgatePlayer player = channel.attr(playerAttribute).get(); - if (player != null && api.removePlayer(player)) { + if (player != null && api.setPendingRemove(player)) { logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername()); } } diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java index bb5cbe12..b94e1d30 100644 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java +++ b/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java @@ -7,7 +7,6 @@ import net.minecraft.server.network.ServerGamePacketListenerImpl; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.player.FloodgatePlayerImpl; import org.geysermc.floodgate.util.LanguageManager; public final class FabricEventListener { @@ -18,7 +17,6 @@ public final class FabricEventListener { public void onPlayerJoin(ServerGamePacketListenerImpl networkHandler, PacketSender packetSender, MinecraftServer server) { FloodgatePlayer player = api.getPlayer(networkHandler.player.getUUID()); if (player != null) { - player.as(FloodgatePlayerImpl.class).setLogin(false); logger.translatedInfo( "floodgate.ingame.login_name", player.getCorrectUsername(), player.getCorrectUniqueId() diff --git a/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java new file mode 100644 index 00000000..3805dbb5 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java @@ -0,0 +1,12 @@ +package org.geysermc.floodgate.mixin; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.server.level.ChunkMap; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ChunkMap.class) +public interface ChunkMapMixin { + @Accessor("entityMap") + Int2ObjectMap getEntityMap(); +} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java index 3d824010..c6730278 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java @@ -19,7 +19,7 @@ public final class FabricCommandModule extends CommandModule { public CommandManager commandManager(CommandUtil commandUtil) { FabricCommandManager commandManager = new FabricServerCommandManager<>( CommandExecutionCoordinator.simpleCoordinator(), - commandUtil::getAudience, + commandUtil::getUserAudience, audience -> (CommandSourceStack) audience.source() ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index 87a7eea3..3cc792d7 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -5,6 +5,7 @@ import org.geysermc.floodgate.listener.FabricEventListener; import org.geysermc.floodgate.listener.FabricEventRegistration; import org.geysermc.floodgate.logger.Log4jFloodgateLogger; import org.geysermc.floodgate.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.platform.util.PlatformUtils; import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; import org.geysermc.floodgate.util.FabricCommandUtil; import com.google.inject.AbstractModule; @@ -18,11 +19,17 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.skin.SkinApplier; +import org.geysermc.floodgate.util.FabricPlatformUtils; import org.geysermc.floodgate.util.LanguageManager; @RequiredArgsConstructor public final class FabricPlatformModule extends AbstractModule { + @Override + protected void configure() { + bind(PlatformUtils.class).to(FabricPlatformUtils.class); + } + @Provides @Singleton public FloodgateLogger floodgateLogger(LanguageManager languageManager) { @@ -35,7 +42,7 @@ public final class FabricPlatformModule extends AbstractModule { FloodgateApi api, FloodgateLogger logger, LanguageManager languageManager) { - return new FabricCommandUtil(api, logger, languageManager); + return new FabricCommandUtil(languageManager, api, logger); } @Provides diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index b38be056..c0fa023a 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -1,26 +1,17 @@ package org.geysermc.floodgate.pluginmessage; -import com.google.common.collect.Lists; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; -import com.mojang.datafixers.util.Pair; -import lombok.RequiredArgsConstructor; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.*; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.ai.attributes.AttributeInstance; -import net.minecraft.world.item.ItemStack; import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.mixin.ChunkMapMixin; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinData; -import java.util.Collection; -import java.util.List; - -@RequiredArgsConstructor public final class FabricSkinApplier implements SkinApplier { // See FabricCommandUtil private static MinecraftServer SERVER; @@ -40,60 +31,20 @@ public final class FabricSkinApplier implements SkinApplier { properties.removeAll("textures"); properties.put("textures", new Property("textures", skinData.getValue(), skinData.getSignature())); - // Skin is applied - now it's time to refresh the player for everyone. Oof. + ChunkMap tracker = ((ServerLevel) bedrockPlayer.level).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entry = ((ChunkMapMixin) tracker).getEntityMap().get(bedrockPlayer.getId()); + entry.broadcastRemoved(); + + // Skin is applied - now it's time to refresh the player for everyone. for (ServerPlayer otherPlayer : SERVER.getPlayerList().getPlayers()) { if (otherPlayer == bedrockPlayer) { continue; } - boolean loadedInWorld = otherPlayer.getCommandSenderWorld().getEntity(bedrockPlayer.getId()) != null; - if (loadedInWorld) { - // Player is loaded in this world - otherPlayer.connection.send(new ClientboundRemoveEntitiesPacket(bedrockPlayer.getId())); - } otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, bedrockPlayer)); - otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, bedrockPlayer)); - if (loadedInWorld) { - // Copied from EntityTrackerEntry - Packet spawnPacket = bedrockPlayer.getAddEntityPacket(); - otherPlayer.connection.send(spawnPacket); - if (!bedrockPlayer.getEntityData().isEmpty()) { - otherPlayer.connection.send(new ClientboundSetEntityDataPacket(bedrockPlayer.getId(), bedrockPlayer.getEntityData(), true)); - } - - Collection collection = bedrockPlayer.getAttributes().getDirtyAttributes(); - if (!collection.isEmpty()) { - otherPlayer.connection.send(new ClientboundUpdateAttributesPacket(bedrockPlayer.getId(), collection)); - } - - otherPlayer.connection.send(new ClientboundSetEntityMotionPacket(bedrockPlayer.getId(), bedrockPlayer.getDeltaMovement())); - - List> equipmentList = Lists.newArrayList(); - EquipmentSlot[] slots = EquipmentSlot.values(); - - for (EquipmentSlot equipmentSlot : slots) { - ItemStack itemStack = bedrockPlayer.getItemBySlot(equipmentSlot); - if (!itemStack.isEmpty()) { - equipmentList.add(Pair.of(equipmentSlot, itemStack.copy())); - } - } - - if (!equipmentList.isEmpty()) { - otherPlayer.connection.send(new ClientboundSetEquipmentPacket(bedrockPlayer.getId(), equipmentList)); - } - - for (MobEffectInstance mobEffectInstance : bedrockPlayer.getActiveEffects()) { - otherPlayer.connection.send(new ClientboundUpdateMobEffectPacket(bedrockPlayer.getId(), mobEffectInstance)); - } - - if (!bedrockPlayer.getPassengers().isEmpty()) { - otherPlayer.connection.send(new ClientboundSetPassengersPacket(bedrockPlayer)); - } - - if (bedrockPlayer.getVehicle() != null) { - otherPlayer.connection.send(new ClientboundSetPassengersPacket(bedrockPlayer.getVehicle())); - } + if (bedrockPlayer.level == otherPlayer.level) { + entry.updatePlayer(otherPlayer); } } }); diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 29758e42..a976bca6 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -1,15 +1,9 @@ package org.geysermc.floodgate.util; import com.mojang.authlib.GameProfile; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import me.lucko.fabric.api.permissions.v0.Permissions; -import net.kyori.adventure.platform.fabric.FabricServerAudiences; -import net.kyori.adventure.platform.fabric.PlayerLocales; import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TextComponent; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.UserWhiteListEntry; @@ -17,110 +11,66 @@ import net.minecraft.world.entity.Entity; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.UserAudienceArgument; import java.util.*; -@RequiredArgsConstructor -public final class FabricCommandUtil implements CommandUtil { +public final class FabricCommandUtil extends CommandUtil { // Static because commands *need* to be initialized before the server is available // Otherwise it would be a class variable private static MinecraftServer SERVER; - // This one also requires the server so it's bundled in - private static FabricServerAudiences ADVENTURE; - @Getter private final FloodgateApi api; - @Getter private final FloodgateLogger logger; - @Getter private final LanguageManager manager; + private final FloodgateLogger logger; + private UserAudience console; - @Override - public @NonNull UserAudience getAudience(@NonNull Object source) { - if (!(source instanceof CommandSourceStack commandSource)) { - throw new RuntimeException(); - } - - if (commandSource.getEntity() instanceof ServerPlayer) { - return getAudience0((ServerPlayer) commandSource.getEntity()); - } - - return new FabricUserAudience(null, manager.getDefaultLocale(), commandSource, this); + public FabricCommandUtil(LanguageManager manager, FloodgateApi api, FloodgateLogger logger) { + super(manager, api); + this.logger = logger; } @Override - public UserAudience getAudienceByUuid(@NonNull UUID uuid) { + public UserAudience getUserAudience(final @NonNull Object sourceObj) { + if (!(sourceObj instanceof CommandSourceStack stack)) { + throw new IllegalArgumentException(); + } + if (stack.getEntity() == null) { + if (console != null) { + return console; + } + return console = new UserAudience.ConsoleAudience(stack, this); + } + ServerPlayer player = stack.getPlayer(); + //Locale locale = PlayerLocales.locale(player); + return new UserAudience.PlayerAudience(player.getUUID(), player.getGameProfile().getName(), "en_US", + stack, this, true); + } + + @Override + protected String getUsernameFromSource(@NonNull Object source) { + return ((ServerPlayer) source).getGameProfile().getName(); + } + + @Override + protected UUID getUuidFromSource(@NonNull Object source) { + return ((ServerPlayer) source).getUUID(); + } + + @Override + public Object getPlayerByUuid(@NonNull UUID uuid) { ServerPlayer player = SERVER.getPlayerList().getPlayer(uuid); - if (player != null) { - return getAudience0(player); - } - return getOfflineAudienceByUuid(uuid); + return player != null ? player : uuid; } @Override - public UserAudience getAudienceByUsername(@NonNull String username) { + public Object getPlayerByUsername(@NonNull String username) { ServerPlayer player = SERVER.getPlayerList().getPlayerByName(username); - if (player != null) { - return getAudience0(player); - } - return getOfflineAudienceByUsername(username); - } - - private FabricUserAudience getAudience0(ServerPlayer player) { - // Apparently can be null even if Javadocs say otherwise - Locale locale = PlayerLocales.locale(player); - return new FabricUserAudience.NamedFabricUserAudience( - player.getName().getString(), - player.getUUID(), locale != null ? - locale.getLanguage().toLowerCase(Locale.ROOT) + "_" + locale.getCountry().toUpperCase(Locale.ROOT) : - manager.getDefaultLocale(), player.createCommandSourceStack(), this, true); + return player != null ? player : username; } @Override - public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { - return new FabricUserAudience(uuid, null, null, this); - } - - @Override - public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { - UUID uuid = null; - Optional profile = SERVER.getProfileCache().get(username); - if (profile.isPresent()) { - uuid = profile.get().getId(); - } - return new FabricUserAudience.NamedFabricUserAudience(username, uuid, username, null, this, false); - } - - @Override - public @NonNull Collection getOnlineUsernames(UserAudienceArgument.@NonNull PlayerType limitTo) { - List players = SERVER.getPlayerList().getPlayers(); - - Collection usernames = new ArrayList<>(); - switch (limitTo) { - case ALL_PLAYERS: - for (ServerPlayer player : players) { - usernames.add(player.getName().getString()); - } - break; - case ONLY_JAVA: - for (ServerPlayer player : players) { - if (!api.isFloodgatePlayer(player.getUUID())) { - usernames.add(player.getName().getString()); - } - } - break; - case ONLY_BEDROCK: - for (ServerPlayer player : players) { - if (api.isFloodgatePlayer(player.getUUID())) { - usernames.add(player.getName().getString()); - } - } - break; - default: - throw new IllegalStateException("Unknown PlayerType"); - } - return usernames; + protected Collection getOnlinePlayers() { + return SERVER.getPlayerList().getPlayers(); } @Override @@ -128,35 +78,12 @@ public final class FabricCommandUtil implements CommandUtil { return Permissions.check((Entity) player, permission); } - @Override - public Collection getOnlinePlayersWithPermission(String permission) { - List players = new ArrayList<>(); - for (ServerPlayer player : SERVER.getPlayerList().getPlayers()) { - if (hasPermission(player, permission)) { - players.add(player); - } - } - return players; - } - - @Override - public void sendMessage(Object player, String locale, TranslatableMessage message, Object... args) { - CommandSourceStack commandSource = (CommandSourceStack) player; - if (commandSource.getEntity() instanceof ServerPlayer) { - SERVER.execute(() -> ((ServerPlayer) commandSource.getEntity()) - .displayClientMessage(translateAndTransform(locale, message, args), false)); - } else { - // Console? - logger.info(message.translateMessage(manager, locale, args)); - } - } - @Override public void sendMessage(Object target, String message) { CommandSourceStack commandSource = (CommandSourceStack) target; if (commandSource.getEntity() instanceof ServerPlayer) { SERVER.execute(() -> ((ServerPlayer) commandSource.getEntity()) - .displayClientMessage(new TextComponent(message), false)); + .displayClientMessage(Component.literal(message), false)); } else { // Console? logger.info(message); @@ -164,8 +91,10 @@ public final class FabricCommandUtil implements CommandUtil { } @Override - public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { - getPlayer(player).connection.disconnect(translateAndTransform(locale, message, args)); + public void kickPlayer(Object o, String message) { + if (o instanceof ServerPlayer player) { + player.connection.disconnect(Component.literal(message)); + } } @Override @@ -182,26 +111,7 @@ public final class FabricCommandUtil implements CommandUtil { return true; } - private ServerPlayer getPlayer(Object instance) { - try { - CommandSourceStack source = (CommandSourceStack) instance; - return source.getPlayerOrException(); - } catch (ClassCastException | CommandSyntaxException exception) { - logger.error("Failed to cast {} as a player", instance.getClass().getName()); - throw new RuntimeException(); - } - } - - public Component translateAndTransform(String locale, TranslatableMessage message, Object... args) { - return new TextComponent(message.translateMessage(manager, locale, args)); - } - - public FabricServerAudiences getAdventure() { - return ADVENTURE; - } - - public static void setLaterVariables(MinecraftServer server, FabricServerAudiences adventure) { + public static void setServer(MinecraftServer server) { SERVER = server; - ADVENTURE = adventure; } } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java b/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java new file mode 100644 index 00000000..a355fe1f --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java @@ -0,0 +1,28 @@ +package org.geysermc.floodgate.util; + +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import org.geysermc.floodgate.platform.util.PlatformUtils; + +public class FabricPlatformUtils extends PlatformUtils { + private static MinecraftServer SERVER; + + @Override + public AuthType authType() { + return SERVER.usesAuthentication() ? AuthType.ONLINE : AuthType.OFFLINE; + } + + @Override + public String minecraftVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + @Override + public String serverImplementationName() { + return "Fabric"; + } + + public static void setServer(MinecraftServer server) { + SERVER = server; + } +} diff --git a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java b/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java deleted file mode 100644 index ba780c13..00000000 --- a/src/main/java/org/geysermc/floodgate/util/FabricUserAudience.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.geysermc.floodgate.util; - -import lombok.RequiredArgsConstructor; -import me.lucko.fabric.api.permissions.v0.Permissions; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.audience.ForwardingAudience; -import net.kyori.adventure.audience.MessageType; -import net.kyori.adventure.identity.Identity; -import net.kyori.adventure.text.Component; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.server.level.ServerPlayer; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.TranslatableMessage; -import org.geysermc.floodgate.player.UserAudience; - -import java.util.UUID; - -@RequiredArgsConstructor -public class FabricUserAudience implements UserAudience, ForwardingAudience.Single { - private final UUID uuid; - private final String locale; - private final CommandSourceStack source; - private final FabricCommandUtil commandUtil; - - @Override - public @NonNull Audience audience() { - return commandUtil.getAdventure().audience(source); - } - - @Override - public @NonNull UUID uuid() { - return uuid; - } - - @Override - public @NonNull String username() { - if (source == null) { - return ""; - } - - return source.getTextName(); - } - - @Override - public @NonNull String locale() { - return locale; - } - - @Override - public @NonNull CommandSourceStack source() { - return source; - } - - @Override - public boolean hasPermission(@NonNull String permission) { - return Permissions.check(source, permission); - } - - @Override - public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { - commandUtil.getAdventure().audience(this.source).sendMessage(message); - } - - @Override - public void sendMessage(TranslatableMessage message, Object... args) { - commandUtil.sendMessage(this.source, this.locale, message, args); - } - - @Override - public void disconnect(@NonNull Component reason) { - if (source.getEntity() instanceof ServerPlayer) { - ((ServerPlayer) source.getEntity()).connection.disconnect( - commandUtil.getAdventure().toNative(reason) - ); - } - } - - @Override - public void disconnect(TranslatableMessage message, Object... args) { - if (source.getEntity() instanceof ServerPlayer) { - ((ServerPlayer) source.getEntity()).connection.disconnect( - commandUtil.translateAndTransform(this.locale, message, args) - ); - } - } - - /** - * Used whenever a name has been explicitly defined for us. Most helpful in offline players. - */ - public static final class NamedFabricUserAudience extends FabricUserAudience implements PlayerAudience { - private final String name; - private final boolean online; - - public NamedFabricUserAudience( - String name, - UUID uuid, - String locale, - CommandSourceStack source, - FabricCommandUtil commandUtil, - boolean online) { - super(uuid, locale, source, commandUtil); - this.name = name; - this.online = online; - } - - @Override - public @NonNull String username() { - return name; - } - - @Override - public boolean online() { - return online; - } - } -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index dd7781f5..7f2a1fcd 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,8 +24,8 @@ "floodgate.mixins.json" ], "depends": { - "fabricloader": ">=0.11.3", + "fabricloader": ">=0.14.6", "fabric": "*", - "minecraft": ">=1.18.2" + "minecraft": ">=1.19" } } diff --git a/src/main/resources/floodgate.accesswidener b/src/main/resources/floodgate.accesswidener index 4e7dcb8e..cad9dbdc 100644 --- a/src/main/resources/floodgate.accesswidener +++ b/src/main/resources/floodgate.accesswidener @@ -1,4 +1,6 @@ accessWidener v1 named # To change login state -accessible class net/minecraft/server/network/ServerLoginPacketListenerImpl$State \ No newline at end of file +accessible class net/minecraft/server/network/ServerLoginPacketListenerImpl$State +# For player skin refreshing +accessible class net/minecraft/server/level/ChunkMap$TrackedEntity \ No newline at end of file diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index 2b61e57f..8d29fb4d 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -4,10 +4,11 @@ "package": "org.geysermc.floodgate.mixin", "compatibilityLevel": "JAVA_16", "mixins": [ + "ChunkMapMixin", + "ClientIntentionPacketMixin", "ConnectionMixin", - "ServerLoginPacketListenerImplMixin", "ServerConnectionListenerMixin", - "ClientIntentionPacketMixin" + "ServerLoginPacketListenerImplMixin" ], "injectors": { "defaultRequire": 1 From d52b33588ab367cc04c44356a651021ad9f538ef Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 15 Jun 2022 21:21:45 -0400 Subject: [PATCH 37/87] Fix linked player skins; clean up skin applying code Fixes #39 --- build.gradle | 1 - .../org/geysermc/floodgate/FabricMod.java | 7 +---- .../floodgate/MinecraftServerHolder.java | 20 +++++++++++++ .../addon/data/FabricDataHandler.java | 29 ++++++++++++++++-- .../module/FabricPlatformModule.java | 8 +++++ .../FabricPluginMessageRegistration.java | 17 +++++++++++ .../pluginmessage/FabricSkinApplier.java | 30 +++++++++++-------- .../floodgate/util/FabricCommandUtil.java | 22 +++++--------- .../floodgate/util/FabricPlatformUtils.java | 12 ++------ 9 files changed, 100 insertions(+), 46 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java create mode 100644 src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java diff --git a/build.gradle b/build.gradle index bfa7224f..7dd52402 100644 --- a/build.gradle +++ b/build.gradle @@ -110,7 +110,6 @@ task remappedShadowJar(type: RemapJarTask) { dependsOn tasks.shadowJar input = tasks.shadowJar.archiveFile addNestedDependencies = true - //remapAccessWidener = true // Required for our access widener changes to go into effect and not crash on startup archiveName = "floodgate-fabric.jar" } diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 91a51de0..558f8ff0 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -8,9 +8,6 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.module.*; -import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; -import org.geysermc.floodgate.util.FabricCommandUtil; -import org.geysermc.floodgate.util.FabricPlatformUtils; public class FabricMod implements ModInitializer { @Override @@ -31,9 +28,7 @@ public class FabricMod implements ModInitializer { // Stupid hack, see the class for more information // This can probably be Guice-i-fied but that is beyond me - FabricCommandUtil.setServer(server); - FabricSkinApplier.setServer(server); - FabricPlatformUtils.setServer(server); + MinecraftServerHolder.set(server); platform.enable( new FabricAddonModule(), diff --git a/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java b/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java new file mode 100644 index 00000000..b1f0c9e6 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java @@ -0,0 +1,20 @@ +package org.geysermc.floodgate; + +import net.minecraft.server.MinecraftServer; + +public final class MinecraftServerHolder { + // Static because commands *need* to be initialized before the server is available + // Otherwise it would be a class variable + private static MinecraftServer INSTANCE; + + public static MinecraftServer get() { + return INSTANCE; + } + + static void set(MinecraftServer instance) { + INSTANCE = instance; + } + + private MinecraftServerHolder() { + } +} diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 71a5aa2e..32a646a0 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -1,12 +1,15 @@ package org.geysermc.floodgate.addon.data; +import com.mojang.logging.LogUtils; import io.netty.channel.Channel; import io.netty.util.AttributeKey; +import net.minecraft.DefaultUncaughtExceptionHandler; import net.minecraft.network.Connection; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import net.minecraft.network.protocol.login.ServerboundHelloPacket; import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.mixin.ConnectionMixin; import org.geysermc.floodgate.mixin.ClientIntentionPacketMixin; @@ -17,10 +20,13 @@ import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; +import org.slf4j.Logger; import java.net.InetSocketAddress; public final class FabricDataHandler extends CommonDataHandler { + private static final Logger LOGGER = LogUtils.getLogger(); + private final FloodgateLogger logger; private Connection networkManager; private FloodgatePlayer player; @@ -95,8 +101,27 @@ public final class FabricDataHandler extends CommonDataHandler { GameProfile gameProfile = new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername()); - ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); - ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setLoginState(); + if (player.isLinked() && player.getCorrectUniqueId().version() == 4) { + Thread texturesThread = new Thread("Bedrock Linked Player Texture Download") { + @Override + public void run() { + try { + MinecraftServerHolder.get().getSessionService() + .fillProfileProperties(gameProfile, true); + } catch (Exception e) { + LOGGER.error("Unable to get Bedrock linked player textures for " + gameProfile.getName(), e); + } + ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()) + .setGameProfile(gameProfile); + ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setLoginState(); + } + }; + texturesThread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); + texturesThread.start(); + } else { + ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); + ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setLoginState(); + } ctx.pipeline().remove(this); return true; diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index 3cc792d7..56ada8dd 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -6,7 +6,9 @@ import org.geysermc.floodgate.listener.FabricEventRegistration; import org.geysermc.floodgate.logger.Log4jFloodgateLogger; import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.util.PlatformUtils; +import org.geysermc.floodgate.pluginmessage.FabricPluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; +import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; import org.geysermc.floodgate.util.FabricCommandUtil; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -85,6 +87,12 @@ public final class FabricPlatformModule extends AbstractModule { return "Fabric"; } + @Provides + @Singleton + public PluginMessageRegistration pluginMessageRegister() { + return new FabricPluginMessageRegistration(); + } + @Provides @Singleton public SkinApplier skinApplier() { diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java new file mode 100644 index 00000000..1d0e4be2 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java @@ -0,0 +1,17 @@ +package org.geysermc.floodgate.pluginmessage; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.resources.ResourceLocation; + +public class FabricPluginMessageRegistration implements PluginMessageRegistration { + @Override + public void register(PluginMessageChannel channel) { + ServerPlayNetworking.registerGlobalReceiver(new ResourceLocation(channel.getIdentifier()), + (server, player, handler, buf, responseSender) -> { + System.out.println("Received channel of " + channel.getIdentifier()); + byte[] bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + channel.handleServerCall(bytes, player.getUUID(), player.getGameProfile().getName()); + }); + } +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index c0fa023a..788102df 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -3,23 +3,23 @@ package org.geysermc.floodgate.pluginmessage; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.mixin.ChunkMapMixin; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinData; public final class FabricSkinApplier implements SkinApplier { - // See FabricCommandUtil - private static MinecraftServer SERVER; @Override public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { - SERVER.execute(() -> { - ServerPlayer bedrockPlayer = SERVER.getPlayerList().getPlayer(floodgatePlayer.getCorrectUniqueId()); + MinecraftServerHolder.get().execute(() -> { + System.out.println("Refreshing skins for " + floodgatePlayer); + ServerPlayer bedrockPlayer = MinecraftServerHolder.get().getPlayerList() + .getPlayer(floodgatePlayer.getCorrectUniqueId()); if (bedrockPlayer == null) { // Disconnected probably? return; @@ -33,24 +33,28 @@ public final class FabricSkinApplier implements SkinApplier { ChunkMap tracker = ((ServerLevel) bedrockPlayer.level).getChunkSource().chunkMap; ChunkMap.TrackedEntity entry = ((ChunkMapMixin) tracker).getEntityMap().get(bedrockPlayer.getId()); - entry.broadcastRemoved(); + + System.out.println("eeeee"); // Skin is applied - now it's time to refresh the player for everyone. - for (ServerPlayer otherPlayer : SERVER.getPlayerList().getPlayers()) { - if (otherPlayer == bedrockPlayer) { - continue; + for (ServerPlayer otherPlayer : MinecraftServerHolder.get().getPlayerList().getPlayers()) { + boolean samePlayer = otherPlayer == bedrockPlayer; + if (!samePlayer) { + // TrackedEntity#broadcastRemoved doesn't actually remove them from seenBy + entry.removePlayer(otherPlayer); } otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, bedrockPlayer)); otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, bedrockPlayer)); + if (samePlayer) { + continue; + } + if (bedrockPlayer.level == otherPlayer.level) { + System.out.println("Updating entry"); entry.updatePlayer(otherPlayer); } } }); } - - public static void setServer(MinecraftServer server) { - SERVER = server; - } } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index a976bca6..15086a70 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -4,11 +4,11 @@ import com.mojang.authlib.GameProfile; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.UserWhiteListEntry; import net.minecraft.world.entity.Entity; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; @@ -17,10 +17,6 @@ import org.geysermc.floodgate.player.UserAudience; import java.util.*; public final class FabricCommandUtil extends CommandUtil { - // Static because commands *need* to be initialized before the server is available - // Otherwise it would be a class variable - private static MinecraftServer SERVER; - private final FloodgateLogger logger; private UserAudience console; @@ -58,19 +54,19 @@ public final class FabricCommandUtil extends CommandUtil { @Override public Object getPlayerByUuid(@NonNull UUID uuid) { - ServerPlayer player = SERVER.getPlayerList().getPlayer(uuid); + ServerPlayer player = MinecraftServerHolder.get().getPlayerList().getPlayer(uuid); return player != null ? player : uuid; } @Override public Object getPlayerByUsername(@NonNull String username) { - ServerPlayer player = SERVER.getPlayerList().getPlayerByName(username); + ServerPlayer player = MinecraftServerHolder.get().getPlayerList().getPlayerByName(username); return player != null ? player : username; } @Override protected Collection getOnlinePlayers() { - return SERVER.getPlayerList().getPlayers(); + return MinecraftServerHolder.get().getPlayerList().getPlayers(); } @Override @@ -82,7 +78,7 @@ public final class FabricCommandUtil extends CommandUtil { public void sendMessage(Object target, String message) { CommandSourceStack commandSource = (CommandSourceStack) target; if (commandSource.getEntity() instanceof ServerPlayer) { - SERVER.execute(() -> ((ServerPlayer) commandSource.getEntity()) + MinecraftServerHolder.get().execute(() -> ((ServerPlayer) commandSource.getEntity()) .displayClientMessage(Component.literal(message), false)); } else { // Console? @@ -100,18 +96,14 @@ public final class FabricCommandUtil extends CommandUtil { @Override public boolean whitelistPlayer(UUID uuid, String username) { GameProfile profile = new GameProfile(uuid, username); - SERVER.getPlayerList().getWhiteList().add(new UserWhiteListEntry(profile)); + MinecraftServerHolder.get().getPlayerList().getWhiteList().add(new UserWhiteListEntry(profile)); return true; } @Override public boolean removePlayerFromWhitelist(UUID uuid, String username) { GameProfile profile = new GameProfile(uuid, username); - SERVER.getPlayerList().getWhiteList().remove(profile); + MinecraftServerHolder.get().getPlayerList().getWhiteList().remove(profile); return true; } - - public static void setServer(MinecraftServer server) { - SERVER = server; - } } diff --git a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java b/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java index a355fe1f..900ec2dc 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java @@ -1,15 +1,13 @@ package org.geysermc.floodgate.util; import net.minecraft.SharedConstants; -import net.minecraft.server.MinecraftServer; +import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.platform.util.PlatformUtils; public class FabricPlatformUtils extends PlatformUtils { - private static MinecraftServer SERVER; - @Override public AuthType authType() { - return SERVER.usesAuthentication() ? AuthType.ONLINE : AuthType.OFFLINE; + return MinecraftServerHolder.get().usesAuthentication() ? AuthType.ONLINE : AuthType.OFFLINE; } @Override @@ -19,10 +17,6 @@ public class FabricPlatformUtils extends PlatformUtils { @Override public String serverImplementationName() { - return "Fabric"; - } - - public static void setServer(MinecraftServer server) { - SERVER = server; + return MinecraftServerHolder.get().getServerModName(); } } From 61372d7967c58d3dede75b611c7e6c9e8c9ad0c9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 15 Jun 2022 21:23:05 -0400 Subject: [PATCH 38/87] oops --- .../pluginmessage/FabricPluginMessageRegistration.java | 1 - .../geysermc/floodgate/pluginmessage/FabricSkinApplier.java | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java index 1d0e4be2..09ab54c8 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java @@ -8,7 +8,6 @@ public class FabricPluginMessageRegistration implements PluginMessageRegistratio public void register(PluginMessageChannel channel) { ServerPlayNetworking.registerGlobalReceiver(new ResourceLocation(channel.getIdentifier()), (server, player, handler, buf, responseSender) -> { - System.out.println("Received channel of " + channel.getIdentifier()); byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); channel.handleServerCall(bytes, player.getUUID(), player.getGameProfile().getName()); diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 788102df..56264559 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -17,7 +17,6 @@ public final class FabricSkinApplier implements SkinApplier { @Override public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { MinecraftServerHolder.get().execute(() -> { - System.out.println("Refreshing skins for " + floodgatePlayer); ServerPlayer bedrockPlayer = MinecraftServerHolder.get().getPlayerList() .getPlayer(floodgatePlayer.getCorrectUniqueId()); if (bedrockPlayer == null) { @@ -33,9 +32,6 @@ public final class FabricSkinApplier implements SkinApplier { ChunkMap tracker = ((ServerLevel) bedrockPlayer.level).getChunkSource().chunkMap; ChunkMap.TrackedEntity entry = ((ChunkMapMixin) tracker).getEntityMap().get(bedrockPlayer.getId()); - - System.out.println("eeeee"); - // Skin is applied - now it's time to refresh the player for everyone. for (ServerPlayer otherPlayer : MinecraftServerHolder.get().getPlayerList().getPlayers()) { boolean samePlayer = otherPlayer == bedrockPlayer; @@ -51,7 +47,6 @@ public final class FabricSkinApplier implements SkinApplier { } if (bedrockPlayer.level == otherPlayer.level) { - System.out.println("Updating entry"); entry.updatePlayer(otherPlayer); } } From bb45676d89bb23bc76b7bb6eadd276f46b378cca Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:56:20 -0500 Subject: [PATCH 39/87] Update to 1.19.3 --- build.gradle | 4 ++-- gradle.properties | 6 +++--- .../floodgate/pluginmessage/FabricSkinApplier.java | 9 ++++++--- src/main/resources/fabric.mod.json | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 7dd52402..fa7f8359 100644 --- a/build.gradle +++ b/build.gradle @@ -40,11 +40,11 @@ dependencies { exclude group: 'it.unimi.dsi.fastutil', module: "*" } - include(modImplementation('cloud.commandframework:cloud-fabric:1.7.0-SNAPSHOT') { + include(modImplementation('cloud.commandframework:cloud-fabric:1.8.0-SNAPSHOT') { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.4.0-SNAPSHOT') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.6.0-SNAPSHOT') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included diff --git a/gradle.properties b/gradle.properties index 0c43ff6a..6f7b3a32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,14 +2,14 @@ org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.19 -loader_version=0.14.6 +minecraft_version=1.19.3 +loader_version=0.14.11 # Mod Properties mod_version=2.2.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.55.3+1.19 +fabric_version=0.68.1+1.19.3 # Our stuff lombok_version=1.18.20 diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 56264559..e7fd4163 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -2,7 +2,8 @@ package org.geysermc.floodgate.pluginmessage; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; -import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -12,6 +13,8 @@ import org.geysermc.floodgate.mixin.ChunkMapMixin; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinData; +import java.util.Collections; + public final class FabricSkinApplier implements SkinApplier { @Override @@ -40,8 +43,8 @@ public final class FabricSkinApplier implements SkinApplier { entry.removePlayer(otherPlayer); } - otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.REMOVE_PLAYER, bedrockPlayer)); - otherPlayer.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, bedrockPlayer)); + otherPlayer.connection.send(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(bedrockPlayer.getUUID()))); + otherPlayer.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, bedrockPlayer)); if (samePlayer) { continue; } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7f2a1fcd..454d7f8f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,6 +26,6 @@ "depends": { "fabricloader": ">=0.14.6", "fabric": "*", - "minecraft": ">=1.19" + "minecraft": ">=1.19.3" } } From e223ae3294f4e21bd347924d746f5c7184055161 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 19 Dec 2022 15:02:34 -0500 Subject: [PATCH 40/87] Apply additional Gradle changes from Konica --- build.gradle | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index fa7f8359..789a751c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ import net.fabricmc.loom.task.RemapJarTask plugins { id 'com.github.johnrengelman.shadow' version '7.0.0' - id 'fabric-loom' version '0.12-SNAPSHOT' + id 'fabric-loom' version '1.0-SNAPSHOT' id 'java' id 'maven-publish' } @@ -94,16 +94,21 @@ tasks.withType(JavaCompile) { options.encoding = "UTF-8" } -// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task -// if it is present. -// If you remove this task, sources will not be generated. -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" - from sourceSets.main.allSource +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() } shadowJar { configurations = [project.configurations.shadow] + relocate("org.bstats", "org.geysermc.floodgate.shadow.org.bstats") + + exclude { + FileTreeElement e -> + e.name.startsWith("com.google") && !e.name.startsWith("com.google.inject") // Guava and Gson + } } task remappedShadowJar(type: RemapJarTask) { @@ -124,19 +129,12 @@ artifacts { publishing { publications { mavenJava(MavenPublication) { - // add all the jars that should be included when publishing to maven - artifact(remapJar) { - builtBy remapJar - } - artifact(sourcesJar) { - builtBy remapSourcesJar - } + from components.java } } // select the repositories you want to publish to repositories { - // uncomment to publish to the local maven mavenLocal() } } From 232ccfa5f6644c227f9ff6206155161411fbc9be Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 19 Dec 2022 15:16:52 -0500 Subject: [PATCH 41/87] I still cannot get this to compile locally. Attempt three. --- build.gradle | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle b/build.gradle index 789a751c..879f3889 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,8 @@ dependencies { // Base Floodgate implementation("org.geysermc.floodgate:core:${project.mod_version}") shadow("org.geysermc.floodgate:core:${project.mod_version}") { + exclude group: "cloud.commandframework", module: "*" // Cloud is included jar-in-jar + exclude group: "org.geysermc.floodgate", module: "api" exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j', module: "slf4j-api" @@ -40,6 +42,12 @@ dependencies { exclude group: 'it.unimi.dsi.fastutil', module: "*" } + shadow(implementation("org.geysermc.floodgate:api:${project.mod_version}")) { + exclude group: 'com.google.guava', module: "guava" + exclude group: 'com.google.code.gson', module: "gson" + exclude group: "org.ow2.asm", module: "*" // From Events lib + } + include(modImplementation('cloud.commandframework:cloud-fabric:1.8.0-SNAPSHOT') { because "Commands library implementation for Fabric" }) From 9fe1431b4753456318babaf242a964161df48714 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 19 Dec 2022 18:18:55 -0500 Subject: [PATCH 42/87] Floodgate-Fabric on 1.19.3 actually works --- build.gradle | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 879f3889..fb5cbec7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,7 @@ import net.fabricmc.loom.task.RemapJarTask +import java.nio.file.FileSystems + plugins { id 'com.github.johnrengelman.shadow' version '7.0.0' id 'fabric-loom' version '1.0-SNAPSHOT' @@ -38,7 +40,6 @@ dependencies { exclude group: 'com.google.guava', module: "guava" exclude group: 'com.google.code.gson', module: "gson" exclude group: 'org.slf4j', module: "slf4j-api" - exclude group: 'net.kyori', module: '*' // Let Adventure-Platform provide its desired Adventure version exclude group: 'it.unimi.dsi.fastutil', module: "*" } @@ -111,12 +112,14 @@ java { shadowJar { configurations = [project.configurations.shadow] - relocate("org.bstats", "org.geysermc.floodgate.shadow.org.bstats") - - exclude { - FileTreeElement e -> - e.name.startsWith("com.google") && !e.name.startsWith("com.google.inject") // Guava and Gson - } + // TODO this is temporary until Floodgate's dev branch is merged + relocate("com.google.inject", "org.geysermc.floodgate.shadow.guice") + exclude([ + 'cloud/**', + 'com/google/common/**', 'com/google/errorprone/**', 'com/google/gson/**', 'com/google/j2objc/**', 'com/google/thirdparty/**', + 'it/unimi/**', + 'org/slf4j/**' + ]) } task remappedShadowJar(type: RemapJarTask) { From b20e118f3a6eb5dd3d3a3dc91185758bde8b71e6 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 19 Dec 2022 18:19:22 -0500 Subject: [PATCH 43/87] Unneeded import --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index fb5cbec7..99ac1591 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,5 @@ import net.fabricmc.loom.task.RemapJarTask -import java.nio.file.FileSystems - plugins { id 'com.github.johnrengelman.shadow' version '7.0.0' id 'fabric-loom' version '1.0-SNAPSHOT' From a1e9ff6f9cc2a4cdf8abc18b73d273ef0d77c909 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 21 Dec 2022 18:56:35 -0500 Subject: [PATCH 44/87] Should fix #73 --- .../org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index e7fd4163..7d5107e8 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -44,7 +44,7 @@ public final class FabricSkinApplier implements SkinApplier { } otherPlayer.connection.send(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(bedrockPlayer.getUUID()))); - otherPlayer.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, bedrockPlayer)); + otherPlayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(Collections.singletonList(bedrockPlayer))); if (samePlayer) { continue; } From def43a4b3f1fdd9701673116e5761d244bc3282a Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 12 Feb 2023 20:58:09 +0100 Subject: [PATCH 45/87] FabricProxyLite support (#80) * FabricProxyLite support - apply workaround of changing the limit of 255 to instead the Short.MAX_VALUE int, as also done by Paper/Spigot. --- .../addon/data/FabricDataHandler.java | 4 +- .../mixin/ClientIntentionPacketMixin.java | 14 +++--- .../ClientIntentionPacketMixinInterface.java | 14 ++++++ .../floodgate/util/MixinConfigPlugin.java | 47 +++++++++++++++++++ src/main/resources/floodgate.mixins.json | 2 + 5 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java create mode 100644 src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 32a646a0..704e00b9 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -12,7 +12,7 @@ import net.minecraft.server.network.ServerLoginPacketListenerImpl; import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.mixin.ConnectionMixin; -import org.geysermc.floodgate.mixin.ClientIntentionPacketMixin; +import org.geysermc.floodgate.mixin.ClientIntentionPacketMixinInterface; import org.geysermc.floodgate.mixin_interface.ServerLoginPacketListenerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; @@ -48,7 +48,7 @@ public final class FabricDataHandler extends CommonDataHandler { protected Object setHostname(Object handshakePacket, String hostname) { // While it would be ideal to simply create a new handshake packet, the packet constructor // does not allow us to set the protocol version - ((ClientIntentionPacketMixin) handshakePacket).setAddress(hostname); + ((ClientIntentionPacketMixinInterface) handshakePacket).setAddress(hostname); return handshakePacket; } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java index 8cef9aca..3b59a680 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java @@ -2,13 +2,13 @@ package org.geysermc.floodgate.mixin; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; @Mixin(ClientIntentionPacket.class) -public interface ClientIntentionPacketMixin { - - @Accessor("hostName") - @Mutable - void setAddress(String address); +public class ClientIntentionPacketMixin { + @ModifyConstant(method = "(Lnet/minecraft/network/FriendlyByteBuf;)V", constant = @Constant(intValue = 255)) + private int floodgate$setHandshakeLength(int defaultValue) { + return Short.MAX_VALUE; + } } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java new file mode 100644 index 00000000..8ffdcba3 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.mixin; + +import net.minecraft.network.protocol.handshake.ClientIntentionPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientIntentionPacket.class) +public interface ClientIntentionPacketMixinInterface { + + @Accessor("hostName") + @Mutable + void setAddress(String address); +} diff --git a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java b/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java new file mode 100644 index 00000000..ca0dc892 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java @@ -0,0 +1,47 @@ +package org.geysermc.floodgate.util; + +import net.fabricmc.loader.api.FabricLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +public class MixinConfigPlugin implements IMixinConfigPlugin { + + @Override + public void onLoad(String mixinPackage) { + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (mixinClassName.equals("org/geysermc/floodgate/mixin/ClientIntentionPacketMixin")) { + //returns true if fabricproxy-lite is present, therefore loading the mixin. If not present, the mixin will not be loaded. + return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); + } + return true; + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } +} diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index 8d29fb4d..d80a693b 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -5,11 +5,13 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "ChunkMapMixin", + "ClientIntentionPacketMixinInterface", "ClientIntentionPacketMixin", "ConnectionMixin", "ServerConnectionListenerMixin", "ServerLoginPacketListenerImplMixin" ], + "plugin": "org.geysermc.floodgate.util.MixinConfigPlugin", "injectors": { "defaultRequire": 1 } From 1371eaf0ae301543577c8a03c62f1952f140938c Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:09:12 -0500 Subject: [PATCH 46/87] Add PluginMessageModule and bind new FabricPluginMessageUtils (#82) --- .../org/geysermc/floodgate/FabricMod.java | 3 +- .../module/FabricPlatformModule.java | 8 ++++++ .../FabricPluginMessageUtils.java | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 558f8ff0..1eaab881 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -32,7 +32,8 @@ public class FabricMod implements ModInitializer { platform.enable( new FabricAddonModule(), - new FabricListenerModule() + new FabricListenerModule(), + new PluginMessageModule() ); long endCtm = System.currentTimeMillis(); diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index 56ada8dd..3071107c 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -5,8 +5,10 @@ import org.geysermc.floodgate.listener.FabricEventListener; import org.geysermc.floodgate.listener.FabricEventRegistration; import org.geysermc.floodgate.logger.Log4jFloodgateLogger; import org.geysermc.floodgate.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.platform.util.PlatformUtils; import org.geysermc.floodgate.pluginmessage.FabricPluginMessageRegistration; +import org.geysermc.floodgate.pluginmessage.FabricPluginMessageUtils; import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; import org.geysermc.floodgate.util.FabricCommandUtil; @@ -81,6 +83,12 @@ public final class FabricPlatformModule extends AbstractModule { return "packet_handler"; } + @Provides + @Singleton + public PluginMessageUtils pluginMessageUtils() { + return new FabricPluginMessageUtils(); + } + @Provides @Named("implementationName") public String implementationName() { diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java new file mode 100644 index 00000000..63b597ae --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java @@ -0,0 +1,28 @@ +package org.geysermc.floodgate.pluginmessage; + +import io.netty.buffer.Unpooled; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.geysermc.floodgate.MinecraftServerHolder; +import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; + +import java.util.UUID; + +public class FabricPluginMessageUtils extends PluginMessageUtils { + + @Override + public boolean sendMessage(UUID uuid, String channel, byte[] data) { + try { + ServerPlayer player = MinecraftServerHolder.get().getPlayerList().getPlayer(uuid); + ResourceLocation resource = new ResourceLocation(channel); // automatically splits over the : + FriendlyByteBuf dataBuffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); + ServerPlayNetworking.send(player, resource, dataBuffer); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } +} From 8b99d9ebd69d20cde0ec479f48a6b90451dbd2d0 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Tue, 28 Feb 2023 18:06:03 +0100 Subject: [PATCH 47/87] relocate snakeyaml (#85) fixes error when using floodgate-fabric with Simple Voice Chat Discord Bridge --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 99ac1591..3dce8a2d 100644 --- a/build.gradle +++ b/build.gradle @@ -112,6 +112,7 @@ shadowJar { configurations = [project.configurations.shadow] // TODO this is temporary until Floodgate's dev branch is merged relocate("com.google.inject", "org.geysermc.floodgate.shadow.guice") + relocate("org.yaml.snakeyaml", "org.geysermc.floodgate.shadow.snakeyaml") //relocate snakeyaml to avoid conflicts with other mods exclude([ 'cloud/**', 'com/google/common/**', 'com/google/errorprone/**', 'com/google/gson/**', 'com/google/j2objc/**', 'com/google/thirdparty/**', From 661c1720d2e4367bad39725abc521c01dfbf2a9e Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Wed, 15 Mar 2023 18:58:45 +0100 Subject: [PATCH 48/87] fix injection failure issue (#87) --- build.gradle | 4 ++-- src/main/resources/fabric.mod.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 3dce8a2d..bf9e8185 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,7 @@ dependencies { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.6.0-SNAPSHOT') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.8.0-SNAPSHOT') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included @@ -147,4 +147,4 @@ publishing { repositories { mavenLocal() } -} +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 454d7f8f..8beee375 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,6 +26,6 @@ "depends": { "fabricloader": ">=0.14.6", "fabric": "*", - "minecraft": ">=1.19.3" + "minecraft": ">=1.19.4" } -} +} \ No newline at end of file From 8b53939adc3e0d76d49241b7dbdeba439f080692 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Wed, 15 Mar 2023 19:26:24 +0100 Subject: [PATCH 49/87] Update gradle.properties (#88) --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6f7b3a32..83c1a108 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,14 +2,14 @@ org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.19.3 -loader_version=0.14.11 +minecraft_version=1.19.4 +loader_version=0.14.17 # Mod Properties mod_version=2.2.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.68.1+1.19.3 +fabric_version=0.76.0+1.19.4 # Our stuff lombok_version=1.18.20 From 90bb7e42ab025c291b0e98d15d8b42135f032073 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:25:11 -0400 Subject: [PATCH 50/87] Publish to Modrinth --- .github/workflows/publish.yml | 17 +++++++++++++++++ build.gradle | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..7d9d5bc7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,17 @@ +name: Publish +on: push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: gradle/wrapper-validation-action@v1 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17 + - name: Publish to Modrinth + env: + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + run: ./gradlew modrinth \ No newline at end of file diff --git a/build.gradle b/build.gradle index bf9e8185..74045806 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { id 'fabric-loom' version '1.0-SNAPSHOT' id 'java' id 'maven-publish' + id "com.modrinth.minotaur" version "2.+" } sourceCompatibility = JavaVersion.VERSION_17 @@ -147,4 +148,22 @@ publishing { repositories { mavenLocal() } +} + +modrinth { + projectId = "wKkoqHrH" + versionNumber = project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER") + versionType = "beta" + changelog = "A changelog can be found at https://github.com/GeyserMC/Floodgate-Fabric/commits" + + syncBodyFrom = rootProject.file("README.md").text + + uploadFile = tasks.getByPath("remappedShadowJar") + gameVersions = ["1.19.4"] + + loaders = ["fabric"] + + dependencies { + required.project "fabric-api" + } } \ No newline at end of file From 182e3e154f4918432b71c5ae2b1372e350c78ddb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:32:07 -0400 Subject: [PATCH 51/87] Change project ID --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 74045806..9fc2ca74 100644 --- a/build.gradle +++ b/build.gradle @@ -151,7 +151,7 @@ publishing { } modrinth { - projectId = "wKkoqHrH" + projectId = "bWrNNfkb" versionNumber = project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER") versionType = "beta" changelog = "A changelog can be found at https://github.com/GeyserMC/Floodgate-Fabric/commits" From a03f081408f3cdb8b2677ef3123e43dd117bffb7 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 7 Apr 2023 21:05:11 +0200 Subject: [PATCH 52/87] let's actually check for the right mixin (#90) --- .../java/org/geysermc/floodgate/util/MixinConfigPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java b/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java index ca0dc892..dbedf9e5 100644 --- a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java +++ b/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java @@ -21,7 +21,7 @@ public class MixinConfigPlugin implements IMixinConfigPlugin { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - if (mixinClassName.equals("org/geysermc/floodgate/mixin/ClientIntentionPacketMixin")) { + if (mixinClassName.equals("org.geysermc.floodgate.mixin.ClientIntentionPacketMixin")) { //returns true if fabricproxy-lite is present, therefore loading the mixin. If not present, the mixin will not be loaded. return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); } @@ -44,4 +44,4 @@ public class MixinConfigPlugin implements IMixinConfigPlugin { @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } -} +} \ No newline at end of file From 1fb00698abf3e7dfaf9382b6e8a5b219616db3dc Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 8 Jun 2023 16:33:36 +0200 Subject: [PATCH 53/87] fix building & 1.20 support (#92) * fix building, bump while at it * fixes https://github.com/GeyserMC/Floodgate-Fabric/issues/94 --- build.gradle | 2 +- src/main/resources/floodgate.accesswidener | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9fc2ca74..1ed9d3f5 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ dependencies { exclude group: "org.ow2.asm", module: "*" // From Events lib } - include(modImplementation('cloud.commandframework:cloud-fabric:1.8.0-SNAPSHOT') { + include(modImplementation('cloud.commandframework:cloud-fabric:1.8.3') { because "Commands library implementation for Fabric" }) diff --git a/src/main/resources/floodgate.accesswidener b/src/main/resources/floodgate.accesswidener index cad9dbdc..6097e7e7 100644 --- a/src/main/resources/floodgate.accesswidener +++ b/src/main/resources/floodgate.accesswidener @@ -3,4 +3,6 @@ accessWidener v1 named # To change login state accessible class net/minecraft/server/network/ServerLoginPacketListenerImpl$State # For player skin refreshing -accessible class net/minecraft/server/level/ChunkMap$TrackedEntity \ No newline at end of file +accessible class net/minecraft/server/level/ChunkMap$TrackedEntity +# To access skins +accessible field net/minecraft/world/entity/Entity level Lnet/minecraft/world/level/Level; \ No newline at end of file From fb38525d675fd506e04724ab54e799be293b0d84 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 8 Jun 2023 18:09:28 +0200 Subject: [PATCH 54/87] Avoid exception when running "floodgate" from console (#93) * dont throw an exception when running "floodgate" from console * apply suggestion --- .../java/org/geysermc/floodgate/util/FabricCommandUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 15086a70..3176f505 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -3,10 +3,10 @@ package org.geysermc.floodgate.util; import com.mojang.authlib.GameProfile; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.UserWhiteListEntry; -import net.minecraft.world.entity.Entity; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.FloodgateApi; @@ -70,8 +70,8 @@ public final class FabricCommandUtil extends CommandUtil { } @Override - public boolean hasPermission(Object player, String permission) { - return Permissions.check((Entity) player, permission); + public boolean hasPermission(Object source, String permission) { + return Permissions.check((SharedSuggestionProvider) source, permission); } @Override From 31db6d07de1606bd38e9dc45fcaa63482ae220f2 Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 9 Jun 2023 19:33:13 +0200 Subject: [PATCH 55/87] add 1.20 version to modrinth (#96) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1ed9d3f5..875f85dd 100644 --- a/build.gradle +++ b/build.gradle @@ -159,7 +159,7 @@ modrinth { syncBodyFrom = rootProject.file("README.md").text uploadFile = tasks.getByPath("remappedShadowJar") - gameVersions = ["1.19.4"] + gameVersions = ["1.19.4, 1.20"] loaders = ["fabric"] From 8869b57e8b2ab35a77b329d5fd731599cccdb60c Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 9 Jun 2023 20:56:39 +0200 Subject: [PATCH 56/87] Fix modrinth game versions (#97) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 875f85dd..333f07a3 100644 --- a/build.gradle +++ b/build.gradle @@ -159,7 +159,7 @@ modrinth { syncBodyFrom = rootProject.file("README.md").text uploadFile = tasks.getByPath("remappedShadowJar") - gameVersions = ["1.19.4, 1.20"] + gameVersions.addAll("1.19.4", "1.20") loaders = ["fabric"] From c7fcf107b496ad4c0d556d8f994354319918a98a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 17:12:56 -0400 Subject: [PATCH 57/87] Move to Java Edition 1.20.2 (#104) --- build.gradle | 6 +-- gradle.properties | 6 +-- .../addon/data/FabricDataHandler.java | 53 +++++++++++-------- .../ServerLoginPacketListenerImplMixin.java | 22 -------- .../ServerLoginPacketListenerSetter.java | 9 ---- src/main/resources/fabric.mod.json | 4 +- src/main/resources/floodgate.accesswidener | 4 +- src/main/resources/floodgate.mixins.json | 3 +- 8 files changed, 42 insertions(+), 65 deletions(-) delete mode 100644 src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java delete mode 100644 src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java diff --git a/build.gradle b/build.gradle index 333f07a3..f9adf97e 100644 --- a/build.gradle +++ b/build.gradle @@ -48,11 +48,11 @@ dependencies { exclude group: "org.ow2.asm", module: "*" // From Events lib } - include(modImplementation('cloud.commandframework:cloud-fabric:1.8.3') { + include(modImplementation('cloud.commandframework:cloud-fabric:1.8.4') { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.8.0-SNAPSHOT') { + include(modImplementation('net.kyori:adventure-platform-fabric:5.10.0') { because "Chat library implementation for Fabric that includes methods for communicating with the server" // Thanks to zml for this fix // The package modifies Brigadier which causes a LinkageError at runtime if included @@ -159,7 +159,7 @@ modrinth { syncBodyFrom = rootProject.file("README.md").text uploadFile = tasks.getByPath("remappedShadowJar") - gameVersions.addAll("1.19.4", "1.20") + gameVersions.addAll("1.20.2") loaders = ["fabric"] diff --git a/gradle.properties b/gradle.properties index 83c1a108..415b6eab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,14 +2,14 @@ org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.19.4 -loader_version=0.14.17 +minecraft_version=1.20.2 +loader_version=0.14.22 # Mod Properties mod_version=2.2.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.76.0+1.19.4 +fabric_version=0.89.2+1.20.2 # Our stuff lombok_version=1.18.20 diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java index 704e00b9..eaced776 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java @@ -1,5 +1,6 @@ package org.geysermc.floodgate.addon.data; +import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.logging.LogUtils; import io.netty.channel.Channel; import io.netty.util.AttributeKey; @@ -13,7 +14,6 @@ import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.mixin.ConnectionMixin; import org.geysermc.floodgate.mixin.ClientIntentionPacketMixinInterface; -import org.geysermc.floodgate.mixin_interface.ServerLoginPacketListenerSetter; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; import org.geysermc.floodgate.api.player.FloodgatePlayer; @@ -78,7 +78,7 @@ public final class FabricDataHandler extends CommonDataHandler { if (packet instanceof ClientIntentionPacket intentionPacket) { ctx.pipeline().addAfter("splitter", "floodgate_packet_blocker", blocker); networkManager = (Connection) ctx.channel().pipeline().get("packet_handler"); - handle(packet, intentionPacket.getHostName()); + handle(packet, intentionPacket.hostName()); return false; } return !checkAndHandleLogin(packet); @@ -93,7 +93,7 @@ public final class FabricDataHandler extends CommonDataHandler { } // we have to fake the offline player (login) cycle - if (!(networkManager.getPacketListener() instanceof ServerLoginPacketListenerImpl)) { + if (!(networkManager.getPacketListener() instanceof ServerLoginPacketListenerImpl packetListener)) { // player is not in the login state, abort ctx.pipeline().remove(this); return true; @@ -102,25 +102,9 @@ public final class FabricDataHandler extends CommonDataHandler { GameProfile gameProfile = new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername()); if (player.isLinked() && player.getCorrectUniqueId().version() == 4) { - Thread texturesThread = new Thread("Bedrock Linked Player Texture Download") { - @Override - public void run() { - try { - MinecraftServerHolder.get().getSessionService() - .fillProfileProperties(gameProfile, true); - } catch (Exception e) { - LOGGER.error("Unable to get Bedrock linked player textures for " + gameProfile.getName(), e); - } - ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()) - .setGameProfile(gameProfile); - ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setLoginState(); - } - }; - texturesThread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); - texturesThread.start(); + verifyLinkedPlayerAsync(packetListener, gameProfile); } else { - ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setGameProfile(gameProfile); - ((ServerLoginPacketListenerSetter) networkManager.getPacketListener()).setLoginState(); + packetListener.startClientVerification(gameProfile); } ctx.pipeline().remove(this); @@ -129,12 +113,37 @@ public final class FabricDataHandler extends CommonDataHandler { return false; } + /** + * Starts a new thread that fetches the linked player's textures, + * and then starts client verification with the more accurate game profile. + * + * @param packetListener the login packet listener for this connection + * @param gameProfile the player's initial profile. it will NOT be mutated. + */ + private void verifyLinkedPlayerAsync(ServerLoginPacketListenerImpl packetListener, GameProfile gameProfile) { + Thread texturesThread = new Thread("Bedrock Linked Player Texture Download") { + @Override + public void run() { + GameProfile effectiveProfile = gameProfile; + try { + MinecraftSessionService service = MinecraftServerHolder.get().getSessionService(); + effectiveProfile = service.fetchProfile(effectiveProfile.getId(), true).profile(); + } catch (Exception e) { + LOGGER.error("Unable to get Bedrock linked player textures for " + effectiveProfile.getName(), e); + } + packetListener.startClientVerification(effectiveProfile); + } + }; + texturesThread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); + texturesThread.start(); + } + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); if (config.isDebug()) { - cause.printStackTrace(); + LOGGER.error("Exception caught in FabricDataHandler", cause); } } } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java deleted file mode 100644 index afebed5b..00000000 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerLoginPacketListenerImplMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.geysermc.floodgate.mixin; - -import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.geysermc.floodgate.mixin_interface.ServerLoginPacketListenerSetter; -import com.mojang.authlib.GameProfile; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(ServerLoginPacketListenerImpl.class) -public abstract class ServerLoginPacketListenerImplMixin implements ServerLoginPacketListenerSetter { - @Shadow - ServerLoginPacketListenerImpl.State state; - - @Accessor("gameProfile") - public abstract void setGameProfile(GameProfile profile); - - @Override - public void setLoginState() { - this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; - } -} diff --git a/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java b/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java deleted file mode 100644 index 4861cc76..00000000 --- a/src/main/java/org/geysermc/floodgate/mixin_interface/ServerLoginPacketListenerSetter.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.geysermc.floodgate.mixin_interface; - -import com.mojang.authlib.GameProfile; - -public interface ServerLoginPacketListenerSetter { - void setGameProfile(GameProfile profile); - - void setLoginState(); -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 8beee375..a80208d1 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,8 +24,8 @@ "floodgate.mixins.json" ], "depends": { - "fabricloader": ">=0.14.6", + "fabricloader": ">=0.14.22", "fabric": "*", - "minecraft": ">=1.19.4" + "minecraft": ">=1.20.2" } } \ No newline at end of file diff --git a/src/main/resources/floodgate.accesswidener b/src/main/resources/floodgate.accesswidener index 6097e7e7..f3860e9f 100644 --- a/src/main/resources/floodgate.accesswidener +++ b/src/main/resources/floodgate.accesswidener @@ -1,7 +1,7 @@ accessWidener v1 named -# To change login state -accessible class net/minecraft/server/network/ServerLoginPacketListenerImpl$State +# For setting gameprofile and starting connection verification +accessible method net/minecraft/server/network/ServerLoginPacketListenerImpl startClientVerification (Lcom/mojang/authlib/GameProfile;)V # For player skin refreshing accessible class net/minecraft/server/level/ChunkMap$TrackedEntity # To access skins diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index d80a693b..4b1347eb 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -8,8 +8,7 @@ "ClientIntentionPacketMixinInterface", "ClientIntentionPacketMixin", "ConnectionMixin", - "ServerConnectionListenerMixin", - "ServerLoginPacketListenerImplMixin" + "ServerConnectionListenerMixin" ], "plugin": "org.geysermc.floodgate.util.MixinConfigPlugin", "injectors": { From 1900da2ff00a8dccfcd498c00cac5a44f9839b85 Mon Sep 17 00:00:00 2001 From: derspyy <65043391+derspyy@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:06:15 -0300 Subject: [PATCH 58/87] changed the filename. (#95) * changed versioning * updated jenkinsfile * separate task :p --- build.gradle | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f9adf97e..bf754d2e 100644 --- a/build.gradle +++ b/build.gradle @@ -129,6 +129,13 @@ task remappedShadowJar(type: RemapJarTask) { archiveName = "floodgate-fabric.jar" } +task remappedModrinthJar(type: RemapJarTask) { + dependsOn tasks.shadowJar + input = tasks.shadowJar.archiveFile + addNestedDependencies = true + version = "${project.mod_version}+build.${System.getenv("GITHUB_RUN_NUMBER")}" +} + tasks.assemble.dependsOn tasks.remappedShadowJar artifacts { @@ -150,6 +157,8 @@ publishing { } } +tasks.modrinth.dependsOn tasks.remappedModrinthJar + modrinth { projectId = "bWrNNfkb" versionNumber = project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER") @@ -158,7 +167,7 @@ modrinth { syncBodyFrom = rootProject.file("README.md").text - uploadFile = tasks.getByPath("remappedShadowJar") + uploadFile = tasks.getByPath("remappedModrinthJar") gameVersions.addAll("1.20.2") loaders = ["fabric"] @@ -166,4 +175,4 @@ modrinth { dependencies { required.project "fabric-api" } -} \ No newline at end of file +} From 5633bb8f9e7e83042c55f340dfae3b683e6d7621 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 19 Nov 2023 19:49:34 +0100 Subject: [PATCH 59/87] Fix handshake mixin (#112) --- .../geysermc/floodgate/mixin/ClientIntentionPacketMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java index 3b59a680..e8c0dbf8 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java +++ b/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java @@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.ModifyConstant; @Mixin(ClientIntentionPacket.class) public class ClientIntentionPacketMixin { @ModifyConstant(method = "(Lnet/minecraft/network/FriendlyByteBuf;)V", constant = @Constant(intValue = 255)) - private int floodgate$setHandshakeLength(int defaultValue) { + private static int floodgate$setHandshakeLength(int defaultValue) { return Short.MAX_VALUE; } } From 160cc5e963f4a8d1d7e4a8b97dccb4a28f47d87c Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 14 Dec 2023 20:04:34 -0500 Subject: [PATCH 60/87] Target 1.20.4 (#113) * Target 1.20.4 and remove adventure-platform dependency * Update fabric.mod.json * 1.20.2 & cleanup publish action --------- Co-authored-by: Kas-tle <26531652+Kas-tle@users.noreply.github.com> --- .github/workflows/publish.yml | 18 ++++++++++++++---- build.gradle | 16 ++-------------- gradle.properties | 6 +++--- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7d9d5bc7..31a0e0d0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,13 +5,23 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v3 + - uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9 + - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + - uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 with: distribution: 'temurin' java-version: 17 - name: Publish to Modrinth + uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 env: MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} - run: ./gradlew modrinth \ No newline at end of file + with: + arguments: modrinth + gradle-home-cache-cleanup: true + - name: Archive Artifacts + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 + if: success() + with: + name: Floodgate Fabric + path: build/libs/floodgate-fabric.jar + if-no-files-found: error \ No newline at end of file diff --git a/build.gradle b/build.gradle index bf754d2e..ea7dce62 100644 --- a/build.gradle +++ b/build.gradle @@ -52,13 +52,6 @@ dependencies { because "Commands library implementation for Fabric" }) - include(modImplementation('net.kyori:adventure-platform-fabric:5.10.0') { - because "Chat library implementation for Fabric that includes methods for communicating with the server" - // Thanks to zml for this fix - // The package modifies Brigadier which causes a LinkageError at runtime if included - exclude group: 'ca.stellardrift', module: "colonel" - }) - // Lombok compileOnly "org.projectlombok:lombok:${project.lombok_version}" annotationProcessor "org.projectlombok:lombok:${project.lombok_version}" @@ -69,12 +62,6 @@ repositories { maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } - // specifically for adventure-platform-fabric:5.4.0-SNAPSHOT - maven { - name = "sonatype-oss-snapshots1" - url = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - mavenContent { snapshotsOnly() } - } // Standard OpenCollab repositories maven { name = 'opencollab-release-repo' @@ -160,6 +147,7 @@ publishing { tasks.modrinth.dependsOn tasks.remappedModrinthJar modrinth { + token = System.getenv('MODRINTH_TOKEN') // Prevent GitHub Actions from caching empty Modrinth token projectId = "bWrNNfkb" versionNumber = project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER") versionType = "beta" @@ -168,7 +156,7 @@ modrinth { syncBodyFrom = rootProject.file("README.md").text uploadFile = tasks.getByPath("remappedModrinthJar") - gameVersions.addAll("1.20.2") + gameVersions.addAll("1.20.2", "1.20.4") loaders = ["fabric"] diff --git a/gradle.properties b/gradle.properties index 415b6eab..1d56e9ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,14 +2,14 @@ org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.20.2 -loader_version=0.14.22 +minecraft_version=1.20.4 +loader_version=0.15.2 # Mod Properties mod_version=2.2.0-SNAPSHOT maven_group=org.geysermc.floodgate archives_base_name=floodgate-fabric # Dependencies # check this on https://modmuss50.me/fabric.html -fabric_version=0.89.2+1.20.2 +fabric_version=0.91.2+1.20.4 # Our stuff lombok_version=1.18.20 From 046311de09e638249a9a48a2d107cda19bd8ce28 Mon Sep 17 00:00:00 2001 From: pompompopi <143640209+pompompopi@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:11:09 +0100 Subject: [PATCH 61/87] Do not expect to always receive an instance of Channel in injector (#118) Mods like Raknetify break this assumption, so to avoid class cast exceptions we use an instanceof cast. Fixes #100 --- .../org/geysermc/floodgate/inject/fabric/FabricInjector.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java index 7eaf9867..bbba0158 100644 --- a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java @@ -22,7 +22,10 @@ public final class FabricInjector extends CommonPlatformInjector { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { super.channelRead(ctx, msg); - Channel channel = (Channel) msg; + if (!(msg instanceof Channel channel)) { + return; + } + channel.pipeline().addLast(new ChannelInitializer() { @Override protected void initChannel(Channel channel) { From f05ed8cd2fee4f3500c7ea25744b4d1cc25cda62 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 28 Apr 2024 23:57:07 +0200 Subject: [PATCH 62/87] Update cloud to 2.0, update to Floodgate 2.2.3, support 1.20.5 (#115) * Update for floodgate 2.2.2-SNAPSHOT * Fix spacing on //no-op comment * Add jitpack repo and target floodgat-master * update gradle, loom, shade + relocate old version of cloud * exclude meta-inf too * - Update to Floodgate 2.2.3 - Update bstats properly to fix shutdown bug - Move to kts build/settings file - Fix command permission issues - Update to cloud 2.0 * prepare for 1.20.5 * 1.20.5.. i dislike this * also register channels to send packets * update cloud, use jitpack to pull in floodgate * some minor stuff * proper permissions check * update dependencies, finally * update modrinth version --------- Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> --- build.gradle | 166 ------------------ build.gradle.kts | 144 +++++++++++++++ gradle.properties | 13 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 10 +- settings.gradle | 10 -- settings.gradle.kts | 8 + .../org/geysermc/floodgate/FabricMod.java | 2 +- .../geysermc/floodgate/FabricPlatform.java | 14 -- .../inject/fabric/FabricInjector.java | 15 +- .../logger/Log4jFloodgateLogger.java | 35 ++-- .../floodgate/module/FabricCommandModule.java | 14 +- .../module/FabricPlatformModule.java | 10 +- .../FabricPluginMessageRegistration.java | 51 +++++- .../FabricPluginMessageUtils.java | 24 ++- .../pluginmessage/FabricSkinApplier.java | 8 +- .../pluginmessage/payloads/FormPayload.java | 27 +++ .../pluginmessage/payloads/PacketPayload.java | 27 +++ .../pluginmessage/payloads/SkinPayload.java | 27 +++ .../payloads/TransferPayload.java | 27 +++ .../floodgate/util/FabricCommandUtil.java | 7 +- src/main/resources/fabric.mod.json | 4 +- 22 files changed, 382 insertions(+), 263 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts delete mode 100644 src/main/java/org/geysermc/floodgate/FabricPlatform.java create mode 100644 src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java create mode 100644 src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java create mode 100644 src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java create mode 100644 src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java diff --git a/build.gradle b/build.gradle deleted file mode 100644 index ea7dce62..00000000 --- a/build.gradle +++ /dev/null @@ -1,166 +0,0 @@ -import net.fabricmc.loom.task.RemapJarTask - -plugins { - id 'com.github.johnrengelman.shadow' version '7.0.0' - id 'fabric-loom' version '1.0-SNAPSHOT' - id 'java' - id 'maven-publish' - id "com.modrinth.minotaur" version "2.+" -} - -sourceCompatibility = JavaVersion.VERSION_17 -targetCompatibility = JavaVersion.VERSION_17 - -archivesBaseName = project.archives_base_name -version = project.mod_version -group = project.maven_group - -loom { - accessWidenerPath = file("src/main/resources/floodgate.accesswidener") -} - -dependencies { - //to change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings loom.officialMojangMappings() - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. - // You may need to force-disable transitiveness on them. - - // Base Floodgate - implementation("org.geysermc.floodgate:core:${project.mod_version}") - shadow("org.geysermc.floodgate:core:${project.mod_version}") { - exclude group: "cloud.commandframework", module: "*" // Cloud is included jar-in-jar - exclude group: "org.geysermc.floodgate", module: "api" - exclude group: 'com.google.guava', module: "guava" - exclude group: 'com.google.code.gson', module: "gson" - exclude group: 'org.slf4j', module: "slf4j-api" - exclude group: 'it.unimi.dsi.fastutil', module: "*" - } - - shadow(implementation("org.geysermc.floodgate:api:${project.mod_version}")) { - exclude group: 'com.google.guava', module: "guava" - exclude group: 'com.google.code.gson', module: "gson" - exclude group: "org.ow2.asm", module: "*" // From Events lib - } - - include(modImplementation('cloud.commandframework:cloud-fabric:1.8.4') { - because "Commands library implementation for Fabric" - }) - - // Lombok - compileOnly "org.projectlombok:lombok:${project.lombok_version}" - annotationProcessor "org.projectlombok:lombok:${project.lombok_version}" -} - -repositories { - //mavenLocal() - maven { - url = 'https://oss.sonatype.org/content/repositories/snapshots' - } - // Standard OpenCollab repositories - maven { - name = 'opencollab-release-repo' - url = 'https://repo.opencollab.dev/maven-releases/' - //TODO set as releases - } - maven { - name = 'opencollab-snapshot-repo' - url = 'https://repo.opencollab.dev/maven-snapshots/' - } -} - -processResources { - inputs.property "version", project.version - - filesMatching("fabric.mod.json") { - expand "version": project.version - } -} - -// ensure that the encoding is set to UTF-8, no matter what the system default is -// this fixes some edge cases with special characters not displaying correctly -// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -java { - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. - withSourcesJar() -} - -shadowJar { - configurations = [project.configurations.shadow] - // TODO this is temporary until Floodgate's dev branch is merged - relocate("com.google.inject", "org.geysermc.floodgate.shadow.guice") - relocate("org.yaml.snakeyaml", "org.geysermc.floodgate.shadow.snakeyaml") //relocate snakeyaml to avoid conflicts with other mods - exclude([ - 'cloud/**', - 'com/google/common/**', 'com/google/errorprone/**', 'com/google/gson/**', 'com/google/j2objc/**', 'com/google/thirdparty/**', - 'it/unimi/**', - 'org/slf4j/**' - ]) -} - -task remappedShadowJar(type: RemapJarTask) { - dependsOn tasks.shadowJar - input = tasks.shadowJar.archiveFile - addNestedDependencies = true - archiveName = "floodgate-fabric.jar" -} - -task remappedModrinthJar(type: RemapJarTask) { - dependsOn tasks.shadowJar - input = tasks.shadowJar.archiveFile - addNestedDependencies = true - version = "${project.mod_version}+build.${System.getenv("GITHUB_RUN_NUMBER")}" -} - -tasks.assemble.dependsOn tasks.remappedShadowJar - -artifacts { - archives remappedShadowJar - shadow shadowJar -} - -// configure the maven publication -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - } - } - - // select the repositories you want to publish to - repositories { - mavenLocal() - } -} - -tasks.modrinth.dependsOn tasks.remappedModrinthJar - -modrinth { - token = System.getenv('MODRINTH_TOKEN') // Prevent GitHub Actions from caching empty Modrinth token - projectId = "bWrNNfkb" - versionNumber = project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER") - versionType = "beta" - changelog = "A changelog can be found at https://github.com/GeyserMC/Floodgate-Fabric/commits" - - syncBodyFrom = rootProject.file("README.md").text - - uploadFile = tasks.getByPath("remappedModrinthJar") - gameVersions.addAll("1.20.2", "1.20.4") - - loaders = ["fabric"] - - dependencies { - required.project "fabric-api" - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..5913d590 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,144 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id("com.github.johnrengelman.shadow") version "8.1.1" + id("fabric-loom") version "1.6-SNAPSHOT" + id("java") + id("maven-publish") + id("com.modrinth.minotaur") version "2.+" +} + +loom { + accessWidenerPath = file("src/main/resources/floodgate.accesswidener") +} + +dependencies { + //to change the versions see the gradle.properties file + minecraft("com.mojang:minecraft:1.20.5") + mappings(loom.officialMojangMappings()) + modImplementation("net.fabricmc:fabric-loader:0.15.10") + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation("net.fabricmc.fabric-api:fabric-api:0.97.6+1.20.5") + + // Base Floodgate + implementation("org.geysermc.floodgate:core:2.2.3-SNAPSHOT") + shadow("org.geysermc.floodgate:core:2.2.3-SNAPSHOT") { isTransitive = false } + shadow("org.geysermc.floodgate:api:2.2.3-SNAPSHOT") { isTransitive = false } + + // Requires relocation + shadow("org.bstats:bstats-base:3.0.2") + + // Shadow & relocate these since the (indirectly) depend on quite old dependencies + shadow("com.google.inject:guice:6.0.0") { isTransitive = false } + shadow("org.geysermc.configutils:configutils:1.0-SNAPSHOT") { + exclude("org.checkerframework") + exclude("com.google.errorprone") + exclude("com.github.spotbugs") + exclude("com.nukkitx.fastutil") + } + + include("aopalliance:aopalliance:1.0") + include("javax.inject:javax.inject:1") + include("jakarta.inject:jakarta.inject-api:2.0.1") + include("org.java-websocket:Java-WebSocket:1.5.2") + + // Just like Geyser, include these + include("org.geysermc.geyser", "common", "2.2.3-SNAPSHOT") + include("org.geysermc.cumulus", "cumulus", "1.1.2") + include("org.geysermc.event", "events", "1.1-SNAPSHOT") + include("org.lanternpowered", "lmbda", "2.0.0") // used in events + + // cloud + include("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") + modImplementation("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") + + // Lombok + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") +} + +repositories { + //mavenLocal() + mavenCentral() + maven("https://maven.fabricmc.net/") + maven("https://repo.opencollab.dev/main/") + maven("https://jitpack.io") { + content { + includeGroupByRegex("com.github.*") + } + } + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} + +java { + withSourcesJar() +} + +tasks { + shadowJar { + configurations = listOf(project.configurations.shadow.get()) + + relocate("org.bstats", "org.geysermc.floodgate.shadow.bstats") + relocate("com.google.inject", "org.geysermc.floodgate.shadow.google.inject") + relocate("org.yaml", "org.geysermc.floodgate.shadow.org.yaml") + } + + processResources { + filesMatching("fabric.mod.json") { + expand("version" to project.version) + } + } + + remapJar { + dependsOn(shadowJar) + mustRunAfter(shadowJar) + inputFile.set(shadowJar.get().archiveFile) + addNestedDependencies = true // todo? + archiveFileName.set("floodgate-fabric.jar") + } + + register("remapModrinthJar", RemapJarTask::class) { + dependsOn(shadowJar) + inputFile.set(remapJar.get().archiveFile) + addNestedDependencies = true + archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER")) + archiveClassifier.set("") + } +} + +publishing { + publications { + register("publish", MavenPublication::class) { + from(project.components["java"]) + + // skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651 + val javaComponent = project.components["java"] as AdhocComponentWithVariants + javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() } + } + } + + repositories { + mavenLocal() + } +} + +modrinth { + token.set(System.getenv("MODRINTH_TOKEN")) // Prevent GitHub Actions from caching empty Modrinth token + projectId.set("bWrNNfkb") + versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER")) + versionType.set("beta") + changelog.set("A changelog can be found at https://github.com/GeyserMC/Floodgate-Fabric/commits") + + syncBodyFrom.set(rootProject.file("README.md").readText()) + + uploadFile.set(tasks.named("remapModrinthJar")) + gameVersions.addAll("1.20.5") + + loaders.add("fabric") + + dependencies { + required.project("fabric-api") + } +} diff --git a/gradle.properties b/gradle.properties index 1d56e9ae..573a53c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,6 @@ # Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx2G -# Fabric Properties -# check these on https://modmuss50.me/fabric.html -minecraft_version=1.20.4 -loader_version=0.15.2 # Mod Properties -mod_version=2.2.0-SNAPSHOT -maven_group=org.geysermc.floodgate +version=2.2.3-SNAPSHOT +group=org.geysermc.floodgate archives_base_name=floodgate-fabric -# Dependencies -# check this on https://modmuss50.me/fabric.html -fabric_version=0.91.2+1.20.4 -# Our stuff -lombok_version=1.18.20 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102e..48c0a02c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c7873..c53aefaa 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 5b60df3d..00000000 --- a/settings.gradle +++ /dev/null @@ -1,10 +0,0 @@ -pluginManagement { - repositories { - jcenter() - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..71ef923d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + //mavenLocal() + mavenCentral() + gradlePluginPortal() + maven("https://maven.fabricmc.net/") + } +} diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 1eaab881..a2da1327 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -19,7 +19,7 @@ public class FabricMod implements ModInitializer { new FabricPlatformModule() ); - FabricPlatform platform = injector.getInstance(FabricPlatform.class); + FloodgatePlatform platform = injector.getInstance(FloodgatePlatform.class); platform.enable(new FabricCommandModule()); diff --git a/src/main/java/org/geysermc/floodgate/FabricPlatform.java b/src/main/java/org/geysermc/floodgate/FabricPlatform.java deleted file mode 100644 index af1a0a20..00000000 --- a/src/main/java/org/geysermc/floodgate/FabricPlatform.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.geysermc.floodgate; - -import com.google.inject.Inject; -import com.google.inject.Injector; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.inject.PlatformInjector; -import org.geysermc.floodgate.api.logger.FloodgateLogger; - -public final class FabricPlatform extends FloodgatePlatform { - @Inject - public FabricPlatform(FloodgateApi api, PlatformInjector platformInjector, FloodgateLogger logger, Injector guice) { - super(api, platformInjector, logger, guice); - } -} diff --git a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java index bbba0158..e5a767f3 100644 --- a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java @@ -3,6 +3,7 @@ package org.geysermc.floodgate.inject.fabric; import io.netty.channel.*; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.inject.CommonPlatformInjector; @RequiredArgsConstructor @@ -12,23 +13,23 @@ public final class FabricInjector extends CommonPlatformInjector { @Getter private final boolean injected = true; @Override - public boolean inject() throws Exception { - return true; + public void inject() throws Exception { + //no-op } public void injectClient(ChannelFuture future) { future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) throws Exception { super.channelRead(ctx, msg); if (!(msg instanceof Channel channel)) { return; } - channel.pipeline().addLast(new ChannelInitializer() { + channel.pipeline().addLast(new ChannelInitializer<>() { @Override - protected void initChannel(Channel channel) { + protected void initChannel(@NonNull Channel channel) { injectAddonsCall(channel, false); addInjectedClient(channel); channel.closeFuture().addListener(listener -> { @@ -42,8 +43,8 @@ public final class FabricInjector extends CommonPlatformInjector { } @Override - public boolean removeInjection() throws Exception { - return true; + public void removeInjection() throws Exception { + //no-op } public static FabricInjector getInstance() { diff --git a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java b/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java index a4cb38ff..d24223fa 100644 --- a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java +++ b/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java @@ -1,18 +1,31 @@ package org.geysermc.floodgate.logger; -import lombok.RequiredArgsConstructor; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.util.LanguageManager; import static org.geysermc.floodgate.util.MessageFormatter.format; -@RequiredArgsConstructor +@Singleton public final class Log4jFloodgateLogger implements FloodgateLogger { - private final Logger logger; - private final LanguageManager languageManager; + @Inject + @Named("logger") + private Logger logger; + private LanguageManager languageManager; + + @Inject + private void init(LanguageManager languageManager, FloodgateConfig config) { + this.languageManager = languageManager; + if (config.isDebug() && !logger.isDebugEnabled()) { + Configurator.setLevel(logger.getName(), Level.DEBUG); + } + } @Override public void error(String message, Object... args) { @@ -49,20 +62,6 @@ public final class Log4jFloodgateLogger implements FloodgateLogger { logger.trace(message, args); } - @Override - public void enableDebug() { - if (!logger.isDebugEnabled()) { - Configurator.setLevel(logger.getName(), Level.DEBUG); - } - } - - @Override - public void disableDebug() { - if (logger.isDebugEnabled()) { - Configurator.setLevel(logger.getName(), Level.INFO); - } - } - @Override public boolean isDebug() { return logger.isDebugEnabled(); diff --git a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java index c6730278..c02adb16 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java @@ -1,9 +1,5 @@ package org.geysermc.floodgate.module; -import cloud.commandframework.CommandManager; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.fabric.FabricCommandManager; -import cloud.commandframework.fabric.FabricServerCommandManager; import com.google.inject.Provides; import com.google.inject.Singleton; import lombok.SneakyThrows; @@ -11,6 +7,11 @@ import net.minecraft.commands.CommandSourceStack; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.UserAudience; +import org.geysermc.floodgate.player.audience.FloodgateSenderMapper; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.fabric.FabricCommandManager; +import org.incendo.cloud.fabric.FabricServerCommandManager; public final class FabricCommandModule extends CommandModule { @Provides @@ -18,9 +19,8 @@ public final class FabricCommandModule extends CommandModule { @SneakyThrows public CommandManager commandManager(CommandUtil commandUtil) { FabricCommandManager commandManager = new FabricServerCommandManager<>( - CommandExecutionCoordinator.simpleCoordinator(), - commandUtil::getUserAudience, - audience -> (CommandSourceStack) audience.source() + ExecutionCoordinator.simpleCoordinator(), + new FloodgateSenderMapper<>(commandUtil) ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); return commandManager; diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java index 3071107c..9a592830 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java @@ -1,5 +1,7 @@ package org.geysermc.floodgate.module; +import com.google.inject.name.Names; +import org.apache.logging.log4j.Logger; import org.geysermc.floodgate.inject.fabric.FabricInjector; import org.geysermc.floodgate.listener.FabricEventListener; import org.geysermc.floodgate.listener.FabricEventRegistration; @@ -32,12 +34,8 @@ public final class FabricPlatformModule extends AbstractModule { @Override protected void configure() { bind(PlatformUtils.class).to(FabricPlatformUtils.class); - } - - @Provides - @Singleton - public FloodgateLogger floodgateLogger(LanguageManager languageManager) { - return new Log4jFloodgateLogger(LogManager.getLogger("floodgate"), languageManager); + bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(LogManager.getLogger("floodgate")); + bind(FloodgateLogger.class).to(Log4jFloodgateLogger.class); } @Provides diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java index 09ab54c8..ee5b781a 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java @@ -1,16 +1,53 @@ package org.geysermc.floodgate.pluginmessage; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.resources.ResourceLocation; +import org.geysermc.floodgate.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.pluginmessage.payloads.TransferPayload; public class FabricPluginMessageRegistration implements PluginMessageRegistration { @Override public void register(PluginMessageChannel channel) { - ServerPlayNetworking.registerGlobalReceiver(new ResourceLocation(channel.getIdentifier()), - (server, player, handler, buf, responseSender) -> { - byte[] bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); - channel.handleServerCall(bytes, player.getUUID(), player.getGameProfile().getName()); - }); + switch (channel.getIdentifier()) { + case "floodgate:form" -> { + PayloadTypeRegistry.playC2S().register(FormPayload.TYPE, FormPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(FormPayload.TYPE, FormPayload.STREAM_CODEC); + ServerPlayNetworking.registerGlobalReceiver(FormPayload.TYPE, + ((payload, context) -> channel.handleServerCall( + payload.data(), + context.player().getUUID(), + context.player().getGameProfile().getName()))); + } + case "floodgate:packet" -> { + PayloadTypeRegistry.playC2S().register(PacketPayload.TYPE, PacketPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(PacketPayload.TYPE, PacketPayload.STREAM_CODEC); + ServerPlayNetworking.registerGlobalReceiver(PacketPayload.TYPE, + ((payload, context) -> channel.handleServerCall( + payload.data(), + context.player().getUUID(), + context.player().getGameProfile().getName()))); + } + case "floodgate:skin" -> { + PayloadTypeRegistry.playC2S().register(SkinPayload.TYPE, SkinPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(SkinPayload.TYPE, SkinPayload.STREAM_CODEC); + ServerPlayNetworking.registerGlobalReceiver(SkinPayload.TYPE, + ((payload, context) -> channel.handleServerCall( + payload.data(), + context.player().getUUID(), + context.player().getGameProfile().getName()))); + } + case "floodgate:transfer" -> { + PayloadTypeRegistry.playC2S().register(TransferPayload.TYPE, TransferPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(TransferPayload.TYPE, TransferPayload.STREAM_CODEC); + ServerPlayNetworking.registerGlobalReceiver(TransferPayload.TYPE, + ((payload, context) -> channel.handleServerCall( + payload.data(), + context.player().getUUID(), + context.player().getGameProfile().getName()))); + } + default -> throw new IllegalArgumentException("unknown channel: " + channel); + } } } diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java index 63b597ae..6a5b3b4c 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java @@ -1,13 +1,23 @@ package org.geysermc.floodgate.pluginmessage; import io.netty.buffer.Unpooled; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import org.geysermc.floodgate.MinecraftServerHolder; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.pluginmessage.payloads.TransferPayload; +import java.util.Objects; import java.util.UUID; public class FabricPluginMessageUtils extends PluginMessageUtils { @@ -16,9 +26,17 @@ public class FabricPluginMessageUtils extends PluginMessageUtils { public boolean sendMessage(UUID uuid, String channel, byte[] data) { try { ServerPlayer player = MinecraftServerHolder.get().getPlayerList().getPlayer(uuid); - ResourceLocation resource = new ResourceLocation(channel); // automatically splits over the : - FriendlyByteBuf dataBuffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); - ServerPlayNetworking.send(player, resource, dataBuffer); + final CustomPacketPayload payload; + switch (channel) { + case "floodgate:form" -> payload = new FormPayload(data); + case "floodgate:packet" -> payload = new PacketPayload(data); + case "floodgate:skin" -> payload = new SkinPayload(data); + case "floodgate:transfer" -> payload = new TransferPayload(data); + default -> throw new IllegalArgumentException("unknown channel: " + channel); + } + + Objects.requireNonNull(player); + ServerPlayNetworking.send(player, payload); } catch (Exception e) { e.printStackTrace(); return false; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java index 7d5107e8..374c2d8c 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java @@ -7,18 +7,20 @@ import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.mixin.ChunkMapMixin; import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.skin.SkinData; + +import static org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; import java.util.Collections; public final class FabricSkinApplier implements SkinApplier { @Override - public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { + public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { MinecraftServerHolder.get().execute(() -> { ServerPlayer bedrockPlayer = MinecraftServerHolder.get().getPlayerList() .getPlayer(floodgatePlayer.getCorrectUniqueId()); @@ -31,7 +33,7 @@ public final class FabricSkinApplier implements SkinApplier { PropertyMap properties = bedrockPlayer.getGameProfile().getProperties(); properties.removeAll("textures"); - properties.put("textures", new Property("textures", skinData.getValue(), skinData.getSignature())); + properties.put("textures", new Property("textures", skinData.value(), skinData.signature())); ChunkMap tracker = ((ServerLevel) bedrockPlayer.level).getChunkSource().chunkMap; ChunkMap.TrackedEntity entry = ((ChunkMapMixin) tracker).getEntityMap().get(bedrockPlayer.getId()); diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java new file mode 100644 index 00000000..132e39b9 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.pluginmessage.payloads; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.checkerframework.checker.nullness.qual.NonNull; + +public record FormPayload(byte[] data) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(FormPayload::write, FormPayload::new); + public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:form"); + + private FormPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readByteArray()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByteArray(this.data); + } + + public CustomPacketPayload.@NonNull Type type() { + return TYPE; + } + + public byte[] data() { + return this.data; + } +} \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java new file mode 100644 index 00000000..89276b58 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.pluginmessage.payloads; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.checkerframework.checker.nullness.qual.NonNull; + +public record PacketPayload(byte[] data) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(PacketPayload::write, PacketPayload::new); + public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:packet"); + + private PacketPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readByteArray()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByteArray(this.data); + } + + public CustomPacketPayload.@NonNull Type type() { + return TYPE; + } + + public byte[] data() { + return this.data; + } +} \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java new file mode 100644 index 00000000..a2507e56 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.pluginmessage.payloads; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.checkerframework.checker.nullness.qual.NonNull; + +public record SkinPayload(byte[] data) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(SkinPayload::write, SkinPayload::new); + public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:skin"); + + private SkinPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readByteArray()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByteArray(this.data); + } + + public CustomPacketPayload.@NonNull Type type() { + return TYPE; + } + + public byte[] data() { + return this.data; + } +} \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java new file mode 100644 index 00000000..b291b92d --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.pluginmessage.payloads; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.checkerframework.checker.nullness.qual.NonNull; + +public record TransferPayload(byte[] data) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(TransferPayload::write, TransferPayload::new); + public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:transfer"); + + private TransferPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readByteArray()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeByteArray(this.data); + } + + public CustomPacketPayload.@NonNull Type type() { + return TYPE; + } + + public byte[] data() { + return this.data; + } +} \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java index 3176f505..0382148b 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java @@ -2,6 +2,7 @@ package org.geysermc.floodgate.util; import com.mojang.authlib.GameProfile; import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; @@ -15,6 +16,7 @@ import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.UserAudience; import java.util.*; +import java.util.logging.Logger; public final class FabricCommandUtil extends CommandUtil { private final FloodgateLogger logger; @@ -26,7 +28,7 @@ public final class FabricCommandUtil extends CommandUtil { } @Override - public UserAudience getUserAudience(final @NonNull Object sourceObj) { + public @NonNull UserAudience getUserAudience(final @NonNull Object sourceObj) { if (!(sourceObj instanceof CommandSourceStack stack)) { throw new IllegalArgumentException(); } @@ -71,7 +73,8 @@ public final class FabricCommandUtil extends CommandUtil { @Override public boolean hasPermission(Object source, String permission) { - return Permissions.check((SharedSuggestionProvider) source, permission); + return Permissions.check((SharedSuggestionProvider) source, + permission, MinecraftServerHolder.get().getOperatorUserPermissionLevel()); } @Override diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a80208d1..bdca63a7 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,8 +24,8 @@ "floodgate.mixins.json" ], "depends": { - "fabricloader": ">=0.14.22", + "fabricloader": ">=0.15.10", "fabric": "*", - "minecraft": ">=1.20.2" + "minecraft": ">=1.20.5" } } \ No newline at end of file From 2680cb9f564b1dd1953385996643b78a58c7bfd3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:01:42 -0400 Subject: [PATCH 63/87] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 667df30e..242aa8fd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,8 +1,8 @@ pipeline { agent any tools { - gradle 'Gradle 7' - jdk 'Java 17' + gradle 'Gradle 8' + jdk 'Java 21' } parameters { From 1277e3f46ec6b5a9707df9ece9dc968496147ba7 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 29 Apr 2024 00:16:20 +0200 Subject: [PATCH 64/87] Update publish.yml to use java 21 (#119) --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 31a0e0d0..bbb20574 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Publish to Modrinth uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 env: From 3d35cdb26e8131607dfc020b4eedfd4005a20d4d Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 29 Apr 2024 00:48:15 +0200 Subject: [PATCH 65/87] Fix modrinth upload task (#120) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5913d590..07f46b8f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -101,7 +101,7 @@ tasks { register("remapModrinthJar", RemapJarTask::class) { dependsOn(shadowJar) - inputFile.set(remapJar.get().archiveFile) + inputFile.set(shadowJar.get().archiveFile) addNestedDependencies = true archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER")) archiveClassifier.set("") From bf91d2667a0044630a61fe14f99c445512d2f37a Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 1 May 2024 19:54:28 +0200 Subject: [PATCH 66/87] Add hacky mixin into ModInjector to ensure floodgate-fabric works with direct connections (#122) --- build.gradle.kts | 5 +++- .../inject/fabric/FabricInjector.java | 19 ++++++++------ .../mixin/GeyserModInjectorMixin.java | 25 +++++++++++++++++++ .../floodgate/util/MixinConfigPlugin.java | 3 +++ src/main/resources/floodgate.mixins.json | 3 ++- 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java diff --git a/build.gradle.kts b/build.gradle.kts index 07f46b8f..eb3c6bbe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,6 +49,9 @@ dependencies { include("org.geysermc.event", "events", "1.1-SNAPSHOT") include("org.lanternpowered", "lmbda", "2.0.0") // used in events + // Geyser dependency for the fun injector mixin :))) + modImplementation("org.geysermc.geyser:fabric:2.2.3-SNAPSHOT") + // cloud include("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") modImplementation("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") @@ -134,7 +137,7 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) uploadFile.set(tasks.named("remapModrinthJar")) - gameVersions.addAll("1.20.5") + gameVersions.addAll("1.20.5", "1.20.6") loaders.add("fabric") diff --git a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java index e5a767f3..15f0a1a0 100644 --- a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java @@ -1,23 +1,35 @@ package org.geysermc.floodgate.inject.fabric; +import com.google.inject.Inject; import io.netty.channel.*; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.inject.CommonPlatformInjector; @RequiredArgsConstructor public final class FabricInjector extends CommonPlatformInjector { + + @Setter @Getter private static FabricInjector instance; @Getter private final boolean injected = true; + @Inject private FloodgateLogger logger; + @Override public void inject() throws Exception { //no-op } public void injectClient(ChannelFuture future) { + if (future.channel().pipeline().names().contains("floodgate-init")) { + logger.debug("Tried to inject twice!"); + return; + } + future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { @Override public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) throws Exception { @@ -47,11 +59,4 @@ public final class FabricInjector extends CommonPlatformInjector { //no-op } - public static FabricInjector getInstance() { - return instance; - } - - public static void setInstance(FabricInjector injector) { - instance = injector; - } } diff --git a/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java b/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java new file mode 100644 index 00000000..4b117adf --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java @@ -0,0 +1,25 @@ +package org.geysermc.floodgate.mixin; + +import io.netty.channel.ChannelFuture; +import org.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.platform.mod.GeyserModInjector; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(value = GeyserModInjector.class, remap = false) +public class GeyserModInjectorMixin { + + @Shadow + private List allServerChannels; + + @Inject(method = "initializeLocalChannel0", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) + public void onChannelAdd(GeyserBootstrap bootstrap, CallbackInfo ci) { + FabricInjector.getInstance().injectClient(this.allServerChannels.get(this.allServerChannels.size() - 1)); + } +} diff --git a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java b/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java index dbedf9e5..b8ee1620 100644 --- a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java +++ b/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java @@ -25,6 +25,9 @@ public class MixinConfigPlugin implements IMixinConfigPlugin { //returns true if fabricproxy-lite is present, therefore loading the mixin. If not present, the mixin will not be loaded. return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); } + if (mixinClassName.equals("org.geysermc.floodgate.mixin.GeyserModInjectorMixin")) { + return FabricLoader.getInstance().isModLoaded("geyser-fabric"); + } return true; } diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index 4b1347eb..ca2c72ec 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -5,9 +5,10 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "ChunkMapMixin", - "ClientIntentionPacketMixinInterface", "ClientIntentionPacketMixin", + "ClientIntentionPacketMixinInterface", "ConnectionMixin", + "GeyserModInjectorMixin", "ServerConnectionListenerMixin" ], "plugin": "org.geysermc.floodgate.util.MixinConfigPlugin", From 3548ae533e74ace51c950cb8da6f8804331776ea Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 1 May 2024 22:20:11 +0200 Subject: [PATCH 67/87] Don't crash when switching local worlds (#123) * Don't crash on server/world switch * prettier --- .../org/geysermc/floodgate/FabricMod.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index a2da1327..090ee15e 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -1,5 +1,7 @@ package org.geysermc.floodgate; +import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import org.geysermc.floodgate.inject.fabric.FabricInjector; import com.google.inject.Guice; import com.google.inject.Injector; @@ -10,6 +12,9 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.module.*; public class FabricMod implements ModInitializer { + + private boolean started; + @Override public void onInitialize() { FabricInjector.setInstance(new FabricInjector()); @@ -30,19 +35,28 @@ public class FabricMod implements ModInitializer { // This can probably be Guice-i-fied but that is beyond me MinecraftServerHolder.set(server); - platform.enable( - new FabricAddonModule(), - new FabricListenerModule(), - new PluginMessageModule() - ); + if (!started) { + platform.enable( + new FabricAddonModule(), + new FabricListenerModule(), + new PluginMessageModule() + ); + started = true; + } long endCtm = System.currentTimeMillis(); injector.getInstance(FloodgateLogger.class) .translatedInfo("floodgate.core.finish", endCtm - ctm); }); - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> { - platform.disable(); - }); + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + ClientLifecycleEvents.CLIENT_STOPPING.register(($) -> { + platform.disable(); + }); + } else { + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> { + platform.disable(); + }); + } } } From fdd816dbf6b191f582c7be890c44fe3a9eaebb7d Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 8 May 2024 18:34:18 +0200 Subject: [PATCH 68/87] Resolve config loading issues (#124) * Resolve config loading issue * remove unused common dependency, uncomment mavenLocal() * Use timestamped floodgate core dependency * use isTransitive instead of manual exclusions --- build.gradle.kts | 10 ++--- .../org/geysermc/floodgate/FabricMod.java | 3 +- .../floodgate/util/FabricTemplateReader.java | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java diff --git a/build.gradle.kts b/build.gradle.kts index eb3c6bbe..e6045a51 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,9 +22,9 @@ dependencies { modImplementation("net.fabricmc.fabric-api:fabric-api:0.97.6+1.20.5") // Base Floodgate - implementation("org.geysermc.floodgate:core:2.2.3-SNAPSHOT") - shadow("org.geysermc.floodgate:core:2.2.3-SNAPSHOT") { isTransitive = false } - shadow("org.geysermc.floodgate:api:2.2.3-SNAPSHOT") { isTransitive = false } + implementation("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") + shadow("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") { isTransitive = false } + shadow("org.geysermc.floodgate:api:2.2.3-20240508.151752-4") { isTransitive = false } // Requires relocation shadow("org.bstats:bstats-base:3.0.2") @@ -50,7 +50,7 @@ dependencies { include("org.lanternpowered", "lmbda", "2.0.0") // used in events // Geyser dependency for the fun injector mixin :))) - modImplementation("org.geysermc.geyser:fabric:2.2.3-SNAPSHOT") + modCompileOnly("org.geysermc.geyser:fabric:2.2.3-SNAPSHOT") { isTransitive = false } // cloud include("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") @@ -62,7 +62,7 @@ dependencies { } repositories { - //mavenLocal() + // mavenLocal() mavenCentral() maven("https://maven.fabricmc.net/") maven("https://repo.opencollab.dev/main/") diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java index 090ee15e..e93365fd 100644 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ b/src/main/java/org/geysermc/floodgate/FabricMod.java @@ -10,6 +10,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.module.*; +import org.geysermc.floodgate.util.FabricTemplateReader; public class FabricMod implements ModInitializer { @@ -20,7 +21,7 @@ public class FabricMod implements ModInitializer { FabricInjector.setInstance(new FabricInjector()); Injector injector = Guice.createInjector( - new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate")), + new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate"), new FabricTemplateReader()), new FabricPlatformModule() ); diff --git a/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java b/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java new file mode 100644 index 00000000..b772fde6 --- /dev/null +++ b/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java @@ -0,0 +1,38 @@ +package org.geysermc.floodgate.util; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.geysermc.configutils.file.template.TemplateReader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Optional; + +public class FabricTemplateReader implements TemplateReader { + + private final ModContainer container; + + public FabricTemplateReader() { + container = FabricLoader.getInstance().getModContainer("floodgate").orElseThrow(); + } + + @Override + public BufferedReader read(String configName) { + Optional optional = container.findPath(configName); + if (optional.isPresent()) { + try { + InputStream stream = optional.get().getFileSystem() + .provider() + .newInputStream(optional.get()); + return new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return null; + } +} From 0e4ecb5e989fad93f6f76cd88c6c5b46cc1a753e Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 13 Jun 2024 17:47:20 +0200 Subject: [PATCH 69/87] Update to 1.21 --- build.gradle.kts | 6 +++--- .../floodgate/pluginmessage/payloads/FormPayload.java | 8 +++----- .../floodgate/pluginmessage/payloads/PacketPayload.java | 8 +++----- .../floodgate/pluginmessage/payloads/SkinPayload.java | 8 +++----- .../floodgate/pluginmessage/payloads/TransferPayload.java | 8 +++----- src/main/resources/fabric.mod.json | 4 ++-- src/main/resources/floodgate.mixins.json | 2 +- 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e6045a51..2f31d2fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,12 +14,12 @@ loom { dependencies { //to change the versions see the gradle.properties file - minecraft("com.mojang:minecraft:1.20.5") + minecraft("com.mojang:minecraft:1.21") mappings(loom.officialMojangMappings()) - modImplementation("net.fabricmc:fabric-loader:0.15.10") + modImplementation("net.fabricmc:fabric-loader:0.15.11") // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation("net.fabricmc.fabric-api:fabric-api:0.97.6+1.20.5") + modImplementation("net.fabricmc.fabric-api:fabric-api:0.100.1+1.21") // Base Floodgate implementation("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java index 132e39b9..6ab4add4 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java @@ -3,11 +3,12 @@ package org.geysermc.floodgate.pluginmessage.payloads; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; import org.checkerframework.checker.nullness.qual.NonNull; public record FormPayload(byte[] data) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(FormPayload::write, FormPayload::new); - public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:form"); + public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:form")); private FormPayload(FriendlyByteBuf friendlyByteBuf) { this(friendlyByteBuf.readByteArray()); @@ -17,11 +18,8 @@ public record FormPayload(byte[] data) implements CustomPacketPayload { friendlyByteBuf.writeByteArray(this.data); } + @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } - - public byte[] data() { - return this.data; - } } \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java index 89276b58..08bc2c60 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java @@ -3,11 +3,12 @@ package org.geysermc.floodgate.pluginmessage.payloads; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; import org.checkerframework.checker.nullness.qual.NonNull; public record PacketPayload(byte[] data) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(PacketPayload::write, PacketPayload::new); - public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:packet"); + public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:packet")); private PacketPayload(FriendlyByteBuf friendlyByteBuf) { this(friendlyByteBuf.readByteArray()); @@ -17,11 +18,8 @@ public record PacketPayload(byte[] data) implements CustomPacketPayload { friendlyByteBuf.writeByteArray(this.data); } + @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } - - public byte[] data() { - return this.data; - } } \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java index a2507e56..900f00c7 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java @@ -3,11 +3,12 @@ package org.geysermc.floodgate.pluginmessage.payloads; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; import org.checkerframework.checker.nullness.qual.NonNull; public record SkinPayload(byte[] data) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(SkinPayload::write, SkinPayload::new); - public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:skin"); + public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:skin")); private SkinPayload(FriendlyByteBuf friendlyByteBuf) { this(friendlyByteBuf.readByteArray()); @@ -17,11 +18,8 @@ public record SkinPayload(byte[] data) implements CustomPacketPayload { friendlyByteBuf.writeByteArray(this.data); } + @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } - - public byte[] data() { - return this.data; - } } \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java index b291b92d..5c16b796 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java @@ -3,11 +3,12 @@ package org.geysermc.floodgate.pluginmessage.payloads; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; import org.checkerframework.checker.nullness.qual.NonNull; public record TransferPayload(byte[] data) implements CustomPacketPayload { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(TransferPayload::write, TransferPayload::new); - public static final CustomPacketPayload.Type TYPE = CustomPacketPayload.createType("floodgate:transfer"); + public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:transfer")); private TransferPayload(FriendlyByteBuf friendlyByteBuf) { this(friendlyByteBuf.readByteArray()); @@ -17,11 +18,8 @@ public record TransferPayload(byte[] data) implements CustomPacketPayload { friendlyByteBuf.writeByteArray(this.data); } + @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } - - public byte[] data() { - return this.data; - } } \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index bdca63a7..24b30092 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,8 +24,8 @@ "floodgate.mixins.json" ], "depends": { - "fabricloader": ">=0.15.10", + "fabricloader": ">=0.15.11", "fabric": "*", - "minecraft": ">=1.20.5" + "minecraft": ">=1.21" } } \ No newline at end of file diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index ca2c72ec..ded3017f 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "org.geysermc.floodgate.mixin", - "compatibilityLevel": "JAVA_16", + "compatibilityLevel": "JAVA_21", "mixins": [ "ChunkMapMixin", "ClientIntentionPacketMixin", From e5879557f5b36d604660722f0eb2415bd432d06f Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 13 Jun 2024 17:50:27 +0200 Subject: [PATCH 70/87] Show 1.21 being supported on modrinth --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2f31d2fb..525d5124 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -137,7 +137,7 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) uploadFile.set(tasks.named("remapModrinthJar")) - gameVersions.addAll("1.20.5", "1.20.6") + gameVersions.addAll("1.21") loaders.add("fabric") From 587717b07b52d6d11bf2be9ba9ad4a640b67814a Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 13 Jun 2024 17:51:54 +0200 Subject: [PATCH 71/87] Yeet Jenkinsfile --- Jenkinsfile | 72 ----------------------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 242aa8fd..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,72 +0,0 @@ -pipeline { - agent any - tools { - gradle 'Gradle 8' - jdk 'Java 21' - } - - parameters { - booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD') - } - - options { - buildDiscarder(logRotator(artifactNumToKeepStr: '20')) - } - - stages { - stage ('Build') { - steps { - sh './gradlew clean build --refresh-dependencies' - } - post { - success { - archiveArtifacts artifacts: 'build/libs/floodgate-fabric.jar', fingerprint: true - } - } - } - } - - post { - always { - script { - def changeLogSets = currentBuild.changeSets - def message = "**Changes:**" - - if (changeLogSets.size() == 0) { - message += "\n*No changes.*" - } else { - def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") - def count = 0; - def extra = 0; - for (int i = 0; i < changeLogSets.size(); i++) { - def entries = changeLogSets[i].items - for (int j = 0; j < entries.length; j++) { - if (count <= 10) { - def entry = entries[j] - def commitId = entry.commitId.substring(0, 6) - message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" - count++ - } else { - extra++; - } - } - } - - if (extra != 0) { - message += "\n - ${extra} more commits" - } - } - - env.changes = message - } - deleteDir() - script { - if(!params.SKIP_DISCORD) { - withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Floodgate-Fabric)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK - } - } - } - } - } -} From b219b96819196d60df67f8a096e28e9ef94dc81f Mon Sep 17 00:00:00 2001 From: Alex <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Sun, 21 Jul 2024 00:37:46 +0200 Subject: [PATCH 72/87] Add an .editorconfig (#121) --- .editorconfig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c32bad0c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +tab_width = 4 +max_line_length = off + +[*.java] +ij_java_class_count_to_use_import_on_demand = 9999 +ij_java_doc_align_exception_comments = false +ij_java_doc_align_param_comments = false From 5c760a5c1e7bc5d2a6c9d657682fbdc51f6df732 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 23 Jul 2024 20:05:28 +0200 Subject: [PATCH 73/87] Fix: Reading of custom payloads (#128) --- .../floodgate/pluginmessage/payloads/FormPayload.java | 8 +++++--- .../floodgate/pluginmessage/payloads/PacketPayload.java | 8 +++++--- .../floodgate/pluginmessage/payloads/SkinPayload.java | 8 +++++--- .../floodgate/pluginmessage/payloads/TransferPayload.java | 8 +++++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java index 6ab4add4..475d138f 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java @@ -1,5 +1,6 @@ package org.geysermc.floodgate.pluginmessage.payloads; +import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -11,15 +12,16 @@ public record FormPayload(byte[] data) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:form")); private FormPayload(FriendlyByteBuf friendlyByteBuf) { - this(friendlyByteBuf.readByteArray()); + this(ByteBufUtil.getBytes(friendlyByteBuf)); + friendlyByteBuf.readerIndex(friendlyByteBuf.readerIndex() + this.data.length); } private void write(FriendlyByteBuf friendlyByteBuf) { - friendlyByteBuf.writeByteArray(this.data); + friendlyByteBuf.writeBytes(this.data); } @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } -} \ No newline at end of file +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java index 08bc2c60..b00021b5 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java @@ -1,5 +1,6 @@ package org.geysermc.floodgate.pluginmessage.payloads; +import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -11,15 +12,16 @@ public record PacketPayload(byte[] data) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:packet")); private PacketPayload(FriendlyByteBuf friendlyByteBuf) { - this(friendlyByteBuf.readByteArray()); + this(ByteBufUtil.getBytes(friendlyByteBuf)); + friendlyByteBuf.readerIndex(friendlyByteBuf.readerIndex() + this.data.length); } private void write(FriendlyByteBuf friendlyByteBuf) { - friendlyByteBuf.writeByteArray(this.data); + friendlyByteBuf.writeBytes(this.data); } @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } -} \ No newline at end of file +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java index 900f00c7..a1e6eee6 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java @@ -1,5 +1,6 @@ package org.geysermc.floodgate.pluginmessage.payloads; +import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -11,15 +12,16 @@ public record SkinPayload(byte[] data) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:skin")); private SkinPayload(FriendlyByteBuf friendlyByteBuf) { - this(friendlyByteBuf.readByteArray()); + this(ByteBufUtil.getBytes(friendlyByteBuf)); + friendlyByteBuf.readerIndex(friendlyByteBuf.readerIndex() + this.data.length); } private void write(FriendlyByteBuf friendlyByteBuf) { - friendlyByteBuf.writeByteArray(this.data); + friendlyByteBuf.writeBytes(this.data); } @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } -} \ No newline at end of file +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java index 5c16b796..63963279 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java +++ b/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java @@ -1,5 +1,6 @@ package org.geysermc.floodgate.pluginmessage.payloads; +import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @@ -11,15 +12,16 @@ public record TransferPayload(byte[] data) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new Type<>(ResourceLocation.parse("floodgate:transfer")); private TransferPayload(FriendlyByteBuf friendlyByteBuf) { - this(friendlyByteBuf.readByteArray()); + this(ByteBufUtil.getBytes(friendlyByteBuf)); + friendlyByteBuf.readerIndex(friendlyByteBuf.readerIndex() + this.data.length); } private void write(FriendlyByteBuf friendlyByteBuf) { - friendlyByteBuf.writeByteArray(this.data); + friendlyByteBuf.writeBytes(this.data); } @Override public CustomPacketPayload.@NonNull Type type() { return TYPE; } -} \ No newline at end of file +} From 4502fd20cd523ff1ceda53e8f87bbd3957dbc5d7 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 8 Aug 2024 19:46:22 +0200 Subject: [PATCH 74/87] Back to mixin compatability level 17 --- src/main/resources/floodgate.mixins.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/floodgate.mixins.json b/src/main/resources/floodgate.mixins.json index ded3017f..ca8e5597 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/src/main/resources/floodgate.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "org.geysermc.floodgate.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_17", "mixins": [ "ChunkMapMixin", "ClientIntentionPacketMixin", From 0ef06597f18fd8a233cdac77d74a118fbc49ebb5 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 11 Sep 2024 02:00:55 +0800 Subject: [PATCH 75/87] Support neoforge platform, move to using architectury (#125) * rebase * split fabric/neoforge specific code * more progress * dependency configuration * exclude more junk * neo, you make me go insane * neoforge seems to work! * some minor cleanup, add neoforge command module * mixin config plugin shenanigans * fix language strings loading * some cleanup, yeet Jenkinsfile * proper repository declaration using plugin, target jitpack branch * oops * address reviews * Update for 1.21 * some minor fixes * Fix modrinth task, add floodgate version command mixin to disable version checking, update to loom 1.7, update cloud, update floodgate core to not use my branch * oops * what on earth is going on now * neoforge works again!!!!!!!!! * Address review, dont rely on locals in mixin * modrinth version/name changes, similar to geyser * Update README.md Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> * Improve handling of PayloadRegistrar this took years off my life * address review * Move blossom version declaration to libs.versions.toml * remove unused versions from catalogue * Only run modrinth task if successful & on Geyser repo * cleanup & fix gh actions building/archiving * run and uses are different steps --------- Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com> --- .github/workflows/publish.yml | 22 ++- README.md | 13 +- build-logic/build.gradle.kts | 23 +++ build-logic/settings.gradle.kts | 11 ++ build-logic/src/main/kotlin/LibsAccessor.kt | 6 + build-logic/src/main/kotlin/extensions.kt | 36 ++++ ...oodgate-modded.base-conventions.gradle.kts | 37 +++++ .../floodgate-modded.build-logic.gradle.kts | 14 ++ ...ate-modded.platform-conventions.gradle.kts | 134 +++++++++++++++ ...gate-modded.publish-conventions.gradle.kts | 15 ++ ...dgate-modded.shadow-conventions.gradle.kts | 37 +++++ build.gradle.kts | 157 ++---------------- fabric/build.gradle.kts | 46 +++++ fabric/gradle.properties | 1 + .../util/fabric/MixinConfigPluginImpl.java | 14 ++ .../platform/fabric/FabricFloodgateMod.java | 54 ++++++ .../listener/FabricEventRegistration.java | 14 ++ .../fabric}/module/FabricCommandModule.java | 13 +- .../fabric/module/FabricPlatformModule.java | 40 +++++ .../FabricPluginMessageRegistration.java | 12 +- .../FabricPluginMessageUtils.java | 21 +-- fabric/src/main/resources/fabric.mod.json | 30 ++++ gradle.properties | 9 +- gradle/libs.versions.toml | 57 +++++++ gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 51 ++++-- gradlew.bat | 35 ++-- mod/build.gradle.kts | 33 ++++ .../geysermc/floodgate/mod/FloodgateMod.java | 59 +++++++ .../floodgate/mod}/MinecraftServerHolder.java | 2 +- .../floodgate/mod/data/ModDataAddon.java | 14 +- .../floodgate/mod/data/ModDataHandler.java | 24 +-- .../floodgate/mod/inject/ModInjector.java | 16 +- .../mod/listener/ModEventListener.java | 15 +- .../mod}/logger/Log4jFloodgateLogger.java | 8 +- .../floodgate/mod}/mixin/ChunkMapMixin.java | 2 +- .../mixin/ClientIntentionPacketMixin.java | 2 +- .../ClientIntentionPacketMixinInterface.java | 2 +- .../floodgate/mod}/mixin/ConnectionMixin.java | 2 +- .../mod/mixin/FloodgateUtilMixin.java | 49 ++++++ .../mod}/mixin/GeyserModInjectorMixin.java | 8 +- .../mixin/ServerConnectionListenerMixin.java | 10 +- .../floodgate/mod/module/ModAddonModule.java | 14 +- .../mod/module/ModListenerModule.java | 21 +++ .../mod/module/ModPlatformModule.java | 77 +++++++++ .../mod/pluginmessage/ModSkinApplier.java | 14 +- .../pluginmessage/payloads/FormPayload.java | 2 +- .../pluginmessage/payloads/PacketPayload.java | 2 +- .../pluginmessage/payloads/SkinPayload.java | 2 +- .../payloads/TransferPayload.java | 2 +- .../floodgate/mod/util/ModCommandUtil.java | 27 +-- .../mod/util/ModMixinConfigPlugin.java | 25 ++- .../floodgate/mod/util/ModPlatformUtils.java | 8 +- .../floodgate/mod/util/ModTemplateReader.java | 25 +++ .../main/resources/floodgate.accesswidener | 0 .../src}/main/resources/floodgate.mixins.json | 5 +- neoforge/build.gradle.kts | 61 +++++++ neoforge/gradle.properties | 1 + .../neoforge/ModMixinConfigPluginImpl.java | 13 ++ .../neoforge/NeoForgeFloodgateMod.java | 101 +++++++++++ .../listener/NeoForgeEventRegistration.java | 20 +++ .../mixin/NeoForgeFloodgateUtilMixin.java | 27 +++ .../module/NeoForgeCommandModule.java | 29 ++++ .../module/NeoForgePlatformModule.java | 46 +++++ .../NeoForgePluginMessageRegistration.java | 39 +++++ .../NeoForgePluginMessageUtils.java | 37 +++++ .../resources/META-INF/neoforge.mods.toml | 27 +++ .../resources/floodgate_neoforge.mixins.json | 12 ++ settings.gradle.kts | 30 +++- .../org/geysermc/floodgate/FabricMod.java | 63 ------- .../listener/FabricEventRegistration.java | 14 -- .../module/FabricListenerModule.java | 21 --- .../module/FabricPlatformModule.java | 107 ------------ .../floodgate/util/FabricTemplateReader.java | 38 ----- src/main/resources/fabric.mod.json | 31 ---- 76 files changed, 1507 insertions(+), 586 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/LibsAccessor.kt create mode 100644 build-logic/src/main/kotlin/extensions.kt create mode 100644 build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts create mode 100644 build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts create mode 100644 fabric/build.gradle.kts create mode 100644 fabric/gradle.properties create mode 100644 fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java create mode 100644 fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java create mode 100644 fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java rename {src/main/java/org/geysermc/floodgate => fabric/src/main/java/org/geysermc/floodgate/platform/fabric}/module/FabricCommandModule.java (64%) create mode 100644 fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java rename {src/main/java/org/geysermc/floodgate => fabric/src/main/java/org/geysermc/floodgate/platform/fabric}/pluginmessage/FabricPluginMessageRegistration.java (85%) rename {src/main/java/org/geysermc/floodgate => fabric/src/main/java/org/geysermc/floodgate/platform/fabric}/pluginmessage/FabricPluginMessageUtils.java (61%) create mode 100644 fabric/src/main/resources/fabric.mod.json create mode 100644 gradle/libs.versions.toml create mode 100644 mod/build.gradle.kts create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/MinecraftServerHolder.java (92%) rename src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java => mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java (78%) rename src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java => mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java (89%) rename src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java => mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java (78%) rename src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java => mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java (55%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/logger/Log4jFloodgateLogger.java (88%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ChunkMapMixin.java (88%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ClientIntentionPacketMixin.java (92%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ClientIntentionPacketMixinInterface.java (90%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ConnectionMixin.java (87%) create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/GeyserModInjectorMixin.java (71%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/mixin/ServerConnectionListenerMixin.java (73%) rename src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java => mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java (63%) create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java rename src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java => mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java (90%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/FormPayload.java (94%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/PacketPayload.java (94%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/SkinPayload.java (94%) rename {src/main/java/org/geysermc/floodgate => mod/src/main/java/org/geysermc/floodgate/mod}/pluginmessage/payloads/TransferPayload.java (94%) rename src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java => mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java (82%) rename src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java => mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java (56%) rename src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java => mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java (67%) create mode 100644 mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java rename {src => mod/src}/main/resources/floodgate.accesswidener (100%) rename {src => mod/src}/main/resources/floodgate.mixins.json (69%) create mode 100644 neoforge/build.gradle.kts create mode 100644 neoforge/gradle.properties create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java create mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java create mode 100644 neoforge/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge/src/main/resources/floodgate_neoforge.mixins.json delete mode 100644 src/main/java/org/geysermc/floodgate/FabricMod.java delete mode 100644 src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java delete mode 100644 src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java delete mode 100644 src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java delete mode 100644 src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java delete mode 100644 src/main/resources/fabric.mod.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bbb20574..08b4c8ba 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,23 +5,27 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9 - - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 - - uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758 + - name: Setup Gradle + uses: GeyserMC/actions/setup-gradle-composite@master with: - distribution: 'temurin' - java-version: 21 + setup-java_java-version: 21 + + - name: Build Floodgate-Modded + run: ./gradlew build + - name: Publish to Modrinth + if: ${{ success() && github.repository == 'GeyserMC/Floodgate-Modded' && github.ref_name == 'master' }} uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 env: MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} with: arguments: modrinth gradle-home-cache-cleanup: true + - name: Archive Artifacts - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 + uses: GeyserMC/actions/upload-multi-artifact@master if: success() with: - name: Floodgate Fabric - path: build/libs/floodgate-fabric.jar - if-no-files-found: error \ No newline at end of file + artifacts: | + Floodgate-Fabric:fabric/build/libs/floodgate-fabric.jar + Floodgate-NeoForge:neoforge/build/libs/floodgate-neoforge.jar diff --git a/README.md b/README.md index 15381f41..a1a73002 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ -# Floodgate-Fabric -Fabric port of the hybrid mode plugin to allow for connections from Geyser to join online mode servers. +# Floodgate-Modded +Fabric and NeoForge ports of the hybrid mode plugin to allow for connections from Geyser to join online mode servers. -Download: https://ci.opencollab.dev/job/GeyserMC/job/Floodgate-Fabric/job/master/ +Hybrid mode mod to allow for connections from Geyser to join online mode servers. +Geyser is an open collaboration project by CubeCraft Games. + +See the Floodgate section in the GeyserMC Wiki for more info about what Floodgate is, how you setup Floodgate and known issues/caveats. +Additionally, it includes a more in-depth look into how Floodgate works and the Floodgate API. + +Wiki: https://wiki.geysermc.org/floodgate/ +Download: https://modrinth.com/mod/floodgate diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000..47900794 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() + mavenCentral() + maven("https://maven.architectury.dev/") + maven("https://maven.fabricmc.net/") + maven("https://maven.neoforged.net/releases/") +} + +dependencies { + // Used to access version catalogue from the convention plugins + // this is OK as long as the same version catalog is used in the main build and build-logic + // see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192 + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(libs.indra) + implementation(libs.shadow) + implementation(libs.architectury.plugin) + implementation(libs.architectury.loom) + implementation(libs.minotaur) +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 00000000..63bde189 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,11 @@ +@file:Suppress("UnstableApiUsage") + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" \ No newline at end of file diff --git a/build-logic/src/main/kotlin/LibsAccessor.kt b/build-logic/src/main/kotlin/LibsAccessor.kt new file mode 100644 index 00000000..2a0c09eb --- /dev/null +++ b/build-logic/src/main/kotlin/LibsAccessor.kt @@ -0,0 +1,6 @@ +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType + +val Project.libs: LibrariesForLibs + get() = rootProject.extensions.getByType() \ No newline at end of file diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt new file mode 100644 index 00000000..f4c2269e --- /dev/null +++ b/build-logic/src/main/kotlin/extensions.kt @@ -0,0 +1,36 @@ +import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.provider.Provider + +val providedDependencies = mutableMapOf>() + +fun Project.provided(pattern: String, name: String, excludedOn: Int = 0b110) { + providedDependencies.getOrPut(project.name) { mutableSetOf() } + .add("${calcExclusion(pattern, 0b100, excludedOn)}:${calcExclusion(name, 0b10, excludedOn)}") +} + +fun Project.provided(dependency: ProjectDependency) = + provided(dependency.group!!, dependency.name) + +fun Project.provided(dependency: MinimalExternalModuleDependency) = + provided(dependency.module.group, dependency.module.name) + +fun Project.provided(provider: Provider) = + provided(provider.get()) + +fun getProvidedDependenciesForProject(projectName: String): MutableSet { + return providedDependencies.getOrDefault(projectName, emptySet()).toMutableSet() +} + +private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String = + if (excludedOn and bit > 0) section else "" + +fun projectVersion(project: Project): String = + project.version.toString().replace("SNAPSHOT", "b" + buildNumber()) + +fun versionName(project: Project): String = + "Floodgate-" + project.name.replaceFirstChar { it.uppercase() } + "-" + projectVersion(project) + +fun buildNumber(): Int = + (System.getenv("GITHUB_RUN_NUMBER"))?.let { Integer.parseInt(it) } ?: -1 diff --git a/build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts new file mode 100644 index 00000000..77ef1e80 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.base-conventions.gradle.kts @@ -0,0 +1,37 @@ +plugins { + `java-library` + id("net.kyori.indra") +} + +dependencies { + compileOnly("org.checkerframework", "checker-qual", "3.19.0") +} + +indra { + github("GeyserMC", "floodgate-modded") { + ci(true) + issues(true) + scm(true) + } + mitLicense() + + javaVersions { + target(21) + } +} + +tasks { + processResources { + filesMatching(listOf("fabric.mod.json", "META-INF/neoforge.mods.toml")) { + expand( + "id" to "floodgate", + "name" to "Floodgate", + "version" to project.version, + "description" to project.description, + "url" to "https://geysermc.org", + "author" to "GeyserMC", + "minecraft_version" to libs.versions.minecraft.version.get() + ) + } + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts new file mode 100644 index 00000000..ff9b5674 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.build-logic.gradle.kts @@ -0,0 +1,14 @@ +repositories { + // mavenLocal() + mavenCentral() + maven("https://maven.fabricmc.net/") + maven("https://maven.neoforged.net/releases") + maven("https://repo.opencollab.dev/main/") + maven("https://jitpack.io") { + content { + includeGroupByRegex("com.github.*") + } + } + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts new file mode 100644 index 00000000..71b70a27 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -0,0 +1,134 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id("floodgate-modded.publish-conventions") + id("architectury-plugin") + id("dev.architectury.loom") + id("com.modrinth.minotaur") +} + +// These are all provided by Minecraft/server platforms +provided("com.google.code.gson", "gson") +provided("org.slf4j", ".*") +provided("com.google.guava", "guava") +provided("org.ow2.asm", "asm") +provided("com.nukkitx.fastutil", ".*") + +// these we just don't want to include +provided("org.checkerframework", ".*") +provided("com.google.errorprone", ".*") +provided("com.github.spotbugs", "spotbugs-annotations") +provided("com.google.code.findbugs", ".*") + +// cloud-fabric/cloud-neoforge jij's all cloud depends already +provided("org.incendo", ".*") +provided("io.leangen.geantyref", "geantyref") + +architectury { + minecraft = libs.versions.minecraft.version.get() +} + +loom { + silentMojangMappingsLicense() +} + +configurations { + create("includeTransitive").isTransitive = true +} + +dependencies { + minecraft(libs.minecraft) + mappings(loom.officialMojangMappings()) + + // These are under our own namespace + shadow(libs.floodgate.api) { isTransitive = false } + shadow(libs.floodgate.core) { isTransitive = false } + + // Requires relocation + shadow(libs.bstats) { isTransitive = false } + + // Shadow & relocate these since the (indirectly) depend on quite old dependencies + shadow(libs.guice) { isTransitive = false } + shadow(libs.configutils) { + exclude("org.checkerframework") + exclude("com.google.errorprone") + exclude("com.github.spotbugs") + exclude("com.nukkitx.fastutil") + } + +} + +tasks { + sourcesJar { + archiveClassifier.set("sources") + from(sourceSets.main.get().allSource) + } + + shadowJar { + // Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be + configurations = listOf(project.configurations.shadow.get()) + + // Relocate these + relocate("org.bstats", "org.geysermc.floodgate.shadow.bstats") + relocate("com.google.inject", "org.geysermc.floodgate.shadow.google.inject") + relocate("org.yaml", "org.geysermc.floodgate.shadow.org.yaml") + + // The remapped shadowJar is the final desired mod jar + archiveVersion.set(project.version.toString()) + archiveClassifier.set("shaded") + } + + remapJar { + dependsOn(shadowJar) + inputFile.set(shadowJar.get().archiveFile) + archiveClassifier.set("") + archiveVersion.set("") + } + + register("remapModrinthJar", RemapJarTask::class) { + dependsOn(shadowJar) + inputFile.set(shadowJar.get().archiveFile) + archiveVersion.set(versionName(project)) + archiveClassifier.set("") + } + + // Readme sync + modrinth.get().dependsOn(tasks.modrinthSyncBody) +} + +afterEvaluate { + val providedDependencies = getProvidedDependenciesForProject(project.name) + + // These are shaded, no need to JiJ them + configurations["shadow"].resolvedConfiguration.resolvedArtifacts.forEach {shadowed -> + val string = "${shadowed.moduleVersion.id.group}:${shadowed.moduleVersion.id.name}" + println("Not including shadowed dependency: $string") + providedDependencies.add(string) + } + + configurations["includeTransitive"].resolvedConfiguration.resolvedArtifacts.forEach { dep -> + if (!providedDependencies.contains("${dep.moduleVersion.id.group}:${dep.moduleVersion.id.name}") + and !providedDependencies.contains("${dep.moduleVersion.id.group}:.*")) { + println("Including dependency via JiJ: ${dep.id}") + dependencies.add("include", dep.moduleVersion.id.toString()) + } else { + println("Not including ${dep.id} for ${project.name}!") + } + } +} + +modrinth { + token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token? + projectId.set("bWrNNfkb") + versionName.set(versionName(project)) + versionNumber.set(projectVersion(project)) + versionType.set("release") + changelog.set("A changelog can be found at https://github.com/GeyserMC/Floodgate-Modded/commits") + + syncBodyFrom.set(rootProject.file("README.md").readText()) + + uploadFile.set(tasks.getByPath("remapModrinthJar")) + gameVersions.add(libs.minecraft.get().version as String) + gameVersions.add("1.21.1") + failSilently.set(false) +} diff --git a/build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts new file mode 100644 index 00000000..fdfa4ef6 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.publish-conventions.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("floodgate-modded.shadow-conventions") + id("net.kyori.indra.publishing") +} + +indra { + publishSnapshotsTo("geysermc", "https://repo.opencollab.dev/maven-snapshots") + publishReleasesTo("geysermc", "https://repo.opencollab.dev/maven-releases") +} + +publishing { + // skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651 + val javaComponent = project.components["java"] as AdhocComponentWithVariants + javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts new file mode 100644 index 00000000..e4affd71 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate-modded.shadow-conventions.gradle.kts @@ -0,0 +1,37 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + id("floodgate-modded.base-conventions") + id("com.github.johnrengelman.shadow") +} + +tasks { + named("jar") { + archiveClassifier.set("unshaded") + from(project.rootProject.file("LICENSE")) + } + val shadowJar = named("shadowJar") { + archiveBaseName.set(project.name) + archiveVersion.set("") + archiveClassifier.set("") + + val sJar: ShadowJar = this + + doFirst { + providedDependencies[project.name]?.forEach { string -> + sJar.dependencies { + println("Excluding $string from ${project.name}") + exclude(dependency(string)) + } + } + + sJar.dependencies { + exclude(dependency("org.checkerframework:checker-qual:.*")) + exclude(dependency("org.jetbrains:annotations:.*")) + } + } + } + named("build") { + dependsOn(shadowJar) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 525d5124..d81fbb1c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,147 +1,24 @@ -import net.fabricmc.loom.task.RemapJarTask - plugins { - id("com.github.johnrengelman.shadow") version "8.1.1" - id("fabric-loom") version "1.6-SNAPSHOT" - id("java") - id("maven-publish") - id("com.modrinth.minotaur") version "2.+" + `java-library` + id("floodgate-modded.build-logic") + alias(libs.plugins.lombok) apply false } -loom { - accessWidenerPath = file("src/main/resources/floodgate.accesswidener") -} +val platforms = setOf( + projects.fabric, + projects.neoforge, + projects.mod +).map { it.dependencyProject } -dependencies { - //to change the versions see the gradle.properties file - minecraft("com.mojang:minecraft:1.21") - mappings(loom.officialMojangMappings()) - modImplementation("net.fabricmc:fabric-loader:0.15.11") - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation("net.fabricmc.fabric-api:fabric-api:0.100.1+1.21") - - // Base Floodgate - implementation("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") - shadow("org.geysermc.floodgate:core:2.2.3-20240508.151752-4") { isTransitive = false } - shadow("org.geysermc.floodgate:api:2.2.3-20240508.151752-4") { isTransitive = false } - - // Requires relocation - shadow("org.bstats:bstats-base:3.0.2") - - // Shadow & relocate these since the (indirectly) depend on quite old dependencies - shadow("com.google.inject:guice:6.0.0") { isTransitive = false } - shadow("org.geysermc.configutils:configutils:1.0-SNAPSHOT") { - exclude("org.checkerframework") - exclude("com.google.errorprone") - exclude("com.github.spotbugs") - exclude("com.nukkitx.fastutil") +subprojects { + apply { + plugin("java-library") + plugin("io.freefair.lombok") + plugin("floodgate-modded.build-logic") } - include("aopalliance:aopalliance:1.0") - include("javax.inject:javax.inject:1") - include("jakarta.inject:jakarta.inject-api:2.0.1") - include("org.java-websocket:Java-WebSocket:1.5.2") - - // Just like Geyser, include these - include("org.geysermc.geyser", "common", "2.2.3-SNAPSHOT") - include("org.geysermc.cumulus", "cumulus", "1.1.2") - include("org.geysermc.event", "events", "1.1-SNAPSHOT") - include("org.lanternpowered", "lmbda", "2.0.0") // used in events - - // Geyser dependency for the fun injector mixin :))) - modCompileOnly("org.geysermc.geyser:fabric:2.2.3-SNAPSHOT") { isTransitive = false } - - // cloud - include("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") - modImplementation("org.incendo:cloud-fabric:2.0.0-SNAPSHOT") - - // Lombok - compileOnly("org.projectlombok:lombok:1.18.32") - annotationProcessor("org.projectlombok:lombok:1.18.32") -} - -repositories { - // mavenLocal() - mavenCentral() - maven("https://maven.fabricmc.net/") - maven("https://repo.opencollab.dev/main/") - maven("https://jitpack.io") { - content { - includeGroupByRegex("com.github.*") - } + when (this) { + in platforms -> plugins.apply("floodgate-modded.platform-conventions") + else -> plugins.apply("floodgate-modded.base-conventions") } - maven("https://oss.sonatype.org/content/repositories/snapshots/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") -} - -java { - withSourcesJar() -} - -tasks { - shadowJar { - configurations = listOf(project.configurations.shadow.get()) - - relocate("org.bstats", "org.geysermc.floodgate.shadow.bstats") - relocate("com.google.inject", "org.geysermc.floodgate.shadow.google.inject") - relocate("org.yaml", "org.geysermc.floodgate.shadow.org.yaml") - } - - processResources { - filesMatching("fabric.mod.json") { - expand("version" to project.version) - } - } - - remapJar { - dependsOn(shadowJar) - mustRunAfter(shadowJar) - inputFile.set(shadowJar.get().archiveFile) - addNestedDependencies = true // todo? - archiveFileName.set("floodgate-fabric.jar") - } - - register("remapModrinthJar", RemapJarTask::class) { - dependsOn(shadowJar) - inputFile.set(shadowJar.get().archiveFile) - addNestedDependencies = true - archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER")) - archiveClassifier.set("") - } -} - -publishing { - publications { - register("publish", MavenPublication::class) { - from(project.components["java"]) - - // skip shadow jar from publishing. Workaround for https://github.com/johnrengelman/shadow/issues/651 - val javaComponent = project.components["java"] as AdhocComponentWithVariants - javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { skip() } - } - } - - repositories { - mavenLocal() - } -} - -modrinth { - token.set(System.getenv("MODRINTH_TOKEN")) // Prevent GitHub Actions from caching empty Modrinth token - projectId.set("bWrNNfkb") - versionNumber.set(project.version as String + "-" + System.getenv("GITHUB_RUN_NUMBER")) - versionType.set("beta") - changelog.set("A changelog can be found at https://github.com/GeyserMC/Floodgate-Fabric/commits") - - syncBodyFrom.set(rootProject.file("README.md").readText()) - - uploadFile.set(tasks.named("remapModrinthJar")) - gameVersions.addAll("1.21") - - loaders.add("fabric") - - dependencies { - required.project("fabric-api") - } -} +} \ No newline at end of file diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts new file mode 100644 index 00000000..7aef15a6 --- /dev/null +++ b/fabric/build.gradle.kts @@ -0,0 +1,46 @@ +architectury { + platformSetupLoomIde() + fabric() +} + +// Used to extend runtime/compile classpaths +val common: Configuration by configurations.creating +// Needed to read mixin config in the runServer task, and for the architectury transformer +// (e.g. the @ExpectPlatform annotation) +val developmentFabric: Configuration = configurations.getByName("developmentFabric") +// Our custom transitive include configuration +val includeTransitive: Configuration = configurations.getByName("includeTransitive") + +configurations { + compileClasspath.get().extendsFrom(configurations["common"]) + runtimeClasspath.get().extendsFrom(configurations["common"]) + developmentFabric.extendsFrom(configurations["common"]) +} + +dependencies { + modImplementation(libs.fabric.loader) + modApi(libs.fabric.api) + // "namedElements" configuration should be used to depend on different loom projects + common(project(":mod", configuration = "namedElements")) { isTransitive = false } + // Bundle transformed classes of the common module for production mod jar + shadow(project(path = ":mod", configuration = "transformProductionFabric")) { + isTransitive = false + } + + includeTransitive(libs.floodgate.core) + implementation(libs.floodgate.core) + implementation(libs.guice) + + modImplementation(libs.cloud.fabric) + include(libs.cloud.fabric) +} + +tasks { + remapJar { + archiveBaseName.set("floodgate-fabric") + } + + modrinth { + loaders.add("fabric") + } +} diff --git a/fabric/gradle.properties b/fabric/gradle.properties new file mode 100644 index 00000000..90ee7a25 --- /dev/null +++ b/fabric/gradle.properties @@ -0,0 +1 @@ +loom.platform=fabric \ No newline at end of file diff --git a/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java b/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java new file mode 100644 index 00000000..bf8f6d27 --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.mod.util.fabric; + +import net.fabricmc.loader.api.FabricLoader; + +public class MixinConfigPluginImpl { + + public static boolean isGeyserLoaded() { + return FabricLoader.getInstance().isModLoaded("geyser-fabric"); + } + + public static boolean applyProxyFix() { + return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); + } +} diff --git a/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java new file mode 100644 index 00000000..ed1a98cf --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/FabricFloodgateMod.java @@ -0,0 +1,54 @@ +package org.geysermc.floodgate.platform.fabric; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.core.module.PluginMessageModule; +import org.geysermc.floodgate.core.module.ServerCommonModule; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.mod.util.ModTemplateReader; +import org.geysermc.floodgate.platform.fabric.module.FabricCommandModule; +import org.geysermc.floodgate.platform.fabric.module.FabricPlatformModule; + +import java.nio.file.Path; + +public final class FabricFloodgateMod extends FloodgateMod implements ModInitializer { + + private ModContainer container; + + @Override + public void onInitialize() { + container = FabricLoader.getInstance().getModContainer("floodgate").orElseThrow(); + init( + new ServerCommonModule( + FabricLoader.getInstance().getConfigDir().resolve("floodgate"), + new ModTemplateReader() + ), + new FabricPlatformModule(), + new FabricCommandModule(), + new PluginMessageModule() + ); + + ServerLifecycleEvents.SERVER_STARTED.register(this::enable); + + if (isClient()) { + ClientLifecycleEvents.CLIENT_STOPPING.register($ -> this.disable()); + } else { + ServerLifecycleEvents.SERVER_STOPPING.register($ -> this.disable()); + } + } + + @Override + public @Nullable Path resourcePath(String file) { + return container.findPath(file).orElse(null); + } + + @Override + public boolean isClient() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; + } +} diff --git a/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java new file mode 100644 index 00000000..80e486d2 --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/listener/FabricEventRegistration.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.platform.fabric.listener; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; + +public final class FabricEventRegistration implements ListenerRegistration { + @Override + public void register(ModEventListener listener) { + ServerPlayConnectionEvents.JOIN.register( + (handler, sender, server) -> listener.onPlayerJoin(handler.getPlayer().getUUID()) + ); + } +} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricCommandModule.java similarity index 64% rename from src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java rename to fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricCommandModule.java index c02adb16..4d9ccb81 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricCommandModule.java +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricCommandModule.java @@ -1,13 +1,15 @@ -package org.geysermc.floodgate.module; +package org.geysermc.floodgate.platform.fabric.module; import com.google.inject.Provides; import com.google.inject.Singleton; import lombok.SneakyThrows; import net.minecraft.commands.CommandSourceStack; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; -import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.audience.FloodgateSenderMapper; +import org.geysermc.floodgate.core.module.CommandModule; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.player.FloodgateCommandPreprocessor; +import org.geysermc.floodgate.core.player.UserAudience; +import org.geysermc.floodgate.core.player.audience.FloodgateSenderMapper; +import org.geysermc.floodgate.mod.util.ModCommandUtil; import org.incendo.cloud.CommandManager; import org.incendo.cloud.execution.ExecutionCoordinator; import org.incendo.cloud.fabric.FabricCommandManager; @@ -23,6 +25,7 @@ public final class FabricCommandModule extends CommandModule { new FloodgateSenderMapper<>(commandUtil) ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + ((ModCommandUtil) commandUtil).setCommandManager(commandManager); return commandManager; } diff --git a/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java new file mode 100644 index 00000000..2f4cf18a --- /dev/null +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/module/FabricPlatformModule.java @@ -0,0 +1,40 @@ +package org.geysermc.floodgate.platform.fabric.module; + +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; +import org.geysermc.floodgate.mod.module.ModPlatformModule; +import org.geysermc.floodgate.platform.fabric.listener.FabricEventRegistration; +import org.geysermc.floodgate.platform.fabric.pluginmessage.FabricPluginMessageRegistration; +import org.geysermc.floodgate.platform.fabric.pluginmessage.FabricPluginMessageUtils; + +public class FabricPlatformModule extends ModPlatformModule { + + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new FabricEventRegistration(); + } + + @Provides + @Singleton + public PluginMessageUtils pluginMessageUtils() { + return new FabricPluginMessageUtils(); + } + + @Provides + @Singleton + public PluginMessageRegistration pluginMessageRegister() { + return new FabricPluginMessageRegistration(); + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "Fabric"; + } +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageRegistration.java similarity index 85% rename from src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java rename to fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageRegistration.java index ee5b781a..d9da35c1 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageRegistration.java +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageRegistration.java @@ -1,11 +1,13 @@ -package org.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.platform.fabric.pluginmessage; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import org.geysermc.floodgate.pluginmessage.payloads.FormPayload; -import org.geysermc.floodgate.pluginmessage.payloads.PacketPayload; -import org.geysermc.floodgate.pluginmessage.payloads.SkinPayload; -import org.geysermc.floodgate.pluginmessage.payloads.TransferPayload; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; public class FabricPluginMessageRegistration implements PluginMessageRegistration { @Override diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageUtils.java similarity index 61% rename from src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java rename to fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageUtils.java index 6a5b3b4c..9aa1094f 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricPluginMessageUtils.java +++ b/fabric/src/main/java/org/geysermc/floodgate/platform/fabric/pluginmessage/FabricPluginMessageUtils.java @@ -1,21 +1,14 @@ -package org.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.platform.fabric.pluginmessage; -import io.netty.buffer.Unpooled; -import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; -import org.geysermc.floodgate.MinecraftServerHolder; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.InstanceHolder; -import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; -import org.geysermc.floodgate.pluginmessage.payloads.FormPayload; -import org.geysermc.floodgate.pluginmessage.payloads.PacketPayload; -import org.geysermc.floodgate.pluginmessage.payloads.SkinPayload; -import org.geysermc.floodgate.pluginmessage.payloads.TransferPayload; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; import java.util.Objects; import java.util.UUID; diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..62a1c738 --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "$id", + "version": "$version", + "name": "$name", + "description": "$description", + "authors": [ + "$author" + ], + "contact": { + "website": "$url", + "repo": "https://github.com/GeyserMC/Floodgate-Modded" + }, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "org.geysermc.floodgate.platform.fabric.FabricFloodgateMod" + ] + }, + "accessWidener": "floodgate.accesswidener", + "mixins": [ + "floodgate.mixins.json" + ], + "depends": { + "fabricloader": ">=0.15.10", + "fabric": "*", + "minecraft": ">=$minecraft_version" + } +} diff --git a/gradle.properties b/gradle.properties index 573a53c0..a5ca268a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,9 @@ -# Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx2G +org.gradle.daemon=false +org.gradle.caching=true +org.gradle.vfs.watch=false + # Mod Properties -version=2.2.3-SNAPSHOT +version=2.2.4-SNAPSHOT group=org.geysermc.floodgate -archives_base_name=floodgate-fabric +id=floodgate-modded diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..bae1d82a --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,57 @@ +[versions] +geyser = "2.2.3-SNAPSHOT" +blossom = "1.2.0" +indra = "3.1.3" +shadow = "8.1.1" +architectury-plugin = "3.4-SNAPSHOT" +architectury-loom = "1.7-SNAPSHOT" +minecraft-version = "1.21" +minotaur = "2.+" +guice = "6.0.0" +cloud = "2.0.0-beta.7" +lombok = "8.6" +bstats = "3.0.2" +configutils = "1.0-SNAPSHOT" +mixin = "0.8.5" +asm = "5.2" +floodgate = "core-repackage-2.2.3-SNAPSHOT" + +# fabric +fabric-loader = "0.15.11" +fabric-api = "0.100.1+1.21" + +# neoforge +neoforge-version = "21.0.87-beta" + +[libraries] +floodgate-core = { group = "org.geysermc.floodgate", name = "core", version.ref = "floodgate" } +floodgate-api = { group = "org.geysermc.floodgate", name = "api", version.ref = "floodgate" } + +geyser-fabric = { group = "org.geysermc.geyser", name = "fabric", version.ref = "geyser" } +geyser-mod = { group = "org.geysermc.geyser", name = "mod", version.ref = "geyser" } +geyser-core = { group = "org.geysermc.geyser", name = "core", version.ref = "geyser" } +indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" } +shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" } +architectury-plugin = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury-plugin" } +architectury-loom = { group = "dev.architectury.loom", name = "dev.architectury.loom.gradle.plugin", version.ref = "architectury-loom" } +guice = { group = "com.google.inject", name = "guice", version.ref = "guice" } +bstats = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } +configutils = { group = "org.geysermc.configutils", name = "configutils", version.ref = "configutils" } +cloud-fabric = { group = "org.incendo", name = "cloud-fabric", version.ref = "cloud" } +cloud-neoforge = { group = "org.incendo", name = "cloud-neoforge", version.ref = "cloud" } +minotaur = { group = "com.modrinth.minotaur", name = "Minotaur", version.ref = "minotaur" } +mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } +asm = { group = "org.ow2.asm", name = "asm-debug-all", version.ref = "asm" } +minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft-version" } + +# Fabric +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } + +# NeoForge +neoforge = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge-version" } + +[plugins] +lombok = { id = "io.freefair.lombok", version.ref = "lombok" } +blossom = { id = "net.kyori.blossom", version.ref = "blossom"} +indra = { id = "net.kyori.indra", version.ref = "indra" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02c..a4413138 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefaa..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# 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"' +# 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 @@ -133,22 +131,29 @@ 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. 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=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# 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. + +# 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, 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" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..7101f8e4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/mod/build.gradle.kts b/mod/build.gradle.kts new file mode 100644 index 00000000..3d990ac1 --- /dev/null +++ b/mod/build.gradle.kts @@ -0,0 +1,33 @@ +architectury { + common("neoforge", "fabric") +} + +loom { + accessWidenerPath = file("src/main/resources/floodgate.accesswidener") + mixin.defaultRefmapName.set("floodgate-refmap.json") +} + +dependencies { + api(libs.floodgate.core) + api(libs.floodgate.api) + api(libs.guice) + + compileOnly(libs.mixin) + compileOnly(libs.asm) + modCompileOnly(libs.geyser.mod) { isTransitive = false } + modCompileOnly(libs.geyser.core) { isTransitive = false } + + // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. + compileOnly(libs.fabric.loader) +} + +afterEvaluate { + // We don't need these + tasks.named("remapModrinthJar").configure { + enabled = false + } + + tasks.named("modrinth").configure { + enabled = false + } +} \ No newline at end of file diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java b/mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java new file mode 100644 index 00000000..b765b4f0 --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/FloodgateMod.java @@ -0,0 +1,59 @@ +package org.geysermc.floodgate.mod; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import net.minecraft.server.MinecraftServer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.FloodgatePlatform; +import org.geysermc.floodgate.mod.module.ModAddonModule; +import org.geysermc.floodgate.mod.module.ModListenerModule; + +import java.nio.file.Path; + +public abstract class FloodgateMod { + public static FloodgateMod INSTANCE; + + private boolean started; + private FloodgatePlatform platform; + protected Injector injector; + + protected void init(Module... modules) { + INSTANCE = this; + injector = Guice.createInjector(modules); + platform = injector.getInstance(FloodgatePlatform.class); + } + + protected void enable(MinecraftServer server) { + long ctm = System.currentTimeMillis(); + + // Stupid hack, see the class for more information + // This can probably be Guice-i-fied but that is beyond me + MinecraftServerHolder.set(server); + + if (!started) { + platform.enable( + new ModAddonModule(), + new ModListenerModule() + ); + started = true; + } + + long endCtm = System.currentTimeMillis(); + injector.getInstance(FloodgateLogger.class) + .translatedInfo("floodgate.core.finish", endCtm - ctm); + } + + protected void disable() { + platform.disable(); + } + + protected void enable(Module... module) { + platform.enable(module); + } + + public @Nullable abstract Path resourcePath(String file); + + public abstract boolean isClient(); +} diff --git a/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java b/mod/src/main/java/org/geysermc/floodgate/mod/MinecraftServerHolder.java similarity index 92% rename from src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java rename to mod/src/main/java/org/geysermc/floodgate/mod/MinecraftServerHolder.java index b1f0c9e6..85be6d28 100644 --- a/src/main/java/org/geysermc/floodgate/MinecraftServerHolder.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/MinecraftServerHolder.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate; +package org.geysermc.floodgate.mod; import net.minecraft.server.MinecraftServer; diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java similarity index 78% rename from src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java rename to mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java index ca93aae4..6f0bb8eb 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataAddon.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataAddon.java @@ -1,18 +1,18 @@ -package org.geysermc.floodgate.addon.data; +package org.geysermc.floodgate.mod.data; import com.google.inject.Inject; import com.google.inject.name.Named; import io.netty.channel.Channel; import io.netty.util.AttributeKey; -import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.inject.InjectorAddon; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler; -import org.geysermc.floodgate.util.Utils; +import org.geysermc.floodgate.core.api.SimpleFloodgateApi; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.core.util.Utils; -public final class FabricDataAddon implements InjectorAddon { +public final class ModDataAddon implements InjectorAddon { @Inject private FloodgateHandshakeHandler handshakeHandler; @Inject private FloodgateConfig config; @Inject private SimpleFloodgateApi api; @@ -34,7 +34,7 @@ public final class FabricDataAddon implements InjectorAddon { public void onInject(Channel channel, boolean toServer) { channel.pipeline().addBefore( packetHandlerName, "floodgate_data_handler", - new FabricDataHandler(handshakeHandler, config, kickMessageAttribute, logger) + new ModDataHandler(handshakeHandler, config, kickMessageAttribute, logger) ); } diff --git a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java similarity index 89% rename from src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java rename to mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java index eaced776..30590dff 100644 --- a/src/main/java/org/geysermc/floodgate/addon/data/FabricDataHandler.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java @@ -1,8 +1,10 @@ -package org.geysermc.floodgate.addon.data; +package org.geysermc.floodgate.mod.data; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftSessionService; import com.mojang.logging.LogUtils; import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; import io.netty.util.AttributeKey; import net.minecraft.DefaultUncaughtExceptionHandler; import net.minecraft.network.Connection; @@ -10,28 +12,28 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import net.minecraft.network.protocol.login.ServerboundHelloPacket; import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.mixin.ConnectionMixin; -import org.geysermc.floodgate.mixin.ClientIntentionPacketMixinInterface; -import com.mojang.authlib.GameProfile; -import io.netty.channel.ChannelHandlerContext; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler; -import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.core.addon.data.CommonDataHandler; +import org.geysermc.floodgate.core.addon.data.PacketBlocker; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler; +import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.mixin.ClientIntentionPacketMixinInterface; +import org.geysermc.floodgate.mod.mixin.ConnectionMixin; import org.slf4j.Logger; import java.net.InetSocketAddress; -public final class FabricDataHandler extends CommonDataHandler { +public final class ModDataHandler extends CommonDataHandler { private static final Logger LOGGER = LogUtils.getLogger(); private final FloodgateLogger logger; private Connection networkManager; private FloodgatePlayer player; - public FabricDataHandler( + public ModDataHandler( FloodgateHandshakeHandler handshakeHandler, FloodgateConfig config, AttributeKey kickMessageAttribute, FloodgateLogger logger) { diff --git a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java b/mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java similarity index 78% rename from src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java rename to mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java index 15f0a1a0..dcd2d232 100644 --- a/src/main/java/org/geysermc/floodgate/inject/fabric/FabricInjector.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java @@ -1,19 +1,21 @@ -package org.geysermc.floodgate.inject.fabric; +package org.geysermc.floodgate.mod.inject; import com.google.inject.Inject; -import io.netty.channel.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.core.inject.CommonPlatformInjector; @RequiredArgsConstructor -public final class FabricInjector extends CommonPlatformInjector { +public final class ModInjector extends CommonPlatformInjector { - @Setter @Getter - private static FabricInjector instance; + public static ModInjector INSTANCE = new ModInjector(); @Getter private final boolean injected = true; diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java b/mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java similarity index 55% rename from src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java rename to mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java index b94e1d30..6f116e3d 100644 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventListener.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/listener/ModEventListener.java @@ -1,21 +1,20 @@ -package org.geysermc.floodgate.listener; +package org.geysermc.floodgate.mod.listener; import com.google.inject.Inject; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.util.LanguageManager; +import org.geysermc.floodgate.core.util.LanguageManager; -public final class FabricEventListener { +import java.util.UUID; + +public final class ModEventListener { @Inject private FloodgateApi api; @Inject private FloodgateLogger logger; @Inject private LanguageManager languageManager; - public void onPlayerJoin(ServerGamePacketListenerImpl networkHandler, PacketSender packetSender, MinecraftServer server) { - FloodgatePlayer player = api.getPlayer(networkHandler.player.getUUID()); + public void onPlayerJoin(UUID uuid) { + FloodgatePlayer player = api.getPlayer(uuid); if (player != null) { logger.translatedInfo( "floodgate.ingame.login_name", diff --git a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java b/mod/src/main/java/org/geysermc/floodgate/mod/logger/Log4jFloodgateLogger.java similarity index 88% rename from src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java rename to mod/src/main/java/org/geysermc/floodgate/mod/logger/Log4jFloodgateLogger.java index d24223fa..b3eb4814 100644 --- a/src/main/java/org/geysermc/floodgate/logger/Log4jFloodgateLogger.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/logger/Log4jFloodgateLogger.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.logger; +package org.geysermc.floodgate.mod.logger; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -7,10 +7,10 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.util.LanguageManager; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.util.LanguageManager; -import static org.geysermc.floodgate.util.MessageFormatter.format; +import static org.geysermc.floodgate.core.util.MessageFormatter.format; @Singleton public final class Log4jFloodgateLogger implements FloodgateLogger { diff --git a/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java similarity index 88% rename from src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java index 3805dbb5..c959ebfc 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ChunkMapMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.server.level.ChunkMap; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java similarity index 92% rename from src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java index e8c0dbf8..75b5df4d 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java similarity index 90% rename from src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java index 8ffdcba3..ee067929 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ClientIntentionPacketMixinInterface.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java similarity index 87% rename from src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java index 64948225..e085cd3a 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ConnectionMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import net.minecraft.network.Connection; import org.spongepowered.asm.mixin.Mixin; diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java new file mode 100644 index 00000000..e654b7cf --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java @@ -0,0 +1,49 @@ +package org.geysermc.floodgate.mod.mixin; + +import org.geysermc.floodgate.core.util.Utils; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Mixins into Floodgate's {@link Utils} class to modify how resources are loaded from the jar. + * This must be done due to mod platforms sharing a classloader across mods - this leads to Floodgate + * loading Geyser's language files, as they're not prefixed to avoid conflicts. + * To resolve this, this mixin replaces those calls with the platform-appropriate methods to load files. + */ +@Mixin(value = Utils.class, remap = false) +public class FloodgateUtilMixin { + + @Redirect(method = "readProperties", + at = @At(value = "INVOKE", target = "Ljava/lang/ClassLoader;getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;")) + private static InputStream floodgate$redirectInputStream(ClassLoader instance, String string) { + Path path = FloodgateMod.INSTANCE.resourcePath(string); + try { + return path == null ? null : Files.newInputStream(path); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Redirect(method = "getGeneratedClassesForAnnotation(Ljava/lang/String;)Ljava/util/Set;", + at = @At(value = "INVOKE", target = "Ljava/lang/ClassLoader;getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream;")) + private static InputStream floodgate$redirectInputStreamAnnotation(ClassLoader instance, String string) { + Path path = FloodgateMod.INSTANCE.resourcePath(string); + + if (path == null) { + throw new IllegalStateException("Unable to find annotation class! " + string); + } + + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/GeyserModInjectorMixin.java similarity index 71% rename from src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/GeyserModInjectorMixin.java index 4b117adf..39ab73d7 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/GeyserModInjectorMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/GeyserModInjectorMixin.java @@ -1,7 +1,7 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; import io.netty.channel.ChannelFuture; -import org.geysermc.floodgate.inject.fabric.FabricInjector; +import org.geysermc.floodgate.mod.inject.ModInjector; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.platform.mod.GeyserModInjector; import org.spongepowered.asm.mixin.Mixin; @@ -19,7 +19,7 @@ public class GeyserModInjectorMixin { private List allServerChannels; @Inject(method = "initializeLocalChannel0", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) - public void onChannelAdd(GeyserBootstrap bootstrap, CallbackInfo ci) { - FabricInjector.getInstance().injectClient(this.allServerChannels.get(this.allServerChannels.size() - 1)); + public void floodgate$onChannelAdd(GeyserBootstrap bootstrap, CallbackInfo ci) { + ModInjector.INSTANCE.injectClient(this.allServerChannels.get(this.allServerChannels.size() - 1)); } } diff --git a/src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java similarity index 73% rename from src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java index dd04e51d..e094da2b 100644 --- a/src/main/java/org/geysermc/floodgate/mixin/ServerConnectionListenerMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java @@ -1,8 +1,8 @@ -package org.geysermc.floodgate.mixin; +package org.geysermc.floodgate.mod.mixin; -import net.minecraft.server.network.ServerConnectionListener; -import org.geysermc.floodgate.inject.fabric.FabricInjector; import io.netty.channel.ChannelFuture; +import net.minecraft.server.network.ServerConnectionListener; +import org.geysermc.floodgate.mod.inject.ModInjector; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -19,7 +19,7 @@ public abstract class ServerConnectionListenerMixin { @Shadow @Final private List channels; @Inject(method = "startTcpServerListener", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) - public void onChannelAdd(InetAddress address, int port, CallbackInfo ci) { - FabricInjector.getInstance().injectClient(this.channels.get(this.channels.size() - 1)); + public void floodgate$onChannelAdd(InetAddress address, int port, CallbackInfo ci) { + ModInjector.INSTANCE.injectClient(this.channels.get(this.channels.size() - 1)); } } diff --git a/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java similarity index 63% rename from src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java rename to mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java index 2dd731ce..fdd9f577 100644 --- a/src/main/java/org/geysermc/floodgate/module/FabricAddonModule.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModAddonModule.java @@ -1,15 +1,15 @@ -package org.geysermc.floodgate.module; +package org.geysermc.floodgate.mod.module; -import org.geysermc.floodgate.addon.data.FabricDataAddon; import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.multibindings.ProvidesIntoSet; -import org.geysermc.floodgate.addon.AddonManagerAddon; -import org.geysermc.floodgate.addon.DebugAddon; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.register.AddonRegister; +import org.geysermc.floodgate.core.addon.AddonManagerAddon; +import org.geysermc.floodgate.core.addon.DebugAddon; +import org.geysermc.floodgate.core.register.AddonRegister; +import org.geysermc.floodgate.mod.data.ModDataAddon; -public final class FabricAddonModule extends AbstractModule { +public final class ModAddonModule extends AbstractModule { @Override protected void configure() { bind(AddonRegister.class).asEagerSingleton(); @@ -24,7 +24,7 @@ public final class FabricAddonModule extends AbstractModule { @Singleton @ProvidesIntoSet public InjectorAddon dataAddon() { - return new FabricDataAddon(); + return new ModDataAddon(); } @Singleton diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java new file mode 100644 index 00000000..98d4532e --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModListenerModule.java @@ -0,0 +1,21 @@ +package org.geysermc.floodgate.mod.module; + +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.core.register.ListenerRegister; +import org.geysermc.floodgate.mod.listener.ModEventListener; + +public final class ModListenerModule extends AbstractModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public ModEventListener modEventListener() { + return new ModEventListener(); + } +} diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java new file mode 100644 index 00000000..534849a1 --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/module/ModPlatformModule.java @@ -0,0 +1,77 @@ +package org.geysermc.floodgate.mod.module; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.inject.CommonPlatformInjector; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.platform.util.PlatformUtils; +import org.geysermc.floodgate.core.skin.SkinApplier; +import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.mod.inject.ModInjector; +import org.geysermc.floodgate.mod.logger.Log4jFloodgateLogger; +import org.geysermc.floodgate.mod.pluginmessage.ModSkinApplier; +import org.geysermc.floodgate.mod.util.ModCommandUtil; +import org.geysermc.floodgate.mod.util.ModPlatformUtils; + +@RequiredArgsConstructor +public abstract class ModPlatformModule extends AbstractModule { + + @Provides + @Singleton + public CommandUtil commandUtil( + FloodgateApi api, + FloodgateLogger logger, + LanguageManager languageManager) { + return new ModCommandUtil(languageManager, api, logger); + } + + @Override + protected void configure() { + bind(PlatformUtils.class).to(ModPlatformUtils.class); + bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(LogManager.getLogger("floodgate")); + bind(FloodgateLogger.class).to(Log4jFloodgateLogger.class); + } + + /* + DebugAddon / PlatformInjector + */ + + @Provides + @Singleton + public CommonPlatformInjector platformInjector() { + return ModInjector.INSTANCE; + } + + @Provides + @Named("packetEncoder") + public String packetEncoder() { + return FloodgateMod.INSTANCE.isClient() ? "encoder" : "outbound_config"; + } + + @Provides + @Named("packetDecoder") + public String packetDecoder() { + return FloodgateMod.INSTANCE.isClient() ? "inbound_config" : "decoder" ; + } + + @Provides + @Named("packetHandler") + public String packetHandler() { + return "packet_handler"; + } + + @Provides + @Singleton + public SkinApplier skinApplier() { + return new ModSkinApplier(); + } +} diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java similarity index 90% rename from src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java index 374c2d8c..45371729 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/FabricSkinApplier.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage; +package org.geysermc.floodgate.mod.pluginmessage; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; @@ -8,16 +8,16 @@ import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.mixin.ChunkMapMixin; -import org.geysermc.floodgate.skin.SkinApplier; - -import static org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; +import org.geysermc.floodgate.core.skin.SkinApplier; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.mixin.ChunkMapMixin; import java.util.Collections; -public final class FabricSkinApplier implements SkinApplier { +import static org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; + +public final class ModSkinApplier implements SkinApplier { @Override public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/FormPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/FormPayload.java index 475d138f..fa7b727b 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/FormPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/FormPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/PacketPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/PacketPayload.java index b00021b5..944ea862 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/PacketPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/PacketPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/SkinPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/SkinPayload.java index a1e6eee6..16fda96b 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/SkinPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/SkinPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/TransferPayload.java similarity index 94% rename from src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java rename to mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/TransferPayload.java index 63963279..597634e0 100644 --- a/src/main/java/org/geysermc/floodgate/pluginmessage/payloads/TransferPayload.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/pluginmessage/payloads/TransferPayload.java @@ -1,4 +1,4 @@ -package org.geysermc.floodgate.pluginmessage.payloads; +package org.geysermc.floodgate.mod.pluginmessage.payloads; import io.netty.buffer.ByteBufUtil; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java similarity index 82% rename from src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java rename to mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java index 0382148b..c5685d29 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricCommandUtil.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java @@ -1,28 +1,30 @@ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.mod.util; import com.mojang.authlib.GameProfile; -import me.lucko.fabric.api.permissions.v0.Permissions; -import net.minecraft.commands.CommandSource; +import lombok.Setter; import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.UserWhiteListEntry; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.MinecraftServerHolder; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.player.UserAudience; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.player.UserAudience; +import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.incendo.cloud.CommandManager; -import java.util.*; -import java.util.logging.Logger; +import java.util.Collection; +import java.util.UUID; -public final class FabricCommandUtil extends CommandUtil { +public final class ModCommandUtil extends CommandUtil { private final FloodgateLogger logger; private UserAudience console; + @Setter + private CommandManager commandManager; - public FabricCommandUtil(LanguageManager manager, FloodgateApi api, FloodgateLogger logger) { + public ModCommandUtil(LanguageManager manager, FloodgateApi api, FloodgateLogger logger) { super(manager, api); this.logger = logger; } @@ -73,8 +75,7 @@ public final class FabricCommandUtil extends CommandUtil { @Override public boolean hasPermission(Object source, String permission) { - return Permissions.check((SharedSuggestionProvider) source, - permission, MinecraftServerHolder.get().getOperatorUserPermissionLevel()); + return commandManager.hasPermission(getUserAudience(source), permission); } @Override diff --git a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java similarity index 56% rename from src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java rename to mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java index b8ee1620..47945b7f 100644 --- a/src/main/java/org/geysermc/floodgate/util/MixinConfigPlugin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModMixinConfigPlugin.java @@ -1,6 +1,6 @@ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.mod.util; -import net.fabricmc.loader.api.FabricLoader; +import dev.architectury.injectables.annotations.ExpectPlatform; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; @@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import java.util.List; import java.util.Set; -public class MixinConfigPlugin implements IMixinConfigPlugin { +public class ModMixinConfigPlugin implements IMixinConfigPlugin { @Override public void onLoad(String mixinPackage) { @@ -21,12 +21,11 @@ public class MixinConfigPlugin implements IMixinConfigPlugin { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - if (mixinClassName.equals("org.geysermc.floodgate.mixin.ClientIntentionPacketMixin")) { - //returns true if fabricproxy-lite is present, therefore loading the mixin. If not present, the mixin will not be loaded. - return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); + if (mixinClassName.equals("org.geysermc.floodgate.mod.mixin.ClientIntentionPacketMixin")) { + return applyProxyFix(); } - if (mixinClassName.equals("org.geysermc.floodgate.mixin.GeyserModInjectorMixin")) { - return FabricLoader.getInstance().isModLoaded("geyser-fabric"); + if (mixinClassName.equals("org.geysermc.floodgate.mod.mixin.GeyserModInjectorMixin")) { + return isGeyserLoaded(); } return true; } @@ -47,4 +46,14 @@ public class MixinConfigPlugin implements IMixinConfigPlugin { @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + + @ExpectPlatform + public static boolean isGeyserLoaded() { + throw new IllegalStateException("isGeyserLoaded is not implemented!"); + } + + @ExpectPlatform + public static boolean applyProxyFix() { + throw new IllegalStateException("applyProxyFix is not implemented!"); + } } \ No newline at end of file diff --git a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java similarity index 67% rename from src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java rename to mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java index 900ec2dc..6ab2dfd6 100644 --- a/src/main/java/org/geysermc/floodgate/util/FabricPlatformUtils.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java @@ -1,10 +1,10 @@ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.mod.util; import net.minecraft.SharedConstants; -import org.geysermc.floodgate.MinecraftServerHolder; -import org.geysermc.floodgate.platform.util.PlatformUtils; +import org.geysermc.floodgate.core.platform.util.PlatformUtils; +import org.geysermc.floodgate.mod.MinecraftServerHolder; -public class FabricPlatformUtils extends PlatformUtils { +public class ModPlatformUtils extends PlatformUtils { @Override public AuthType authType() { return MinecraftServerHolder.get().usesAuthentication() ? AuthType.ONLINE : AuthType.OFFLINE; diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java new file mode 100644 index 00000000..9f0d4df1 --- /dev/null +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModTemplateReader.java @@ -0,0 +1,25 @@ +package org.geysermc.floodgate.mod.util; + +import org.geysermc.configutils.file.template.TemplateReader; +import org.geysermc.floodgate.mod.FloodgateMod; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ModTemplateReader implements TemplateReader { + + @Override + public BufferedReader read(String configName) { + Path path = FloodgateMod.INSTANCE.resourcePath(configName); + if (path != null) { + try { + return Files.newBufferedReader(path); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return null; + } +} diff --git a/src/main/resources/floodgate.accesswidener b/mod/src/main/resources/floodgate.accesswidener similarity index 100% rename from src/main/resources/floodgate.accesswidener rename to mod/src/main/resources/floodgate.accesswidener diff --git a/src/main/resources/floodgate.mixins.json b/mod/src/main/resources/floodgate.mixins.json similarity index 69% rename from src/main/resources/floodgate.mixins.json rename to mod/src/main/resources/floodgate.mixins.json index ca8e5597..d3597fb1 100644 --- a/src/main/resources/floodgate.mixins.json +++ b/mod/src/main/resources/floodgate.mixins.json @@ -1,17 +1,18 @@ { "required": true, "minVersion": "0.8", - "package": "org.geysermc.floodgate.mixin", + "package": "org.geysermc.floodgate.mod.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ "ChunkMapMixin", "ClientIntentionPacketMixin", "ClientIntentionPacketMixinInterface", "ConnectionMixin", + "FloodgateUtilMixin", "GeyserModInjectorMixin", "ServerConnectionListenerMixin" ], - "plugin": "org.geysermc.floodgate.util.MixinConfigPlugin", + "plugin": "org.geysermc.floodgate.mod.util.ModMixinConfigPlugin", "injectors": { "defaultRequire": 1 } diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts new file mode 100644 index 00000000..6f18ab50 --- /dev/null +++ b/neoforge/build.gradle.kts @@ -0,0 +1,61 @@ +architectury { + platformSetupLoomIde() + neoForge() +} + +provided("com.google.guava", "failureaccess") + +// Used to extend runtime/compile classpaths +val common: Configuration by configurations.creating +// Needed to read mixin config in the runServer task, and for the architectury transformer +// (e.g. the @ExpectPlatform annotation) +val developmentNeoForge: Configuration = configurations.getByName("developmentNeoForge") +// Our custom transitive include configuration +val includeTransitive: Configuration = configurations.getByName("includeTransitive") + +configurations { + compileClasspath.get().extendsFrom(configurations["common"]) + runtimeClasspath.get().extendsFrom(configurations["common"]) + developmentNeoForge.extendsFrom(configurations["common"]) +} + +dependencies { + // See https://github.com/google/guava/issues/6618 + modules { + module("com.google.guava:listenablefuture") { + replacedBy("com.google.guava:guava", "listenablefuture is part of guava") + } + } + + neoForge(libs.neoforge) + // "namedElements" configuration should be used to depend on different loom projects + common(project(":mod", configuration = "namedElements")) { isTransitive = false } + // Bundle transformed classes of the common module for production mod jar + shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) { isTransitive = false } + + includeTransitive(libs.floodgate.core) + + implementation(libs.floodgate.core) + implementation(libs.guice) + + modImplementation(libs.cloud.neoforge) + include(libs.cloud.neoforge) +} + +tasks { + processResources { + from(project(":mod").file("src/main/resources/floodgate.accesswidener")) { + into("/assets/") + } + } + + remapJar { + dependsOn(processResources) + atAccessWideners.add("floodgate.accesswidener") + archiveBaseName.set("floodgate-neoforge") + } + + modrinth { + loaders.add("neoforge") + } +} diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties new file mode 100644 index 00000000..2914393d --- /dev/null +++ b/neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge \ No newline at end of file diff --git a/neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java b/neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java new file mode 100644 index 00000000..27159f6f --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/mod/util/neoforge/ModMixinConfigPluginImpl.java @@ -0,0 +1,13 @@ +package org.geysermc.floodgate.mod.util.neoforge; + +import net.neoforged.fml.loading.LoadingModList; + +public class ModMixinConfigPluginImpl { + public static boolean isGeyserLoaded() { + return LoadingModList.get().getModFileById("geyser_neoforge") != null; + } + + public static boolean applyProxyFix() { + return false; + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java new file mode 100644 index 00000000..e3d09d57 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java @@ -0,0 +1,101 @@ +package org.geysermc.floodgate.platform.neoforge; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.GameShuttingDownEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.module.PluginMessageModule; +import org.geysermc.floodgate.core.module.ServerCommonModule; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.mod.util.ModTemplateReader; +import org.geysermc.floodgate.platform.neoforge.module.NeoForgeCommandModule; +import org.geysermc.floodgate.platform.neoforge.module.NeoForgePlatformModule; +import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageRegistration; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.nio.file.Path; +import java.util.Set; +import java.util.stream.Collectors; + +@Mod("floodgate") +public final class NeoForgeFloodgateMod extends FloodgateMod { + + private final ModContainer container; + + public NeoForgeFloodgateMod(IEventBus modEventBus, ModContainer container) { + this.container = container; + init( + new ServerCommonModule( + FMLPaths.CONFIGDIR.get().resolve("floodgate"), + new ModTemplateReader() + ), + new NeoForgePlatformModule(), + new NeoForgeCommandModule() + ); + + modEventBus.addListener(this::onRegisterPackets); + NeoForge.EVENT_BUS.addListener(this::onServerStarted); + if (FMLLoader.getDist().isClient()) { + NeoForge.EVENT_BUS.addListener(this::onClientStop); + } else { + NeoForge.EVENT_BUS.addListener(this::onServerStop); + } + } + + private void onServerStarted(ServerStartedEvent event) { + this.enable(event.getServer()); + } + + private void onClientStop(GameShuttingDownEvent ignored) { + this.disable(); + } + + private void onServerStop(ServerStoppingEvent ignored) { + this.disable(); + } + + private void onRegisterPackets(final RegisterPayloadHandlersEvent event) { + // Set the registrar once we're given it - NeoForgePluginMessageRegistration was created earlier in NeoForgePlatformModule + NeoForgePluginMessageRegistration pluginMessageRegistration = injector.getInstance(NeoForgePluginMessageRegistration.class); + pluginMessageRegistration.setRegistrar(event.registrar("floodgate").optional()); + + // We can now trigger the registering of our plugin message channels + enable(new PluginMessageModule()); + } + + @Override + public @Nullable Path resourcePath(String file) { + return container.getModInfo().getOwningFile().getFile().findResource(file); + } + + @Override + public boolean isClient() { + return FMLLoader.getDist().isClient(); + } + + public Set> getAnnotatedClasses(Class annotationClass) { + return container.getModInfo() + .getOwningFile() + .getFile() + .getScanResult() + .getAnnotatedBy(annotationClass, ElementType.TYPE) + .map(annotationData -> { + try { + return Class.forName(annotationData.clazz().getClassName()); + } catch (Exception e) { + injector.getInstance(FloodgateLogger.class).error(e.getMessage(), e); + return null; + } + }) + .collect(Collectors.toSet()); + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java new file mode 100644 index 00000000..77afc914 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/listener/NeoForgeEventRegistration.java @@ -0,0 +1,20 @@ +package org.geysermc.floodgate.platform.neoforge.listener; + +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; + +public final class NeoForgeEventRegistration implements ListenerRegistration { + private ModEventListener listener; + + @Override + public void register(ModEventListener listener) { + NeoForge.EVENT_BUS.addListener(this::onPlayerJoin); + this.listener = listener; + } + + private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + listener.onPlayerJoin(event.getEntity().getUUID()); + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java new file mode 100644 index 00000000..cee3218c --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.platform.neoforge.mixin; + +import org.geysermc.floodgate.core.util.Utils; +import org.geysermc.floodgate.mod.FloodgateMod; +import org.geysermc.floodgate.platform.neoforge.NeoForgeFloodgateMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Mixin into Floodgate's {@link Utils} class as NeoForge is really picky about how it allows scanning + * mod-owned classes. + */ +@Mixin(value = Utils.class, remap = false) +public class NeoForgeFloodgateUtilMixin { + + /** + * @author geysermc + * @reason NeoForge is really picky about how it allows scanning mod-owned classes. + */ + @Overwrite(remap = false) + public static Set> getGeneratedClassesForAnnotation(Class annotationClass) { + return ((NeoForgeFloodgateMod) FloodgateMod.INSTANCE).getAnnotatedClasses(annotationClass); + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java new file mode 100644 index 00000000..5b06fe88 --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgeCommandModule.java @@ -0,0 +1,29 @@ +package org.geysermc.floodgate.platform.neoforge.module; + +import com.google.inject.Provides; +import com.google.inject.Singleton; +import lombok.SneakyThrows; +import org.geysermc.floodgate.core.module.CommandModule; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.player.FloodgateCommandPreprocessor; +import org.geysermc.floodgate.core.player.UserAudience; +import org.geysermc.floodgate.core.player.audience.FloodgateSenderMapper; +import org.geysermc.floodgate.mod.util.ModCommandUtil; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.neoforge.NeoForgeServerCommandManager; + +public class NeoForgeCommandModule extends CommandModule { + @Provides + @Singleton + @SneakyThrows + public CommandManager commandManager(CommandUtil commandUtil) { + CommandManager commandManager = new NeoForgeServerCommandManager<>( + ExecutionCoordinator.simpleCoordinator(), + new FloodgateSenderMapper<>(commandUtil) + ); + commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + ((ModCommandUtil) commandUtil).setCommandManager(commandManager); + return commandManager; + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java new file mode 100644 index 00000000..f224ad0f --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/module/NeoForgePlatformModule.java @@ -0,0 +1,46 @@ +package org.geysermc.floodgate.platform.neoforge.module; + +import com.google.inject.Provides; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.listener.ModEventListener; +import org.geysermc.floodgate.mod.module.ModPlatformModule; +import org.geysermc.floodgate.platform.neoforge.listener.NeoForgeEventRegistration; +import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageRegistration; +import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageUtils; + +public class NeoForgePlatformModule extends ModPlatformModule { + + @Override + protected void configure() { + super.configure(); + + // We retrieve using NeoForgePluginMessageRegistration.class from our the mod class. + // We do this to ensure that injector#getInstance with either class returns the same singleton + bind(PluginMessageRegistration.class).to(NeoForgePluginMessageRegistration.class).in(Scopes.SINGLETON); + bind(NeoForgePluginMessageRegistration.class).toInstance(new NeoForgePluginMessageRegistration()); + } + + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new NeoForgeEventRegistration(); + } + + @Provides + @Singleton + public PluginMessageUtils pluginMessageUtils() { + return new NeoForgePluginMessageUtils(); + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "NeoForge"; + } + +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java new file mode 100644 index 00000000..bd73bb3f --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageRegistration.java @@ -0,0 +1,39 @@ +package org.geysermc.floodgate.platform.neoforge.pluginmessage; + +import lombok.Setter; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; + +public class NeoForgePluginMessageRegistration implements PluginMessageRegistration { + + @Setter + private PayloadRegistrar registrar; + + @Override + public void register(PluginMessageChannel channel) { + switch (channel.getIdentifier()) { + case "floodgate:form" -> + registrar.playBidirectional(FormPayload.TYPE, FormPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + case "floodgate:packet" -> + registrar.playBidirectional(PacketPayload.TYPE, PacketPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + case "floodgate:skin" -> + registrar.playBidirectional(SkinPayload.TYPE, SkinPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + case "floodgate:transfer" -> + registrar.playBidirectional(TransferPayload.TYPE, TransferPayload.STREAM_CODEC, (payload, context) -> + channel.handleServerCall(payload.data(), context.player().getUUID(), + context.player().getGameProfile().getName())); + default -> throw new IllegalArgumentException("unknown channel: " + channel); + } + } +} diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java new file mode 100644 index 00000000..5faf4d5b --- /dev/null +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/pluginmessage/NeoForgePluginMessageUtils.java @@ -0,0 +1,37 @@ +package org.geysermc.floodgate.platform.neoforge.pluginmessage; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.mod.MinecraftServerHolder; +import org.geysermc.floodgate.mod.pluginmessage.payloads.FormPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.PacketPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.SkinPayload; +import org.geysermc.floodgate.mod.pluginmessage.payloads.TransferPayload; + +import java.util.Objects; +import java.util.UUID; + +public class NeoForgePluginMessageUtils extends PluginMessageUtils { + public boolean sendMessage(UUID uuid, String channel, byte[] data) { + try { + ServerPlayer player = MinecraftServerHolder.get().getPlayerList().getPlayer(uuid); + final CustomPacketPayload payload; + switch (channel) { + case "floodgate:form" -> payload = new FormPayload(data); + case "floodgate:packet" -> payload = new PacketPayload(data); + case "floodgate:skin" -> payload = new SkinPayload(data); + case "floodgate:transfer" -> payload = new TransferPayload(data); + default -> throw new IllegalArgumentException("unknown channel: " + channel); + } + + Objects.requireNonNull(player); + PacketDistributor.sendToPlayer(player, payload); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..04901cab --- /dev/null +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,27 @@ +modLoader="javafml" +loaderVersion="[1,)" +license="MIT" +[[mods]] +modId="$id" +version="$version" +displayName="$name" +displayURL="$url" +logoFile= "../assets/floodgate/icon.png" +authors="$author" +description="$description" +[[mixins]] +config = "floodgate.mixins.json" +[[mixins]] +config = "floodgate_neoforge.mixins.json" +[[dependencies.floodgate]] +modId="neoforge" +type="required" +versionRange="[21.0.0-beta,)" +ordering="NONE" +side="BOTH" +[[dependencies.floodgate]] +modId="minecraft" +type="required" +versionRange="[$minecraft_version,)" +ordering="NONE" +side="BOTH" \ No newline at end of file diff --git a/neoforge/src/main/resources/floodgate_neoforge.mixins.json b/neoforge/src/main/resources/floodgate_neoforge.mixins.json new file mode 100644 index 00000000..9c06f8d8 --- /dev/null +++ b/neoforge/src/main/resources/floodgate_neoforge.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.floodgate.platform.neoforge.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "NeoForgeFloodgateUtilMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 71ef923d..5d93e26a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,34 @@ +@file:Suppress("UnstableApiUsage") + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + pluginManagement { repositories { - //mavenLocal() - mavenCentral() gradlePluginPortal() + maven("https://repo.opencollab.dev/main/") + maven("https://jitpack.io") { + content { + includeGroupByRegex("com\\.github\\..*") + } + } + + maven("https://maven.architectury.dev/") + maven("https://maven.neoforged.net/releases") maven("https://maven.fabricmc.net/") } + + plugins { + id("net.kyori.blossom") + id("net.kyori.indra") + id("net.kyori.indra.git") + id("floodgate-modded.build-logic") + } + + includeBuild("build-logic") } + +rootProject.name = "floodgate-modded" + +include(":mod") +include(":fabric") +include(":neoforge") diff --git a/src/main/java/org/geysermc/floodgate/FabricMod.java b/src/main/java/org/geysermc/floodgate/FabricMod.java deleted file mode 100644 index e93365fd..00000000 --- a/src/main/java/org/geysermc/floodgate/FabricMod.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.geysermc.floodgate; - -import net.fabricmc.api.EnvType; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; -import org.geysermc.floodgate.inject.fabric.FabricInjector; -import com.google.inject.Guice; -import com.google.inject.Injector; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.loader.api.FabricLoader; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.module.*; -import org.geysermc.floodgate.util.FabricTemplateReader; - -public class FabricMod implements ModInitializer { - - private boolean started; - - @Override - public void onInitialize() { - FabricInjector.setInstance(new FabricInjector()); - - Injector injector = Guice.createInjector( - new ServerCommonModule(FabricLoader.getInstance().getConfigDir().resolve("floodgate"), new FabricTemplateReader()), - new FabricPlatformModule() - ); - - FloodgatePlatform platform = injector.getInstance(FloodgatePlatform.class); - - platform.enable(new FabricCommandModule()); - - ServerLifecycleEvents.SERVER_STARTED.register((server) -> { - long ctm = System.currentTimeMillis(); - - // Stupid hack, see the class for more information - // This can probably be Guice-i-fied but that is beyond me - MinecraftServerHolder.set(server); - - if (!started) { - platform.enable( - new FabricAddonModule(), - new FabricListenerModule(), - new PluginMessageModule() - ); - started = true; - } - - long endCtm = System.currentTimeMillis(); - injector.getInstance(FloodgateLogger.class) - .translatedInfo("floodgate.core.finish", endCtm - ctm); - }); - - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { - ClientLifecycleEvents.CLIENT_STOPPING.register(($) -> { - platform.disable(); - }); - } else { - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> { - platform.disable(); - }); - } - } -} diff --git a/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java b/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java deleted file mode 100644 index 43daf0fa..00000000 --- a/src/main/java/org/geysermc/floodgate/listener/FabricEventRegistration.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.geysermc.floodgate.listener; - -import com.google.inject.Inject; -import lombok.RequiredArgsConstructor; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import org.geysermc.floodgate.platform.listener.ListenerRegistration; - -@RequiredArgsConstructor(onConstructor = @__(@Inject)) -public final class FabricEventRegistration implements ListenerRegistration { - @Override - public void register(FabricEventListener listener) { - ServerPlayConnectionEvents.JOIN.register(listener::onPlayerJoin); - } -} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java b/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java deleted file mode 100644 index ec67abcf..00000000 --- a/src/main/java/org/geysermc/floodgate/module/FabricListenerModule.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.geysermc.floodgate.module; - -import com.google.inject.AbstractModule; -import com.google.inject.Singleton; -import com.google.inject.TypeLiteral; -import com.google.inject.multibindings.ProvidesIntoSet; -import org.geysermc.floodgate.listener.FabricEventListener; -import org.geysermc.floodgate.register.ListenerRegister; - -public final class FabricListenerModule extends AbstractModule { - @Override - protected void configure() { - bind(new TypeLiteral>() {}).asEagerSingleton(); - } - - @Singleton - @ProvidesIntoSet - public FabricEventListener fabricEventListener() { - return new FabricEventListener(); - } -} diff --git a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java b/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java deleted file mode 100644 index 9a592830..00000000 --- a/src/main/java/org/geysermc/floodgate/module/FabricPlatformModule.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.geysermc.floodgate.module; - -import com.google.inject.name.Names; -import org.apache.logging.log4j.Logger; -import org.geysermc.floodgate.inject.fabric.FabricInjector; -import org.geysermc.floodgate.listener.FabricEventListener; -import org.geysermc.floodgate.listener.FabricEventRegistration; -import org.geysermc.floodgate.logger.Log4jFloodgateLogger; -import org.geysermc.floodgate.platform.listener.ListenerRegistration; -import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; -import org.geysermc.floodgate.platform.util.PlatformUtils; -import org.geysermc.floodgate.pluginmessage.FabricPluginMessageRegistration; -import org.geysermc.floodgate.pluginmessage.FabricPluginMessageUtils; -import org.geysermc.floodgate.pluginmessage.FabricSkinApplier; -import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; -import org.geysermc.floodgate.util.FabricCommandUtil; -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import com.google.inject.Singleton; -import com.google.inject.name.Named; -import lombok.RequiredArgsConstructor; -import org.apache.logging.log4j.LogManager; -import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.inject.CommonPlatformInjector; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.util.FabricPlatformUtils; -import org.geysermc.floodgate.util.LanguageManager; - -@RequiredArgsConstructor -public final class FabricPlatformModule extends AbstractModule { - - @Override - protected void configure() { - bind(PlatformUtils.class).to(FabricPlatformUtils.class); - bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(LogManager.getLogger("floodgate")); - bind(FloodgateLogger.class).to(Log4jFloodgateLogger.class); - } - - @Provides - @Singleton - public CommandUtil commandUtil( - FloodgateApi api, - FloodgateLogger logger, - LanguageManager languageManager) { - return new FabricCommandUtil(languageManager, api, logger); - } - - @Provides - @Singleton - public ListenerRegistration listenerRegistration() { - return new FabricEventRegistration(); - } - - /* - DebugAddon / PlatformInjector - */ - - @Provides - @Singleton - public CommonPlatformInjector platformInjector() { - return FabricInjector.getInstance(); - } - - @Provides - @Named("packetEncoder") - public String packetEncoder() { - return "encoder"; - } - - @Provides - @Named("packetDecoder") - public String packetDecoder() { - return "decoder"; - } - - @Provides - @Named("packetHandler") - public String packetHandler() { - return "packet_handler"; - } - - @Provides - @Singleton - public PluginMessageUtils pluginMessageUtils() { - return new FabricPluginMessageUtils(); - } - - @Provides - @Named("implementationName") - public String implementationName() { - return "Fabric"; - } - - @Provides - @Singleton - public PluginMessageRegistration pluginMessageRegister() { - return new FabricPluginMessageRegistration(); - } - - @Provides - @Singleton - public SkinApplier skinApplier() { - return new FabricSkinApplier(); - } -} diff --git a/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java b/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java deleted file mode 100644 index b772fde6..00000000 --- a/src/main/java/org/geysermc/floodgate/util/FabricTemplateReader.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.geysermc.floodgate.util; - -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import org.geysermc.configutils.file.template.TemplateReader; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.Optional; - -public class FabricTemplateReader implements TemplateReader { - - private final ModContainer container; - - public FabricTemplateReader() { - container = FabricLoader.getInstance().getModContainer("floodgate").orElseThrow(); - } - - @Override - public BufferedReader read(String configName) { - Optional optional = container.findPath(configName); - if (optional.isPresent()) { - try { - InputStream stream = optional.get().getFileSystem() - .provider() - .newInputStream(optional.get()); - return new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - return null; - } -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json deleted file mode 100644 index 24b30092..00000000 --- a/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "schemaVersion": 1, - "id": "floodgate", - "version": "${version}", - "name": "Floodgate-Fabric", - "description": "", - "authors": [ - "GeyserMC" - ], - "contact": { - "website": "https://geysermc.org", - "repo": "https://github.com/GeyserMC/Floodgate-Fabric" - }, - "license": "MIT", - "icon": "assets/floodgate/icon.png", - "environment": "*", - "entrypoints": { - "main": [ - "org.geysermc.floodgate.FabricMod" - ] - }, - "accessWidener": "floodgate.accesswidener", - "mixins": [ - "floodgate.mixins.json" - ], - "depends": { - "fabricloader": ">=0.15.11", - "fabric": "*", - "minecraft": ">=1.21" - } -} \ No newline at end of file From 65cd5573a2c3bd8104c2e6d76cba84b596cb6afa Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 12 Sep 2024 03:23:07 +0800 Subject: [PATCH 76/87] Fix: Annotated classes loading, fix floodgate-fabric mixin config plugin --- ...mpl.java => ModMixinConfigPluginImpl.java} | 2 +- .../mod/mixin/FloodgateUtilMixin.java | 2 +- .../org.geysermc.floodgate.core.util.AutoBind | 3 +++ .../neoforge/NeoForgeFloodgateMod.java | 21 --------------- .../mixin/NeoForgeFloodgateUtilMixin.java | 27 ------------------- .../resources/META-INF/neoforge.mods.toml | 4 +-- .../resources/floodgate_neoforge.mixins.json | 12 --------- 7 files changed, 6 insertions(+), 65 deletions(-) rename fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/{MixinConfigPluginImpl.java => ModMixinConfigPluginImpl.java} (89%) create mode 100644 mod/src/main/resources/org.geysermc.floodgate.core.util.AutoBind delete mode 100644 neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java delete mode 100644 neoforge/src/main/resources/floodgate_neoforge.mixins.json diff --git a/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java b/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/ModMixinConfigPluginImpl.java similarity index 89% rename from fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java rename to fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/ModMixinConfigPluginImpl.java index bf8f6d27..ffd3fe3e 100644 --- a/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/MixinConfigPluginImpl.java +++ b/fabric/src/main/java/org/geysermc/floodgate/mod/util/fabric/ModMixinConfigPluginImpl.java @@ -2,7 +2,7 @@ package org.geysermc.floodgate.mod.util.fabric; import net.fabricmc.loader.api.FabricLoader; -public class MixinConfigPluginImpl { +public class ModMixinConfigPluginImpl { public static boolean isGeyserLoaded() { return FabricLoader.getInstance().isModLoaded("geyser-fabric"); diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java index e654b7cf..d2511825 100644 --- a/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/mixin/FloodgateUtilMixin.java @@ -37,7 +37,7 @@ public class FloodgateUtilMixin { Path path = FloodgateMod.INSTANCE.resourcePath(string); if (path == null) { - throw new IllegalStateException("Unable to find annotation class! " + string); + throw new IllegalStateException("Unable to find classes marked by annotation class! " + string); } try { diff --git a/mod/src/main/resources/org.geysermc.floodgate.core.util.AutoBind b/mod/src/main/resources/org.geysermc.floodgate.core.util.AutoBind new file mode 100644 index 00000000..7d85275b --- /dev/null +++ b/mod/src/main/resources/org.geysermc.floodgate.core.util.AutoBind @@ -0,0 +1,3 @@ +org.geysermc.floodgate.core.util.Metrics +org.geysermc.floodgate.core.news.NewsChecker +org.geysermc.floodgate.core.util.PostEnableMessages diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java index e3d09d57..9268bbd5 100644 --- a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java +++ b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/NeoForgeFloodgateMod.java @@ -11,7 +11,6 @@ import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.module.PluginMessageModule; import org.geysermc.floodgate.core.module.ServerCommonModule; import org.geysermc.floodgate.mod.FloodgateMod; @@ -20,11 +19,7 @@ import org.geysermc.floodgate.platform.neoforge.module.NeoForgeCommandModule; import org.geysermc.floodgate.platform.neoforge.module.NeoForgePlatformModule; import org.geysermc.floodgate.platform.neoforge.pluginmessage.NeoForgePluginMessageRegistration; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; import java.nio.file.Path; -import java.util.Set; -import java.util.stream.Collectors; @Mod("floodgate") public final class NeoForgeFloodgateMod extends FloodgateMod { @@ -82,20 +77,4 @@ public final class NeoForgeFloodgateMod extends FloodgateMod { return FMLLoader.getDist().isClient(); } - public Set> getAnnotatedClasses(Class annotationClass) { - return container.getModInfo() - .getOwningFile() - .getFile() - .getScanResult() - .getAnnotatedBy(annotationClass, ElementType.TYPE) - .map(annotationData -> { - try { - return Class.forName(annotationData.clazz().getClassName()); - } catch (Exception e) { - injector.getInstance(FloodgateLogger.class).error(e.getMessage(), e); - return null; - } - }) - .collect(Collectors.toSet()); - } } diff --git a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java b/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java deleted file mode 100644 index cee3218c..00000000 --- a/neoforge/src/main/java/org/geysermc/floodgate/platform/neoforge/mixin/NeoForgeFloodgateUtilMixin.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.geysermc.floodgate.platform.neoforge.mixin; - -import org.geysermc.floodgate.core.util.Utils; -import org.geysermc.floodgate.mod.FloodgateMod; -import org.geysermc.floodgate.platform.neoforge.NeoForgeFloodgateMod; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; - -import java.lang.annotation.Annotation; -import java.util.Set; - -/** - * Mixin into Floodgate's {@link Utils} class as NeoForge is really picky about how it allows scanning - * mod-owned classes. - */ -@Mixin(value = Utils.class, remap = false) -public class NeoForgeFloodgateUtilMixin { - - /** - * @author geysermc - * @reason NeoForge is really picky about how it allows scanning mod-owned classes. - */ - @Overwrite(remap = false) - public static Set> getGeneratedClassesForAnnotation(Class annotationClass) { - return ((NeoForgeFloodgateMod) FloodgateMod.INSTANCE).getAnnotatedClasses(annotationClass); - } -} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 04901cab..7d7373f5 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -11,8 +11,6 @@ authors="$author" description="$description" [[mixins]] config = "floodgate.mixins.json" -[[mixins]] -config = "floodgate_neoforge.mixins.json" [[dependencies.floodgate]] modId="neoforge" type="required" @@ -24,4 +22,4 @@ modId="minecraft" type="required" versionRange="[$minecraft_version,)" ordering="NONE" -side="BOTH" \ No newline at end of file +side="BOTH" diff --git a/neoforge/src/main/resources/floodgate_neoforge.mixins.json b/neoforge/src/main/resources/floodgate_neoforge.mixins.json deleted file mode 100644 index 9c06f8d8..00000000 --- a/neoforge/src/main/resources/floodgate_neoforge.mixins.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "required": true, - "minVersion": "0.8", - "package": "org.geysermc.floodgate.platform.neoforge.mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [ - "NeoForgeFloodgateUtilMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} From e816246ee8b641c4cad6925032533a2e729ef837 Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 14 Sep 2024 04:54:47 +0800 Subject: [PATCH 77/87] Fix: Local linking jars not loading, AW remapping on neoforge (#136) --- ...ate-modded.platform-conventions.gradle.kts | 20 +++++++++++-------- mod/build.gradle.kts | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index 71b70a27..19deadc7 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -1,5 +1,3 @@ -import net.fabricmc.loom.task.RemapJarTask - plugins { id("floodgate-modded.publish-conventions") id("architectury-plugin") @@ -85,11 +83,17 @@ tasks { archiveVersion.set("") } - register("remapModrinthJar", RemapJarTask::class) { - dependsOn(shadowJar) - inputFile.set(shadowJar.get().archiveFile) - archiveVersion.set(versionName(project)) - archiveClassifier.set("") + register("renameTask") { + dependsOn(remapJar) + + val modrinthFileName = "${versionName(project)}.jar" + val libsFile = remapJar.get().destinationDirectory.get().asFile + + from(remapJar.get().archiveFile) + rename { modrinthFileName } + into(libsFile) + + outputs.file(libsFile.resolve(modrinthFileName)) } // Readme sync @@ -127,7 +131,7 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) - uploadFile.set(tasks.getByPath("remapModrinthJar")) + uploadFile.set(tasks.getByPath("renameTask").outputs.files.first()) gameVersions.add(libs.minecraft.get().version as String) gameVersions.add("1.21.1") failSilently.set(false) diff --git a/mod/build.gradle.kts b/mod/build.gradle.kts index 3d990ac1..3dfb8039 100644 --- a/mod/build.gradle.kts +++ b/mod/build.gradle.kts @@ -23,11 +23,11 @@ dependencies { afterEvaluate { // We don't need these - tasks.named("remapModrinthJar").configure { + tasks.named("renameTask").configure { enabled = false } tasks.named("modrinth").configure { enabled = false } -} \ No newline at end of file +} From 77adddcee4d76d45eeeb7300b0540b8a0bac45bf Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 14 Sep 2024 05:34:03 +0800 Subject: [PATCH 78/87] temp fix for modrinth uploading let's hope this works --- .../kotlin/floodgate-modded.platform-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index 19deadc7..a3c1942a 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -131,7 +131,7 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) - uploadFile.set(tasks.getByPath("renameTask").outputs.files.first()) + uploadFile.set(tasks.remapJar.get().destinationDirectory.get().asFile.resolve("${versionName(project)}.jar")) gameVersions.add(libs.minecraft.get().version as String) gameVersions.add("1.21.1") failSilently.set(false) From ec9a6497f3457ab9dc5a0c4b97135a8680e3463d Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 14 Sep 2024 05:52:12 +0800 Subject: [PATCH 79/87] Ensure the modrinth file rename task is ran before trying to upload to modrinth --- .../main/kotlin/floodgate-modded.platform-conventions.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index a3c1942a..244027e5 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -98,6 +98,7 @@ tasks { // Readme sync modrinth.get().dependsOn(tasks.modrinthSyncBody) + modrinth.get().dependsOn(tasks.getByName("renameTask")) } afterEvaluate { From c2fd9c7fe113f9c12ea214689b977de8d8ce6cfb Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 31 Oct 2024 18:19:49 +0800 Subject: [PATCH 80/87] indicate 1.21.2/.3 support --- .../kotlin/floodgate-modded.platform-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index 244027e5..420e17cb 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -134,6 +134,6 @@ modrinth { uploadFile.set(tasks.remapJar.get().destinationDirectory.get().asFile.resolve("${versionName(project)}.jar")) gameVersions.add(libs.minecraft.get().version as String) - gameVersions.add("1.21.1") + gameVersions.addAll("1.21.1", "1.21.2", "1.21.3") failSilently.set(false) } From b094a1daec2f2fb0f5fc0d111880464862b0d98e Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 2 Nov 2024 05:20:45 +0800 Subject: [PATCH 81/87] Avoid unclear disconnect messages when e.g. key verification failed --- .../org/geysermc/floodgate/mod/data/ModDataHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java index 30590dff..eded69cc 100644 --- a/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/data/ModDataHandler.java @@ -90,7 +90,13 @@ public final class ModDataHandler extends CommonDataHandler { if (packet instanceof ServerboundHelloPacket) { String kickMessage = getKickMessage(); if (kickMessage != null) { - networkManager.disconnect(Component.nullToEmpty(kickMessage)); + Component message = Component.nullToEmpty(kickMessage); + // If possible, disconnect using the "proper" packet listener; otherwise there's no proper disconnect message + if (networkManager.getPacketListener() instanceof ServerLoginPacketListenerImpl loginPacketListener) { + loginPacketListener.disconnect(message); + } else { + networkManager.disconnect(message); + } return true; } From 2d09b1dd948854f415ef56f6a7c4ee7503a3dcb4 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Wed, 27 Nov 2024 19:53:01 +0800 Subject: [PATCH 82/87] port https://github.com/GeyserMC/Geyser/commit/47b68f8140d1f7816f28a23baadb078a48cf344a to floodgate-modded --- .../floodgate-modded.platform-conventions.gradle.kts | 1 - fabric/build.gradle.kts | 1 + gradle/libs.versions.toml | 8 +++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index 420e17cb..e2a1d829 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -134,6 +134,5 @@ modrinth { uploadFile.set(tasks.remapJar.get().destinationDirectory.get().asFile.resolve("${versionName(project)}.jar")) gameVersions.add(libs.minecraft.get().version as String) - gameVersions.addAll("1.21.1", "1.21.2", "1.21.3") failSilently.set(false) } diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 7aef15a6..559f6bab 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { modImplementation(libs.cloud.fabric) include(libs.cloud.fabric) + include(libs.fabric.permissions.api) } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bae1d82a..81e70989 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,10 +5,10 @@ indra = "3.1.3" shadow = "8.1.1" architectury-plugin = "3.4-SNAPSHOT" architectury-loom = "1.7-SNAPSHOT" -minecraft-version = "1.21" +minecraft-version = "1.21.3" minotaur = "2.+" guice = "6.0.0" -cloud = "2.0.0-beta.7" +cloud = "2.0.0-beta.9" lombok = "8.6" bstats = "3.0.2" configutils = "1.0-SNAPSHOT" @@ -19,9 +19,10 @@ floodgate = "core-repackage-2.2.3-SNAPSHOT" # fabric fabric-loader = "0.15.11" fabric-api = "0.100.1+1.21" +fabric-permissions-api = "0.3.3" # neoforge -neoforge-version = "21.0.87-beta" +neoforge-version = "21.3.0-beta" [libraries] floodgate-core = { group = "org.geysermc.floodgate", name = "core", version.ref = "floodgate" } @@ -47,6 +48,7 @@ minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft # Fabric fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } +fabric-permissions-api = { group = "me.lucko", name = "fabric-permissions-api", version.ref = "fabric-permissions-api" } # NeoForge neoforge = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge-version" } From 5cc21208840697c44911075ac3283d209f5a2551 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 3 Dec 2024 03:56:15 +0800 Subject: [PATCH 83/87] Bump cloud-minecraft-modded to beta10 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81e70989..50b61db4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ architectury-loom = "1.7-SNAPSHOT" minecraft-version = "1.21.3" minotaur = "2.+" guice = "6.0.0" -cloud = "2.0.0-beta.9" +cloud = "2.0.0-beta.10" lombok = "8.6" bstats = "3.0.2" configutils = "1.0-SNAPSHOT" From 6723ee7875991fdaf64d5f2a717a0c7123d69906 Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 6 Dec 2024 13:18:07 +0800 Subject: [PATCH 84/87] Indicate 1.21.4 support on modrinth (#139) --- .../kotlin/floodgate-modded.platform-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index e2a1d829..56f946a4 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -133,6 +133,6 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) uploadFile.set(tasks.remapJar.get().destinationDirectory.get().asFile.resolve("${versionName(project)}.jar")) - gameVersions.add(libs.minecraft.get().version as String) + gameVersions.addAll(libs.minecraft.get().version as String, "1.21.4") failSilently.set(false) } From 6b41763f29818bbab166f14ef23e36fb3b1546e8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 9 Jan 2025 19:43:03 +0000 Subject: [PATCH 85/87] Update fabric.mod.json (#142) --- fabric/src/main/resources/fabric.mod.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 62a1c738..ba341e71 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -24,7 +24,7 @@ ], "depends": { "fabricloader": ">=0.15.10", - "fabric": "*", + "fabric-api": "*", "minecraft": ">=$minecraft_version" } } From 5ba81a2986ad6eec918e4c125183e664a3f639c5 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 21 Apr 2025 12:10:37 +0200 Subject: [PATCH 86/87] Indicate 1.21.5 support --- .../kotlin/floodgate-modded.platform-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index 56f946a4..722404a9 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -133,6 +133,6 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) uploadFile.set(tasks.remapJar.get().destinationDirectory.get().asFile.resolve("${versionName(project)}.jar")) - gameVersions.addAll(libs.minecraft.get().version as String, "1.21.4") + gameVersions.addAll(libs.minecraft.get().version as String, "1.21.4", "1.21.5") failSilently.set(false) } From 9800802c8def5dcab3f6419f84e9c31547e7c943 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 26 Jun 2025 20:59:15 +0000 Subject: [PATCH 87/87] Floodgate for 1.21.6 (#149) * Bump a bunch of versions * Tiny fix * Bump cloud * Only specify 1.21.6 once Co-authored-by: chris --------- Co-authored-by: chris --- ...ate-modded.platform-conventions.gradle.kts | 2 +- gradle.properties | 2 +- gradle/libs.versions.toml | 16 ++++++++-------- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 12 +++++++----- gradlew.bat | 6 ++++-- .../floodgate/mod/util/ModPlatformUtils.java | 2 +- 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts index 722404a9..e2a1d829 100644 --- a/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate-modded.platform-conventions.gradle.kts @@ -133,6 +133,6 @@ modrinth { syncBodyFrom.set(rootProject.file("README.md").readText()) uploadFile.set(tasks.remapJar.get().destinationDirectory.get().asFile.resolve("${versionName(project)}.jar")) - gameVersions.addAll(libs.minecraft.get().version as String, "1.21.4", "1.21.5") + gameVersions.add(libs.minecraft.get().version as String) failSilently.set(false) } diff --git a/gradle.properties b/gradle.properties index a5ca268a..5ce1faf7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,6 @@ org.gradle.caching=true org.gradle.vfs.watch=false # Mod Properties -version=2.2.4-SNAPSHOT +version=2.2.5-SNAPSHOT group=org.geysermc.floodgate id=floodgate-modded diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50b61db4..8e539f25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,14 @@ [versions] -geyser = "2.2.3-SNAPSHOT" +geyser = "2.7.2-SNAPSHOT" blossom = "1.2.0" indra = "3.1.3" shadow = "8.1.1" architectury-plugin = "3.4-SNAPSHOT" -architectury-loom = "1.7-SNAPSHOT" -minecraft-version = "1.21.3" +architectury-loom = "1.10-SNAPSHOT" +minecraft-version = "1.21.6" minotaur = "2.+" guice = "6.0.0" -cloud = "2.0.0-beta.10" +cloud = "2.0.0-beta.11" lombok = "8.6" bstats = "3.0.2" configutils = "1.0-SNAPSHOT" @@ -17,12 +17,12 @@ asm = "5.2" floodgate = "core-repackage-2.2.3-SNAPSHOT" # fabric -fabric-loader = "0.15.11" -fabric-api = "0.100.1+1.21" -fabric-permissions-api = "0.3.3" +fabric-loader = "0.16.14" +fabric-api = "0.127.1+1.21.6" +fabric-permissions-api = "0.4.1-SNAPSHOT" # neoforge -neoforge-version = "21.3.0-beta" +neoforge-version = "21.6.11-beta" [libraries] floodgate-core = { group = "org.geysermc.floodgate", name = "core", version.ref = "floodgate" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 35073 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 delta 34912 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

Gn4F+B~IivciU9^t0j-Mgao3 zSDF_?f~c=V=QJRSDTG0SibzjML$_?2eqZ;J*7Sv$*0SQ|ck$fX&LMyXFj}UH(!X;; zB_rKmM-taavzEk&gLSiCiBQajx$z%gBZY2MWvC{Hu6xguR`}SPCYt=dRq%rvBj{Fm zC((mn$ribN^qcyB1%X3(k|%E_DUER~AaFfd`ka)HnDr+6$D@YQOxx6KM*(1%3K(cN)g#u>Nj zSe+9sTUSkMGjfMgDtJR@vD1d)`pbSW-0<1e-=u}RsMD+k{l0hwcY_*KZ6iTiEY zvhB)Rb+_>O`_G{!9hoB`cHmH^`y16;w=svR7eT_-3lxcF;^GA1TX?&*pZ^>PO=rAR zf>Bg{MSwttyH_=OVpF`QmjK>AoqcfNU(>W7vLGI)=JN~Wip|HV<;xk6!nw-e%NfZ| zzTG*4uw&~&^A}>E>0cIw_Jv-|Eb%GzDo(dt3%-#DqGwPwTVxB|6EnQ;jGl@ua``AFlDZP;dPLtPI}=%iz-tv8 z0Wsw+|0e=GQ7YrS|6^cT|7SaRiKzV3V^_ao_ zLY3Jnp<0O6yE&KIx6-5V@Xf^n02@G2n5}2Z;SiD4L{RAFnq$Q#yt1)MDoHmEC6mX1 zS^rhw8mZJk9tiETa5*ryrCn&Ev?`7mQWz*vQE!SAF{D@b7IGpKrj^_PC2Cpj!8E{W zvFzy&O4Z-Exr$Z*YH4e|imE`&n<$L-_Bju=Axiik+hBtA4XNDik(G_;6^mQ3bT)Y% z6x=a+LKFZbjyb;`MRk~Dbxyc&L; z8*}!9&j0wewMM#O`c#7HJ|+Gh5%3~W10b6sdmCg3G_v+@H>n*c5H`f+7%{TeSrzt89GYJqm>j-!*dReeu&KHubhzjSy_c~BJcbaFtZWAB}~KP3%*u{zHi zVSUi2H8EsuSb3l7_T1hP!$xTtb{3|ZZNAJ{&Ko;#>^^43b7`eE;`87q81Jp;dZfC< z$BD`h-*j=%uTpG8Me6dF zrH%)Bw-a0}S41ILo*k2zn6P@?USXtC>pX*tzce7A^JD7^^p7K5kh-HO&2haDTL%2^ zSWQb2B6}e*;x?eKq?CdG7F=wHVY)Lb(kQu1R#1Fx|3?>_%cjNM-xJlAg9kr`!>&;E zTYmHhqHh&qbfO`~w3V;BM(q(_Q-5^!esaBI&QbZ^%N-ZDYft#FTS;%{ zKzlSwZIS%zDi#%DMK>`_vmE^krJL5@PmpT2m26Q`O)VRAL>){MN45|7GTk=q^zLpF zjS(Os=`#On$XI#$A5ewac9Ma}mDxSu^5{#jHC+24a2GbfBJ&Zn8W= zm=l7VE0g^z$3ikyU#ysh8b-PH(&-yZL$JV-of-ZM@~N^#DbQ3Ltlq*5@>WzSNxrRK zYl2VS8r;TT`wLfD_O0dhX9vR#S8rMOuUCRkWZE#OjRi$l*#C7}mgGzZBD%Z=p3z|CaVM$$pyW5-pJJDCToY zO3R5)P(Gnd>6wh9Z$Sr@cMXmClU(h-@5kmiBTNTU-|5vq&Fs!ah|o47kW?SO8uWv> zW$=Ud@@|*9p@Rb=!wl;%>k)kH7fPtcD=gd}^IxN^=Cg>zq^jij!f=1PlT|9jh3K9g zF~Z)B;kb^a0hLmJvON8Ho)foq-oC)&E)b|a^|b}6n!8&AIaousO^VnYzYfuijuEo5 z7IcUMbYD=vec4eZX7;p31NB+T9BOMJp9ZI9$dH1kJsJpEtf@}tL4)_*PxgdOge9_EaR!?wWtBx%*f$IGoR>f3Qf2aT0%+fq=1xVEqRl;UaA2Ncs4B1M1#foI2bj4 znX}t7;-FCLK&;>ZGP}{GxK67$Kz&pO%%J>DBMP_zZsLOmdpDUDp&f8=L>(Kcj+S^jA5dco4-7XN z)h;m#54CEy9)Ch-E7gHP@a@TXl=_%&|iUlIrQzn=LqONBu9FCn`3f8aqvRu=RrJ_RH1^Uf=t z%Ir*({+wEeC??C+u!hCi<5m`RsRO6ti7YaEtY0|U)-QfNsdN{=83K_}m$0Z=ElWyt znvo5=%f<;|hNnL-r#v5ab&S2*yK>~a7m(My$cfd*tff?=?7-j3^|&9H7G*W`)m8M7 zzd0+b)c@`bQN1-^dC$_04tK0{mU5tx_zo;&TWou8F(H_J?O+Y)VLXzmU^> zvL!5+1H?opj`?lAktaOu%N#k4;X;UX5LuO`4UCVO$t+kZBYu`1&6IV@J>0}x1ecuH zlD9U=_lk1TIRMm6DeY2;BJJEE%b0z;UdvH_a3%o)Z^wM&<$zhQpv90@0c+t?W`9kolKUklpX5M&Qw06u=>GPCr5Imvh*% zfI`tI-eneDRQo?m*zD1i;!B>*z4Xioa_-S=cbv-k_#Wg=)b$0@{SK>Mr!_T?H`S-?j;3$4)ITn$`g;J$^TppD)^pRz#^l?XgZ2CW z3g5G^iF*GZYQ}{B|H-fqh=_>)E~=3y3Zg=i75G5E)*a>R9bn~cNW{h5&P(vQ6!WHv zw1-89smtY~JnCQS(=9zM)6>UAi%G-r^LA9_HF0Vp3%JF2P%+E&^afy61yxnAyU;Z{ z$~H5X6?sMoUuOT_tU7i5i%5HI{^@#Hx@zhtP55>r_<3LwusK*SC#%i+gn&iRg z_8UN=rLVp*gT(K~{0X0f_=?~bBbfB`=XrTFn3U!)9n*@Uj$-mr^9PNi<22UJKAK&D z|1@Ck3(Ub;>68;)gIn_Zu{uoVRMhAkIqgBS(v2b2{gf?0xd(1sJfY`56mVy>~^w!wmX_kjW8#?_Nk{}zB9ULo>4fO(vnWfC+pG4>%*KZ?JuCdXu%aZ}q7pC%E50@U9+KQZL5 z!*I`SOtNf$Y$CsRsNaf~yyw^>#X_mCiF&*gr=cBb zoPu7PwX(+Wvl~i(XH|)jj@Cu+rzpJMn4kVvCJ~ReCf08viF$q9;CYnv-96k{G?pf_ zQglN`JiS#vok)~^Z2>41#7LPFgd_xrqNO%DQI|!Qs|nWt`co#BwY$&Wm^6#~)`_1k zpwiR~&z#mtSDuYm(=NoLv$%Y}bTjog$RJ8$j1(s})=}su0b?o8i28-|xu58ipFBml z2`4qZ$BbY5>(i2%wmh!+C}$97?X3LgTQ_{(SaFZvq9YCn@BNz z&h#;4h?5#`&_0()uJ;_rR(Q^eY*=&vu)#EeMeaN1puPv5+iQFg1EC(`_99_5v<1r4D ztc(+-eVWf_np;q$M*H49#{R)eIWCI%R&6F34;h9eNG(XNO5ao2MI8;j}y% zZeA>zX{#$;muhtY{_|;bkk~!U~Ih z2QUO}hk~o?sn;#|Mt$0}4=+BRa703n6>fBm(cesk8Cmugg_wi|BWj}V-VuU9jNH+o zgNYGSKPm>qR&nI(2Gu*})AOBfXf0J~CC50C!3KXu6-qZAG!VMZbmnqL6HWG>o$^sjoSLbQxra@WyKV$+_Qe}t7d)c`bpJG++ zw|9D3>XUH^Wplo~MN%WK18n3HeXoe*jKwVRK!=RMtIr1v z;Py~7;eZl&=^UyumN&CecrGBEat}4?mtZ>@`wPjVK@Z)FZ;05^9kztq;qmbxQIJ4kXTk)) zaVfD^K2x7SB6E!Zz@0p|Fkge*0(0?ogmTX8d=?n{2x)}K2$`bjDmcLg3#wU)i)by? zW^G8rRQKBwjke5zHScinRlE|wo0XyhBc9R52IsKWf4-@=l!yO&+l=K`-7Ib9U~hPy z!cH>H)e6$;m&w^0d`axGqDwBgu`B+L4a`xr#5g%b=0?c41`|lx0O9fiIVaFAsO$Ol zayhm4C9X%hzUf&ctylV$%ntuA$(yo*X`gaVX0$|x{#!YK^cvLmNWPZaTd3&xP7ny% zkn}2AdJkpAgmsh}Q$tY3(2RtO;%R*~8r#ZbSbMR4LaL9Sb6O&Ce(GlO${jtl&`n|D z9;zUQPXCHqTm&t^lk9RlZiiquSY_og^?kgVruz%myd95Fr!V z-$OIXSt?(pxN-M{NjA)j1KKIp(&c2RVjd_}7+CbQfw zTRjg}A0~}Ht_?-@wD0bI-;LQwT?mKywmDZ7*j4>4pR6@UVU3mb?-cbQt~aIG&RBjl zs-4UNtOH3+dAF%U=={qB@qijh4J6K?Et zPLlfPlv<+i>ty5rh;Q>iGFoaq4LyBIZl3L{KGUmqPL~ZCosOl;7w2SxcE}pvK;5|6 zly3JjUsvk|d7L3bFs&;q@_|p?vdU_UzhrS$Fw-_NoEdoIT#-0hKC37!>-i6FaO(es zY97)m4YO<|eqGMrYejC&-IFmc{=P7>qFWX;)}q!&e9-F59o>V+`X>J}%Te0$|A_jg z;6Q>k+$6iPv$1Vs<78u-8{4*LW82u+wryJ*+qQFa&bf8(!&F!IOw~M0&-C>FDa>dUDb=K0XL# zw0(2m3{A-k482S5U_oqLwJfXJ&hK;~y*=aC=O6A%-%#42Q&b23|5jxM95JBdZPYaZ zXfK@oM8KAHHezs8pGKBg&~JxSIEpSkAV#PMNmn9cSho6yp99k1>@s>RtEd>t9C~AY zeIPxowntzs?~#6MLEx}yoP#?zox$DeG|R2BTpWm4|ur~9xSfHIzuGC@6pqmX7pgMjJ(%@TfPe-_R*z} z?G`log;t%`w|osj`Q=o;b3eUdr7~vMs%u_SR~yw5YSV< zCjH3%P;{@}YsQnd2niYKw5xjRT=l+KGNc4EBJEhU5PcL0&AYJKT=%F!lBO~|KuS?F z#mZmJ&r`D*k0xzZ+7V|y*>7PfIAw%7o6`O+>Y}zX?gyoA#bS-k=Btq|Iv8>=dwnLq ztDGW(e=|)RNp1FXF0QVRnl;%RKu53$thEYFoy>CS@23w@i&e{$OdG1VBc}{JU{U#F zwH%=_7+?@4tR&iKFXxIGfF3882kwL)Z+a6Yc*w$8caV7zWp0M|OH&ZTtUl$fzzh#& zfw9Hj1ksBWn&|*dfx>cCXv{oNbnHk_y#R4gg-YIl4M#RdMVfxM71t{QDB(iNv{;mB zc;!)+6No%125qe63{8*pGufr*E8npy2|=hf+Uhk-sj)I=2RnEW=^NHaOWMk z=vz>3?zz{j1469&r^ENB>a+(8+P&hk!jU4m$P-G4+Yz(o+nB)VtQ&P^hgF!{uFi3e ziN#EDsD^dJ#q69Y^=Xa^Adnr}xGdaum%p83{eXS8&oymVk*QNTi@@=#Pj5xo&S+Ou zv_SSM@h8NOR;W@Z2#tU82W!k32`oFZD`czy_}r)?i9zTbNy?fvcRO8_d`xgb_sYKD&sII$b$Nn7Eh#KqU? zyNW40j=^DE+N#hk&{>`!#~=4qwdc zc`O`^P?=MJd7}t9kQ_;Y-FFRFyU7H#U}*IIGrMaGS;(huDhrSCZMEv`4l*L>0|Ka~ z<0N%Sj}sER6P_%#mOu8$Kw@E@aca-bDs`B=67`7Rx(zbG)huE!ntMSqxYEtm<|T2{ z*HFk^Hy{j_`VG;Oenf}ek-EX9ot*TepWIwIr%Ay52WsOnkO~@7Hq9NgU|nXS5oD#h zO}VW&EbEOlv@UsxDtl8k2c@r>1Neg^32rIEev5ChX8Qrno$5b~cSj#-Qv{gafRFYq z^S#(3t?&|H*;Eg`2V&Z|ba_X@Zu$wr(L3s;tW zKzre+#aaoc-&J3Pu?@IjT-OxH%9hKO%`e}d^-#RRNAwQ6_+gi2QVM8$|BKEn&jdew z?9+{Zk+1T7baFB6=^G!aj@VAR~humfi-l zViyGGBO|vZW+t#1P6BtOhIdVD?K?3NuRtmg1F<$l%`tH z=i3)1Ib_~WIlSU|DA>Jfqe6vi_LL8tKE`$=<_b1e1F^AbX+GeL2#+t15&ilJV)<(eJC1YsLq!kBURWXm@j=aN ziggg*6ED!xp3@7Qi|rZpjb^yp4bmUGdL+Q=L|nQ@2^jbIkAQ&04-DqC68gGn47Vd7 zV*2VElHY-bQ`mu-+yD=4Xyy*6OG0D5>ap_j?<1|j^wJV=eFM|@U^G=Wml{n<)UeJw zt#(6=pDAVx%l@U^bt&{b?6`r4ghT;FsC$CG9sV@yJjrEYk&aY$mwB9NncS#pS_C!jJrsaZ&3!#?70o=Q`BV3U<~{1wqp*2!2*pO zz|j(MQ{$6wVIq^63d8^To0EK-!n%YkLI)J=cyYHh*ipmnh3JC(f-8D<&=JDkV$9_b zOoDmVpgwmk2BnEicb0JQb-qFN^$yJ4T)3HQ^d&<FZ)~tN-}tfNZD#4}_=Q4DXJ$TJ2(7xfGP%}@jZ5;_B$!j_jIYL%vx-MOvcYDG^%g8P0Fnk0|*KF5n< zZ;aH_%5w!xFnU~}VKO$So2y_AEMN0(o2(*Rqb_PUv8I8 zqa<;%Sv@?43q6F+)=eGU{26?G&Q9@)CPLT_2^OBUG#F=KGZsgs=U<5iux2vM@|rO^ z8R8~JYc*2S^3GV`Bl99&4*gyq2NVpYYG)JjH0V;aG@9m65bf6BoyJ+hM+qDBaivl` zq_>6LlWE0N^zX>(m`VuP=7L>^;)AH-U|ikYVyYbLM$A|+{w$Hi7_=InfUyi~EDHXf zG|w;^m$3xf&u_G@FM+cGf-Bk$!SFHx9jv`5W%BSIof=dDP8zKnfRnL zj;-qFizeC%D0aW4oman7BX-Tvqoh<~wm{D%#Lc`$@E&u_#bH$f#)A@@J(nmjgYs-N zmOmfsU7S#{!F`&XBYQFPasOH;7r*hj=^b0E7sZYoy^CtLZz7SMH~%fC&CBnnTRlmQ zS8>PQI{fC104|v;iuhugCeH-Vy-(3wc{^u5{J!-JsX0Z z?+(-k{q)JMA=}slVn?x65ilVX$GQ6ZvcBVid{QKV;i2a3EJ!2O-)S~s?U3<;-}T3uZmj=(+a4wKN z&lTKS>}k`6jd#S#E&m;up`IMiD@`LA)SB1o4iNq3Dxf$6PU`}&c;W7UHco{gtn&@( z+VySYn{ojMdK#S?+Y~9Yrtk@h4Ah4g;1n+OY zoOX(NSJu*iK!piCa$Oj}YTdo?=D%p2#;=-xaLF>~ljG9G_(yjiBjw=F>A^-s>aa2V zYAu7tQqY@rWERHXz_eMV!r^9B*pBr+{w;#AlUEXoP<}^^pWGo`_v-eQe_GoVs3!8Q zB~B~jfuLs&Z{>Ymvo}WtTeh51P~Jpld9Wl1a_x3N^n4-0xDE_T`O(rxBKsrA{Q9>5 z+6P_+YdSuRkYuc+2{GM+z$4$P( za&zLg<{!gYJ5W#V*5>^Mclq+Ns;J@bO7y*C(X6mGWE1qVv4NK`s&)YizS*MYmCaZ8 z7@aHuym4w?;p*fQLM*&w8DW^WvAjd+H4*^#POr5F+=}Lwan9acKKQIVzC{!8m%-?t z?hBO>gcZ$E0a=gm)Xnh&?137cU2q`g6j##6wMGBc-sw+o7nldAQg5$P|wyNjBm|Kth6{boc4!xwg zo=3iAM429B7TOz69wIYLt`}G-mN+dyYNw$#m?6=o2Fq3K)tl#w<@&L+nxen%w`y^Z zv2eDzv34F^6gCzwRRrvZjgLa8plkxRF@_2wMOPZ4{Kjr{vVw|r^L~{Af)5pxcda`n zI*BU-rrpd-a`6{(`4vZCq~r3DK2P{hQP0sQ*R+4i&-iu9-dhuU-hR(fLlDVhkR(w) z?YLr!z3yTGlOJBWlG+>|f>M~GklCprh($i0`nxXusZLIM8n^(o(wh1UT}CPhRg z@0`{bib0MxLFfkAi2B7RfBy$Y?Zq&y;IDdWuM=}7^P9r9jX$McQc9rF!DeOAySF*Z zada9}9!4!1U4z=%Y(`*1h@Q1>jW?|mJg-nmxsO%ui6mrLmIEEOcH#c5wdf`~OLEvh zSBu$&fm2ji6BOn__TOF^BJcn@2CH_9QB~{)om+On9_aveRS2izb##Fa@nhC?nVMnX|RtX-z6>cT=(0Iy4|#8qaerCl0#%2f#;}^UDDsND zKMm#amLtRipGBl4?fMl*@yk2R63lAJ|8BR zhZ6Uf1^YA#v(QbEjROeSdLbLo{l@H#9ml8{DdenI`2}0CBUixPgHsMb_LLJk;(34P za1Zxev1)&aKxC*2%9wPvXgKk2)oD9yi03nHpw=ZJtx%;?5GoZ>r6aOrxwU{IzQE(V&6q+NDB>tpA&Ml{Rbp&tc<0Q*g$^T8Qxyr! zbbHwNp+$j?9i58XnGuR6vKomH*7I7(0e-g|y^FnsI5=wFOQZWzHX z+TLy+z`$$#*)IL&6{Gp+(c+!JZMSG%ik<@=o6&vULJ07KceEQOw3#gbHLTze5D740 zB-S}?Z?_Ea6y*fN>i3)aUEBLgq8(Fn!X>Pv!1ZQ^xm|W!PTL3EC$(TQ0q) zI{6F8wFY9HN7s96OkcGY8YctK+r2#P<@}{b87FR9a2LXiQ}w+X2oyoEA#V#tzK^_>=#sxaQAwv64r{n^)q(cy=kLJ^xA3$`MFrRsG%f#w6H zo-RxL&YH!thaVKJHy{Z+>vA|~3L^Ong0nqBe|VARqm{IH zPgWx-(4c7kzI0rYA$BTFkl!w{%s7Dl*umt-f_^0|l&cKp%bL8cQ-z6g3L|VOMdD8K zTBSqL#Ty!Q$)}mxYz|k23}iA#$KR~I2?ZjuqM_DagmgZlLbyM4kS|}0n!|-cY6zxw zvjEbLx4HEDdszf3zJ00{CH23TUXSbb))4@Hjo)eV{nnP6`$xsT2oUDPD7dV`{i;yCdXEf8@xzYf_WNKD$@`=h3jn2cSmi44u%J}bhjW6rk7&=cmDsKOi3 zB;$EIYn+AVQJ3V(aRSolzEC_*uKY97{enwno+)BCu~B{S*<9!3N|HMuah;4>7eJ%6 zu*97x!n=|D>mqw6$xWd*1iHooa)yMYa~!5ZGJByE&ru0Eq=wF!Nj#!5;0%kE@+vpO zQx99G(&Q9_KH~r*9=!LuA3s_bM;|?^Tc0^K%n(vkHrp_rNa9f8#HK#gPw|*ss@X7 zx-AMkGyTHXy5G*LvC|_-XXqWK`Qk=?_5Gm0fX_K^L581dn?70-!p=#Wr5F)AVD&lqX?k5ZCds@PNa`~e= z*yPAeGHRc+C#7XzwT`<72+_NC2LI%~%rj9VYiM3nEIXC8aO{X)(Vm(^FNkcUZkqkM zPcxs>F20(WoI^8yI-M*W^*@Au9kneO9t{MfgzCR#a&#Le0M<*>80`~~nDg{ZxArby zo$Y9~@vpRA>Ck9o#MgvWq%7slroQS4b@mDy zwlj{A+LBP!64Pk42y5qWq_|?<|~2`d{dWc@J)8NmQ1MmwU_f z(BhP6Aaou_Bbqj;2YZltnJCz;lOw4y{cm!X+dOQ0@Y59Nt?>VQeK`Y zMy!(JQ>Id5nwc-i=r8*!6!`6TawnWg?7!bqfiP8tAV$Ly42msb_*(@s#(T!GpTUkc zD!mZ_@R|Y*LD=Y3NNuXT77gwIP&U-y1=5x6r1H`l@=2F8? zT$bTs5TFY+ibd@lq2Tj+soiTC$hagTt@Pb6_Bv_yqv8$;#std<9Eq-SB+e5Y zfaA4+v4weJHz~7=vFTbEDXwAU#hqIXm+?9l*uIz?G&n&XY)P=7Xa=(b(Y}%E0u#&8 z=Wlzs9e4BP{=guwrHDGVj6lclvOKcH;D>RICH|(r6&$+VGh!;#Sqi1=t)sa`m3uU9 zGW6#<=y6m$;mwa@DueLJ;1~71L09ZRf%R+p^$1d{U9B7c4H+t>I2wI=;g|yJY{^*v z96y-^r;c`{oG|$$n#8ZCpCi;aWX}}HBn`eyM8l<|52tV=kC{&F@pbP((h4n7G&ra0 z^OMQ*dadN&z7nHGY7LF}-u6Ojs2jYd)(4+H=os9HCnMbF@M!xppFtaL09QkH@DOGPUKwd^GG0o>i2e{jp+U<=FlNCQH{3 z|3r*7l%mxP?dZO3a%0$ka`97q`cBKWSi~l-UenGJl=EZE=-xt>K(Z{%u25OI_=!3> z7J;6d`@5Iee*Tur4P5Bm4g%i?o7Z2SOiA&7u;D`mAg?E~YXbtGKgpd z-3w_IInyw|OL-O7@x%JZ^{PWArTKAB@s;cTLz1$>Bvpri4aW_!v%}K?>4pHg#K~ zr11WXr^rE}+clwR%9s#fWG#A9Dy){QkF(tnME|-#lG-m}neZE66+<$Lchl-Kd_qxl=;leBAoN&dF(zq1F0ni*m!O z0B~bVIq<}9qlH^^|+A?q7%7w(c7%hGj9 zp@fs;Hg*|}%^z*_e`<)f;n^dQ%3{M04W!CqBetpWaFCDu}| zR;)Z`F5cC~Li}|b7J3QH8u=5Cu4sViy=%nsuL&)lBN_peG`F-!)Q-Ns)5=STQfaWx zcWMMh5zdkvUr}4;2%J>>Is@`!8ioYB5ntivmIND~Q4oNX2m6D@tn*QRsR@sM^JieLBJ#3<|;Fox;Kk{n*JG)EdD6C7ROyIRUeyQHT}k#(8dhbt4dLU7at$qs5Ld*{lVk4`G7`qZ3?u9E;k4JZsj-!8ik0#{ z)CsIl%*M|cNeY2g34VV)DSAXUx%xU&fJP|2w1K$<$-9)nGmGy(>x_K7 zuoC}ChzZR5=$DfnXGGFgi$4edax2F3w@Luf(k16_ij}mW9PyeC9-K|?oRfjZDWS>t zn-JP9tp0L6!mgj8nGXWO-@@7yCTwZ1q%JH{R}d=}FUO>IP_ihXO(9`|?ahDT{bJcw zgZOsB7w3G0m&N5*<_BsGmF1ORGrfL9cbkN^5%`a0!G@!<`yao|HQfFQXsH^U)V>DEQk+ zNWzJdcN#w)3~;j7TON-J=`alS@SPoC8ZgXXNAlqb@Rm43ESBKeRr)pxqVZ1-oKI(2 z31=KL*D2vG0uF1iD$XruuRy_)`PD*f1l0VxWw*Kw%kiejS*M10=+>zYc|;P{;JBb;Y^k^qYNJz-4%1Wx7_Acm~mKQ|k-aWX$!d_Tztg z+}e+lUrn1<+kXa$mf}%Nho9yu4@tjz7}^X{U+1b?H)1%11~(l#DC~nG2%P?NrCn)| zj4~E`9xjhbTZYg=1D=hQFn-4(@15}YP=m6*ZBSTSiGk1eNHdrL3pWvV@(@b$R&2%* zXM`}VQ~9%%KPXEgl+K(fXMFI+7j;Yn9S}f#NzkC zf`GK5{a;oL&;P85fi-bvm8nq z2h=j{9PpveLTUMA8)xFD;CL`LR_u)zvYHlB@a#Z%yShmxHBWIv2U_FLRJMt%XBRa~ zbSp8BQ}8%pY9eOpQ1$cJ8ZY$IiH3=WLJY;J4gz1KVy%4bAJaLrq}2&&!_g6NY|l-i zCRkXX95pTT9@pFis2DVc@_IKK5BXKbD9@%9mM}NWLH{l--zX#hRe8*sDxY9{w9(cV z%xVKdMMf17DJfjf&Mm{?tITZeMJS1vu(Y(FY)^C20X6t-({kWO+;WYr^GM=$_m3_tz>=$FF)g?aj{lez zlkyIqrTC8&p!9pZq{Wv|?eOdP|6Z9SUJPH`E~P_fen^QVFJ#13Ok(^{1G^Zqu>)kr zlz{o4q)h1122d)5L`$;`-U6*l9gi?}wbM2vt1C9AD}{(=IJH*cb#&*WUjUfKa@k0d z^LeZDdFJ|}(fVRX>&3hH@uyc@gweKaH%jV-(dE$`!5WvsUeV;6z8A8O@w7+aC1E)B&M4l@QdZvMqvfzvV7H2$6V2-;3rgffxC ziVCk|?5Z$y9BMRLbFZ#EwDI~D;dC(&MEg^8U5#xWG+KD zsJ(AoUCBE6g@7*c6x^MR7{M*Amua?g?Qfh+6z*e16&!K}_bjK@u~67=PXg<=Ho;e| zp3-A?F3h|rZGoLl^VN$xpzAp2_Sz);34ITZ!6{xfjlsS3DM=Qn`4EM8pNJj)^Z7H( z7Enql=nG1Jl7gv(?{AznQ&=@9b!oO`wa+!0^!p$RW79Bbxt+u$Ip^xR_p5KjTU@4p z%%7RH4IDTduTU_eZAM}0=mN5?+J%Mcnu)Y*EhN%Sb9e@UluFGn%Y+TxM1xp!_gco) z!F}~n8f+R;u1st+i+-jDT(4(Z2yWkk)(HnvP9on@*fV?i3I|6+!;}u}^fFMe8x*+J z9aCca>;#3%&yU7EgG>Cm_IZ}ejtdj;hh`F=2(4$$>Y8nHjxU(;C;@%$UHuisSqGZ> z#kxOEjWGgSocM0G-6CFCl#fuDmM-GsLqbCu0W3zqrvL76uze|6S zF2cmoWWQmb=b*-KvJtSOn!WVZM?Zu=BM_Dzvg!V_ppP&VyZuyK#p{p#)#ldTWnC%K z>lA_zP8|p0a3CMDLC)dZY0VZfW4VeyKR@&!SR)|k8iTfI4F}9BL`Fd$teC9y&)&vi z^GGnV*}WBKIAxemrIUz9E#bACuqNfQxomfG%{e3>I=Z#TeY}h|gqlmiC$zWx0DbL& z^o&byw1P**ZI-NQ7A@RL*>F45$!9QX*FFd#5@7c0_HRMcD2H2SU*xDe3tnN?!;Y6V zW0j;xRaLfJvIQd1ctRNOykc(R@Jy(6d($goMGOC={ZPIHD7eE1-{1`UeD5A|e@{HsJRyo`U@nvK_+qV^_2`d!K{eVOeFbNQ%N zU)gGJ+cR!lK>e02dib3Zo}KeWK|^`qi6~{Vj(~MC;Q<&IICJX$fa;LSBPev0q7vKO ziVQjyGwKaUGczdA;j0MF6N>TWPEWYC_oEP*y|-)KfbN31qX zatVn|`+F5hDs&(rjb?7^w}nxC82^&p(bc@ZsK0<%f~RaxBJE*mXO$2=`nrmDdY^11 z_sU75MxtAE;aOay75FN=SB+8p>|bwIf|@mWPBz9f%fYsa1;vC_(&Kq(oCa@Vd>@4<_uB63O3CR}x~ z3hAIp#Z-dTxq+ND;2FA34hvBee*88^;1gOxnohDGq+u9Gh&zV48@r?F{U{q=bFANh zXB+arJDtYNsX+uMlV*?-_lnXHIGS9|l?1ME9h}_7j|y_>s1Dd?rdnRAVd!E9oR%I@ z-{fzaj&89#B)jM+^1@5UvV;={w1FbjHeJEq5{{fEMjl$^hOR9Yl@R1X!C>IcA^F(1aLeB z(lkdY$M~cxj`rmE()z|j)fV&41}*~Kpq1hi>mz~mqINX*awbS$X3=O65_Dus`i&U> zO|etx&Q&^s>m9NAw0$a-&|7K+*^^XyGs*3R;>FZx)!)rdQaSiYmu6q)`DnV>Fl#aR z`^G22fL^+T0Q?*Pqx|9jQPzrk0aU^4eS-3Pi1SdwGq7;!>irH{a(~k6f+-h40zlly zZX|7|3u+pF|AoI8`tzLuNed+3H;Rc>k_Z9BtF1InJH-Ep+~(Q_)`3@#!HxGUMY?Io z#GJ(u^B@p+QGHjWPI9Ha!&XINm&^`@p@PSCl-Yj`>Yn%Ysz-T2L@JyL7if-1XS3Pa zXK5<%^THtv+hb_xr{?vKkvBc>YJrfTaZemX)`>*@b0|@Dk(QbXRtkz@OO?ENMo07b zR}Pan(DsnKAH}Vc4J<2F5W#vvf62~6l#pqh?iYB{QtvZ!y7C;6O$BK-r=Wj3ey&y@ zcL;S<=HaiFyX_H1TPe;mM!*hdh%L$%ZDJ)F;m(Jb?BSlLdK9_T@>Q=_h z%pC4VO1)uwEPH6gL`+V?Or3EQqb(I35nDyb1kP={j9X)0D#-~P;-hS$2IYn?I!yTE zxSJ(WJsjwcEC|6wX?G_p8+U55$@WN-UG(6iJ;TqG$~%`RB}}1bc;J@aNV5D4A?8g} zG9Z4MB!UA<)m1MVrFyK?S!UPw@=S9heibFnHh-6mx1` zrfBdPV^Kk4m2v$ycwnJLfQ_Gs4`M!1v-P4_&B2)!eMhXIqhbbEP3+DPWyTHu`Nl>w zghl#VQ^=VsJjntcXF5GN7NtnYU|JNdLhr@|#duE!$oeb7tQE&hXQ)fWZ^RKSjA|{c z0@b_9XA#r&xm70c@sekjg6<0e>OWBbo%8m)=xNU-q&Auy0g#?QSXNKV%P28%O;b;7 zA4q(zX0|{Ep5>t-V=0;b1Zq|CO*cC8s>*p-_A-xSWTFp*U4!5IYPrnkkb?^Pj(A z3L(AD{UbZB0V)r>E$VZLA`U(YPl}_yEikY*jIw_aht-h--V8Ib;787j20qqYf5X@d zDUS6iJu?lUoj?ADmN`kIdVSv=nk`8-m)J@B{9P*d^iw7#OSgp2z-$sZe4cO;lavqq z2UAwRE$&05etPpf~iROHh&|FcLi&9Q!3XSS{y*n;ZTx%BVQ8F1~W-!yi5Fc^Sr zWvhHk{QeAXKgYG%QTC&G!s6PBr^e$4F1JWBy*$wUxYGhY{qAh1<5dHv#VTctV@oGC zOX~8+2%~qcco&n1u6+0Nj=KhN^2<8O*F4}5 z5L=B`J09zOPONt`iR;9MmYyX*JGNZ^fcT^Zyc3d?-|koXBW=m&j43*zK7X)pa3T#! zQ~|oS470jAaapG!^6=LyWwiCuGiazP^?@2_ku$0yO>?p$u5}hEBMP zWV7b#GYVhaiD^&NMBQyqesAHKI9=AKZ_E8BV0%%v%2pvQ*`t_{DCbIdOUbjZJI7^_chALvk^LD--8{O9|ZKtE!(GLR2Y)saWDf213{Umho(a)fVXaT(c;sBQ`b z6#u{g$&PDza`5D-5OgP0Fw#91)@vKC-h)hUt|XOgIoXN55jY=8=Lm=|bhD4eq2?-I zp*RpdFvx0-Z+lD)ei4kWt^ z#a^m*X^UK3Ah7mty>*Cmx|Vfy(kO2hh=85Lfa?nvvk!QSbbf;IHfm|&TOjZ|m(%3C zZYvE9G5Zkee%=jHwQE^E@ZaBoYs6~N$BU{RF&8m5XwlsCh*z&K;X=d+3nnk3r8Qrl z{UTjH&mwG+ZmXKbIVR_09SN@bG8*a;Cv;CGu-IwZenDF%LJUGNDCHP;zZ)hqx}^J7 zD{}_X_Vb#gni2(!o%Pz@i+lksIE_QI*2ybQtWr(NCsh#au@1S*tB0m)s7fh7^yoWc zP07(0IV^LsxJDi;D~G}jZ%|Op@D0I3Co*vK3H&7h8#eKp;yCWBsS&PHi1->berf;D z;LFj)?e5(E8cgMd~BxWokw$KLET# zR6{Fm-RhU;+9L(@X!Sg+(*H5>Ur8JCIVd`Z011>yU^lpt@^(<<+* zZ&lv^l&cF#>OBy@WI%Tjozj~Fm}r=slDJ{u{h7Z`N_M%gS$wzOboABqD0P=72>nmZ z%~Hk*Bd?hv2*+4#^kB#L%P`z6Yc#}u+b3HTdq)`Du#PkRAU$JQ63FQGns`Z}iqqlr z$5F@Z<3R^Ed4Xcx`(B>})9eE=mLg*a-PCO4-iYffmTXzVYOJ1@7x3i{HyUWsHINKG zUKW!{td>o)*#?&W?cd+OzCk!@Lf*6llw-kbPhQ4)2Z5iC-YBRlekvChfv!2-Mv3g} zWBp}HPpaF?TbUL}n7b5ZY2$|m0bML9TQgf-?pnJz;uxEs{mK`Rg_TkbCzIsX6r6RH^z6C!U;Y=Q}P>zfSGy#f7o5{U_Nx zCY)LR9wCCFNP?%ZJo4khOr?YlDaqUO{`+ed`&(&<#|mQc|*>t+4v=3Itus5GNf11GQL!cHmti$C)u z*7#ilkN52I{&ZKJ|9uBfsN(rZzY0ti|4BCmWsHwiM@Ld}cXTqgmUpnVGj?z?H~ykh zL}LN}^$?Dd;kHMquTKy}g$A{>`SK8kr*i`hkG%7Pmxp9rvBjPoo{zxY^7^hdo}J?W%${&ccU0?N>ta) zZj$tA2yz|v9U7QLT5FpPbuGCOKz;miFcQNwo3x4Y&ljP!f6NxC2VhVXk%x<_lo$9! z_%R(BSJBbg&$)aTT8zIJ)V?VtfjNz9(hx)+jeh^dYAY3u;7j+$c@d{>5yA+^5A0dC zZ`n2JsF(7fs%Gnl=-1Qx`K!Vm&fW4_uhJCjv80|Ga##QD#eKwqZD;GVdqXM~FvV=O zXmhz@>ifmz$PmR5bsVw{ALQ002j7=(@wz$?bMnq1%2>L~AfH3Nd+Fr8FTHwt1tb<~E42W-82ZiijQ}>nd7Vy1hUT|}i^^#&i&<)*+;8Ljw zf*n$At6VqJf}!PiQGB21zosWo*!F}QdI)7T!pwCcBt_&Iv2)0>K1P#8a2Otn3G88X zP_NAC&PLuuAIKw&3dfAx_SVV`(*i3t=GJ)?mF8pE{Z{ZuWQ^pdOlE(U4P5+`Y!%U( z$Mww#sP=|0OC9lZ-o?3wYIPTlW|L5yYTU}>Ew)~ zOGVGXLi&(DX3bumZtxIG256kbP1~6U=Xe)r+pfy0RP69-RQ;ynP1Ul6VlO!o-!ZaG zJ{ZBSdC)-B{gjgObn2`!1mBd8pWkT{HrwT2sbPSn-skqJ(&CXv#CCfK5%(Vc8+5nm zZtR*)&2`b6dur$!n)Gt=Mbuf#PN#Q3@b9O%&)X)E1cJE!esm*A0DmT|Fqj-W{n>>g ztPL0++r#bWw2rovF`ylpY*YRrOa^dZ>Y#|}fiV6n(CNC-E!WXhYV#+vN`LFWpT5OivhuVq z>lj&x|0iSZE`^N$jAsFI{_G3HP$YgIsQ2@YiLq|$z}LD=YqOMKy%BhW>zwW%?8oo{ z;ZLw{bS*02wTl0WR{6AbaW#2dx+LHS)x&Ru_G)b%HQ@_!*J#$WP|!k#C9@Z$HA^fl zmseVxHxi*?&R9zAE$(4dFBGknHwlEzQ)TK;L{WkJNupX{6sQWsGf(y0jKL0gVK;aT zZcDL=^g!oH$2)4}O4g31(3hc1E~eUSX>eqv%1oKWiKt@mzx+YEI;>CA(VH?3L=LfN zO^9>SP*y)tELxzRQ@W&Wt0*D+U3xsh>)9hj=q7v0S|G0?iMKcPyXQ`j0W^mar;z^8 zQcH}mdQ-V%TpABBc~p(Hh_v1Ig${W9G#*X$8ai)fpQrtSY4dk9@%U{=u~L<2%bP*1 zLVB&P9#rdEagJeQ6s>SU8q6rVTl_DzLu{H=5!p;mrQTh@ugkRZ(E~2u3-vegKSY=h z{unm9syasOt`DwL2##(4>XIR=T)Y{n{}9;R3m`@yAGt~b1CORpTa_SwXzdKYx^M8I z81>w9K?I=eFLOZjG50n|z`jASxK<|8qqgaAcTy4D*?aqSXFF-zqUlkhyV4wFZvk!Y`LE0&>7mk-1n1F+Ce>GJ3)a*ai zvb&4GcNxaO@s!KmxwR!#`4r&KZ$CtV%d;CM$Msap&3C*-Q+?tm=iS(6c~$={TF({N z3-71aRk1(6zYP1D4ef^1n~T8za*+>^zmHset@`r+oBFKD?7;JX7pzQe6hU?DQ3UL~ z>7O##F0fx&FVm9j*yu_DTryvooAId}EatFK7cgP3k^^Yq3d-^+s?<0BhgbeU!qBTp z9J#p?;^E%jr01oN>+^-@V+ZijagXr_zIlSt<;BVNcBK0cD#4BgaWx&19E6~6bJFB+ zn9*n&wyY%pEqlwQ)@)>_ftwIv=M^&)9AUKrWhEsU)+6LAO`Va%tp9s?>zgJ!8N|=H z%8iU6in=htAA{MbIt376qx9UFZpTT{V5d_&bhXAA_t^l zxYUYoR9;QjqRI;w9ukZC1P8%`i2#<@E+;7F@n|AY1b;qv5SHO!LI5qvZF>;+x?vm^ z@gNMSLWA4b;m}eS5x7zK#|XdboQ3hY3yP@PCx=U!mOvSBC;|e@W3*2cwa>Dz=;ICT=AB~$rzkyMq0^44P;myD>T*x1DrlBf%v{B`<)yg zlaM+H?NKjS7pMdCQ#xjoRwH$-!Fus6`!m#>dReJ#F=oj1Wi}SjnymqR3SSBn564Ad z6|YP#cGIA2M!>x9fNsWWB@!XXcurcb!eeHC`O;+BLoVTNqIZO^M}nb+*~GbF6Z#(H zZ^91;a>Di_+v5!?a(YfSpgU0>PNmdrGTv}8&%qRo_(f$ z+QKbd&QH2B(zjBR7m>W{!`Ae!>9BVRZK>DOMNDTIn+*EnD_I3?x7Zgo#kn~k?zfLE z?nR0ZO#(jy!!8=4PH!1p&*!H8E2OivFn0FC_TBLkFGnD|*)E21*euUjb$#<;eOYR` zglJ2G%XWnz;z$$e(7`HxMj!XQ%Gf#zeY=TYiq=y= z4t1rO!I%#@eY%`|W|~%ReW?xLHCf%LRYnPPb|TSt87==Scn&5b8+#|IG;Xb^n#Gwzcm$N%jW(pQ$E(p#755J%i`}Z(o%tmtZ_D-T zo{Q%N)VH>-Xl>rNJhQ@Ld&QOSI=zo}23|U3$eqc3KGHz9C&Hpqa2(p| zuE|%vJ5?~PZ#`D{>|CjrZbMki!Pyzk+jXl8j!bhmOz~z_qj dDuazZG^u*-) zCV{{p$D*4m=Z;!QJ0C3EBfaUv#@3CmRgS-#a4+Uk9LtR(mzdSt-?*B-Q{Jgs!F;+_ zT&Jc~{N951OskjVtnWOLnfUacmSEkQ#fQojqa=HN?aN#ubFA){K&TrfoSnBphJ<43S zS5?OUrqPYpEqf;(zc;IIty0d`-n-Ke%PlArJt^DN>ZYW)ZS5z$Rnx=cxbM1#zPRNS1N0u>c z*gpBeTvyKAjdr#Uz%$J3C+p2~l9`-P6`=_1Y83#nAp`Kd5jfb*H#u;=%w#=&g~|Q% z1!N9S7n+#|tj#t98*^$X3S9)i3I*n?N+YghXJ9Z!QN%wvV7|W$;;Lx|1{)Ol%c4L- zlz|txtAK4ozLL`xMPZR7Sm6grRj@+Db&BXF9#sG|gMywm=IO1O01imHv3 zFjX9ri>su;HX}wK85k5$R0A&?fdtA5_Q_vX$jc(elo%M~QB>A-K~#2igOjH$d^80Z zC8#NjzZarXrcVZJJEF73z+ix4=7c`5N>TJt7>~rfl$^E}UAMlP2$P)vS26{cE?ONf_&Vd@-E2?PMU+S~pB diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138..ff23a68d 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.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # 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 +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_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. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java index 6ab2dfd6..56135eab 100644 --- a/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java +++ b/mod/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java @@ -12,7 +12,7 @@ public class ModPlatformUtils extends PlatformUtils { @Override public String minecraftVersion() { - return SharedConstants.getCurrentVersion().getName(); + return SharedConstants.getCurrentVersion().name(); } @Override