Add NeoForge 1.21.1 mod with live server configuration under MIT
This commit is contained in:
1 parent
adfebbcb13
commit
2b5ff6f8d5
32 files changed
+1818
No files matched your search
+122
@@ -0,0 +1,122 @@
|
|||||||
|
# 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
|
||||||
|
.gradle-user-home/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Ignore Gradle GUI config
|
||||||
|
gradle-app.setting
|
||||||
|
|
||||||
|
# Cache of project
|
||||||
|
.gradletasknamecache
|
||||||
|
|
||||||
|
**/build/
|
||||||
|
|
||||||
|
# Common working directory
|
||||||
|
run/
|
||||||
|
run-data/
|
||||||
|
runs/
|
||||||
|
run-neoforge-server/
|
||||||
|
|
||||||
|
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||||
|
!gradle-wrapper.jar
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id 'net.neoforged.moddev' version '2.0.107'
|
||||||
|
}
|
||||||
|
group = mod_group_id
|
||||||
|
version = mod_version
|
||||||
|
base { archivesName = "${mod_id}-neoforge-1.21.1" }
|
||||||
|
java { toolchain.languageVersion = JavaLanguageVersion.of(21) }
|
||||||
|
neoForge {
|
||||||
|
version = neo_version
|
||||||
|
runs {
|
||||||
|
client { client() }
|
||||||
|
server {
|
||||||
|
server()
|
||||||
|
gameDirectory = file('run-neoforge-server')
|
||||||
|
programArgument '--nogui'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mods {
|
||||||
|
"${mod_id}" { sourceSet sourceSets.main }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks.named('processResources', ProcessResources).configure {
|
||||||
|
def properties = [mod_id: mod_id, mod_version: mod_version, mod_name: mod_name,
|
||||||
|
mod_license: mod_license, mod_authors: mod_authors]
|
||||||
|
inputs.properties properties
|
||||||
|
filesMatching('META-INF/neoforge.mods.toml') { expand properties }
|
||||||
|
}
|
||||||
|
tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' }
|
||||||
|
|
||||||
|
tasks.named('jar', Jar).configure {
|
||||||
|
from('LICENSE')
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx3G
|
||||||
|
org.gradle.daemon=false
|
||||||
|
neo_version=21.1.209
|
||||||
|
mod_id=forged_carpet_tool
|
||||||
|
mod_name=Forged Carpet Tool
|
||||||
|
mod_license=MIT
|
||||||
|
mod_version=1.1.1
|
||||||
|
mod_group_id=cn.disy920
|
||||||
|
mod_authors=Disy920
|
||||||
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
# 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 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/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/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
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
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
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 ;; #(
|
||||||
|
MSYS* | 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
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
# 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" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
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.
|
||||||
|
#
|
||||||
|
# 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" "$@"
|
||||||
Vendored
+92
@@ -0,0 +1,92 @@
|
|||||||
|
@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=.
|
||||||
|
@rem This is normally unused
|
||||||
|
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% equ 0 goto execute
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
: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 %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
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!
|
||||||
|
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
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven { url = 'https://maven.neoforged.net/releases' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootProject.name = 'forged-carpet-tool'
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool;
|
||||||
|
|
||||||
|
import cn.disy920.forged_carpet_tool.config.ClientInit;
|
||||||
|
import cn.disy920.forged_carpet_tool.config.ConfigIO;
|
||||||
|
import cn.disy920.forged_carpet_tool.config.ServerConfig;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
|
import net.neoforged.fml.loading.FMLEnvironment;
|
||||||
|
import net.neoforged.api.distmarker.Dist;
|
||||||
|
import net.neoforged.fml.common.Mod;
|
||||||
|
import net.neoforged.fml.config.ModConfig;
|
||||||
|
import net.neoforged.fml.event.config.ModConfigEvent;
|
||||||
|
|
||||||
|
@Mod(ForgedCarpetToolMod.MOD_ID)
|
||||||
|
public final class ForgedCarpetToolMod {
|
||||||
|
public static final String MOD_ID = "forged_carpet_tool";
|
||||||
|
|
||||||
|
public ForgedCarpetToolMod(IEventBus modEventBus, ModContainer context) {
|
||||||
|
|
||||||
|
context.registerConfig(ModConfig.Type.SERVER, ServerConfig.SPEC);
|
||||||
|
modEventBus.addListener(NetworkHandler::register);
|
||||||
|
modEventBus.addListener(this::onConfigLoading);
|
||||||
|
modEventBus.addListener(this::onConfigReloading);
|
||||||
|
|
||||||
|
if (FMLEnvironment.dist == Dist.CLIENT) ClientInit.registerConfigScreen(context);
|
||||||
|
NeoForge.EVENT_BUS.register(ModEvents.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onConfigLoading(ModConfigEvent.Loading event) {
|
||||||
|
ConfigIO.captureServerConfig(event.getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onConfigReloading(ModConfigEvent.Reloading event) {
|
||||||
|
ConfigIO.captureServerConfig(event.getConfig());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool;
|
||||||
|
|
||||||
|
import cn.disy920.forged_carpet_tool.config.ConfigSync;
|
||||||
|
import cn.disy920.forged_carpet_tool.config.ServerConfig;
|
||||||
|
import cn.disy920.forged_carpet_tool.config.ConfigCommands;
|
||||||
|
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.damagesource.DamageTypes;
|
||||||
|
import net.neoforged.neoforge.event.entity.living.LivingDeathEvent;
|
||||||
|
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||||
|
import net.neoforged.bus.api.EventPriority;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
|
||||||
|
public final class ModEvents {
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||||
|
ConfigCommands.register(event.getDispatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String KEEP_VOID_INVENTORY = ForgedCarpetToolMod.MOD_ID + ":keep_void_inventory";
|
||||||
|
|
||||||
|
@SubscribeEvent(priority = EventPriority.LOWEST, receiveCanceled = true)
|
||||||
|
public static void onPlayerDeath(LivingDeathEvent event) {
|
||||||
|
if (event.getEntity() instanceof ServerPlayer player) {
|
||||||
|
// Latch the decision for this death, even if the config changes before respawn.
|
||||||
|
player.getPersistentData().putBoolean(KEEP_VOID_INVENTORY,
|
||||||
|
!event.isCanceled()
|
||||||
|
&& event.getSource().is(DamageTypes.FELL_OUT_OF_WORLD)
|
||||||
|
&& ServerConfig.KEEP_INVENTORY_ON_VOID_DEATH.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onPlayerClone(PlayerEvent.Clone event) {
|
||||||
|
if (event.isWasDeath() && event.getEntity() instanceof ServerPlayer
|
||||||
|
&& event.getOriginal().getPersistentData().getBoolean(KEEP_VOID_INVENTORY)) {
|
||||||
|
event.getEntity().getInventory().replaceWith(event.getOriginal().getInventory());
|
||||||
|
event.getOriginal().getPersistentData().remove(KEEP_VOID_INVENTORY);
|
||||||
|
event.getEntity().getPersistentData().remove(KEEP_VOID_INVENTORY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||||
|
if (event.getEntity() instanceof ServerPlayer serverPlayer) {
|
||||||
|
ConfigSync.syncToPlayer(serverPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool;
|
||||||
|
import cn.disy920.forged_carpet_tool.config.SyncServerConfigPacket;
|
||||||
|
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||||
|
public final class NetworkHandler {
|
||||||
|
private NetworkHandler() {}
|
||||||
|
public static void register(RegisterPayloadHandlersEvent event) {
|
||||||
|
var registrar = event.registrar("2").optional();
|
||||||
|
registrar.playToServer(UpdateServerConfigPacket.TYPE, UpdateServerConfigPacket.CODEC, UpdateServerConfigPacket::handle);
|
||||||
|
registrar.playToClient(SyncServerConfigPacket.TYPE, SyncServerConfigPacket.CODEC, SyncServerConfigPacket::handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool;
|
||||||
|
import cn.disy920.forged_carpet_tool.config.*;
|
||||||
|
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 net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
import java.util.List;
|
||||||
|
public record UpdateServerConfigPacket(List<ConfigEntryValue> entries) implements CustomPacketPayload {
|
||||||
|
public static final Type<UpdateServerConfigPacket> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ForgedCarpetToolMod.MOD_ID, "config_update"));
|
||||||
|
public static final StreamCodec<FriendlyByteBuf, UpdateServerConfigPacket> CODEC = StreamCodec.of(
|
||||||
|
(buf, packet) -> buf.writeCollection(packet.entries, ConfigEntryValue::encode),
|
||||||
|
buf -> new UpdateServerConfigPacket(ConfigEntryValue.readEntries(buf)));
|
||||||
|
public Type<? extends CustomPacketPayload> type() { return TYPE; }
|
||||||
|
public static void handle(UpdateServerConfigPacket packet, IPayloadContext context) {
|
||||||
|
if (!(context.player() instanceof ServerPlayer sender) || !sender.hasPermissions(2) || packet.entries.isEmpty()) return;
|
||||||
|
try {
|
||||||
|
ConfigIO.applyUpdates(packet.entries);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
ConfigSync.syncToPlayer(sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ids = packet.entries.stream().map(ConfigEntryValue::entryId).toList();
|
||||||
|
for (ServerPlayer player : sender.getServer().getPlayerList().getPlayers()) ConfigSync.syncUpdates(player, ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
|
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
|
|
||||||
|
public final class ClientInit {
|
||||||
|
private ClientInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerConfigScreen(ModContainer context) {
|
||||||
|
NeoForge.EVENT_BUS.addListener(ClientInit::onLoggingOut);
|
||||||
|
context.registerExtensionPoint(
|
||||||
|
IConfigScreenFactory.class,
|
||||||
|
(container, parent) -> new ServerConfigScreen(parent)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onLoggingOut(ClientPlayerNetworkEvent.LoggingOut event) {
|
||||||
|
ConfigState.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.arguments.BoolArgumentType;
|
||||||
|
import com.mojang.logging.LogUtils;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
import net.minecraft.commands.Commands;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ConfigCommands {
|
||||||
|
private ConfigCommands() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
|
dispatcher.register(Commands.literal("fct")
|
||||||
|
.requires(source -> source.hasPermission(2))
|
||||||
|
.then(Commands.literal("config")
|
||||||
|
.then(Commands.literal("keep_inventory_on_void_death")
|
||||||
|
.executes(context -> {
|
||||||
|
context.getSource().sendSuccess(() -> Component.literal(
|
||||||
|
"keep_inventory_on_void_death = "
|
||||||
|
+ ServerConfig.KEEP_INVENTORY_ON_VOID_DEATH.get()), false);
|
||||||
|
return 1;
|
||||||
|
})
|
||||||
|
.then(Commands.argument("enabled", BoolArgumentType.bool())
|
||||||
|
.executes(context -> setVoidInventory(context.getSource(),
|
||||||
|
BoolArgumentType.getBool(context, "enabled")))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int setVoidInventory(CommandSourceStack source, boolean enabled) {
|
||||||
|
ServerConfig.EntryDefinition definition = ServerConfig.describeEntries().stream()
|
||||||
|
.filter(entry -> entry.path().equals(ServerConfig.KEEP_INVENTORY_ON_VOID_DEATH.getPath()))
|
||||||
|
.findFirst().orElseThrow();
|
||||||
|
try {
|
||||||
|
ConfigIO.applyUpdates(List.of(new ConfigEntryValue(definition.id(), enabled)));
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
LogUtils.getLogger().error("Failed to save void inventory config", exception);
|
||||||
|
source.sendFailure(Component.translatable("forged_carpet_tool.config.command.save_failed"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (ServerPlayer player : source.getServer().getPlayerList().getPlayers()) {
|
||||||
|
ConfigSync.syncUpdates(player, List.of(definition.id()));
|
||||||
|
}
|
||||||
|
source.sendSuccess(() -> Component.literal("keep_inventory_on_void_death = " + enabled), true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
|
||||||
|
public record ConfigEntryValue(int entryId, Object value) {
|
||||||
|
public static java.util.List<ConfigEntryValue> readEntries(FriendlyByteBuf buffer) {
|
||||||
|
int size = buffer.readVarInt();
|
||||||
|
if (size < 0 || size > ServerConfig.describeEntries().size()) throw new IllegalArgumentException("Invalid config count");
|
||||||
|
var entries = new java.util.ArrayList<ConfigEntryValue>(size);
|
||||||
|
var ids = new java.util.HashSet<Integer>();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
var entry = decode(buffer);
|
||||||
|
if (!ids.add(entry.entryId())) throw new IllegalArgumentException("Duplicate config id");
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
public static void encode(FriendlyByteBuf buffer, ConfigEntryValue entryValue) {
|
||||||
|
ServerConfig.EntryDefinition definition = ServerConfig.definitionById(entryValue.entryId());
|
||||||
|
buffer.writeVarInt(entryValue.entryId());
|
||||||
|
switch (definition.valueType()) {
|
||||||
|
case BOOLEAN -> buffer.writeBoolean((Boolean) entryValue.value());
|
||||||
|
case INTEGER -> buffer.writeVarInt((Integer) entryValue.value());
|
||||||
|
case LONG -> buffer.writeVarLong((Long) entryValue.value());
|
||||||
|
case DOUBLE -> buffer.writeDouble(((Number) entryValue.value()).doubleValue());
|
||||||
|
case STRING -> buffer.writeUtf((String) entryValue.value(), 32767);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConfigEntryValue decode(FriendlyByteBuf buffer) {
|
||||||
|
int entryId = buffer.readVarInt();
|
||||||
|
ServerConfig.EntryDefinition definition = ServerConfig.definitionById(entryId);
|
||||||
|
Object value = switch (definition.valueType()) {
|
||||||
|
case BOOLEAN -> buffer.readBoolean();
|
||||||
|
case INTEGER -> buffer.readVarInt();
|
||||||
|
case LONG -> buffer.readVarLong();
|
||||||
|
case DOUBLE -> buffer.readDouble();
|
||||||
|
case STRING -> buffer.readUtf(32767);
|
||||||
|
};
|
||||||
|
return new ConfigEntryValue(entryId, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||||
|
import net.neoforged.fml.config.ModConfig;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class ConfigIO {
|
||||||
|
private static ModConfig activeServerConfig;
|
||||||
|
|
||||||
|
private ConfigIO() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void captureServerConfig(ModConfig modConfig) {
|
||||||
|
if (modConfig.getType() == ModConfig.Type.SERVER && modConfig.getSpec() == ServerConfig.SPEC) {
|
||||||
|
activeServerConfig = modConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ConfigEntryValue> snapshotAllValues() {
|
||||||
|
List<ConfigEntryValue> snapshot = new ArrayList<>();
|
||||||
|
for (ServerConfig.EntryDefinition definition : ServerConfig.describeEntries()) {
|
||||||
|
snapshot.add(new ConfigEntryValue(definition.id(), getCurrentValue(definition)));
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ConfigEntryValue> snapshotValues(List<Integer> entryIds) {
|
||||||
|
List<ConfigEntryValue> snapshot = new ArrayList<>(entryIds.size());
|
||||||
|
for (int entryId : entryIds) {
|
||||||
|
ServerConfig.EntryDefinition definition = ServerConfig.definitionById(entryId);
|
||||||
|
snapshot.add(new ConfigEntryValue(entryId, getCurrentValue(definition)));
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyUpdates(List<ConfigEntryValue> updates) {
|
||||||
|
if (activeServerConfig == null) {
|
||||||
|
throw new IllegalStateException("Server config has not been loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommentedConfig targetConfig = activeServerConfig.getLoadedConfig().config();
|
||||||
|
for (ConfigEntryValue update : updates) {
|
||||||
|
ServerConfig.EntryDefinition definition = ServerConfig.definitionById(update.entryId());
|
||||||
|
targetConfig.set(definition.path(), coerceValue(definition, update.value()));
|
||||||
|
}
|
||||||
|
ServerConfig.SPEC.correct(targetConfig);
|
||||||
|
ServerConfig.SPEC.afterReload();
|
||||||
|
activeServerConfig.getLoadedConfig().save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path getServerConfigPath() {
|
||||||
|
if (activeServerConfig == null) {
|
||||||
|
throw new IllegalStateException("Server config has not been loaded");
|
||||||
|
}
|
||||||
|
return activeServerConfig.getFullPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getCurrentValue(ServerConfig.EntryDefinition definition) {
|
||||||
|
if (activeServerConfig == null) {
|
||||||
|
return definition.defaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = activeServerConfig.getLoadedConfig().config().get(definition.path());
|
||||||
|
return value != null ? value : definition.defaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object coerceValue(ServerConfig.EntryDefinition definition, Object value) {
|
||||||
|
return switch (definition.valueType()) {
|
||||||
|
case BOOLEAN -> (Boolean) value;
|
||||||
|
case INTEGER -> ((Number) value).intValue();
|
||||||
|
case LONG -> ((Number) value).longValue();
|
||||||
|
case DOUBLE -> ((Number) value).doubleValue();
|
||||||
|
case STRING -> Objects.toString(value, "");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class ConfigState {
|
||||||
|
private static final Map<Integer, Object> values = new HashMap<>();
|
||||||
|
private static boolean canEditServerConfig;
|
||||||
|
|
||||||
|
private ConfigState() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getValue(int entryId) {
|
||||||
|
return values.get(entryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
values.clear();
|
||||||
|
canEditServerConfig = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void replaceValues(List<ConfigEntryValue> updatedValues) {
|
||||||
|
values.clear();
|
||||||
|
applyUpdates(updatedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyUpdates(List<ConfigEntryValue> updatedValues) {
|
||||||
|
for (ConfigEntryValue entryValue : updatedValues) {
|
||||||
|
values.put(entryValue.entryId(), entryValue.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canEditServerConfig() {
|
||||||
|
return canEditServerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCanEditServerConfig(boolean canEditServerConfig) {
|
||||||
|
ConfigState.canEditServerConfig = canEditServerConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
|
import java.util.List;
|
||||||
|
public final class ConfigSync {
|
||||||
|
private ConfigSync() {}
|
||||||
|
public static void syncToPlayer(ServerPlayer player) {
|
||||||
|
if (!player.connection.hasChannel(SyncServerConfigPacket.TYPE)) return;
|
||||||
|
PacketDistributor.sendToPlayer(player, new SyncServerConfigPacket(true, ConfigIO.snapshotAllValues(), player.hasPermissions(2)));
|
||||||
|
}
|
||||||
|
public static void syncUpdates(ServerPlayer player, List<Integer> ids) {
|
||||||
|
if (!player.connection.hasChannel(SyncServerConfigPacket.TYPE)) return;
|
||||||
|
PacketDistributor.sendToPlayer(player, new SyncServerConfigPacket(false, ConfigIO.snapshotValues(ids), player.hasPermissions(2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ServerConfig {
|
||||||
|
public static final ModConfigSpec SPEC;
|
||||||
|
private static final List<EntryDefinition> ENTRIES;
|
||||||
|
|
||||||
|
public static ModConfigSpec.BooleanValue ENABLE_SAFETY_UPDATE_SUPPRESSION;
|
||||||
|
public static ModConfigSpec.IntValue NETHER_PORTAL_MAX_SIZE;
|
||||||
|
public static ModConfigSpec.BooleanValue AVOID_CHAT_OUT_OF_ORDER;
|
||||||
|
public static ModConfigSpec.BooleanValue KEEP_INVENTORY_ON_VOID_DEATH;
|
||||||
|
|
||||||
|
static {
|
||||||
|
EntryRegistrar registrar = new EntryRegistrar();
|
||||||
|
|
||||||
|
registrar.push("creative");
|
||||||
|
|
||||||
|
ENABLE_SAFETY_UPDATE_SUPPRESSION = registrar.defineBoolean(
|
||||||
|
"enableSaftyUpdateSuppression",
|
||||||
|
false,
|
||||||
|
"forged_carpet_tool.config.creative.enable_safety_update_suppression.title",
|
||||||
|
"forged_carpet_tool.config.creative.enable_safety_update_suppression.subtitle"
|
||||||
|
);
|
||||||
|
|
||||||
|
NETHER_PORTAL_MAX_SIZE = registrar.defineInt(
|
||||||
|
"netherPortalMaxSize",
|
||||||
|
21,
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
"forged_carpet_tool.config.creative.nether_portal_max_size.title",
|
||||||
|
"forged_carpet_tool.config.creative.nether_portal_max_size.subtitle"
|
||||||
|
);
|
||||||
|
|
||||||
|
registrar.pop();
|
||||||
|
|
||||||
|
registrar.push("feature");
|
||||||
|
|
||||||
|
AVOID_CHAT_OUT_OF_ORDER = registrar.defineBoolean(
|
||||||
|
"avoid_chat_out_of_order",
|
||||||
|
false,
|
||||||
|
"forged_carpet_tool.config.feature.avoid_chat_out_of_order.title",
|
||||||
|
"forged_carpet_tool.config.feature.avoid_chat_out_of_order.subtitle"
|
||||||
|
);
|
||||||
|
|
||||||
|
KEEP_INVENTORY_ON_VOID_DEATH = registrar.defineBoolean(
|
||||||
|
"keep_inventory_on_void_death",
|
||||||
|
false,
|
||||||
|
"forged_carpet_tool.config.feature.keep_inventory_on_void_death.title",
|
||||||
|
"forged_carpet_tool.config.feature.keep_inventory_on_void_death.subtitle"
|
||||||
|
);
|
||||||
|
|
||||||
|
registrar.pop();
|
||||||
|
|
||||||
|
SPEC = registrar.buildSpec();
|
||||||
|
ENTRIES = registrar.buildEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerConfig() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<EntryDefinition> describeEntries() {
|
||||||
|
return ENTRIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntryDefinition definitionById(int entryId) {
|
||||||
|
if (entryId < 0 || entryId >= ENTRIES.size()) {
|
||||||
|
throw new IllegalArgumentException("Unknown config entry id: " + entryId);
|
||||||
|
}
|
||||||
|
return ENTRIES.get(entryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ValueType {
|
||||||
|
BOOLEAN,
|
||||||
|
INTEGER,
|
||||||
|
LONG,
|
||||||
|
DOUBLE,
|
||||||
|
STRING
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ControlType {
|
||||||
|
TOGGLE,
|
||||||
|
SLIDER,
|
||||||
|
TEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
public record EntryDefinition(
|
||||||
|
int id,
|
||||||
|
List<String> path,
|
||||||
|
String titleKey,
|
||||||
|
String subtitleKey,
|
||||||
|
ValueType valueType,
|
||||||
|
ControlType controlType,
|
||||||
|
Object defaultValue,
|
||||||
|
Double minValue,
|
||||||
|
Double maxValue
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class EntryRegistrar {
|
||||||
|
private final ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
||||||
|
private final List<EntryDefinition> entries = new ArrayList<>();
|
||||||
|
|
||||||
|
public EntryRegistrar push(String path) {
|
||||||
|
this.builder.push(path);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntryRegistrar pop() {
|
||||||
|
this.builder.pop();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModConfigSpec buildSpec() {
|
||||||
|
return this.builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EntryDefinition> buildEntries() {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<>(this.entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModConfigSpec.BooleanValue defineBoolean(
|
||||||
|
String path,
|
||||||
|
boolean defaultValue,
|
||||||
|
String titleKey,
|
||||||
|
String subtitleKey
|
||||||
|
) {
|
||||||
|
ModConfigSpec.BooleanValue value = this.builder.define(path, defaultValue);
|
||||||
|
this.entries.add(new EntryDefinition(
|
||||||
|
this.entries.size(),
|
||||||
|
value.getPath(),
|
||||||
|
titleKey,
|
||||||
|
subtitleKey,
|
||||||
|
ValueType.BOOLEAN,
|
||||||
|
ControlType.TOGGLE,
|
||||||
|
defaultValue,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModConfigSpec.IntValue defineInt(
|
||||||
|
String path,
|
||||||
|
int defaultValue,
|
||||||
|
int minValue,
|
||||||
|
int maxValue,
|
||||||
|
String titleKey,
|
||||||
|
String subtitleKey
|
||||||
|
) {
|
||||||
|
ModConfigSpec.IntValue value = this.builder.defineInRange(path, defaultValue, minValue, maxValue);
|
||||||
|
this.entries.add(new EntryDefinition(
|
||||||
|
this.entries.size(),
|
||||||
|
value.getPath(),
|
||||||
|
titleKey,
|
||||||
|
subtitleKey,
|
||||||
|
ValueType.INTEGER,
|
||||||
|
ControlType.SLIDER,
|
||||||
|
defaultValue,
|
||||||
|
(double) minValue,
|
||||||
|
(double) maxValue
|
||||||
|
));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModConfigSpec.LongValue defineLong(
|
||||||
|
String path,
|
||||||
|
long defaultValue,
|
||||||
|
long minValue,
|
||||||
|
long maxValue,
|
||||||
|
String titleKey,
|
||||||
|
String subtitleKey
|
||||||
|
) {
|
||||||
|
ModConfigSpec.LongValue value = this.builder.defineInRange(path, defaultValue, minValue, maxValue);
|
||||||
|
this.entries.add(new EntryDefinition(
|
||||||
|
this.entries.size(),
|
||||||
|
value.getPath(),
|
||||||
|
titleKey,
|
||||||
|
subtitleKey,
|
||||||
|
ValueType.LONG,
|
||||||
|
ControlType.SLIDER,
|
||||||
|
defaultValue,
|
||||||
|
(double) minValue,
|
||||||
|
(double) maxValue
|
||||||
|
));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModConfigSpec.DoubleValue defineDouble(
|
||||||
|
String path,
|
||||||
|
double defaultValue,
|
||||||
|
double minValue,
|
||||||
|
double maxValue,
|
||||||
|
String titleKey,
|
||||||
|
String subtitleKey
|
||||||
|
) {
|
||||||
|
ModConfigSpec.DoubleValue value = this.builder.defineInRange(path, defaultValue, minValue, maxValue);
|
||||||
|
this.entries.add(new EntryDefinition(
|
||||||
|
this.entries.size(),
|
||||||
|
value.getPath(),
|
||||||
|
titleKey,
|
||||||
|
subtitleKey,
|
||||||
|
ValueType.DOUBLE,
|
||||||
|
ControlType.SLIDER,
|
||||||
|
defaultValue,
|
||||||
|
minValue,
|
||||||
|
maxValue
|
||||||
|
));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModConfigSpec.ConfigValue<String> defineString(
|
||||||
|
String path,
|
||||||
|
String defaultValue,
|
||||||
|
String titleKey,
|
||||||
|
String subtitleKey
|
||||||
|
) {
|
||||||
|
ModConfigSpec.ConfigValue<String> value = this.builder.define(path, defaultValue);
|
||||||
|
this.entries.add(new EntryDefinition(
|
||||||
|
this.entries.size(),
|
||||||
|
value.getPath(),
|
||||||
|
titleKey,
|
||||||
|
subtitleKey,
|
||||||
|
ValueType.STRING,
|
||||||
|
ControlType.TEXT,
|
||||||
|
defaultValue,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
|
import cn.disy920.forged_carpet_tool.UpdateServerConfigPacket;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||||
|
import net.minecraft.client.gui.components.AbstractWidget;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.components.EditBox;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.CommonComponents;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class ServerConfigScreen extends Screen {
|
||||||
|
private static final int ROW_PADDING = 12;
|
||||||
|
private static final int TOP_MARGIN = 64;
|
||||||
|
private static final int BOTTOM_BAR_HEIGHT = 56;
|
||||||
|
private static final int SIDE_PADDING = 20;
|
||||||
|
private static final int COLUMN_GAP = 14;
|
||||||
|
private static final int MIN_CONTROL_WIDTH = 110;
|
||||||
|
private static final int MAX_CONTROL_WIDTH = 190;
|
||||||
|
|
||||||
|
private final Screen parent;
|
||||||
|
private final List<EntryRow> rows = new ArrayList<>();
|
||||||
|
private Button saveButton;
|
||||||
|
private int scrollOffset;
|
||||||
|
private int maxScroll;
|
||||||
|
private Component statusMessage = CommonComponents.EMPTY;
|
||||||
|
|
||||||
|
public ServerConfigScreen(Screen parent) {
|
||||||
|
super(Component.translatable("forged_carpet_tool.config.title"));
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
this.clearWidgets();
|
||||||
|
this.rows.clear();
|
||||||
|
this.statusMessage = CommonComponents.EMPTY;
|
||||||
|
|
||||||
|
for (ServerConfig.EntryDefinition definition : ServerConfig.describeEntries()) {
|
||||||
|
this.rows.add(createRow(definition));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveButton = this.addRenderableWidget(Button.builder(
|
||||||
|
Component.translatable("forged_carpet_tool.config.save"),
|
||||||
|
button -> this.saveConfig()
|
||||||
|
).bounds(this.width / 2 - 110, this.height - 30, 100, 20).build());
|
||||||
|
this.saveButton.active = this.canEditConfig();
|
||||||
|
|
||||||
|
this.addRenderableWidget(Button.builder(CommonComponents.GUI_DONE, button -> this.onClose())
|
||||||
|
.bounds(this.width / 2 + 10, this.height - 30, 100, 20)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
this.updateEntryLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||||
|
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
|
this.updateEntryLayout();
|
||||||
|
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||||
|
|
||||||
|
guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, 20, 0xFFFFFF);
|
||||||
|
int left = this.getContentLeft();
|
||||||
|
guiGraphics.drawString(this.font, Component.translatable("forged_carpet_tool.config.description"), left, 38, 0xA0A0A0);
|
||||||
|
|
||||||
|
Component permissionText = this.canEditConfig()
|
||||||
|
? Component.translatable("forged_carpet_tool.config.permission.op").withStyle(ChatFormatting.GREEN)
|
||||||
|
: Component.translatable("forged_carpet_tool.config.permission.read_only").withStyle(ChatFormatting.RED);
|
||||||
|
guiGraphics.drawString(this.font, permissionText, left, this.height - 42, 0xFFFFFF);
|
||||||
|
|
||||||
|
if (!this.statusMessage.getString().isEmpty()) {
|
||||||
|
guiGraphics.drawString(this.font, this.statusMessage, left, this.height - 54, 0xE0B060);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rows.isEmpty()) {
|
||||||
|
guiGraphics.drawCenteredString(
|
||||||
|
this.font,
|
||||||
|
Component.translatable("forged_carpet_tool.config.empty"),
|
||||||
|
this.width / 2,
|
||||||
|
this.getListTop() + 12,
|
||||||
|
0xA0A0A0
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (EntryRow row : this.rows) {
|
||||||
|
if (!row.widget.visible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int titleX = left;
|
||||||
|
int titleY = row.rowTop + 6;
|
||||||
|
guiGraphics.drawWordWrap(this.font, Component.translatable(row.definition.titleKey()), titleX, titleY, this.getTextWidth(), 0xFFFFFF);
|
||||||
|
guiGraphics.drawWordWrap(this.font, Component.translatable(row.definition.subtitleKey()), titleX, titleY + 14, this.getTextWidth(), 0x909090);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseScrolled(double mouseX, double mouseY, double horizontal, double delta) {
|
||||||
|
if (this.maxScroll <= 0) {
|
||||||
|
return super.mouseScrolled(mouseX, mouseY, horizontal, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollOffset = (int) Math.max(0, Math.min(this.maxScroll, this.scrollOffset - delta * 22));
|
||||||
|
this.updateEntryLayout();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose() {
|
||||||
|
Minecraft.getInstance().setScreen(this.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveConfig() {
|
||||||
|
if (!this.canEditConfig()) return;
|
||||||
|
try {
|
||||||
|
List<ConfigEntryValue> changedEntries = this.collectChangedEntries();
|
||||||
|
if (changedEntries.isEmpty()) {
|
||||||
|
this.statusMessage = Component.translatable("forged_carpet_tool.config.no_changes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketDistributor.sendToServer(new UpdateServerConfigPacket(changedEntries));
|
||||||
|
this.statusMessage = Component.translatable("forged_carpet_tool.config.saved");
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
this.statusMessage = Component.literal(exception.getMessage()).withStyle(ChatFormatting.RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canEditConfig() {
|
||||||
|
var connection = Minecraft.getInstance().getConnection();
|
||||||
|
return connection != null && connection.hasChannel(UpdateServerConfigPacket.TYPE)
|
||||||
|
&& ConfigState.canEditServerConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ConfigEntryValue> collectChangedEntries() {
|
||||||
|
List<ConfigEntryValue> changedEntries = new ArrayList<>();
|
||||||
|
for (EntryRow row : this.rows) {
|
||||||
|
Object currentValue = row.readValue();
|
||||||
|
Object syncedValue = ConfigState.getValue(row.definition.id());
|
||||||
|
Object baseValue = syncedValue != null ? syncedValue : row.definition.defaultValue();
|
||||||
|
if (!Objects.equals(currentValue, baseValue)) {
|
||||||
|
changedEntries.add(new ConfigEntryValue(row.definition.id(), currentValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changedEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntryRow createRow(ServerConfig.EntryDefinition definition) {
|
||||||
|
Object currentValue = ConfigState.getValue(definition.id());
|
||||||
|
if (currentValue == null) {
|
||||||
|
currentValue = definition.defaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryRow row = new EntryRow(definition);
|
||||||
|
row.widget = switch (definition.controlType()) {
|
||||||
|
case TOGGLE -> this.addRenderableWidget(this.createToggle(row, currentValue));
|
||||||
|
case SLIDER -> this.addRenderableWidget(this.createSlider(row, currentValue));
|
||||||
|
case TEXT -> this.addRenderableWidget(this.createTextBox(definition, currentValue));
|
||||||
|
};
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button createToggle(EntryRow row, Object currentValue) {
|
||||||
|
boolean initialValue = currentValue instanceof Boolean bool ? bool : (Boolean) row.definition.defaultValue();
|
||||||
|
row.booleanValue = initialValue;
|
||||||
|
return Button.builder(booleanLabel(initialValue), button -> {
|
||||||
|
row.booleanValue = !row.booleanValue;
|
||||||
|
button.setMessage(booleanLabel(row.booleanValue));
|
||||||
|
}).bounds(0, 0, this.getControlWidth(), 20).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractSliderButton createSlider(EntryRow row, Object currentValue) {
|
||||||
|
double minValue = row.definition.minValue() == null ? 0.0D : row.definition.minValue();
|
||||||
|
double maxValue = row.definition.maxValue() == null ? 1.0D : row.definition.maxValue();
|
||||||
|
double initialNumber = currentValue instanceof Number number
|
||||||
|
? number.doubleValue()
|
||||||
|
: ((Number) row.definition.defaultValue()).doubleValue();
|
||||||
|
return new ValueSlider(0, 0, this.getControlWidth(), 20, row.definition, minValue, maxValue, initialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EditBox createTextBox(ServerConfig.EntryDefinition definition, Object currentValue) {
|
||||||
|
EditBox input = new EditBox(this.font, 0, 0, this.getControlWidth(), 20, Component.translatable(definition.titleKey()));
|
||||||
|
input.setEditable(this.canEditConfig());
|
||||||
|
input.setMaxLength(256);
|
||||||
|
input.setValue(String.valueOf(currentValue));
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEntryLayout() {
|
||||||
|
int controlLeft = this.getControlLeft();
|
||||||
|
int listTop = this.getListTop();
|
||||||
|
int listBottom = this.getListBottom();
|
||||||
|
int contentHeight = Math.max(0, this.getContentHeight());
|
||||||
|
int viewportHeight = Math.max(0, listBottom - listTop);
|
||||||
|
|
||||||
|
this.maxScroll = Math.max(0, contentHeight - viewportHeight);
|
||||||
|
if (this.scrollOffset > this.maxScroll) {
|
||||||
|
this.scrollOffset = this.maxScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index = 0; index < this.rows.size(); index++) {
|
||||||
|
EntryRow row = this.rows.get(index);
|
||||||
|
int rowTop = listTop + this.getRowOffset(index) - this.scrollOffset;
|
||||||
|
row.rowTop = rowTop;
|
||||||
|
|
||||||
|
row.widget.setX(controlLeft);
|
||||||
|
row.widget.setY(rowTop + ROW_PADDING);
|
||||||
|
|
||||||
|
int rowHeight = this.getRowHeight(row.definition);
|
||||||
|
boolean visible = rowTop >= listTop - rowHeight && rowTop + rowHeight <= listBottom + rowHeight;
|
||||||
|
row.widget.visible = visible;
|
||||||
|
row.widget.active = visible && this.canEditConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getContentLeft() {
|
||||||
|
return SIDE_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getControlLeft() {
|
||||||
|
return this.width - SIDE_PADDING - this.getControlWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getListTop() {
|
||||||
|
return TOP_MARGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getListBottom() {
|
||||||
|
return this.height - BOTTOM_BAR_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getTextWidth() {
|
||||||
|
return Math.max(80, this.getControlLeft() - this.getContentLeft() - COLUMN_GAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getControlWidth() {
|
||||||
|
int dynamicWidth = Math.max(MIN_CONTROL_WIDTH, this.width / 3);
|
||||||
|
return Math.min(MAX_CONTROL_WIDTH, dynamicWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getContentHeight() {
|
||||||
|
int totalHeight = 0;
|
||||||
|
for (EntryRow row : this.rows) {
|
||||||
|
totalHeight += this.getRowHeight(row.definition);
|
||||||
|
}
|
||||||
|
return totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getRowOffset(int index) {
|
||||||
|
int offset = 0;
|
||||||
|
for (int i = 0; i < index; i++) {
|
||||||
|
offset += this.getRowHeight(this.rows.get(i).definition);
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getRowHeight(ServerConfig.EntryDefinition definition) {
|
||||||
|
int titleLines = this.font.split(Component.translatable(definition.titleKey()), this.getTextWidth()).size();
|
||||||
|
int subtitleLines = this.font.split(Component.translatable(definition.subtitleKey()), this.getTextWidth()).size();
|
||||||
|
int textHeight = Math.max(1, titleLines) * 9 + Math.max(1, subtitleLines) * 9;
|
||||||
|
return Math.max(38, textHeight + 2 * ROW_PADDING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Component booleanLabel(boolean value) {
|
||||||
|
return Component.translatable(
|
||||||
|
value ? "forged_carpet_tool.config.boolean.true" : "forged_carpet_tool.config.boolean.false"
|
||||||
|
).withStyle(value ? ChatFormatting.GREEN : ChatFormatting.RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseInteger(EditBox input, String titleKey) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(input.getValue());
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
throw new IllegalArgumentException(Component.translatable("forged_carpet_tool.config.invalid_int", Component.translatable(titleKey)).getString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long parseLong(EditBox input, String titleKey) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(input.getValue());
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
throw new IllegalArgumentException(Component.translatable("forged_carpet_tool.config.invalid_long", Component.translatable(titleKey)).getString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double parseDouble(EditBox input, String titleKey) {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(input.getValue());
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
throw new IllegalArgumentException(Component.translatable("forged_carpet_tool.config.invalid_double", Component.translatable(titleKey)).getString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class EntryRow {
|
||||||
|
private final ServerConfig.EntryDefinition definition;
|
||||||
|
private AbstractWidget widget;
|
||||||
|
private boolean booleanValue;
|
||||||
|
private int rowTop;
|
||||||
|
|
||||||
|
private EntryRow(ServerConfig.EntryDefinition definition) {
|
||||||
|
this.definition = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readValue() {
|
||||||
|
return switch (this.definition.controlType()) {
|
||||||
|
case TOGGLE -> this.booleanValue;
|
||||||
|
case SLIDER -> ((ValueSlider) this.widget).readValue();
|
||||||
|
case TEXT -> switch (this.definition.valueType()) {
|
||||||
|
case STRING -> ((EditBox) this.widget).getValue();
|
||||||
|
case INTEGER -> parseInteger((EditBox) this.widget, this.definition.titleKey());
|
||||||
|
case LONG -> parseLong((EditBox) this.widget, this.definition.titleKey());
|
||||||
|
case DOUBLE -> parseDouble((EditBox) this.widget, this.definition.titleKey());
|
||||||
|
case BOOLEAN -> throw new IllegalStateException("Boolean values must use toggle controls");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ValueSlider extends AbstractSliderButton {
|
||||||
|
private final ServerConfig.EntryDefinition definition;
|
||||||
|
private final double minValue;
|
||||||
|
private final double maxValue;
|
||||||
|
|
||||||
|
private ValueSlider(
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
ServerConfig.EntryDefinition definition,
|
||||||
|
double minValue,
|
||||||
|
double maxValue,
|
||||||
|
double initialValue
|
||||||
|
) {
|
||||||
|
super(x, y, width, height, CommonComponents.EMPTY, normalize(initialValue, minValue, maxValue));
|
||||||
|
this.definition = definition;
|
||||||
|
this.minValue = minValue;
|
||||||
|
this.maxValue = maxValue;
|
||||||
|
this.updateMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateMessage() {
|
||||||
|
this.setMessage(Component.literal(this.formatValue(this.readValue())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void applyValue() {
|
||||||
|
this.updateMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readValue() {
|
||||||
|
double value = this.minValue + (this.maxValue - this.minValue) * this.value;
|
||||||
|
return switch (this.definition.valueType()) {
|
||||||
|
case INTEGER -> (int) Math.round(value);
|
||||||
|
case LONG -> Math.round(value);
|
||||||
|
case DOUBLE -> value;
|
||||||
|
case BOOLEAN, STRING -> throw new IllegalStateException("Slider is only valid for numeric config entries");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatValue(Object value) {
|
||||||
|
return switch (this.definition.valueType()) {
|
||||||
|
case INTEGER -> Integer.toString((Integer) value);
|
||||||
|
case LONG -> Long.toString((Long) value);
|
||||||
|
case DOUBLE -> String.format("%.2f", (Double) value);
|
||||||
|
case BOOLEAN, STRING -> value.toString();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double normalize(double value, double minValue, double maxValue) {
|
||||||
|
if (maxValue <= minValue) {
|
||||||
|
return 0.0D;
|
||||||
|
}
|
||||||
|
return Math.max(0.0D, Math.min(1.0D, (value - minValue) / (maxValue - minValue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.config;
|
||||||
|
import cn.disy920.forged_carpet_tool.ForgedCarpetToolMod;
|
||||||
|
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.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
import java.util.List;
|
||||||
|
public record SyncServerConfigPacket(boolean replaceAll, List<ConfigEntryValue> entries, boolean canEdit) implements CustomPacketPayload {
|
||||||
|
public static final Type<SyncServerConfigPacket> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ForgedCarpetToolMod.MOD_ID, "config_sync"));
|
||||||
|
public static final StreamCodec<FriendlyByteBuf, SyncServerConfigPacket> CODEC = StreamCodec.of(
|
||||||
|
(buf, packet) -> {
|
||||||
|
buf.writeBoolean(packet.replaceAll);
|
||||||
|
buf.writeCollection(packet.entries, ConfigEntryValue::encode);
|
||||||
|
buf.writeBoolean(packet.canEdit);
|
||||||
|
},
|
||||||
|
buf -> new SyncServerConfigPacket(buf.readBoolean(), ConfigEntryValue.readEntries(buf), buf.readBoolean()));
|
||||||
|
public Type<? extends CustomPacketPayload> type() { return TYPE; }
|
||||||
|
public static void handle(SyncServerConfigPacket packet, IPayloadContext context) {
|
||||||
|
if (packet.replaceAll) ConfigState.replaceValues(packet.entries);
|
||||||
|
else ConfigState.applyUpdates(packet.entries);
|
||||||
|
ConfigState.setCanEditServerConfig(packet.canEdit);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.mixin.avoid_chat_out_of_order;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import static cn.disy920.forged_carpet_tool.config.ServerConfig.AVOID_CHAT_OUT_OF_ORDER;
|
||||||
|
|
||||||
|
@Mixin(targets = "net.minecraft.network.chat.SignedMessageChain$1")
|
||||||
|
public class MixinServerGamePacketListener {
|
||||||
|
@Redirect(
|
||||||
|
method = "unpack",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Ljava/time/Instant;isBefore(Ljava/time/Instant;)Z"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
private boolean avoidOutOfOrder(Instant timestamp, Instant previousTimestamp) {
|
||||||
|
return !AVOID_CHAT_OUT_OF_ORDER.get() && timestamp.isBefore(previousTimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.mixin.max_portal;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.portal.PortalShape;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Constant;
|
||||||
|
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||||
|
|
||||||
|
import static cn.disy920.forged_carpet_tool.config.ServerConfig.NETHER_PORTAL_MAX_SIZE;
|
||||||
|
|
||||||
|
@Mixin(PortalShape.class)
|
||||||
|
public class MixinPortalShape {
|
||||||
|
@ModifyConstant(
|
||||||
|
method = "calculateBottomLeft",
|
||||||
|
constant = @Constant(intValue = 21)
|
||||||
|
)
|
||||||
|
private int modifyBottomLeft(int value) {
|
||||||
|
return NETHER_PORTAL_MAX_SIZE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyConstant(
|
||||||
|
method = "calculateWidth",
|
||||||
|
constant = @Constant(intValue = 21)
|
||||||
|
)
|
||||||
|
private int modifyWidth(int value) {
|
||||||
|
return NETHER_PORTAL_MAX_SIZE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyConstant(
|
||||||
|
method = "getDistanceUntilEdgeAboveFrame",
|
||||||
|
constant = @Constant(intValue = 21)
|
||||||
|
)
|
||||||
|
private int modifyDistanceUntilEdgeAboveFrame(int value) {
|
||||||
|
return NETHER_PORTAL_MAX_SIZE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyConstant(
|
||||||
|
method = "calculateHeight",
|
||||||
|
constant = @Constant(intValue = 21)
|
||||||
|
)
|
||||||
|
private int modifyHeight(int value) {
|
||||||
|
return NETHER_PORTAL_MAX_SIZE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyConstant(
|
||||||
|
method = "getDistanceUntilTop",
|
||||||
|
constant = @Constant(intValue = 21)
|
||||||
|
)
|
||||||
|
private int modifyDistanceUntilTop(int value) {
|
||||||
|
return NETHER_PORTAL_MAX_SIZE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModifyConstant(
|
||||||
|
method = "isValid",
|
||||||
|
constant = @Constant(intValue = 21)
|
||||||
|
)
|
||||||
|
private int modifyIsValid(int value) {
|
||||||
|
return NETHER_PORTAL_MAX_SIZE.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.mixin.update_suppress;
|
||||||
|
|
||||||
|
import cn.disy920.forged_carpet_tool.update_suppression.UpdateSuppressedException;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.tags.BlockTags;
|
||||||
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import static cn.disy920.forged_carpet_tool.config.ServerConfig.ENABLE_SAFETY_UPDATE_SUPPRESSION;
|
||||||
|
|
||||||
|
@Mixin(BlockBehaviour.BlockStateBase.class)
|
||||||
|
public class MixinBlockStateBase {
|
||||||
|
@Redirect(
|
||||||
|
method = "updateNeighbourShapes(Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;II)V",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/world/level/LevelAccessor;neighborShapeChanged(Lnet/minecraft/core/Direction;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;II)V"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
private void suppressUpdate(LevelAccessor level, Direction direction, BlockState state,
|
||||||
|
BlockPos targetPos, BlockPos sourcePos, int flags, int recursionLeft) {
|
||||||
|
if (!level.isClientSide() && ENABLE_SAFETY_UPDATE_SUPPRESSION.get()
|
||||||
|
&& level.getBlockState(targetPos).is(Blocks.NETHERITE_BLOCK)
|
||||||
|
&& level.getBlockState(targetPos.above()).is(BlockTags.TRAPDOORS)) {
|
||||||
|
throw new UpdateSuppressedException();
|
||||||
|
}
|
||||||
|
level.neighborShapeChanged(direction, state, targetPos, sourcePos, flags, recursionLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.mixin.update_suppress;
|
||||||
|
|
||||||
|
import cn.disy920.forged_carpet_tool.update_suppression.UpdateSuppressedException;
|
||||||
|
import net.minecraft.ReportedException;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
@Mixin(MinecraftServer.class)
|
||||||
|
public class MixinServer {
|
||||||
|
@Redirect(
|
||||||
|
method = "tickServer",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/server/MinecraftServer;tickChildren(Ljava/util/function/BooleanSupplier;)V"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
public void yeetSuppressionException(MinecraftServer instance, BooleanSupplier throwable) {
|
||||||
|
try {
|
||||||
|
instance.tickChildren(throwable);
|
||||||
|
}
|
||||||
|
catch (UpdateSuppressedException e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
catch (ReportedException e) {
|
||||||
|
if (!(e.getCause() instanceof UpdateSuppressedException)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.mixin.void_death;
|
||||||
|
|
||||||
|
import cn.disy920.forged_carpet_tool.ModEvents;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Player.class)
|
||||||
|
public abstract class MixinPlayer {
|
||||||
|
@Inject(method = "dropEquipment", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void forgedCarpetTool$keepVoidInventory(CallbackInfo ci) {
|
||||||
|
if ((Object) this instanceof ServerPlayer player
|
||||||
|
&& player.getPersistentData().getBoolean(ModEvents.KEEP_VOID_INVENTORY)) {
|
||||||
|
// Preserve the inventory before vanilla destroys vanishing items or drops stacks.
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package cn.disy920.forged_carpet_tool.update_suppression;
|
||||||
|
|
||||||
|
public class UpdateSuppressedException extends RuntimeException {
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
modLoader="javafml"
|
||||||
|
loaderVersion="[4,)"
|
||||||
|
license="${mod_license}"
|
||||||
|
[[mods]]
|
||||||
|
modId="${mod_id}"
|
||||||
|
version="${mod_version}"
|
||||||
|
displayName="${mod_name}"
|
||||||
|
authors="${mod_authors}"
|
||||||
|
description='''Server gameplay tools with live configuration.'''
|
||||||
|
[[mixins]]
|
||||||
|
config="forged_carpet_tool.mixins.json"
|
||||||
|
[[dependencies.${mod_id}]]
|
||||||
|
modId="neoforge"
|
||||||
|
type="required"
|
||||||
|
versionRange="[21.1.209,21.2)"
|
||||||
|
ordering="NONE"
|
||||||
|
side="BOTH"
|
||||||
|
[[dependencies.${mod_id}]]
|
||||||
|
modId="minecraft"
|
||||||
|
type="required"
|
||||||
|
versionRange="[1.21.1,1.21.2)"
|
||||||
|
ordering="NONE"
|
||||||
|
side="BOTH"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"forged_carpet_tool.config.command.save_failed": "Failed to save config. Check the server log.",
|
||||||
|
"forged_carpet_tool.config.feature.keep_inventory_on_void_death.title": "Keep Inventory on Void Death",
|
||||||
|
"forged_carpet_tool.config.feature.keep_inventory_on_void_death.subtitle": "Keep inventory, armor and offhand items (including vanishing items) on death from void damage and restore them on respawn. Experience follows vanilla rules.",
|
||||||
|
"forged_carpet_tool.config.title": "Server Config",
|
||||||
|
"forged_carpet_tool.config.save": "Save",
|
||||||
|
"forged_carpet_tool.config.description": "Edit server config entries here. Changes apply immediately after saving.",
|
||||||
|
"forged_carpet_tool.config.permission.op": "You have OP permission and can modify the server config.",
|
||||||
|
"forged_carpet_tool.config.permission.read_only": "Read only: only players with OP permission can modify the server config.",
|
||||||
|
"forged_carpet_tool.config.empty": "There are no config entries to display yet.",
|
||||||
|
"forged_carpet_tool.config.saved": "The updated config was sent to the server.",
|
||||||
|
"forged_carpet_tool.config.no_changes": "There are no config changes to sync.",
|
||||||
|
"forged_carpet_tool.config.boolean.true": "Enabled",
|
||||||
|
"forged_carpet_tool.config.boolean.false": "Disabled",
|
||||||
|
"forged_carpet_tool.config.invalid_int": "%s is not a valid integer",
|
||||||
|
"forged_carpet_tool.config.invalid_long": "%s is not a valid long",
|
||||||
|
"forged_carpet_tool.config.invalid_double": "%s is not a valid decimal number",
|
||||||
|
"forged_carpet_tool.config.creative.enable_safety_update_suppression.title": "Enable Safety Update Suppression",
|
||||||
|
"forged_carpet_tool.config.creative.enable_safety_update_suppression.subtitle": "When enabled, place any trapdoor above the corresponding lower bound alloy block. When the block update chain is transmitted to the corresponding lower bound alloy block, the block update chain will be suppressed.",
|
||||||
|
"forged_carpet_tool.config.creative.nether_portal_max_size.title": "Nether Portal Max Size",
|
||||||
|
"forged_carpet_tool.config.creative.nether_portal_max_size.subtitle": "The maximum size of nether portals, in blocks. Please note: excessively large portals may cause performance issues.",
|
||||||
|
"forged_carpet_tool.config.feature.avoid_chat_out_of_order.title": "Avoid Chat Message Out-of-Order Error",
|
||||||
|
"forged_carpet_tool.config.feature.avoid_chat_out_of_order.subtitle": "When enabled, the server will automatically ignore the detection of chat message out-of-order errors, thus preventing players from being kicked from the server due to this error."
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"forged_carpet_tool.config.command.save_failed": "配置保存失败,请检查服务端日志。",
|
||||||
|
"forged_carpet_tool.config.feature.keep_inventory_on_void_death.title": "虚空死亡保留背包",
|
||||||
|
"forged_carpet_tool.config.feature.keep_inventory_on_void_death.subtitle": "因虚空伤害死亡时保留物品栏、盔甲和副手(含消失诅咒物品),重生后恢复。经验仍按原版处理。",
|
||||||
|
"forged_carpet_tool.config.title": "服务端配置",
|
||||||
|
"forged_carpet_tool.config.save": "保存",
|
||||||
|
"forged_carpet_tool.config.description": "在这里直接编辑服务端配置项,保存后立即生效。",
|
||||||
|
"forged_carpet_tool.config.permission.op": "你拥有 OP 权限,可以修改服务端配置。",
|
||||||
|
"forged_carpet_tool.config.permission.read_only": "当前为只读状态,只有拥有 OP 权限的玩家可以修改服务端配置。",
|
||||||
|
"forged_carpet_tool.config.empty": "当前还没有可显示的配置项。",
|
||||||
|
"forged_carpet_tool.config.saved": "配置已发送到服务端。",
|
||||||
|
"forged_carpet_tool.config.no_changes": "没有需要同步的配置变更。",
|
||||||
|
"forged_carpet_tool.config.boolean.true": "启用",
|
||||||
|
"forged_carpet_tool.config.boolean.false": "禁用",
|
||||||
|
"forged_carpet_tool.config.invalid_int": "%s 不是有效的整数",
|
||||||
|
"forged_carpet_tool.config.invalid_long": "%s 不是有效的长整数",
|
||||||
|
"forged_carpet_tool.config.invalid_double": "%s 不是有效的小数",
|
||||||
|
"forged_carpet_tool.config.creative.enable_safety_update_suppression.title": "启用安全更新抑制器",
|
||||||
|
"forged_carpet_tool.config.creative.enable_safety_update_suppression.subtitle": "启用后,将任意活板门放置于下界合金块上方,在方块更新链传递到对应下界合金块时,整条更新链会被抑制。",
|
||||||
|
"forged_carpet_tool.config.creative.nether_portal_max_size.title": "下界传送门最大尺寸",
|
||||||
|
"forged_carpet_tool.config.creative.nether_portal_max_size.subtitle": "下界传送门的最大尺寸,单位为方块。请注意:过大的传送门可能会导致性能问题。",
|
||||||
|
"forged_carpet_tool.config.feature.avoid_chat_out_of_order.title": "忽略聊天消息乱序错误",
|
||||||
|
"forged_carpet_tool.config.feature.avoid_chat_out_of_order.subtitle": "启用后,服务器将自动忽略对聊天消息乱序错误的检测,从而避免因该错误被踢出服务器。"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "cn.disy920.forged_carpet_tool.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_21",
|
||||||
|
"mixins": [
|
||||||
|
"void_death.MixinPlayer",
|
||||||
|
"avoid_chat_out_of_order.MixinServerGamePacketListener",
|
||||||
|
"max_portal.MixinPortalShape",
|
||||||
|
"update_suppress.MixinBlockStateBase",
|
||||||
|
"update_suppress.MixinServer"
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
],
|
||||||
|
"server": [
|
||||||
|
],
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
},
|
||||||
|
"overwrites": {
|
||||||
|
"requireAnnotations": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"pack": {
|
||||||
|
"description": "forged_carpet_tool resources",
|
||||||
|
"pack_format": 34
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user