Initial commit

This commit is contained in:
ShadowNetReal
2026-07-23 18:21:37 -04:00
commit a35a0892f6
81 changed files with 613418 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
version: 1
reviews:
skip_bots: true
+9
View File
@@ -0,0 +1,9 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
+4
View File
@@ -0,0 +1,4 @@
build/
.gradle/
bin/
.idea/
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 TuffNetwork
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+78
View File
@@ -0,0 +1,78 @@
# TuffXPlus
> [!WARNING]
> This is not a "crack" for Minecraft, it simply allows for better TuffClient & ShadowNetClient integration on servers.
![preview](./img/showcase.png)
> [!WARNING]
> > This version of TuffXPlus is not maintained by the Creators of the original TuffXPlus and is an optimized version by @ryan_norshare which uses way less memory.
TuffX+ is a single, unified plugin that combines:
- Below Y0 *[previousely TuffX]* (Below Y0 support for modern world depth)
- ViaBlocks (custom block palette + chunk updates for modern blocks)
- ViaEntities (modern entity sync without nametags)
- TuffActions (swimming sync + creative item handling + module restrictions)
---
## Installation
### Requirements
- Java 17+
- Spigot/Paper 1.18+
- [ViaVersion](https://hangar.papermc.io/ViaVersion/ViaVersion) and [ViaBackwards](https://hangar.papermc.io/ViaVersion/ViaBackwards) (on the *backend* server *only*; not proxy)
> PacketEvents, Jackson, and WebSocket libraries are shaded into the jar.
*DO NOT* install the old TuffX plugin; *only* this one
It is highly recommended to install [AxSmithing](https://hangar.papermc.io/Artillex-Studios/AxSmithing) for smithing table support (not specific to TuffClient).
### Steps
1. Download the latest release from the [Releases page](https://github.com/TuffNetwork/TuffXPlus/releases) (or the latest beta from [the builds directory](builds)).
2. Drop `TuffXPlus-x.x.x.jar` into your server's `plugins` folder.
3. Start the server to generate `plugins/TuffXPlus/config.yml`.
4. Configure features in `config.yml` (`y0`, `registry`, `viablocks`, `viaentities`, `swimming`, `creative-items`, `restrictions`).
5. Restart the server or run `/tuffx reload`.
---
## Features
- Below Y0: sends extra chunk data for Y < 0 so TuffX client can see and interact with modern world depth.
- ViaBlocks: synchronizes modern block states to TuffX client with a custom palette.
- ViaEntities: syncs modern entities to TuffX client without nametags.
- TuffActions: swimming state sync and creative item handling.
- Restrictions: disallow TuffClient modules: [module list](/docs/restrictions.md)
- Optional server registry over WebSocket (for discovery).
## Commands
- `/tuffx reload` - reload the config
- `/viablocks get` - give a set of custom blocks (creative)
- `/viablocks refresh` - resend ViaBlocks data in view distance
- `/restrictions disallow` - add a module to the disallow list and send an update to all TuffClient clients
- `/restrictions allow` - remove a module from the disallow list and send an update
## Permissions
- `tuffx.reload`
- `tuffx.viablocks.command.get`
- `tuffx.viablocks.command.refresh`
- `tuffx.restrictions.command.disallow`
- `tuffx.restrictions.command.allow`
## Compiling
```sh
git clone https://github.com/TuffNetwork/TuffXPlus.git
cd TuffXPlus
./gradlew build
```
Output jar: `build/libs/TuffXPlus-x.x.x.jar`.
## Support
For general issues, [make an issue here](https://github.com/TuffNetwork/TuffXPlus/issues)
For immediate issues, [join our Discord](https://discord.gg/keWXRC9Jd8) and make a ticket in `#🎫tuff-tickets` with 'Client' type.
+101
View File
@@ -0,0 +1,101 @@
plugins {
id 'java'
id 'com.gradleup.shadow' version '9.4.1'
}
group = 'tf.tuff'
version = '1.1.1'
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
}
repositories {
mavenCentral()
maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
maven { url = 'https://repo.viaversion.com/everything/' }
maven { url = 'https://repo.codemc.io/repository/maven-releases/' }
maven { url = 'https://repo.codemc.io/repository/maven-snapshots/' }
maven { url = 'https://jitpack.io' }
maven { url = 'https://repo.dmulloy2.net/repository/public/' }
}
dependencies {
compileOnly 'org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT'
implementation 'com.github.retrooper:packetevents-spigot:2.13.0'
compileOnly 'com.viaversion:viabackwards:5.9.1'
compileOnly 'com.viaversion:viaversion:5.9.1'
compileOnly 'it.unimi.dsi:fastutil:8.5.18'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.22.0'
compileOnly 'io.netty:netty-all:4.2.15.Final'
implementation 'org.java-websocket:Java-WebSocket:1.6.0'
compileOnly 'org.projectlombok:lombok:1.18.46'
annotationProcessor 'org.projectlombok:lombok:1.18.46'
// Tests
testImplementation 'com.github.seeseemelk:MockBukkit-v1.18:2.85.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'
testRuntimeOnly 'org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT'
testRuntimeOnly 'io.netty:netty-all:4.2.15.Final'
testRuntimeOnly 'it.unimi.dsi:fastutil:8.5.18'
testRuntimeOnly 'com.viaversion:viaversion:5.9.1'
testRuntimeOnly 'com.viaversion:viabackwards:5.9.1'
}
processResources {
filesMatching('plugin.yml') {
expand(
'version': project.version,
'name': project.name
)
}
}
shadowJar {
archiveClassifier.set('')
archiveFileName.set("${project.name}-${project.version}.jar")
relocate 'com.github.retrooper.packetevents', 'tf.tuff.packetevents'
relocate 'io.github.retrooper.packetevents', 'tf.tuff.packetevents'
relocate 'com.fasterxml.jackson', 'tf.tuff.jackson'
relocate 'org.java_websocket', 'tf.tuff.websocket'
exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
exclude 'META-INF/*.RSA'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/versions/**'
exclude 'module-info.class'
}
tasks.named('jar') {
enabled = false
}
tasks.named('build') {
dependsOn shadowJar
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat = "full"
}
}
+3
View File
@@ -0,0 +1,3 @@
@echo off
call gradlew build
pause
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+115
View File
@@ -0,0 +1,115 @@
# Restrictions
Restrictions can be configured in this plugin's `config.yaml` You may also use `/restrictions disallow <moduleSlug>` to add a module to the disallow list. Use `/restrictions allow <moduleSlug>` to remove it again and allow players to use that module.
**Most client TuffClient versions require all module slugs to be __all lowercase__.**
---
## Exploit Discovered - it is recommended to always restrict these
- `clientbrand` - Old version of `clientbrander` - This version still displayed the brand players who were invisible.
---
## Possible PVP or Progression Advantage
- `quickelytra` - Automatically use elytra when using a firework
- `fastcrystals` - Instantly removes end crystals client-side when attacked (allows faster spam - counteracts bad ping)
- `anchoroptimizer` - Predicts Respawn Anchor explosions client-side.
- `hotbaroptimizer` - Instantly syncs hotbar slot selection to the server
- `hotbar_switcher` - Hotbar Switcher switch rows with (key) + (num).
- `fullbright` - Maximizes in-game brightness.
- `tnt` - Adds a timer above TNT to show the amount of time before explosion
- `antipickup` - Auto drops blacklisted items
- `zoom` - Zoom in by hitting the keybind you set in Controls (Default to Z)
- `glowingores` - Makes ores glow
- `rangecrosshair` - Turns crosshair red if an entity is in hitting range
- `betterhitboxes` - Turns hitbox a different color if the entity can be hit
- `minimap` - Enables a minimap when you are moving
## Visual cleanup
- `cpvpmode` - Removes fire effects, smoke, and crystal explosion particles
- `noexplosionparticles` - Removes explosion particles for better visibility
- `norain` - Disables rain particles
- `noglint` - Disables enchant glint for items
- `nodynamicfov` - Removes Dynamic FOV
- `smalltools` - Renders held tools smaller in first-person.
- `noeffect` - Removes particles from rendering
- `nohurtcam` - Removes the Hurt Shake
- `nodeathanimation` - Removes the death animation tilt on entities
- `chatclear` - Makes chat background to be clear
- `nobackgroundtint` - Disables background tint in-game
- `lowonfire` - Adjust the size of the fire overlay in your GUI!
## Gameplay Practice/Information
- `wtaptrainer` - Practice W-tapping timing without modifying combat
- `pvptracker` - Tracks your PVP stats.
- `cps` - Displays your clicks per second.
- `speed` - Shows your current movement speed
- `reach` - Shows how many blocks away whatever entity you hit is
- `combocounter` - Show current combo count
- `shieldstatus` - Colors shields based on their state
- `totem_counter` - Tells you how many totems you have in your inventory
- `fps` - Displays your FPS
- `armorhud` - Shows the Armor HUD, and warns you if it has low durability.
- `speedrun_timer` - Lets you start a timer as soon as you enter the world
## Utility
- `sprinttoggle` - Toggle your Sprinting
- `shifttoggle` - Toggle your Sneaking
- `autologin` - Automatically logs you in
- `tabcomplete` - Enable the modern chat completion UI.
- `worldeditcui` - Shows WorldEdit selections in-game.
- `autogg` - Sends 'gg' whenever the game ends
- `fancy_hover_block` - Changes the color of the block hovering to a gradient
## Other Information
- `clientbrander` - Shows what client other Eaglercraft players are using above their name
- `appleskin` - The Appleskin Mod!
- `compass` - Shows a compass on the HUD.
- `inventoryHUD` - Displays your inventory items on screen
- `keystrokes` - Shows Keystrokes
- `WAILA` - Displays what block you are looking at.
- `potions` - Shows active effects
- `shulker` - Shows shulker box contents
- `chatheads` - Renders player heads next to their chat messages
---
## Other Modules (Not recommended to disallow)
### In-Game Customization
- `crosshair` - Customize your crosshair!
- `waveycapes` - Capes, but wavey
- `mace3d` - Renders the mace item as a 3D model
- `braysbow` - Custom bow and arrow models
### Backport
**y0, ViaBlocks, and ViaEntities have separate configuration within the plugin**
- `viaviewer` - Allows viewing new items on servers with newer versions (100% client-side)
- `viablocks` - Allows viewing new placed blocks on servers with newer versions
- `y0` - Allows below y0 support - disabling this ignores y0 packets
- `viaviewerentity` - Allows viewing new entities on servers with newer versions
### Performance
- `vanillafix_chunk` - Speeds up chunk unloading (possible issues, may require rejoining)
- `ldm` - Reduces lag in areas that are not needed. Prevents Memory Leaks on low-end devices
### Non-game GUIs
- `listlayout` - Swap between list and grid layouts
- `teto_mode` - Joke theme lmao - Kasane Teto
- `debug` - Debug mode
- `sodiumUI` - Switches to sodium video settings UI.
- `minecraftgui` - Toggles between Tuff UI and Minecraft UI
- `movingBG` - Toggles moving the main menu background based on mouse movement
---
### Old modules
- `smalltotems` - Renders held totems smaller in first-person
- `durability` - Shows your Armor's Durability on the hotbar
- `colorful_containers` - Makes Container GUIs colorful
- `enhanced_hotbar` - Makes your healthbar icons like Hunger and Health prettier
- `healthbar` - Displays other entities' health in a healthbar above their name.
- `streamer` - Hides mentions of your XYZ coordinates
- `widgets` - Modifies hotbar/buttons/minecraft GUI related
- `fastmath` - Speeds up FPS by improving math functions
Binary file not shown.
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# 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/3d91ce3b8caaf77ad09f381f43615b715b53f72c/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
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
# 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" )
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, 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" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# 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
+82
View File
@@ -0,0 +1,82 @@
@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
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
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
"%COMSPEC%" /c exit 1
: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
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

+1
View File
@@ -0,0 +1 @@
rootProject.name = 'TuffXPlus'
@@ -0,0 +1,32 @@
package tf.tuff;
import com.github.retrooper.packetevents.event.PacketListener;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData;
import org.bukkit.World;
import org.bukkit.entity.Player;
public class NetworkListener implements PacketListener {
private final TuffX plugin;
public NetworkListener(TuffX plugin) {
this.plugin = plugin;
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.CHUNK_DATA) {
Player player = (Player) event.getPlayer();
if (player == null) return;
WrapperPlayServerChunkData wrapper = new WrapperPlayServerChunkData(event);
int chunkX = wrapper.getColumn().getX();
int chunkZ = wrapper.getColumn().getZ();
World world = player.getWorld();
plugin.y0Plugin.chunkPacketListener.handleChunk(plugin, player, world, chunkX, chunkZ);
}
}
}
+68
View File
@@ -0,0 +1,68 @@
package tf.tuff;
import org.bukkit.plugin.java.JavaPlugin;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import tf.tuff.util.SchedulerCompat;
public class ServerRegistry {
private final JavaPlugin p;
private final String wsUrl;
private final String server;
private WebSocketClient client;
private volatile boolean running = true;
public ServerRegistry(JavaPlugin pl, String registryUrl, String serverAddr) {
p = pl;
wsUrl = registryUrl;
server = serverAddr;
}
public void connect() {
SchedulerCompat.runAsync(p, this::doConnect);
}
private void doConnect() {
if (!running)
return;
try {
client = new WebSocketClient(new URI(wsUrl)) {
@Override
public void onOpen(ServerHandshake h) {
send("{\"type\":\"register\",\"server\":\"" + server + "\"}");
}
@Override
public void onMessage(String msg) {
}
@Override
public void onClose(int code, String reason, boolean remote) {
if (running) {
SchedulerCompat.runAsyncLater(p, ServerRegistry.this::doConnect, 100L);
}
}
@Override
public void onError(Exception e) {
}
};
client.setConnectionLostTimeout(30);
client.connect();
} catch (Exception e) {
if (running) {
SchedulerCompat.runAsyncLater(p, this::doConnect, 100L);
}
}
}
public void disconnect() {
running = false;
if (client != null) {
client.close();
}
}
}
+354
View File
@@ -0,0 +1,354 @@
package tf.tuff;
import java.io.File;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.EntityToggleSwimEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.plugin.messaging.PluginMessageListener;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import tf.tuff.netty.ChunkInjector;
import tf.tuff.tuffactions.TuffActions;
import tf.tuff.util.SchedulerCompat;
import tf.tuff.viablocks.ViaBlocksPlugin;
import tf.tuff.viaentities.ViaEntitiesPlugin;
import tf.tuff.y0.Y0Plugin;
public class TuffX extends JavaPlugin implements Listener, PluginMessageListener {
public ServerRegistry serverRegistry;
public Y0Plugin y0Plugin;
public ViaBlocksPlugin viaBlocksPlugin;
public TuffActions tuffActions;
public ViaEntitiesPlugin viaEntitiesPlugin;
private ChunkInjector chunkInjector;
private boolean packetEventsEnabled;
// required by MockBukkit
public TuffX(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) {
super(loader, description, dataFolder, file);
}
public TuffX() { super(); }
@Override
public void onLoad() {
this.y0Plugin = new Y0Plugin(this);
this.viaBlocksPlugin = new ViaBlocksPlugin(this);
this.tuffActions = new TuffActions(this);
this.viaEntitiesPlugin = new ViaEntitiesPlugin(this);
if (shouldBootstrapPacketEvents()) {
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this));
PacketEvents.getAPI().getSettings().reEncodeByDefault(false)
.checkForUpdates(false);
PacketEvents.getAPI().load();
packetEventsEnabled = true;
}
}
@Override
public void onEnable() {
if (packetEventsEnabled && PacketEvents.getAPI() != null) {
PacketEvents.getAPI().init();
} else {
packetEventsEnabled = false;
}
saveDefaultConfig();
getLogger().info(SchedulerCompat.isFolia()
? "Folia detected. Using region and entity schedulers."
: "Using standard Bukkit-compatible schedulers.");
y0Plugin.onTuffXEnable();
tuffActions.onTuffXEnable();
viaBlocksPlugin.onTuffXEnable();
viaEntitiesPlugin.onTuffXEnable();
chunkInjector = new ChunkInjector(viaBlocksPlugin.blockListener, y0Plugin);
viaBlocksPlugin.blockListener.setChunkInjector(chunkInjector);
y0Plugin.setChunkInjector(chunkInjector);
getConfig().options().copyDefaults(true);
saveConfig();
getServer().getScheduler().runTaskTimer(this, () -> {
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
long max = runtime.maxMemory();
double percent = (double) used / (double) max;
if (percent >= 0.80D) {
if (y0Plugin != null) {
y0Plugin.forceClearCache();
}
getLogger().warning("[TuffXPlus] Memory watchdog cleared Y0 cache. Used: " + (used / 1024L / 1024L) + "MB / " + (max / 1024L / 1024L) + "MB");
}
if (percent >= 0.95D) {
System.gc();
getLogger().warning("[TuffXPlus] Emergency GC requested.");
}
}, 20L * 60L, 20L * 60L);
if (packetEventsEnabled) {
PacketEvents.getAPI().getEventManager().registerListener(
new NetworkListener(this), PacketListenerPriority.NORMAL
);
}
getServer().getPluginManager().registerEvents(this, this);
setupRegistry();
lfe();
}
private void setupRegistry() {
if (getConfig().getBoolean("registry.enabled", false)) {
String url = getConfig().getString("registry.server-url");
String ws = getConfig().getString("registry.server");
if (ws != null && !ws.isEmpty() && !ws.equals("wss://urserverip.net")) {
serverRegistry = new ServerRegistry(this, url, ws);
serverRegistry.connect();
}
}
}
@Override
public void onDisable() {
y0Plugin.onTuffXDisable();
viaBlocksPlugin.onTuffXDisable();
viaEntitiesPlugin.onTuffXDisable();
if (serverRegistry != null) {
serverRegistry.disconnect();
serverRegistry = null;
}
if (packetEventsEnabled && PacketEvents.getAPI() != null) {
PacketEvents.getAPI().terminate();
}
packetEventsEnabled = false;
getServer().getMessenger().unregisterIncomingPluginChannel(this);
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
}
private boolean shouldBootstrapPacketEvents() {
return getServer() == null
|| !getServer().getClass().getName().startsWith("be.seeseemelk.mockbukkit");
}
public void reloadTuffX(){
saveDefaultConfig();
reloadConfig();
getConfig().options().copyDefaults(true);
saveConfig();
if (serverRegistry != null) {
serverRegistry.disconnect();
serverRegistry = null;
}
setupRegistry();
viaBlocksPlugin.onTuffXReload();
y0Plugin.onTuffXReload();
tuffActions.onTuffXReload();
viaEntitiesPlugin.onTuffXReload();
getLogger().info("TuffX reloaded.");
}
public boolean TuffXCommand(CommandSender sender, Command command, String label, String[] args){
if (args.length > 0) {
if (args[0].equalsIgnoreCase("reload")) {
if (!(sender instanceof Player)) {
reloadTuffX();
} else {
Player player = (Player) sender;
if (!player.hasPermission("tuffx.reload")) {
player.sendMessage("§cYou do not have permission to use this command.");
return false;
}
reloadTuffX();
player.sendMessage("TuffX reloaded.");
}
}
}
return true;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("tuffx")) return TuffXCommand(sender, command, label, args);
if (command.getName().equalsIgnoreCase("viablocks")) return viaBlocksPlugin.onTuffXCommand(sender, command, label, args);
if (command.getName().equalsIgnoreCase("restrictions")) return tuffActions.onTuffXCommand(sender, command, label, args);
if (command.getName().equalsIgnoreCase("tuffxclearcache")) {
if (!sender.hasPermission("tuffx.admin")) {
sender.sendMessage("§cNo permission.");
return true;
}
if (y0Plugin != null) {
y0Plugin.forceClearCache();
}
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
long max = runtime.maxMemory();
sender.sendMessage("§aTuffXPlus Y0 cache cleared.");
sender.sendMessage("§7Used RAM: §f" + (used / 1024L / 1024L) + "MB / " + (max / 1024L / 1024L) + "MB");
return true;
}
return true;
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!player.isOnline()) return;
if (channel.equals("eagler:below_y0")) y0Plugin.handlePacket(player,message);
else if (channel.equals("viablocks:handshake")) viaBlocksPlugin.handlePacket(player,message);
else if (channel.equals("eagler:tuffactions")) tuffActions.handlePacket(player,message);
else if (channel.equals("entities:handshake")) viaEntitiesPlugin.handlePacket(player,message);
else getLogger().warning("Received plugin message on unknown channel '%s' from %s".formatted(channel, player.getName()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerChangeWorld(PlayerChangedWorldEvent e) {
y0Plugin.handlePlayerChangeWorld(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockForm(BlockFormEvent e) {
viaBlocksPlugin.blockListener.handleBlockForm(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockFade(BlockFadeEvent e) {
viaBlocksPlugin.blockListener.handleBlockFade(e);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent e) {
y0Plugin.handlePlayerJoin(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockGrow(BlockGrowEvent e) {
viaBlocksPlugin.blockListener.handleBlockGrow(e);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
y0Plugin.handlePlayerQuit(e);
tuffActions.handlePlayerQuit(e);
viaBlocksPlugin.blockListener.handlePlayerQuit(e);
viaEntitiesPlugin.handlePlayerQuit(e);
}
@EventHandler
public void onToggleSwim(EntityToggleSwimEvent e) {
tuffActions.handleToggleSwim(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockSpread(BlockSpreadEvent e) {
viaBlocksPlugin.blockListener.handleBlockSpread(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent e) {
viaBlocksPlugin.blockListener.handleBlockBreak(e);
y0Plugin.handleBlockBreak(e);
}
@EventHandler
public void onPlayerInventoryClick(InventoryClickEvent e) {
tuffActions.handlePlayerInventoryClick(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockPlace(BlockPlaceEvent e) {
viaBlocksPlugin.blockListener.handleBlockPlace(e);
y0Plugin.handleBlockPlace(e);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockPhysics(BlockPhysicsEvent e) {
y0Plugin.handleBlockPhysics(e);
viaBlocksPlugin.blockListener.handleBlockPhysics(e);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent e) {
y0Plugin.handleChunkLoad(e);
viaBlocksPlugin.blockListener.handleChunkLoad(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockExplode(BlockExplodeEvent e) {
viaBlocksPlugin.blockListener.handleBlockExplode(e);
y0Plugin.handleBlockExplode(e);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockFromTo(BlockFromToEvent e) {
viaBlocksPlugin.blockListener.handleBlockFromTo(e);
y0Plugin.handleBlockFromTo(e);
}
private void lfe() {
getLogger().info("");
getLogger().info("████████╗██╗ ██╗███████╗ ███████╗ ██╗ ██╗ ██╗");
getLogger().info("╚══██╔══╝██║ ██║██╔════╝ ██╔════╝ ╚██╗██╔╝ ██║");
getLogger().info(" ██║ ██║ ██║██████╗ ██████╗ ╚███╔╝ ████████████╗");
getLogger().info(" ██║ ██║ ██║██╔═══╝ ██╔═══╝ ██╔██╗ ╚════██╔════╝");
getLogger().info(" ██║ ╚██████╔╝██║ ██║ ██╔╝╚██╗ ██║");
getLogger().info(" ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝");
getLogger().info("");
getLogger().info("CREDITS");
getLogger().info("");
getLogger().info("Y0 support:");
getLogger().info("• Below y0 (client + plugin) programmed by Potato (@justatypicalpotato)");
getLogger().info("• llucasandersen - plugin optimizations");
getLogger().info("");
getLogger().info("ViaBlocks:");
getLogger().info("• ViaBlocks partial plugin and client rewrite by Potato");
getLogger().info("• llucasandersen (Complex client models and texture fixes,");
getLogger().info(" optimizations, PacketEvents migration and async safety fixes)");
getLogger().info("• coleis1op, if ts is driving me crazy, im taking credit");
getLogger().info("");
getLogger().info("Other:");
getLogger().info("• Swimming and creative items programmed by Potato (@justatypicalpotato)");
getLogger().info("• shaded build, 1.14+ support (before merge) - llucasandersen");
getLogger().info("• Restrictions - UplandJacob");
getLogger().info("• Overall plugin merges by Potato");
getLogger().info("• Major Ram optimizations - MrNorshare");
}
}
@@ -0,0 +1,77 @@
package tf.tuff.netty;
import com.viaversion.viaversion.api.Via;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Player;
import java.util.UUID;
public abstract class BaseInjector {
private final String handlerName;
protected BaseInjector(String handlerName) {
this.handlerName = handlerName;
}
protected abstract ChannelHandler createHandler(Player player);
protected void onPostInject(Player player) {
}
public void inject(Player player) {
UUID uuid = player.getUniqueId();
var viaConnection = Via.getAPI().getConnection(uuid);
if (viaConnection == null) return;
Channel channel = viaConnection.getChannel();
if (channel == null) return;
channel.eventLoop().submit(() -> {
try {
if (channel.pipeline().get(handlerName) != null) {
channel.pipeline().remove(handlerName);
}
String targetHandler = null;
String[] anchors = {"packet_handler", "encoder", "via-encoder"};
for (int i = 0; i < anchors.length; ++i) {
if (channel.pipeline().get(anchors[i]) != null) {
targetHandler = anchors[i];
break;
}
}
ChannelHandler handler = createHandler(player);
if (targetHandler != null) {
channel.pipeline().addBefore(targetHandler, handlerName, handler);
} else {
channel.pipeline().addFirst(handlerName, handler);
}
onPostInject(player);
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void eject(Player player) {
UUID uuid = player.getUniqueId();
var viaConnection = Via.getAPI().getConnection(uuid);
if (viaConnection == null) return;
Channel channel = viaConnection.getChannel();
if (channel != null && channel.isOpen()) {
channel.eventLoop().submit(() -> {
try {
if (channel.pipeline().get(handlerName) != null) {
channel.pipeline().remove(handlerName);
}
} catch (Exception e) {
}
});
}
}
}
@@ -0,0 +1,455 @@
package tf.tuff.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.bukkit.World;
import org.bukkit.entity.Player;
import tf.tuff.viablocks.CustomBlockListener;
import tf.tuff.y0.Y0Plugin;
import tf.tuff.util.SchedulerCompat;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class ChunkHandler extends ChannelOutboundHandlerAdapter {
private final CustomBlockListener viaBlocks;
private final Y0Plugin y0;
private final Player player;
private final UUID playerId;
private final Map<Long, QueuedPacket> queue = new ConcurrentHashMap<>();
private volatile ChannelHandlerContext ctx;
private static final long TIMEOUT_MS = 500;
static record BlockChangePosition(int x, int y, int z) {}
public ChunkHandler(CustomBlockListener viaBlocks, Y0Plugin y0, Player player) {
this.viaBlocks = viaBlocks;
this.y0 = y0;
this.player = player;
this.playerId = player.getUniqueId();
}
private static class QueuedPacket {
final ByteBuf buf;
final ChannelPromise promise;
final int chunkX;
final int chunkZ;
final long time;
volatile boolean viaReady;
volatile boolean y0Ready;
volatile byte[] viaData;
volatile byte[] y0Data;
QueuedPacket(ByteBuf buf, ChannelPromise promise, int cx, int cz) {
this.buf = buf;
this.promise = promise;
this.chunkX = cx;
this.chunkZ = cz;
this.time = System.currentTimeMillis();
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (!(msg instanceof ByteBuf)) {
super.write(ctx, msg, promise);
return;
}
ByteBuf buf = (ByteBuf) msg;
buf.markReaderIndex();
try {
int packetId = readVarInt(buf);
if (packetId == 0x20) {
handleChunkPacket(ctx, buf, promise);
return;
}
if (packetId == 0x0B && isViaActive()) {
handleBlockChange(ctx, buf, promise);
return;
}
if (packetId == 0x10 && isViaActive()) {
handleMultiBlockChange(ctx, buf, promise);
return;
}
} catch (Exception e) {
} finally {
if (buf.refCnt() > 0 && msg == buf) {
buf.resetReaderIndex();
}
}
super.write(ctx, msg, promise);
}
private boolean isViaActive() {
return viaBlocks != null
&& viaBlocks.plugin.isEnabled()
&& viaBlocks.plugin.isPlayerEnabled(player);
}
private void handleChunkPacket(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise) throws Exception {
int chunkX = buf.readInt();
int chunkZ = buf.readInt();
buf.resetReaderIndex();
boolean viaActive = isViaActive();
byte[] viaData = viaActive ? viaBlocks.getExtraDataForChunk(player.getWorld().getName(), chunkX, chunkZ) : null;
byte[] y0Data = y0 != null ? y0.getY0DataForChunk(player, chunkX, chunkZ) : null;
boolean needY0 = y0 != null && y0.isPlayerReady(player);
boolean viaReady = !viaActive || viaData != null;
boolean y0Ready = !needY0 || y0Data != null;
if (viaReady && y0Ready) {
writeWithData(ctx, buf, promise, viaData, y0Data);
return;
}
long key = key(chunkX, chunkZ);
QueuedPacket q = new QueuedPacket(buf.retain(), promise, chunkX, chunkZ);
q.viaReady = viaReady;
q.y0Ready = y0Ready;
q.viaData = viaData;
q.y0Data = y0Data;
QueuedPacket old = queue.put(key, q);
if (queue.size() > 256) {
clearOldQueuePackets(1000L);
}
if (queue.size() > 1024) {
forceClearQueue();
}
if (old != null) {
if (old.buf.refCnt() > 0) {
old.buf.release();
}
if (!old.promise.isDone()) {
old.promise.tryFailure(new RuntimeException("Replaced queued chunk packet"));
}
}
if (!viaReady) {
requestViaCache(chunkX, chunkZ, key);
}
if (!y0Ready) {
requestY0Cache(chunkX, chunkZ, key);
}
scheduleTimeout(key);
}
private void handleBlockChange(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise) throws Exception {
BlockChangePosition position = decodeSingleBlockChangePosition(buf.getLong(buf.readerIndex()));
resolveViaDataOnRegionThread(ctx, buf, promise, player.getWorld(), position.x >> 4, position.z >> 4, () -> {
World world = player.getWorld();
if (!world.isChunkLoaded(position.x >> 4, position.z >> 4)) {
return null;
}
return viaBlocks.getExtraDataForSingleBlock(world, position.x, position.y, position.z);
});
}
private void handleMultiBlockChange(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise) throws Exception {
buf.resetReaderIndex();
buf.skipBytes(varIntLen(buf));
long chunkSectionPos = buf.readLong();
int cx = decodeSectionCoordX(chunkSectionPos);
int cz = decodeSectionCoordZ(chunkSectionPos);
buf.readBoolean();
int count = readVarInt(buf);
java.util.List<Long> locs = new java.util.ArrayList<>(count);
for (int i = 0; i < count; i++) {
BlockChangePosition position = decodeMultiBlockChangePosition(chunkSectionPos, readVarLong(buf));
locs.add(viaBlocks.packLocation(position.x, position.y, position.z));
}
resolveViaDataOnRegionThread(ctx, buf, promise, player.getWorld(), cx, cz, () -> viaBlocks.getExtraDataForMultiBlock(player.getWorld(), locs));
}
private void resolveViaDataOnRegionThread(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise,
World world, int chunkX, int chunkZ,
java.util.concurrent.Callable<byte[]> supplier) {
ByteBuf retained = buf.retain();
SchedulerCompat.runRegion(viaBlocks.plugin.plugin, world, chunkX, chunkZ, () -> {
byte[] data = null;
try {
if (player.isOnline() && isViaActive()) {
data = supplier.call();
}
} catch (Exception ignored) {
}
final byte[] resolvedData = data;
ChannelHandlerContext currentCtx = this.ctx != null ? this.ctx : ctx;
currentCtx.channel().eventLoop().execute(() -> {
try {
retained.resetReaderIndex();
if (resolvedData != null && resolvedData.length > 0) {
writeWithViaOnly(currentCtx, retained, promise, resolvedData);
} else {
currentCtx.write(retained, promise);
}
} catch (Exception ignored) {
} finally {
if (retained.refCnt() > 0) {
retained.release();
}
}
});
});
}
private void requestViaCache(int cx, int cz, long key) {
SchedulerCompat.runRegion(viaBlocks.plugin.plugin, player.getWorld(), cx, cz, () -> {
if (!player.isOnline()) {
release(key);
return;
}
viaBlocks.cacheChunkWithCallback(player.getWorld(), cx, cz, data -> {
QueuedPacket q = queue.get(key);
if (q != null) {
q.viaData = data;
q.viaReady = true;
tryRelease(key);
}
});
});
}
private void requestY0Cache(int cx, int cz, long key) {
SchedulerCompat.runRegion(viaBlocks.plugin.plugin, player.getWorld(), cx, cz, () -> {
if (!player.isOnline()) {
release(key);
return;
}
y0.cacheChunkWithCallback(player, cx, cz, data -> {
QueuedPacket q = queue.get(key);
if (q != null) {
q.y0Data = data;
q.y0Ready = true;
tryRelease(key);
}
});
});
}
private void tryRelease(long key) {
QueuedPacket q = queue.get(key);
if (q != null && q.viaReady && q.y0Ready) {
release(key);
}
}
private void release(long key) {
QueuedPacket q = queue.remove(key);
if (q == null) return;
if (ctx != null && ctx.channel().isOpen()) {
ctx.channel().eventLoop().execute(() -> {
try {
writeWithData(ctx, q.buf, q.promise, q.viaData, q.y0Data);
} catch (Exception e) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
}
});
} else {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
}
}
private void scheduleTimeout(long key) {
if (ctx != null) {
ctx.channel().eventLoop().schedule(() -> {
QueuedPacket q = queue.get(key);
if (q != null && System.currentTimeMillis() - q.time >= TIMEOUT_MS) {
release(key);
}
}, TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
}
private void writeWithData(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise,
byte[] viaData, byte[] y0Data) throws Exception {
boolean hasVia = viaData != null && viaData.length > 0;
boolean hasY0 = y0Data != null && y0Data.length > 0;
if (!hasVia && !hasY0) {
ctx.write(buf, promise);
return;
}
CompositeByteBuf composite = ctx.alloc().compositeBuffer();
composite.addComponent(true, buf);
if (hasVia) {
ByteBuf tail = ctx.alloc().buffer();
tail.writeBytes(viaData);
composite.addComponent(true, tail);
}
if (hasY0) {
ByteBuf tail = ctx.alloc().buffer();
tail.writeInt(0x59304348);
tail.writeInt(y0Data.length);
tail.writeBytes(y0Data);
composite.addComponent(true, tail);
}
ctx.write(composite, promise);
}
private void writeWithViaOnly(ChannelHandlerContext ctx, ByteBuf buf, ChannelPromise promise,
byte[] data) throws Exception {
ByteBuf tail = ctx.alloc().buffer();
tail.writeBytes(data);
CompositeByteBuf composite = ctx.alloc().compositeBuffer();
composite.addComponents(true, buf, tail);
ctx.write(composite, promise);
}
private long key(int x, int z) {
return ((long) x << 32) | (z & 0xFFFFFFFFL);
}
private int readVarInt(ByteBuf buf) {
int n = 0;
int r = 0;
byte b;
do {
b = buf.readByte();
r |= (b & 0x7F) << (7 * n++);
if (n > 5) throw new RuntimeException("VarInt too big");
} while ((b & 0x80) != 0);
return r;
}
private long readVarLong(ByteBuf buf) {
long value = 0L;
int position = 0;
byte currentByte;
do {
currentByte = buf.readByte();
value |= (long) (currentByte & 0x7F) << position;
position += 7;
if (position > 70) {
throw new RuntimeException("VarLong too big");
}
} while ((currentByte & 0x80) != 0);
return value;
}
static BlockChangePosition decodeSingleBlockChangePosition(long value) {
int x = decodeSigned((int) (value >> 38), 26);
int z = decodeSigned((int) ((value >> 12) & 0x3FFFFFFL), 26);
int y = decodeSigned((int) (value & 0xFFFL), 12);
return new BlockChangePosition(x, y, z);
}
static BlockChangePosition decodeMultiBlockChangePosition(long sectionPosition, long entry) {
int sectionX = decodeSectionCoordX(sectionPosition);
int sectionY = decodeSectionCoordY(sectionPosition);
int sectionZ = decodeSectionCoordZ(sectionPosition);
int localPosition = (int) (entry & 0xFFFL);
int x = (sectionX << 4) | ((localPosition >> 8) & 0xF);
int z = (sectionZ << 4) | ((localPosition >> 4) & 0xF);
int y = (sectionY << 4) | (localPosition & 0xF);
return new BlockChangePosition(x, y, z);
}
private static int decodeSectionCoordX(long sectionPosition) {
return decodeSigned((int) (sectionPosition >> 42), 22);
}
private static int decodeSectionCoordY(long sectionPosition) {
return decodeSigned((int) (sectionPosition & 0xFFFFFL), 20);
}
private static int decodeSectionCoordZ(long sectionPosition) {
return decodeSigned((int) ((sectionPosition >> 20) & 0x3FFFFFL), 22);
}
private static int decodeSigned(int value, int bits) {
int signBit = 1 << (bits - 1);
int fullMask = (1 << bits) - 1;
value &= fullMask;
return (value ^ signBit) - signBit;
}
private int varIntLen(ByteBuf buf) {
int s = buf.readerIndex();
readVarInt(buf);
int l = buf.readerIndex() - s;
buf.readerIndex(s);
return l;
}
public void forceClearQueue() {
for (QueuedPacket q : queue.values()) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
if (!q.promise.isDone()) {
q.promise.tryFailure(new RuntimeException("Force cleared queued chunk packet"));
}
}
queue.clear();
}
public void clearOldQueuePackets(long maxAgeMs) {
long now = System.currentTimeMillis();
for (Map.Entry<Long, QueuedPacket> entry : queue.entrySet()) {
QueuedPacket q = entry.getValue();
if (now - q.time >= maxAgeMs && queue.remove(entry.getKey(), q)) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
if (!q.promise.isDone()) {
q.promise.tryFailure(new RuntimeException("Expired queued chunk packet"));
}
}
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
for (QueuedPacket q : queue.values()) {
if (q.buf.refCnt() > 0) {
q.buf.release();
}
}
queue.clear();
}
}
@@ -0,0 +1,64 @@
package tf.tuff.netty;
import com.viaversion.viaversion.api.Via;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Player;
import tf.tuff.viablocks.CustomBlockListener;
import tf.tuff.y0.Y0Plugin;
import java.util.UUID;
public class ChunkInjector extends BaseInjector {
private final CustomBlockListener viaBlocks;
private final Y0Plugin y0;
public ChunkInjector(CustomBlockListener viaBlocks, Y0Plugin y0) {
super("tuff_chunk_handler");
this.viaBlocks = viaBlocks;
this.y0 = y0;
}
@Override
protected ChannelHandler createHandler(Player player) {
return new ChunkHandler(viaBlocks, y0, player);
}
@Override
public void inject(Player player) {
UUID uuid = player.getUniqueId();
var viaConnection = Via.getAPI().getConnection(uuid);
if (viaConnection == null) return;
Channel channel = viaConnection.getChannel();
if (channel == null) return;
channel.eventLoop().submit(() -> {
try {
if (channel.pipeline().get("tuff_chunk_handler") != null) {
channel.pipeline().remove("tuff_chunk_handler");
}
if (channel.pipeline().get("viablocks_chunk_handler") != null) {
channel.pipeline().remove("viablocks_chunk_handler");
}
if (channel.pipeline().get("y0_chunk_handler") != null) {
channel.pipeline().remove("y0_chunk_handler");
}
if (channel.pipeline().get("via-encoder") != null) {
channel.pipeline().addBefore(
"via-encoder",
"tuff_chunk_handler",
new ChunkHandler(viaBlocks, y0, player)
);
} else {
channel.pipeline().addFirst("tuff_chunk_handler",
new ChunkHandler(viaBlocks, y0, player));
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
@@ -0,0 +1,57 @@
package tf.tuff.tuffactions;
import java.util.logging.Level;
import tf.tuff.TuffX;
public abstract class TuffActionBase {
private boolean enabled = false;
private boolean debugMode = false;
private final String name;
private final String configPath;
protected final TuffActions actsPlugin;
protected final TuffX plugin;
public TuffActionBase(TuffActions actsPlugin, String name, String configPath, boolean defaultEnabled) {
this.actsPlugin = actsPlugin;
this.plugin = actsPlugin.plugin;
this.name = name;
this.configPath = configPath;
plugin.getConfig().addDefault(configPath+".enabled", defaultEnabled);
plugin.getConfig().addDefault(configPath+".debug", false);
}
public boolean isEnabled() {
return enabled;
}
public void onConfigLoad() {
boolean wasEnabled = this.enabled;
this.enabled = plugin.getConfig().getBoolean(this.configPath+".enabled");
if (enabled) {
this.enable(wasEnabled);
} else if (wasEnabled) {
this.disable();
}
this.debugMode = plugin.getConfig().getBoolean(this.configPath+".debug");
}
protected void enable(boolean wasEnabled) {
actsPlugin.info(name+" enabled.");
}
protected void disable() {
actsPlugin.info(name+" is now disabled.");
}
protected boolean isDebug() {
return debugMode;
}
protected void debug(String msg) {
if (debugMode) actsPlugin.log(Level.INFO, msg);
}
protected void debug(String msg, Exception e) {
if (debugMode) actsPlugin.log(Level.INFO, msg, e);
}
}
@@ -0,0 +1,159 @@
package tf.tuff.tuffactions;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.GameMode;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityToggleSwimEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
import tf.tuff.TuffX;
import tf.tuff.tuffactions.creative.CreativeMenu;
import tf.tuff.tuffactions.restrictions.Restrictions;
import tf.tuff.tuffactions.swimming.Swimming;
public class TuffActions {
public static final String CHANNEL = "eagler:tuffactions";
private Swimming swimmingManager;
private CreativeMenu creativeManager;
private Restrictions restrictions;
public final TuffX plugin;
public static final Set<UUID> tuffPlayers = ConcurrentHashMap.newKeySet();
public TuffActions(TuffX plugin){
this.plugin = plugin;
}
private void loadConfig() {
info("TuffActions has been enabled");
info("Enabling features...");
swimmingManager.onConfigLoad();
creativeManager.onConfigLoad();
restrictions.onConfigLoad();
}
public void onTuffXReload() {
loadConfig();
info("Misc features reloaded.");
}
public void onTuffXEnable() {
this.swimmingManager = new Swimming(this);
this.creativeManager = new CreativeMenu(this);
this.restrictions = new Restrictions(this);
plugin.getConfig().options().copyDefaults(true);
loadConfig();
info("Finished enabling features.");
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "eagler:tuffactions");
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, "eagler:tuffactions", plugin);
}
public boolean onTuffXCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("restrictions")) return this.restrictions.onTuffXCommand(sender, command, label, args);
return true;
}
public void handlePacket(Player player, byte[] message) {
if (player == null || message == null || message.length < 13) return;
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
in.readInt();
in.readInt();
in.readInt();
int actionLength = in.readUnsignedByte();
if (actionLength == 0 || in.available() < actionLength) return;
byte[] actionBytes = new byte[actionLength];
in.readFully(actionBytes);
String action = new String(actionBytes, StandardCharsets.UTF_8);
tuffPlayers.add(player.getUniqueId());
if ("swimming_state".equals(action)) {
swimmingManager.handleSwimState(player, in.readBoolean());
} else if ("elytra_state".equals(action)) {
swimmingManager.handleElytraState(player, in.readBoolean());
} else if ("swim_ready".equals(action)){
swimmingManager.handleSwimReady(player);
} else if ("creative_ready".equals(action)){
creativeManager.handleCreativeReady(player);
} else if ("give_creative_item".equals(action)){
if (!creativeManager.isEnabled()) return;
if (player.getGameMode() != GameMode.CREATIVE) return;
int itemLength = in.readUnsignedByte();
if (in.available() < itemLength + Integer.BYTES) return;
byte[] itemBytes = new byte[itemLength];
in.readFully(itemBytes);
String item = new String(itemBytes, StandardCharsets.UTF_8);
int amount = in.readInt();
creativeManager.handlePlaceholderTaken(player, item, amount);
} else if ("pick_viablock".equals(action)){
if (!creativeManager.isEnabled()) return;
if (player.getGameMode() != GameMode.CREATIVE) return;
int blockLength = in.readUnsignedByte();
if (in.available() < blockLength + 1) return;
byte[] blockBytes = new byte[blockLength];
in.readFully(blockBytes);
String blockName = new String(blockBytes, StandardCharsets.UTF_8);
int hotbarSlot = in.readUnsignedByte();
creativeManager.handlePickViablock(player, blockName, hotbarSlot);
} else if ("restrictions_ready".equals(action)) {
restrictions.handleRestrictionsReady(player);
}
} catch (IOException e) {
log(Level.WARNING, "Failed to read a plugin message from " + player.getName(), e);
}
}
public void sendPluginMessage(Player player, byte[] payload) {
sendPluginMessage(player, CHANNEL, payload);
}
public void sendPluginMessage(Player player, String channel, byte[] payload) {
if (player == null || payload == null || !player.isOnline() || PacketEvents.getAPI() == null || !PacketEvents.getAPI().isInitialized()) return;
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(channel, payload);
PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet);
}
public void handlePlayerQuit(PlayerQuitEvent event) {
swimmingManager.handlePlayerQuit(event);
tuffPlayers.remove(event.getPlayer().getUniqueId());
}
public void handleToggleSwim(EntityToggleSwimEvent event) {
swimmingManager.handleToggleSwim(event);
}
public void handlePlayerInventoryClick(InventoryClickEvent event) {
creativeManager.onPlayerInventoryClick(event);
}
public void log(Level level, String msg, Throwable e) {
plugin.getLogger().log(level, "[TuffActions] "+msg, e);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[TuffActions] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
}
@@ -0,0 +1,115 @@
package tf.tuff.tuffactions.creative;
import tf.tuff.tuffactions.TuffActionBase;
import tf.tuff.tuffactions.TuffActions;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import tf.tuff.util.SchedulerCompat;
public class CreativeMenu extends TuffActionBase {
private final Set<String> itemMapping = ConcurrentHashMap.newKeySet();
private final Map<UUID, ItemStack> playerHoldingPlaceholder = new ConcurrentHashMap<>();
private final TabUtil tabUtil;
public CreativeMenu(TuffActions actsPlugin) {
super(actsPlugin, "Creative Items", "creative-items", true);
this.tabUtil = new TabUtil(actsPlugin);
}
@Override
protected void enable(boolean wasEnabled) {
if (!wasEnabled) {
if (itemMapping.isEmpty()) initializeMappings();
}
super.enable(wasEnabled);
}
@Override
protected void disable() {
itemMapping.clear();
super.disable();
}
public void initializeMappings() {
for (Material m : Material.values()){
if (m.isItem()){
itemMapping.add(m.name());
}
}
}
/*** CUSTOM SERVER-BOUND PACKETS ***/
public void handleCreativeReady(Player player) {
if (!isEnabled()) return;
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("creative_items");
out.writeInt(itemMapping.size());
for (String item : itemMapping) {
out.writeUTF(item);
String category = this.tabUtil.getCreativeCategory(item);
out.writeUTF(category != null ? category : "");
}
actsPlugin.sendPluginMessage(player, bout.toByteArray());
} catch (IOException e) {
debug("Failed to send creative items to " + player.getName(), e);
}
}
public void handlePlaceholderTaken(Player player, String realItemName, int amount) {
Material realMaterial = Material.getMaterial(realItemName.toUpperCase(Locale.ROOT));
if (realMaterial != null) {
ItemStack realItemStack = new ItemStack(realMaterial, amount);
playerHoldingPlaceholder.put(player.getUniqueId(), realItemStack);
}
}
public void handlePickViablock(Player player, String blockName, int hotbarSlot) {
Material material = Material.getMaterial(blockName.toUpperCase(Locale.ROOT));
if (material == null || !material.isItem()) {
return;
}
if (hotbarSlot < 0 || hotbarSlot > 8) {
hotbarSlot = 0;
}
player.getInventory().setItem(hotbarSlot, new ItemStack(material, 1));
}
/*** EVENT HANDLERS ***/
public void onPlayerInventoryClick(InventoryClickEvent event) {
if (!isEnabled()) return;
Player player = (Player) event.getWhoClicked();
UUID playerUUID = player.getUniqueId();
if (playerHoldingPlaceholder.containsKey(playerUUID)) {
InventoryAction action = event.getAction();
if (action == InventoryAction.PLACE_ALL || action == InventoryAction.PLACE_ONE || action == InventoryAction.SWAP_WITH_CURSOR) {
SchedulerCompat.runEntityLater(player, plugin, () -> {
ItemStack realItemStack = playerHoldingPlaceholder.get(playerUUID);
if (realItemStack != null && event.getClickedInventory() != null) {
event.getClickedInventory().setItem(event.getSlot(), realItemStack);
}
playerHoldingPlaceholder.remove(playerUUID);
}, 1L);
}
}
}
}
@@ -0,0 +1,58 @@
package tf.tuff.tuffactions.creative;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nullable;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import tf.tuff.tuffactions.TuffActions;
public class TabUtil {
private final TuffActions plugin;
private final File mappingFile;
private Map<String, String> creativeTabMap;
public TabUtil(TuffActions plugin) {
this.plugin = plugin;
this.mappingFile = new File(plugin.plugin.getDataFolder(), "tab-mapping.json");
setupMappingFile();
loadMapping();
}
private void setupMappingFile() {
if (!mappingFile.exists()) {
plugin.info("Creative tab mapping not found, creating from resources...");
plugin.plugin.saveResource("tab-mapping.json", false);
}
}
private void loadMapping() {
try {
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<String, String>> typeRef = new TypeReference<Map<String, String>>() {};
this.creativeTabMap = mapper.readValue(mappingFile, typeRef);
plugin.info("Successfully loaded " + creativeTabMap.size() + " creative tab mappings.");
} catch (IOException e) {
plugin.log(Level.SEVERE, "Failed to load creative tab mapping from file!", e);
this.creativeTabMap = Collections.emptyMap();
}
}
@Nullable
public String getCreativeCategory(String material) {
if (material == null || creativeTabMap.isEmpty()) {
return null;
}
return creativeTabMap.get(material);
}
}
@@ -0,0 +1,102 @@
package tf.tuff.tuffactions.restrictions;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tf.tuff.tuffactions.TuffActionBase;
import tf.tuff.tuffactions.TuffActions;
public class Restrictions extends TuffActionBase {
private RestrictionsCommand commandHandler;
private Set<String> disallowed = ConcurrentHashMap.newKeySet();
private static final List<String> example = List.of("clientbrand");
public Restrictions(TuffActions actsPlugin) {
super(actsPlugin, "Restrictions", "restrictions", true);
this.commandHandler = new RestrictionsCommand(this, plugin);
plugin.getConfig().addDefault("restrictions.disallow", example);
}
public void loadConfig() {
disallowed.clear();
actsPlugin.info("Loading Restrictions config...");
List<?> config = plugin.getConfig().getList("restrictions.disallow");
for (Object val : config) {
if (val instanceof String) disallowed.add((String)val);
}
}
@Override
protected void enable(boolean wasEnabled) {
loadConfig();
super.enable(wasEnabled);
}
@Override
protected void disable() {
disallowed.clear();
super.disable();
}
/*** CUSTOM SERVER-BOUND PACKETS ***/
public void handleRestrictionsReady(Player player) {
if (!isEnabled()) return;
debug("Sending restrictions to %s".formatted(player.getName()));
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("client_features_all");
out.writeInt(disallowed.size());
for (String key : disallowed) {
out.writeUTF(key);
out.writeBoolean(false);
}
actsPlugin.sendPluginMessage(player, bout.toByteArray());
} catch (IOException e) {
debug("Failed to send Restrictions to " + player.getName(), e);
}
}
/*** CUSTOM CLIENT-BOUND PACKETS ***/
public void sendSingleUpdateToAll(String key) {
for (UUID uuid : TuffActions.tuffPlayers) {
Player player = Bukkit.getPlayer(uuid);
handleSingleUpdate(player, key);
}
}
private void handleSingleUpdate(Player player, String key) {
if (!isEnabled()) return;
debug("Sending restriction update for '%s' to %s".formatted(key, player.getName()));
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("client_feature");
out.writeUTF(key);
out.writeBoolean(!disallowed.contains(key));
actsPlugin.sendPluginMessage(player, bout.toByteArray());
} catch (IOException e) {
actsPlugin.log(Level.WARNING, "Failed to send Restriction "+key+" to " + player.getName(), e);
}
}
/*** COMMANDS ***/
public boolean onTuffXCommand(CommandSender sender, Command command, String label, String[] args) {
if (!isEnabled()) {
sender.sendMessage("Restrictions are currently disabled.");
return true;
}
return commandHandler.onCommand(sender, command, label, args);
}
}
@@ -0,0 +1,74 @@
package tf.tuff.tuffactions.restrictions;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import tf.tuff.TuffX;
public class RestrictionsCommand {
private final TuffX tuffx;
private final Restrictions restrictions;
RestrictionsCommand(Restrictions restrictions, TuffX tuffx) {
this.restrictions = restrictions;
this.tuffx = tuffx;
}
private boolean error(CommandSender sender, String msg) {
sender.sendMessage("\u00A7c%s".formatted(msg));
return true;
}
private boolean noPermission(CommandSender sender) {
return error(sender, "You do not have permission to use this command.");
}
private boolean invalidUsage(CommandSender sender, String msg) {
return error(sender, "Invalid usage. %s Use: /restrictions <allow|disallow> <module>".formatted(msg));
}
private boolean unknownError(CommandSender sender, Exception e) {
tuffx.getLogger().log(Level.SEVERE, "An unknown error occurred while executing a restrictions command", e);
return error(sender, "An unknown error has occurred. Check server logs.");
}
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) return invalidUsage(sender, "'allow' or 'disallow' required.");
if (args[0].equalsIgnoreCase("disallow")) {
if (!sender.hasPermission("tuffx.restrictions.command.disallow")) return noPermission(sender);
if (args.length < 2) return invalidUsage(sender, "Module name required.");
try {
@SuppressWarnings("unchecked")
List<String> config = (List<String>)tuffx.getConfig().getList("restrictions.disallow");
config.add(args[1]);
tuffx.getConfig().set("restrictions.disallow", config);
tuffx.saveConfig();
restrictions.loadConfig();
restrictions.sendSingleUpdateToAll(args[1]);
} catch(Exception e) {
return unknownError(sender, e);
}
sender.sendMessage("\u00A7aModule '%s' added to disallow list.".formatted(args[1]));
return true;
} else if (args[0].equalsIgnoreCase("allow")) {
if (!sender.hasPermission("tuffx.restrictions.command.allow")) return noPermission(sender);
if (args.length < 2) return invalidUsage(sender, "Module name required.");
try {
@SuppressWarnings("unchecked")
List<String> config = (List<String>)tuffx.getConfig().getList("restrictions.disallow");
if (!config.contains(args[1])) return error(sender, "Module '%s' not in disallow list. No change.".formatted(args[1]));
config.remove(args[1]);
tuffx.getConfig().set("restrictions.disallow", config);
tuffx.saveConfig();
restrictions.loadConfig();
restrictions.sendSingleUpdateToAll(args[1]);
} catch(Exception e) {
return unknownError(sender, e);
}
sender.sendMessage("\u00A7aModule '%s' removed from disallow list.".formatted(args[1]));
return true;
}
return invalidUsage(sender, "Unknown option.");
}
}
@@ -0,0 +1,166 @@
package tf.tuff.tuffactions.swimming;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityToggleSwimEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
import io.github.retrooper.packetevents.util.folia.TaskWrapper;
import tf.tuff.tuffactions.TuffActionBase;
import tf.tuff.tuffactions.TuffActions;
import tf.tuff.util.SchedulerCompat;
public class Swimming extends TuffActionBase {
private final Set<UUID> swimmingPlayers = ConcurrentHashMap.newKeySet();
private final Map<UUID, TaskWrapper> swimStateTasks = new ConcurrentHashMap<>();
public Swimming(TuffActions plugin) {
super(plugin, "Swimming", "swimming", true);
}
@Override
protected void disable() {
for (TaskWrapper task : swimStateTasks.values()) {
task.cancel();
}
swimStateTasks.clear();
swimmingPlayers.clear();
super.disable();
}
/*** CUSTOM, SERVER-BOUND PACKETS ***/
public void handleSwimReady(Player player) {
if (!isEnabled()) return;
SchedulerCompat.runEntityLater(player, plugin, () -> {
for (UUID swimmingPlayerId : swimmingPlayers) {
Player swimmingPlayer = Bukkit.getPlayer(swimmingPlayerId);
if (swimmingPlayer != null && swimmingPlayer.isOnline() && player.canSee(swimmingPlayer)) {
sendSwimState(player, swimmingPlayer, true);
}
}
}, 20L);
}
public void handleSwimState(Player player, boolean isSwimming) {
if (!isEnabled()) return;
if (isSwimming) {
swimmingPlayers.add(player.getUniqueId());
startSwimMaintenance(player);
} else {
swimmingPlayers.remove(player.getUniqueId());
stopSwimMaintenance(player.getUniqueId());
}
SchedulerCompat.runEntity(player, plugin, () -> applySwimmingState(player, isSwimming));
broadcastSwimState(player, isSwimming);
}
public void handleElytraState(Player player, boolean isGliding) {
if (!isEnabled()) return;
SchedulerCompat.runEntity(player, plugin, () -> {
ItemStack chest = player.getInventory().getChestplate();
if (chest != null && chest.getType() == Material.ELYTRA) player.setGliding(isGliding);
});
}
/*** EVENT HANDLERS ***/
public void handleToggleSwim(EntityToggleSwimEvent event) {
if (!isEnabled()) return;
if (!(event.getEntity() instanceof Player)) return;
Player player = (Player) event.getEntity();
if (!event.isSwimming() && swimmingPlayers.contains(player.getUniqueId())) {
event.setCancelled(true);
SchedulerCompat.runEntity(player, plugin, () -> {
if (swimmingPlayers.contains(player.getUniqueId()) && player.isOnline()) {
applySwimmingState(player, true);
}
});
}
}
public void handlePlayerQuit(PlayerQuitEvent event) {
if (!isEnabled()) return;
Player player = event.getPlayer();
stopSwimMaintenance(player.getUniqueId());
if (swimmingPlayers.remove(player.getUniqueId())) {
broadcastSwimState(player, false);
}
}
/*** CUSTOM CLIENT-BOUND PACKETS ***/
private void broadcastSwimState(Player subject, boolean isSwimming) {
for (UUID otherUUID : TuffActions.tuffPlayers) {
if (!otherUUID.equals(subject.getUniqueId())) {
Player recipient = Bukkit.getPlayer(otherUUID);
if (recipient != null && recipient.isOnline()) {
SchedulerCompat.runEntity(recipient, plugin, () -> {
if (recipient.isOnline() && recipient.canSee(subject)) {
sendSwimState(recipient, subject, isSwimming);
}
});
}
}
}
}
private void sendSwimState(Player recipient, Player subject, boolean isSwimming) {
if (recipient == null || !recipient.isOnline()) return;
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("update_other_swim");
out.writeLong(subject.getUniqueId().getMostSignificantBits());
out.writeLong(subject.getUniqueId().getLeastSignificantBits());
out.writeBoolean(isSwimming);
actsPlugin.sendPluginMessage(recipient, bout.toByteArray());
} catch (IOException e) {
debug("Failed to send swim state to " + recipient.getName(), e);
}
}
private void maintainSwimmingState(Player player) {
if (!player.isOnline()) {
stopSwimMaintenance(player.getUniqueId());
swimmingPlayers.remove(player.getUniqueId());
return;
}
if (!player.isInWater()) {
stopSwimMaintenance(player.getUniqueId());
swimmingPlayers.remove(player.getUniqueId());
applySwimmingState(player, false);
broadcastSwimState(player, false);
return;
}
applySwimmingState(player, true);
}
private void applySwimmingState(Player player, boolean swimming) {
if (player == null || !player.isOnline()) return;
if (swimming && !player.isInWater()) return;
if (player.isSwimming() != swimming) {
player.setSwimming(swimming);
}
}
private void startSwimMaintenance(Player player) {
swimStateTasks.computeIfAbsent(player.getUniqueId(),
ignored -> SchedulerCompat.runEntityTimer(player, plugin, () -> maintainSwimmingState(player), 1L, 1L));
}
private void stopSwimMaintenance(UUID playerId) {
TaskWrapper task = swimStateTasks.remove(playerId);
if (task != null) {
task.cancel();
}
}
}
@@ -0,0 +1,82 @@
package tf.tuff.util;
import java.util.concurrent.TimeUnit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import io.github.retrooper.packetevents.util.folia.TaskWrapper;
public final class SchedulerCompat {
private SchedulerCompat() {
}
public static boolean isFolia() {
return FoliaScheduler.isFolia();
}
public static void runGlobal(Plugin plugin, Runnable task) {
FoliaScheduler.getGlobalRegionScheduler().execute(plugin, task);
}
public static TaskWrapper runGlobalLater(Plugin plugin, Runnable task, long delayTicks) {
return FoliaScheduler.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayTicks);
}
public static TaskWrapper runGlobalTimer(Plugin plugin, Runnable task, long delayTicks, long periodTicks) {
return FoliaScheduler.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> task.run(), delayTicks, periodTicks);
}
public static void runAsync(Plugin plugin, Runnable task) {
FoliaScheduler.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
}
public static TaskWrapper runAsyncLater(Plugin plugin, Runnable task, long delayTicks) {
return FoliaScheduler.getAsyncScheduler().runDelayed(plugin, scheduledTask -> task.run(), delayTicks * 50L, TimeUnit.MILLISECONDS);
}
public static void runRegion(Plugin plugin, World world, int chunkX, int chunkZ, Runnable task) {
FoliaScheduler.getRegionScheduler().execute(plugin, world, chunkX, chunkZ, task);
}
public static TaskWrapper runRegionLater(Plugin plugin, World world, int chunkX, int chunkZ, Runnable task, long delayTicks) {
return FoliaScheduler.getRegionScheduler().runDelayed(plugin, world, chunkX, chunkZ, scheduledTask -> task.run(), delayTicks);
}
public static void runRegion(Plugin plugin, Location location, Runnable task) {
FoliaScheduler.getRegionScheduler().execute(plugin, location, task);
}
public static TaskWrapper runRegionLater(Plugin plugin, Location location, Runnable task, long delayTicks) {
return FoliaScheduler.getRegionScheduler().runDelayed(plugin, location, scheduledTask -> task.run(), delayTicks);
}
public static void runEntity(Entity entity, Plugin plugin, Runnable task) {
FoliaScheduler.getEntityScheduler().execute(entity, plugin, task, () -> {
}, 0L);
}
public static TaskWrapper runEntityLater(Entity entity, Plugin plugin, Runnable task, long delayTicks) {
return FoliaScheduler.getEntityScheduler().runDelayed(entity, plugin, scheduledTask -> task.run(), () -> {
}, delayTicks);
}
public static TaskWrapper runEntityTimer(Entity entity, Plugin plugin, Runnable task, long delayTicks, long periodTicks) {
return FoliaScheduler.getEntityScheduler().runAtFixedRate(entity, plugin, scheduledTask -> task.run(), () -> {
}, delayTicks, periodTicks);
}
public static void sendPluginMessage(Plugin plugin, Player player, String channel, byte[] payload) {
if (player == null || channel == null || payload == null || !player.isOnline()) return;
runEntity(player, plugin, () -> {
if (player.isOnline()) {
player.sendPluginMessage(plugin, channel, payload);
}
});
}
}
@@ -0,0 +1,678 @@
package tf.tuff.viablocks;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.block.*;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import tf.tuff.netty.ChunkInjector;
import tf.tuff.util.SchedulerCompat;
import tf.tuff.viablocks.version.VersionAdapter;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
public class CustomBlockListener {
public final ViaBlocksPlugin plugin;
private final VersionAdapter versionAdapter;
private final PaletteManager paletteManager;
private final EnumSet<Material> modernMaterials;
private ChunkInjector chunkInjector;
private static final long X_MASK = (1L << 26) - 1L;
private static final long Z_MASK = (1L << 26) - 1L;
private static final long Y_MASK = (1L << 12) - 1L;
private static final int Z_SHIFT = 12;
private static final int X_SHIFT = 12 + 26;
private final Map<UUID, Map<Integer, List<Long>>> pendingUpdates = new HashMap<>();
private final Set<UUID> pendingFlush = new HashSet<>();
private static final double UPDATE_RADIUS_SQUARED = 6400;
private static final @Nonnull byte[] EMPTY_PACKET = new byte[0];
private final Cache<BlockData, Integer> blockDataIdCache;
private final Cache<String, byte[]> chunkPacketCache;
private final Cache<Long, Integer> recentModernChanges;
public CustomBlockListener(ViaBlocksPlugin plugin, VersionAdapter versionAdapter, PaletteManager paletteManager) {
this.plugin = plugin;
this.versionAdapter = versionAdapter;
this.paletteManager = paletteManager;
this.modernMaterials = versionAdapter.getModernMaterials();
this.blockDataIdCache = CacheBuilder.newBuilder()
.maximumSize(8000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
this.chunkPacketCache = CacheBuilder.newBuilder()
.maximumSize(4096)
.expireAfterAccess(5, TimeUnit.MINUTES)
.build();
this.recentModernChanges = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
public byte[] getCachedChunkData(String worldName, int x, int z) {
return chunkPacketCache.getIfPresent(chunkKey(worldName, x, z));
}
public void setChunkInjector(ChunkInjector injector) {
if (injector == null) return;
this.chunkInjector = injector;
}
private @Nonnull String chunkKey(String worldName, int x, int z) {
return worldName + "_" + x + "_" + z;
}
public void onViaBlocksPlayerJoin(Player player) {
if (plugin.isFirstJoin(player)) {
plugin.sendWelcomeGui(player);
plugin.markPlayerAsJoined(player);
}
sendPaletteToClient(player);
preCacheVisibleChunks(player);
if (chunkInjector != null) {
chunkInjector.inject(player);
}
sendInitialChunks(player);
}
private void preCacheVisibleChunks(Player player) {
World world = player.getWorld();
int viewDistance = Math.min(this.versionAdapter.getClientViewDistance(player), 16);
int px = player.getLocation().getChunk().getX();
int pz = player.getLocation().getChunk().getZ();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int cx = px + x;
int cz = pz + z;
if (world.isChunkLoaded(cx, cz)) {
Chunk chunk = world.getChunkAt(cx, cz);
prepareChunkCache(chunk);
}
}
}
}
private static final int CHUNKS_PER_TICK = 8;
private void sendInitialChunks(Player player) {
World world = player.getWorld();
int viewDistance = Math.min(this.versionAdapter.getClientViewDistance(player), 16);
int px = player.getLocation().getChunk().getX();
int pz = player.getLocation().getChunk().getZ();
List<int[]> chunks = new ArrayList<>();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int cx = px + x;
int cz = pz + z;
if (world.isChunkLoaded(cx, cz)) {
chunks.add(new int[]{cx, cz, x * x + z * z});
}
}
}
chunks.sort((a, b) -> Integer.compare(a[2], b[2]));
sendChunksBatched(player, world.getName(), chunks, 0);
}
private void sendChunksBatched(Player player, String worldName, List<int[]> chunks, int startIndex) {
if (!player.isOnline() || startIndex >= chunks.size()) return;
int endIndex = Math.min(startIndex + CHUNKS_PER_TICK, chunks.size());
for (int i = startIndex; i < endIndex; i++) {
int[] chunk = chunks.get(i);
World world = player.getWorld();
if (!world.getName().equals(worldName)) {
return;
}
cacheChunkWithCallback(world, chunk[0], chunk[1], data -> {
if (!player.isOnline() || !plugin.isPlayerEnabled(player)) return;
if (data != null && data.length > 0) {
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, data);
}
});
}
if (endIndex < chunks.size()) {
final int nextStart = endIndex;
if (player.isOnline()) {
SchedulerCompat.runEntityLater(player, plugin.plugin, () -> sendChunksBatched(player, worldName, chunks, nextStart), 1L);
}
}
}
public void handlePlayerQuit(PlayerQuitEvent event) {
if (chunkInjector != null) {
chunkInjector.eject(event.getPlayer());
}
UUID playerId = event.getPlayer().getUniqueId();
pendingUpdates.remove(playerId);
pendingFlush.remove(playerId);
plugin.viaBlocksEnabledPlayers.remove(playerId);
plugin.setPlayerEnabled(event.getPlayer(), false);
}
public void handleChunkLoad(ChunkLoadEvent event) {
if (!plugin.isEnabled() || !hasViaBlocksPlayersInWorld(event.getWorld())) return;
prepareChunkCache(event.getChunk());
}
public void prepareChunkCache(Chunk chunk) {
if (!plugin.isEnabled() || !chunk.isLoaded() || modernMaterials.isEmpty()) return;
if (plugin.chunkExecutor == null || plugin.chunkExecutor.isShutdown()) return;
String key = chunkKey(chunk.getWorld().getName(), chunk.getX(), chunk.getZ());
if (chunkPacketCache.getIfPresent(key) != null) return;
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
int minHeight = chunk.getWorld().getMinHeight();
int maxHeight = chunk.getWorld().getMaxHeight();
plugin.chunkExecutor.submit(() -> {
try {
if (chunkPacketCache.getIfPresent(key) != null) return;
Map<Integer, List<Long>> foundBlocks = findModernBlocksInChunk(snapshot, minHeight, maxHeight);
if (foundBlocks.isEmpty()) {
chunkPacketCache.put(key, EMPTY_PACKET);
} else {
chunkPacketCache.put(key, buildChunkPacket(foundBlocks));
}
} catch (Exception e) {}
});
}
public byte[] getExtraDataForChunk(String worldName, int x, int z) {
return chunkPacketCache.getIfPresent(chunkKey(worldName, x, z));
}
public void cacheChunkWithCallback(World world, int x, int z, Consumer<byte[]> callback) {
if (!plugin.isEnabled()) {
deliverCallback(callback, null);
return;
}
String key = chunkKey(world.getName(), x, z);
byte[] existing = chunkPacketCache.getIfPresent(key);
if (existing != null) {
deliverCallback(callback, existing.length > 0 ? existing : null);
return;
}
if (!world.isChunkLoaded(x, z)) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
return;
}
Chunk chunk = world.getChunkAt(x, z);
if (!chunk.isLoaded() || modernMaterials.isEmpty()) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
return;
}
if (plugin.chunkExecutor == null || plugin.chunkExecutor.isShutdown()) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
return;
}
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
int minHeight = world.getMinHeight();
int maxHeight = world.getMaxHeight();
plugin.chunkExecutor.submit(() -> {
try {
byte[] cached = chunkPacketCache.getIfPresent(key);
if (cached != null) {
deliverCallback(callback, cached.length > 0 ? cached : null);
return;
}
Map<Integer, List<Long>> foundBlocks = findModernBlocksInChunk(snapshot, minHeight, maxHeight);
if (foundBlocks.isEmpty()) {
chunkPacketCache.put(key, EMPTY_PACKET);
deliverCallback(callback, null);
} else {
@SuppressWarnings("null")
@Nonnull byte[] data = buildChunkPacket(foundBlocks);
chunkPacketCache.put(key, data);
deliverCallback(callback, data);
}
} catch (Exception e) {
deliverCallback(callback, null);
}
});
}
@SuppressWarnings("unchecked")
private Map<Integer, List<Long>> findModernBlocksInChunk(ChunkSnapshot chunkSnapshot, int minHeight, int maxHeight) {
Int2ObjectMap<LongList> foundBlocks = new Int2ObjectOpenHashMap<>();
int chunkX = chunkSnapshot.getX() << 4;
int chunkZ = chunkSnapshot.getZ() << 4;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = minHeight; y < maxHeight; y++) {
// Check material FIRST — getBlockType() returns an enum, no allocation
Material blockType = chunkSnapshot.getBlockType(x, y, z);
if (blockType == Material.AIR
|| blockType == Material.CAVE_AIR
|| blockType == Material.VOID_AIR
|| !this.modernMaterials.contains(blockType)) {
continue;
}
// Only allocate BlockData for confirmed modern blocks
@SuppressWarnings("null")
@Nonnull BlockData data = chunkSnapshot.getBlockData(x, y, z);
Integer cachedId = blockDataIdCache.getIfPresent(data);
int materialId;
if (cachedId != null) {
materialId = cachedId;
} else {
materialId = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, materialId);
}
if (materialId != -1) {
long packedLocation = packLocation(chunkX + x, y, chunkZ + z);
LongList locs = foundBlocks.get(materialId);
if (locs == null) {
locs = new LongArrayList();
foundBlocks.put(materialId, locs);
}
locs.add(packedLocation);
}
}
}
}
return (Map<Integer, List<Long>>) (Map<?, ?>) foundBlocks;
}
public byte[] getExtraDataForMultiBlock(World world, List<Long> locations) {
Map<Integer, List<Long>> foundBlocks = new HashMap<>();
for (long packedLoc : locations) {
Integer cachedId = recentModernChanges.getIfPresent(packedLoc);
if (cachedId != null) {
List<Long> locs = foundBlocks.get(cachedId);
if (locs == null) {
locs = new ArrayList<>();
foundBlocks.put(cachedId, locs);
}
locs.add(packedLoc);
continue;
}
int x = (int) (packedLoc >> 38);
int y = (int) ((packedLoc << 52) >> 52);
int z = (int) ((packedLoc << 26) >> 38);
Block block = world.getBlockAt(x, y, z);
BlockData data = block.getBlockData();
if (isModernMaterial(data.getMaterial())) {
int id = getMaterialId(data);
if (id != -1) {
List<Long> locs2 = foundBlocks.get(id);
if (locs2 == null) {
locs2 = new ArrayList<>();
foundBlocks.put(id, locs2);
}
locs2.add(packedLoc);
}
}
}
if (foundBlocks.isEmpty()) return null;
return buildChunkPacket(foundBlocks);
}
public byte[] getExtraDataForSingleBlock(World world, int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
BlockData data = block.getBlockData();
long packed = packLocation(x, y, z);
if (!isModernMaterial(data.getMaterial())) {
Integer cachedId = recentModernChanges.getIfPresent(packed);
if (cachedId != null && cachedId > 0) {
recentModernChanges.put(packed, 0);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ADD_SINGLE");
out.writeInt(0);
out.writeLong(packed);
return out.toByteArray();
}
return null;
}
int id = getMaterialId(data);
if (id == -1) return null;
recentModernChanges.put(packed, id);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ADD_SINGLE");
out.writeInt(id);
out.writeLong(packed);
return out.toByteArray();
}
public void handleBlockPlace(BlockPlaceEvent event) {
handleModernBlockChange(
event.getBlockReplacedState().getBlockData(),
event.getBlock().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockBreak(BlockBreakEvent event) {
Block block = event.getBlock();
BlockData data = block.getBlockData();
Location loc = block.getLocation();
handleModernBlockChange(data, Material.AIR.createBlockData(), loc);
if (data instanceof Door) {
Door door = (Door) data;
Location otherHalf = door.getHalf() == Bisected.Half.BOTTOM
? loc.clone().add(0, 1, 0)
: loc.clone().add(0, -1, 0);
long otherPacked = packLocation(otherHalf);
recentModernChanges.put(otherPacked, 0);
invalidateChunkCache(otherHalf.getChunk());
}
}
public void handleBlockExplode(BlockExplodeEvent event) {
for (Block block : event.blockList()) {
handleModernBlockChange(
block.getBlockData(),
Material.AIR.createBlockData(),
block.getLocation()
);
}
}
public void handleBlockFromTo(BlockFromToEvent event) {
Block destroyedBlock = event.getToBlock();
handleModernBlockChange(
destroyedBlock.getBlockData(),
Material.AIR.createBlockData(),
destroyedBlock.getLocation()
);
}
public void handleBlockGrow(BlockGrowEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockFade(BlockFadeEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockForm(BlockFormEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockSpread(BlockSpreadEvent event) {
handleModernBlockChange(
event.getBlock().getBlockData(),
event.getNewState().getBlockData(),
event.getBlock().getLocation()
);
}
public void handleBlockPhysics(BlockPhysicsEvent event) {
Block block = event.getBlock();
if (isModernMaterial(block.getType())) {
BlockData data = block.getBlockData();
long packed = packLocation(block.getLocation());
Integer cachedId = recentModernChanges.getIfPresent(packed);
int currentId = getMaterialId(data);
if (cachedId == null || cachedId != currentId) {
recentModernChanges.put(packed, currentId);
sendBlockStateUpdateToNearbyPlayers(block.getLocation(), data);
invalidateChunkCache(block.getChunk());
}
}
}
private void handleModernBlockChange(BlockData before, BlockData after, Location location) {
if (!plugin.isEnabled() || location == null) return;
boolean afterModern = after != null && isModernMaterial(after.getMaterial());
boolean beforeModern = before != null && isModernMaterial(before.getMaterial());
long packed = packLocation(location);
if (afterModern) {
int id = getMaterialId(after);
recentModernChanges.put(packed, id);
sendBlockStateUpdateToNearbyPlayers(location, after);
} else if (beforeModern || recentModernChanges.getIfPresent(packed) != null) {
recentModernChanges.put(packed, 0);
sendClearUpdateToNearbyPlayers(location);
} else {
recentModernChanges.invalidate(packed);
}
invalidateChunkCache(location.getChunk());
}
public Integer getRecentChange(long packed) {
return recentModernChanges.getIfPresent(packed);
}
private void sendBlockStateUpdateToNearbyPlayers(Location location, BlockData data) {
if (!plugin.isEnabled() || data == null || location.getWorld() == null) return;
Integer cachedId = blockDataIdCache.getIfPresent(data);
int stateId;
if (cachedId != null) {
stateId = cachedId;
} else {
stateId = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, stateId);
}
if (stateId == -1) return;
scheduleNearbyEnabledPlayers(location, player -> sendPacket(player, stateId, location));
}
private void sendClearUpdateToNearbyPlayers(Location location) {
if (!plugin.isEnabled() || plugin.viaBlocksEnabledPlayers.isEmpty() || location.getWorld() == null) return;
final int AIR_ID = 0;
scheduleNearbyEnabledPlayers(location, player -> sendPacket(player, AIR_ID, location));
}
private void invalidateChunkCache(Chunk chunk) {
if (chunk == null) return;
chunkPacketCache.invalidate(chunkKey(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()));
}
private void sendPacket(Player player, int stateId, Location location) {
if (!player.isOnline()) return;
UUID playerId = player.getUniqueId();
Map<Integer, List<Long>> updateData = pendingUpdates.get(playerId);
if (updateData == null) {
updateData = new HashMap<>();
pendingUpdates.put(playerId, updateData);
}
List<Long> stateList = updateData.get(stateId);
if (stateList == null) {
stateList = new ArrayList<>();
updateData.put(stateId, stateList);
}
stateList.add(packLocation(location));
if (pendingFlush.add(playerId)) {
SchedulerCompat.runEntityLater(player, plugin.plugin, () -> flushPendingUpdates(playerId), plugin.getUpdateBatchDelayTicks());
}
}
private void flushPendingUpdates(UUID playerId) {
Map<Integer, List<Long>> updateData = pendingUpdates.remove(playerId);
pendingFlush.remove(playerId);
if (updateData == null || updateData.isEmpty()) return;
Player player = plugin.plugin.getServer().getPlayer(playerId);
if (player == null || !player.isOnline()) return;
byte[] packetData = buildChunkPacket(updateData);
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, packetData);
}
private int getMaterialId(BlockData data) {
@SuppressWarnings("null")
Integer cachedId = blockDataIdCache.getIfPresent(data);
if (cachedId != null) return cachedId;
int id = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, id);
return id;
}
@SuppressWarnings("null")
private @Nonnull byte[] buildChunkPacket(Map<Integer, List<Long>> blockData) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ADD_CHUNK");
out.writeInt(blockData.size());
for (Map.Entry<Integer, List<Long>> entry : blockData.entrySet()) {
out.writeInt(entry.getKey());
out.writeInt(entry.getValue().size());
for (Long loc : entry.getValue()) {
out.writeLong(loc);
}
}
return out.toByteArray();
}
public void sendPaletteToClient(Player player) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("INIT_PALETTE");
List<String> palette = this.paletteManager.getPalette();
out.writeInt(palette.size());
for (String state : palette) {
out.writeUTF(state);
}
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, out.toByteArray());
}
public boolean isModernMaterial(Material material) {
return this.modernMaterials.contains(material);
}
public void processChunkForSinglePlayer(Chunk chunk, Player player) {
if (!chunk.isLoaded() || !plugin.isPlayerEnabled(player)) return;
cacheChunkWithCallback(chunk.getWorld(), chunk.getX(), chunk.getZ(), data -> {
if (!player.isOnline() || !plugin.isPlayerEnabled(player)) return;
if (data != null && data.length > 0) {
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, data);
}
});
}
public void clearCache() {
blockDataIdCache.invalidateAll();
pendingUpdates.clear();
pendingFlush.clear();
chunkPacketCache.invalidateAll();
recentModernChanges.invalidateAll();
}
private void deliverCallback(Consumer<byte[]> callback, byte[] data) {
if (callback != null) {
callback.accept(data);
}
}
private boolean hasViaBlocksPlayersInWorld(World world) {
for (UUID playerId : plugin.viaBlocksEnabledPlayers) {
Player player = Bukkit.getPlayer(playerId);
if (player != null && player.isOnline() && world.equals(player.getWorld())) {
return true;
}
}
return false;
}
private void scheduleNearbyEnabledPlayers(Location location, Consumer<Player> action) {
World world = location.getWorld();
if (world == null) return;
SchedulerCompat.runGlobal(plugin.plugin, () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!plugin.isPlayerEnabled(player)) continue;
SchedulerCompat.runEntity(player, plugin.plugin, () -> {
if (!player.isOnline()) return;
if (!world.equals(player.getWorld())) return;
if (player.getLocation().distanceSquared(location) >= UPDATE_RADIUS_SQUARED) return;
action.accept(player);
});
}
});
}
public long packLocation(int x, int y, int z) {
return ((long)x & X_MASK) << X_SHIFT | ((long)z & Z_MASK) << Z_SHIFT | ((long)y & Y_MASK);
}
public long packLocation(Location loc) {
return packLocation(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
}
@@ -0,0 +1,137 @@
package tf.tuff.viablocks;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import tf.tuff.util.SchedulerCompat;
import tf.tuff.viablocks.version.VersionAdapter;
public class PaletteManager {
ViaBlocksPlugin plugin;
private final CopyOnWriteArrayList<String> palette = new CopyOnWriteArrayList<>();
private final Map<String, Integer> stateToIdMap = new ConcurrentHashMap<>();
private volatile List<String> paletteSnapshot;
public PaletteManager(VersionAdapter versionAdapter) {
plugin = ViaBlocksPlugin.instance;
generate(versionAdapter);
}
private void generate(VersionAdapter versionAdapter) {
plugin.info("Generating ViaBlocks Pre-Defined Palette...");
List<String> initialPalette = new ArrayList<>();
for (Material material : versionAdapter.getModernMaterials()) {
if (material.isBlock()) {
addEntryInternal(initialPalette, versionAdapter.getMaterialKey(material));
}
}
String[] thickness = {"tip", "frustum", "middle", "base"};
String[] direction = {"up", "down"};
String[] bools = {"true", "false"};
String[] facings = {"north", "south", "east", "west"};
String[] tilts = {"none", "unbalanced", "partial", "full"};
for (String d : direction) {
for (String t : thickness) {
for (String w : bools) {
addEntryInternal(initialPalette, "minecraft:pointed_dripstone[thickness=" + t + ",vertical_direction=" + d + ",waterlogged=" + w + "]");
}
}
}
for (String f : facings) {
for (String t : tilts) {
for (String w : bools) {
addEntryInternal(initialPalette, "minecraft:big_dripleaf[facing=" + f + ",tilt=" + t + ",waterlogged=" + w + "]");
}
}
}
for (String f : facings) {
for (String h : new String[]{"lower", "upper"}) {
addEntryInternal(initialPalette, "minecraft:small_dripleaf[facing=" + f + ",half=" + h + ",waterlogged=false]");
}
}
for (String b : bools) {
addEntryInternal(initialPalette, "minecraft:cave_vines[age=0,berries=" + b + "]");
addEntryInternal(initialPalette, "minecraft:cave_vines_plant[berries=" + b + "]");
}
palette.addAll(initialPalette);
paletteSnapshot = new ArrayList<>(palette);
plugin.info("Palette initialized with " + palette.size() + " entries.");
}
private void addEntryInternal(List<String> localPalette, String state) {
if (!stateToIdMap.containsKey(state)) {
stateToIdMap.put(state, localPalette.size());
localPalette.add(state);
}
}
public int getOrCreateId(String state) {
Integer id = stateToIdMap.get(state);
if (id != null) {
return id;
}
synchronized (this) {
id = stateToIdMap.get(state);
if (id != null) return id;
int newId = palette.size();
palette.add(state);
stateToIdMap.put(state, newId);
paletteSnapshot = null;
broadcastNewPaletteEntry(state);
return newId;
}
}
private void broadcastNewPaletteEntry(String state) {
if (plugin == null || !plugin.plugin.isEnabled() || !plugin.isEnabled()) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("NEW_PALETTE_ENTRY");
out.writeUTF(state);
byte[] data = out.toByteArray();
SchedulerCompat.runGlobal(plugin.plugin, () -> {
if (!plugin.plugin.isEnabled() || !plugin.isEnabled()) return;
for (Player player : Bukkit.getOnlinePlayers()) {
if (plugin.isPlayerEnabled(player)) {
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaBlocksPlugin.CLIENTBOUND_CHANNEL, data);
}
}
});
}
public synchronized int getId(String state) {
return stateToIdMap.getOrDefault(state, -1);
}
public synchronized List<String> getPalette() {
List<String> snapshot = paletteSnapshot;
if (snapshot == null) {
snapshot = new ArrayList<>(palette);
paletteSnapshot = snapshot;
}
return snapshot;
}
}
@@ -0,0 +1,317 @@
package tf.tuff.viablocks;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import tf.tuff.viablocks.version.VersionAdapter;
import tf.tuff.viablocks.version.modern.ModernAdapter;
import tf.tuff.TuffX;
import tf.tuff.util.SchedulerCompat;
public final class ViaBlocksPlugin {
public static final String CLIENTBOUND_CHANNEL = "viablocks:data";
public static final String SERVERBOUND_CHANNEL = "viablocks:handshake";
public final Set<UUID> viaBlocksEnabledPlayers = ConcurrentHashMap.newKeySet();
public CustomBlockListener blockListener;
static ViaBlocksPlugin instance;
private File playerDataFile;
private FileConfiguration playerDataConfig;
private final Set<UUID> joinedPlayersCache = ConcurrentHashMap.newKeySet();
private boolean enabled;
private boolean debug;
private boolean sendWelcomeBook;
public VersionAdapter versionAdapter;
public PaletteManager paletteManager;
private long updateBatchDelayTicks = 1L;
public ExecutorService chunkExecutor;
public TuffX plugin;
public ViaBlocksPlugin(TuffX plugin){
this.plugin = plugin;
}
public void onTuffXReload() {
Set<UUID> previouslyEnabledPlayers = ConcurrentHashMap.newKeySet();
previouslyEnabledPlayers.addAll(viaBlocksEnabledPlayers);
loadSyncSettings();
if (chunkExecutor != null) {
chunkExecutor.shutdownNow();
}
this.chunkExecutor = enabled
? Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()))
: null;
if (playerDataFile == null) {
playerDataFile = new File(plugin.getDataFolder(), "players.yml");
}
playerDataConfig = YamlConfiguration.loadConfiguration(playerDataFile);
if (blockListener != null) {
blockListener.clearCache();
}
viaBlocksEnabledPlayers.clear();
if (enabled && blockListener != null) {
for (Player player : plugin.getServer().getOnlinePlayers()) {
if (!previouslyEnabledPlayers.contains(player.getUniqueId())) continue;
setPlayerEnabled(player, true);
blockListener.onViaBlocksPlayerJoin(player);
}
}
info("ViaBlocks reloaded.");
}
public void onTuffXEnable() {
instance = this;
this.versionAdapter = new ModernAdapter();
this.paletteManager = new PaletteManager(this.versionAdapter);
plugin.saveDefaultConfig();
loadSyncSettings();
this.chunkExecutor = enabled
? Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()))
: null;
setupPlayerData();
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL, plugin);
this.blockListener = new CustomBlockListener(this, this.versionAdapter, this.paletteManager);
plugin.getCommand("viablocks").setExecutor(plugin);
if (enabled) {
info("ViaBlocks has been enabled successfully and is listening for client handshakes.");
} else {
info("ViaBlocks is disabled in config.");
}
}
public void handlePacket(Player player, byte[] message) {
if (!isEnabled() || blockListener == null) return;
if (!isPlayerEnabled(player) && isEnabled()) {
debug("Received ViaBlocks handshake from player: " + player.getName() + ". Enabling custom blocks.");
setPlayerEnabled(player, true);
blockListener.onViaBlocksPlayerJoin(player);
}
}
public PaletteManager getPaletteManager() {
return this.paletteManager;
}
public long getUpdateBatchDelayTicks() {
return this.updateBatchDelayTicks;
}
private void loadSyncSettings() {
enabled = plugin.getConfig().getBoolean("viablocks.viablocks-enabled", false);
debug = plugin.getConfig().getBoolean("viablocks.debug", false);
sendWelcomeBook = plugin.getConfig().getBoolean("viablocks.send-welcome-book", true);
String mode = plugin.getConfig().getString("viablocks.sync-mode", "normal");
if (mode == null) {
mode = "normal";
}
this.updateBatchDelayTicks = mode.equalsIgnoreCase("reduced") ? 10L : 1L;
}
public void onTuffXDisable(){
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL);
if (chunkExecutor != null) {
chunkExecutor.shutdownNow();
chunkExecutor = null;
}
viaBlocksEnabledPlayers.clear();
info("ViaBlocks has been disabled.");
}
private void setupPlayerData() {
playerDataFile = new File(plugin.getDataFolder(), "players.yml");
if (!playerDataFile.exists()) {
try {
playerDataFile.createNewFile();
} catch (IOException e) {
severe("Could not create players.yml!");
e.printStackTrace();
}
}
playerDataConfig = YamlConfiguration.loadConfiguration(playerDataFile);
joinedPlayersCache.clear();
for (String uuidStr : playerDataConfig.getStringList("joined-players")) {
try {
joinedPlayersCache.add(UUID.fromString(uuidStr));
} catch (IllegalArgumentException ignored) {
}
}
}
public boolean hasPlayerJoinedBefore(Player player) {
return joinedPlayersCache.contains(player.getUniqueId());
}
public boolean isFirstJoin(Player player) {
return !hasPlayerJoinedBefore(player);
}
public void markPlayerAsJoined(Player player) {
UUID uuid = player.getUniqueId();
if (joinedPlayersCache.add(uuid)) {
List<String> joinedPlayers = playerDataConfig.getStringList("joined-players");
joinedPlayers.add(uuid.toString());
playerDataConfig.set("joined-players", joinedPlayers);
try {
playerDataConfig.save(playerDataFile);
} catch (IOException e) {
severe("Could not save to players.yml!");
e.printStackTrace();
}
}
}
public void sendWelcomeGui(Player player) {
if (!this.sendWelcomeBook) return;
ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) book.getItemMeta();
if (meta == null) return;
meta.setTitle("ViaBlocks Information");
meta.setAuthor("ViaBlocks");
TextComponent welcome = new TextComponent("Welcome to ViaBlocks!");
welcome.setColor(ChatColor.DARK_AQUA);
welcome.setBold(true);
TextComponent body = new TextComponent("\n\nThis feature is in active development!\n\nIf you find any visual bugs or issues, please report them on our ");
body.setColor(ChatColor.BLACK);
TextComponent link = new TextComponent("bug tracker");
link.setColor(ChatColor.BLUE);
link.setUnderlined(true);
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/TuffNetwork/Tuff-Client-Builds/issues"));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(new ComponentBuilder("Click to open the bug tracker!").color(ChatColor.GRAY).create())));
TextComponent disclaimer = new TextComponent("\n\n(Bamboo and kelp are noted.)");
disclaimer.setColor(ChatColor.DARK_GRAY);
disclaimer.setItalic(true);
meta.spigot().addPage(new ComponentBuilder("").append(welcome).append(body).append(link).append(new TextComponent(".")).append(disclaimer).create());
book.setItemMeta(meta);
SchedulerCompat.runEntity(player, plugin, () -> player.openBook(book));
}
public boolean isEnabled() {
return enabled;
}
public boolean isPlayerEnabled(Player player) {
if (player == null) return false;
return viaBlocksEnabledPlayers.contains(player.getUniqueId());
}
public void setPlayerEnabled(Player player, boolean enabled) {
if (enabled && isEnabled()) {
viaBlocksEnabledPlayers.add(player.getUniqueId());
} else {
viaBlocksEnabledPlayers.remove(player.getUniqueId());
}
}
public CustomBlockListener getBlockListener() {
return this.blockListener;
}
public boolean onTuffXCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("This command can only be executed by a player.");
return true;
}
Player player = (Player) sender;
if (args.length > 0) {
if (args[0].equalsIgnoreCase("get")) {
if (!player.hasPermission("tuffx.viablocks.command.get")) {
player.sendMessage("\u00A7cYou do not have permission to use this command.");
return true;
}
this.versionAdapter.giveCustomBlocks(player);
player.sendMessage("\u00A7aYou have been given a set of custom blocks.");
return true;
} else if (args[0].equalsIgnoreCase("refresh")) {
if (!player.hasPermission("tuffx.viablocks.command.refresh")) {
player.sendMessage("\u00A7cYou do not have permission to use this command.");
return true;
}
if (!isEnabled() || blockListener == null) {
player.sendMessage("\u00A7cViaBlocks is disabled.");
return true;
}
player.sendMessage("\u00A7aRefreshing modern blocks in your view distance...");
World world = player.getWorld();
int viewDistance = this.versionAdapter.getClientViewDistance(player);
int playerChunkX = player.getLocation().getChunk().getX();
int playerChunkZ = player.getLocation().getChunk().getZ();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int chunkX = playerChunkX + x;
int chunkZ = playerChunkZ + z;
if (world.isChunkLoaded(chunkX, chunkZ)) {
blockListener.processChunkForSinglePlayer(world.getChunkAt(chunkX, chunkZ), player);
}
}
}
player.sendMessage("\u00A7aRefresh complete!");
return true;
}
}
player.sendMessage("\u00A7cInvalid usage. Use: /viablocks <get|refresh>");
return true;
}
public boolean isDebug() {
return debug;
}
public void debug(String message) {
if (isDebug()) info(message);
}
public void log(Level level, String msg, Throwable e) {
plugin.getLogger().log(level, "[ViaBlocks] "+msg, e);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[ViaBlocks] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void severe(String msg) {
log(Level.SEVERE, msg);
}
}
@@ -0,0 +1,14 @@
package tf.tuff.viablocks.version;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.util.EnumSet;
public interface VersionAdapter {
String getBlockDataString(Block block);
String getMaterialKey(Material material);
int getClientViewDistance(Player player);
void giveCustomBlocks(Player player);
EnumSet<Material> getModernMaterials();
}
@@ -0,0 +1,669 @@
package tf.tuff.viablocks.version.modern;
import tf.tuff.viablocks.version.VersionAdapter;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Method;
import java.util.EnumSet;
public class ModernAdapter implements VersionAdapter {
private static final String[] MODERN_MATERIAL_NAMES = {
"TUBE_CORAL",
"BRAIN_CORAL",
"BUBBLE_CORAL",
"FIRE_CORAL",
"HORN_CORAL",
"TUBE_CORAL_BLOCK",
"BRAIN_CORAL_BLOCK",
"BUBBLE_CORAL_BLOCK",
"FIRE_CORAL_BLOCK",
"HORN_CORAL_BLOCK",
"TUBE_CORAL_FAN",
"BRAIN_CORAL_FAN",
"BUBBLE_CORAL_FAN",
"FIRE_CORAL_FAN",
"HORN_CORAL_FAN",
"TUBE_CORAL_WALL_FAN",
"BRAIN_CORAL_WALL_FAN",
"BUBBLE_CORAL_WALL_FAN",
"FIRE_CORAL_WALL_FAN",
"HORN_CORAL_WALL_FAN",
"DEAD_TUBE_CORAL",
"DEAD_BRAIN_CORAL",
"DEAD_BUBBLE_CORAL",
"DEAD_FIRE_CORAL",
"DEAD_HORN_CORAL",
"DEAD_TUBE_CORAL_BLOCK",
"DEAD_BRAIN_CORAL_BLOCK",
"DEAD_BUBBLE_CORAL_BLOCK",
"DEAD_FIRE_CORAL_BLOCK",
"DEAD_HORN_CORAL_BLOCK",
"DEAD_TUBE_CORAL_FAN",
"DEAD_BRAIN_CORAL_FAN",
"DEAD_BUBBLE_CORAL_FAN",
"DEAD_FIRE_CORAL_FAN",
"DEAD_HORN_CORAL_FAN",
"DEAD_TUBE_CORAL_WALL_FAN",
"DEAD_BRAIN_CORAL_WALL_FAN",
"DEAD_BUBBLE_CORAL_WALL_FAN",
"DEAD_FIRE_CORAL_WALL_FAN",
"DEAD_HORN_CORAL_WALL_FAN",
"BUBBLE_COLUMN",
"SEA_PICKLE",
"KELP",
"KELP_PLANT",
"DRIED_KELP_BLOCK",
"SEAGRASS",
"TALL_SEAGRASS",
"STRIPPED_OAK_LOG",
"STRIPPED_SPRUCE_LOG",
"STRIPPED_BIRCH_LOG",
"STRIPPED_JUNGLE_LOG",
"STRIPPED_ACACIA_LOG",
"STRIPPED_DARK_OAK_LOG",
"STRIPPED_OAK_WOOD",
"STRIPPED_SPRUCE_WOOD",
"STRIPPED_BIRCH_WOOD",
"STRIPPED_JUNGLE_WOOD",
"STRIPPED_ACACIA_WOOD",
"STRIPPED_DARK_OAK_WOOD",
"OAK_WOOD",
"SPRUCE_WOOD",
"BIRCH_WOOD",
"JUNGLE_WOOD",
"ACACIA_WOOD",
"DARK_OAK_WOOD",
"BLUE_ICE",
"CONDUIT",
"PRISMARINE_STAIRS",
"PRISMARINE_BRICK_STAIRS",
"DARK_PRISMARINE_STAIRS",
"PRISMARINE_SLAB",
"PRISMARINE_BRICK_SLAB",
"DARK_PRISMARINE_SLAB",
"TURTLE_EGG",
"SMOOTH_STONE",
"SMOOTH_SANDSTONE",
"SMOOTH_QUARTZ",
"SMOOTH_RED_SANDSTONE",
"CUT_SANDSTONE",
"CUT_RED_SANDSTONE",
"SMOOTH_STONE_SLAB",
"CUT_SANDSTONE_SLAB",
"CUT_RED_SANDSTONE_SLAB",
"BARREL",
"BLAST_FURNACE",
"SMOKER",
"CARTOGRAPHY_TABLE",
"FLETCHING_TABLE",
"GRINDSTONE",
"LECTERN",
"SMITHING_TABLE",
"STONECUTTER",
"BELL",
"LANTERN",
"SCAFFOLDING",
"LOOM",
"COMPOSTER",
"STONE_STAIRS",
"GRANITE_STAIRS",
"POLISHED_GRANITE_STAIRS",
"DIORITE_STAIRS",
"POLISHED_DIORITE_STAIRS",
"ANDESITE_STAIRS",
"POLISHED_ANDESITE_STAIRS",
"STONE_SLAB",
"GRANITE_SLAB",
"POLISHED_GRANITE_SLAB",
"DIORITE_SLAB",
"POLISHED_DIORITE_SLAB",
"ANDESITE_SLAB",
"POLISHED_ANDESITE_SLAB",
"MOSSY_STONE_BRICK_STAIRS",
"MOSSY_STONE_BRICK_SLAB",
"MOSSY_COBBLESTONE_STAIRS",
"MOSSY_COBBLESTONE_SLAB",
"END_STONE_BRICK_STAIRS",
"END_STONE_BRICK_SLAB",
"RED_NETHER_BRICK_STAIRS",
"RED_NETHER_BRICK_SLAB",
"SMOOTH_SANDSTONE_STAIRS",
"SMOOTH_RED_SANDSTONE_STAIRS",
"SMOOTH_QUARTZ_STAIRS",
"GRANITE_WALL",
"DIORITE_WALL",
"ANDESITE_WALL",
"SANDSTONE_WALL",
"RED_SANDSTONE_WALL",
"BRICK_WALL",
"STONE_BRICK_WALL",
"MOSSY_STONE_BRICK_WALL",
"NETHER_BRICK_WALL",
"RED_NETHER_BRICK_WALL",
"CRACKED_NETHER_BRICKS",
"CHISELED_NETHER_BRICKS",
"END_STONE_BRICK_WALL",
"PRISMARINE_WALL",
"CORNFLOWER",
"LILY_OF_THE_VALLEY",
"WITHER_ROSE",
"SWEET_BERRY_BUSH",
"BAMBOO",
"BAMBOO_SAPLING",
"JIGSAW",
"BEE_NEST",
"BEEHIVE",
"HONEY_BLOCK",
"HONEYCOMB_BLOCK",
"CRIMSON_STEM",
"WARPED_STEM",
"STRIPPED_CRIMSON_STEM",
"STRIPPED_WARPED_STEM",
"CRIMSON_HYPHAE",
"WARPED_HYPHAE",
"STRIPPED_CRIMSON_HYPHAE",
"STRIPPED_WARPED_HYPHAE",
"CRIMSON_NYLIUM",
"WARPED_NYLIUM",
"CRIMSON_PLANKS",
"WARPED_PLANKS",
"CRIMSON_STAIRS",
"WARPED_STAIRS",
"CRIMSON_SLAB",
"WARPED_SLAB",
"CRIMSON_FENCE",
"WARPED_FENCE",
"CRIMSON_DOOR",
"WARPED_DOOR",
"CRIMSON_TRAPDOOR",
"WARPED_TRAPDOOR",
"CRIMSON_FENCE_GATE",
"WARPED_FENCE_GATE",
"CRIMSON_BUTTON",
"WARPED_BUTTON",
"CRIMSON_PRESSURE_PLATE",
"WARPED_PRESSURE_PLATE",
"CRIMSON_SIGN",
"WARPED_SIGN",
"CRIMSON_WALL_SIGN",
"WARPED_WALL_SIGN",
"NETHER_GOLD_ORE",
"ANCIENT_DEBRIS",
"CRYING_OBSIDIAN",
"RESPAWN_ANCHOR",
"NETHERITE_BLOCK",
"SOUL_SOIL",
"BASALT",
"POLISHED_BASALT",
"SMOOTH_BASALT",
"SOUL_TORCH",
"SOUL_WALL_TORCH",
"SOUL_LANTERN",
"SOUL_FIRE",
"CAMPFIRE",
"SOUL_CAMPFIRE",
"SHROOMLIGHT",
"TARGET",
"BLACKSTONE",
"GILDED_BLACKSTONE",
"POLISHED_BLACKSTONE",
"POLISHED_BLACKSTONE_BRICKS",
"CHISELED_POLISHED_BLACKSTONE",
"CRACKED_POLISHED_BLACKSTONE_BRICKS",
"BLACKSTONE_STAIRS",
"POLISHED_BLACKSTONE_STAIRS",
"POLISHED_BLACKSTONE_BRICK_STAIRS",
"BLACKSTONE_SLAB",
"POLISHED_BLACKSTONE_SLAB",
"POLISHED_BLACKSTONE_BRICK_SLAB",
"BLACKSTONE_WALL",
"POLISHED_BLACKSTONE_WALL",
"POLISHED_BLACKSTONE_BRICK_WALL",
"CRIMSON_ROOTS",
"WARPED_ROOTS",
"NETHER_SPROUTS",
"CRIMSON_FUNGUS",
"WARPED_FUNGUS",
"WEEPING_VINES",
"WEEPING_VINES_PLANT",
"TWISTING_VINES",
"TWISTING_VINES_PLANT",
"CHAIN",
"IRON_CHAIN",
"LODESTONE",
"DEEPSLATE",
"INFESTED_DEEPSLATE",
"COBBLED_DEEPSLATE",
"POLISHED_DEEPSLATE",
"DEEPSLATE_BRICKS",
"CRACKED_DEEPSLATE_BRICKS",
"DEEPSLATE_TILES",
"CRACKED_DEEPSLATE_TILES",
"CHISELED_DEEPSLATE",
"COBBLED_DEEPSLATE_STAIRS",
"POLISHED_DEEPSLATE_STAIRS",
"DEEPSLATE_BRICK_STAIRS",
"DEEPSLATE_TILE_STAIRS",
"COBBLED_DEEPSLATE_SLAB",
"POLISHED_DEEPSLATE_SLAB",
"DEEPSLATE_BRICK_SLAB",
"DEEPSLATE_TILE_SLAB",
"COBBLED_DEEPSLATE_WALL",
"POLISHED_DEEPSLATE_WALL",
"DEEPSLATE_BRICK_WALL",
"DEEPSLATE_TILE_WALL",
"DEEPSLATE_COAL_ORE",
"DEEPSLATE_IRON_ORE",
"DEEPSLATE_GOLD_ORE",
"DEEPSLATE_DIAMOND_ORE",
"DEEPSLATE_LAPIS_ORE",
"DEEPSLATE_REDSTONE_ORE",
"DEEPSLATE_EMERALD_ORE",
"DEEPSLATE_COPPER_ORE",
"COPPER_ORE",
"COPPER_BLOCK",
"CUT_COPPER",
"COPPER_CHAIN",
"EXPOSED_COPPER",
"EXPOSED_CUT_COPPER",
"EXPOSED_COPPER_CHAIN",
"WEATHERED_COPPER",
"WEATHERED_CUT_COPPER",
"WEATHERED_COPPER_CHAIN",
"OXIDIZED_COPPER",
"OXIDIZED_CUT_COPPER",
"OXIDIZED_COPPER_CHAIN",
"WAXED_COPPER_BLOCK",
"WAXED_CUT_COPPER",
"WAXED_COPPER_CHAIN",
"WAXED_EXPOSED_COPPER",
"WAXED_EXPOSED_CUT_COPPER",
"WAXED_EXPOSED_COPPER_CHAIN",
"WAXED_WEATHERED_COPPER",
"WAXED_WEATHERED_CUT_COPPER",
"WAXED_WEATHERED_COPPER_CHAIN",
"WAXED_OXIDIZED_COPPER",
"WAXED_OXIDIZED_CUT_COPPER",
"WAXED_OXIDIZED_COPPER_CHAIN",
"CUT_COPPER_STAIRS",
"EXPOSED_CUT_COPPER_STAIRS",
"WEATHERED_CUT_COPPER_STAIRS",
"OXIDIZED_CUT_COPPER_STAIRS",
"CUT_COPPER_SLAB",
"EXPOSED_CUT_COPPER_SLAB",
"WEATHERED_CUT_COPPER_SLAB",
"OXIDIZED_CUT_COPPER_SLAB",
"WAXED_CUT_COPPER_STAIRS",
"WAXED_EXPOSED_CUT_COPPER_STAIRS",
"WAXED_WEATHERED_CUT_COPPER_STAIRS",
"WAXED_OXIDIZED_CUT_COPPER_STAIRS",
"WAXED_CUT_COPPER_SLAB",
"WAXED_EXPOSED_CUT_COPPER_SLAB",
"WAXED_WEATHERED_CUT_COPPER_SLAB",
"WAXED_OXIDIZED_CUT_COPPER_SLAB",
"RAW_COPPER_BLOCK",
"RAW_IRON_BLOCK",
"RAW_GOLD_BLOCK",
"TUFF",
"CALCITE",
"AMETHYST_BLOCK",
"BUDDING_AMETHYST",
"AMETHYST_CLUSTER",
"LARGE_AMETHYST_BUD",
"MEDIUM_AMETHYST_BUD",
"SMALL_AMETHYST_BUD",
"DRIPSTONE_BLOCK",
"POINTED_DRIPSTONE",
"MOSS_BLOCK",
"MOSS_CARPET",
"HANGING_ROOTS",
"ROOTED_DIRT",
"AZALEA",
"FLOWERING_AZALEA",
"AZALEA_LEAVES",
"FLOWERING_AZALEA_LEAVES",
"SPORE_BLOSSOM",
"CAVE_VINES",
"CAVE_VINES_PLANT",
"GLOW_BERRIES",
"TINTED_GLASS",
"LIGHT",
"SCULK_SENSOR",
"CANDLE",
"WHITE_CANDLE",
"ORANGE_CANDLE",
"MAGENTA_CANDLE",
"LIGHT_BLUE_CANDLE",
"YELLOW_CANDLE",
"LIME_CANDLE",
"PINK_CANDLE",
"GRAY_CANDLE",
"LIGHT_GRAY_CANDLE",
"CYAN_CANDLE",
"PURPLE_CANDLE",
"BLUE_CANDLE",
"BROWN_CANDLE",
"GREEN_CANDLE",
"RED_CANDLE",
"BLACK_CANDLE",
"CANDLE_CAKE",
"WHITE_CANDLE_CAKE",
"ORANGE_CANDLE_CAKE",
"MAGENTA_CANDLE_CAKE",
"LIGHT_BLUE_CANDLE_CAKE",
"YELLOW_CANDLE_CAKE",
"LIME_CANDLE_CAKE",
"PINK_CANDLE_CAKE",
"GRAY_CANDLE_CAKE",
"LIGHT_GRAY_CANDLE_CAKE",
"CYAN_CANDLE_CAKE",
"PURPLE_CANDLE_CAKE",
"BLUE_CANDLE_CAKE",
"BROWN_CANDLE_CAKE",
"GREEN_CANDLE_CAKE",
"RED_CANDLE_CAKE",
"BLACK_CANDLE_CAKE",
"POWDER_SNOW",
"LIGHTNING_ROD",
"GLOW_LICHEN",
"BIG_DRIPLEAF",
"BIG_DRIPLEAF_STEM",
"SMALL_DRIPLEAF",
"MANGROVE_LOG",
"STRIPPED_MANGROVE_LOG",
"MANGROVE_WOOD",
"STRIPPED_MANGROVE_WOOD",
"MANGROVE_PLANKS",
"MANGROVE_STAIRS",
"MANGROVE_SLAB",
"MANGROVE_FENCE",
"MANGROVE_FENCE_GATE",
"MANGROVE_DOOR",
"MANGROVE_TRAPDOOR",
"MANGROVE_BUTTON",
"MANGROVE_PRESSURE_PLATE",
"MANGROVE_SIGN",
"MANGROVE_WALL_SIGN",
"MANGROVE_LEAVES",
"MANGROVE_ROOTS",
"MUDDY_MANGROVE_ROOTS",
"MANGROVE_PROPAGULE",
"MUD",
"PACKED_MUD",
"MUD_BRICKS",
"MUD_BRICK_STAIRS",
"MUD_BRICK_SLAB",
"MUD_BRICK_WALL",
"PEARLESCENT_FROGLIGHT",
"VERDANT_FROGLIGHT",
"OCHRE_FROGLIGHT",
"FROGSPAWN",
"REINFORCED_DEEPSLATE",
"SCULK",
"SCULK_CATALYST",
"SCULK_SHRIEKER",
"SCULK_VEIN",
"CHERRY_LOG",
"STRIPPED_CHERRY_LOG",
"CHERRY_WOOD",
"STRIPPED_CHERRY_WOOD",
"CHERRY_PLANKS",
"CHERRY_STAIRS",
"CHERRY_SLAB",
"CHERRY_FENCE",
"CHERRY_FENCE_GATE",
"CHERRY_DOOR",
"CHERRY_TRAPDOOR",
"CHERRY_BUTTON",
"CHERRY_PRESSURE_PLATE",
"CHERRY_SIGN",
"CHERRY_WALL_SIGN",
"CHERRY_LEAVES",
"CHERRY_SAPLING",
"PINK_PETALS",
"BAMBOO_BLOCK",
"STRIPPED_BAMBOO_BLOCK",
"BAMBOO_PLANKS",
"BAMBOO_STAIRS",
"BAMBOO_SLAB",
"BAMBOO_FENCE",
"BAMBOO_FENCE_GATE",
"BAMBOO_DOOR",
"BAMBOO_TRAPDOOR",
"BAMBOO_BUTTON",
"BAMBOO_PRESSURE_PLATE",
"BAMBOO_SIGN",
"BAMBOO_WALL_SIGN",
"BAMBOO_MOSAIC",
"BAMBOO_MOSAIC_STAIRS",
"BAMBOO_MOSAIC_SLAB",
"BAMBOO_HANGING_SIGN",
"OAK_HANGING_SIGN",
"SPRUCE_HANGING_SIGN",
"BIRCH_HANGING_SIGN",
"JUNGLE_HANGING_SIGN",
"ACACIA_HANGING_SIGN",
"DARK_OAK_HANGING_SIGN",
"MANGROVE_HANGING_SIGN",
"CHERRY_HANGING_SIGN",
"CRIMSON_HANGING_SIGN",
"WARPED_HANGING_SIGN",
"CHISELED_BOOKSHELF",
"SUSPICIOUS_SAND",
"SUSPICIOUS_GRAVEL",
"DECORATED_POT",
"PIGLIN_HEAD",
"CALIBRATED_SCULK_SENSOR",
"TORCHFLOWER",
"TORCHFLOWER_CROP",
"PITCHER_PLANT",
"PITCHER_CROP",
"SNIFFER_EGG",
"BRUSH",
"CRAFTER",
"TRIAL_SPAWNER",
"VAULT",
"TUFF_STAIRS",
"TUFF_SLAB",
"TUFF_WALL",
"POLISHED_TUFF",
"POLISHED_TUFF_STAIRS",
"POLISHED_TUFF_SLAB",
"POLISHED_TUFF_WALL",
"TUFF_BRICKS",
"TUFF_BRICK_STAIRS",
"TUFF_BRICK_SLAB",
"TUFF_BRICK_WALL",
"CHISELED_TUFF",
"CHISELED_TUFF_BRICKS",
"COPPER_GRATE",
"EXPOSED_COPPER_GRATE",
"WEATHERED_COPPER_GRATE",
"OXIDIZED_COPPER_GRATE",
"WAXED_COPPER_GRATE",
"WAXED_EXPOSED_COPPER_GRATE",
"WAXED_WEATHERED_COPPER_GRATE",
"WAXED_OXIDIZED_COPPER_GRATE",
"COPPER_BULB",
"EXPOSED_COPPER_BULB",
"WEATHERED_COPPER_BULB",
"OXIDIZED_COPPER_BULB",
"WAXED_COPPER_BULB",
"WAXED_EXPOSED_COPPER_BULB",
"WAXED_WEATHERED_COPPER_BULB",
"WAXED_OXIDIZED_COPPER_BULB",
"COPPER_TRAPDOOR",
"EXPOSED_COPPER_TRAPDOOR",
"WEATHERED_COPPER_TRAPDOOR",
"OXIDIZED_COPPER_TRAPDOOR",
"WAXED_COPPER_TRAPDOOR",
"WAXED_EXPOSED_COPPER_TRAPDOOR",
"WAXED_WEATHERED_COPPER_TRAPDOOR",
"WAXED_OXIDIZED_COPPER_TRAPDOOR",
"COPPER_DOOR",
"EXPOSED_COPPER_DOOR",
"WEATHERED_COPPER_DOOR",
"OXIDIZED_COPPER_DOOR",
"WAXED_COPPER_DOOR",
"WAXED_EXPOSED_COPPER_DOOR",
"WAXED_WEATHERED_COPPER_DOOR",
"WAXED_OXIDIZED_COPPER_DOOR",
"CHISELED_COPPER",
"EXPOSED_CHISELED_COPPER",
"WEATHERED_CHISELED_COPPER",
"OXIDIZED_CHISELED_COPPER",
"WAXED_CHISELED_COPPER",
"WAXED_EXPOSED_CHISELED_COPPER",
"WAXED_WEATHERED_CHISELED_COPPER",
"WAXED_OXIDIZED_CHISELED_COPPER",
"HEAVY_CORE",
"COPPER_CHEST",
"EXPOSED_COPPER_CHEST",
"WEATHERED_COPPER_CHEST",
"OXIDIZED_COPPER_CHEST",
"WAXED_COPPER_CHEST",
"WAXED_EXPOSED_COPPER_CHEST",
"WAXED_WEATHERED_COPPER_CHEST",
"WAXED_OXIDIZED_COPPER_CHEST",
"COPPER_LANTERN",
"EXPOSED_COPPER_LANTERN",
"WEATHERED_COPPER_LANTERN",
"OXIDIZED_COPPER_LANTERN",
"WAXED_COPPER_LANTERN",
"WAXED_EXPOSED_COPPER_LANTERN",
"WAXED_WEATHERED_COPPER_LANTERN",
"WAXED_OXIDIZED_COPPER_LANTERN",
"OPEN_EYEBLOSSOM",
"CLOSED_EYEBLOSSOM",
"POLISHED_BLACKSTONE_BUTTON",
"POLISHED_BLACKSTONE_PRESSURE_PLATE",
"DARK_OAK_BUTTON",
"DARK_OAK_PRESSURE_PLATE",
"DARK_OAK_TRAPDOOR",
"JUNGLE_BUTTON",
"JUNGLE_PRESSURE_PLATE",
"JUNGLE_TRAPDOOR",
"ACACIA_BUTTON",
"ACACIA_PRESSURE_PLATE",
"ACACIA_TRAPDOOR",
"SPRUCE_BUTTON",
"SPRUCE_PRESSURE_PLATE",
"SPRUCE_TRAPDOOR",
"BIRCH_BUTTON",
"BIRCH_PRESSURE_PLATE",
"BIRCH_TRAPDOOR",
"BUSH",
"FIREFLY_BUSH",
"SHORT_DRY_GRASS",
"TALL_DRY_GRASS",
"CACTUS_FLOWER",
"LEAF_LITTER",
"WILDFLOWERS",
"WARPED_WART_BLOCK",
"PALE_OAK_LOG",
"STRIPPED_PALE_OAK_LOG",
"PALE_OAK_WOOD",
"STRIPPED_PALE_OAK_WOOD",
"PALE_OAK_PLANKS",
"PALE_OAK_STAIRS",
"PALE_OAK_SLAB",
"PALE_OAK_FENCE",
"PALE_OAK_FENCE_GATE",
"PALE_OAK_DOOR",
"PALE_OAK_TRAPDOOR",
"PALE_OAK_BUTTON",
"PALE_OAK_PRESSURE_PLATE",
"PALE_OAK_SIGN",
"PALE_OAK_HANGING_SIGN",
"PALE_OAK_WALL_SIGN",
"PALE_OAK_LEAVES",
"PALE_OAK_SAPLING",
"PALE_MOSS_BLOCK",
"PALE_MOSS_CARPET",
"PALE_HANGING_MOSS",
"CREAKING_HEART",
"RESIN_BLOCK",
"RESIN_BRICKS",
"RESIN_BRICK_STAIRS",
"RESIN_BRICK_SLAB",
"RESIN_BRICK_WALL",
"CHISELED_RESIN_BRICKS",
"SPRUCE_SHELF",
"CRIMSON_SHELF",
"BAMBOO_SHELF",
"CHERRY_SHELF",
"PALE_OAK_SHELF",
"JUNGLE_SHELF",
"MANGROVE_SHELF",
"DARK_OAK_SHELF",
"OAK_SHELF",
"WARPED_SHELF",
"BIRCH_SHELF",
"ACACIA_SHELF",
};
private static volatile Method clientViewDistanceMethod;
private static volatile boolean clientViewDistanceChecked;
@Override
public EnumSet<Material> getModernMaterials() {
EnumSet<Material> materials = EnumSet.noneOf(Material.class);
for (String name : MODERN_MATERIAL_NAMES) {
Material material = Material.matchMaterial(name);
if (material != null) {
materials.add(material);
}
}
return materials;
}
@Override
public String getBlockDataString(Block block) {
return block.getBlockData().getAsString();
}
@Override
public String getMaterialKey(Material material) {
return material.getKey().toString();
}
@Override
public int getClientViewDistance(Player player) {
if (!clientViewDistanceChecked) {
try {
clientViewDistanceMethod = player.getClass().getMethod("getClientViewDistance");
} catch (Exception e) {
clientViewDistanceMethod = null;
}
clientViewDistanceChecked = true;
}
if (clientViewDistanceMethod != null) {
try {
Object value = clientViewDistanceMethod.invoke(player);
if (value instanceof Integer) {
return (Integer) value;
}
} catch (Exception e) {
return player.getServer().getViewDistance();
}
}
return player.getServer().getViewDistance();
}
@Override
public void giveCustomBlocks(Player player) {
addItemIfPresent(player, "DEEPSLATE");
addItemIfPresent(player, "TUFF");
}
private void addItemIfPresent(Player player, String materialName) {
Material material = Material.matchMaterial(materialName);
if (material != null) {
player.getInventory().addItem(new ItemStack(material));
}
}
}
@@ -0,0 +1,400 @@
package tf.tuff.viaentities;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import org.bukkit.entity.Player;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import tf.tuff.util.SchedulerCompat;
public class EntityDataHandler extends ChannelOutboundHandlerAdapter {
private final ViaEntitiesPlugin plugin;
private final Player player;
private final EntityMappingManager entityMappingManager;
public EntityDataHandler(ViaEntitiesPlugin plugin, Player player) {
this.plugin = plugin;
this.player = player;
this.entityMappingManager = plugin.entityMappingManager;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
String className = msg.getClass().getName();
String simpleClassName = msg.getClass().getSimpleName();
if (className.contains("Bundle") || simpleClassName.contains("Bundle")) {
try {
Iterable<?> packets = null;
for (java.lang.reflect.Method m : msg.getClass().getMethods()) {
if (m.getName().equals("subPackets") && m.getParameterCount() == 0) {
Object result = m.invoke(msg);
if (result instanceof Iterable) {
packets = (Iterable<?>) result;
break;
}
}
}
if (packets == null) {
Class<?> clazz = msg.getClass();
outer:
while (clazz != null) {
for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
f.setAccessible(true);
Object val = f.get(msg);
if (val instanceof Iterable) {
packets = (Iterable<?>) val;
break outer;
}
}
clazz = clazz.getSuperclass();
}
}
if (packets != null) {
for (Object subPacket : packets) {
String subClass = subPacket.getClass().getSimpleName();
String subFullClass = subPacket.getClass().getName();
if (subClass.contains("Add") || subClass.contains("Spawn") ||
subFullClass.contains("AddEntity") || subFullClass.contains("SpawnEntity")) {
handleSpawnPacket(subPacket);
}
}
}
} catch (Exception e) {
}
}
boolean isSpawnPacket = className.contains("SpawnEntity") || className.contains("AddEntity") ||
simpleClassName.equals("PacketPlayOutSpawnEntity") ||
simpleClassName.equals("PacketPlayOutSpawnEntityLiving") ||
simpleClassName.equals("ClientboundAddEntityPacket") ||
simpleClassName.equals("ClientboundAddMobPacket");
if (!isSpawnPacket) {
try {
java.lang.reflect.Method getTypeMethod = msg.getClass().getMethod("getType");
java.lang.reflect.Method getIdMethod = msg.getClass().getMethod("getId");
if (getTypeMethod != null && getIdMethod != null) {
Object typeResult = getTypeMethod.invoke(msg);
if (typeResult != null && typeResult.toString().contains("entity")) {
isSpawnPacket = true;
}
}
} catch (NoSuchMethodException e) {
} catch (Exception e) {
}
}
if (isSpawnPacket) {
try {
handleSpawnPacket(msg);
} catch (Exception e) {
}
} else if (className.contains("EntityMetadata") || className.contains("SetEntityData") ||
simpleClassName.equals("PacketPlayOutEntityMetadata") ||
simpleClassName.equals("ClientboundSetEntityDataPacket")) {
try {
int entityId = getIntField(msg, "a", "id", "entityId");
if (entityId != -1) {
sendEntityMetadata(entityId, null);
}
} catch (Exception e) {
}
} else if (className.contains("Animation") ||
simpleClassName.equals("PacketPlayOutAnimation") ||
simpleClassName.equals("ClientboundAnimatePacket")) {
try {
int entityId = getIntField(msg, "a", "id", "entityId");
int animationType = getIntField(msg, "b", "action", "animationType");
if (entityId != -1) {
sendEntityAnimation(entityId, animationType);
}
} catch (Exception e) {
}
} else if (className.contains("EntityDestroy") || className.contains("RemoveEntities") ||
simpleClassName.equals("PacketPlayOutEntityDestroy") ||
simpleClassName.equals("ClientboundRemoveEntitiesPacket")) {
try {
handleDestroyPacket(msg);
} catch (Exception e) {
}
}
super.write(ctx, msg, promise);
}
private void handleSpawnPacket(Object msg) throws Exception {
int entityId = -1;
Object entityTypeObj = null;
double x = 0, y = 0, z = 0;
float yaw = 0, pitch = 0;
java.util.List<Double> doubles = new java.util.ArrayList<>();
java.util.List<Byte> bytes = new java.util.ArrayList<>();
for (java.lang.reflect.Field field : msg.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(msg);
String typeName = field.getType().getName();
if (value == null) continue;
if (typeName.contains("EntityType") || typeName.contains("EntityTypes")) {
entityTypeObj = value;
} else if (value instanceof Integer && entityId == -1) {
entityId = (Integer) value;
} else if (value instanceof Double) {
doubles.add((Double) value);
} else if (value instanceof Byte) {
bytes.add((Byte) value);
}
}
if (doubles.size() >= 3) {
x = doubles.get(0);
y = doubles.get(1);
z = doubles.get(2);
}
if (bytes.size() >= 2) {
pitch = bytes.get(0) * 360.0f / 256.0f;
yaw = bytes.get(1) * 360.0f / 256.0f;
}
if (entityId == -1) return;
if (entityTypeObj == null) return;
String entityTypeStr = entityTypeObj.toString();
String entityTypeName = extractEntityTypeName(entityTypeStr);
if (entityMappingManager.isModernEntity(entityTypeName)) {
sendEntitySpawn(entityId, entityTypeName, x, y, z, yaw, pitch);
}
}
private void handleDestroyPacket(Object msg) throws Exception {
for (java.lang.reflect.Field field : msg.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof it.unimi.dsi.fastutil.ints.IntList) {
it.unimi.dsi.fastutil.ints.IntList idList = (it.unimi.dsi.fastutil.ints.IntList) value;
for (int i = 0; i < idList.size(); i++) {
sendEntityDestroy(idList.getInt(i));
}
return;
} else if (value instanceof int[]) {
for (int id : (int[]) value) {
sendEntityDestroy(id);
}
return;
} else if (value instanceof java.util.List) {
for (Object item : (java.util.List<?>) value) {
if (item instanceof Integer) {
sendEntityDestroy((Integer) item);
}
}
return;
}
}
}
private int getIntField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof Integer) return (Integer) value;
if (value instanceof Number) return ((Number) value).intValue();
} catch (Exception ignored) {}
}
return -1;
}
private double getDoubleField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof Double) return (Double) value;
if (value instanceof Number) return ((Number) value).doubleValue();
} catch (Exception ignored) {}
}
return 0.0;
}
private float getAngleField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
Object value = field.get(msg);
if (value instanceof Byte) return ((Byte) value) * 360.0f / 256.0f;
if (value instanceof Float) return (Float) value;
if (value instanceof Number) return ((Number) value).floatValue();
} catch (Exception ignored) {}
}
return 0.0f;
}
private Object getField(Object msg, String... fieldNames) {
for (String name : fieldNames) {
try {
java.lang.reflect.Field field = msg.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(msg);
} catch (Exception ignored) {}
}
return null;
}
private String extractEntityTypeName(String typeStr) {
if (typeStr == null) return null;
if (typeStr.startsWith("entity.minecraft.")) {
String name = typeStr.substring("entity.minecraft.".length());
return "minecraft:" + name;
}
if (typeStr.contains("ResourceKey[minecraft:entity_type / minecraft:")) {
int start = typeStr.indexOf("minecraft:", typeStr.indexOf("minecraft:") + 10) + 10;
int end = typeStr.indexOf("]", start);
if (end > start) {
return "minecraft:" + typeStr.substring(start, end);
}
}
if (typeStr.contains("entity_type.minecraft.")) {
int start = typeStr.indexOf("entity_type.minecraft.") + 22;
int end = typeStr.length();
for (int i = start; i < typeStr.length(); i++) {
char c = typeStr.charAt(i);
if (!Character.isLetterOrDigit(c) && c != '_') {
end = i;
break;
}
}
return "minecraft:" + typeStr.substring(start, end);
}
if (typeStr.startsWith("minecraft:")) {
return typeStr;
}
if (typeStr.contains("minecraft:")) {
int start = typeStr.indexOf("minecraft:");
int end = typeStr.length();
for (int i = start + 10; i < typeStr.length(); i++) {
char c = typeStr.charAt(i);
if (!Character.isLetterOrDigit(c) && c != '_' && c != ':') {
end = i;
break;
}
}
return typeStr.substring(start, end);
}
return typeStr;
}
private void sendEntitySpawn(int entityId, String entityType, double x, double y, double z, float yaw, float pitch) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
int paletteIndex = entityMappingManager.getEntityIndex(entityType);
if (paletteIndex == -1) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("SPAWN_ENTITY");
out.writeInt(entityId);
out.writeShort(paletteIndex);
out.writeDouble(x);
out.writeDouble(y);
out.writeDouble(z);
out.writeFloat(yaw);
out.writeFloat(pitch);
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
}
private void sendEntityMetadata(int entityId, Object packedItems) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
try {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ENTITY_METADATA");
out.writeInt(entityId);
if (packedItems instanceof java.util.List) {
java.util.List<?> items = (java.util.List<?>) packedItems;
out.writeInt(items.size());
for (Object item : items) {
java.lang.reflect.Method getIdMethod = item.getClass().getMethod("id");
int metaId = (int) getIdMethod.invoke(item);
out.writeInt(metaId);
java.lang.reflect.Method getValueMethod = item.getClass().getMethod("value");
Object value = getValueMethod.invoke(item);
if (value instanceof Boolean) {
out.writeByte(0);
out.writeBoolean((Boolean) value);
} else if (value instanceof Integer) {
out.writeByte(1);
out.writeInt((Integer) value);
} else if (value instanceof Float) {
out.writeByte(2);
out.writeFloat((Float) value);
} else if (value instanceof String) {
out.writeByte(3);
out.writeUTF((String) value);
} else if (value instanceof Byte) {
out.writeByte(4);
out.writeByte((Byte) value);
} else {
out.writeByte(-1);
}
}
} else {
out.writeInt(0);
}
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
} catch (Exception e) {
}
}
private void sendEntityAnimation(int entityId, int animationType) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("ENTITY_ANIMATION");
out.writeInt(entityId);
out.writeInt(animationType);
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
}
private void sendEntityDestroy(int entityId) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("DESTROY_ENTITY");
out.writeInt(entityId);
byte[] data = out.toByteArray();
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, data);
}
}
@@ -0,0 +1,63 @@
package tf.tuff.viaentities;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import tf.tuff.netty.BaseInjector;
import tf.tuff.util.SchedulerCompat;
public class EntityInjector extends BaseInjector {
private final ViaEntitiesPlugin plugin;
public EntityInjector(ViaEntitiesPlugin plugin) {
super("viaentities_handler");
this.plugin = plugin;
}
@Override
protected ChannelHandler createHandler(Player player) {
return new EntityDataHandler(plugin, player);
}
@Override
protected void onPostInject(Player player) {
SchedulerCompat.runEntity(player, plugin.plugin, () -> sendExistingEntities(player));
}
private void sendExistingEntities(Player player) {
int viewDistance = player.getWorld().getViewDistance() * 16;
for (Entity entity : player.getNearbyEntities(viewDistance, viewDistance, viewDistance)) {
if (entity.equals(player)) continue;
if (entity instanceof Player) continue;
double distance = entity.getLocation().distance(player.getLocation());
if (distance > viewDistance) continue;
String entityType = entity.getType().getKey().toString();
if (plugin.entityMappingManager.isModernEntity(entityType)) {
sendEntityData(player, entity.getEntityId(), entityType, entity);
}
}
}
public void sendEntityData(Player player, int entityId, String entityType, Entity entity) {
if (!plugin.isPlayerEnabled(player.getUniqueId())) return;
int paletteIndex = plugin.entityMappingManager.getEntityIndex(entityType);
if (paletteIndex == -1) return;
com.google.common.io.ByteArrayDataOutput out = com.google.common.io.ByteStreams.newDataOutput();
out.writeUTF("SPAWN_ENTITY");
out.writeInt(entityId);
out.writeShort(paletteIndex);
out.writeDouble(entity.getLocation().getX());
out.writeDouble(entity.getLocation().getY());
out.writeDouble(entity.getLocation().getZ());
out.writeFloat(entity.getLocation().getYaw());
out.writeFloat(entity.getLocation().getPitch());
SchedulerCompat.sendPluginMessage(plugin.plugin, player, ViaEntitiesPlugin.CLIENTBOUND_CHANNEL, out.toByteArray());
}
}
@@ -0,0 +1,146 @@
package tf.tuff.viaentities;
import java.io.*;
import java.util.*;
import com.fasterxml.jackson.databind.*;
public class EntityMappingManager {
private final List<String> modernEntities = new ArrayList<>();
private final Map<String, Integer> entityToIndex = new HashMap<>();
private final Set<String> modernEntitySet = new HashSet<>();
private final Map<String, EntityInfo> entityInfoMap = new HashMap<>();
public EntityMappingManager() {
loadEntityMappingsFromJSON();
}
private void loadEntityMappingsFromJSON() {
try (InputStream is = getClass().getClassLoader().getResourceAsStream("entity_mappings.json")) {
if (is == null) {
loadFallbackEntities();
return;
}
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(is);
JsonNode modernEntitiesNode = root.get("modern_entities");
JsonNode entitySizeNode = root.get("entity_size");
if (modernEntitiesNode != null) {
Iterator<Map.Entry<String, JsonNode>> fields = modernEntitiesNode.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
String entityType = entry.getKey();
JsonNode info = entry.getValue();
String addedVersion = info.has("added") ? info.get("added").asText() : "1.13";
String model = info.has("model") && !info.get("model").isNull() ? info.get("model").asText() : null;
boolean animated = info.has("animated") && info.get("animated").asBoolean();
double width = 1.0;
double height = 1.0;
if (entitySizeNode != null && entitySizeNode.has(entityType)) {
JsonNode sizeInfo = entitySizeNode.get(entityType);
width = sizeInfo.has("width") ? sizeInfo.get("width").asDouble() : 1.0;
height = sizeInfo.has("height") ? sizeInfo.get("height").asDouble() : 1.0;
}
addEntity(entityType, new EntityInfo(addedVersion, model, animated, width, height));
}
}
} catch (Exception e) {
loadFallbackEntities();
}
}
private void loadFallbackEntities() {
addEntity("minecraft:allay", new EntityInfo("1.19", "allay", true, 0.35, 0.6));
addEntity("minecraft:axolotl", new EntityInfo("1.17", "axolotl", true, 0.75, 0.42));
addEntity("minecraft:bee", new EntityInfo("1.15", "bee", true, 0.7, 0.6));
addEntity("minecraft:camel", new EntityInfo("1.20", "camel", true, 1.7, 2.375));
addEntity("minecraft:cat", new EntityInfo("1.14", "cat", true, 0.6, 0.7));
addEntity("minecraft:fox", new EntityInfo("1.14", "fox", true, 0.6, 0.7));
addEntity("minecraft:frog", new EntityInfo("1.19", "frog", true, 0.5, 0.5));
addEntity("minecraft:goat", new EntityInfo("1.17", "goat", true, 0.9, 1.3));
addEntity("minecraft:hoglin", new EntityInfo("1.16", "hoglin", true, 1.4, 1.4));
addEntity("minecraft:piglin", new EntityInfo("1.16", "piglin", true, 0.6, 1.95));
addEntity("minecraft:strider", new EntityInfo("1.16", "strider", true, 0.9, 1.7));
addEntity("minecraft:warden", new EntityInfo("1.19", "warden", true, 0.9, 2.9));
addEntity("minecraft:sniffer", new EntityInfo("1.20", "sniffer", true, 1.9, 1.75));
addEntity("minecraft:breeze", new EntityInfo("1.21", "breeze", true, 0.6, 1.77));
addEntity("minecraft:wind_charge", new EntityInfo("1.21", "wind_charge", false, 0.3125, 0.3125));
addEntity("minecraft:breeze_wind_charge", new EntityInfo("1.21", "breeze_wind_charge", false, 0.3125, 0.3125));
addEntity("minecraft:armadillo", new EntityInfo("1.20.5", "armadillo", true, 0.7, 0.65));
addEntity("minecraft:bogged", new EntityInfo("1.21", "bogged", true, 0.6, 1.99));
addEntity("minecraft:phantom", new EntityInfo("1.13", "phantom", true, 0.9, 0.5));
addEntity("minecraft:dolphin", new EntityInfo("1.13", "dolphin", true, 0.9, 0.6));
addEntity("minecraft:drowned", new EntityInfo("1.13", "drowned", true, 0.6, 1.95));
addEntity("minecraft:cod", new EntityInfo("1.13", "cod", true, 0.5, 0.3));
addEntity("minecraft:salmon", new EntityInfo("1.13", "salmon", true, 0.7, 0.4));
addEntity("minecraft:tropical_fish", new EntityInfo("1.13", "tropical_fish", true, 0.5, 0.4));
addEntity("minecraft:pufferfish", new EntityInfo("1.13", "pufferfish", true, 0.7, 0.7));
addEntity("minecraft:turtle", new EntityInfo("1.13", "turtle", true, 1.2, 0.4));
addEntity("minecraft:trident", new EntityInfo("1.13", "trident", false, 0.5, 0.5));
addEntity("minecraft:thrown_trident", new EntityInfo("1.13", "trident", false, 0.5, 0.5));
addEntity("minecraft:glow_squid", new EntityInfo("1.17", "glow_squid", true, 0.8, 0.8));
addEntity("minecraft:glow_item_frame", new EntityInfo("1.17", "glow_item_frame", false, 0.5, 0.5));
addEntity("minecraft:tadpole", new EntityInfo("1.19", "tadpole", true, 0.4, 0.3));
addEntity("minecraft:chest_boat", new EntityInfo("1.19", "chest_boat", false, 1.375, 0.5625));
addEntity("minecraft:piglin_brute", new EntityInfo("1.16", "piglin_brute", true, 0.6, 1.95));
addEntity("minecraft:zoglin", new EntityInfo("1.16", "zoglin", true, 1.4, 1.4));
}
private void addEntity(String entityType, EntityInfo info) {
if (!entityToIndex.containsKey(entityType)) {
entityToIndex.put(entityType, modernEntities.size());
modernEntities.add(entityType);
modernEntitySet.add(entityType);
entityInfoMap.put(entityType, info);
}
}
public boolean isModernEntity(String entityType) {
return modernEntitySet.contains(entityType);
}
public int getEntityIndex(String entityType) {
return entityToIndex.getOrDefault(entityType, -1);
}
public String getEntityByIndex(int index) {
if (index >= 0 && index < modernEntities.size()) {
return modernEntities.get(index);
}
return null;
}
public EntityInfo getEntityInfo(String entityType) {
return entityInfoMap.get(entityType);
}
public List<String> getAllModernEntities() {
return Collections.unmodifiableList(modernEntities);
}
public int getModernEntityCount() {
return modernEntities.size();
}
public static class EntityInfo {
public final String addedVersion;
public final String model;
public final boolean animated;
public final double width;
public final double height;
public EntityInfo(String addedVersion, String model, boolean animated, double width, double height) {
this.addedVersion = addedVersion;
this.model = model;
this.animated = animated;
this.width = width;
this.height = height;
}
}
}
@@ -0,0 +1,138 @@
package tf.tuff.viaentities;
import tf.tuff.TuffX;
import org.bukkit.entity.Player;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import tf.tuff.util.SchedulerCompat;
public final class ViaEntitiesPlugin {
public static final String CLIENTBOUND_CHANNEL = "viaentities:data";
public static final String SERVERBOUND_CHANNEL = "entities:handshake";
public final Set<UUID> viaEntitiesEnabledPlayers = new HashSet<>();
static ViaEntitiesPlugin instance;
public EntityMappingManager entityMappingManager;
private EntityInjector entityInjector;
private boolean enabled = true;
private boolean debug = false;
private int maxDistance = -1;
public TuffX plugin;
public ViaEntitiesPlugin(TuffX plugin) {
this.plugin = plugin;
plugin.getConfig().addDefault("viaentities.viaentities-enabled", true);
plugin.getConfig().addDefault("viaentities.debug", false);
plugin.getConfig().addDefault("viaentities.max-distance", -1);
}
public void onTuffXReload() {
loadConfig();
}
private void loadConfig() {
enabled = plugin.getConfig().getBoolean("viaentities.viaentities-enabled");
debug = plugin.getConfig().getBoolean("viaentities.debug");
maxDistance = plugin.getConfig().getInt("viaentities.max-distance");
}
public boolean isDebug() {
return debug;
}
public void debug(String message) {
if (isDebug()) info(message);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[ViaEntities] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void onTuffXEnable() {
instance = this;
loadConfig();
this.entityMappingManager = new EntityMappingManager();
this.entityInjector = new EntityInjector(this);
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL, plugin);
if (enabled) {
info("ViaEntities enabled with " + entityMappingManager.getModernEntityCount() + " modern entities");
} else {
info("ViaEntities disabled in config");
}
}
public boolean isEnabled() {
return enabled;
}
public int getMaxDistance() {
return maxDistance;
}
public void handlePacket(Player player, byte[] message) {
if (!enabled) return;
if (!isPlayerEnabled(player.getUniqueId())) {
debug("Received handshake from " + player.getName());
setPlayerEnabled(player.getUniqueId(), true);
entityInjector.inject(player);
sendPaletteToClient(player);
debug("Sent palette with " + entityMappingManager.getModernEntityCount() + " entities to " + player.getName());
}
}
private void sendPaletteToClient(Player player) {
com.google.common.io.ByteArrayDataOutput out = com.google.common.io.ByteStreams.newDataOutput();
out.writeUTF("INIT_PALETTE");
java.util.List<String> palette = entityMappingManager.getAllModernEntities();
out.writeInt(palette.size());
for (String entityType : palette) {
out.writeUTF(entityType);
}
SchedulerCompat.sendPluginMessage(plugin, player, CLIENTBOUND_CHANNEL, out.toByteArray());
}
public void handlePlayerQuit(org.bukkit.event.player.PlayerQuitEvent event) {
entityInjector.eject(event.getPlayer());
viaEntitiesEnabledPlayers.remove(event.getPlayer().getUniqueId());
}
public void onTuffXDisable() {
plugin.getServer().getMessenger().unregisterOutgoingPluginChannel(plugin, CLIENTBOUND_CHANNEL);
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin, SERVERBOUND_CHANNEL);
}
public boolean isPlayerEnabled(UUID playerId) {
return viaEntitiesEnabledPlayers.contains(playerId);
}
public void setPlayerEnabled(UUID playerId, boolean enabled) {
if (enabled) {
viaEntitiesEnabledPlayers.add(playerId);
} else {
viaEntitiesEnabledPlayers.remove(playerId);
}
}
public EntityInjector getEntityInjector() {
return entityInjector;
}
}
@@ -0,0 +1,29 @@
package tf.tuff.y0;
import tf.tuff.TuffX;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.entity.Player;
import tf.tuff.util.SchedulerCompat;
public class ChunkPacketListener {
public final Y0Plugin plugin;
public ChunkPacketListener(Y0Plugin plugin) {
this.plugin = plugin;
}
public void handleChunk(TuffX plugin, Player player, World world, int chunkX, int chunkZ){
if (!this.plugin.isPlayerReady(player)) return;
SchedulerCompat.runRegion(plugin, world, chunkX, chunkZ, () -> {
if (player.isOnline() && world.isChunkLoaded(chunkX, chunkZ)) {
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
this.plugin.processAndSendChunk(player, chunk);
}
});
}
}
+345
View File
@@ -0,0 +1,345 @@
package tf.tuff.y0;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.viaversion.viabackwards.api.BackwardsProtocol;
import com.viaversion.viabackwards.api.data.BackwardsMappingData;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import tf.tuff.TuffX;
import tf.tuff.util.SchedulerCompat;
public class ViaBlockIds {
private final TuffX p;
private final Y0Plugin plugin;
private final String serverVersion;
private final File mappingsFile;
private Object2ObjectOpenHashMap<String, int[]> legacyMappings = new Object2ObjectOpenHashMap<>();
public ViaBlockIds(TuffX pl) {
p = pl;
plugin = pl.y0Plugin;
serverVersion = getServerMCVersion();
mappingsFile = new File(pl.getDataFolder(), serverVersion + "-mappings.json");
plugin.info("Server Minecraft Version: " + serverVersion);
SchedulerCompat.runGlobalLater(pl, this::initializeMappings, 1L);
}
private void initializeMappings() {
try {
if (Via.getAPI() == null) {
plugin.severe("ViaVersion API not found! Is ViaVersion installed?");
return;
}
} catch (IllegalArgumentException e) {
plugin.severe("ViaVersion API not found! Is ViaVersion installed?");
return;
}
if (!mappingsFile.exists()) {
plugin.info("Mapping file not found, generating...");
if (!p.getDataFolder().exists()) {
p.getDataFolder().mkdirs();
}
generateMappings();
} else {
plugin.info("Loading mappings from " + mappingsFile.getName());
loadMappings();
}
}
private static final int[] DEFAULT_LEGACY = {1, 0};
public int[] toLegacy(String k) {
int[] result = legacyMappings.get(k);
return result != null ? result : DEFAULT_LEGACY;
}
public int[] toLegacy(BlockData bd) {
org.bukkit.Material mat = bd.getMaterial();
if (mat == org.bukkit.Material.CHEST) {
return new int[] {54, getLegacyChestMeta(bd)};
}
if (mat == org.bukkit.Material.TRAPPED_CHEST) {
return new int[] {146, getLegacyChestMeta(bd)};
}
if (mat == org.bukkit.Material.ENDER_CHEST) {
return new int[] {130, getLegacyChestMeta(bd)};
}
String k = bd.getAsString();
if (k.startsWith("minecraft:")) {
k = k.substring(10);
}
return toLegacy(k);
}
private int getLegacyChestMeta(BlockData bd) {
if (bd instanceof org.bukkit.block.data.Directional directional) {
org.bukkit.block.BlockFace face = directional.getFacing();
switch (face) {
case NORTH:
return 2;
case SOUTH:
return 3;
case WEST:
return 4;
case EAST:
return 5;
default:
return 3;
}
}
String s = bd.getAsString();
if (s.contains("facing=north")) {
return 2;
}
if (s.contains("facing=south")) {
return 3;
}
if (s.contains("facing=west")) {
return 4;
}
if (s.contains("facing=east")) {
return 5;
}
return 3;
}
public int[] toLegacy(Block b) {
return toLegacy(b.getBlockData());
}
private String getServerMCVersion() {
String vs = Bukkit.getServer().getVersion();
int mi = vs.indexOf("MC: ");
if (mi != -1) {
int ei = vs.indexOf(')', mi);
return ei != -1 ? vs.substring(mi + 4, ei) : vs.substring(mi + 4);
}
plugin.log(Level.WARNING, "Could not detect Minecraft version. Defaulting to 1.21.");
return "1.21";
}
public static record MappingFile (String version, InputStream stream) {}
public @Nonnull MappingFile findMappingFile(String serverVers) {
String[] vp = serverVers.split("\\.");
int maj, min, pat;
try {
maj = Integer.parseInt(vp[0]);
min = Integer.parseInt(vp[1]);
pat = vp.length > 2 ? Integer.parseInt(vp[2]) : 0;
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
plugin.severe("Could not parse server version string: " + serverVers);
return new MappingFile(serverVers, p.getResource("mapping-" + serverVers + ".json"));
}
plugin.info("Searching for mappings, starting from " + serverVers + " and going down.");
for (int m = min; m >= 0; m--) {
int sp = (m == min) ? pat : 11; // if on correct minor version, start from patch, otherwise start from highest possible patch
for (int pt = sp; pt >= 0; pt--) {
String vtt = maj + "." + m + "." + pt;
String fileName = "mapping-" + vtt + ".json";
InputStream inpStream = p.getResource(fileName);
if (inpStream != null) {
if (!vtt.equals(serverVers)) {
plugin.info("Using fallback mapping file: " + fileName);
} else {
plugin.info("Found exact mapping file: " + fileName);
}
return new MappingFile(vtt, inpStream);
}
}
// Check for the major.min version without patch
String fileName = "mapping-" + maj + "." + m + ".json";
InputStream inpStream = p.getResource(fileName);
if (inpStream != null) {
plugin.info("Using fallback mapping file: " + fileName);
return new MappingFile(maj + "." + m, inpStream);
}
// Switch to 1.21.x versions after 26.0
if (maj >= 26 && m == 0) {
maj = 1;
m = 22; // will be 21 in the next iteration
}
}
plugin.severe("Could not find any suitable mapping file after checking all versions down to 1.0.0");
return new MappingFile(serverVers, null);
}
private void generateMappings() {
try {
MappingFile mapFile = findMappingFile(serverVersion);
if (mapFile.stream == null) {
plugin.severe("Failed to find mapping file for " + serverVersion + " in plugin resources!");
return;
}
ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> r = mapper.readValue(mapFile.stream, Map.class);
mapFile.stream.close();
@SuppressWarnings("unchecked")
List<String> states = (List<String>) r.get("blockstates");
if (states == null) {
plugin.severe("'blockstates' key not found in JSON.");
return;
}
Object2ObjectOpenHashMap<String, int[]> newLegacyMappings = new Object2ObjectOpenHashMap<>();
plugin.info("Generating legacy mappings for " + states.size() + " block states...");
ProtocolVersion serverProto = ProtocolVersion.getClosest(mapFile.version); // start from base mappings file version
ProtocolVersion clientProto = ProtocolVersion.v1_12_2;
List<ProtocolPathEntry> protoPath = Via.getManager()
.getProtocolManager()
.getProtocolPath(clientProto, serverProto);
if (protoPath == null) {
plugin.log(Level.SEVERE, "Protocol path is null!");
return;
}
for (int i = 0; i < states.size(); i++) {
String k = states.get(i).replace("minecraft:", "");
String blockName = k.contains("[") ? k.substring(0, k.indexOf("[")) : k;
int[] legacy;
switch (blockName) {
case "chest":
legacy = new int[]{54, 0};
break;
case "ender_chest":
legacy = new int[]{130, 0};
break;
case "trapped_chest":
legacy = new int[]{146, 0};
break;
default:
legacy = convertToLegacy(protoPath, i);
break;
}
newLegacyMappings.put(k, legacy);
}
legacyMappings = newLegacyMappings;
Map<String, Object> outputMap = new Object2ObjectOpenHashMap<>();
outputMap.put("blockstates", legacyMappings);
mappingsFile.getParentFile().mkdirs();
mapper.writerWithDefaultPrettyPrinter().writeValue(mappingsFile, outputMap);
plugin.info("Successfully wrote mappings to " + mappingsFile.getName());
} catch (Exception e) {
plugin.log(Level.SEVERE, "Error generating legacy mappings.", e);
}
}
private void loadMappings() {
try {
ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> r = mapper.readValue(mappingsFile, Map.class);
@SuppressWarnings("unchecked")
Map<String, List<Integer>> readMap = (Map<String, List<Integer>>) r.get("blockstates");
if (readMap == null) {
plugin.severe("Invalid format in mappings file. Regenerating...");
generateMappings();
return;
}
legacyMappings = new Object2ObjectOpenHashMap<>();
for (Map.Entry<String, List<Integer>> e : readMap.entrySet()) {
String fullKey = e.getKey();
List<Integer> ll = e.getValue();
if (ll != null && ll.size() == 2) {
String blockName = fullKey.contains("[") ? fullKey.substring(0, fullKey.indexOf("[")) : fullKey;
int[] finalId;
switch (blockName) {
case "chest":
finalId = new int[]{54, 0};
break;
case "ender_chest":
finalId = new int[]{130, 0};
break;
case "trapped_chest":
finalId = new int[]{146, 0};
break;
default:
finalId = new int[]{ll.get(0), ll.get(1)};
break;
}
legacyMappings.put(fullKey, finalId);
}
}
plugin.info("Loaded " + legacyMappings.size() + " legacy mappings.");
} catch (IOException e) {
plugin.log(Level.SEVERE, "Failed to load mappings file.", e);
}
}
public int[] convertToLegacy(List<ProtocolPathEntry> protoPath, int stateId) {
for (int i = protoPath.size() - 1; i >= 0; i--) {
ProtocolPathEntry entry = protoPath.get(i);
Protocol<?, ?, ?, ?> protocol = entry.protocol();
if (protocol instanceof BackwardsProtocol) {
BackwardsMappingData mappingData = ((BackwardsProtocol<?, ?, ?, ?>) protocol).getMappingData();
if (mappingData != null && mappingData.getBlockStateMappings() != null) {
int newStateId = mappingData.getBlockStateMappings().getNewId(stateId);
if (newStateId != -1) stateId = newStateId;
}
}
}
int blockId = stateId >> 4;
int blockMetadata = stateId & 0xF;
return new int[]{blockId, blockMetadata};
}
}
+931
View File
@@ -0,0 +1,931 @@
package tf.tuff.y0;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import tf.tuff.TuffX;
import tf.tuff.netty.ChunkInjector;
import tf.tuff.util.SchedulerCompat;
public class Y0Plugin {
public static final String CHANNEL = "eagler:below_y0";
public ViaBlockIds viaIds;
private ObjectOpenHashSet<String> enabledWorlds;
private boolean debug;
private int processorThreadCount;
private int cacheSize;
private int cacheExp;
private int concLevel;
private boolean kickOutdatedClients;
private final Set<UUID> readyPlayers = ConcurrentHashMap.newKeySet();
private volatile Cache<WorldChunk, ObjectArrayList<byte[]>> chunkCache;
private volatile Cache<WorldChunk, byte[]> chunkCacheCombined;
private volatile ExecutorService chunkProcessor;
private final ThreadLocal<Object2ObjectOpenHashMap<BlockData, int[]>> threadChunkData = ThreadLocal.withInitial(() -> new Object2ObjectOpenHashMap<>(256));
private final ThreadLocal<ByteArrayOutputStream> threadOut = ThreadLocal.withInitial(() -> new ByteArrayOutputStream(8256));
private final ThreadLocal<byte[]> threadData = ThreadLocal.withInitial(() -> new byte[12288]);
private TuffX plugin;
private static final int[] EMPTY_LEGACY = {1, 0};
private static final Map<BlockData, Integer> emissionCache = new ConcurrentHashMap<>();
private static Method getLightEmissionMethod;
public ChunkPacketListener chunkPacketListener;
private ChunkInjector chunkInjector;
static {
try {
getLightEmissionMethod = BlockData.class.getMethod("getLightEmission");
getLightEmissionMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
getLightEmissionMethod = null;
}
}
private static final Map<Material, Integer> legacy_light_map = Map.ofEntries(
Map.entry(Material.TORCH, 14),
Map.entry(Material.SOUL_TORCH, 10),
Map.entry(Material.LANTERN, 15),
Map.entry(Material.SOUL_LANTERN, 10),
Map.entry(Material.GLOWSTONE, 15),
Map.entry(Material.SEA_LANTERN, 15),
Map.entry(Material.REDSTONE_LAMP, 15),
Map.entry(Material.SHROOMLIGHT, 15),
Map.entry(Material.CAMPFIRE, 15),
Map.entry(Material.SOUL_CAMPFIRE, 10),
Map.entry(Material.END_ROD, 14),
Map.entry(Material.MAGMA_BLOCK, 3),
Map.entry(Material.FIRE, 15),
Map.entry(Material.SOUL_FIRE, 10),
Map.entry(Material.CANDLE, 3),
Map.entry(Material.WHITE_CANDLE, 3),
Map.entry(Material.CAKE, 0),
Map.entry(Material.CANDLE_CAKE, 3)
);
public Y0Plugin(TuffX plugin){
this.plugin = plugin;
}
private void debug(String m) {
if (debug) plugin.getLogger().info("[Y0-Debug] " + m);
}
public void log(Level level, String msg, Throwable e) {
plugin.getLogger().log(level, "[Y0] "+msg, e);
}
public void log(Level level, String msg) {
plugin.getLogger().log(level, "[Y0] "+msg);
}
public void info(String msg) {
log(Level.INFO, msg);
}
public void severe(String msg) {
log(Level.SEVERE, msg);
}
public record WorldChunk(String w, int x, int z) {}
private void loadConfig() {
debug = plugin.getConfig().getBoolean("y0.debug-mode");
ObjectArrayList<String> ewList = new ObjectArrayList<>(plugin.getConfig().getStringList("y0.enabled-worlds"));
enabledWorlds = new ObjectOpenHashSet<>(ewList.size());
if (plugin.getConfig().getBoolean("y0.y0-enabled")) enabledWorlds.addAll(ewList);
int threadSetting = plugin.getConfig().getInt("y0.chunk-processor-threads");
if (threadSetting <= 0) {
processorThreadCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
} else {
processorThreadCount = threadSetting;
}
cacheSize = plugin.getConfig().getInt("y0.cache-size");
cacheExp = plugin.getConfig().getInt("y0.cache-expiration");
concLevel = Runtime.getRuntime().availableProcessors();
kickOutdatedClients = plugin.getConfig().getBoolean("y0.kick-outdated-clients");
}
private void startup() {
loadConfig();
chunkCache = CacheBuilder.newBuilder()
.maximumSize(cacheSize)
.expireAfterAccess(cacheExp, TimeUnit.MINUTES)
.concurrencyLevel(concLevel)
.initialCapacity(256)
.build();
chunkCacheCombined = CacheBuilder.newBuilder()
.maximumSize(cacheSize)
.expireAfterAccess(cacheExp, TimeUnit.MINUTES)
.concurrencyLevel(concLevel)
.initialCapacity(256)
.build();
chunkProcessor = Executors.newFixedThreadPool(processorThreadCount, run -> {
Thread thread = new Thread(run, "TuffX-Chunk-" + System.nanoTime());
thread.setDaemon(true);
thread.setPriority(Thread.NORM_PRIORITY - 1);
return thread;
});
}
private void shutdown() {
if (chunkCache != null) {
chunkCache.invalidateAll();
}
if (chunkCacheCombined != null) {
chunkCacheCombined.invalidateAll();
}
if (chunkProcessor != null) {
chunkProcessor.shutdown();
try {
if (!chunkProcessor.awaitTermination(10, TimeUnit.SECONDS)) {
chunkProcessor.shutdownNow();
if (!chunkProcessor.awaitTermination(5, TimeUnit.SECONDS)) {
severe("Failed to shutdown chunk processor pool!");
}
}
} catch (InterruptedException e) {
chunkProcessor.shutdownNow();
Thread.currentThread().interrupt();
} finally {
chunkProcessor = null;
}
}
}
public void onTuffXReload() {
loadConfig();
shutdown();
startup();
emissionCache.clear();
info("Y0 reloaded.");
}
public void forceClearCache() {
if (chunkCache != null) {
chunkCache.invalidateAll();
chunkCache.cleanUp();
}
if (chunkCacheCombined != null) {
chunkCacheCombined.invalidateAll();
chunkCacheCombined.cleanUp();
}
emissionCache.clear();
}
public void onTuffXEnable() {
plugin.getConfig().addDefault("y0.y0-enabled", true);
plugin.getConfig().addDefault("y0.debug-mode", false);
plugin.getConfig().addDefault("y0.enabled-worlds", new ArrayList<>(java.util.Collections.singletonList("world")));
plugin.getConfig().addDefault("y0.chunk-processor-threads", 3);
plugin.getConfig().addDefault("y0.cache-size", 192);
plugin.getConfig().addDefault("y0.cache-expiration", 2);
plugin.getConfig().addDefault("y0.kick-outdated-clients", true);
plugin.getConfig().options().copyDefaults(true);
startup();
this.chunkPacketListener = new ChunkPacketListener(this);
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, CHANNEL, plugin);
if (viaIds == null) viaIds = new ViaBlockIds(this.plugin);
}
public record Coords(int x, int y, int z) {}
public void onTuffXDisable() {
shutdown();
readyPlayers.clear();
if (viaIds != null) viaIds = null;
}
public boolean isPlayerReady(Player player) {
if (player == null) return false;
return readyPlayers.contains(player.getUniqueId());
}
public void setChunkInjector(ChunkInjector injector) {
if (injector == null) return;
this.chunkInjector = injector;
}
public void handlePacket(Player player, byte[] data) {
try (DataInputStream i = new DataInputStream(new ByteArrayInputStream(data))) {
i.readInt();
i.readInt();
i.readInt();
int al = i.readUnsignedByte();
byte[] ab = new byte[al];
i.readFully(ab);
String subchannel = new String(ab, StandardCharsets.UTF_8);
handlePacket(player, subchannel);
} catch (IOException e) {
log(Level.WARNING, "Failed to parse plugin message from " + player.getName() + ": " + e.getMessage());
}
}
private void handlePacket(Player player, String subchannel) {
if (!enabledWorlds.contains(player.getWorld().getName()) && !subchannel.equalsIgnoreCase("ready")) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(false));
return;
}
switch (subchannel.toLowerCase()) {
case "ready2":
debug("Player " + player.getName() + " is READY.");
readyPlayers.add(player.getUniqueId());
if (enabledWorlds.contains(player.getWorld().getName())) {
readyPlayers.add(player.getUniqueId());
preCacheVisibleChunks(player);
if (chunkInjector != null) {
chunkInjector.inject(player);
}
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(true));
resendChunksInView(player);
} else {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(false));
}
break;
case "use_on_block":
break;
case "ready":
if (kickOutdatedClients) {
player.kickPlayer("§cYour client is not compatible with the version of §6TuffX§c the server has installed!\n§7Please update your client.");
}
}
}
private void preCacheVisibleChunks(Player player) {
World world = player.getWorld();
int viewDistance = player.getClientViewDistance();
int playerChunkX = player.getLocation().getChunk().getX();
int playerChunkZ = player.getLocation().getChunk().getZ();
Object2ObjectOpenHashMap<BlockData, int[]> cvt = new Object2ObjectOpenHashMap<>(256);
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int currentChunkX = playerChunkX + x;
int currentChunkZ = playerChunkZ + z;
if (world.isChunkLoaded(currentChunkX, currentChunkZ)) {
WorldChunk k = new WorldChunk(world.getName(), currentChunkX, currentChunkZ);
if (chunkCache.getIfPresent(k) == null) {
try {
Chunk chunk = world.getChunkAt(currentChunkX, currentChunkZ);
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
cvt.clear();
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, currentChunkX, currentChunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
} catch (Exception e) { debug("Exception while pre-caching visible chunks for player "+player.getName()+": "+e.getMessage()); }
}
}
}
}
}
private static final int Y0_CHUNKS_PER_TICK = 8;
public void resendChunksInView(Player player) {
World world = player.getWorld();
int viewDistance = player.getClientViewDistance();
int playerChunkX = player.getLocation().getChunk().getX();
int playerChunkZ = player.getLocation().getChunk().getZ();
List<int[]> chunks = new ArrayList<>();
for (int x = -viewDistance; x <= viewDistance; x++) {
for (int z = -viewDistance; z <= viewDistance; z++) {
int currentChunkX = playerChunkX + x;
int currentChunkZ = playerChunkZ + z;
if (world.isChunkLoaded(currentChunkX, currentChunkZ)) {
chunks.add(new int[]{currentChunkX, currentChunkZ, x * x + z * z});
}
}
}
chunks.sort((a, b) -> Integer.compare(a[2], b[2]));
sendY0ChunksBatched(player, world.getName(), chunks, 0);
}
private void sendY0ChunksBatched(Player player, String worldName, List<int[]> chunks, int startIndex) {
if (!player.isOnline() || startIndex >= chunks.size()) return;
int endIndex = Math.min(startIndex + Y0_CHUNKS_PER_TICK, chunks.size());
for (int i = startIndex; i < endIndex; i++) {
int[] chunk = chunks.get(i);
WorldChunk k = new WorldChunk(worldName, chunk[0], chunk[1]);
ObjectArrayList<byte[]> cachedData = chunkCache.getIfPresent(k);
if (cachedData != null && !cachedData.isEmpty()) {
for (byte[] py : cachedData) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, py);
}
}
}
if (endIndex < chunks.size()) {
final int nextStart = endIndex;
if (player.isOnline()) {
SchedulerCompat.runEntityLater(player, plugin, () -> sendY0ChunksBatched(player, worldName, chunks, nextStart), 1L);
}
}
}
private byte[] y0StatusPkt(boolean s) {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("y0_status");
out.writeBoolean(s);
return bout.toByteArray();
} catch (IOException e) { return null; }
}
private byte[] dimensionChangePkt() {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("dimension_change");
return bout.toByteArray();
} catch (IOException e) { return null; }
}
public void handlePlayerChangeWorld(PlayerChangedWorldEvent event) {
Player player = event.getPlayer();
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, dimensionChangePkt());
boolean isEnabledWorld = enabledWorlds.contains(player.getWorld().getName());
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(isEnabledWorld));
if (isPlayerReady(player) && isEnabledWorld) {
resendChunksInView(player);
}
}
public void handlePlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, dimensionChangePkt());
boolean isEnabledWorld = enabledWorlds.contains(player.getWorld().getName());
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, y0StatusPkt(isEnabledWorld));
}
public void processAndSendChunk(final Player player, final Chunk c) {
if (c == null || player == null || !player.isOnline()) return;
if (!c.getWorld().equals(player.getWorld())) return;
if (enabledWorlds != null && !enabledWorlds.contains(c.getWorld().getName())) return;
final WorldChunk k = new WorldChunk(c.getWorld().getName(), c.getX(), c.getZ());
ObjectArrayList<byte[]> cachedData = chunkCache.getIfPresent(k);
if (cachedData != null) {
if (player.isOnline() && c.getWorld().equals(player.getWorld())) {
for (byte[] py : cachedData) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, py);
}
}
return;
}
final ChunkSnapshot snapshot = c.getChunkSnapshot(false, false, false);
processSnapshotAsync(player, snapshot, c.getX(), c.getZ());
}
private void processSnapshotAsync(final Player player, final ChunkSnapshot snapshot, final int chunkX, final int chunkZ) {
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) return;
final WorldChunk k = new WorldChunk(snapshot.getWorldName(), chunkX, chunkZ);
executor.submit(() -> {
final ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
final Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
for (int sy = -4; sy < 0; sy++) {
if (!player.isOnline()) {
return;
}
try {
byte[] py = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (py != null) {
pp.add(py);
}
} catch (IOException e) {
severe("Payload creation failed: " + e.getMessage());
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
if (!pp.isEmpty()) {
for (byte[] py : pp) {
SchedulerCompat.sendPluginMessage(plugin, player, CHANNEL, py);
}
}
});
}
private void invalidateChunkCache(World w, int x, int z) {
WorldChunk k = new WorldChunk(w.getName(), x >> 4, z >> 4);
chunkCache.invalidate(k);
chunkCacheCombined.invalidate(k);
}
public byte[] getY0DataForChunk(Player player, int chunkX, int chunkZ) {
if (!isPlayerReady(player)) return null;
World world = player.getWorld();
if (enabledWorlds == null || !enabledWorlds.contains(world.getName())) return null;
WorldChunk k = new WorldChunk(world.getName(), chunkX, chunkZ);
byte[] combined = chunkCacheCombined.getIfPresent(k);
if (combined != null) return combined.length > 0 ? combined : null;
ObjectArrayList<byte[]> cachedData = chunkCache.getIfPresent(k);
if (cachedData != null) {
byte[] cb = buildCombined(cachedData);
if (cb != null) chunkCacheCombined.put(k, cb);
return cb;
}
return null;
}
private byte[] buildCombined(ObjectArrayList<byte[]> sections) {
if (sections.isEmpty()) return null;
int totalLen = 0;
for (int i = 0, l = sections.size(); i < l; i++) {
totalLen += sections.get(i).length;
}
byte[] result = new byte[totalLen];
int offset = 0;
for (int i = 0, l = sections.size(); i < l; i++) {
byte[] s = sections.get(i);
System.arraycopy(s, 0, result, offset, s.length);
offset += s.length;
}
return result;
}
private void storeCombined(@Nonnull WorldChunk k, ObjectArrayList<byte[]> sections) {
byte[] cb = buildCombined(sections);
if (cb != null) {
chunkCacheCombined.put(k, cb);
}
}
public void preCacheY0Data(Player player, int chunkX, int chunkZ) {
if (!isPlayerReady(player)) return;
World world = player.getWorld();
if (enabledWorlds == null || !enabledWorlds.contains(world.getName())) return;
WorldChunk k = new WorldChunk(world.getName(), chunkX, chunkZ);
if (chunkCache.getIfPresent(k) != null) return;
if (!world.isChunkLoaded(chunkX, chunkZ)) return;
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) return;
executor.submit(() -> {
try {
if (chunkCache.getIfPresent(k) != null) return;
Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
} catch (Exception e) { debug("Exception while pre-caching chunk %s, %s for %s: %s".formatted(chunkX, chunkZ, player.getName(), e.getMessage())); }
});
}
public void cacheChunkWithCallback(Player player, int chunkX, int chunkZ, Consumer<byte[]> callback) {
if (!isPlayerReady(player)) {
callback.accept(null);
return;
}
World world = player.getWorld();
if (enabledWorlds == null || !enabledWorlds.contains(world.getName())) {
callback.accept(null);
return;
}
WorldChunk k = new WorldChunk(world.getName(), chunkX, chunkZ);
byte[] combined = chunkCacheCombined.getIfPresent(k);
if (combined != null) {
callback.accept(combined.length > 0 ? combined : null);
return;
}
ObjectArrayList<byte[]> existing = chunkCache.getIfPresent(k);
if (existing != null) {
byte[] cb = buildCombined(existing);
if (cb != null) chunkCacheCombined.put(k, cb);
callback.accept(cb);
return;
}
if (!world.isChunkLoaded(chunkX, chunkZ)) {
callback.accept(null);
return;
}
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) {
callback.accept(null);
return;
}
executor.submit(() -> {
try {
byte[] cached = chunkCacheCombined.getIfPresent(k);
if (cached != null) {
callback.accept(cached.length > 0 ? cached : null);
return;
}
Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
byte[] cb = buildCombined(pp);
if (cb != null) chunkCacheCombined.put(k, cb);
callback.accept(cb);
} catch (Exception e) {
callback.accept(null);
}
});
}
public void handlePlayerQuit(PlayerQuitEvent event) {
if (chunkInjector != null) {
chunkInjector.eject(event.getPlayer());
}
readyPlayers.remove(event.getPlayer().getUniqueId());
}
public void handleChunkLoad(ChunkLoadEvent event) {
if (enabledWorlds == null || !enabledWorlds.contains(event.getWorld().getName())) return;
if (!hasReadyPlayersInWorld(event.getWorld())) return;
Chunk chunk = event.getChunk();
int chunkX = chunk.getX();
int chunkZ = chunk.getZ();
WorldChunk k = new WorldChunk(event.getWorld().getName(), chunkX, chunkZ);
if (chunkCache.getIfPresent(k) != null) return;
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, false, false);
ExecutorService executor = chunkProcessor;
if (executor == null || executor.isShutdown()) return;
executor.submit(() -> {
try {
if (chunkCache.getIfPresent(k) != null) return;
Object2ObjectOpenHashMap<BlockData, int[]> cvt = threadChunkData.get();
cvt.clear();
ObjectArrayList<byte[]> pp = new ObjectArrayList<>(4);
for (int sy = -4; sy < 0; sy++) {
byte[] sectionData = createSectionPayload(snapshot, chunkX, chunkZ, sy, cvt);
if (sectionData != null) {
pp.add(sectionData);
}
}
chunkCache.put(k, pp);
storeCombined(k, pp);
} catch (Exception e) { debug("Exception while handling chunk load: "+e.getMessage()); }
});
}
private byte[] createSectionPayload(ChunkSnapshot s, int x, int z, int sy, Object2ObjectOpenHashMap<BlockData, int[]> c) throws IOException {
// Ensure thread-local buffer is exactly 12,288 bytes to prevent overflow
byte[] bd = threadData.get();
Arrays.fill(bd, (byte) 0);
int idx = 0;
boolean hasContent = false;
int by = sy << 4;
// Optimized Loop Order: Matches standard Minecraft internal memory layouts
for (int xx = 0; xx < 16; xx++) {
for (int zz = 0; zz < 16; zz++) {
for (int y = 0; y < 16; y++) {
int wy = by + y;
BlockData blkData = s.getBlockData(xx, wy, zz);
int[] ld = c.get(blkData); // Fast map lookup
if (ld == null) { // Avoid getOrDefault overhead
ld = (viaIds != null) ? viaIds.toLegacy(blkData) : EMPTY_LEGACY;
c.put(blkData, ld);
}
// Bitwise packing
short lb = (short) ((ld[1] << 12) | (ld[0] & 0xFFF));
byte pl = (byte) ((s.getBlockSkyLight(xx, wy, zz) << 4) | s.getBlockEmittedLight(xx, wy, zz));
// Write sequence
int linear = ((y << 8) | (zz << 4) | xx) * 3;
bd[linear] = (byte) (lb >> 8);
bd[linear + 1] = (byte) lb;
bd[linear + 2] = pl;
if (linear + 3 > idx) idx = linear + 3;
if (lb != 0 || pl != 0) {
hasContent = true;
}
}
}
}
if (!hasContent) return null;
ByteArrayOutputStream bout = threadOut.get();
bout.reset();
// DataOutputStream wrapper safely writes schema
try (DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("chunk_data");
out.writeInt(x);
out.writeInt(z);
out.writeInt(sy);
out.write(bd, 0, idx);
}
return bout.toByteArray();
}
public void handleBlockBreak(BlockBreakEvent event) {
if (event.getBlock().getY() < 0) {
handleBlockChange(event.getBlock().getLocation(), event.getBlock().getBlockData(), Material.AIR.createBlockData());
invalidateChunkCache(event.getBlock().getWorld(), event.getBlock().getX(), event.getBlock().getZ());
}
}
public void handleBlockPlace(BlockPlaceEvent event) {
if (event.getBlock().getY() < 0) {
handleBlockChange(event.getBlock().getLocation(), event.getBlockReplacedState().getBlockData(), event.getBlock().getBlockData());
invalidateChunkCache(event.getBlock().getWorld(), event.getBlock().getX(), event.getBlock().getZ());
}
}
public void handleBlockPhysics(BlockPhysicsEvent event) {
final Block block = event.getBlock();
if (block.getY() < 0) {
final Location loc = block.getLocation();
final World world = loc.getWorld();
SchedulerCompat.runRegionLater(plugin, loc, () -> {
BlockData ud = world.getBlockData(loc);
sendSingleBlockUpdate(loc, ud);
invalidateChunkCache(world, loc.getBlockX(), loc.getBlockZ());
}, 1L);
}
}
public void handleBlockExplode(BlockExplodeEvent event) {
final List<Block> btu = new ArrayList<>(event.blockList());
for (Block block : btu) {
if (block.getY() >= 0) continue;
final Location loc = block.getLocation();
SchedulerCompat.runRegionLater(plugin, loc, () -> {
sendSingleBlockUpdate(loc, Material.AIR.createBlockData());
invalidateChunkCache(loc.getWorld(), loc.getBlockX(), loc.getBlockZ());
}, 1L);
}
}
public void handleBlockFromTo(BlockFromToEvent event) {
final Block block = event.getToBlock();
if (block.getY() < 0) {
SchedulerCompat.runRegionLater(plugin, block.getLocation(), () -> {
sendSingleBlockUpdate(block.getLocation(), block.getBlockData());
invalidateChunkCache(block.getWorld(), block.getX(), block.getZ());
}, 1L);
}
}
private void sendSingleBlockUpdate(Location loc, BlockData data) {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("block_update");
out.writeInt(loc.getBlockX());
out.writeInt(loc.getBlockY());
out.writeInt(loc.getBlockZ());
int[] ld = viaIds.toLegacy(data);
out.writeShort((short) ((ld[1] << 12) | (ld[0] & 0xFFF)));
byte[] py = bout.toByteArray();
sendToNearbyPlayers(loc, py);
} catch (IOException e) {
severe("Failed to create single block update payload: " + e.getMessage());
}
}
public static int getEmission(BlockData data) {
if (getLightEmissionMethod != null) {
try {
return (int) getLightEmissionMethod.invoke(data);
} catch (Exception e) {}
}
return legacy_light_map.getOrDefault(data.getMaterial(), 0);
}
private void handleBlockChange(Location loc, BlockData od, BlockData nd) {
sendSingleBlockUpdate(loc, nd);
boolean oe = getEmission(od) > 0;
boolean ne = getEmission(nd) > 0;
boolean oo = od.getMaterial().isOccluding();
boolean no = nd.getMaterial().isOccluding();
if (oe != ne || oo != no) {
sendLightUpdate(loc);
}
}
private void sendLightUpdate(Location loc) {
ObjectOpenHashSet<Coords> stu = new ObjectOpenHashSet<>();
World w = loc.getWorld();
int bx = loc.getBlockX();
int by = loc.getBlockY();
int bz = loc.getBlockZ();
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dz = -1; dz <= 1; dz++) {
int ny = by + dy;
if (ny < -64 || ny >= 0) continue;
stu.add(new Coords(
(bx + dx) >> 4,
ny >> 4,
(bz + dz) >> 4
));
}
}
}
for (Coords sc : stu) {
if (!w.isChunkLoaded(sc.x, sc.z)) continue;
ChunkSnapshot s = w.getChunkAt(sc.x, sc.z).getChunkSnapshot(true, false, false);
if (chunkProcessor != null && !chunkProcessor.isShutdown()) {
chunkProcessor.submit(() -> {
try {
byte[] py = createLightPayload(s, sc);
sendToNearbyPlayers(loc, py);
} catch (IOException e) {
severe("Failed to create lighting payload: " + e.getMessage());
}
});
}
}
}
private void sendToNearbyPlayers(Location loc, byte[] payload) {
if (payload == null || loc.getWorld() == null || readyPlayers.isEmpty()) return;
final World world = loc.getWorld();
SchedulerCompat.runGlobal(plugin, () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!player.isOnline() || !isPlayerReady(player)) continue;
if (!world.equals(player.getWorld())) continue;
if (player.getLocation().distanceSquared(loc) >= 4096) continue;
SchedulerCompat.runEntity(player, plugin, () -> {
if (!player.isOnline() || !isPlayerReady(player)) return;
if (!world.equals(player.getWorld())) return;
if (player.getLocation().distanceSquared(loc) >= 4096) return;
player.sendPluginMessage(plugin, CHANNEL, payload);
});
}
});
}
private byte[] createLightPayload(ChunkSnapshot s, Coords sc) throws IOException {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(4120);
DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("lighting_update");
out.writeInt(sc.x);
out.writeInt(sc.z);
out.writeInt(sc.y);
byte[] ld = new byte[4096];
int by = sc.y * 16;
int i = 0;
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int wy = by + y;
int bl = s.getBlockEmittedLight(x, wy, z);
int sl = s.getBlockSkyLight(x, wy, z);
ld[i++] = (byte) ((sl << 4) | bl);
}
}
}
out.write(ld);
return bout.toByteArray();
}
}
private boolean hasReadyPlayersInWorld(World world) {
for (UUID playerId : readyPlayers) {
Player player = plugin.getServer().getPlayer(playerId);
if (player != null && player.isOnline() && world.equals(player.getWorld())) {
return true;
}
}
return false;
}
}
+94
View File
@@ -0,0 +1,94 @@
# \\// //=\\
# || || - ||
# || \\=//
y0:
y0-enabled: true
# Debug mode
debug-mode: false
# Worlds for y0 to be enabled in
enabled-worlds:
- 'world'
# The amount of threads for the chunk processor to use - using more threads processes chunks faster, but increases CPU usage
# -1 is automatic
chunk-processor-threads: 3
# The size of the cache
cache-size: 192
# How long until the cache expires (minutes)
cache-expiration: 2
# Kick outdated clients that (generally) not compatible with this version of TuffX
kick-outdated-clients: false
# ||\\ ||== //_\\ <> //= ==== ||\\ \\//
# ||// ||== || __ || \\ || ||// ||
# ||\\ ||== \\__/ || =// || ||\\ ||
registry:
# Set to true to list your server on the TuffClient discovery
enabled: false
# WebSocket URL for the server discovery
server-url: 'wss://api.tuffest.org/ws'
# IP for your Eaglercraft server
server: 'wss://urserverip.net'
# \\ // <> //\\ ||\\ || //=\\ //= ||// //=
# \\ // || //==\\ ||<< || || || || ||\\ \\
# \\// || // \\ ||// ||== \\=// \\= || \\ =//
viablocks:
viablocks-enabled: false
# Set to true to send a welcome book to players with the ViaBlocks client when they join for the first time.
# This book can explain the feature and provide a link for bug reports.
send-welcome-book: false
# Set to true to enable detailed logging in the server console for debugging.
debug: false
# Sync mode controls how often chunk and block updates are sent to clients.
# normal: base speed for fast sync.
# reduced: much slower updates to minimize client lag.
sync-mode: normal
# \\ // <> //\\ ||== ||\ || ==== <> ==== <> ||== //=
# \\ // || //==\\ ||== ||\\|| || || || || ||== \\
# \\// || // \\ ||== || \|| || || || || ||== =//
viaentities:
viaentities-enabled: true
# Set to true to enable detailed logging in the server console for debugging.
debug: false
# Measured in blocks, leave as '-1' for the default view distance.
max-distance: -1
# ==== || || ||== ||== //\\ //= ==== <> //=\\ ||\ || //=
# || || || ||== ||== //==\\ || || || || || ||\\|| \\
# || \\=// || || // \\ \\= || || \\=// || \|| =//
# 1.13+ swimming
swimming:
enabled: true
debug: false
# Allows players to see 1.13+ things in the creative menu
creative-items:
enabled: true
debug: false
restrictions:
enabled: true
debug: false
# What TuffClient modules are not allowed
disallow:
- clientbrand
+84
View File
@@ -0,0 +1,84 @@
{
"modern_entities": {
"minecraft:allay": {"added": "1.19", "model": "allay", "animated": true},
"minecraft:armadillo": {"added": "1.20.5", "model": "armadillo", "animated": true},
"minecraft:axolotl": {"added": "1.17", "model": "axolotl", "animated": true},
"minecraft:bee": {"added": "1.15", "model": "bee", "animated": true},
"minecraft:bogged": {"added": "1.21", "model": "bogged", "animated": true},
"minecraft:breeze": {"added": "1.21", "model": "breeze", "animated": true},
"minecraft:breeze_wind_charge": {"added": "1.21", "model": "wind_charge", "animated": false},
"minecraft:camel": {"added": "1.20", "model": "camel", "animated": true},
"minecraft:cat": {"added": "1.14", "model": "cat", "animated": true},
"minecraft:cod": {"added": "1.13", "model": "cod", "animated": true},
"minecraft:copper_golem": {"added": "1.21", "model": "copper_golem", "animated": true},
"minecraft:creaking": {"added": "1.21", "model": "creaking", "animated": true},
"minecraft:dolphin": {"added": "1.13", "model": "dolphin", "animated": true},
"minecraft:drowned": {"added": "1.13", "model": "drowned", "animated": true},
"minecraft:fox": {"added": "1.14", "model": "fox", "animated": true},
"minecraft:frog": {"added": "1.19", "model": "frog", "animated": true},
"minecraft:glow_squid": {"added": "1.17", "model": "glow_squid", "animated": true},
"minecraft:goat": {"added": "1.17", "model": "goat", "animated": true},
"minecraft:happy_ghast": {"added": "1.21", "model": "happy_ghast", "animated": true},
"minecraft:hoglin": {"added": "1.16", "model": "hoglin", "animated": true},
"minecraft:panda": {"added": "1.14", "model": "panda", "animated": true},
"minecraft:phantom": {"added": "1.13", "model": "phantom", "animated": true},
"minecraft:piglin": {"added": "1.16", "model": "piglin", "animated": true},
"minecraft:piglin_brute": {"added": "1.16.2", "model": "piglin_brute", "animated": true},
"minecraft:pillager": {"added": "1.14", "model": "pillager", "animated": true},
"minecraft:pufferfish": {"added": "1.13", "model": "pufferfish", "animated": true},
"minecraft:ravager": {"added": "1.14", "model": "ravager", "animated": true},
"minecraft:salmon": {"added": "1.13", "model": "salmon", "animated": true},
"minecraft:sniffer": {"added": "1.20", "model": "sniffer", "animated": true},
"minecraft:strider": {"added": "1.16", "model": "strider", "animated": true},
"minecraft:tadpole": {"added": "1.19", "model": "tadpole", "animated": true},
"minecraft:trader_llama": {"added": "1.14", "model": "trader_llama", "animated": true},
"minecraft:trident": {"added": "1.13", "model": "trident", "animated": false},
"minecraft:tropical_fish": {"added": "1.13", "model": "tropical_fish", "animated": true},
"minecraft:turtle": {"added": "1.13", "model": "turtle", "animated": true},
"minecraft:vex": {"added": "1.11", "model": "vex", "animated": true},
"minecraft:wandering_trader": {"added": "1.14", "model": "wandering_trader", "animated": true},
"minecraft:warden": {"added": "1.19", "model": "warden", "animated": true},
"minecraft:wind_charge": {"added": "1.21", "model": "wind_charge", "animated": false},
"minecraft:zoglin": {"added": "1.16", "model": "zoglin", "animated": true},
"minecraft:glow_item_frame": {"added": "1.17", "model": "item_frame", "animated": false},
"minecraft:marker": {"added": "1.17", "model": null, "animated": false},
"minecraft:ominous_item_spawner": {"added": "1.21", "model": null, "animated": false}
},
"entity_size": {
"minecraft:allay": {"width": 0.35, "height": 0.6},
"minecraft:armadillo": {"width": 0.7, "height": 0.65},
"minecraft:axolotl": {"width": 0.75, "height": 0.42},
"minecraft:bee": {"width": 0.7, "height": 0.6},
"minecraft:bogged": {"width": 0.6, "height": 1.99},
"minecraft:breeze": {"width": 0.6, "height": 1.77},
"minecraft:camel": {"width": 1.7, "height": 2.375},
"minecraft:cat": {"width": 0.6, "height": 0.7},
"minecraft:cod": {"width": 0.5, "height": 0.3},
"minecraft:creaking": {"width": 0.9, "height": 2.7},
"minecraft:dolphin": {"width": 0.9, "height": 0.6},
"minecraft:drowned": {"width": 0.6, "height": 1.95},
"minecraft:fox": {"width": 0.6, "height": 0.7},
"minecraft:frog": {"width": 0.5, "height": 0.5},
"minecraft:glow_squid": {"width": 0.8, "height": 0.8},
"minecraft:goat": {"width": 0.9, "height": 1.3},
"minecraft:happy_ghast": {"width": 4.0, "height": 4.0},
"minecraft:hoglin": {"width": 1.4, "height": 1.4},
"minecraft:panda": {"width": 1.3, "height": 1.25},
"minecraft:phantom": {"width": 0.9, "height": 0.5},
"minecraft:piglin": {"width": 0.6, "height": 1.95},
"minecraft:piglin_brute": {"width": 0.6, "height": 1.95},
"minecraft:pillager": {"width": 0.6, "height": 1.95},
"minecraft:pufferfish": {"width": 0.7, "height": 0.7},
"minecraft:ravager": {"width": 1.95, "height": 2.2},
"minecraft:salmon": {"width": 0.7, "height": 0.4},
"minecraft:sniffer": {"width": 1.9, "height": 1.75},
"minecraft:strider": {"width": 0.9, "height": 1.7},
"minecraft:tadpole": {"width": 0.4, "height": 0.3},
"minecraft:trident": {"width": 0.5, "height": 0.5},
"minecraft:tropical_fish": {"width": 0.5, "height": 0.4},
"minecraft:turtle": {"width": 1.2, "height": 0.4},
"minecraft:vex": {"width": 0.4, "height": 0.8},
"minecraft:warden": {"width": 0.9, "height": 2.9},
"minecraft:zoglin": {"width": 1.4, "height": 1.4}
}
}
+467
View File
@@ -0,0 +1,467 @@
{
"sounds": [
"ambient.cave",
"block.anvil.break",
"block.anvil.destroy",
"block.anvil.fall",
"block.anvil.hit",
"block.anvil.land",
"block.anvil.place",
"block.anvil.step",
"block.anvil.use",
"block.brewing_stand.brew",
"block.chest.close",
"block.chest.locked",
"block.chest.open",
"block.chorus_flower.death",
"block.chorus_flower.grow",
"block.cloth.break",
"block.cloth.fall",
"block.cloth.hit",
"block.cloth.place",
"block.cloth.step",
"block.comparator.click",
"block.dispenser.dispense",
"block.dispenser.fail",
"block.dispenser.launch",
"block.enchantment_table.use",
"block.end_gateway.spawn",
"block.enderchest.close",
"block.enderchest.open",
"block.fence_gate.close",
"block.fence_gate.open",
"block.fire.ambient",
"block.fire.extinguish",
"block.furnace.fire_crackle",
"block.glass.break",
"block.glass.fall",
"block.glass.hit",
"block.glass.place",
"block.glass.step",
"block.grass.break",
"block.grass.fall",
"block.grass.hit",
"block.grass.place",
"block.grass.step",
"block.gravel.break",
"block.gravel.fall",
"block.gravel.hit",
"block.gravel.place",
"block.gravel.step",
"block.iron_door.close",
"block.iron_door.open",
"block.iron_trapdoor.close",
"block.iron_trapdoor.open",
"block.ladder.break",
"block.ladder.fall",
"block.ladder.hit",
"block.ladder.place",
"block.ladder.step",
"block.lava.ambient",
"block.lava.extinguish",
"block.lava.pop",
"block.lever.click",
"block.metal.break",
"block.metal.fall",
"block.metal.hit",
"block.metal.place",
"block.metal.step",
"block.metal_pressureplate.click_off",
"block.metal_pressureplate.click_on",
"block.note.basedrum",
"block.note.bass",
"block.note.harp",
"block.note.hat",
"block.note.pling",
"block.note.snare",
"block.piston.contract",
"block.piston.extend",
"block.portal.ambient",
"block.portal.travel",
"block.portal.trigger",
"block.redstone_torch.burnout",
"block.sand.break",
"block.sand.fall",
"block.sand.hit",
"block.sand.place",
"block.sand.step",
"block.slime.break",
"block.slime.fall",
"block.slime.hit",
"block.slime.place",
"block.slime.step",
"block.snow.break",
"block.snow.fall",
"block.snow.hit",
"block.snow.place",
"block.snow.step",
"block.stone.break",
"block.stone.fall",
"block.stone.hit",
"block.stone.place",
"block.stone.step",
"block.stone_button.click_off",
"block.stone_button.click_on",
"block.stone_pressureplate.click_off",
"block.stone_pressureplate.click_on",
"block.tripwire.attach",
"block.tripwire.click_off",
"block.tripwire.click_on",
"block.tripwire.detach",
"block.water.ambient",
"block.waterlily.place",
"block.wood.break",
"block.wood.fall",
"block.wood.hit",
"block.wood.place",
"block.wood.step",
"block.wood_button.click_off",
"block.wood_button.click_on",
"block.wood_pressureplate.click_off",
"block.wood_pressureplate.click_on",
"block.wooden_door.close",
"block.wooden_door.open",
"block.wooden_trapdoor.close",
"block.wooden_trapdoor.open",
"enchant.thorns.hit",
"entity.armorstand.break",
"entity.armorstand.fall",
"entity.armorstand.hit",
"entity.armorstand.place",
"entity.arrow.hit",
"entity.arrow.hit_player",
"entity.arrow.shoot",
"entity.bat.ambient",
"entity.bat.death",
"entity.bat.hurt",
"entity.bat.loop",
"entity.bat.takeoff",
"entity.blaze.ambient",
"entity.blaze.burn",
"entity.blaze.death",
"entity.blaze.hurt",
"entity.blaze.shoot",
"entity.bobber.splash",
"entity.bobber.throw",
"entity.cat.ambient",
"entity.cat.death",
"entity.cat.hiss",
"entity.cat.hurt",
"entity.cat.purr",
"entity.cat.purreow",
"entity.chicken.ambient",
"entity.chicken.death",
"entity.chicken.egg",
"entity.chicken.hurt",
"entity.chicken.step",
"entity.cow.ambient",
"entity.cow.death",
"entity.cow.hurt",
"entity.cow.milk",
"entity.cow.step",
"entity.creeper.death",
"entity.creeper.hurt",
"entity.creeper.primed",
"entity.donkey.ambient",
"entity.donkey.angry",
"entity.donkey.chest",
"entity.donkey.death",
"entity.donkey.hurt",
"entity.egg.throw",
"entity.elder_guardian.ambient",
"entity.elder_guardian.ambient_land",
"entity.elder_guardian.curse",
"entity.elder_guardian.death",
"entity.elder_guardian.death_land",
"entity.elder_guardian.hurt",
"entity.elder_guardian.hurt_land",
"entity.enderdragon.ambient",
"entity.enderdragon.death",
"entity.enderdragon.flap",
"entity.enderdragon.growl",
"entity.enderdragon.hurt",
"entity.enderdragon.shoot",
"entity.enderdragon_fireball.explode",
"entity.endereye.launch",
"entity.endermen.ambient",
"entity.endermen.death",
"entity.endermen.hurt",
"entity.endermen.scream",
"entity.endermen.stare",
"entity.endermen.teleport",
"entity.endermite.ambient",
"entity.endermite.death",
"entity.endermite.hurt",
"entity.endermite.step",
"entity.enderpearl.throw",
"entity.experience_bottle.throw",
"entity.experience_orb.pickup",
"entity.experience_orb.touch",
"entity.firework.blast",
"entity.firework.blast_far",
"entity.firework.large_blast",
"entity.firework.large_blast_far",
"entity.firework.launch",
"entity.firework.shoot",
"entity.firework.twinkle",
"entity.firework.twinkle_far",
"entity.generic.big_fall",
"entity.generic.burn",
"entity.generic.death",
"entity.generic.drink",
"entity.generic.eat",
"entity.generic.explode",
"entity.generic.extinguish_fire",
"entity.generic.hurt",
"entity.generic.small_fall",
"entity.generic.splash",
"entity.generic.swim",
"entity.ghast.ambient",
"entity.ghast.death",
"entity.ghast.hurt",
"entity.ghast.scream",
"entity.ghast.shoot",
"entity.ghast.warn",
"entity.guardian.ambient",
"entity.guardian.ambient_land",
"entity.guardian.attack",
"entity.guardian.death",
"entity.guardian.death_land",
"entity.guardian.flop",
"entity.guardian.hurt",
"entity.guardian.hurt_land",
"entity.horse.ambient",
"entity.horse.angry",
"entity.horse.armor",
"entity.horse.breathe",
"entity.horse.death",
"entity.horse.eat",
"entity.horse.gallop",
"entity.horse.hurt",
"entity.horse.jump",
"entity.horse.land",
"entity.horse.saddle",
"entity.horse.step",
"entity.horse.step_wood",
"entity.hostile.big_fall",
"entity.hostile.death",
"entity.hostile.hurt",
"entity.hostile.small_fall",
"entity.hostile.splash",
"entity.hostile.swim",
"entity.husk.ambient",
"entity.husk.death",
"entity.husk.hurt",
"entity.husk.step",
"entity.irongolem.attack",
"entity.irongolem.death",
"entity.irongolem.hurt",
"entity.irongolem.step",
"entity.item.break",
"entity.item.pickup",
"entity.itemframe.add_item",
"entity.itemframe.break",
"entity.itemframe.place",
"entity.itemframe.remove_item",
"entity.itemframe.rotate_item",
"entity.leashknot.break",
"entity.leashknot.place",
"entity.lightning.impact",
"entity.lightning.thunder",
"entity.lingeringpotion.throw",
"entity.magmacube.death",
"entity.magmacube.hurt",
"entity.magmacube.jump",
"entity.magmacube.squish",
"entity.minecart.inside",
"entity.minecart.riding",
"entity.mooshroom.shear",
"entity.mule.ambient",
"entity.mule.death",
"entity.mule.hurt",
"entity.painting.break",
"entity.painting.place",
"entity.pig.ambient",
"entity.pig.death",
"entity.pig.hurt",
"entity.pig.saddle",
"entity.pig.step",
"entity.player.attack.crit",
"entity.player.attack.knockback",
"entity.player.attack.nodamage",
"entity.player.attack.strong",
"entity.player.attack.sweep",
"entity.player.attack.weak",
"entity.player.big_fall",
"entity.player.breath",
"entity.player.burp",
"entity.player.death",
"entity.player.hurt",
"entity.player.levelup",
"entity.player.small_fall",
"entity.player.splash",
"entity.player.swim",
"entity.polar_bear.ambient",
"entity.polar_bear.baby_ambient",
"entity.polar_bear.death",
"entity.polar_bear.hurt",
"entity.polar_bear.step",
"entity.polar_bear.warning",
"entity.rabbit.ambient",
"entity.rabbit.attack",
"entity.rabbit.death",
"entity.rabbit.hurt",
"entity.rabbit.jump",
"entity.sheep.ambient",
"entity.sheep.death",
"entity.sheep.hurt",
"entity.sheep.shear",
"entity.sheep.step",
"entity.shulker.ambient",
"entity.shulker.close",
"entity.shulker.death",
"entity.shulker.hurt",
"entity.shulker.hurt_closed",
"entity.shulker.open",
"entity.shulker.shoot",
"entity.shulker.teleport",
"entity.shulker_bullet.hit",
"entity.shulker_bullet.hurt",
"entity.silverfish.ambient",
"entity.silverfish.death",
"entity.silverfish.hurt",
"entity.silverfish.step",
"entity.skeleton.ambient",
"entity.skeleton.death",
"entity.skeleton.hurt",
"entity.skeleton.shoot",
"entity.skeleton.step",
"entity.skeleton_horse.ambient",
"entity.skeleton_horse.death",
"entity.skeleton_horse.hurt",
"entity.slime.attack",
"entity.slime.death",
"entity.slime.hurt",
"entity.slime.jump",
"entity.slime.squish",
"entity.small_magmacube.death",
"entity.small_magmacube.hurt",
"entity.small_magmacube.squish",
"entity.small_slime.death",
"entity.small_slime.hurt",
"entity.small_slime.jump",
"entity.small_slime.squish",
"entity.snowball.throw",
"entity.snowman.ambient",
"entity.snowman.death",
"entity.snowman.hurt",
"entity.snowman.shoot",
"entity.spider.ambient",
"entity.spider.death",
"entity.spider.hurt",
"entity.spider.step",
"entity.splash_potion.break",
"entity.splash_potion.throw",
"entity.squid.ambient",
"entity.squid.death",
"entity.squid.hurt",
"entity.stray.ambient",
"entity.stray.death",
"entity.stray.hurt",
"entity.stray.step",
"entity.tnt.primed",
"entity.villager.ambient",
"entity.villager.death",
"entity.villager.hurt",
"entity.villager.no",
"entity.villager.trading",
"entity.villager.yes",
"entity.witch.ambient",
"entity.witch.death",
"entity.witch.drink",
"entity.witch.hurt",
"entity.witch.throw",
"entity.wither.ambient",
"entity.wither.break_block",
"entity.wither.death",
"entity.wither.hurt",
"entity.wither.shoot",
"entity.wither.spawn",
"entity.wither_skeleton.ambient",
"entity.wither_skeleton.death",
"entity.wither_skeleton.hurt",
"entity.wither_skeleton.step",
"entity.wolf.ambient",
"entity.wolf.death",
"entity.wolf.growl",
"entity.wolf.howl",
"entity.wolf.hurt",
"entity.wolf.pant",
"entity.wolf.shake",
"entity.wolf.step",
"entity.wolf.whine",
"entity.zombie.ambient",
"entity.zombie.attack_door_wood",
"entity.zombie.attack_iron_door",
"entity.zombie.break_door_wood",
"entity.zombie.death",
"entity.zombie.hurt",
"entity.zombie.infect",
"entity.zombie.step",
"entity.zombie_horse.ambient",
"entity.zombie_horse.death",
"entity.zombie_horse.hurt",
"entity.zombie_pig.ambient",
"entity.zombie_pig.angry",
"entity.zombie_pig.death",
"entity.zombie_pig.hurt",
"entity.zombie_villager.ambient",
"entity.zombie_villager.converted",
"entity.zombie_villager.cure",
"entity.zombie_villager.death",
"entity.zombie_villager.hurt",
"entity.zombie_villager.step",
"item.armor.equip_chain",
"item.armor.equip_diamond",
"item.armor.equip_generic",
"item.armor.equip_gold",
"item.armor.equip_iron",
"item.armor.equip_leather",
"item.bottle.fill",
"item.bottle.fill_dragonbreath",
"item.bucket.empty",
"item.bucket.empty_lava",
"item.bucket.fill",
"item.bucket.fill_lava",
"item.chorus_fruit.teleport",
"item.elytra.flying",
"item.firecharge.use",
"item.flintandsteel.use",
"item.hoe.till",
"item.shield.block",
"item.shield.break",
"item.shovel.flatten",
"music.creative",
"music.credits",
"music.dragon",
"music.end",
"music.game",
"music.menu",
"music.nether",
"record.11",
"record.13",
"record.blocks",
"record.cat",
"record.chirp",
"record.far",
"record.mall",
"record.mellohi",
"record.stal",
"record.strad",
"record.wait",
"record.ward",
"ui.button.click",
"weather.rain",
"weather.rain.above"
]
}
+497
View File
@@ -0,0 +1,497 @@
{
"sounds": [
"ambient.cave",
"block.anvil.break",
"block.anvil.destroy",
"block.anvil.fall",
"block.anvil.hit",
"block.anvil.land",
"block.anvil.place",
"block.anvil.step",
"block.anvil.use",
"block.brewing_stand.brew",
"block.chest.close",
"block.chest.locked",
"block.chest.open",
"block.chorus_flower.death",
"block.chorus_flower.grow",
"block.cloth.break",
"block.cloth.fall",
"block.cloth.hit",
"block.cloth.place",
"block.cloth.step",
"block.comparator.click",
"block.dispenser.dispense",
"block.dispenser.fail",
"block.dispenser.launch",
"block.enchantment_table.use",
"block.end_gateway.spawn",
"block.enderchest.close",
"block.enderchest.open",
"block.fence_gate.close",
"block.fence_gate.open",
"block.fire.ambient",
"block.fire.extinguish",
"block.furnace.fire_crackle",
"block.glass.break",
"block.glass.fall",
"block.glass.hit",
"block.glass.place",
"block.glass.step",
"block.grass.break",
"block.grass.fall",
"block.grass.hit",
"block.grass.place",
"block.grass.step",
"block.gravel.break",
"block.gravel.fall",
"block.gravel.hit",
"block.gravel.place",
"block.gravel.step",
"block.iron_door.close",
"block.iron_door.open",
"block.iron_trapdoor.close",
"block.iron_trapdoor.open",
"block.ladder.break",
"block.ladder.fall",
"block.ladder.hit",
"block.ladder.place",
"block.ladder.step",
"block.lava.ambient",
"block.lava.extinguish",
"block.lava.pop",
"block.lever.click",
"block.metal.break",
"block.metal.fall",
"block.metal.hit",
"block.metal.place",
"block.metal.step",
"block.metal_pressureplate.click_off",
"block.metal_pressureplate.click_on",
"block.note.basedrum",
"block.note.bass",
"block.note.harp",
"block.note.hat",
"block.note.pling",
"block.note.snare",
"block.piston.contract",
"block.piston.extend",
"block.portal.ambient",
"block.portal.travel",
"block.portal.trigger",
"block.redstone_torch.burnout",
"block.sand.break",
"block.sand.fall",
"block.sand.hit",
"block.sand.place",
"block.sand.step",
"block.shulker_box.close",
"block.shulker_box.open",
"block.slime.break",
"block.slime.fall",
"block.slime.hit",
"block.slime.place",
"block.slime.step",
"block.snow.break",
"block.snow.fall",
"block.snow.hit",
"block.snow.place",
"block.snow.step",
"block.stone.break",
"block.stone.fall",
"block.stone.hit",
"block.stone.place",
"block.stone.step",
"block.stone_button.click_off",
"block.stone_button.click_on",
"block.stone_pressureplate.click_off",
"block.stone_pressureplate.click_on",
"block.tripwire.attach",
"block.tripwire.click_off",
"block.tripwire.click_on",
"block.tripwire.detach",
"block.water.ambient",
"block.waterlily.place",
"block.wood.break",
"block.wood.fall",
"block.wood.hit",
"block.wood.place",
"block.wood.step",
"block.wood_button.click_off",
"block.wood_button.click_on",
"block.wood_pressureplate.click_off",
"block.wood_pressureplate.click_on",
"block.wooden_door.close",
"block.wooden_door.open",
"block.wooden_trapdoor.close",
"block.wooden_trapdoor.open",
"enchant.thorns.hit",
"entity.armorstand.break",
"entity.armorstand.fall",
"entity.armorstand.hit",
"entity.armorstand.place",
"entity.arrow.hit",
"entity.arrow.hit_player",
"entity.arrow.shoot",
"entity.bat.ambient",
"entity.bat.death",
"entity.bat.hurt",
"entity.bat.loop",
"entity.bat.takeoff",
"entity.blaze.ambient",
"entity.blaze.burn",
"entity.blaze.death",
"entity.blaze.hurt",
"entity.blaze.shoot",
"entity.bobber.splash",
"entity.bobber.throw",
"entity.cat.ambient",
"entity.cat.death",
"entity.cat.hiss",
"entity.cat.hurt",
"entity.cat.purr",
"entity.cat.purreow",
"entity.chicken.ambient",
"entity.chicken.death",
"entity.chicken.egg",
"entity.chicken.hurt",
"entity.chicken.step",
"entity.cow.ambient",
"entity.cow.death",
"entity.cow.hurt",
"entity.cow.milk",
"entity.cow.step",
"entity.creeper.death",
"entity.creeper.hurt",
"entity.creeper.primed",
"entity.donkey.ambient",
"entity.donkey.angry",
"entity.donkey.chest",
"entity.donkey.death",
"entity.donkey.hurt",
"entity.egg.throw",
"entity.elder_guardian.ambient",
"entity.elder_guardian.ambient_land",
"entity.elder_guardian.curse",
"entity.elder_guardian.death",
"entity.elder_guardian.death_land",
"entity.elder_guardian.hurt",
"entity.elder_guardian.flop",
"entity.elder_guardian.hurt_land",
"entity.enderdragon.ambient",
"entity.enderdragon.death",
"entity.enderdragon.flap",
"entity.enderdragon.growl",
"entity.enderdragon.hurt",
"entity.enderdragon.shoot",
"entity.enderdragon_fireball.explode",
"entity.endereye.launch",
"entity.endermen.ambient",
"entity.endermen.death",
"entity.endermen.hurt",
"entity.endermen.scream",
"entity.endermen.stare",
"entity.endermen.teleport",
"entity.endermite.ambient",
"entity.endermite.death",
"entity.endermite.hurt",
"entity.endermite.step",
"entity.enderpearl.throw",
"entity.evocation_fangs.attack",
"entity.evocation_illager.ambient",
"entity.evocation_illager.cast_spell",
"entity.evocation_illager.death",
"entity.evocation_illager.hurt",
"entity.evocation_illager.prepare_attack",
"entity.evocation_illager.prepare_summon",
"entity.evocation_illager.prepare_wololo",
"entity.experience_bottle.throw",
"entity.experience_orb.pickup",
"entity.firework.blast",
"entity.firework.blast_far",
"entity.firework.large_blast",
"entity.firework.large_blast_far",
"entity.firework.launch",
"entity.firework.shoot",
"entity.firework.twinkle",
"entity.firework.twinkle_far",
"entity.generic.big_fall",
"entity.generic.burn",
"entity.generic.death",
"entity.generic.drink",
"entity.generic.eat",
"entity.generic.explode",
"entity.generic.extinguish_fire",
"entity.generic.hurt",
"entity.generic.small_fall",
"entity.generic.splash",
"entity.generic.swim",
"entity.ghast.ambient",
"entity.ghast.death",
"entity.ghast.hurt",
"entity.ghast.scream",
"entity.ghast.shoot",
"entity.ghast.warn",
"entity.guardian.ambient",
"entity.guardian.ambient_land",
"entity.guardian.attack",
"entity.guardian.death",
"entity.guardian.death_land",
"entity.guardian.flop",
"entity.guardian.hurt",
"entity.guardian.hurt_land",
"entity.horse.ambient",
"entity.horse.angry",
"entity.horse.armor",
"entity.horse.breathe",
"entity.horse.death",
"entity.horse.eat",
"entity.horse.gallop",
"entity.horse.hurt",
"entity.horse.jump",
"entity.horse.land",
"entity.horse.saddle",
"entity.horse.step",
"entity.horse.step_wood",
"entity.hostile.big_fall",
"entity.hostile.death",
"entity.hostile.hurt",
"entity.hostile.small_fall",
"entity.hostile.splash",
"entity.hostile.swim",
"entity.husk.ambient",
"entity.husk.death",
"entity.husk.hurt",
"entity.husk.step",
"entity.irongolem.attack",
"entity.irongolem.death",
"entity.irongolem.hurt",
"entity.irongolem.step",
"entity.item.break",
"entity.item.pickup",
"entity.itemframe.add_item",
"entity.itemframe.break",
"entity.itemframe.place",
"entity.itemframe.remove_item",
"entity.itemframe.rotate_item",
"entity.leashknot.break",
"entity.leashknot.place",
"entity.lightning.impact",
"entity.lightning.thunder",
"entity.lingeringpotion.throw",
"entity.llama.ambient",
"entity.llama.angry",
"entity.llama.chest",
"entity.llama.death",
"entity.llama.eat",
"entity.llama.hurt",
"entity.llama.spit",
"entity.llama.step",
"entity.llama.swag",
"entity.magmacube.death",
"entity.magmacube.hurt",
"entity.magmacube.jump",
"entity.magmacube.squish",
"entity.minecart.inside",
"entity.minecart.riding",
"entity.mooshroom.shear",
"entity.mule.ambient",
"entity.mule.chest",
"entity.mule.death",
"entity.mule.hurt",
"entity.painting.break",
"entity.painting.place",
"entity.pig.ambient",
"entity.pig.death",
"entity.pig.hurt",
"entity.pig.saddle",
"entity.pig.step",
"entity.player.attack.crit",
"entity.player.attack.knockback",
"entity.player.attack.nodamage",
"entity.player.attack.strong",
"entity.player.attack.sweep",
"entity.player.attack.weak",
"entity.player.big_fall",
"entity.player.breath",
"entity.player.burp",
"entity.player.death",
"entity.player.hurt",
"entity.player.levelup",
"entity.player.small_fall",
"entity.player.splash",
"entity.player.swim",
"entity.polar_bear.ambient",
"entity.polar_bear.baby_ambient",
"entity.polar_bear.death",
"entity.polar_bear.hurt",
"entity.polar_bear.step",
"entity.polar_bear.warning",
"entity.rabbit.ambient",
"entity.rabbit.attack",
"entity.rabbit.death",
"entity.rabbit.hurt",
"entity.rabbit.jump",
"entity.sheep.ambient",
"entity.sheep.death",
"entity.sheep.hurt",
"entity.sheep.shear",
"entity.sheep.step",
"entity.shulker.ambient",
"entity.shulker.close",
"entity.shulker.death",
"entity.shulker.hurt",
"entity.shulker.hurt_closed",
"entity.shulker.open",
"entity.shulker.shoot",
"entity.shulker.teleport",
"entity.shulker_bullet.hit",
"entity.shulker_bullet.hurt",
"entity.silverfish.ambient",
"entity.silverfish.death",
"entity.silverfish.hurt",
"entity.silverfish.step",
"entity.skeleton.ambient",
"entity.skeleton.death",
"entity.skeleton.hurt",
"entity.skeleton.shoot",
"entity.skeleton.step",
"entity.skeleton_horse.ambient",
"entity.skeleton_horse.death",
"entity.skeleton_horse.hurt",
"entity.slime.attack",
"entity.slime.death",
"entity.slime.hurt",
"entity.slime.jump",
"entity.slime.squish",
"entity.small_magmacube.death",
"entity.small_magmacube.hurt",
"entity.small_magmacube.squish",
"entity.small_slime.death",
"entity.small_slime.hurt",
"entity.small_slime.jump",
"entity.small_slime.squish",
"entity.snowball.throw",
"entity.snowman.ambient",
"entity.snowman.death",
"entity.snowman.hurt",
"entity.snowman.shoot",
"entity.spider.ambient",
"entity.spider.death",
"entity.spider.hurt",
"entity.spider.step",
"entity.splash_potion.break",
"entity.splash_potion.throw",
"entity.squid.ambient",
"entity.squid.death",
"entity.squid.hurt",
"entity.stray.ambient",
"entity.stray.death",
"entity.stray.hurt",
"entity.stray.step",
"entity.tnt.primed",
"entity.vex.ambient",
"entity.vex.charge",
"entity.vex.death",
"entity.vex.hurt",
"entity.villager.ambient",
"entity.villager.death",
"entity.villager.hurt",
"entity.villager.no",
"entity.villager.trading",
"entity.villager.yes",
"entity.vindication_illager.ambient",
"entity.vindication_illager.death",
"entity.vindication_illager.hurt",
"entity.witch.ambient",
"entity.witch.death",
"entity.witch.drink",
"entity.witch.hurt",
"entity.witch.throw",
"entity.wither.ambient",
"entity.wither.break_block",
"entity.wither.death",
"entity.wither.hurt",
"entity.wither.shoot",
"entity.wither.spawn",
"entity.wither_skeleton.ambient",
"entity.wither_skeleton.death",
"entity.wither_skeleton.hurt",
"entity.wither_skeleton.step",
"entity.wolf.ambient",
"entity.wolf.death",
"entity.wolf.growl",
"entity.wolf.howl",
"entity.wolf.hurt",
"entity.wolf.pant",
"entity.wolf.shake",
"entity.wolf.step",
"entity.wolf.whine",
"entity.zombie.ambient",
"entity.zombie.attack_door_wood",
"entity.zombie.attack_iron_door",
"entity.zombie.break_door_wood",
"entity.zombie.death",
"entity.zombie.hurt",
"entity.zombie.infect",
"entity.zombie.step",
"entity.zombie_horse.ambient",
"entity.zombie_horse.death",
"entity.zombie_horse.hurt",
"entity.zombie_pig.ambient",
"entity.zombie_pig.angry",
"entity.zombie_pig.death",
"entity.zombie_pig.hurt",
"entity.zombie_villager.ambient",
"entity.zombie_villager.converted",
"entity.zombie_villager.cure",
"entity.zombie_villager.death",
"entity.zombie_villager.hurt",
"entity.zombie_villager.step",
"item.armor.equip_chain",
"item.armor.equip_diamond",
"item.armor.equip_elytra",
"item.armor.equip_generic",
"item.armor.equip_gold",
"item.armor.equip_iron",
"item.armor.equip_leather",
"item.bottle.empty",
"item.bottle.fill",
"item.bottle.fill_dragonbreath",
"item.bucket.empty",
"item.bucket.empty_lava",
"item.bucket.fill",
"item.bucket.fill_lava",
"item.chorus_fruit.teleport",
"item.elytra.flying",
"item.firecharge.use",
"item.flintandsteel.use",
"item.hoe.till",
"item.shield.block",
"item.shield.break",
"item.shovel.flatten",
"item.totem.use",
"music.creative",
"music.credits",
"music.dragon",
"music.end",
"music.game",
"music.menu",
"music.nether",
"record.11",
"record.13",
"record.blocks",
"record.cat",
"record.chirp",
"record.far",
"record.mall",
"record.mellohi",
"record.stal",
"record.strad",
"record.wait",
"record.ward",
"ui.button.click",
"weather.rain",
"weather.rain.above"
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
{
}
+250
View File
@@ -0,0 +1,250 @@
{
"sounds": [
"ambient.cave.cave",
"ambient.weather.rain",
"ambient.weather.thunder",
"game.player.hurt.fall.big",
"game.player.hurt.fall.small",
"game.neutral.hurt.fall.big",
"game.neutral.hurt.fall.small",
"game.hostile.hurt.fall.big",
"game.hostile.hurt.fall.small",
"game.player.hurt",
"game.neutral.hurt",
"game.hostile.hurt",
"game.player.die",
"game.neutral.die",
"game.hostile.die",
"dig.cloth",
"dig.grass",
"dig.gravel",
"dig.sand",
"dig.snow",
"dig.stone",
"dig.wood",
"fire.fire",
"fire.ignite",
"item.fireCharge.use",
"fireworks.blast",
"fireworks.blast_far",
"fireworks.largeBlast",
"fireworks.largeBlast_far",
"fireworks.launch",
"fireworks.twinkle",
"fireworks.twinkle_far",
"liquid.lava",
"liquid.lavapop",
"game.neutral.swim.splash",
"game.player.swim.splash",
"game.hostile.swim.splash",
"game.player.swim",
"game.neutral.swim",
"game.hostile.swim",
"liquid.water",
"minecart.base",
"minecart.inside",
"mob.bat.death",
"mob.bat.hurt",
"mob.bat.idle",
"mob.bat.loop",
"mob.bat.takeoff",
"mob.blaze.breathe",
"mob.blaze.death",
"mob.blaze.hit",
"mob.guardian.hit",
"mob.guardian.idle",
"mob.guardian.death",
"mob.guardian.elder.hit",
"mob.guardian.elder.idle",
"mob.guardian.elder.death",
"mob.guardian.land.hit",
"mob.guardian.land.idle",
"mob.guardian.land.death",
"mob.guardian.curse",
"mob.guardian.attack",
"mob.guardian.flop",
"mob.cat.hiss",
"mob.cat.hitt",
"mob.cat.meow",
"mob.cat.purr",
"mob.cat.purreow",
"mob.chicken.hurt",
"mob.chicken.plop",
"mob.chicken.say",
"mob.chicken.step",
"mob.cow.hurt",
"mob.cow.say",
"mob.cow.step",
"mob.creeper.death",
"mob.creeper.say",
"mob.enderdragon.end",
"mob.enderdragon.growl",
"mob.enderdragon.hit",
"mob.enderdragon.wings",
"mob.endermen.death",
"mob.endermen.hit",
"mob.endermen.idle",
"mob.endermen.portal",
"mob.endermen.scream",
"mob.endermen.stare",
"mob.ghast.affectionate_scream",
"mob.ghast.charge",
"mob.ghast.death",
"mob.ghast.fireball",
"mob.ghast.moan",
"mob.ghast.scream",
"mob.horse.angry",
"mob.horse.armor",
"mob.horse.breathe",
"mob.horse.death",
"mob.horse.donkey.angry",
"mob.horse.donkey.death",
"mob.horse.donkey.hit",
"mob.horse.donkey.idle",
"mob.horse.gallop",
"mob.horse.hit",
"mob.horse.idle",
"mob.horse.jump",
"mob.horse.land",
"mob.horse.leather",
"mob.horse.skeleton.death",
"mob.horse.skeleton.hit",
"mob.horse.skeleton.idle",
"mob.horse.soft",
"mob.horse.wood",
"mob.horse.zombie.death",
"mob.horse.zombie.hit",
"mob.horse.zombie.idle",
"mob.irongolem.death",
"mob.irongolem.hit",
"mob.irongolem.throw",
"mob.irongolem.walk",
"mob.magmacube.big",
"mob.magmacube.jump",
"mob.magmacube.small",
"mob.pig.death",
"mob.pig.say",
"mob.pig.step",
"mob.rabbit.hurt",
"mob.rabbit.idle",
"mob.rabbit.hop",
"mob.rabbit.death",
"mob.sheep.say",
"mob.sheep.shear",
"mob.sheep.step",
"mob.silverfish.hit",
"mob.silverfish.kill",
"mob.silverfish.say",
"mob.silverfish.step",
"mob.skeleton.death",
"mob.skeleton.hurt",
"mob.skeleton.say",
"mob.skeleton.step",
"mob.slime.attack",
"mob.slime.big",
"mob.slime.small",
"mob.spider.death",
"mob.spider.say",
"mob.spider.step",
"mob.villager.death",
"mob.villager.haggle",
"mob.villager.hit",
"mob.villager.idle",
"mob.villager.no",
"mob.villager.yes",
"mob.wither.death",
"mob.wither.hurt",
"mob.wither.idle",
"mob.wither.shoot",
"mob.wither.spawn",
"mob.wolf.bark",
"mob.wolf.death",
"mob.wolf.growl",
"mob.wolf.howl",
"mob.wolf.hurt",
"mob.wolf.panting",
"mob.wolf.shake",
"mob.wolf.step",
"mob.wolf.whine",
"mob.zombie.death",
"mob.zombie.hurt",
"mob.zombie.infect",
"mob.zombie.metal",
"mob.zombie.remedy",
"mob.zombie.say",
"mob.zombie.step",
"mob.zombie.unfect",
"mob.zombie.wood",
"mob.zombie.woodbreak",
"mob.zombiepig.zpig",
"mob.zombiepig.zpigangry",
"mob.zombiepig.zpigdeath",
"mob.zombiepig.zpighurt",
"note.bass",
"note.bassattack",
"note.bd",
"note.harp",
"note.hat",
"note.pling",
"note.snare",
"portal.portal",
"portal.travel",
"portal.trigger",
"random.anvil_break",
"random.anvil_land",
"random.anvil_use",
"random.bow",
"random.bowhit",
"random.break",
"random.burp",
"random.chestclosed",
"random.chestopen",
"gui.button.press",
"random.click",
"random.door_close",
"random.door_open",
"random.drink",
"random.eat",
"random.explode",
"random.fizz",
"game.tnt.primed",
"creeper.primed",
"dig.glass",
"game.potion.smash",
"random.levelup",
"random.orb",
"random.pop",
"random.splash",
"random.successful_hit",
"random.wood_click",
"records.11",
"records.13",
"records.blocks",
"records.cat",
"records.chirp",
"records.far",
"records.mall",
"records.mellohi",
"records.stal",
"records.strad",
"records.wait",
"records.ward",
"step.cloth",
"step.grass",
"step.gravel",
"step.ladder",
"step.sand",
"step.snow",
"step.stone",
"step.wood",
"tile.piston.in",
"tile.piston.out",
"music.menu",
"music.game",
"music.game.creative",
"music.game.end",
"music.game.end.dragon",
"music.game.end.credits",
"music.game.nether"
]
}
+448
View File
@@ -0,0 +1,448 @@
{
"sounds": [
"ambient.cave",
"block.anvil.break",
"block.anvil.destroy",
"block.anvil.fall",
"block.anvil.hit",
"block.anvil.land",
"block.anvil.place",
"block.anvil.step",
"block.anvil.use",
"block.brewing_stand.brew",
"block.chest.close",
"block.chest.locked",
"block.chest.open",
"block.chorus_flower.death",
"block.chorus_flower.grow",
"block.cloth.break",
"block.cloth.fall",
"block.cloth.hit",
"block.cloth.place",
"block.cloth.step",
"block.comparator.click",
"block.dispenser.dispense",
"block.dispenser.fail",
"block.dispenser.launch",
"block.end_gateway.spawn",
"block.enderchest.close",
"block.enderchest.open",
"block.fence_gate.close",
"block.fence_gate.open",
"block.fire.ambient",
"block.fire.extinguish",
"block.furnace.fire_crackle",
"block.glass.break",
"block.glass.fall",
"block.glass.hit",
"block.glass.place",
"block.glass.step",
"block.grass.break",
"block.grass.fall",
"block.grass.hit",
"block.grass.place",
"block.grass.step",
"block.gravel.break",
"block.gravel.fall",
"block.gravel.hit",
"block.gravel.place",
"block.gravel.step",
"block.iron_door.close",
"block.iron_door.open",
"block.iron_trapdoor.close",
"block.iron_trapdoor.open",
"block.ladder.break",
"block.ladder.fall",
"block.ladder.hit",
"block.ladder.place",
"block.ladder.step",
"block.lava.ambient",
"block.lava.extinguish",
"block.lava.pop",
"block.lever.click",
"block.metal.break",
"block.metal.fall",
"block.metal.hit",
"block.metal.place",
"block.metal.step",
"block.metal_pressureplate.click_off",
"block.metal_pressureplate.click_on",
"block.note.basedrum",
"block.note.bass",
"block.note.harp",
"block.note.hat",
"block.note.pling",
"block.note.snare",
"block.piston.contract",
"block.piston.extend",
"block.portal.ambient",
"block.portal.travel",
"block.portal.trigger",
"block.redstone_torch.burnout",
"block.sand.break",
"block.sand.fall",
"block.sand.hit",
"block.sand.place",
"block.sand.step",
"block.slime.break",
"block.slime.fall",
"block.slime.hit",
"block.slime.place",
"block.slime.step",
"block.snow.break",
"block.snow.fall",
"block.snow.hit",
"block.snow.place",
"block.snow.step",
"block.stone.break",
"block.stone.fall",
"block.stone.hit",
"block.stone.place",
"block.stone.step",
"block.stone_button.click_off",
"block.stone_button.click_on",
"block.stone_pressureplate.click_off",
"block.stone_pressureplate.click_on",
"block.tripwire.attach",
"block.tripwire.click_off",
"block.tripwire.click_on",
"block.tripwire.detach",
"block.water.ambient",
"block.waterlily.place",
"block.wood.break",
"block.wood.fall",
"block.wood.hit",
"block.wood.place",
"block.wood.step",
"block.wood_button.click_off",
"block.wood_button.click_on",
"block.wood_pressureplate.click_off",
"block.wood_pressureplate.click_on",
"block.wooden_door.close",
"block.wooden_door.open",
"block.wooden_trapdoor.close",
"block.wooden_trapdoor.open",
"enchant.thorns.hit",
"entity.armorstand.break",
"entity.armorstand.fall",
"entity.armorstand.hit",
"entity.armorstand.place",
"entity.arrow.hit",
"entity.arrow.hit_player",
"entity.arrow.shoot",
"entity.bat.ambient",
"entity.bat.death",
"entity.bat.hurt",
"entity.bat.loop",
"entity.bat.takeoff",
"entity.blaze.ambient",
"entity.blaze.burn",
"entity.blaze.death",
"entity.blaze.hurt",
"entity.blaze.shoot",
"entity.bobber.splash",
"entity.bobber.throw",
"entity.cat.ambient",
"entity.cat.death",
"entity.cat.hiss",
"entity.cat.hurt",
"entity.cat.purr",
"entity.cat.purreow",
"entity.chicken.ambient",
"entity.chicken.death",
"entity.chicken.egg",
"entity.chicken.hurt",
"entity.chicken.step",
"entity.cow.ambient",
"entity.cow.death",
"entity.cow.hurt",
"entity.cow.milk",
"entity.cow.step",
"entity.creeper.death",
"entity.creeper.hurt",
"entity.creeper.primed",
"entity.donkey.ambient",
"entity.donkey.angry",
"entity.donkey.chest",
"entity.donkey.death",
"entity.donkey.hurt",
"entity.egg.throw",
"entity.elder_guardian.ambient",
"entity.elder_guardian.ambient_land",
"entity.elder_guardian.curse",
"entity.elder_guardian.death",
"entity.elder_guardian.death_land",
"entity.elder_guardian.hurt",
"entity.elder_guardian.hurt_land",
"entity.enderdragon.ambient",
"entity.enderdragon.death",
"entity.enderdragon.flap",
"entity.enderdragon.growl",
"entity.enderdragon.hurt",
"entity.enderdragon.shoot",
"entity.enderdragon_fireball.explode",
"entity.endereye.launch",
"entity.endermen.ambient",
"entity.endermen.death",
"entity.endermen.hurt",
"entity.endermen.scream",
"entity.endermen.stare",
"entity.endermen.teleport",
"entity.endermite.ambient",
"entity.endermite.death",
"entity.endermite.hurt",
"entity.endermite.step",
"entity.enderpearl.throw",
"entity.experience_bottle.throw",
"entity.experience_orb.pickup",
"entity.experience_orb.touch",
"entity.firework.blast",
"entity.firework.blast_far",
"entity.firework.large_blast",
"entity.firework.large_blast_far",
"entity.firework.launch",
"entity.firework.shoot",
"entity.firework.twinkle",
"entity.firework.twinkle_far",
"entity.generic.big_fall",
"entity.generic.burn",
"entity.generic.death",
"entity.generic.drink",
"entity.generic.eat",
"entity.generic.explode",
"entity.generic.extinguish_fire",
"entity.generic.hurt",
"entity.generic.small_fall",
"entity.generic.splash",
"entity.generic.swim",
"entity.ghast.ambient",
"entity.ghast.death",
"entity.ghast.hurt",
"entity.ghast.scream",
"entity.ghast.shoot",
"entity.ghast.warn",
"entity.guardian.ambient",
"entity.guardian.ambient_land",
"entity.guardian.attack",
"entity.guardian.death",
"entity.guardian.death_land",
"entity.guardian.flop",
"entity.guardian.hurt",
"entity.guardian.hurt_land",
"entity.horse.ambient",
"entity.horse.angry",
"entity.horse.armor",
"entity.horse.breathe",
"entity.horse.death",
"entity.horse.eat",
"entity.horse.gallop",
"entity.horse.hurt",
"entity.horse.jump",
"entity.horse.land",
"entity.horse.saddle",
"entity.horse.step",
"entity.horse.step_wood",
"entity.hostile.big_fall",
"entity.hostile.death",
"entity.hostile.hurt",
"entity.hostile.small_fall",
"entity.hostile.splash",
"entity.hostile.swim",
"entity.irongolem.attack",
"entity.irongolem.death",
"entity.irongolem.hurt",
"entity.irongolem.step",
"entity.item.break",
"entity.item.pickup",
"entity.itemframe.add_item",
"entity.itemframe.break",
"entity.itemframe.place",
"entity.itemframe.remove_item",
"entity.itemframe.rotate_item",
"entity.leashknot.break",
"entity.leashknot.place",
"entity.lightning.impact",
"entity.lightning.thunder",
"entity.lingeringpotion.throw",
"entity.magmacube.death",
"entity.magmacube.hurt",
"entity.magmacube.jump",
"entity.magmacube.squish",
"entity.minecart.inside",
"entity.minecart.riding",
"entity.mooshroom.shear",
"entity.mule.ambient",
"entity.mule.death",
"entity.mule.hurt",
"entity.painting.break",
"entity.painting.place",
"entity.pig.ambient",
"entity.pig.death",
"entity.pig.hurt",
"entity.pig.saddle",
"entity.pig.step",
"entity.player.attack.crit",
"entity.player.attack.knockback",
"entity.player.attack.nodamage",
"entity.player.attack.strong",
"entity.player.attack.sweep",
"entity.player.attack.weak",
"entity.player.big_fall",
"entity.player.breath",
"entity.player.burp",
"entity.player.death",
"entity.player.hurt",
"entity.player.levelup",
"entity.player.small_fall",
"entity.player.splash",
"entity.player.swim",
"entity.rabbit.ambient",
"entity.rabbit.attack",
"entity.rabbit.death",
"entity.rabbit.hurt",
"entity.rabbit.jump",
"entity.sheep.ambient",
"entity.sheep.death",
"entity.sheep.hurt",
"entity.sheep.shear",
"entity.sheep.step",
"entity.shulker.ambient",
"entity.shulker.close",
"entity.shulker.death",
"entity.shulker.hurt",
"entity.shulker.hurt_closed",
"entity.shulker.open",
"entity.shulker.shoot",
"entity.shulker.teleport",
"entity.shulker_bullet.hit",
"entity.shulker_bullet.hurt",
"entity.silverfish.ambient",
"entity.silverfish.death",
"entity.silverfish.hurt",
"entity.silverfish.step",
"entity.skeleton.ambient",
"entity.skeleton.death",
"entity.skeleton.hurt",
"entity.skeleton.shoot",
"entity.skeleton.step",
"entity.skeleton_horse.ambient",
"entity.skeleton_horse.death",
"entity.skeleton_horse.hurt",
"entity.slime.attack",
"entity.slime.death",
"entity.slime.hurt",
"entity.slime.jump",
"entity.slime.squish",
"entity.small_magmacube.death",
"entity.small_magmacube.hurt",
"entity.small_magmacube.squish",
"entity.small_slime.death",
"entity.small_slime.hurt",
"entity.small_slime.jump",
"entity.small_slime.squish",
"entity.snowball.throw",
"entity.snowman.ambient",
"entity.snowman.death",
"entity.snowman.hurt",
"entity.snowman.shoot",
"entity.spider.ambient",
"entity.spider.death",
"entity.spider.hurt",
"entity.spider.step",
"entity.splash_potion.break",
"entity.splash_potion.throw",
"entity.squid.ambient",
"entity.squid.death",
"entity.squid.hurt",
"entity.tnt.primed",
"entity.villager.ambient",
"entity.villager.death",
"entity.villager.hurt",
"entity.villager.no",
"entity.villager.trading",
"entity.villager.yes",
"entity.witch.ambient",
"entity.witch.death",
"entity.witch.drink",
"entity.witch.hurt",
"entity.witch.throw",
"entity.wither.ambient",
"entity.wither.break_block",
"entity.wither.death",
"entity.wither.hurt",
"entity.wither.shoot",
"entity.wither.spawn",
"entity.wolf.ambient",
"entity.wolf.death",
"entity.wolf.growl",
"entity.wolf.howl",
"entity.wolf.hurt",
"entity.wolf.pant",
"entity.wolf.shake",
"entity.wolf.step",
"entity.wolf.whine",
"entity.zombie.ambient",
"entity.zombie.attack_door_wood",
"entity.zombie.attack_iron_door",
"entity.zombie.break_door_wood",
"entity.zombie.death",
"entity.zombie.hurt",
"entity.zombie.infect",
"entity.zombie.step",
"entity.zombie_horse.ambient",
"entity.zombie_horse.death",
"entity.zombie_horse.hurt",
"entity.zombie_pig.ambient",
"entity.zombie_pig.angry",
"entity.zombie_pig.death",
"entity.zombie_pig.hurt",
"entity.zombie_villager.ambient",
"entity.zombie_villager.converted",
"entity.zombie_villager.cure",
"entity.zombie_villager.death",
"entity.zombie_villager.hurt",
"entity.zombie_villager.step",
"item.armor.equip_chain",
"item.armor.equip_diamond",
"item.armor.equip_generic",
"item.armor.equip_gold",
"item.armor.equip_iron",
"item.armor.equip_leather",
"item.bottle.fill",
"item.bottle.fill_dragonbreath",
"item.bucket.empty",
"item.bucket.empty_lava",
"item.bucket.fill",
"item.bucket.fill_lava",
"item.chorus_fruit.teleport",
"item.elytra.flying",
"item.firecharge.use",
"item.flintandsteel.use",
"item.hoe.till",
"item.shield.block",
"item.shield.break",
"item.shovel.flatten",
"music.creative",
"music.credits",
"music.dragon",
"music.end",
"music.game",
"music.menu",
"music.nether",
"record.11",
"record.13",
"record.blocks",
"record.cat",
"record.chirp",
"record.far",
"record.mall",
"record.mellohi",
"record.stal",
"record.strad",
"record.wait",
"record.ward",
"ui.button.click",
"weather.rain",
"weather.rain.above"
]
}
+48
View File
@@ -0,0 +1,48 @@
name: ${name}
version: ${version}
main: tf.tuff.TuffX
author: Potato
authors: [Potato, SyntaxSavy, llucasandersen, coleis1op, UplandJacob, MrNorshare]
api-version: 1.18
depend: [ViaVersion, ViaBackwards]
commands:
viablocks:
description: Main command for ViaBlocks.
usage: /viablocks <get|refresh>
permission: tuffx.viablocks.command
tuffx:
description: Main command for TuffX.
usage: /tuffx <reload>
restrictions:
description: Allow or disallow TuffClient modules.
usage: /restrictions <allow|disallow> <module>
tuffxclearcache:
description: Clears TuffXPlus Y0 cache
usage: /tuffxclearcache
permission: tuffx.admin
permissions:
tuffx.reload:
description: Allows use of the /tuffx reload subcommand.
default: op
tuffx.viablocks.command:
description: Allows use of the /viablocks command.
default: op
tuffx.viablocks.command.get:
description: Allows use of the /viablocks get subcommand.
default: op
tuffx.viablocks.command.refresh:
description: Allows use of the /viablocks refresh subcommand.
default: op
tuffx.restrictions.command:
description: Allows use of the /restrictions command.
default: op
tuffx.restrictions.command.allow:
description: Allows use of the /restrictions allow subcommand.
default: op
tuffx.restrictions.command.disallow:
description: Allows use of the /restrictions disallow subcommand.
default: op
tuffx.admin:
default: op
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
package tf.tuff;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class TuffXTest {
private static ServerMock server;
private static TuffX plugin;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
plugin = MockBukkit.load(TuffX.class);
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
@Test
void pluginEnablesSuccessfully() {
assertTrue(plugin.isEnabled(), "Plugin should be enabled after load");
}
@Test
void reloadDoesNotThrow() {
assertDoesNotThrow(() -> plugin.reloadTuffX(),
"reloadTuffX() should not throw");
}
@Test
void findsMappingFile() {
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("26.1.1").version().equals("1.21.11"), "Mapping file for 26.1.1 should be 1.21.11"); // check jump from 26.x to 1.21.x
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("1.21.11").version().equals("1.21.11"), "Mapping file for 1.21.11 should be 1.21.11"); // check direct match
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("1.21.10").version().equals("1.21.9"), "Mapping file for 1.21.10 should be 1.21.9"); // check fallback to one patch version lower
assertTrue(plugin.y0Plugin.viaIds.findMappingFile("1.21.1").version().equals("1.21"), "Mapping file for 1.21.1 should be 1.21"); // check one that won't have a patch version
}
}
@@ -0,0 +1,48 @@
package tf.tuff.netty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ChunkHandlerTest {
@Test
void decodesSingleBlockChangePositionWithNegativeCoordinates() {
long packed = packBlockPosition(-21, -64, 37);
ChunkHandler.BlockChangePosition position = ChunkHandler.decodeSingleBlockChangePosition(packed);
assertEquals(-21, position.x());
assertEquals(-64, position.y());
assertEquals(37, position.z());
}
@Test
void decodesSectionBlockChangePositionBelowYZero() {
long sectionPosition = packSectionPosition(12, -5, -9);
long entry = packSectionEntry(3, 11, 7, 8123);
ChunkHandler.BlockChangePosition position = ChunkHandler.decodeMultiBlockChangePosition(sectionPosition, entry);
assertEquals((12 << 4) + 3, position.x());
assertEquals((-5 << 4) + 7, position.y());
assertEquals((-9 << 4) + 11, position.z());
}
private static long packBlockPosition(int x, int y, int z) {
return ((long) x & 0x3FFFFFFL) << 38
| ((long) z & 0x3FFFFFFL) << 12
| ((long) y & 0xFFFL);
}
private static long packSectionPosition(int x, int y, int z) {
return ((long) x & 0x3FFFFFL) << 42
| ((long) z & 0x3FFFFFL) << 20
| ((long) y & 0xFFFFFL);
}
private static long packSectionEntry(int localX, int localZ, int localY, int blockStateId) {
int localPosition = ((localX & 0xF) << 8) | ((localZ & 0xF) << 4) | (localY & 0xF);
return ((long) blockStateId << 12) | (localPosition & 0xFFFL);
}
}