├── .gitattributes
├── .gitignore
├── LICENSE.txt
├── README.md
├── build.gradle
├── build.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── fi
│ └── dy
│ └── masa
│ └── servux
│ ├── Reference.java
│ ├── Servux.java
│ ├── dataproviders
│ ├── DataProviderBase.java
│ ├── DataProviderManager.java
│ ├── IDataProvider.java
│ └── StructureDataProvider.java
│ ├── mixin
│ ├── IMixinCustomPayloadC2SPacket.java
│ ├── MixinMinecraftServer.java
│ ├── MixinServerPlayNetworkHandler.java
│ └── MixinThreadedAnvilChunkStorage.java
│ ├── network
│ ├── IPluginChannelHandler.java
│ ├── PacketSplitter.java
│ ├── ServerPacketChannelHandler.java
│ ├── packet
│ │ └── StructureDataPacketHandler.java
│ └── util
│ │ └── PacketUtils.java
│ └── util
│ ├── JsonUtils.java
│ ├── PlayerDimensionPosition.java
│ └── Timeout.java
└── resources
├── fabric.mod.json
└── mixins.servux.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # text stuff
2 | * text=auto
3 | *.bat text eol=crlf
4 | *.groovy text eol=lf
5 | *.java text eol=crlf
6 | *.md text
7 | *.properties text eol=lf
8 | *.scala text eol=lf
9 | *.sh text eol=lf
10 | .gitattributes text eol=lf
11 | .gitignore text eol=lf
12 | build.gradle text eol=lf
13 | gradlew text eol=lf
14 | gradle/wrapper/gradle-wrapper.properties text eol=crlf
15 | COPYING.txt text eol=lf
16 | COPYING.LESSER.txt text eol=lf
17 | README.md text eol=lf
18 |
19 | #binary
20 | *.dat binary
21 | *.bin binary
22 | *.png binary
23 | *.exe binary
24 | *.dll binary
25 | *.zip binary
26 | *.jar binary
27 | *.7z binary
28 | *.db binary
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .settings
3 | bin/
4 | build/
5 | eclipse/
6 | logs/
7 | .classpath
8 | .project
9 | build.number
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Servux
2 | ==============
3 | Servux is a server-side mod that provides extra support/features for some client-side mods when playing on a server.
4 |
5 | **Servux itself is never needed on the clients or in single player**,
6 | it's only needed/useful on the dedicated server side in multiplayer.
7 |
8 | In version 0.1.x it only has one thing, which is sending structure bounding boxes for MiniHUD so that it can render those also in multiplayer.
9 |
10 | For compiled builds (= downloads), see https://www.curseforge.com/minecraft/mc-mods/servux
11 |
12 | Compiling
13 | =========
14 | * Clone the repository
15 | * Open a command prompt/terminal to the repository directory
16 | * run 'gradlew build'
17 | * The built jar file will be in build/libs/
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'fabric-loom' version '0.12-SNAPSHOT'
3 | }
4 |
5 | sourceCompatibility = JavaVersion.VERSION_17
6 | targetCompatibility = JavaVersion.VERSION_17
7 |
8 | ext.configFile = file "build.properties"
9 |
10 | configFile.withReader {
11 | def prop = new Properties()
12 | prop.load(it)
13 | project.ext.config = new ConfigSlurper().parse prop
14 | }
15 |
16 | dependencies {
17 | minecraft "com.mojang:minecraft:${config.minecraft_version}"
18 | mappings "net.fabricmc:yarn:${config.yarn_mappings_version}:v2"
19 | modImplementation "net.fabricmc:fabric-loader:${config.fabric_loader_version}"
20 | implementation "com.google.code.findbugs:jsr305:3.0.2"
21 |
22 | // Fabric API. This is technically optional, but you probably want it anyway.
23 | //modCompile "net.fabricmc.fabric-api:fabric-api:" + config.fabric_version
24 | }
25 |
26 | group = config.group + "." + config.mod_id
27 | archivesBaseName = config.mod_file_name + '-' + config.minecraft_version_out
28 | version = config.mod_version
29 |
30 | if (version.endsWith('-dev')) {
31 | version += "." + new Date().format('yyyyMMdd.HHmmss')
32 | }
33 |
34 | processResources {
35 | // Exclude the GIMP image files
36 | exclude '**/*.xcf'
37 | exclude '**/xcf'
38 |
39 | // this will ensure that this task is redone when the versions change.
40 | //inputs.property "minecraft_version", project.config.minecraft_version
41 |
42 | inputs.property "mod_version", config.mod_version
43 |
44 | filesMatching("fabric.mod.json") {
45 | expand "mod_version": config.mod_version
46 | }
47 | }
48 |
49 | tasks.withType(JavaCompile).configureEach {
50 | // ensure that the encoding is set to UTF-8, no matter what the system default is
51 | // this fixes some edge cases with special characters not displaying correctly
52 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
53 | // If Javadoc is generated, this must be specified in that task too.
54 | it.options.encoding = "UTF-8"
55 |
56 | // Minecraft 1.18 (1.18-pre2) upwards uses Java 17.
57 | it.options.release = 17
58 | }
59 |
--------------------------------------------------------------------------------
/build.properties:
--------------------------------------------------------------------------------
1 | # Thu Jan 14 08:33:00 EET 2016
2 | group = fi.dy.masa
3 | mod_id = servux
4 | mod_name = Servux
5 | author = masa
6 | mod_file_name = servux-fabric
7 |
8 | # Current mod version
9 | mod_version = 0.1.0
10 |
11 | # Minecraft, Forge and MCP mappings versions
12 | minecraft_version_out = 1.19.2
13 | minecraft_version = 1.19.2
14 | yarn_mappings_version = 1.19.1+build.1
15 |
16 | fabric_loader_version = 0.14.9
17 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maruohon/servux/643d053ae9b0b44ad07f78d97442020406c2d369/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 22 17:36:22 EDT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven {
4 | name = 'Fabric'
5 | url = 'https://maven.fabricmc.net/'
6 | }
7 | gradlePluginPortal()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/Reference.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux;
2 |
3 | public class Reference
4 | {
5 | public static final String MOD_ID = "servux";
6 | public static final String MOD_NAME = "ServuX";
7 | public static final String MOD_VERSION = Servux.getModVersionString(MOD_ID);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/Servux.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux;
2 |
3 | import org.apache.logging.log4j.LogManager;
4 | import org.apache.logging.log4j.Logger;
5 | import fi.dy.masa.servux.dataproviders.DataProviderManager;
6 | import fi.dy.masa.servux.dataproviders.StructureDataProvider;
7 | import net.fabricmc.api.ModInitializer;
8 |
9 | public class Servux implements ModInitializer
10 | {
11 | public static final Logger logger = LogManager.getLogger(Reference.MOD_ID);
12 |
13 | @Override
14 | public void onInitialize()
15 | {
16 | DataProviderManager.INSTANCE.registerDataProvider(StructureDataProvider.INSTANCE);
17 | DataProviderManager.INSTANCE.readFromConfig();
18 | }
19 |
20 | public static String getModVersionString(String modId)
21 | {
22 | for (net.fabricmc.loader.api.ModContainer container : net.fabricmc.loader.api.FabricLoader.getInstance().getAllMods())
23 | {
24 | if (container.getMetadata().getId().equals(modId))
25 | {
26 | return container.getMetadata().getVersion().getFriendlyString();
27 | }
28 | }
29 |
30 | return "?";
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/dataproviders/DataProviderBase.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.dataproviders;
2 |
3 | import net.minecraft.util.Identifier;
4 |
5 | public abstract class DataProviderBase implements IDataProvider
6 | {
7 | protected final Identifier networkChannel;
8 | protected final String name;
9 | protected final String description;
10 | protected final int protocolVersion;
11 | protected boolean enabled;
12 | private int tickRate = 40;
13 |
14 | protected DataProviderBase(String name, Identifier channel, int protocolVersion, String description)
15 | {
16 | this.name = name;
17 | this.networkChannel = channel;
18 | this.protocolVersion = protocolVersion;
19 | this.description = description;
20 | }
21 |
22 | @Override
23 | public String getName()
24 | {
25 | return this.name;
26 | }
27 |
28 | @Override
29 | public String getDescription()
30 | {
31 | return this.description;
32 | }
33 |
34 | @Override
35 | public Identifier getNetworkChannel()
36 | {
37 | return this.networkChannel;
38 | }
39 |
40 | @Override
41 | public int getProtocolVersion()
42 | {
43 | return this.protocolVersion;
44 | }
45 |
46 | @Override
47 | public boolean isEnabled()
48 | {
49 | return this.enabled;
50 | }
51 |
52 | @Override
53 | public void setEnabled(boolean enabled)
54 | {
55 | this.enabled = enabled;
56 | }
57 |
58 | protected void setTickRate(int tickRate)
59 | {
60 | this.tickRate = Math.max(tickRate, 1);
61 | }
62 |
63 | @Override
64 | public final int getTickRate()
65 | {
66 | return this.tickRate;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/dataproviders/DataProviderManager.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.dataproviders;
2 |
3 | import java.io.File;
4 | import java.util.ArrayList;
5 | import java.util.HashMap;
6 | import com.google.common.collect.ImmutableList;
7 | import com.google.gson.JsonElement;
8 | import com.google.gson.JsonObject;
9 | import com.google.gson.JsonPrimitive;
10 | import net.minecraft.server.MinecraftServer;
11 | import fi.dy.masa.servux.network.IPluginChannelHandler;
12 | import fi.dy.masa.servux.network.ServerPacketChannelHandler;
13 | import fi.dy.masa.servux.util.JsonUtils;
14 |
15 | public class DataProviderManager
16 | {
17 | public static final DataProviderManager INSTANCE = new DataProviderManager();
18 |
19 | protected final HashMap providers = new HashMap<>();
20 | protected ImmutableList providersImmutable = ImmutableList.of();
21 | protected ArrayList providersTicking = new ArrayList<>();
22 |
23 | public ImmutableList getAllProviders()
24 | {
25 | return this.providersImmutable;
26 | }
27 |
28 | /**
29 | * Registers the given data provider, if it's not already registered
30 | * @param provider
31 | * @return true if the provider did not exist yet and was successfully registered
32 | */
33 | public boolean registerDataProvider(IDataProvider provider)
34 | {
35 | String name = provider.getName();
36 |
37 | if (this.providers.containsKey(name) == false)
38 | {
39 | this.providers.put(name, provider);
40 | this.providersImmutable = ImmutableList.copyOf(this.providers.values());
41 | //System.out.printf("registerDataProvider: %s\n", provider);
42 | return true;
43 | }
44 |
45 | return false;
46 | }
47 |
48 | public boolean setProviderEnabled(String providerName, boolean enabled)
49 | {
50 | IDataProvider provider = this.providers.get(providerName);
51 | return provider != null && this.setProviderEnabled(provider, enabled);
52 | }
53 |
54 | public boolean setProviderEnabled(IDataProvider provider, boolean enabled)
55 | {
56 | boolean wasEnabled = provider.isEnabled();
57 | enabled = true; // FIXME TODO remove debug
58 |
59 | if (enabled || wasEnabled != enabled)
60 | {
61 | //System.out.printf("setProviderEnabled: %s (%s)\n", enabled, provider);
62 | provider.setEnabled(enabled);
63 | this.updatePacketHandlerRegistration(provider);
64 |
65 | if (enabled && provider.shouldTick() && this.providersTicking.contains(provider) == false)
66 | {
67 | this.providersTicking.add(provider);
68 | }
69 | else
70 | {
71 | this.providersTicking.remove(provider);
72 | }
73 |
74 | return true;
75 | }
76 |
77 | return false;
78 | }
79 |
80 | public void tickProviders(MinecraftServer server, int tickCounter)
81 | {
82 | if (this.providersTicking.isEmpty() == false)
83 | {
84 | for (IDataProvider provider : this.providersTicking)
85 | {
86 | if ((tickCounter % provider.getTickRate()) == 0)
87 | {
88 | provider.tick(server, tickCounter);
89 | }
90 | }
91 | }
92 | }
93 |
94 | protected void registerEnabledPacketHandlers()
95 | {
96 | for (IDataProvider provider : this.providersImmutable)
97 | {
98 | this.updatePacketHandlerRegistration(provider);
99 | }
100 | }
101 |
102 | protected void updatePacketHandlerRegistration(IDataProvider provider)
103 | {
104 | IPluginChannelHandler handler = provider.getPacketHandler();
105 |
106 | if (provider.isEnabled())
107 | {
108 | ServerPacketChannelHandler.INSTANCE.registerServerChannelHandler(handler);
109 | }
110 | else
111 | {
112 | ServerPacketChannelHandler.INSTANCE.unregisterServerChannelHandler(handler);
113 | }
114 | }
115 |
116 | public void readFromConfig()
117 | {
118 | JsonElement el = JsonUtils.parseJsonFile(this.getConfigFile());
119 | JsonObject obj = null;
120 |
121 | if (el != null && el.isJsonObject())
122 | {
123 | JsonObject root = el.getAsJsonObject();
124 |
125 | if (JsonUtils.hasObject(root, "DataProviderToggles"))
126 | {
127 | obj = JsonUtils.getNestedObject(root, "DataProviderToggles", false);
128 | }
129 | }
130 |
131 | for (IDataProvider provider : this.providersImmutable)
132 | {
133 | String name = provider.getName();
134 | boolean enabled = obj != null && JsonUtils.getBooleanOrDefault(obj, name, false);
135 | this.setProviderEnabled(provider, enabled);
136 | }
137 | }
138 |
139 | public void writeToConfig()
140 | {
141 | JsonObject root = new JsonObject();
142 | JsonObject objToggles = new JsonObject();
143 |
144 | for (IDataProvider provider : this.providersImmutable)
145 | {
146 | String name = provider.getName();
147 | objToggles.add(name, new JsonPrimitive(provider.isEnabled()));
148 | }
149 |
150 | root.add("DataProviderToggles", objToggles);
151 |
152 | JsonUtils.writeJsonToFile(root, this.getConfigFile());
153 | }
154 |
155 | protected File getConfigFile()
156 | {
157 | return new File("servux.json");
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/dataproviders/IDataProvider.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.dataproviders;
2 |
3 | import net.minecraft.server.MinecraftServer;
4 | import net.minecraft.util.Identifier;
5 | import fi.dy.masa.servux.network.IPluginChannelHandler;
6 |
7 | public interface IDataProvider
8 | {
9 | /**
10 | * Returns the simple name for this data provider.
11 | * This should preferably be a lower case alphanumeric string with no
12 | * other special characters than '-' and '_'.
13 | * This name will be used in the enable/disable commands as the argument
14 | * and also as the config file key/identifier.
15 | * @return
16 | */
17 | String getName();
18 |
19 | /**
20 | * Returns the description of this data provider.
21 | * Used in the command to list the available providers and to check the status
22 | * of a given provider.
23 | * @return
24 | */
25 | String getDescription();
26 |
27 | /**
28 | * Returns the network channel name used by this data provider to listen
29 | * for incoming data requests and to respond and send the requested data.
30 | * @return
31 | */
32 | Identifier getNetworkChannel();
33 |
34 | /**
35 | * Returns the current protocol version this provider supports
36 | * @return
37 | */
38 | int getProtocolVersion();
39 |
40 | /**
41 | * Returns true if this data provider is currently enabled.
42 | * @return
43 | */
44 | boolean isEnabled();
45 |
46 | /**
47 | * Enables or disables this data provider
48 | * @param enabled
49 | */
50 | void setEnabled(boolean enabled);
51 |
52 | /**
53 | * Returns whether or not this data provider should get ticked to periodically send some data,
54 | * or if it's only listening for incoming requests and responds to them directly.
55 | * @return
56 | */
57 | default boolean shouldTick()
58 | {
59 | return false;
60 | }
61 |
62 | /**
63 | * Returns the interval in game ticks that this data provider should be ticked at
64 | * @return
65 | */
66 | int getTickRate();
67 |
68 | /**
69 | * Called at the given tick rate
70 | * @param server
71 | * @param tickCounter The current server tick (since last server start)
72 | */
73 | default void tick(MinecraftServer server, int tickCounter)
74 | {
75 | }
76 |
77 | /**
78 | * Returns the network packet handler used for this data provider.
79 | * @return
80 | */
81 | IPluginChannelHandler getPacketHandler();
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/dataproviders/StructureDataProvider.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.dataproviders;
2 |
3 | import java.util.HashMap;
4 | import java.util.HashSet;
5 | import java.util.Iterator;
6 | import java.util.Map;
7 | import java.util.Set;
8 | import java.util.UUID;
9 | import it.unimi.dsi.fastutil.longs.LongIterator;
10 | import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
11 | import it.unimi.dsi.fastutil.longs.LongSet;
12 | import net.minecraft.nbt.NbtCompound;
13 | import net.minecraft.nbt.NbtList;
14 | import net.minecraft.server.MinecraftServer;
15 | import net.minecraft.server.network.ServerPlayerEntity;
16 | import net.minecraft.server.world.ServerWorld;
17 | import net.minecraft.structure.StructureContext;
18 | import net.minecraft.structure.StructureStart;
19 | import net.minecraft.util.math.ChunkPos;
20 | import net.minecraft.world.World;
21 | import net.minecraft.world.chunk.Chunk;
22 | import net.minecraft.world.chunk.ChunkStatus;
23 | import net.minecraft.world.chunk.WorldChunk;
24 | import net.minecraft.world.gen.structure.Structure;
25 | import fi.dy.masa.servux.network.IPluginChannelHandler;
26 | import fi.dy.masa.servux.network.PacketSplitter;
27 | import fi.dy.masa.servux.network.packet.StructureDataPacketHandler;
28 | import fi.dy.masa.servux.util.PlayerDimensionPosition;
29 | import fi.dy.masa.servux.util.Timeout;
30 |
31 | public class StructureDataProvider extends DataProviderBase
32 | {
33 | public static final StructureDataProvider INSTANCE = new StructureDataProvider();
34 |
35 | protected final Map registeredPlayers = new HashMap<>();
36 | protected final Map> timeouts = new HashMap<>();
37 | protected final NbtCompound metadata = new NbtCompound();
38 | protected int timeout = 30 * 20;
39 | protected int updateInterval = 40;
40 | protected int retainDistance;
41 |
42 | protected StructureDataProvider()
43 | {
44 | super("structure_bounding_boxes",
45 | StructureDataPacketHandler.CHANNEL, StructureDataPacketHandler.PROTOCOL_VERSION,
46 | "Structure Bounding Boxes data for structures such as Witch Huts, Ocean Monuments, Nether Fortresses etc.");
47 |
48 | this.metadata.putString("id", StructureDataPacketHandler.CHANNEL.toString());
49 | this.metadata.putInt("timeout", this.timeout);
50 | this.metadata.putInt("version", StructureDataPacketHandler.PROTOCOL_VERSION);
51 | }
52 |
53 | @Override
54 | public boolean shouldTick()
55 | {
56 | return true;
57 | }
58 |
59 | @Override
60 | public IPluginChannelHandler getPacketHandler()
61 | {
62 | return StructureDataPacketHandler.INSTANCE;
63 | }
64 |
65 | @Override
66 | public void tick(MinecraftServer server, int tickCounter)
67 | {
68 | if ((tickCounter % this.updateInterval) == 0)
69 | {
70 | if (this.registeredPlayers.isEmpty() == false)
71 | {
72 | // System.out.printf("=======================\n");
73 | // System.out.printf("tick: %d - %s\n", tickCounter, this.isEnabled());
74 | this.retainDistance = server.getPlayerManager().getViewDistance() + 2;
75 | Iterator uuidIter = this.registeredPlayers.keySet().iterator();
76 |
77 | while (uuidIter.hasNext())
78 | {
79 | UUID uuid = uuidIter.next();
80 | ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
81 |
82 | if (player != null)
83 | {
84 | this.checkForDimensionChange(player);
85 | this.refreshTrackedChunks(player, tickCounter);
86 | }
87 | else
88 | {
89 | this.timeouts.remove(uuid);
90 | uuidIter.remove();
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
97 | public void onStartedWatchingChunk(ServerPlayerEntity player, WorldChunk chunk)
98 | {
99 | UUID uuid = player.getUuid();
100 |
101 | if (this.registeredPlayers.containsKey(uuid))
102 | {
103 | this.addChunkTimeoutIfHasReferences(uuid, chunk, player.getServer().getTicks());
104 | }
105 | }
106 |
107 | public boolean register(ServerPlayerEntity player)
108 | {
109 | // System.out.printf("register\n");
110 | boolean registered = false;
111 | UUID uuid = player.getUuid();
112 |
113 | if (this.registeredPlayers.containsKey(uuid) == false)
114 | {
115 | PacketSplitter.sendPacketTypeAndCompound(StructureDataPacketHandler.CHANNEL, StructureDataPacketHandler.PACKET_S2C_METADATA, this.metadata, player);
116 |
117 | this.registeredPlayers.put(uuid, new PlayerDimensionPosition(player));
118 | int tickCounter = player.getServer().getTicks();
119 | this.initialSyncStructuresToPlayerWithinRange(player, player.getServer().getPlayerManager().getViewDistance(), tickCounter);
120 |
121 | registered = true;
122 | }
123 |
124 | return registered;
125 | }
126 |
127 | public boolean unregister(ServerPlayerEntity player)
128 | {
129 | // System.out.printf("unregister\n");
130 | return this.registeredPlayers.remove(player.getUuid()) != null;
131 | }
132 |
133 | protected void initialSyncStructuresToPlayerWithinRange(ServerPlayerEntity player, int chunkRadius, int tickCounter)
134 | {
135 | UUID uuid = player.getUuid();
136 | ChunkPos center = player.getWatchedSection().toChunkPos();
137 | Map references =
138 | this.getStructureReferencesWithinRange(player.getWorld(), center, chunkRadius);
139 |
140 | this.timeouts.remove(uuid);
141 | this.registeredPlayers.computeIfAbsent(uuid, (u) -> new PlayerDimensionPosition(player)).setPosition(player);
142 |
143 | // System.out.printf("initialSyncStructuresToPlayerWithinRange: references: %d\n", references.size());
144 | this.sendStructures(player, references, tickCounter);
145 | }
146 |
147 | protected void addChunkTimeoutIfHasReferences(final UUID uuid, WorldChunk chunk, final int tickCounter)
148 | {
149 | final ChunkPos pos = chunk.getPos();
150 |
151 | if (this.chunkHasStructureReferences(pos.x, pos.z, chunk.getWorld()))
152 | {
153 | final Map map = this.timeouts.computeIfAbsent(uuid, (u) -> new HashMap<>());
154 | final int timeout = this.timeout;
155 |
156 | //System.out.printf("addChunkTimeoutIfHasReferences: %s\n", pos);
157 | // Set the timeout so it's already expired and will cause the chunk to be sent on the next update tick
158 | map.computeIfAbsent(pos, (p) -> new Timeout(tickCounter - timeout));
159 | }
160 | }
161 |
162 | protected void checkForDimensionChange(ServerPlayerEntity player)
163 | {
164 | UUID uuid = player.getUuid();
165 | PlayerDimensionPosition playerPos = this.registeredPlayers.get(uuid);
166 |
167 | if (playerPos == null || playerPos.dimensionChanged(player))
168 | {
169 | this.timeouts.remove(uuid);
170 | this.registeredPlayers.computeIfAbsent(uuid, (u) -> new PlayerDimensionPosition(player)).setPosition(player);
171 | }
172 | }
173 |
174 | protected void addOrRefreshTimeouts(final UUID uuid,
175 | final Map references,
176 | final int tickCounter)
177 | {
178 | // System.out.printf("addOrRefreshTimeouts: references: %d\n", references.size());
179 | Map map = this.timeouts.computeIfAbsent(uuid, (u) -> new HashMap<>());
180 |
181 | for (LongSet chunks : references.values())
182 | {
183 | for (Long chunkPosLong : chunks)
184 | {
185 | final ChunkPos pos = new ChunkPos(chunkPosLong);
186 | map.computeIfAbsent(pos, (p) -> new Timeout(tickCounter)).setLastSync(tickCounter);
187 | }
188 | }
189 | }
190 |
191 | protected void refreshTrackedChunks(ServerPlayerEntity player, int tickCounter)
192 | {
193 | UUID uuid = player.getUuid();
194 | Map map = this.timeouts.get(uuid);
195 |
196 | if (map != null)
197 | {
198 | // System.out.printf("refreshTrackedChunks: timeouts: %d\n", map.size());
199 | this.sendAndRefreshExpiredStructures(player, map, tickCounter);
200 | }
201 | }
202 |
203 | protected boolean isOutOfRange(ChunkPos pos, ChunkPos center)
204 | {
205 | int chunkRadius = this.retainDistance;
206 |
207 | return Math.abs(pos.x - center.x) > chunkRadius ||
208 | Math.abs(pos.z - center.z) > chunkRadius;
209 | }
210 |
211 | protected void sendAndRefreshExpiredStructures(ServerPlayerEntity player, Map map, int tickCounter)
212 | {
213 | Set positionsToUpdate = new HashSet<>();
214 |
215 | for (Map.Entry entry : map.entrySet())
216 | {
217 | Timeout timeout = entry.getValue();
218 |
219 | if (timeout.needsUpdate(tickCounter, this.timeout))
220 | {
221 | positionsToUpdate.add(entry.getKey());
222 | }
223 | }
224 |
225 | if (positionsToUpdate.isEmpty() == false)
226 | {
227 | ServerWorld world = player.getWorld();
228 | ChunkPos center = player.getWatchedSection().toChunkPos();
229 | Map references = new HashMap<>();
230 |
231 | for (ChunkPos pos : positionsToUpdate)
232 | {
233 | if (this.isOutOfRange(pos, center))
234 | {
235 | map.remove(pos);
236 | }
237 | else
238 | {
239 | this.getStructureReferencesFromChunk(pos.x, pos.z, world, references);
240 |
241 | Timeout timeout = map.get(pos);
242 |
243 | if (timeout != null)
244 | {
245 | timeout.setLastSync(tickCounter);
246 | }
247 | }
248 | }
249 |
250 | // System.out.printf("sendAndRefreshExpiredStructures: positionsToUpdate: %d -> references: %d, to: %d\n", positionsToUpdate.size(), references.size(), this.timeout);
251 |
252 | if (references.isEmpty() == false)
253 | {
254 | this.sendStructures(player, references, tickCounter);
255 | }
256 | }
257 | }
258 |
259 | protected void getStructureReferencesFromChunk(int chunkX, int chunkZ, World world, Map references)
260 | {
261 | if (world.isChunkLoaded(chunkX, chunkZ) == false)
262 | {
263 | return;
264 | }
265 |
266 | Chunk chunk = world.getChunk(chunkX, chunkZ, ChunkStatus.STRUCTURE_STARTS, false);
267 |
268 | if (chunk == null)
269 | {
270 | return;
271 | }
272 |
273 | for (Map.Entry entry : chunk.getStructureReferences().entrySet())
274 | {
275 | Structure feature = entry.getKey();
276 | LongSet startChunks = entry.getValue();
277 |
278 | // TODO add an option && feature != StructureFeature.MINESHAFT
279 | if (startChunks.isEmpty() == false)
280 | {
281 | references.merge(feature, startChunks, (oldSet, entrySet) -> {
282 | LongOpenHashSet newSet = new LongOpenHashSet(oldSet);
283 | newSet.addAll(entrySet);
284 | return newSet;
285 | });
286 | }
287 | }
288 | }
289 |
290 | protected boolean chunkHasStructureReferences(int chunkX, int chunkZ, World world)
291 | {
292 | if (world.isChunkLoaded(chunkX, chunkZ) == false)
293 | {
294 | return false;
295 | }
296 |
297 | Chunk chunk = world.getChunk(chunkX, chunkZ, ChunkStatus.STRUCTURE_STARTS, false);
298 |
299 | if (chunk == null)
300 | {
301 | return false;
302 | }
303 |
304 | for (Map.Entry entry : chunk.getStructureReferences().entrySet())
305 | {
306 | // TODO add an option entry.getKey() != StructureFeature.MINESHAFT &&
307 | if (entry.getValue().isEmpty() == false)
308 | {
309 | return true;
310 | }
311 | }
312 |
313 | return false;
314 | }
315 |
316 | protected Map
317 | getStructureStartsFromReferences(ServerWorld world, Map references)
318 | {
319 | Map starts = new HashMap<>();
320 |
321 | for (Map.Entry entry : references.entrySet())
322 | {
323 | Structure structure = entry.getKey();
324 | LongSet startChunks = entry.getValue();
325 | LongIterator iter = startChunks.iterator();
326 |
327 | while (iter.hasNext())
328 | {
329 | ChunkPos pos = new ChunkPos(iter.nextLong());
330 |
331 | if (world.isChunkLoaded(pos.x, pos.z) == false)
332 | {
333 | continue;
334 | }
335 |
336 | Chunk chunk = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS, false);
337 |
338 | if (chunk == null)
339 | {
340 | continue;
341 | }
342 |
343 | StructureStart start = chunk.getStructureStart(structure);
344 |
345 | if (start != null)
346 | {
347 | starts.put(pos, start);
348 | }
349 | }
350 | }
351 |
352 | // System.out.printf("getStructureStartsFromReferences: references: %d -> starts: %d\n", references.size(), starts.size());
353 | return starts;
354 | }
355 |
356 | protected Map
357 | getStructureReferencesWithinRange(ServerWorld world, ChunkPos center, int chunkRadius)
358 | {
359 | Map references = new HashMap<>();
360 |
361 | for (int cx = center.x - chunkRadius; cx <= center.x + chunkRadius; ++cx)
362 | {
363 | for (int cz = center.z - chunkRadius; cz <= center.z + chunkRadius; ++cz)
364 | {
365 | this.getStructureReferencesFromChunk(cx, cz, world, references);
366 | }
367 | }
368 |
369 | // System.out.printf("getStructureReferencesWithinRange: references: %d\n", references.size());
370 | return references;
371 | }
372 |
373 | protected void sendStructures(ServerPlayerEntity player,
374 | Map references,
375 | int tickCounter)
376 | {
377 | ServerWorld world = player.getWorld();
378 | Map starts = this.getStructureStartsFromReferences(world, references);
379 |
380 | if (starts.isEmpty() == false)
381 | {
382 | this.addOrRefreshTimeouts(player.getUuid(), references, tickCounter);
383 |
384 | NbtList structureList = this.getStructureList(starts, world);
385 | // System.out.printf("sendStructures: starts: %d -> structureList: %d. refs: %s\n", starts.size(), structureList.size(), references.keySet());
386 |
387 | NbtCompound tag = new NbtCompound();
388 | tag.put("Structures", structureList);
389 |
390 | PacketSplitter.sendPacketTypeAndCompound(StructureDataPacketHandler.CHANNEL, StructureDataPacketHandler.PACKET_S2C_STRUCTURE_DATA, tag, player);
391 | }
392 | }
393 |
394 | protected NbtList getStructureList(Map structures, ServerWorld world)
395 | {
396 | NbtList list = new NbtList();
397 | StructureContext ctx = StructureContext.from(world);
398 |
399 | for (Map.Entry entry : structures.entrySet())
400 | {
401 | ChunkPos pos = entry.getKey();
402 | list.add(entry.getValue().toNbt(ctx, pos));
403 | }
404 |
405 | return list;
406 | }
407 | }
408 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/mixin/IMixinCustomPayloadC2SPacket.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.mixin;
2 |
3 | import org.spongepowered.asm.mixin.Mixin;
4 | import org.spongepowered.asm.mixin.gen.Accessor;
5 | import net.minecraft.network.PacketByteBuf;
6 | import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
7 | import net.minecraft.util.Identifier;
8 |
9 | @Mixin(CustomPayloadC2SPacket.class)
10 | public interface IMixinCustomPayloadC2SPacket
11 | {
12 | @Accessor("channel")
13 | Identifier servux_getChannel();
14 |
15 | @Accessor("data")
16 | PacketByteBuf servux_getData();
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/mixin/MixinMinecraftServer.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.mixin;
2 |
3 | import java.util.function.BooleanSupplier;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.Shadow;
6 | import org.spongepowered.asm.mixin.injection.At;
7 | import org.spongepowered.asm.mixin.injection.Inject;
8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
9 | import net.minecraft.server.MinecraftServer;
10 | import net.minecraft.util.profiler.Profiler;
11 | import fi.dy.masa.servux.dataproviders.DataProviderManager;
12 |
13 | @Mixin(MinecraftServer.class)
14 | public abstract class MixinMinecraftServer
15 | {
16 | @Shadow private Profiler profiler;
17 | @Shadow private int ticks;
18 |
19 | @Inject(method = "tick", at = @At("RETURN"))
20 | private void servux_onTickEnd(BooleanSupplier supplier, CallbackInfo ci)
21 | {
22 | this.profiler.push("servux_tick");
23 | DataProviderManager.INSTANCE.tickProviders((MinecraftServer) (Object) this, this.ticks);
24 | this.profiler.pop();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/mixin/MixinServerPlayNetworkHandler.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.mixin;
2 |
3 | import org.spongepowered.asm.mixin.Mixin;
4 | import org.spongepowered.asm.mixin.injection.At;
5 | import org.spongepowered.asm.mixin.injection.Inject;
6 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
7 | import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
8 | import net.minecraft.server.network.ServerPlayNetworkHandler;
9 | import fi.dy.masa.servux.network.ServerPacketChannelHandler;
10 |
11 | @Mixin(value = ServerPlayNetworkHandler.class, priority = 998)
12 | public abstract class MixinServerPlayNetworkHandler
13 | {
14 | @Inject(method = "onCustomPayload", at = @At("HEAD"))
15 | private void servux_handleCustomPayload(CustomPayloadC2SPacket packet, CallbackInfo ci)
16 | {
17 | ServerPacketChannelHandler.INSTANCE.processPacketFromClient(packet, (ServerPlayNetworkHandler) (Object) this);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/mixin/MixinThreadedAnvilChunkStorage.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.mixin;
2 |
3 | import org.apache.commons.lang3.mutable.MutableObject;
4 | import org.spongepowered.asm.mixin.Mixin;
5 | import org.spongepowered.asm.mixin.injection.At;
6 | import org.spongepowered.asm.mixin.injection.Inject;
7 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
8 | import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket;
9 | import net.minecraft.server.network.ServerPlayerEntity;
10 | import net.minecraft.server.world.ThreadedAnvilChunkStorage;
11 | import net.minecraft.world.chunk.WorldChunk;
12 | import fi.dy.masa.servux.dataproviders.StructureDataProvider;
13 |
14 | @Mixin(ThreadedAnvilChunkStorage.class)
15 | public abstract class MixinThreadedAnvilChunkStorage
16 | {
17 | @Inject(method = "sendChunkDataPackets", at = @At("HEAD"))
18 | private void servux_onSendChunkPacket(ServerPlayerEntity player,
19 | MutableObject cachedDataPacket,
20 | WorldChunk chunk,
21 | CallbackInfo ci)
22 | {
23 | if (StructureDataProvider.INSTANCE.isEnabled())
24 | {
25 | StructureDataProvider.INSTANCE.onStartedWatchingChunk(player, chunk);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/network/IPluginChannelHandler.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.network;
2 |
3 | import net.minecraft.network.PacketByteBuf;
4 | import net.minecraft.server.network.ServerPlayNetworkHandler;
5 | import net.minecraft.util.Identifier;
6 |
7 | public interface IPluginChannelHandler
8 | {
9 | Identifier getChannel();
10 |
11 | default void onPacketReceived(PacketByteBuf buf, ServerPlayNetworkHandler netHandler)
12 | {
13 | }
14 |
15 | default boolean isSubscribable()
16 | {
17 | return false;
18 | }
19 |
20 | default boolean subscribe(ServerPlayNetworkHandler netHandler)
21 | {
22 | return false;
23 | }
24 |
25 | default boolean unsubscribe(ServerPlayNetworkHandler netHandler)
26 | {
27 | return false;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/network/PacketSplitter.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.network;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.function.Consumer;
6 | import javax.annotation.Nullable;
7 | import io.netty.buffer.Unpooled;
8 | import org.apache.commons.lang3.tuple.Pair;
9 | import net.minecraft.nbt.NbtCompound;
10 | import net.minecraft.network.PacketByteBuf;
11 | import net.minecraft.network.listener.PacketListener;
12 | import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
13 | import net.minecraft.server.network.ServerPlayNetworkHandler;
14 | import net.minecraft.server.network.ServerPlayerEntity;
15 | import net.minecraft.util.Identifier;
16 | import fi.dy.masa.servux.network.util.PacketUtils;
17 |
18 | /**
19 | * Network packet splitter code from QuickCarpet by skyrising
20 | * @author skyrising
21 | */
22 | public class PacketSplitter
23 | {
24 | public static final int MAX_TOTAL_PER_PACKET_S2C = 1048576;
25 | public static final int MAX_PAYLOAD_PER_PACKET_S2C = MAX_TOTAL_PER_PACKET_S2C - 5;
26 | public static final int DEFAULT_MAX_RECEIVE_SIZE_C2S = 1048576;
27 |
28 | private static final Map, ReadingSession> READING_SESSIONS = new HashMap<>();
29 |
30 | public static void send(PacketByteBuf packet, Identifier channel, ServerPlayNetworkHandler networkHandler)
31 | {
32 | send(packet, MAX_PAYLOAD_PER_PACKET_S2C, buf -> networkHandler.sendPacket(new CustomPayloadS2CPacket(channel, buf)));
33 | }
34 |
35 | private static void send(PacketByteBuf packet, int payloadLimit, Consumer sender)
36 | {
37 | int len = packet.writerIndex();
38 |
39 | packet.readerIndex(0);
40 |
41 | for (int offset = 0; offset < len; offset += payloadLimit)
42 | {
43 | int thisLen = Math.min(len - offset, payloadLimit);
44 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer(thisLen));
45 |
46 | if (offset == 0)
47 | {
48 | buf.writeVarInt(len);
49 | }
50 |
51 | buf.writeBytes(packet, thisLen);
52 | sender.accept(buf);
53 | }
54 |
55 | packet.release();
56 | }
57 |
58 | @Nullable
59 | public static PacketByteBuf receive(Identifier channel, PacketByteBuf data, ServerPlayNetworkHandler networkHandler)
60 | {
61 | return receive(channel, data, DEFAULT_MAX_RECEIVE_SIZE_C2S, networkHandler);
62 | }
63 |
64 | @Nullable
65 | private static PacketByteBuf receive(Identifier channel, PacketByteBuf data, int maxLength, ServerPlayNetworkHandler networkHandler)
66 | {
67 | Pair key = Pair.of(networkHandler, channel);
68 | return READING_SESSIONS.computeIfAbsent(key, ReadingSession::new).receive(data, maxLength);
69 | }
70 |
71 | /**
72 | * Sends a packet type ID as a VarInt, and then the given Compound tag.
73 | */
74 | public static void sendPacketTypeAndCompound(Identifier channel, int packetType, NbtCompound data, ServerPlayerEntity player)
75 | {
76 | sendPacketTypeAndCompound(channel, packetType, data, player.networkHandler);
77 | }
78 |
79 | /**
80 | * Sends a packet type ID as a VarInt, and then the given Compound tag.
81 | */
82 | public static void sendPacketTypeAndCompound(Identifier channel, int packetType, NbtCompound data, ServerPlayNetworkHandler networkHandler)
83 | {
84 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
85 | buf.writeVarInt(packetType);
86 | buf.writeNbt(data);
87 |
88 | send(buf, channel, networkHandler);
89 | }
90 |
91 | private static class ReadingSession
92 | {
93 | private final Pair key;
94 | private int expectedSize = -1;
95 | private PacketByteBuf received;
96 |
97 | private ReadingSession(Pair key)
98 | {
99 | this.key = key;
100 | }
101 |
102 | @Nullable
103 | private PacketByteBuf receive(PacketByteBuf data, int maxLength)
104 | {
105 | data.readerIndex(0);
106 | data = PacketUtils.slice(data);
107 |
108 | if (this.expectedSize < 0)
109 | {
110 | this.expectedSize = data.readVarInt();
111 |
112 | if (this.expectedSize > maxLength)
113 | {
114 | throw new IllegalArgumentException("Payload too large");
115 | }
116 |
117 | this.received = new PacketByteBuf(Unpooled.buffer(this.expectedSize));
118 | }
119 |
120 | this.received.writeBytes(data.readBytes(data.readableBytes()));
121 |
122 | if (this.received.writerIndex() >= this.expectedSize)
123 | {
124 | READING_SESSIONS.remove(this.key);
125 | return this.received;
126 | }
127 |
128 | return null;
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/network/ServerPacketChannelHandler.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.network;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.function.BiConsumer;
7 | import com.google.common.base.Charsets;
8 | import net.minecraft.network.PacketByteBuf;
9 | import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
10 | import net.minecraft.server.network.ServerPlayNetworkHandler;
11 | import net.minecraft.util.Identifier;
12 | import fi.dy.masa.servux.mixin.IMixinCustomPayloadC2SPacket;
13 | import fi.dy.masa.servux.network.util.PacketUtils;
14 |
15 | public class ServerPacketChannelHandler
16 | {
17 | public static final Identifier REGISTER = new Identifier("minecraft:register");
18 | public static final Identifier UNREGISTER = new Identifier("minecraft:unregister");
19 |
20 | public static final ServerPacketChannelHandler INSTANCE = new ServerPacketChannelHandler();
21 |
22 | private final HashMap handlers = new HashMap<>();
23 | private final HashMap subscribableHandlers = new HashMap<>();
24 |
25 | private ServerPacketChannelHandler()
26 | {
27 | }
28 |
29 | public void registerServerChannelHandler(IPluginChannelHandler handler)
30 | {
31 | synchronized (this.handlers)
32 | {
33 | List toRegister = new ArrayList<>();
34 | Identifier channel = handler.getChannel();
35 |
36 | if (this.handlers.containsKey(channel) == false)
37 | {
38 | this.handlers.put(channel, handler);
39 | toRegister.add(channel);
40 |
41 | if (handler.isSubscribable())
42 | {
43 | this.subscribableHandlers.put(channel, handler);
44 | }
45 | }
46 | }
47 | }
48 |
49 | public void unregisterServerChannelHandler(IPluginChannelHandler handler)
50 | {
51 | synchronized (this.handlers)
52 | {
53 | List toUnRegister = new ArrayList<>();
54 | Identifier channel = handler.getChannel();
55 |
56 | if (this.handlers.remove(channel, handler))
57 | {
58 | toUnRegister.add(channel);
59 | this.subscribableHandlers.remove(channel);
60 | }
61 | }
62 | }
63 |
64 | public void processPacketFromClient(CustomPayloadC2SPacket packet, ServerPlayNetworkHandler netHandler)
65 | {
66 | IMixinCustomPayloadC2SPacket accessor = ((IMixinCustomPayloadC2SPacket) packet);
67 | Identifier channel = accessor.servux_getChannel();
68 | PacketByteBuf data = accessor.servux_getData();
69 |
70 | IPluginChannelHandler handler;
71 |
72 | synchronized (this.handlers)
73 | {
74 | handler = this.handlers.get(channel);
75 | }
76 |
77 | if (handler != null)
78 | {
79 | final PacketByteBuf slice = PacketUtils.retainedSlice(data);
80 | this.schedule(() -> this.handleReceivedData(channel, slice, handler, netHandler), netHandler);
81 | }
82 | else if (channel.equals(REGISTER))
83 | {
84 | final List channels = getChannels(data);
85 | this.schedule(() -> this.updateRegistrationForChannels(channels, IPluginChannelHandler::subscribe, netHandler), netHandler);
86 | }
87 | else if (channel.equals(UNREGISTER))
88 | {
89 | final List channels = getChannels(data);
90 | this.schedule(() -> this.updateRegistrationForChannels(channels, IPluginChannelHandler::unsubscribe, netHandler), netHandler);
91 | }
92 | }
93 |
94 | private void updateRegistrationForChannels(List channels,
95 | BiConsumer action,
96 | ServerPlayNetworkHandler netHandler)
97 | {
98 | for (Identifier channel : channels)
99 | {
100 | IPluginChannelHandler handler = this.subscribableHandlers.get(channel);
101 |
102 | if (handler != null)
103 | {
104 | action.accept(handler, netHandler);
105 | }
106 | }
107 | }
108 |
109 | private void handleReceivedData(Identifier channel, PacketByteBuf data,
110 | IPluginChannelHandler handler, ServerPlayNetworkHandler netHandler)
111 | {
112 | PacketByteBuf buf = PacketSplitter.receive(channel, data, netHandler);
113 | data.release();
114 |
115 | // Finished the complete packet
116 | if (buf != null)
117 | {
118 | handler.onPacketReceived(buf, netHandler);
119 | buf.release();
120 | }
121 | }
122 |
123 | private void schedule(Runnable task, ServerPlayNetworkHandler netHandler)
124 | {
125 | netHandler.getPlayer().getServer().execute(task);
126 | }
127 |
128 | private static List getChannels(PacketByteBuf buf)
129 | {
130 | buf.readerIndex(0);
131 | buf = PacketUtils.slice(buf);
132 |
133 | byte[] bytes = new byte[buf.readableBytes()];
134 | buf.readBytes(bytes);
135 | String channelString = new String(bytes, Charsets.UTF_8);
136 | List channels = new ArrayList<>();
137 |
138 | for (String channel : channelString.split("\0"))
139 | {
140 | try
141 | {
142 | Identifier id = new Identifier(channel);
143 | channels.add(id);
144 | }
145 | catch (Exception ignore) {}
146 | }
147 |
148 | return channels;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/network/packet/StructureDataPacketHandler.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.network.packet;
2 |
3 | import net.minecraft.server.network.ServerPlayNetworkHandler;
4 | import net.minecraft.util.Identifier;
5 | import fi.dy.masa.servux.dataproviders.StructureDataProvider;
6 | import fi.dy.masa.servux.network.IPluginChannelHandler;
7 |
8 | public class StructureDataPacketHandler implements IPluginChannelHandler
9 | {
10 | public static final Identifier CHANNEL = new Identifier("servux:structures");
11 | public static final StructureDataPacketHandler INSTANCE = new StructureDataPacketHandler();
12 |
13 | public static final int PROTOCOL_VERSION = 1;
14 | public static final int PACKET_S2C_METADATA = 1;
15 | public static final int PACKET_S2C_STRUCTURE_DATA = 2;
16 |
17 | @Override
18 | public Identifier getChannel()
19 | {
20 | return CHANNEL;
21 | }
22 |
23 | @Override
24 | public boolean isSubscribable()
25 | {
26 | return true;
27 | }
28 |
29 | @Override
30 | public boolean subscribe(ServerPlayNetworkHandler netHandler)
31 | {
32 | return StructureDataProvider.INSTANCE.register(netHandler.getPlayer());
33 | }
34 |
35 | @Override
36 | public boolean unsubscribe(ServerPlayNetworkHandler netHandler)
37 | {
38 | return StructureDataProvider.INSTANCE.unregister(netHandler.getPlayer());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/network/util/PacketUtils.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.network.util;
2 |
3 | import java.util.Objects;
4 | import net.minecraft.network.PacketByteBuf;
5 | import io.netty.buffer.ByteBuf;
6 |
7 | public class PacketUtils
8 | {
9 | /**
10 | * Wraps the newly created buf from {@code buf.slice()} in a PacketByteBuf.
11 | *
12 | * @param buf the original ByteBuf
13 | * @return a slice of the buffer
14 | * @see ByteBuf#slice()
15 | */
16 | public static PacketByteBuf slice(ByteBuf buf)
17 | {
18 | Objects.requireNonNull(buf, "ByteBuf cannot be null");
19 | return new PacketByteBuf(buf.slice());
20 | }
21 |
22 | /**
23 | * Wraps the newly created buf from {@code buf.retainedSlice()} in a PacketByteBuf.
24 | *
25 | * @param buf the original ByteBuf
26 | * @return a slice of the buffer
27 | * @see ByteBuf#retainedSlice()
28 | */
29 | public static PacketByteBuf retainedSlice(ByteBuf buf)
30 | {
31 | Objects.requireNonNull(buf, "ByteBuf cannot be null");
32 | return new PacketByteBuf(buf.retainedSlice());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/util/JsonUtils.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.util;
2 |
3 | import java.io.File;
4 | import java.io.FileReader;
5 | import java.io.FileWriter;
6 | import java.io.IOException;
7 | import java.util.Map;
8 | import javax.annotation.Nonnull;
9 | import javax.annotation.Nullable;
10 | import com.google.gson.Gson;
11 | import com.google.gson.GsonBuilder;
12 | import com.google.gson.JsonArray;
13 | import com.google.gson.JsonElement;
14 | import com.google.gson.JsonObject;
15 | import com.google.gson.JsonParser;
16 | import net.minecraft.util.math.BlockPos;
17 | import net.minecraft.util.math.Vec3d;
18 | import net.minecraft.util.math.Vec3i;
19 | import fi.dy.masa.servux.Servux;
20 |
21 | public class JsonUtils
22 | {
23 | public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
24 |
25 | @Nullable
26 | public static JsonObject getNestedObject(JsonObject parent, String key, boolean create)
27 | {
28 | if (parent.has(key) == false || parent.get(key).isJsonObject() == false)
29 | {
30 | if (create == false)
31 | {
32 | return null;
33 | }
34 |
35 | JsonObject obj = new JsonObject();
36 | parent.add(key, obj);
37 | return obj;
38 | }
39 | else
40 | {
41 | return parent.get(key).getAsJsonObject();
42 | }
43 | }
44 |
45 | public static boolean hasBoolean(JsonObject obj, String name)
46 | {
47 | JsonElement el = obj.get(name);
48 |
49 | if (el != null && el.isJsonPrimitive())
50 | {
51 | try
52 | {
53 | el.getAsBoolean();
54 | return true;
55 | }
56 | catch (Exception ignore) {}
57 | }
58 |
59 | return false;
60 | }
61 |
62 | public static boolean hasInteger(JsonObject obj, String name)
63 | {
64 | JsonElement el = obj.get(name);
65 |
66 | if (el != null && el.isJsonPrimitive())
67 | {
68 | try
69 | {
70 | el.getAsInt();
71 | return true;
72 | }
73 | catch (Exception ignore) {}
74 | }
75 |
76 | return false;
77 | }
78 |
79 | public static boolean hasLong(JsonObject obj, String name)
80 | {
81 | JsonElement el = obj.get(name);
82 |
83 | if (el != null && el.isJsonPrimitive())
84 | {
85 | try
86 | {
87 | el.getAsLong();
88 | return true;
89 | }
90 | catch (Exception ignore) {}
91 | }
92 |
93 | return false;
94 | }
95 |
96 | public static boolean hasFloat(JsonObject obj, String name)
97 | {
98 | JsonElement el = obj.get(name);
99 |
100 | if (el != null && el.isJsonPrimitive())
101 | {
102 | try
103 | {
104 | el.getAsFloat();
105 | return true;
106 | }
107 | catch (Exception ignore) {}
108 | }
109 |
110 | return false;
111 | }
112 |
113 | public static boolean hasDouble(JsonObject obj, String name)
114 | {
115 | JsonElement el = obj.get(name);
116 |
117 | if (el != null && el.isJsonPrimitive())
118 | {
119 | try
120 | {
121 | el.getAsDouble();
122 | return true;
123 | }
124 | catch (Exception ignore) {}
125 | }
126 |
127 | return false;
128 | }
129 |
130 | public static boolean hasString(JsonObject obj, String name)
131 | {
132 | JsonElement el = obj.get(name);
133 |
134 | if (el != null && el.isJsonPrimitive())
135 | {
136 | try
137 | {
138 | el.getAsString();
139 | return true;
140 | }
141 | catch (Exception ignore) {}
142 | }
143 |
144 | return false;
145 | }
146 |
147 | public static boolean hasObject(JsonObject obj, String name)
148 | {
149 | JsonElement el = obj.get(name);
150 | return el != null && el.isJsonObject();
151 | }
152 |
153 | public static boolean hasArray(JsonObject obj, String name)
154 | {
155 | JsonElement el = obj.get(name);
156 | return el != null && el.isJsonArray();
157 | }
158 |
159 | public static boolean getBooleanOrDefault(JsonObject obj, String name, boolean defaultValue)
160 | {
161 | if (obj.has(name) && obj.get(name).isJsonPrimitive())
162 | {
163 | try
164 | {
165 | return obj.get(name).getAsBoolean();
166 | }
167 | catch (Exception ignore) {}
168 | }
169 |
170 | return defaultValue;
171 | }
172 |
173 | public static int getIntegerOrDefault(JsonObject obj, String name, int defaultValue)
174 | {
175 | if (obj.has(name) && obj.get(name).isJsonPrimitive())
176 | {
177 | try
178 | {
179 | return obj.get(name).getAsInt();
180 | }
181 | catch (Exception ignore) {}
182 | }
183 |
184 | return defaultValue;
185 | }
186 |
187 | public static long getLongOrDefault(JsonObject obj, String name, long defaultValue)
188 | {
189 | if (obj.has(name) && obj.get(name).isJsonPrimitive())
190 | {
191 | try
192 | {
193 | return obj.get(name).getAsLong();
194 | }
195 | catch (Exception ignore) {}
196 | }
197 |
198 | return defaultValue;
199 | }
200 |
201 | public static float getFloatOrDefault(JsonObject obj, String name, float defaultValue)
202 | {
203 | if (obj.has(name) && obj.get(name).isJsonPrimitive())
204 | {
205 | try
206 | {
207 | return obj.get(name).getAsFloat();
208 | }
209 | catch (Exception ignore) {}
210 | }
211 |
212 | return defaultValue;
213 | }
214 |
215 | public static double getDoubleOrDefault(JsonObject obj, String name, double defaultValue)
216 | {
217 | if (obj.has(name) && obj.get(name).isJsonPrimitive())
218 | {
219 | try
220 | {
221 | return obj.get(name).getAsDouble();
222 | }
223 | catch (Exception ignore) {}
224 | }
225 |
226 | return defaultValue;
227 | }
228 |
229 | public static String getStringOrDefault(JsonObject obj, String name, String defaultValue)
230 | {
231 | if (obj.has(name) && obj.get(name).isJsonPrimitive())
232 | {
233 | try
234 | {
235 | return obj.get(name).getAsString();
236 | }
237 | catch (Exception ignore) {}
238 | }
239 |
240 | return defaultValue;
241 | }
242 |
243 | public static boolean getBoolean(JsonObject obj, String name)
244 | {
245 | return getBooleanOrDefault(obj, name, false);
246 | }
247 |
248 | public static int getInteger(JsonObject obj, String name)
249 | {
250 | return getIntegerOrDefault(obj, name, 0);
251 | }
252 |
253 | public static long getLong(JsonObject obj, String name)
254 | {
255 | return getLongOrDefault(obj, name, 0);
256 | }
257 |
258 | public static float getFloat(JsonObject obj, String name)
259 | {
260 | return getFloatOrDefault(obj, name, 0);
261 | }
262 |
263 | public static double getDouble(JsonObject obj, String name)
264 | {
265 | return getDoubleOrDefault(obj, name, 0);
266 | }
267 |
268 | @Nullable
269 | public static String getString(JsonObject obj, String name)
270 | {
271 | return getStringOrDefault(obj, name, null);
272 | }
273 |
274 | public static boolean hasBlockPos(JsonObject obj, String name)
275 | {
276 | return blockPosFromJson(obj, name) != null;
277 | }
278 |
279 | public static JsonArray blockPosToJson(Vec3i pos)
280 | {
281 | JsonArray arr = new JsonArray();
282 |
283 | arr.add(pos.getX());
284 | arr.add(pos.getY());
285 | arr.add(pos.getZ());
286 |
287 | return arr;
288 | }
289 |
290 | @Nullable
291 | public static BlockPos blockPosFromJson(JsonObject obj, String name)
292 | {
293 | if (hasArray(obj, name))
294 | {
295 | JsonArray arr = obj.getAsJsonArray(name);
296 |
297 | if (arr.size() == 3)
298 | {
299 | try
300 | {
301 | return new BlockPos(arr.get(0).getAsInt(), arr.get(1).getAsInt(), arr.get(2).getAsInt());
302 | }
303 | catch (Exception ignore) {}
304 | }
305 | }
306 |
307 | return null;
308 | }
309 |
310 | public static boolean hasVec3d(JsonObject obj, String name)
311 | {
312 | return vec3dFromJson(obj, name) != null;
313 | }
314 |
315 | public static JsonArray vec3dToJson(Vec3d vec)
316 | {
317 | JsonArray arr = new JsonArray();
318 |
319 | arr.add(vec.x);
320 | arr.add(vec.y);
321 | arr.add(vec.z);
322 |
323 | return arr;
324 | }
325 |
326 | @Nullable
327 | public static Vec3d vec3dFromJson(JsonObject obj, String name)
328 | {
329 | if (hasArray(obj, name))
330 | {
331 | JsonArray arr = obj.getAsJsonArray(name);
332 |
333 | if (arr.size() == 3)
334 | {
335 | try
336 | {
337 | return new Vec3d(arr.get(0).getAsDouble(), arr.get(1).getAsDouble(), arr.get(2).getAsDouble());
338 | }
339 | catch (Exception ignore) {}
340 | }
341 | }
342 |
343 | return null;
344 | }
345 |
346 | // https://stackoverflow.com/questions/29786197/gson-jsonobject-copy-value-affected-others-jsonobject-instance
347 | @Nonnull
348 | public static JsonObject deepCopy(@Nonnull JsonObject jsonObject)
349 | {
350 | JsonObject result = new JsonObject();
351 |
352 | for (Map.Entry entry : jsonObject.entrySet())
353 | {
354 | result.add(entry.getKey(), deepCopy(entry.getValue()));
355 | }
356 |
357 | return result;
358 | }
359 |
360 | @Nonnull
361 | public static JsonArray deepCopy(@Nonnull JsonArray jsonArray)
362 | {
363 | JsonArray result = new JsonArray();
364 |
365 | for (JsonElement e : jsonArray)
366 | {
367 | result.add(deepCopy(e));
368 | }
369 |
370 | return result;
371 | }
372 |
373 | @Nonnull
374 | public static JsonElement deepCopy(@Nonnull JsonElement jsonElement)
375 | {
376 | if (jsonElement.isJsonPrimitive() || jsonElement.isJsonNull())
377 | {
378 | return jsonElement; // these are immutable anyway
379 | }
380 | else if (jsonElement.isJsonObject())
381 | {
382 | return deepCopy(jsonElement.getAsJsonObject());
383 | }
384 | else if (jsonElement.isJsonArray())
385 | {
386 | return deepCopy(jsonElement.getAsJsonArray());
387 | }
388 | else
389 | {
390 | throw new UnsupportedOperationException("Unsupported element: " + jsonElement);
391 | }
392 | }
393 |
394 | @Nullable
395 | public static JsonElement parseJsonFromString(String str)
396 | {
397 | try
398 | {
399 | return JsonParser.parseString(str);
400 | }
401 | catch (Exception ignore) {}
402 |
403 | return null;
404 | }
405 |
406 | @Nullable
407 | public static JsonElement parseJsonFile(File file)
408 | {
409 | if (file != null && file.exists() && file.isFile() && file.canRead())
410 | {
411 | String fileName = file.getAbsolutePath();
412 |
413 | try (FileReader reader = new FileReader(file))
414 | {
415 | return JsonParser.parseReader(reader);
416 | }
417 | catch (Exception e)
418 | {
419 | Servux.logger.error("Failed to parse the JSON file '{}'", fileName, e);
420 | }
421 | }
422 |
423 | return null;
424 | }
425 |
426 | /**
427 | * Converts the given JsonElement tree into its string representation.
428 | * If compact is true, then it's written in one line without spaces or line breaks.
429 | */
430 | public static String jsonToString(JsonElement element, boolean compact)
431 | {
432 | Gson gson = compact ? new Gson() : GSON;
433 | return gson.toJson(element);
434 | }
435 |
436 | public static boolean writeJsonToFile(JsonElement root, File file)
437 | {
438 | return writeJsonToFile(GSON, root, file);
439 | }
440 |
441 | public static boolean writeJsonToFile(Gson gson, JsonElement root, File file)
442 | {
443 | FileWriter writer = null;
444 |
445 | try
446 | {
447 | writer = new FileWriter(file);
448 | writer.write(gson.toJson(root));
449 | writer.close();
450 |
451 | return true;
452 | }
453 | catch (IOException e)
454 | {
455 | Servux.logger.warn("Failed to write JSON data to file '{}'", file.getAbsolutePath(), e);
456 | }
457 | finally
458 | {
459 | try
460 | {
461 | if (writer != null)
462 | {
463 | writer.close();
464 | }
465 | }
466 | catch (Exception e)
467 | {
468 | Servux.logger.warn("Failed to close JSON file", e);
469 | }
470 | }
471 |
472 | return false;
473 | }
474 | }
475 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/util/PlayerDimensionPosition.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.util;
2 |
3 | import net.minecraft.entity.player.PlayerEntity;
4 | import net.minecraft.util.math.BlockPos;
5 | import net.minecraft.world.dimension.DimensionType;
6 |
7 | public class PlayerDimensionPosition
8 | {
9 | protected DimensionType dimensionType;
10 | protected BlockPos pos;
11 |
12 | public PlayerDimensionPosition(PlayerEntity player)
13 | {
14 | this.setPosition(player);
15 | }
16 |
17 | public boolean dimensionChanged(PlayerEntity player)
18 | {
19 | return this.dimensionType != player.getEntityWorld().getDimension();
20 | }
21 |
22 | public boolean needsUpdate(PlayerEntity player, int distanceThreshold)
23 | {
24 | if (player.getEntityWorld().getDimension() != this.dimensionType)
25 | {
26 | return true;
27 | }
28 |
29 | BlockPos pos = player.getBlockPos();
30 |
31 | return Math.abs(pos.getX() - this.pos.getX()) > distanceThreshold ||
32 | Math.abs(pos.getY() - this.pos.getY()) > distanceThreshold ||
33 | Math.abs(pos.getZ() - this.pos.getZ()) > distanceThreshold;
34 | }
35 |
36 | public void setPosition(PlayerEntity player)
37 | {
38 | this.dimensionType = player.getEntityWorld().getDimension();
39 | this.pos = player.getBlockPos();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/fi/dy/masa/servux/util/Timeout.java:
--------------------------------------------------------------------------------
1 | package fi.dy.masa.servux.util;
2 |
3 | public class Timeout
4 | {
5 | protected int lastSync;
6 |
7 | public Timeout(int currentTick)
8 | {
9 | this.lastSync = currentTick;
10 | }
11 |
12 | public boolean needsUpdate(int currentTick, int timeout)
13 | {
14 | return currentTick - this.lastSync >= timeout;
15 | }
16 |
17 | public void setLastSync(int tickCounter)
18 | {
19 | this.lastSync = tickCounter;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/resources/fabric.mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 1,
3 | "id": "servux",
4 | "name": "Servux",
5 | "version": "${mod_version}",
6 |
7 | "description": "Server-side support and integration for masa's client mods",
8 | "license": "LGPLv3",
9 | "authors": [
10 | "masa"
11 | ],
12 | "contact": {
13 | "homepage": "https://www.curseforge.com/minecraft/mc-mods/servux",
14 | "sources": "https://github.com/maruohon/servux",
15 | "twitter": "https://twitter.com/maruohon",
16 | "discord": "https://discordapp.com/channels/211786369951989762/453662800460644354/"
17 | },
18 |
19 | "environment": "server",
20 |
21 | "entrypoints": {
22 | "main": [
23 | "fi.dy.masa.servux.Servux"
24 | ]
25 | },
26 |
27 | "mixins": [
28 | "mixins.servux.json"
29 | ],
30 |
31 | "depends": {
32 | "minecraft": ">=1.19"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/mixins.servux.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "package": "fi.dy.masa.servux.mixin",
4 | "compatibilityLevel": "JAVA_17",
5 | "minVersion": "0.8",
6 | "mixins": [
7 | "IMixinCustomPayloadC2SPacket",
8 | "MixinMinecraftServer",
9 | "MixinServerPlayNetworkHandler",
10 | "MixinThreadedAnvilChunkStorage"
11 | ],
12 | "injectors": {
13 | "defaultRequire": 1
14 | }
15 | }
--------------------------------------------------------------------------------