Initial commit: recovered Forge 1.16.5 project, improved GUI, added WAV/MP3 support
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
.gradle/
|
||||
build/
|
||||
run/
|
||||
out/
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iws
|
||||
*.iml
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
*.log
|
||||
bin/
|
||||
@@ -0,0 +1,72 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net' }
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
|
||||
}
|
||||
}
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
version = '1.0.4'
|
||||
group = 'com.example.soundmod'
|
||||
archivesBaseName = 'soundmod'
|
||||
|
||||
java.toolchain.languageVersion = JavaLanguageVersion.of(8)
|
||||
|
||||
minecraft {
|
||||
mappings channel: 'official', version: '1.16.5'
|
||||
|
||||
runs {
|
||||
client {
|
||||
workingDirectory project.file('run')
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
mods {
|
||||
soundmod {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
server {
|
||||
workingDirectory project.file('run')
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
mods {
|
||||
soundmod {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
shade
|
||||
implementation.extendsFrom shade
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft 'net.minecraftforge:forge:1.16.5-36.2.39'
|
||||
shade 'javazoom:jlayer:1.0.1'
|
||||
}
|
||||
|
||||
jar {
|
||||
from {
|
||||
configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
manifest {
|
||||
attributes([
|
||||
"Specification-Title": "soundmod",
|
||||
"Specification-Vendor": "Codex",
|
||||
"Specification-Version": "1",
|
||||
"Implementation-Title": project.name,
|
||||
"Implementation-Version": "${version}",
|
||||
"Implementation-Vendor": "Codex",
|
||||
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.daemon=false
|
||||
Vendored
BIN
Binary file not shown.
+9
@@ -0,0 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -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
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# gradlew 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 gradlew
|
||||
#
|
||||
# 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/<unknown>/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
@@ -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 gradlew 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 gradlew
|
||||
@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%
|
||||
Binary file not shown.
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.mojang.brigadier.CommandDispatcher
|
||||
* net.minecraft.command.CommandSource
|
||||
* net.minecraftforge.api.distmarker.Dist
|
||||
* net.minecraftforge.common.MinecraftForge
|
||||
* net.minecraftforge.event.RegisterCommandsEvent
|
||||
* net.minecraftforge.eventbus.api.IEventBus
|
||||
* net.minecraftforge.fml.DistExecutor
|
||||
* net.minecraftforge.fml.ModLoadingContext
|
||||
* net.minecraftforge.fml.common.Mod
|
||||
* net.minecraftforge.fml.config.ModConfig$Type
|
||||
* net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
|
||||
* net.minecraftforge.fml.event.server.FMLServerStartedEvent
|
||||
* net.minecraftforge.fml.event.server.FMLServerStoppingEvent
|
||||
* net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext
|
||||
* org.apache.logging.log4j.LogManager
|
||||
* org.apache.logging.log4j.Logger
|
||||
*/
|
||||
package com.example.soundmod;
|
||||
|
||||
import com.example.soundmod.client.ClientHooks;
|
||||
import com.example.soundmod.command.ModCommands;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.registry.ModBlocks;
|
||||
import com.example.soundmod.registry.ModContainers;
|
||||
import com.example.soundmod.registry.ModItems;
|
||||
import com.example.soundmod.registry.ModTileEntities;
|
||||
import com.example.soundmod.sounddata.server.ServerSoundRepository;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.command.CommandSource;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStoppingEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mod(value="soundmod")
|
||||
public class SoundMod {
|
||||
public static final String MODID = "soundmod";
|
||||
public static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
public SoundMod() {
|
||||
IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
ModBlocks.BLOCKS.register(modBus);
|
||||
ModItems.ITEMS.register(modBus);
|
||||
ModTileEntities.TILES.register(modBus);
|
||||
ModContainers.CONTAINERS.register(modBus);
|
||||
modBus.addListener(this::commonSetup);
|
||||
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.COMMON, ModConfig.SPEC);
|
||||
MinecraftForge.EVENT_BUS.addListener(this::onServerStarted);
|
||||
MinecraftForge.EVENT_BUS.addListener(this::onServerStopping);
|
||||
MinecraftForge.EVENT_BUS.addListener(this::onRegisterCommands);
|
||||
DistExecutor.safeRunWhenOn((Dist)Dist.CLIENT, () -> ClientHooks::register);
|
||||
}
|
||||
|
||||
private void commonSetup(FMLCommonSetupEvent event) {
|
||||
event.enqueueWork(NetworkHandler::register);
|
||||
}
|
||||
|
||||
private void onServerStarted(FMLServerStartedEvent event) {
|
||||
ServerSoundRepository.get().bindServer(event.getServer());
|
||||
}
|
||||
|
||||
private void onServerStopping(FMLServerStoppingEvent event) {
|
||||
ServerSoundRepository.get().unbindServer();
|
||||
}
|
||||
|
||||
private void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
ModCommands.register((CommandDispatcher<CommandSource>)event.getDispatcher());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.client.gui.ScreenManager
|
||||
* net.minecraft.inventory.container.ContainerType
|
||||
* net.minecraftforge.api.distmarker.Dist
|
||||
* net.minecraftforge.client.event.RenderGameOverlayEvent$Text
|
||||
* net.minecraftforge.client.event.RenderWorldLastEvent
|
||||
* net.minecraftforge.event.TickEvent$ClientTickEvent
|
||||
* net.minecraftforge.event.TickEvent$Phase
|
||||
* net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
* net.minecraftforge.fml.common.Mod$EventBusSubscriber
|
||||
* net.minecraftforge.fml.common.Mod$EventBusSubscriber$Bus
|
||||
* net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent
|
||||
*/
|
||||
package com.example.soundmod.client;
|
||||
|
||||
import com.example.soundmod.client.audio.AudioPlaybackService;
|
||||
import com.example.soundmod.client.debug.DebugOverlayRenderer;
|
||||
import com.example.soundmod.client.screen.SoundPlayerScreen;
|
||||
import com.example.soundmod.registry.ModContainers;
|
||||
import net.minecraft.client.gui.ScreenManager;
|
||||
import net.minecraft.inventory.container.ContainerType;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.client.event.RenderWorldLastEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
|
||||
@Mod.EventBusSubscriber(modid="soundmod", value={Dist.CLIENT}, bus=Mod.EventBusSubscriber.Bus.MOD)
|
||||
public final class ClientHooks {
|
||||
private ClientHooks() {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||
ScreenManager.register((ContainerType)((ContainerType)ModContainers.SOUND_PLAYER.get()), SoundPlayerScreen::new);
|
||||
}
|
||||
|
||||
@Mod.EventBusSubscriber(modid="soundmod", value={Dist.CLIENT})
|
||||
public static class ClientForgeEvents {
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.END) {
|
||||
AudioPlaybackService.get().tick();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderWorld(RenderWorldLastEvent event) {
|
||||
DebugOverlayRenderer.render(event.getMatrixStack(), event.getPartialTicks());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderHud(RenderGameOverlayEvent.Text event) {
|
||||
DebugOverlayRenderer.renderHud(event.getMatrixStack());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.client.Minecraft
|
||||
* net.minecraft.client.entity.player.ClientPlayerEntity
|
||||
* net.minecraft.entity.Entity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraft.util.math.BlockRayTraceResult
|
||||
* net.minecraft.util.math.RayTraceContext
|
||||
* net.minecraft.util.math.RayTraceContext$BlockMode
|
||||
* net.minecraft.util.math.RayTraceContext$FluidMode
|
||||
* net.minecraft.util.math.RayTraceResult$Type
|
||||
* net.minecraft.util.math.vector.Vector3d
|
||||
* org.lwjgl.BufferUtils
|
||||
* org.lwjgl.openal.AL10
|
||||
* org.lwjgl.stb.STBVorbis
|
||||
* org.lwjgl.stb.STBVorbisInfo
|
||||
* org.lwjgl.system.MemoryStack
|
||||
* org.lwjgl.system.MemoryUtil
|
||||
*/
|
||||
package com.example.soundmod.client.audio;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.common.AttenuationMode;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.C2SRequestSoundPacket;
|
||||
import com.example.soundmod.sounddata.client.ClientSoundCache;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import javazoom.jl.decoder.Bitstream;
|
||||
import javazoom.jl.decoder.Decoder;
|
||||
import javazoom.jl.decoder.Header;
|
||||
import javazoom.jl.decoder.JavaLayerException;
|
||||
import javazoom.jl.decoder.SampleBuffer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.player.ClientPlayerEntity;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.BlockRayTraceResult;
|
||||
import net.minecraft.util.math.RayTraceContext;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.stb.STBVorbis;
|
||||
import org.lwjgl.stb.STBVorbisInfo;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
public final class AudioPlaybackService {
|
||||
private static final int REPLAY_DEBOUNCE_TICKS = 6;
|
||||
private static final AudioPlaybackService INSTANCE = new AudioPlaybackService();
|
||||
private final Map<String, PlayingSource> active = new HashMap<String, PlayingSource>();
|
||||
|
||||
public static AudioPlaybackService get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private AudioPlaybackService() {
|
||||
}
|
||||
|
||||
public synchronized void play(String sourceId, BlockPos pos, SoundSettings settings) {
|
||||
long gameTime;
|
||||
SoundSettings safe = SoundSettings.sanitizedCopy(settings);
|
||||
if (safe.volume != settings.volume || safe.pitch != settings.pitch || safe.radius != settings.radius) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "sanitized params vol={}=>{} pitch={}=>{} radius={}=>{}", Float.valueOf(settings.volume), Float.valueOf(safe.volume), Float.valueOf(settings.pitch), Float.valueOf(safe.pitch), Float.valueOf(settings.radius), Float.valueOf(safe.radius));
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L1, "PLAYBACK", sourceId, "play request soundId={} pos={} {} {} vol={} pitch={} loop={} radius={}", safe.soundId, pos.getX(), pos.getY(), pos.getZ(), Float.valueOf(safe.volume), Float.valueOf(safe.pitch), safe.loop, Float.valueOf(safe.radius));
|
||||
Path file = ClientSoundCache.get().resolveBySoundId(safe.soundId);
|
||||
if (file == null) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "cache miss soundId={} -> request from server", safe.soundId);
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SRequestSoundPacket(safe.soundId));
|
||||
return;
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", sourceId, "resolved cache file={}", file);
|
||||
PlayingSource current = this.active.get(sourceId);
|
||||
long l = gameTime = Minecraft.getInstance().level != null ? Minecraft.getInstance().level.getGameTime() : Long.MIN_VALUE;
|
||||
if (!safe.crossfade && current != null && current.settings.soundId.equals(safe.soundId) && gameTime != Long.MIN_VALUE && gameTime - current.startedAtTick <= 6L) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "PLAYBACK", sourceId, "replay suppressed soundId={} dt={}", safe.soundId, gameTime - current.startedAtTick);
|
||||
return;
|
||||
}
|
||||
if (safe.crossfade && current != null) {
|
||||
current.fadeOutTicks = Math.max(1, safe.fadeOutTicks);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", sourceId, "crossfade enabled fadeOutTicks={}", safe.fadeOutTicks);
|
||||
} else {
|
||||
this.stop(sourceId);
|
||||
}
|
||||
try {
|
||||
this.enforceActiveLimit(safe.priority);
|
||||
DecodedPcm pcm = this.decodeAudio(file);
|
||||
int buffer = AL10.alGenBuffers();
|
||||
int alErr1 = AL10.alGetError();
|
||||
if (alErr1 != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "alGenBuffers error={}", alErr1);
|
||||
}
|
||||
int format = pcm.channels == 1 ? 4353 : 4355;
|
||||
AL10.alBufferData((int)buffer, (int)format, (ShortBuffer)pcm.samples, (int)pcm.sampleRate);
|
||||
int alErr2 = AL10.alGetError();
|
||||
if (alErr2 != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "alBufferData error={} format={} channels={} sampleRate={} samples={}", alErr2, format, pcm.channels, pcm.sampleRate, pcm.samples.remaining());
|
||||
}
|
||||
int source = AL10.alGenSources();
|
||||
int alErr3 = AL10.alGetError();
|
||||
if (alErr3 != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "alGenSources error={}", alErr3);
|
||||
}
|
||||
AL10.alSourcei((int)source, (int)4105, (int)buffer);
|
||||
AL10.alSourcef((int)source, (int)4106, (float)safe.volume);
|
||||
AL10.alSourcef((int)source, (int)4099, (float)safe.pitch);
|
||||
AL10.alSourcei((int)source, (int)4103, (int)(safe.loop ? 1 : 0));
|
||||
this.configureSpatial(source, pos, safe.use3d);
|
||||
AL10.alSourcePlay((int)source);
|
||||
int alErr4 = AL10.alGetError();
|
||||
if (alErr4 != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "alSourcePlay error={}", alErr4);
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L1, "PLAYBACK", sourceId, "play started source={} buffer={} channels={} sampleRate={} frames={}", source, buffer, pcm.channels, pcm.sampleRate, pcm.frames);
|
||||
PlayingSource src = new PlayingSource(sourceId, source, buffer, pos, safe, gameTime);
|
||||
src.fadeInTicks = safe.fadeInTicks;
|
||||
src.targetGain = safe.volume;
|
||||
if (safe.reverb && safe.reverbStrength > 0.01f) {
|
||||
src.reverbSource = this.createReverbSource(sourceId, pos, safe, buffer);
|
||||
}
|
||||
if (src.fadeInTicks > 0) {
|
||||
AL10.alSourcef((int)source, (int)4106, (float)0.0f);
|
||||
}
|
||||
this.active.put(sourceId, src);
|
||||
}
|
||||
catch (Exception e) {
|
||||
SoundMod.LOGGER.error("Failed to play custom sound {}", (Object)safe.soundId, (Object)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "play failed soundId={} reason={}", safe.soundId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updateLive(String sourceId, BlockPos pos, SoundSettings settings) {
|
||||
SoundSettings safe = SoundSettings.sanitizedCopy(settings);
|
||||
PlayingSource current = this.active.get(sourceId);
|
||||
if (current == null) {
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", "LIVE", "source missing -> fallback play sourceId={} soundId={}", sourceId, safe.soundId);
|
||||
this.play(sourceId, pos, safe);
|
||||
return;
|
||||
}
|
||||
if (!current.settings.soundId.equals(safe.soundId)) {
|
||||
SoundDebug.log(SoundDebug.Level.L1, "PLAYBACK", "LIVE", "restart reason=soundId_change sourceId={} old={} new={} crossfade={}", sourceId, current.settings.soundId, safe.soundId, safe.crossfade);
|
||||
if (safe.crossfade) {
|
||||
current.fadeOutTicks = Math.max(1, safe.fadeOutTicks);
|
||||
} else {
|
||||
this.stop(sourceId);
|
||||
}
|
||||
this.play(sourceId, pos, safe);
|
||||
return;
|
||||
}
|
||||
current.settings = safe;
|
||||
current.pos = pos;
|
||||
current.targetGain = safe.volume;
|
||||
current.fadeInTicks = Math.max(0, current.fadeInTicks);
|
||||
this.configureSpatial(current.source, pos, safe.use3d);
|
||||
AL10.alSourcei((int)current.source, (int)4103, (int)(safe.loop ? 1 : 0));
|
||||
if (safe.reverb && safe.reverbStrength > 0.01f) {
|
||||
if (current.reverbSource <= 0) {
|
||||
current.reverbSource = this.createReverbSource(sourceId, pos, safe, current.buffer);
|
||||
} else {
|
||||
this.configureSpatial(current.reverbSource, pos, safe.use3d);
|
||||
}
|
||||
} else if (current.reverbSource > 0) {
|
||||
AL10.alSourceStop((int)current.reverbSource);
|
||||
AL10.alDeleteSources((int)current.reverbSource);
|
||||
current.reverbSource = 0;
|
||||
}
|
||||
current.lastLiveUpdateTick = this.currentGameTime();
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", "LIVE", "updated sourceId={} soundId={} vol={} pitch={} radius={} attn={} use3d={} doppler={} occlusion={} reverb={}", sourceId, safe.soundId, Float.valueOf(safe.volume), Float.valueOf(safe.pitch), Float.valueOf(safe.radius), safe.attenuationMode.name(), safe.use3d, safe.doppler, safe.occlusion, safe.reverb);
|
||||
}
|
||||
|
||||
public synchronized void stop(String sourceId) {
|
||||
PlayingSource src = this.active.remove(sourceId);
|
||||
if (src == null) {
|
||||
return;
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", sourceId, "stop source={} buffer={}", src.source, src.buffer);
|
||||
AL10.alSourceStop((int)src.source);
|
||||
AL10.alDeleteSources((int)src.source);
|
||||
if (src.reverbSource > 0) {
|
||||
AL10.alSourceStop((int)src.reverbSource);
|
||||
AL10.alDeleteSources((int)src.reverbSource);
|
||||
}
|
||||
AL10.alDeleteBuffers((int)src.buffer);
|
||||
}
|
||||
|
||||
public synchronized boolean isPlaying(String sourceId) {
|
||||
return this.active.containsKey(sourceId);
|
||||
}
|
||||
|
||||
public synchronized void tick() {
|
||||
if (this.active.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
ClientPlayerEntity player = mc.player;
|
||||
if (player == null || mc.level == null) {
|
||||
return;
|
||||
}
|
||||
Vector3d playerPos = player.position();
|
||||
ArrayList<String> remove = new ArrayList<String>();
|
||||
int topPriority = Integer.MIN_VALUE;
|
||||
String dominantCategory = "ambient";
|
||||
for (PlayingSource src : this.active.values()) {
|
||||
if (src.settings.priority > topPriority) {
|
||||
topPriority = src.settings.priority;
|
||||
dominantCategory = src.settings.category;
|
||||
continue;
|
||||
}
|
||||
if (src.settings.priority != topPriority || this.categoryRank(src.settings.category) <= this.categoryRank(dominantCategory)) continue;
|
||||
dominantCategory = src.settings.category;
|
||||
}
|
||||
for (PlayingSource src : this.active.values()) {
|
||||
int state = AL10.alGetSourcei((int)src.source, (int)4112);
|
||||
if (SoundDebug.enabled(SoundDebug.Level.L3)) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "PLAYBACK", src.sourceId, "tick state={} gainTarget={}", state, Float.valueOf(src.targetGain));
|
||||
}
|
||||
if (state == 4116 && !src.settings.loop) {
|
||||
remove.add(src.sourceId);
|
||||
continue;
|
||||
}
|
||||
float gain = this.computeAttenuatedGain(src, playerPos);
|
||||
float occlusionFactor = this.computeOcclusionFactor(src, playerPos);
|
||||
gain *= occlusionFactor;
|
||||
gain = this.applyFades(src, gain);
|
||||
gain = this.applyPriorityDucking(src, gain, topPriority, dominantCategory);
|
||||
AL10.alSourcef((int)src.source, (int)4106, (float)Math.max(0.0f, gain));
|
||||
float pitch = src.settings.pitch;
|
||||
if (src.settings.occlusion && src.settings.use3d && occlusionFactor < 0.999f && !"voice".equals(src.settings.category)) {
|
||||
pitch *= Math.max(0.9f, 1.0f - src.settings.occlusionStrength * 0.12f);
|
||||
}
|
||||
if (src.settings.doppler && src.settings.use3d) {
|
||||
Vector3d velocity = player.getDeltaMovement();
|
||||
Vector3d sourceDir = new Vector3d((double)src.pos.getX() + 0.5 - playerPos.x, (double)src.pos.getY() + 0.5 - playerPos.y, (double)src.pos.getZ() + 0.5 - playerPos.z);
|
||||
double len = Math.max(1.0E-4, sourceDir.length());
|
||||
double radial = (velocity.x * sourceDir.x + velocity.y * sourceDir.y + velocity.z * sourceDir.z) / len;
|
||||
float dopplerPitch = (float)Math.max(0.75, Math.min(1.25, 1.0 + radial * 0.04));
|
||||
pitch *= dopplerPitch;
|
||||
}
|
||||
AL10.alSourcef((int)src.source, (int)4099, (float)pitch);
|
||||
if (src.reverbSource <= 0) continue;
|
||||
float wetGain = Math.max(0.0f, gain * src.settings.reverbStrength * 0.45f);
|
||||
AL10.alSourcef((int)src.reverbSource, (int)4106, (float)wetGain);
|
||||
}
|
||||
for (String id : remove) {
|
||||
this.stop(id);
|
||||
}
|
||||
}
|
||||
|
||||
private float applyFades(PlayingSource src, float gain) {
|
||||
if (src.fadeInTicks > 0) {
|
||||
int elapsed = src.settings.fadeInTicks - src.fadeInTicks;
|
||||
float alpha = src.settings.fadeInTicks <= 0 ? 1.0f : (float)elapsed / (float)src.settings.fadeInTicks;
|
||||
--src.fadeInTicks;
|
||||
return gain * Math.min(1.0f, alpha);
|
||||
}
|
||||
if (src.fadeOutTicks > 0) {
|
||||
float alpha = (float)src.fadeOutTicks / (float)Math.max(1, src.settings.fadeOutTicks);
|
||||
--src.fadeOutTicks;
|
||||
if (src.fadeOutTicks <= 0) {
|
||||
this.stop(src.sourceId);
|
||||
return 0.0f;
|
||||
}
|
||||
return gain * alpha;
|
||||
}
|
||||
return gain;
|
||||
}
|
||||
|
||||
private float computeOcclusionFactor(PlayingSource src, Vector3d playerPos) {
|
||||
if (!src.settings.occlusion || !src.settings.use3d) {
|
||||
return 1.0f;
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
BlockRayTraceResult trace = mc.level.clip(new RayTraceContext(playerPos, new Vector3d((double)src.pos.getX() + 0.5, (double)src.pos.getY() + 0.5, (double)src.pos.getZ() + 0.5), RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.NONE, (Entity)mc.player));
|
||||
if (trace.getType() != RayTraceResult.Type.MISS) {
|
||||
float damp = Math.max(0.0f, 1.0f - src.settings.occlusionStrength);
|
||||
return damp;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
private float computeAttenuatedGain(PlayingSource src, Vector3d playerPos) {
|
||||
double atten;
|
||||
if (!src.settings.use3d) {
|
||||
return src.targetGain;
|
||||
}
|
||||
double dx = playerPos.x - ((double)src.pos.getX() + 0.5);
|
||||
double dy = playerPos.y - ((double)src.pos.getY() + 0.5);
|
||||
double dz = playerPos.z - ((double)src.pos.getZ() + 0.5);
|
||||
double distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (src.settings.attenuationMode == AttenuationMode.NONE || src.settings.radius <= 0.1f) {
|
||||
return src.targetGain;
|
||||
}
|
||||
double t = Math.min(1.0, distance / (double)src.settings.radius);
|
||||
switch (src.settings.attenuationMode) {
|
||||
case EXPONENTIAL: {
|
||||
atten = Math.pow(1.0 - t, 2.0);
|
||||
break;
|
||||
}
|
||||
case LOGARITHMIC: {
|
||||
atten = 1.0 - Math.log10(1.0 + 9.0 * t);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
atten = 1.0 - t;
|
||||
}
|
||||
}
|
||||
return (float)Math.max(0.0, (double)src.targetGain * atten);
|
||||
}
|
||||
|
||||
public synchronized Collection<PlayingSourceView> snapshot() {
|
||||
ArrayList<PlayingSourceView> out = new ArrayList<PlayingSourceView>();
|
||||
long nowTick = this.currentGameTime();
|
||||
for (PlayingSource src : this.active.values()) {
|
||||
int alState = AL10.alGetSourcei((int)src.source, (int)4112);
|
||||
ClientSoundCache.Status status = ClientSoundCache.get().status(src.settings.soundId);
|
||||
boolean liveUpdated = nowTick != Long.MIN_VALUE && src.lastLiveUpdateTick != Long.MIN_VALUE && nowTick - src.lastLiveUpdateTick <= 40L;
|
||||
out.add(new PlayingSourceView(src.sourceId, src.settings.soundId, (double)src.pos.getX() + 0.5, (double)src.pos.getY() + 0.5, (double)src.pos.getZ() + 0.5, src.settings.radius, src.settings.priority, status.name(), alState, liveUpdated));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private float applyPriorityDucking(PlayingSource src, float gain, int topPriority, String dominantCategory) {
|
||||
if (topPriority == Integer.MIN_VALUE || src.settings.priority >= topPriority) {
|
||||
return gain;
|
||||
}
|
||||
double strength = (Double)ModConfig.DUCKING_STRENGTH.get();
|
||||
double base = Math.max(0.05, 1.0 - strength);
|
||||
double categoryFactor = this.categoryDuckingFactor(src.settings.category, dominantCategory);
|
||||
return (float)((double)gain * base * categoryFactor);
|
||||
}
|
||||
|
||||
private double categoryDuckingFactor(String sourceCategory, String dominantCategory) {
|
||||
boolean sourceForeground = this.isForegroundCategory(sourceCategory);
|
||||
boolean dominantForeground = this.isForegroundCategory(dominantCategory);
|
||||
if (dominantForeground && !sourceForeground) {
|
||||
return 0.55;
|
||||
}
|
||||
if (!dominantForeground && sourceForeground) {
|
||||
return 0.9;
|
||||
}
|
||||
return 0.78;
|
||||
}
|
||||
|
||||
private boolean isForegroundCategory(String category) {
|
||||
return "voice".equals(category) || "effect".equals(category);
|
||||
}
|
||||
|
||||
private int categoryRank(String category) {
|
||||
if ("voice".equals(category)) {
|
||||
return 4;
|
||||
}
|
||||
if ("effect".equals(category)) {
|
||||
return 3;
|
||||
}
|
||||
if ("ambient".equals(category)) {
|
||||
return 2;
|
||||
}
|
||||
if ("music".equals(category)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void enforceActiveLimit(int incomingPriority) {
|
||||
int maxActive = (Integer)ModConfig.MAX_ACTIVE_SOURCES.get();
|
||||
if (this.active.size() < maxActive) {
|
||||
return;
|
||||
}
|
||||
String victimId = null;
|
||||
PlayingSource victim = null;
|
||||
for (Map.Entry<String, PlayingSource> e : this.active.entrySet()) {
|
||||
PlayingSource src = e.getValue();
|
||||
if (victim != null && src.settings.priority >= victim.settings.priority && (src.settings.priority != victim.settings.priority || src.startedAtTick >= victim.startedAtTick)) continue;
|
||||
victimId = e.getKey();
|
||||
victim = src;
|
||||
}
|
||||
if (victim != null && victimId != null && victim.settings.priority <= incomingPriority) {
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", victimId, "evict by limiter incomingPriority={} victimPriority={}", incomingPriority, victim.settings.priority);
|
||||
this.stop(victimId);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSpatial(int source, BlockPos pos, boolean use3d) {
|
||||
AL10.alSourcef((int)source, (int)4129, (float)0.0f);
|
||||
if (use3d) {
|
||||
AL10.alSourcei((int)source, (int)514, (int)0);
|
||||
AL10.alSource3f((int)source, (int)4100, (float)((float)pos.getX() + 0.5f), (float)((float)pos.getY() + 0.5f), (float)((float)pos.getZ() + 0.5f));
|
||||
} else {
|
||||
AL10.alSourcei((int)source, (int)514, (int)1);
|
||||
AL10.alSource3f((int)source, (int)4100, (float)0.0f, (float)0.0f, (float)0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private int createReverbSource(String sourceId, BlockPos pos, SoundSettings safe, int buffer) {
|
||||
int wet = AL10.alGenSources();
|
||||
int err = AL10.alGetError();
|
||||
if (err != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "reverb source create error={}", err);
|
||||
return 0;
|
||||
}
|
||||
AL10.alSourcei((int)wet, (int)4105, (int)buffer);
|
||||
AL10.alSourcei((int)wet, (int)4103, (int)(safe.loop ? 1 : 0));
|
||||
AL10.alSourcef((int)wet, (int)4106, (float)Math.max(0.0f, safe.volume * safe.reverbStrength * 0.45f));
|
||||
AL10.alSourcef((int)wet, (int)4099, (float)Math.max(0.5f, safe.pitch * 0.97f));
|
||||
this.configureSpatial(wet, pos, safe.use3d);
|
||||
if (safe.use3d) {
|
||||
float ox = (float)((sourceId.hashCode() >> 2 & 7) - 3) * 0.12f;
|
||||
float oy = (float)((sourceId.hashCode() >> 5 & 7) - 3) * 0.06f;
|
||||
float oz = (float)((sourceId.hashCode() >> 8 & 7) - 3) * 0.12f;
|
||||
AL10.alSource3f((int)wet, (int)4100, (float)((float)pos.getX() + 0.5f + ox), (float)((float)pos.getY() + 0.5f + oy), (float)((float)pos.getZ() + 0.5f + oz));
|
||||
}
|
||||
AL10.alSourcePlay((int)wet);
|
||||
int playErr = AL10.alGetError();
|
||||
if (playErr != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", sourceId, "reverb play error={}", playErr);
|
||||
AL10.alDeleteSources((int)wet);
|
||||
return 0;
|
||||
}
|
||||
return wet;
|
||||
}
|
||||
|
||||
private long currentGameTime() {
|
||||
return Minecraft.getInstance().level != null ? Minecraft.getInstance().level.getGameTime() : Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Loose catch block
|
||||
*/
|
||||
private DecodedPcm decodeVorbis(Path file) throws IOException {
|
||||
byte[] data = Files.readAllBytes(file);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "PLAYBACK", "DECODE", "read file={} bytes={}", file, data.length);
|
||||
ByteBuffer ogg = MemoryUtil.memAlloc((int)data.length);
|
||||
ogg.put(data);
|
||||
ogg.flip();
|
||||
try {
|
||||
try (MemoryStack stack = MemoryStack.stackPush();){
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
long decoder = STBVorbis.stb_vorbis_open_memory((ByteBuffer)ogg, (IntBuffer)error, null);
|
||||
if (decoder == 0L) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", "DECODE", "stb_vorbis_open_memory failed file={} code={}", file, error.get(0));
|
||||
throw new IOException("stb_vorbis_open_memory failed: " + error.get(0));
|
||||
}
|
||||
STBVorbisInfo info = STBVorbisInfo.mallocStack((MemoryStack)stack);
|
||||
STBVorbis.stb_vorbis_get_info((long)decoder, (STBVorbisInfo)info);
|
||||
int channels = info.channels();
|
||||
int sampleRate = info.sample_rate();
|
||||
int estimatedFrames = STBVorbis.stb_vorbis_stream_length_in_samples((long)decoder);
|
||||
ShortBuffer pcm = BufferUtils.createShortBuffer((int)Math.max(1, estimatedFrames * channels));
|
||||
int decodedFrames = STBVorbis.stb_vorbis_get_samples_short_interleaved((long)decoder, (int)channels, (ShortBuffer)pcm);
|
||||
STBVorbis.stb_vorbis_close((long)decoder);
|
||||
if (decodedFrames <= 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "PLAYBACK", "DECODE", "decode produced no samples file={} decodedFrames={}", file, decodedFrames);
|
||||
throw new IOException("stb_vorbis_get_samples_short_interleaved returned " + decodedFrames);
|
||||
}
|
||||
int decodedShorts = decodedFrames * channels;
|
||||
pcm.position(0);
|
||||
pcm.limit(decodedShorts);
|
||||
SoundDebug.log(SoundDebug.Level.L1, "PLAYBACK", "DECODE", "decode ok channels={} sampleRate={} frames={} pcmShorts={}", channels, sampleRate, decodedFrames, pcm.remaining());
|
||||
DecodedPcm decodedPcm = new DecodedPcm(pcm, channels, sampleRate, decodedFrames);
|
||||
return decodedPcm;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
MemoryUtil.memFree((Buffer)ogg);
|
||||
}
|
||||
}
|
||||
|
||||
private DecodedPcm decodeAudio(Path file) throws IOException {
|
||||
String name = file.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||
if (name.endsWith(".wav")) {
|
||||
return this.decodeWav(file);
|
||||
} else if (name.endsWith(".mp3")) {
|
||||
return this.decodeMp3(file);
|
||||
} else {
|
||||
return this.decodeVorbis(file);
|
||||
}
|
||||
}
|
||||
|
||||
private DecodedPcm decodeWav(Path file) throws IOException {
|
||||
try {
|
||||
AudioInputStream stream = AudioSystem.getAudioInputStream(file.toFile());
|
||||
AudioFormat baseFormat = stream.getFormat();
|
||||
int channels = baseFormat.getChannels();
|
||||
int sampleRate = (int) baseFormat.getSampleRate();
|
||||
AudioFormat decodedFormat = new AudioFormat(
|
||||
AudioFormat.Encoding.PCM_SIGNED,
|
||||
baseFormat.getSampleRate(),
|
||||
16,
|
||||
baseFormat.getChannels(),
|
||||
baseFormat.getChannels() * 2,
|
||||
baseFormat.getSampleRate(),
|
||||
false
|
||||
);
|
||||
AudioInputStream pcmStream = AudioSystem.getAudioInputStream(decodedFormat, stream);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[4096];
|
||||
int n;
|
||||
while ((n = pcmStream.read(buf)) != -1) {
|
||||
out.write(buf, 0, n);
|
||||
}
|
||||
pcmStream.close();
|
||||
stream.close();
|
||||
|
||||
byte[] pcmBytes = out.toByteArray();
|
||||
ShortBuffer shortBuf = BufferUtils.createShortBuffer(pcmBytes.length / 2);
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(pcmBytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
while (byteBuffer.remaining() >= 2) {
|
||||
shortBuf.put(byteBuffer.getShort());
|
||||
}
|
||||
shortBuf.flip();
|
||||
int frames = shortBuf.remaining() / channels;
|
||||
SoundDebug.log(SoundDebug.Level.L1, "PLAYBACK", "DECODE", "wav decode ok channels={} sampleRate={} frames={} pcmShorts={}", channels, sampleRate, frames, shortBuf.remaining());
|
||||
return new DecodedPcm(shortBuf, channels, sampleRate, frames);
|
||||
} catch (UnsupportedAudioFileException e) {
|
||||
throw new IOException("Unsupported WAV file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private DecodedPcm decodeMp3(Path file) throws IOException {
|
||||
try (InputStream in = Files.newInputStream(file);
|
||||
BufferedInputStream bin = new BufferedInputStream(in)) {
|
||||
Bitstream bitstream = new Bitstream(bin);
|
||||
Decoder decoder = new Decoder();
|
||||
|
||||
Header header = bitstream.readFrame();
|
||||
if (header == null) {
|
||||
throw new IOException("Empty or invalid MP3 file: " + file);
|
||||
}
|
||||
|
||||
int channels = (header.mode() == Header.SINGLE_CHANNEL) ? 1 : 2;
|
||||
int sampleRate = header.frequency();
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
while (header != null) {
|
||||
SampleBuffer output = (SampleBuffer) decoder.decodeFrame(header, bitstream);
|
||||
short[] pcm = output.getBuffer();
|
||||
int len = output.getBufferLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
short s = pcm[i];
|
||||
out.write(s & 0xFF);
|
||||
out.write((s >> 8) & 0xFF);
|
||||
}
|
||||
bitstream.closeFrame();
|
||||
header = bitstream.readFrame();
|
||||
}
|
||||
|
||||
byte[] pcmBytes = out.toByteArray();
|
||||
ShortBuffer shortBuf = BufferUtils.createShortBuffer(pcmBytes.length / 2);
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(pcmBytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
while (byteBuffer.remaining() >= 2) {
|
||||
shortBuf.put(byteBuffer.getShort());
|
||||
}
|
||||
shortBuf.flip();
|
||||
int frames = shortBuf.remaining() / channels;
|
||||
SoundDebug.log(SoundDebug.Level.L1, "PLAYBACK", "DECODE", "mp3 decode ok channels={} sampleRate={} frames={} pcmShorts={}", channels, sampleRate, frames, shortBuf.remaining());
|
||||
return new DecodedPcm(shortBuf, channels, sampleRate, frames);
|
||||
} catch (JavaLayerException e) {
|
||||
throw new IOException("Failed to decode MP3 file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PlayingSource {
|
||||
final String sourceId;
|
||||
final int source;
|
||||
final int buffer;
|
||||
BlockPos pos;
|
||||
SoundSettings settings;
|
||||
final long startedAtTick;
|
||||
float targetGain;
|
||||
int fadeInTicks;
|
||||
int fadeOutTicks;
|
||||
int reverbSource;
|
||||
long lastLiveUpdateTick = Long.MIN_VALUE;
|
||||
|
||||
PlayingSource(String sourceId, int source, int buffer, BlockPos pos, SoundSettings settings, long startedAtTick) {
|
||||
this.sourceId = sourceId;
|
||||
this.source = source;
|
||||
this.buffer = buffer;
|
||||
this.pos = pos;
|
||||
this.settings = settings;
|
||||
this.startedAtTick = startedAtTick;
|
||||
this.targetGain = settings.volume;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DecodedPcm {
|
||||
final ShortBuffer samples;
|
||||
final int channels;
|
||||
final int sampleRate;
|
||||
final int frames;
|
||||
|
||||
DecodedPcm(ShortBuffer samples, int channels, int sampleRate, int frames) {
|
||||
this.samples = samples;
|
||||
this.channels = channels;
|
||||
this.sampleRate = sampleRate;
|
||||
this.frames = frames;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PlayingSourceView {
|
||||
public final String sourceId;
|
||||
public final String soundId;
|
||||
public final double x;
|
||||
public final double y;
|
||||
public final double z;
|
||||
public final float radius;
|
||||
public final int priority;
|
||||
public final String cacheStatus;
|
||||
public final int alState;
|
||||
public final boolean liveUpdated;
|
||||
|
||||
private PlayingSourceView(String sourceId, String soundId, double x, double y, double z, float radius, int priority, String cacheStatus, int alState, boolean liveUpdated) {
|
||||
this.sourceId = sourceId;
|
||||
this.soundId = soundId;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.radius = radius;
|
||||
this.priority = priority;
|
||||
this.cacheStatus = cacheStatus;
|
||||
this.alState = alState;
|
||||
this.liveUpdated = liveUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.mojang.blaze3d.matrix.MatrixStack
|
||||
* com.mojang.blaze3d.vertex.IVertexBuilder
|
||||
* net.minecraft.client.Minecraft
|
||||
* net.minecraft.client.renderer.IRenderTypeBuffer$Impl
|
||||
* net.minecraft.client.renderer.RenderType
|
||||
* net.minecraft.client.renderer.WorldRenderer
|
||||
* net.minecraft.util.math.AxisAlignedBB
|
||||
* net.minecraft.util.math.vector.Vector3d
|
||||
* net.minecraft.util.text.ITextComponent
|
||||
* net.minecraft.util.text.StringTextComponent
|
||||
*/
|
||||
package com.example.soundmod.client.debug;
|
||||
|
||||
import com.example.soundmod.client.audio.AudioPlaybackService;
|
||||
import com.example.soundmod.client.debug.DebugState;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.vertex.IVertexBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.IRenderTypeBuffer;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
|
||||
public final class DebugOverlayRenderer {
|
||||
private DebugOverlayRenderer() {
|
||||
}
|
||||
|
||||
public static void render(MatrixStack stack, float partialTicks) {
|
||||
if (!DebugState.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || mc.level == null) {
|
||||
return;
|
||||
}
|
||||
Vector3d camera = mc.gameRenderer.getMainCamera().getPosition();
|
||||
Collection<AudioPlaybackService.PlayingSourceView> sources = AudioPlaybackService.get().snapshot();
|
||||
IRenderTypeBuffer.Impl bufferSource = mc.renderBuffers().bufferSource();
|
||||
IVertexBuilder lineBuilder = bufferSource.getBuffer(RenderType.lines());
|
||||
for (AudioPlaybackService.PlayingSourceView source : sources) {
|
||||
float[] color = DebugOverlayRenderer.colorFor(source);
|
||||
AxisAlignedBB bb = new AxisAlignedBB(source.x - (double)source.radius - camera.x, source.y - (double)source.radius - camera.y, source.z - (double)source.radius - camera.z, source.x + (double)source.radius - camera.x, source.y + (double)source.radius - camera.y, source.z + (double)source.radius - camera.z);
|
||||
WorldRenderer.renderLineBox((MatrixStack)stack, (IVertexBuilder)lineBuilder, (AxisAlignedBB)bb, (float)color[0], (float)color[1], (float)color[2], (float)0.85f);
|
||||
}
|
||||
bufferSource.endBatch(RenderType.lines());
|
||||
}
|
||||
|
||||
public static void renderHud(MatrixStack stack) {
|
||||
if (!DebugState.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null || mc.level == null || mc.font == null) {
|
||||
return;
|
||||
}
|
||||
Collection<AudioPlaybackService.PlayingSourceView> snapshot = AudioPlaybackService.get().snapshot();
|
||||
ArrayList<AudioPlaybackService.PlayingSourceView> sources = new ArrayList<AudioPlaybackService.PlayingSourceView>(snapshot);
|
||||
sources.sort((a, b) -> Integer.compare(b.priority, a.priority));
|
||||
int x = 8;
|
||||
int y = 8;
|
||||
mc.font.drawShadow(stack, (ITextComponent)new StringTextComponent("SOUNDDBG Active Sources: " + sources.size()), (float)x, (float)y, 0xFFFFFF);
|
||||
y += 10;
|
||||
int limit = Math.min(8, sources.size());
|
||||
for (int i = 0; i < limit; ++i) {
|
||||
AudioPlaybackService.PlayingSourceView s = (AudioPlaybackService.PlayingSourceView)sources.get(i);
|
||||
int color = DebugOverlayRenderer.colorIntFor(s);
|
||||
String line = String.format("#%d p=%d %s sid=%s st=%s cache=%s%s", i + 1, s.priority, s.sourceId, s.soundId, DebugOverlayRenderer.alStateName(s.alState), s.cacheStatus, s.liveUpdated ? " LIVE-UPDATED" : "");
|
||||
mc.font.drawShadow(stack, (ITextComponent)new StringTextComponent(line), (float)x, (float)y, color);
|
||||
y += 10;
|
||||
}
|
||||
}
|
||||
|
||||
private static float[] colorFor(AudioPlaybackService.PlayingSourceView source) {
|
||||
if ("ERROR".equals(source.cacheStatus) || "MISSING".equals(source.cacheStatus)) {
|
||||
return new float[]{0.95f, 0.15f, 0.15f};
|
||||
}
|
||||
if ("TRANSFERRING".equals(source.cacheStatus)) {
|
||||
return new float[]{0.95f, 0.85f, 0.2f};
|
||||
}
|
||||
if (source.alState == 4114) {
|
||||
return new float[]{0.2f, 0.9f, 0.25f};
|
||||
}
|
||||
return new float[]{0.2f, 0.9f, 1.0f};
|
||||
}
|
||||
|
||||
private static int colorIntFor(AudioPlaybackService.PlayingSourceView source) {
|
||||
float[] c = DebugOverlayRenderer.colorFor(source);
|
||||
int r = (int)(Math.max(0.0f, Math.min(1.0f, c[0])) * 255.0f);
|
||||
int g = (int)(Math.max(0.0f, Math.min(1.0f, c[1])) * 255.0f);
|
||||
int b = (int)(Math.max(0.0f, Math.min(1.0f, c[2])) * 255.0f);
|
||||
return r << 16 | g << 8 | b;
|
||||
}
|
||||
|
||||
private static String alStateName(int state) {
|
||||
if (state == 4114) {
|
||||
return "PLAYING";
|
||||
}
|
||||
if (state == 4115) {
|
||||
return "PAUSED";
|
||||
}
|
||||
if (state == 4116) {
|
||||
return "STOPPED";
|
||||
}
|
||||
if (state == 4113) {
|
||||
return "INITIAL";
|
||||
}
|
||||
return Integer.toString(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.client.debug;
|
||||
|
||||
public final class DebugState {
|
||||
private static boolean enabled;
|
||||
|
||||
private DebugState() {
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean enabled) {
|
||||
DebugState.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* com.mojang.brigadier.CommandDispatcher
|
||||
* com.mojang.brigadier.arguments.ArgumentType
|
||||
* com.mojang.brigadier.arguments.IntegerArgumentType
|
||||
* com.mojang.brigadier.arguments.StringArgumentType
|
||||
* com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||
* com.mojang.brigadier.context.CommandContext
|
||||
* net.minecraft.command.CommandSource
|
||||
* net.minecraft.command.Commands
|
||||
* net.minecraft.command.arguments.EntityArgument
|
||||
* net.minecraft.command.arguments.Vec3Argument
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraft.util.math.vector.Vector3d
|
||||
* net.minecraft.util.text.ITextComponent
|
||||
* net.minecraft.util.text.StringTextComponent
|
||||
* net.minecraftforge.fml.network.PacketDistributor
|
||||
*/
|
||||
package com.example.soundmod.command;
|
||||
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.SoundTransferManager;
|
||||
import com.example.soundmod.net.packet.C2SClientDiagReportPacket;
|
||||
import com.example.soundmod.net.packet.S2CClearCachePacket;
|
||||
import com.example.soundmod.net.packet.S2CClientDiagRequestPacket;
|
||||
import com.example.soundmod.net.packet.S2CDebugTogglePacket;
|
||||
import com.example.soundmod.net.packet.S2CPlaySoundPacket;
|
||||
import com.example.soundmod.net.packet.S2CSoundDebugLevelPacket;
|
||||
import com.example.soundmod.net.packet.S2CStopSoundPacket;
|
||||
import com.example.soundmod.sounddata.server.ServerSoundRepository;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.command.CommandSource;
|
||||
import net.minecraft.command.Commands;
|
||||
import net.minecraft.command.arguments.EntityArgument;
|
||||
import net.minecraft.command.arguments.Vec3Argument;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public final class ModCommands {
|
||||
private static final Map<String, DiagRequest> DIAG_REQUESTS = new ConcurrentHashMap<String, DiagRequest>();
|
||||
|
||||
private ModCommands() {
|
||||
}
|
||||
|
||||
public static void register(CommandDispatcher<CommandSource> dispatcher) {
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"playsoundcustom").requires(src -> src.hasPermission(2))).then(Commands.argument((String)"sound", (ArgumentType)StringArgumentType.word()).then(Commands.argument((String)"pos", (ArgumentType)Vec3Argument.vec3()).executes(ctx -> ModCommands.playCustom((CommandContext<CommandSource>)ctx, StringArgumentType.getString((CommandContext)ctx, (String)"sound"), Vec3Argument.getVec3((CommandContext)ctx, (String)"pos"))))));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"stopsoundcustom").requires(src -> src.hasPermission(2))).executes(ctx -> ModCommands.stopCustom((CommandContext<CommandSource>)ctx)));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"sounddebug").requires(src -> src.hasPermission(2))).then(Commands.literal((String)"on").executes(ctx -> ModCommands.setDebug((CommandContext<CommandSource>)ctx, true)))).then(Commands.literal((String)"off").executes(ctx -> ModCommands.setDebug((CommandContext<CommandSource>)ctx, false)))).then(Commands.literal((String)"level").then(Commands.argument((String)"level", (ArgumentType)IntegerArgumentType.integer((int)1, (int)3)).executes(ctx -> ModCommands.setDebugLevel((CommandContext<CommandSource>)ctx, IntegerArgumentType.getInteger((CommandContext)ctx, (String)"level"))))));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"soundreload").requires(src -> src.hasPermission(2))).executes(ctx -> ModCommands.reload((CommandContext<CommandSource>)ctx)));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"soundsync").requires(src -> src.hasPermission(2))).then(Commands.argument((String)"player", (ArgumentType)EntityArgument.player()).then(Commands.argument((String)"sound", (ArgumentType)StringArgumentType.word()).executes(ctx -> ModCommands.syncSound((CommandContext<CommandSource>)ctx, EntityArgument.getPlayer((CommandContext)ctx, (String)"player"), StringArgumentType.getString((CommandContext)ctx, (String)"sound"))))));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"soundcacheclear").requires(src -> src.hasPermission(2))).executes(ctx -> ModCommands.clearCache((CommandContext<CommandSource>)ctx)));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"sounddiag").requires(src -> src.hasPermission(2))).then(Commands.argument((String)"sound", (ArgumentType)StringArgumentType.word()).executes(ctx -> ModCommands.soundDiag((CommandContext<CommandSource>)ctx, StringArgumentType.getString((CommandContext)ctx, (String)"sound")))));
|
||||
dispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal((String)"sounddiagclient").requires(src -> src.hasPermission(2))).then(Commands.argument((String)"player", (ArgumentType)EntityArgument.player()).then(Commands.argument((String)"sound", (ArgumentType)StringArgumentType.word()).executes(ctx -> ModCommands.soundDiagClient((CommandContext<CommandSource>)ctx, EntityArgument.getPlayer((CommandContext)ctx, (String)"player"), StringArgumentType.getString((CommandContext)ctx, (String)"sound"))))));
|
||||
}
|
||||
|
||||
private static int playCustom(CommandContext<CommandSource> ctx, String sound, Vector3d pos) {
|
||||
SoundSettings settings = new SoundSettings();
|
||||
settings.soundId = sound;
|
||||
settings.radius = 48.0f;
|
||||
settings.volume = 1.0f;
|
||||
settings.pitch = 1.0f;
|
||||
BlockPos blockPos = new BlockPos(pos.x, pos.y, pos.z);
|
||||
for (ServerPlayerEntity player : ((CommandSource)ctx.getSource()).getLevel().players()) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CPlaySoundPacket("command_global", blockPos, settings));
|
||||
}
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("Playing custom sound: " + sound), true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int stopCustom(CommandContext<CommandSource> ctx) {
|
||||
for (ServerPlayerEntity player : ((CommandSource)ctx.getSource()).getLevel().players()) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CStopSoundPacket("command_global"));
|
||||
}
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("Stopped custom command sound"), true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int setDebug(CommandContext<CommandSource> ctx, boolean enabled) {
|
||||
SoundDebug.setEnabled(enabled);
|
||||
int levelCode = SoundDebug.getLevel().code();
|
||||
for (ServerPlayerEntity online : ((CommandSource)ctx.getSource()).getServer().getPlayerList().getPlayers()) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> online), (Object)new S2CSoundDebugLevelPacket(levelCode));
|
||||
}
|
||||
ServerPlayerEntity maybePlayer = null;
|
||||
try {
|
||||
maybePlayer = ((CommandSource)ctx.getSource()).getPlayerOrException();
|
||||
}
|
||||
catch (Exception online) {
|
||||
// empty catch block
|
||||
}
|
||||
if (maybePlayer != null) {
|
||||
ServerPlayerEntity targetPlayer = maybePlayer;
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> targetPlayer), (Object)new S2CDebugTogglePacket(enabled));
|
||||
}
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("sounddebug=" + enabled + " level=" + SoundDebug.getLevel().name()), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int setDebugLevel(CommandContext<CommandSource> ctx, int level) {
|
||||
SoundDebug.setLevelCode(level);
|
||||
for (ServerPlayerEntity online : ((CommandSource)ctx.getSource()).getServer().getPlayerList().getPlayers()) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> online), (Object)new S2CSoundDebugLevelPacket(level));
|
||||
}
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("sounddebug level set to L" + level), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int reload(CommandContext<CommandSource> ctx) {
|
||||
ServerSoundRepository.get().reload();
|
||||
ServerSoundRepository.get().sendManifestToAll();
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("customsounds reloaded"), true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int syncSound(CommandContext<CommandSource> ctx, ServerPlayerEntity player, String soundId) {
|
||||
SoundTransferManager.get().enqueueSingle(player, soundId);
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("sync queued for " + player.getScoreboardName() + " sound=" + soundId), true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int clearCache(CommandContext<CommandSource> ctx) {
|
||||
ServerPlayerEntity sender;
|
||||
try {
|
||||
sender = ((CommandSource)ctx.getSource()).getPlayerOrException();
|
||||
}
|
||||
catch (Exception e) {
|
||||
((CommandSource)ctx.getSource()).sendFailure((ITextComponent)new StringTextComponent("Run this command as a player."));
|
||||
return 0;
|
||||
}
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> sender), (Object)new S2CClearCachePacket());
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("client sound cache clear requested"), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int soundDiag(CommandContext<CommandSource> ctx, String soundId) {
|
||||
ServerSoundRepository repo = ServerSoundRepository.get();
|
||||
SoundManifestEntry entry = null;
|
||||
for (SoundManifestEntry e : repo.getManifest()) {
|
||||
if (!e.soundId.equals(soundId)) continue;
|
||||
entry = e;
|
||||
break;
|
||||
}
|
||||
if (entry == null) {
|
||||
((CommandSource)ctx.getSource()).sendFailure((ITextComponent)new StringTextComponent("sounddiag: not found in manifest: " + soundId));
|
||||
return 0;
|
||||
}
|
||||
Path resolved = repo.resolveSound(soundId);
|
||||
boolean exists = resolved != null && Files.isRegularFile(resolved, new LinkOption[0]);
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("sounddiag: id=" + soundId + " flags=" + entry.flags + " size=" + entry.size + " hash=" + entry.sha256 + " exists=" + exists + " path=" + (resolved == null ? "<null>" : resolved.toString())), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int soundDiagClient(CommandContext<CommandSource> ctx, ServerPlayerEntity target, String soundId) {
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
DIAG_REQUESTS.put(requestId, new DiagRequest((CommandSource)ctx.getSource(), target.getUUID(), soundId));
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> target), (Object)new S2CClientDiagRequestPacket(requestId, soundId));
|
||||
((CommandSource)ctx.getSource()).sendSuccess((ITextComponent)new StringTextComponent("sounddiagclient requested: player=" + target.getScoreboardName() + " sound=" + soundId), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static void handleClientDiagReport(ServerPlayerEntity sender, C2SClientDiagReportPacket msg) {
|
||||
DiagRequest req = DIAG_REQUESTS.remove(msg.requestId);
|
||||
if (req == null) {
|
||||
sender.sendMessage((ITextComponent)new StringTextComponent("[sounddiagclient] stale response requestId=" + msg.requestId), sender.getUUID());
|
||||
return;
|
||||
}
|
||||
String text = "sounddiagclient: player=" + sender.getScoreboardName() + " sound=" + msg.soundId + " status=" + msg.status + " size=" + msg.size + " hash=" + msg.actualHash + " path=" + (msg.localPath.isEmpty() ? "<none>" : msg.localPath) + " note=" + msg.note;
|
||||
req.source.sendSuccess((ITextComponent)new StringTextComponent(text), false);
|
||||
}
|
||||
|
||||
private static class DiagRequest {
|
||||
final CommandSource source;
|
||||
final UUID playerId;
|
||||
final String soundId;
|
||||
|
||||
DiagRequest(CommandSource source, UUID playerId, String soundId) {
|
||||
this.source = source;
|
||||
this.playerId = playerId;
|
||||
this.soundId = soundId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
public enum ActivationMode {
|
||||
CLICK,
|
||||
REDSTONE,
|
||||
AUTO,
|
||||
TIMER;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
public enum AttenuationMode {
|
||||
LINEAR,
|
||||
EXPONENTIAL,
|
||||
LOGARITHMIC,
|
||||
NONE;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
public enum RedstoneMode {
|
||||
EDGE,
|
||||
HOLD;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
* net.minecraft.nbt.INBT
|
||||
* net.minecraft.nbt.ListNBT
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.nbt.ListNBT;
|
||||
|
||||
public class ScenarioQueue {
|
||||
public final List<Step> steps = new ArrayList<Step>();
|
||||
public boolean loop;
|
||||
|
||||
public CompoundNBT toNbt() {
|
||||
CompoundNBT tag = new CompoundNBT();
|
||||
ListNBT list = new ListNBT();
|
||||
for (Step step : this.steps) {
|
||||
list.add(step.toNbt());
|
||||
}
|
||||
tag.put("steps", list);
|
||||
tag.putBoolean("loop", this.loop);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static ScenarioQueue fromNbt(CompoundNBT tag) {
|
||||
ScenarioQueue queue = new ScenarioQueue();
|
||||
ListNBT list = tag.getList("steps", 10);
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
queue.steps.add(Step.fromNbt(list.getCompound(i)));
|
||||
}
|
||||
queue.loop = tag.getBoolean("loop");
|
||||
return queue;
|
||||
}
|
||||
|
||||
public static class Step {
|
||||
public String soundId;
|
||||
public int delayTicks;
|
||||
|
||||
public Step(String soundId, int delayTicks) {
|
||||
this.soundId = soundId;
|
||||
this.delayTicks = delayTicks;
|
||||
}
|
||||
|
||||
public CompoundNBT toNbt() {
|
||||
CompoundNBT tag = new CompoundNBT();
|
||||
tag.putString("soundId", this.soundId);
|
||||
tag.putInt("delayTicks", this.delayTicks);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static Step fromNbt(CompoundNBT tag) {
|
||||
return new Step(tag.getString("soundId"), tag.getInt("delayTicks"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
public final class SoundCategoryEx {
|
||||
public static final String MUSIC = "music";
|
||||
public static final String AMBIENT = "ambient";
|
||||
public static final String EFFECT = "effect";
|
||||
public static final String VOICE = "voice";
|
||||
|
||||
private SoundCategoryEx() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
public class SoundManifestEntry {
|
||||
public static final int FLAG_READY = 0;
|
||||
public static final int FLAG_UNSUPPORTED = 1;
|
||||
public static final int FLAG_HASH_ERROR = 2;
|
||||
public final String soundId;
|
||||
public final String relativePath;
|
||||
public final String sha256;
|
||||
public final long size;
|
||||
public final long lastModified;
|
||||
public final int flags;
|
||||
|
||||
public SoundManifestEntry(String soundId, String relativePath, String sha256, long size, long lastModified, int flags) {
|
||||
this.soundId = soundId;
|
||||
this.relativePath = relativePath;
|
||||
this.sha256 = sha256;
|
||||
this.size = size;
|
||||
this.lastModified = lastModified;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public static void write(PacketBuffer buf, SoundManifestEntry e) {
|
||||
buf.writeUtf(e.soundId);
|
||||
buf.writeUtf(e.relativePath);
|
||||
buf.writeUtf(e.sha256);
|
||||
buf.writeLong(e.size);
|
||||
buf.writeLong(e.lastModified);
|
||||
buf.writeVarInt(e.flags);
|
||||
}
|
||||
|
||||
public static SoundManifestEntry read(PacketBuffer buf) {
|
||||
return new SoundManifestEntry(buf.readUtf(Short.MAX_VALUE), buf.readUtf(Short.MAX_VALUE), buf.readUtf(Short.MAX_VALUE), buf.readLong(), buf.readLong(), buf.readVarInt());
|
||||
}
|
||||
|
||||
public static List<String> hashes(List<SoundManifestEntry> entries) {
|
||||
ArrayList<String> hashes = new ArrayList<String>();
|
||||
for (SoundManifestEntry entry : entries) {
|
||||
if (entry.sha256.isEmpty()) continue;
|
||||
hashes.add(entry.sha256);
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
import com.example.soundmod.common.ActivationMode;
|
||||
import com.example.soundmod.common.AttenuationMode;
|
||||
import com.example.soundmod.common.RedstoneMode;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
|
||||
public class SoundSettings {
|
||||
public String soundId = "";
|
||||
public float volume = 1.0f;
|
||||
public float pitch = 1.0f;
|
||||
public float radius = 16.0f;
|
||||
public boolean loop;
|
||||
public int startDelayTicks;
|
||||
public int repeatDelayTicks;
|
||||
public AttenuationMode attenuationMode = AttenuationMode.LINEAR;
|
||||
public String category = "ambient";
|
||||
public int priority;
|
||||
public boolean use3d = true;
|
||||
public boolean doppler;
|
||||
public boolean occlusion;
|
||||
public boolean reverb;
|
||||
public float reverbStrength;
|
||||
public float occlusionStrength;
|
||||
public int fadeInTicks;
|
||||
public int fadeOutTicks;
|
||||
public boolean crossfade;
|
||||
public ActivationMode activationMode = ActivationMode.CLICK;
|
||||
public RedstoneMode redstoneMode = RedstoneMode.EDGE;
|
||||
|
||||
public CompoundNBT toNbt() {
|
||||
CompoundNBT tag = new CompoundNBT();
|
||||
tag.putString("soundId", this.soundId);
|
||||
tag.putFloat("volume", this.volume);
|
||||
tag.putFloat("pitch", this.pitch);
|
||||
tag.putFloat("radius", this.radius);
|
||||
tag.putBoolean("loop", this.loop);
|
||||
tag.putInt("startDelayTicks", this.startDelayTicks);
|
||||
tag.putInt("repeatDelayTicks", this.repeatDelayTicks);
|
||||
tag.putString("attenuationMode", this.attenuationMode.name());
|
||||
tag.putString("category", this.category);
|
||||
tag.putInt("priority", this.priority);
|
||||
tag.putBoolean("use3d", this.use3d);
|
||||
tag.putBoolean("doppler", this.doppler);
|
||||
tag.putBoolean("occlusion", this.occlusion);
|
||||
tag.putBoolean("reverb", this.reverb);
|
||||
tag.putFloat("reverbStrength", this.reverbStrength);
|
||||
tag.putFloat("occlusionStrength", this.occlusionStrength);
|
||||
tag.putInt("fadeInTicks", this.fadeInTicks);
|
||||
tag.putInt("fadeOutTicks", this.fadeOutTicks);
|
||||
tag.putBoolean("crossfade", this.crossfade);
|
||||
tag.putString("activationMode", this.activationMode.name());
|
||||
tag.putString("redstoneMode", this.redstoneMode.name());
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static SoundSettings fromNbt(CompoundNBT tag) {
|
||||
SoundSettings s = new SoundSettings();
|
||||
if (tag.contains("soundId")) {
|
||||
s.soundId = tag.getString("soundId");
|
||||
}
|
||||
if (tag.contains("volume")) {
|
||||
s.volume = tag.getFloat("volume");
|
||||
}
|
||||
if (tag.contains("pitch")) {
|
||||
s.pitch = tag.getFloat("pitch");
|
||||
}
|
||||
if (tag.contains("radius")) {
|
||||
s.radius = tag.getFloat("radius");
|
||||
}
|
||||
if (tag.contains("loop")) {
|
||||
s.loop = tag.getBoolean("loop");
|
||||
}
|
||||
if (tag.contains("startDelayTicks")) {
|
||||
s.startDelayTicks = tag.getInt("startDelayTicks");
|
||||
}
|
||||
if (tag.contains("repeatDelayTicks")) {
|
||||
s.repeatDelayTicks = tag.getInt("repeatDelayTicks");
|
||||
}
|
||||
if (tag.contains("attenuationMode")) {
|
||||
s.attenuationMode = SoundSettings.parse(tag.getString("attenuationMode"), AttenuationMode.LINEAR);
|
||||
}
|
||||
if (tag.contains("category")) {
|
||||
s.category = tag.getString("category");
|
||||
}
|
||||
if (tag.contains("priority")) {
|
||||
s.priority = tag.getInt("priority");
|
||||
}
|
||||
if (tag.contains("use3d")) {
|
||||
s.use3d = tag.getBoolean("use3d");
|
||||
}
|
||||
if (tag.contains("doppler")) {
|
||||
s.doppler = tag.getBoolean("doppler");
|
||||
}
|
||||
if (tag.contains("occlusion")) {
|
||||
s.occlusion = tag.getBoolean("occlusion");
|
||||
}
|
||||
if (tag.contains("reverb")) {
|
||||
s.reverb = tag.getBoolean("reverb");
|
||||
}
|
||||
if (tag.contains("reverbStrength")) {
|
||||
s.reverbStrength = tag.getFloat("reverbStrength");
|
||||
}
|
||||
if (tag.contains("occlusionStrength")) {
|
||||
s.occlusionStrength = tag.getFloat("occlusionStrength");
|
||||
}
|
||||
if (tag.contains("fadeInTicks")) {
|
||||
s.fadeInTicks = tag.getInt("fadeInTicks");
|
||||
}
|
||||
if (tag.contains("fadeOutTicks")) {
|
||||
s.fadeOutTicks = tag.getInt("fadeOutTicks");
|
||||
}
|
||||
if (tag.contains("crossfade")) {
|
||||
s.crossfade = tag.getBoolean("crossfade");
|
||||
}
|
||||
if (tag.contains("activationMode")) {
|
||||
s.activationMode = SoundSettings.parse(tag.getString("activationMode"), ActivationMode.CLICK);
|
||||
}
|
||||
if (tag.contains("redstoneMode")) {
|
||||
s.redstoneMode = SoundSettings.parse(tag.getString("redstoneMode"), RedstoneMode.EDGE);
|
||||
}
|
||||
SoundSettings.sanitizeInPlace(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static SoundSettings sanitizedCopy(SoundSettings source) {
|
||||
SoundSettings copy = SoundSettings.fromNbt(source.toNbt());
|
||||
SoundSettings.sanitizeInPlace(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static void sanitizeInPlace(SoundSettings s) {
|
||||
if (s.soundId == null) {
|
||||
s.soundId = "";
|
||||
}
|
||||
if (!Float.isFinite(s.volume) || s.volume < 0.0f || s.volume > 8.0f) {
|
||||
s.volume = 1.0f;
|
||||
}
|
||||
if (!Float.isFinite(s.pitch) || s.pitch < 0.05f || s.pitch > 4.0f) {
|
||||
s.pitch = 1.0f;
|
||||
}
|
||||
if (!Float.isFinite(s.radius) || s.radius < 0.1f || s.radius > 512.0f) {
|
||||
s.radius = 16.0f;
|
||||
}
|
||||
if (!Float.isFinite(s.reverbStrength) || s.reverbStrength < 0.0f || s.reverbStrength > 1.0f) {
|
||||
s.reverbStrength = 0.0f;
|
||||
}
|
||||
if (!Float.isFinite(s.occlusionStrength) || s.occlusionStrength < 0.0f || s.occlusionStrength > 1.0f) {
|
||||
s.occlusionStrength = 0.0f;
|
||||
}
|
||||
if (s.reverb && s.reverbStrength <= 0.01f) {
|
||||
s.reverbStrength = 0.45f;
|
||||
}
|
||||
if (s.occlusion && s.occlusionStrength <= 0.01f) {
|
||||
s.occlusionStrength = 0.65f;
|
||||
}
|
||||
if (s.startDelayTicks < 0) {
|
||||
s.startDelayTicks = 0;
|
||||
}
|
||||
if (s.repeatDelayTicks < 0) {
|
||||
s.repeatDelayTicks = 0;
|
||||
}
|
||||
if (s.fadeInTicks < 0) {
|
||||
s.fadeInTicks = 0;
|
||||
}
|
||||
if (s.fadeOutTicks < 0) {
|
||||
s.fadeOutTicks = 0;
|
||||
}
|
||||
if (s.redstoneMode == null) {
|
||||
s.redstoneMode = RedstoneMode.EDGE;
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> E parse(String raw, E fallback) {
|
||||
try {
|
||||
return Enum.valueOf(fallback.getDeclaringClass(), raw);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
public enum ZoneIntersectionPolicy {
|
||||
HIGHEST_PRIORITY,
|
||||
BLEND,
|
||||
QUEUE;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.common;
|
||||
|
||||
public enum ZoneShape {
|
||||
SPHERE,
|
||||
BOX;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.server.MinecraftServer
|
||||
* net.minecraft.world.server.ServerWorld
|
||||
* net.minecraftforge.event.TickEvent$Phase
|
||||
* net.minecraftforge.event.TickEvent$ServerTickEvent
|
||||
* net.minecraftforge.event.entity.player.PlayerEvent$PlayerLoggedInEvent
|
||||
* net.minecraftforge.event.entity.player.PlayerEvent$PlayerLoggedOutEvent
|
||||
* net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
* net.minecraftforge.fml.common.Mod$EventBusSubscriber
|
||||
* net.minecraftforge.fml.server.ServerLifecycleHooks
|
||||
*/
|
||||
package com.example.soundmod.core;
|
||||
|
||||
import com.example.soundmod.gameplay.zone.ZoneManager;
|
||||
import com.example.soundmod.net.SoundTransferManager;
|
||||
import com.example.soundmod.sounddata.server.ServerSoundRepository;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.server.ServerLifecycleHooks;
|
||||
|
||||
@Mod.EventBusSubscriber(modid="soundmod")
|
||||
public final class CommonEvents {
|
||||
private CommonEvents() {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (event.getPlayer() instanceof ServerPlayerEntity) {
|
||||
ServerSoundRepository.get().sendManifestTo((ServerPlayerEntity)event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
if (event.getPlayer() instanceof ServerPlayerEntity) {
|
||||
ServerPlayerEntity player = (ServerPlayerEntity)event.getPlayer();
|
||||
SoundTransferManager.get().clearPlayer(player);
|
||||
ZoneManager.clearPlayer(player.getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
|
||||
if (event.phase != TickEvent.Phase.END || server == null) {
|
||||
return;
|
||||
}
|
||||
for (ServerPlayerEntity player : server.getPlayerList().getPlayers()) {
|
||||
SoundTransferManager.get().tickPlayer(player);
|
||||
}
|
||||
ServerSoundRepository.get().tick();
|
||||
for (ServerWorld world : server.getAllLevels()) {
|
||||
ZoneManager.tick(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraftforge.common.ForgeConfigSpec
|
||||
* net.minecraftforge.common.ForgeConfigSpec$Builder
|
||||
* net.minecraftforge.common.ForgeConfigSpec$ConfigValue
|
||||
* net.minecraftforge.common.ForgeConfigSpec$DoubleValue
|
||||
* net.minecraftforge.common.ForgeConfigSpec$IntValue
|
||||
*/
|
||||
package com.example.soundmod.core;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
|
||||
public final class ModConfig {
|
||||
public static final ForgeConfigSpec SPEC;
|
||||
public static final ForgeConfigSpec.IntValue MAX_SOUND_FILE_BYTES;
|
||||
public static final ForgeConfigSpec.IntValue CHUNK_BYTES;
|
||||
public static final ForgeConfigSpec.IntValue MAX_PARALLEL_TRANSFERS;
|
||||
public static final ForgeConfigSpec.IntValue CACHE_MAX_MB;
|
||||
public static final ForgeConfigSpec.IntValue TRANSFER_TIMEOUT_SECONDS;
|
||||
public static final ForgeConfigSpec.IntValue RETRY_LIMIT;
|
||||
public static final ForgeConfigSpec.IntValue MAX_ACTIVE_SOURCES;
|
||||
public static final ForgeConfigSpec.DoubleValue DUCKING_STRENGTH;
|
||||
public static final ForgeConfigSpec.IntValue AUTO_REINDEX_SECONDS;
|
||||
public static final ForgeConfigSpec.ConfigValue<String> LOG_PROFILE;
|
||||
|
||||
private ModConfig() {
|
||||
}
|
||||
|
||||
static {
|
||||
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
||||
builder.push("transfer");
|
||||
MAX_SOUND_FILE_BYTES = builder.comment("Max sound file size in bytes").defineInRange("maxSoundFileBytes", 0x4000000, 1024, Integer.MAX_VALUE);
|
||||
CHUNK_BYTES = builder.comment("Network chunk size in bytes").defineInRange("chunkBytes", 131072, 4096, 524288);
|
||||
MAX_PARALLEL_TRANSFERS = builder.comment("Maximum parallel file transfers per player").defineInRange("maxParallelTransfers", 4, 1, 8);
|
||||
TRANSFER_TIMEOUT_SECONDS = builder.comment("File transfer timeout in seconds").defineInRange("transferTimeoutSeconds", 20, 5, 120);
|
||||
RETRY_LIMIT = builder.comment("Retry attempts for transfer failures").defineInRange("retryLimit", 3, 0, 10);
|
||||
builder.pop();
|
||||
builder.push("cache");
|
||||
CACHE_MAX_MB = builder.comment("Client cache upper bound in MB").defineInRange("maxCacheMb", 1024, 64, 4096);
|
||||
builder.pop();
|
||||
builder.push("audio");
|
||||
MAX_ACTIVE_SOURCES = builder.comment("Maximum simultaneously active custom sound sources on client").defineInRange("maxActiveSources", 24, 4, 128);
|
||||
DUCKING_STRENGTH = builder.comment("How much low-priority sounds are attenuated when high-priority sounds play").defineInRange("duckingStrength", 0.35, 0.0, 0.95);
|
||||
builder.pop();
|
||||
builder.push("repository");
|
||||
AUTO_REINDEX_SECONDS = builder.comment("Server polling interval for world/customsounds changes; 0 disables automatic reindex").defineInRange("autoReindexSeconds", 5, 0, 120);
|
||||
builder.pop();
|
||||
builder.push("logging");
|
||||
LOG_PROFILE = builder.comment("Logging profile: release or dev").define("profile", "release");
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.core;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
|
||||
public final class SoundDebug {
|
||||
private static volatile Level level = Level.OFF;
|
||||
|
||||
private SoundDebug() {
|
||||
}
|
||||
|
||||
public static Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public static void setLevel(Level newLevel) {
|
||||
level = newLevel == null ? Level.OFF : newLevel;
|
||||
SoundMod.LOGGER.info("[SOUNDDBG][COMMAND][GLOBAL] level={}", (Object)level.name());
|
||||
}
|
||||
|
||||
public static void setLevelCode(int code) {
|
||||
SoundDebug.setLevel(Level.fromCode(code));
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean enabled) {
|
||||
if (enabled) {
|
||||
if (level == Level.OFF) {
|
||||
SoundDebug.setLevel(Level.L2);
|
||||
}
|
||||
} else {
|
||||
SoundDebug.setLevel(Level.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean enabled(Level threshold) {
|
||||
if (threshold == Level.L3 && !SoundDebug.isDevProfile()) {
|
||||
return false;
|
||||
}
|
||||
return level.code() >= threshold.code();
|
||||
}
|
||||
|
||||
private static boolean isDevProfile() {
|
||||
try {
|
||||
String profile = (String)ModConfig.LOG_PROFILE.get();
|
||||
return profile != null && profile.trim().equalsIgnoreCase("dev");
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void log(Level threshold, String stage, String id, String message, Object ... args) {
|
||||
if (!SoundDebug.enabled(threshold)) {
|
||||
return;
|
||||
}
|
||||
String fmt = "[SOUNDDBG][" + stage + "][" + id + "] " + message;
|
||||
SoundMod.LOGGER.info(fmt, args);
|
||||
}
|
||||
|
||||
public static void warn(Level threshold, String stage, String id, String message, Object ... args) {
|
||||
if (!SoundDebug.enabled(threshold)) {
|
||||
return;
|
||||
}
|
||||
String fmt = "[SOUNDDBG][" + stage + "][" + id + "] " + message;
|
||||
SoundMod.LOGGER.warn(fmt, args);
|
||||
}
|
||||
|
||||
public static enum Level {
|
||||
OFF(0),
|
||||
L1(1),
|
||||
L2(2),
|
||||
L3(3);
|
||||
|
||||
private final int code;
|
||||
|
||||
private Level(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static Level fromCode(int code) {
|
||||
for (Level level : Level.values()) {
|
||||
if (level.code != code) continue;
|
||||
return level;
|
||||
}
|
||||
return OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.block.AbstractBlock$Properties
|
||||
* net.minecraft.block.Block
|
||||
* net.minecraft.block.BlockState
|
||||
* net.minecraft.entity.player.PlayerEntity
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.inventory.container.INamedContainerProvider
|
||||
* net.minecraft.tileentity.TileEntity
|
||||
* net.minecraft.tileentity.TileEntityType
|
||||
* net.minecraft.util.ActionResultType
|
||||
* net.minecraft.util.Hand
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraft.util.math.BlockRayTraceResult
|
||||
* net.minecraft.world.IBlockReader
|
||||
* net.minecraft.world.World
|
||||
* net.minecraftforge.fml.network.NetworkHooks
|
||||
*/
|
||||
package com.example.soundmod.gameplay.block;
|
||||
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import com.example.soundmod.registry.ModTileEntities;
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.inventory.container.INamedContainerProvider;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityType;
|
||||
import net.minecraft.util.ActionResultType;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.BlockRayTraceResult;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.network.NetworkHooks;
|
||||
|
||||
public class SoundPlayerBlock
|
||||
extends Block {
|
||||
public SoundPlayerBlock(AbstractBlock.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public boolean hasTileEntity(BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public TileEntity createTileEntity(BlockState state, IBlockReader world) {
|
||||
return ((TileEntityType)ModTileEntities.SOUND_PLAYER_TILE.get()).create();
|
||||
}
|
||||
|
||||
public ActionResultType func_225533_a_(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) {
|
||||
TileEntity te = world.getBlockEntity(pos);
|
||||
if (!world.isClientSide && te instanceof SoundPlayerTileEntity && player instanceof ServerPlayerEntity) {
|
||||
SoundPlayerTileEntity soundTe = (SoundPlayerTileEntity)te;
|
||||
if (player.isShiftKeyDown()) {
|
||||
soundTe.playFromClick((ServerPlayerEntity)player);
|
||||
} else {
|
||||
NetworkHooks.openGui((ServerPlayerEntity)((ServerPlayerEntity)player), (INamedContainerProvider)soundTe, (BlockPos)pos);
|
||||
}
|
||||
}
|
||||
return ActionResultType.sidedSuccess((boolean)world.isClientSide);
|
||||
}
|
||||
|
||||
public void func_220069_a(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean moving) {
|
||||
TileEntity te = world.getBlockEntity(pos);
|
||||
if (te instanceof SoundPlayerTileEntity) {
|
||||
((SoundPlayerTileEntity)te).onRedstoneChanged(world.hasNeighborSignal(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.PlayerEntity
|
||||
* net.minecraft.entity.player.PlayerInventory
|
||||
* net.minecraft.inventory.container.Container
|
||||
* net.minecraft.inventory.container.ContainerType
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraft.tileentity.TileEntity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraft.world.World
|
||||
*/
|
||||
package com.example.soundmod.gameplay.container;
|
||||
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import com.example.soundmod.registry.ModContainers;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.inventory.container.Container;
|
||||
import net.minecraft.inventory.container.ContainerType;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class SoundPlayerContainer
|
||||
extends Container {
|
||||
private final World world;
|
||||
private final BlockPos pos;
|
||||
|
||||
public SoundPlayerContainer(int id, PlayerInventory inventory, PacketBuffer data) {
|
||||
this(id, inventory.player.level, data.readBlockPos(), inventory, inventory.player);
|
||||
}
|
||||
|
||||
public SoundPlayerContainer(int id, World world, BlockPos pos, PlayerInventory inventory, PlayerEntity player) {
|
||||
super((ContainerType)ModContainers.SOUND_PLAYER.get(), id);
|
||||
this.world = world;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public BlockPos getPos() {
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
public SoundPlayerTileEntity getTile() {
|
||||
TileEntity te = this.world.getBlockEntity(this.pos);
|
||||
return te instanceof SoundPlayerTileEntity ? (SoundPlayerTileEntity)te : null;
|
||||
}
|
||||
|
||||
public boolean stillValid(PlayerEntity player) {
|
||||
return player.distanceToSqr((double)this.pos.getX() + 0.5, (double)this.pos.getY() + 0.5, (double)this.pos.getZ() + 0.5) < 64.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraftforge.fml.network.PacketDistributor
|
||||
*/
|
||||
package com.example.soundmod.gameplay.scenario;
|
||||
|
||||
import com.example.soundmod.common.ScenarioQueue;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.S2CPlaySoundPacket;
|
||||
import com.example.soundmod.net.packet.S2CStopSoundPacket;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public final class ScenarioManager {
|
||||
private ScenarioManager() {
|
||||
}
|
||||
|
||||
public static void triggerSimpleQueue(ServerPlayerEntity player, String sourcePrefix, BlockPos pos, ScenarioQueue queue, SoundSettings settings) {
|
||||
if (queue.steps.isEmpty()) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CPlaySoundPacket(sourcePrefix + ":single", pos, settings));
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
for (ScenarioQueue.Step step : queue.steps) {
|
||||
SoundSettings copy = SoundSettings.fromNbt(settings.toNbt());
|
||||
copy.soundId = step.soundId;
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CPlaySoundPacket(sourcePrefix + ":step_" + i++, pos, copy));
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop(ServerPlayerEntity player, String sourceId) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CStopSoundPacket(sourceId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* javax.annotation.Nullable
|
||||
* net.minecraft.block.BlockState
|
||||
* net.minecraft.entity.player.PlayerEntity
|
||||
* net.minecraft.entity.player.PlayerInventory
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.inventory.container.Container
|
||||
* net.minecraft.inventory.container.INamedContainerProvider
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
* net.minecraft.nbt.INBT
|
||||
* net.minecraft.network.NetworkManager
|
||||
* net.minecraft.network.play.server.SUpdateTileEntityPacket
|
||||
* net.minecraft.tileentity.ITickableTileEntity
|
||||
* net.minecraft.tileentity.TileEntity
|
||||
* net.minecraft.tileentity.TileEntityType
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraft.util.math.vector.Vector3d
|
||||
* net.minecraft.util.text.ITextComponent
|
||||
* net.minecraft.util.text.TranslationTextComponent
|
||||
* net.minecraftforge.fml.network.PacketDistributor
|
||||
*/
|
||||
package com.example.soundmod.gameplay.tile;
|
||||
|
||||
import com.example.soundmod.common.ActivationMode;
|
||||
import com.example.soundmod.common.RedstoneMode;
|
||||
import com.example.soundmod.common.ScenarioQueue;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import com.example.soundmod.common.ZoneIntersectionPolicy;
|
||||
import com.example.soundmod.common.ZoneShape;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.gameplay.container.SoundPlayerContainer;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.S2CBlockStateSyncPacket;
|
||||
import com.example.soundmod.net.packet.S2CPlaySoundPacket;
|
||||
import com.example.soundmod.net.packet.S2CZoneStateSyncPacket;
|
||||
import com.example.soundmod.registry.ModTileEntities;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.inventory.container.Container;
|
||||
import net.minecraft.inventory.container.INamedContainerProvider;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.network.NetworkManager;
|
||||
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
|
||||
import net.minecraft.tileentity.ITickableTileEntity;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityType;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public class SoundPlayerTileEntity
|
||||
extends TileEntity
|
||||
implements INamedContainerProvider,
|
||||
ITickableTileEntity {
|
||||
private static final int MANUAL_CLICK_DEBOUNCE_TICKS = 6;
|
||||
private static final Map<String, Long> MANUAL_CLICK_GUARD = new ConcurrentHashMap<String, Long>();
|
||||
private SoundSettings settings = new SoundSettings();
|
||||
private ScenarioQueue scenario = new ScenarioQueue();
|
||||
private boolean redstonePowered;
|
||||
private int timerTicks;
|
||||
private boolean playing;
|
||||
private int pendingStartTicks = -1;
|
||||
private boolean scenarioRunning;
|
||||
private int scenarioStepIndex;
|
||||
private int scenarioWaitTicks;
|
||||
private long lastPlayTick = Long.MIN_VALUE;
|
||||
private long lastStopTick = Long.MIN_VALUE;
|
||||
private boolean sourceEnabled = true;
|
||||
private boolean zoneEnabled = true;
|
||||
private boolean wizardCompleted;
|
||||
private ZoneShape zoneShape = ZoneShape.SPHERE;
|
||||
private float zoneRadius = 12.0f;
|
||||
private Vector3d zoneBoxHalfExtents = new Vector3d(8.0, 4.0, 8.0);
|
||||
private boolean zoneAutoStartOnEnter = true;
|
||||
private boolean zoneStopOnExit = true;
|
||||
private ZoneIntersectionPolicy zoneIntersectionPolicy = ZoneIntersectionPolicy.HIGHEST_PRIORITY;
|
||||
private ScenarioQueue zoneScenario = new ScenarioQueue();
|
||||
private boolean zoneUseCustomCoords;
|
||||
private int zoneSourceX;
|
||||
private int zoneSourceY;
|
||||
private int zoneSourceZ;
|
||||
|
||||
public SoundPlayerTileEntity() {
|
||||
super((TileEntityType)ModTileEntities.SOUND_PLAYER_TILE.get());
|
||||
}
|
||||
|
||||
public SoundSettings getSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
public ScenarioQueue getScenario() {
|
||||
return this.scenario;
|
||||
}
|
||||
|
||||
public boolean isSourceEnabled() {
|
||||
return this.sourceEnabled;
|
||||
}
|
||||
|
||||
public boolean isZoneEnabled() {
|
||||
return this.zoneEnabled;
|
||||
}
|
||||
|
||||
public boolean isWizardCompleted() {
|
||||
return this.wizardCompleted;
|
||||
}
|
||||
|
||||
public ZoneShape getZoneShape() {
|
||||
return this.zoneShape;
|
||||
}
|
||||
|
||||
public float getZoneRadius() {
|
||||
return this.zoneRadius;
|
||||
}
|
||||
|
||||
public Vector3d getZoneBoxHalfExtents() {
|
||||
return this.zoneBoxHalfExtents;
|
||||
}
|
||||
|
||||
public boolean isZoneAutoStartOnEnter() {
|
||||
return this.zoneAutoStartOnEnter;
|
||||
}
|
||||
|
||||
public boolean isZoneStopOnExit() {
|
||||
return this.zoneStopOnExit;
|
||||
}
|
||||
|
||||
public ZoneIntersectionPolicy getZoneIntersectionPolicy() {
|
||||
return this.zoneIntersectionPolicy;
|
||||
}
|
||||
|
||||
public ScenarioQueue getZoneScenario() {
|
||||
return this.zoneScenario;
|
||||
}
|
||||
|
||||
public boolean isZoneUseCustomCoords() {
|
||||
return this.zoneUseCustomCoords;
|
||||
}
|
||||
|
||||
public int getZoneSourceX() {
|
||||
return this.zoneSourceX;
|
||||
}
|
||||
|
||||
public int getZoneSourceY() {
|
||||
return this.zoneSourceY;
|
||||
}
|
||||
|
||||
public int getZoneSourceZ() {
|
||||
return this.zoneSourceZ;
|
||||
}
|
||||
|
||||
public BlockPos getZoneSourcePos() {
|
||||
if (this.zoneUseCustomCoords) {
|
||||
return new BlockPos(this.zoneSourceX, this.zoneSourceY, this.zoneSourceZ);
|
||||
}
|
||||
return this.worldPosition;
|
||||
}
|
||||
|
||||
public void updateFromClient(CompoundNBT payload) {
|
||||
ActivationMode oldMode = this.settings.activationMode;
|
||||
this.settings = SoundSettings.fromNbt(payload.getCompound("settings"));
|
||||
this.scenario = ScenarioQueue.fromNbt(payload.getCompound("scenario"));
|
||||
this.sourceEnabled = payload.contains("sourceEnabled") ? payload.getBoolean("sourceEnabled") : this.sourceEnabled;
|
||||
this.zoneEnabled = payload.contains("zoneEnabled") ? payload.getBoolean("zoneEnabled") : this.zoneEnabled;
|
||||
this.wizardCompleted = payload.contains("wizardCompleted") ? payload.getBoolean("wizardCompleted") : this.wizardCompleted;
|
||||
this.zoneShape = this.parseZoneShape(payload.contains("zoneShape") ? payload.getString("zoneShape") : this.zoneShape.name());
|
||||
this.zoneRadius = this.clampFloat(payload.contains("zoneRadius") ? payload.getFloat("zoneRadius") : this.zoneRadius, 1.0f, 256.0f);
|
||||
double boxX = payload.contains("zoneBoxX") ? payload.getDouble("zoneBoxX") : this.zoneBoxHalfExtents.x;
|
||||
double boxY = payload.contains("zoneBoxY") ? payload.getDouble("zoneBoxY") : this.zoneBoxHalfExtents.y;
|
||||
double boxZ = payload.contains("zoneBoxZ") ? payload.getDouble("zoneBoxZ") : this.zoneBoxHalfExtents.z;
|
||||
this.zoneBoxHalfExtents = new Vector3d(this.clampDouble(boxX, 0.5, 128.0), this.clampDouble(boxY, 0.5, 128.0), this.clampDouble(boxZ, 0.5, 128.0));
|
||||
this.zoneAutoStartOnEnter = payload.contains("zoneAutoStartOnEnter") ? payload.getBoolean("zoneAutoStartOnEnter") : this.zoneAutoStartOnEnter;
|
||||
this.zoneStopOnExit = payload.contains("zoneStopOnExit") ? payload.getBoolean("zoneStopOnExit") : this.zoneStopOnExit;
|
||||
this.zoneIntersectionPolicy = this.parseZonePolicy(payload.contains("zoneIntersectionPolicy") ? payload.getString("zoneIntersectionPolicy") : this.zoneIntersectionPolicy.name());
|
||||
if (payload.contains("zoneScenario")) {
|
||||
this.zoneScenario = ScenarioQueue.fromNbt(payload.getCompound("zoneScenario"));
|
||||
}
|
||||
this.zoneUseCustomCoords = payload.contains("zoneUseCustomCoords") ? payload.getBoolean("zoneUseCustomCoords") : this.zoneUseCustomCoords;
|
||||
this.zoneSourceX = payload.contains("zoneSourceX") ? payload.getInt("zoneSourceX") : this.zoneSourceX;
|
||||
this.zoneSourceY = payload.contains("zoneSourceY") ? payload.getInt("zoneSourceY") : this.zoneSourceY;
|
||||
int n = this.zoneSourceZ = payload.contains("zoneSourceZ") ? payload.getInt("zoneSourceZ") : this.zoneSourceZ;
|
||||
if (this.level != null && !this.level.isClientSide) {
|
||||
boolean powered = this.level.hasNeighborSignal(this.worldPosition);
|
||||
if (this.settings.activationMode == ActivationMode.REDSTONE) {
|
||||
if (oldMode != ActivationMode.REDSTONE) {
|
||||
this.redstonePowered = false;
|
||||
}
|
||||
this.onRedstoneChanged(powered);
|
||||
} else if (oldMode == ActivationMode.REDSTONE) {
|
||||
this.redstonePowered = powered;
|
||||
}
|
||||
}
|
||||
this.setChanged();
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRIGGER", this.debugPosId(), "config updated soundId={} mode={} source={} zone={}", this.settings.soundId, this.settings.activationMode.name(), this.sourceEnabled, this.zoneEnabled);
|
||||
this.sync();
|
||||
}
|
||||
|
||||
public void onRedstoneChanged(boolean powered) {
|
||||
if (!this.sourceEnabled) {
|
||||
this.redstonePowered = powered;
|
||||
return;
|
||||
}
|
||||
if (this.settings.activationMode == ActivationMode.REDSTONE) {
|
||||
if (this.settings.redstoneMode == RedstoneMode.EDGE) {
|
||||
if (powered && !this.redstonePowered) {
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "[REDSTONE] mode=EDGE event=rising action=play soundId={}", this.settings.soundId);
|
||||
this.play();
|
||||
} else if (!powered && this.redstonePowered) {
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRIGGER", this.debugPosId(), "[REDSTONE] mode=EDGE event=falling action=none", new Object[0]);
|
||||
}
|
||||
} else if (powered && !this.redstonePowered) {
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "[REDSTONE] mode=HOLD event=rising action=ensure_play soundId={}", this.settings.soundId);
|
||||
this.play();
|
||||
} else if (powered && !this.isSourceRuntimeActive()) {
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRIGGER", this.debugPosId(), "[REDSTONE] mode=HOLD event=steady_high action=rearm_play soundId={}", this.settings.soundId);
|
||||
this.play();
|
||||
} else if (!powered && this.redstonePowered && this.isSourceRuntimeActive()) {
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "[REDSTONE] mode=HOLD event=falling action=stop", new Object[0]);
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
this.redstonePowered = powered;
|
||||
}
|
||||
|
||||
public void play() {
|
||||
if (!this.sourceEnabled) {
|
||||
return;
|
||||
}
|
||||
if (this.level != null && !this.level.isClientSide) {
|
||||
long now = this.level.getGameTime();
|
||||
if (now == this.lastPlayTick) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRIGGER", this.debugPosId(), "play suppressed duplicate tick={}", now);
|
||||
return;
|
||||
}
|
||||
this.lastPlayTick = now;
|
||||
this.pendingStartTicks = Math.max(0, this.settings.startDelayTicks);
|
||||
if (!this.scenario.steps.isEmpty()) {
|
||||
this.scenarioRunning = true;
|
||||
this.scenarioStepIndex = 0;
|
||||
this.scenarioWaitTicks = this.pendingStartTicks;
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "scenario start requested steps={} startDelay={}", this.scenario.steps.size(), this.pendingStartTicks);
|
||||
} else {
|
||||
this.scenarioRunning = false;
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "play scheduled sourceId=sound_player:{} soundId={} startDelay={}", this.worldPosition.asLong(), this.settings.soundId, this.pendingStartTicks);
|
||||
}
|
||||
if (this.pendingStartTicks == 0 && !this.scenarioRunning) {
|
||||
this.emitPlayCurrentSettings();
|
||||
this.pendingStartTicks = -1;
|
||||
}
|
||||
this.setChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void playFromClick(ServerPlayerEntity player) {
|
||||
if (this.level == null || this.level.isClientSide) {
|
||||
return;
|
||||
}
|
||||
long now = this.level.getGameTime();
|
||||
String key = player.getUUID().toString() + ":" + this.worldPosition.asLong();
|
||||
Long last = MANUAL_CLICK_GUARD.get(key);
|
||||
if (last != null && now - last < 6L) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRIGGER", this.debugPosId(), "click debounce suppress player={} dt={}", player.getScoreboardName(), now - last);
|
||||
return;
|
||||
}
|
||||
MANUAL_CLICK_GUARD.put(key, now);
|
||||
this.play();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (this.level != null && !this.level.isClientSide) {
|
||||
long now = this.level.getGameTime();
|
||||
if (now == this.lastStopTick) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRIGGER", this.debugPosId(), "stop suppressed duplicate tick={}", now);
|
||||
return;
|
||||
}
|
||||
this.lastStopTick = now;
|
||||
this.playing = false;
|
||||
this.pendingStartTicks = -1;
|
||||
this.scenarioRunning = false;
|
||||
this.scenarioStepIndex = 0;
|
||||
this.scenarioWaitTicks = 0;
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRIGGER", this.debugPosId(), "stop requested sourceId=sound_player:{}", this.worldPosition.asLong());
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.TRACKING_CHUNK.with(() -> this.level.getChunkAt(this.worldPosition)), (Object)new S2CBlockStateSyncPacket(this.worldPosition, this.save(new CompoundNBT()), false));
|
||||
this.setChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return this.playing;
|
||||
}
|
||||
|
||||
private void sync() {
|
||||
if (this.level != null && !this.level.isClientSide) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.TRACKING_CHUNK.with(() -> this.level.getChunkAt(this.worldPosition)), (Object)new S2CBlockStateSyncPacket(this.worldPosition, this.save(new CompoundNBT()), false));
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.TRACKING_CHUNK.with(() -> this.level.getChunkAt(this.worldPosition)), (Object)new S2CZoneStateSyncPacket(this.worldPosition, this.createZoneSyncPayload()));
|
||||
}
|
||||
}
|
||||
|
||||
public CompoundNBT createZoneSyncPayload() {
|
||||
CompoundNBT tag = new CompoundNBT();
|
||||
tag.putBoolean("zoneEnabled", this.zoneEnabled);
|
||||
tag.putBoolean("sourceEnabled", this.sourceEnabled);
|
||||
tag.putBoolean("wizardCompleted", this.wizardCompleted);
|
||||
tag.putString("zoneShape", this.zoneShape.name());
|
||||
tag.putFloat("zoneRadius", this.zoneRadius);
|
||||
tag.putDouble("zoneBoxX", this.zoneBoxHalfExtents.x);
|
||||
tag.putDouble("zoneBoxY", this.zoneBoxHalfExtents.y);
|
||||
tag.putDouble("zoneBoxZ", this.zoneBoxHalfExtents.z);
|
||||
tag.putBoolean("zoneAutoStartOnEnter", this.zoneAutoStartOnEnter);
|
||||
tag.putBoolean("zoneStopOnExit", this.zoneStopOnExit);
|
||||
tag.putString("zoneIntersectionPolicy", this.zoneIntersectionPolicy.name());
|
||||
tag.put("zoneScenario", (INBT)this.zoneScenario.toNbt());
|
||||
tag.putBoolean("zoneUseCustomCoords", this.zoneUseCustomCoords);
|
||||
tag.putInt("zoneSourceX", this.zoneSourceX);
|
||||
tag.putInt("zoneSourceY", this.zoneSourceY);
|
||||
tag.putInt("zoneSourceZ", this.zoneSourceZ);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void loadZoneSyncPayload(CompoundNBT tag) {
|
||||
if (tag == null) {
|
||||
return;
|
||||
}
|
||||
if (tag.contains("zoneEnabled")) {
|
||||
this.zoneEnabled = tag.getBoolean("zoneEnabled");
|
||||
}
|
||||
if (tag.contains("sourceEnabled")) {
|
||||
this.sourceEnabled = tag.getBoolean("sourceEnabled");
|
||||
}
|
||||
if (tag.contains("wizardCompleted")) {
|
||||
this.wizardCompleted = tag.getBoolean("wizardCompleted");
|
||||
}
|
||||
if (tag.contains("zoneShape")) {
|
||||
this.zoneShape = this.parseZoneShape(tag.getString("zoneShape"));
|
||||
}
|
||||
if (tag.contains("zoneRadius")) {
|
||||
this.zoneRadius = this.clampFloat(tag.getFloat("zoneRadius"), 1.0f, 256.0f);
|
||||
}
|
||||
double bx = tag.contains("zoneBoxX") ? tag.getDouble("zoneBoxX") : this.zoneBoxHalfExtents.x;
|
||||
double by = tag.contains("zoneBoxY") ? tag.getDouble("zoneBoxY") : this.zoneBoxHalfExtents.y;
|
||||
double bz = tag.contains("zoneBoxZ") ? tag.getDouble("zoneBoxZ") : this.zoneBoxHalfExtents.z;
|
||||
this.zoneBoxHalfExtents = new Vector3d(this.clampDouble(bx, 0.5, 128.0), this.clampDouble(by, 0.5, 128.0), this.clampDouble(bz, 0.5, 128.0));
|
||||
if (tag.contains("zoneAutoStartOnEnter")) {
|
||||
this.zoneAutoStartOnEnter = tag.getBoolean("zoneAutoStartOnEnter");
|
||||
}
|
||||
if (tag.contains("zoneStopOnExit")) {
|
||||
this.zoneStopOnExit = tag.getBoolean("zoneStopOnExit");
|
||||
}
|
||||
if (tag.contains("zoneIntersectionPolicy")) {
|
||||
this.zoneIntersectionPolicy = this.parseZonePolicy(tag.getString("zoneIntersectionPolicy"));
|
||||
}
|
||||
if (tag.contains("zoneScenario")) {
|
||||
this.zoneScenario = ScenarioQueue.fromNbt(tag.getCompound("zoneScenario"));
|
||||
}
|
||||
if (tag.contains("zoneUseCustomCoords")) {
|
||||
this.zoneUseCustomCoords = tag.getBoolean("zoneUseCustomCoords");
|
||||
}
|
||||
if (tag.contains("zoneSourceX")) {
|
||||
this.zoneSourceX = tag.getInt("zoneSourceX");
|
||||
}
|
||||
if (tag.contains("zoneSourceY")) {
|
||||
this.zoneSourceY = tag.getInt("zoneSourceY");
|
||||
}
|
||||
if (tag.contains("zoneSourceZ")) {
|
||||
this.zoneSourceZ = tag.getInt("zoneSourceZ");
|
||||
}
|
||||
}
|
||||
|
||||
public CompoundNBT save(CompoundNBT tag) {
|
||||
super.save(tag);
|
||||
tag.put("settings", (INBT)this.settings.toNbt());
|
||||
tag.put("scenario", (INBT)this.scenario.toNbt());
|
||||
tag.putBoolean("redstonePowered", this.redstonePowered);
|
||||
tag.putInt("timerTicks", this.timerTicks);
|
||||
tag.putBoolean("playing", this.playing);
|
||||
tag.putInt("pendingStartTicks", this.pendingStartTicks);
|
||||
tag.putBoolean("scenarioRunning", this.scenarioRunning);
|
||||
tag.putInt("scenarioStepIndex", this.scenarioStepIndex);
|
||||
tag.putInt("scenarioWaitTicks", this.scenarioWaitTicks);
|
||||
tag.putBoolean("sourceEnabled", this.sourceEnabled);
|
||||
tag.putBoolean("zoneEnabled", this.zoneEnabled);
|
||||
tag.putBoolean("wizardCompleted", this.wizardCompleted);
|
||||
tag.putString("zoneShape", this.zoneShape.name());
|
||||
tag.putFloat("zoneRadius", this.zoneRadius);
|
||||
tag.putDouble("zoneBoxX", this.zoneBoxHalfExtents.x);
|
||||
tag.putDouble("zoneBoxY", this.zoneBoxHalfExtents.y);
|
||||
tag.putDouble("zoneBoxZ", this.zoneBoxHalfExtents.z);
|
||||
tag.putBoolean("zoneAutoStartOnEnter", this.zoneAutoStartOnEnter);
|
||||
tag.putBoolean("zoneStopOnExit", this.zoneStopOnExit);
|
||||
tag.putString("zoneIntersectionPolicy", this.zoneIntersectionPolicy.name());
|
||||
tag.put("zoneScenario", (INBT)this.zoneScenario.toNbt());
|
||||
tag.putBoolean("zoneUseCustomCoords", this.zoneUseCustomCoords);
|
||||
tag.putInt("zoneSourceX", this.zoneSourceX);
|
||||
tag.putInt("zoneSourceY", this.zoneSourceY);
|
||||
tag.putInt("zoneSourceZ", this.zoneSourceZ);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void load(BlockState state, CompoundNBT tag) {
|
||||
super.load(state, tag);
|
||||
this.settings = SoundSettings.fromNbt(tag.getCompound("settings"));
|
||||
this.scenario = ScenarioQueue.fromNbt(tag.getCompound("scenario"));
|
||||
this.redstonePowered = tag.getBoolean("redstonePowered");
|
||||
this.timerTicks = tag.getInt("timerTicks");
|
||||
this.playing = tag.getBoolean("playing");
|
||||
this.pendingStartTicks = tag.contains("pendingStartTicks") ? tag.getInt("pendingStartTicks") : -1;
|
||||
this.scenarioRunning = tag.getBoolean("scenarioRunning");
|
||||
this.scenarioStepIndex = tag.getInt("scenarioStepIndex");
|
||||
this.scenarioWaitTicks = tag.getInt("scenarioWaitTicks");
|
||||
this.sourceEnabled = tag.contains("sourceEnabled") ? tag.getBoolean("sourceEnabled") : true;
|
||||
this.zoneEnabled = tag.contains("zoneEnabled") ? tag.getBoolean("zoneEnabled") : true;
|
||||
this.wizardCompleted = tag.getBoolean("wizardCompleted");
|
||||
this.zoneShape = this.parseZoneShape(tag.contains("zoneShape") ? tag.getString("zoneShape") : ZoneShape.SPHERE.name());
|
||||
this.zoneRadius = this.clampFloat(tag.contains("zoneRadius") ? tag.getFloat("zoneRadius") : 12.0f, 1.0f, 256.0f);
|
||||
this.zoneBoxHalfExtents = new Vector3d(this.clampDouble(tag.contains("zoneBoxX") ? tag.getDouble("zoneBoxX") : 8.0, 0.5, 128.0), this.clampDouble(tag.contains("zoneBoxY") ? tag.getDouble("zoneBoxY") : 4.0, 0.5, 128.0), this.clampDouble(tag.contains("zoneBoxZ") ? tag.getDouble("zoneBoxZ") : 8.0, 0.5, 128.0));
|
||||
this.zoneAutoStartOnEnter = tag.contains("zoneAutoStartOnEnter") ? tag.getBoolean("zoneAutoStartOnEnter") : true;
|
||||
this.zoneStopOnExit = tag.contains("zoneStopOnExit") ? tag.getBoolean("zoneStopOnExit") : true;
|
||||
this.zoneIntersectionPolicy = this.parseZonePolicy(tag.contains("zoneIntersectionPolicy") ? tag.getString("zoneIntersectionPolicy") : ZoneIntersectionPolicy.HIGHEST_PRIORITY.name());
|
||||
this.zoneScenario = tag.contains("zoneScenario") ? ScenarioQueue.fromNbt(tag.getCompound("zoneScenario")) : new ScenarioQueue();
|
||||
this.zoneUseCustomCoords = tag.contains("zoneUseCustomCoords") && tag.getBoolean("zoneUseCustomCoords");
|
||||
this.zoneSourceX = tag.contains("zoneSourceX") ? tag.getInt("zoneSourceX") : this.worldPosition.getX();
|
||||
this.zoneSourceY = tag.contains("zoneSourceY") ? tag.getInt("zoneSourceY") : this.worldPosition.getY();
|
||||
this.zoneSourceZ = tag.contains("zoneSourceZ") ? tag.getInt("zoneSourceZ") : this.worldPosition.getZ();
|
||||
}
|
||||
|
||||
public SUpdateTileEntityPacket getUpdatePacket() {
|
||||
return new SUpdateTileEntityPacket(this.worldPosition, 0, this.getUpdateTag());
|
||||
}
|
||||
|
||||
public CompoundNBT getUpdateTag() {
|
||||
return this.save(new CompoundNBT());
|
||||
}
|
||||
|
||||
public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt) {
|
||||
this.load(this.getBlockState(), pkt.getTag());
|
||||
}
|
||||
|
||||
public ITextComponent getDisplayName() {
|
||||
return new TranslationTextComponent("screen.soundmod.sound_player");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Container createMenu(int windowId, PlayerInventory playerInventory, PlayerEntity player) {
|
||||
return new SoundPlayerContainer(windowId, this.level, this.worldPosition, playerInventory, player);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (this.level == null || this.level.isClientSide) {
|
||||
return;
|
||||
}
|
||||
if (!this.sourceEnabled) {
|
||||
if (this.playing) {
|
||||
this.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.settings.activationMode == ActivationMode.REDSTONE) {
|
||||
boolean poweredNow = this.level.hasNeighborSignal(this.worldPosition);
|
||||
if (poweredNow != this.redstonePowered) {
|
||||
this.onRedstoneChanged(poweredNow);
|
||||
} else if (this.settings.redstoneMode == RedstoneMode.HOLD && poweredNow && !this.isSourceRuntimeActive()) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRIGGER", this.debugPosId(), "[REDSTONE] mode=HOLD event=tick_guard action=ensure_play", new Object[0]);
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
if (this.settings.activationMode == ActivationMode.AUTO && !this.playing) {
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRIGGER", this.debugPosId(), "auto mode trigger", new Object[0]);
|
||||
this.play();
|
||||
}
|
||||
if (this.settings.activationMode == ActivationMode.TIMER) {
|
||||
++this.timerTicks;
|
||||
int interval = Math.max(20, this.settings.repeatDelayTicks <= 0 ? 20 : this.settings.repeatDelayTicks);
|
||||
if (this.timerTicks >= interval) {
|
||||
this.timerTicks = 0;
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRIGGER", this.debugPosId(), "timer trigger interval={}", interval);
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
if (this.pendingStartTicks >= 0 && !this.scenarioRunning) {
|
||||
if (this.pendingStartTicks == 0) {
|
||||
this.emitPlayCurrentSettings();
|
||||
this.pendingStartTicks = -1;
|
||||
this.setChanged();
|
||||
} else {
|
||||
--this.pendingStartTicks;
|
||||
}
|
||||
}
|
||||
if (this.scenarioRunning) {
|
||||
this.tickScenario();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickScenario() {
|
||||
if (this.scenario.steps.isEmpty()) {
|
||||
this.scenarioRunning = false;
|
||||
return;
|
||||
}
|
||||
if (this.scenarioWaitTicks > 0) {
|
||||
--this.scenarioWaitTicks;
|
||||
return;
|
||||
}
|
||||
if (this.scenarioStepIndex >= this.scenario.steps.size()) {
|
||||
if (this.scenario.loop) {
|
||||
this.scenarioStepIndex = 0;
|
||||
} else {
|
||||
this.scenarioRunning = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
ScenarioQueue.Step step = this.scenario.steps.get(this.scenarioStepIndex);
|
||||
++this.scenarioStepIndex;
|
||||
this.scenarioWaitTicks = Math.max(0, step.delayTicks);
|
||||
this.emitScenarioStepPlay(step.soundId);
|
||||
this.setChanged();
|
||||
}
|
||||
|
||||
private void emitPlayCurrentSettings() {
|
||||
this.playing = true;
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "play requested sourceId=sound_player:{} soundId={}", this.worldPosition.asLong(), this.settings.soundId);
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.TRACKING_CHUNK.with(() -> this.level.getChunkAt(this.worldPosition)), (Object)new S2CBlockStateSyncPacket(this.worldPosition, this.save(new CompoundNBT()), true));
|
||||
}
|
||||
|
||||
private void emitScenarioStepPlay(String soundId) {
|
||||
SoundSettings stepSettings = SoundSettings.sanitizedCopy(this.settings);
|
||||
stepSettings.soundId = soundId;
|
||||
this.playing = true;
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRIGGER", this.debugPosId(), "scenario step play sourceId=sound_player:{} stepSoundId={} nextDelay={}", this.worldPosition.asLong(), soundId, this.scenarioWaitTicks);
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.TRACKING_CHUNK.with(() -> this.level.getChunkAt(this.worldPosition)), (Object)new S2CPlaySoundPacket("sound_player:" + this.worldPosition.asLong(), this.worldPosition, stepSettings));
|
||||
}
|
||||
|
||||
private boolean isSourceRuntimeActive() {
|
||||
return this.playing || this.pendingStartTicks >= 0 || this.scenarioRunning;
|
||||
}
|
||||
|
||||
private String debugPosId() {
|
||||
return this.worldPosition.getX() + "," + this.worldPosition.getY() + "," + this.worldPosition.getZ();
|
||||
}
|
||||
|
||||
private ZoneShape parseZoneShape(String raw) {
|
||||
try {
|
||||
return ZoneShape.valueOf(raw);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return ZoneShape.SPHERE;
|
||||
}
|
||||
}
|
||||
|
||||
private ZoneIntersectionPolicy parseZonePolicy(String raw) {
|
||||
try {
|
||||
return ZoneIntersectionPolicy.valueOf(raw);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
return ZoneIntersectionPolicy.HIGHEST_PRIORITY;
|
||||
}
|
||||
}
|
||||
|
||||
private float clampFloat(float value, float min, float max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
private double clampDouble(double value, double min, double max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.util.math.AxisAlignedBB
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraft.util.math.vector.Vector3d
|
||||
* net.minecraft.world.server.ServerWorld
|
||||
* net.minecraftforge.fml.network.PacketDistributor
|
||||
*/
|
||||
package com.example.soundmod.gameplay.zone;
|
||||
|
||||
import com.example.soundmod.common.ScenarioQueue;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import com.example.soundmod.common.ZoneIntersectionPolicy;
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.S2CPlaySoundPacket;
|
||||
import com.example.soundmod.net.packet.S2CStopSoundPacket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
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 net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public final class ZoneManager {
|
||||
private static final Map<UUID, Set<Long>> ACTIVE_ZONES = new HashMap<UUID, Set<Long>>();
|
||||
private static final Map<UUID, Map<Long, ScenarioRuntime>> SCENARIO_RUNTIME = new HashMap<UUID, Map<Long, ScenarioRuntime>>();
|
||||
private static final Map<UUID, Integer> QUEUE_CURSOR = new HashMap<UUID, Integer>();
|
||||
private static final Map<UUID, Long> QUEUE_LAST_SWITCH_TICK = new HashMap<UUID, Long>();
|
||||
private static final int QUEUE_SWITCH_INTERVAL_TICKS = 40;
|
||||
|
||||
private ZoneManager() {
|
||||
}
|
||||
|
||||
public static void clearPlayer(UUID playerId) {
|
||||
ACTIVE_ZONES.remove(playerId);
|
||||
SCENARIO_RUNTIME.remove(playerId);
|
||||
QUEUE_CURSOR.remove(playerId);
|
||||
QUEUE_LAST_SWITCH_TICK.remove(playerId);
|
||||
}
|
||||
|
||||
public static void tick(ServerWorld world) {
|
||||
for (ServerPlayerEntity player : world.players()) {
|
||||
Set active = ACTIVE_ZONES.computeIfAbsent(player.getUUID(), x -> new HashSet());
|
||||
Map runtime = SCENARIO_RUNTIME.computeIfAbsent(player.getUUID(), x -> new HashMap());
|
||||
ArrayList<ZoneRef> inside = new ArrayList<ZoneRef>();
|
||||
int radiusChunks = 4;
|
||||
int playerChunkX = player.blockPosition().getX() >> 4;
|
||||
int playerChunkZ = player.blockPosition().getZ() >> 4;
|
||||
for (int cx = playerChunkX - radiusChunks; cx <= playerChunkX + radiusChunks; ++cx) {
|
||||
for (int cz = playerChunkZ - radiusChunks; cz <= playerChunkZ + radiusChunks; ++cz) {
|
||||
if (!world.getChunkSource().hasChunk(cx, cz)) continue;
|
||||
world.getChunk(cx, cz).getBlockEntities().values().forEach(te -> {
|
||||
if (!(te instanceof SoundPlayerTileEntity)) {
|
||||
return;
|
||||
}
|
||||
SoundPlayerTileEntity zone = (SoundPlayerTileEntity)((Object)te);
|
||||
if (!zone.isZoneEnabled()) {
|
||||
return;
|
||||
}
|
||||
boolean isInside = ZoneManager.isInside(zone, player.position());
|
||||
long key = zone.getBlockPos().asLong();
|
||||
if (isInside) {
|
||||
inside.add(new ZoneRef(key, zone));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Set<Long> now = ZoneManager.selectTargetZones(player.getUUID(), inside, world.getGameTime());
|
||||
HashMap<Long, ZoneRef> insideByKey = new HashMap<Long, ZoneRef>();
|
||||
for (ZoneRef ref : inside) {
|
||||
insideByKey.put(ref.key, ref);
|
||||
}
|
||||
for (ZoneRef ref : inside) {
|
||||
if (!now.contains(ref.key) || active.contains(ref.key) || !ref.zone.isZoneAutoStartOnEnter()) continue;
|
||||
ZoneManager.startZoneRuntime(player, ref, runtime);
|
||||
}
|
||||
for (Long key : now) {
|
||||
ZoneRef ref = (ZoneRef)insideByKey.get(key);
|
||||
if (ref == null) continue;
|
||||
ZoneManager.tickZoneRuntime(player, ref, runtime);
|
||||
}
|
||||
for (Object oldObj : new HashSet<>(active)) {
|
||||
Long old = (Long) oldObj;
|
||||
if (now.contains(old)) continue;
|
||||
BlockPos pos = BlockPos.of((long)old);
|
||||
SoundPlayerTileEntity zone = world.getBlockEntity(pos) instanceof SoundPlayerTileEntity ? (SoundPlayerTileEntity)world.getBlockEntity(pos) : null;
|
||||
runtime.remove(old);
|
||||
if (zone != null && !zone.isZoneStopOnExit()) continue;
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CStopSoundPacket("zone:" + old));
|
||||
}
|
||||
active.clear();
|
||||
active.addAll(now);
|
||||
}
|
||||
}
|
||||
|
||||
private static void startZoneRuntime(ServerPlayerEntity player, ZoneRef ref, Map<Long, ScenarioRuntime> runtime) {
|
||||
ScenarioQueue queue = ref.zone.getZoneScenario();
|
||||
if (queue.steps.isEmpty()) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CPlaySoundPacket("zone:" + ref.key, ref.zone.getZoneSourcePos(), ref.zone.getSettings()));
|
||||
runtime.remove(ref.key);
|
||||
return;
|
||||
}
|
||||
ScenarioRuntime sr = new ScenarioRuntime();
|
||||
sr.stepIndex = 0;
|
||||
sr.waitTicks = Math.max(0, ref.zone.getSettings().startDelayTicks);
|
||||
sr.loop = queue.loop;
|
||||
runtime.put(ref.key, sr);
|
||||
}
|
||||
|
||||
private static void tickZoneRuntime(ServerPlayerEntity player, ZoneRef ref, Map<Long, ScenarioRuntime> runtime) {
|
||||
ScenarioQueue queue = ref.zone.getZoneScenario();
|
||||
if (queue.steps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ScenarioRuntime sr = runtime.get(ref.key);
|
||||
if (sr == null) {
|
||||
return;
|
||||
}
|
||||
if (sr.waitTicks > 0) {
|
||||
--sr.waitTicks;
|
||||
return;
|
||||
}
|
||||
if (sr.stepIndex >= queue.steps.size()) {
|
||||
if (sr.loop) {
|
||||
sr.stepIndex = 0;
|
||||
} else {
|
||||
runtime.remove(ref.key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ScenarioQueue.Step step = queue.steps.get(sr.stepIndex);
|
||||
++sr.stepIndex;
|
||||
sr.waitTicks = Math.max(0, step.delayTicks);
|
||||
SoundSettings playSettings = SoundSettings.sanitizedCopy(ref.zone.getSettings());
|
||||
playSettings.soundId = step.soundId;
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CPlaySoundPacket("zone:" + ref.key, ref.zone.getZoneSourcePos(), playSettings));
|
||||
}
|
||||
|
||||
private static Set<Long> selectTargetZones(UUID playerId, List<ZoneRef> inside, long gameTime) {
|
||||
HashSet<Long> out = new HashSet<Long>();
|
||||
if (inside.isEmpty()) {
|
||||
QUEUE_CURSOR.remove(playerId);
|
||||
QUEUE_LAST_SWITCH_TICK.remove(playerId);
|
||||
return out;
|
||||
}
|
||||
boolean anyBlend = inside.stream().anyMatch(z -> z.zone.getZoneIntersectionPolicy() == ZoneIntersectionPolicy.BLEND);
|
||||
if (anyBlend) {
|
||||
for (ZoneRef z2 : inside) {
|
||||
out.add(z2.key);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
boolean anyQueue = inside.stream().anyMatch(z -> z.zone.getZoneIntersectionPolicy() == ZoneIntersectionPolicy.QUEUE);
|
||||
if (anyQueue) {
|
||||
ArrayList<ZoneRef> queueCandidates = new ArrayList<ZoneRef>();
|
||||
for (ZoneRef z3 : inside) {
|
||||
if (z3.zone.getZoneIntersectionPolicy() != ZoneIntersectionPolicy.QUEUE) continue;
|
||||
queueCandidates.add(z3);
|
||||
}
|
||||
queueCandidates.sort(Comparator.comparingLong(a -> a.key));
|
||||
if (!queueCandidates.isEmpty()) {
|
||||
int cursor = QUEUE_CURSOR.getOrDefault(playerId, 0);
|
||||
long lastSwitch = QUEUE_LAST_SWITCH_TICK.getOrDefault(playerId, Long.MIN_VALUE);
|
||||
if (gameTime - lastSwitch >= 40L) {
|
||||
++cursor;
|
||||
QUEUE_LAST_SWITCH_TICK.put(playerId, gameTime);
|
||||
}
|
||||
int normalized = Math.floorMod(cursor, queueCandidates.size());
|
||||
QUEUE_CURSOR.put(playerId, normalized);
|
||||
out.add(((ZoneRef)queueCandidates.get((int)normalized)).key);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
QUEUE_CURSOR.remove(playerId);
|
||||
QUEUE_LAST_SWITCH_TICK.remove(playerId);
|
||||
ZoneRef best = null;
|
||||
for (ZoneRef z4 : inside) {
|
||||
if (best != null && z4.zone.getSettings().priority <= best.zone.getSettings().priority && (z4.zone.getSettings().priority != best.zone.getSettings().priority || z4.key >= best.key)) continue;
|
||||
best = z4;
|
||||
}
|
||||
if (best != null) {
|
||||
out.add(best.key);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean isInside(SoundPlayerTileEntity zone, Vector3d point) {
|
||||
BlockPos center = zone.getBlockPos();
|
||||
switch (zone.getZoneShape()) {
|
||||
case BOX: {
|
||||
Vector3d ext = zone.getZoneBoxHalfExtents();
|
||||
AxisAlignedBB bb = new AxisAlignedBB((double)center.getX() + 0.5 - ext.x, (double)center.getY() + 0.5 - ext.y, (double)center.getZ() + 0.5 - ext.z, (double)center.getX() + 0.5 + ext.x, (double)center.getY() + 0.5 + ext.y, (double)center.getZ() + 0.5 + ext.z);
|
||||
return bb.contains(point);
|
||||
}
|
||||
}
|
||||
double dx = point.x - ((double)center.getX() + 0.5);
|
||||
double dy = point.y - ((double)center.getY() + 0.5);
|
||||
double dz = point.z - ((double)center.getZ() + 0.5);
|
||||
return dx * dx + dy * dy + dz * dz <= (double)(zone.getZoneRadius() * zone.getZoneRadius());
|
||||
}
|
||||
|
||||
private static class ScenarioRuntime {
|
||||
int stepIndex;
|
||||
int waitTicks;
|
||||
boolean loop;
|
||||
|
||||
private ScenarioRuntime() {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ZoneRef {
|
||||
final long key;
|
||||
final SoundPlayerTileEntity zone;
|
||||
|
||||
ZoneRef(long key, SoundPlayerTileEntity zone) {
|
||||
this.key = key;
|
||||
this.zone = zone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.net;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.C2SFileChunkAckPacket;
|
||||
import com.example.soundmod.net.packet.C2SFileTransferCompleteAckPacket;
|
||||
import com.example.soundmod.sounddata.client.ClientSoundCache;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class ClientTransferManager {
|
||||
private static final ClientTransferManager INSTANCE = new ClientTransferManager();
|
||||
private final Map<String, IncomingTransfer> transfers = new HashMap<String, IncomingTransfer>();
|
||||
|
||||
public static ClientTransferManager get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private ClientTransferManager() {
|
||||
}
|
||||
|
||||
public synchronized void begin(String transferId, String soundId, long size, String hash, int chunkSize) {
|
||||
ClientSoundCache cache = ClientSoundCache.get();
|
||||
cache.beginTransfer(soundId);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", transferId, "begin soundId={} size={} hash={}", soundId, size, hash);
|
||||
Path temp = cache.tempPath(transferId);
|
||||
try {
|
||||
Files.deleteIfExists(temp);
|
||||
OutputStream out = Files.newOutputStream(temp, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||
this.transfers.put(transferId, new IncomingTransfer(transferId, soundId, size, hash, out));
|
||||
}
|
||||
catch (IOException e) {
|
||||
cache.markError(soundId);
|
||||
SoundMod.LOGGER.error("Cannot start transfer {}", (Object)transferId, (Object)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "begin failed: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void chunk(String transferId, int chunkIndex, byte[] bytes) {
|
||||
IncomingTransfer t = this.transfers.get(transferId);
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
t.out.write(bytes);
|
||||
t.receivedBytes += (long)bytes.length;
|
||||
t.updatedMillis = System.currentTimeMillis();
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SFileChunkAckPacket(transferId, chunkIndex));
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRANSFER", transferId, "chunk index={} bytes={} total={}", chunkIndex, bytes.length, t.receivedBytes);
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.error("Chunk write failed for {}", (Object)transferId, (Object)e);
|
||||
ClientSoundCache.get().markError(t.soundId);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "chunk write failed: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void end(String transferId, boolean success, String reason) {
|
||||
IncomingTransfer t = this.transfers.remove(transferId);
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
t.out.close();
|
||||
if (!success) {
|
||||
ClientSoundCache.get().markError(t.soundId);
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SFileTransferCompleteAckPacket(transferId, false));
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "end with success=false reason={}", reason);
|
||||
return;
|
||||
}
|
||||
SoundManifestEntry e = ClientSoundCache.get().entry(t.soundId);
|
||||
if (e == null) {
|
||||
ClientSoundCache.get().markError(t.soundId);
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SFileTransferCompleteAckPacket(transferId, false));
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "manifest entry missing for sound={}", t.soundId);
|
||||
return;
|
||||
}
|
||||
ClientSoundCache.get().put(transferId, e);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", transferId, "end success bytes={} expected={}", t.receivedBytes, t.expectedBytes);
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SFileTransferCompleteAckPacket(transferId, true));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
SoundMod.LOGGER.error("Transfer finalize failed {} reason {}", (Object)transferId, (Object)reason, (Object)ex);
|
||||
ClientSoundCache.get().markError(t.soundId);
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SFileTransferCompleteAckPacket(transferId, false));
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "finalize failed: {}", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void error(String transferId, String reason) {
|
||||
IncomingTransfer t = this.transfers.remove(transferId);
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
t.out.close();
|
||||
}
|
||||
catch (IOException iOException) {
|
||||
// empty catch block
|
||||
}
|
||||
ClientSoundCache.get().markError(t.soundId);
|
||||
SoundMod.LOGGER.warn("Server transfer error transferId={} soundId={} reason={}", (Object)transferId, (Object)t.soundId, (Object)reason);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "server error reason={}", reason);
|
||||
}
|
||||
|
||||
public synchronized TransferProgress progressBySoundId(String soundId) {
|
||||
for (IncomingTransfer t : this.transfers.values()) {
|
||||
if (!t.soundId.equals(soundId)) continue;
|
||||
return new TransferProgress(t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class IncomingTransfer {
|
||||
final String transferId;
|
||||
final String soundId;
|
||||
final long expectedBytes;
|
||||
final String hash;
|
||||
final OutputStream out;
|
||||
final long startedMillis;
|
||||
long updatedMillis;
|
||||
long receivedBytes;
|
||||
|
||||
IncomingTransfer(String transferId, String soundId, long expectedBytes, String hash, OutputStream out) {
|
||||
this.updatedMillis = this.startedMillis = System.currentTimeMillis();
|
||||
this.transferId = transferId;
|
||||
this.soundId = soundId;
|
||||
this.expectedBytes = expectedBytes;
|
||||
this.hash = hash;
|
||||
this.out = out;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TransferProgress {
|
||||
public final String transferId;
|
||||
public final String soundId;
|
||||
public final long expectedBytes;
|
||||
public final long receivedBytes;
|
||||
public final long startedMillis;
|
||||
public final long updatedMillis;
|
||||
public final int percent;
|
||||
|
||||
private TransferProgress(IncomingTransfer t) {
|
||||
this.transferId = t.transferId;
|
||||
this.soundId = t.soundId;
|
||||
this.expectedBytes = t.expectedBytes;
|
||||
this.receivedBytes = t.receivedBytes;
|
||||
this.startedMillis = t.startedMillis;
|
||||
this.updatedMillis = t.updatedMillis;
|
||||
this.percent = t.expectedBytes <= 0L ? 0 : (int)Math.max(0L, Math.min(100L, t.receivedBytes * 100L / t.expectedBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.util.ResourceLocation
|
||||
* net.minecraftforge.fml.network.NetworkRegistry
|
||||
* net.minecraftforge.fml.network.simple.SimpleChannel
|
||||
*/
|
||||
package com.example.soundmod.net;
|
||||
|
||||
import com.example.soundmod.net.packet.C2SCacheReportPacket;
|
||||
import com.example.soundmod.net.packet.C2SClientDiagReportPacket;
|
||||
import com.example.soundmod.net.packet.C2SFileChunkAckPacket;
|
||||
import com.example.soundmod.net.packet.C2SFileTransferCompleteAckPacket;
|
||||
import com.example.soundmod.net.packet.C2SRequestSoundPacket;
|
||||
import com.example.soundmod.net.packet.C2SUpdateConfigPacket;
|
||||
import com.example.soundmod.net.packet.S2CBlockStateSyncPacket;
|
||||
import com.example.soundmod.net.packet.S2CClearCachePacket;
|
||||
import com.example.soundmod.net.packet.S2CClientDiagRequestPacket;
|
||||
import com.example.soundmod.net.packet.S2CDebugTogglePacket;
|
||||
import com.example.soundmod.net.packet.S2CFileChunkPacket;
|
||||
import com.example.soundmod.net.packet.S2CFileTransferBeginPacket;
|
||||
import com.example.soundmod.net.packet.S2CFileTransferEndPacket;
|
||||
import com.example.soundmod.net.packet.S2CFileTransferErrorPacket;
|
||||
import com.example.soundmod.net.packet.S2CPlaySoundPacket;
|
||||
import com.example.soundmod.net.packet.S2CSoundDebugLevelPacket;
|
||||
import com.example.soundmod.net.packet.S2CSoundManifestPacket;
|
||||
import com.example.soundmod.net.packet.S2CStopSoundPacket;
|
||||
import com.example.soundmod.net.packet.S2CZoneStateSyncPacket;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.fml.network.NetworkRegistry;
|
||||
import net.minecraftforge.fml.network.simple.SimpleChannel;
|
||||
|
||||
public final class NetworkHandler {
|
||||
private static final String PROTOCOL = "1";
|
||||
public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel((ResourceLocation)new ResourceLocation("soundmod", "main"), () -> "1", "1"::equals, "1"::equals);
|
||||
private static int nextId = 0;
|
||||
|
||||
private NetworkHandler() {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
CHANNEL.registerMessage(nextId++, S2CSoundManifestPacket.class, S2CSoundManifestPacket::encode, S2CSoundManifestPacket::decode, S2CSoundManifestPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, C2SCacheReportPacket.class, C2SCacheReportPacket::encode, C2SCacheReportPacket::decode, C2SCacheReportPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, C2SRequestSoundPacket.class, C2SRequestSoundPacket::encode, C2SRequestSoundPacket::decode, C2SRequestSoundPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CFileTransferBeginPacket.class, S2CFileTransferBeginPacket::encode, S2CFileTransferBeginPacket::decode, S2CFileTransferBeginPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CFileChunkPacket.class, S2CFileChunkPacket::encode, S2CFileChunkPacket::decode, S2CFileChunkPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, C2SFileChunkAckPacket.class, C2SFileChunkAckPacket::encode, C2SFileChunkAckPacket::decode, C2SFileChunkAckPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, C2SFileTransferCompleteAckPacket.class, C2SFileTransferCompleteAckPacket::encode, C2SFileTransferCompleteAckPacket::decode, C2SFileTransferCompleteAckPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CFileTransferEndPacket.class, S2CFileTransferEndPacket::encode, S2CFileTransferEndPacket::decode, S2CFileTransferEndPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CFileTransferErrorPacket.class, S2CFileTransferErrorPacket::encode, S2CFileTransferErrorPacket::decode, S2CFileTransferErrorPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CPlaySoundPacket.class, S2CPlaySoundPacket::encode, S2CPlaySoundPacket::decode, S2CPlaySoundPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CStopSoundPacket.class, S2CStopSoundPacket::encode, S2CStopSoundPacket::decode, S2CStopSoundPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CBlockStateSyncPacket.class, S2CBlockStateSyncPacket::encode, S2CBlockStateSyncPacket::decode, S2CBlockStateSyncPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CZoneStateSyncPacket.class, S2CZoneStateSyncPacket::encode, S2CZoneStateSyncPacket::decode, S2CZoneStateSyncPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, C2SUpdateConfigPacket.class, C2SUpdateConfigPacket::encode, C2SUpdateConfigPacket::decode, C2SUpdateConfigPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CDebugTogglePacket.class, S2CDebugTogglePacket::encode, S2CDebugTogglePacket::decode, S2CDebugTogglePacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CClearCachePacket.class, S2CClearCachePacket::encode, S2CClearCachePacket::decode, S2CClearCachePacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CSoundDebugLevelPacket.class, S2CSoundDebugLevelPacket::encode, S2CSoundDebugLevelPacket::decode, S2CSoundDebugLevelPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, S2CClientDiagRequestPacket.class, S2CClientDiagRequestPacket::encode, S2CClientDiagRequestPacket::decode, S2CClientDiagRequestPacket::handle);
|
||||
CHANNEL.registerMessage(nextId++, C2SClientDiagReportPacket.class, C2SClientDiagReportPacket::encode, C2SClientDiagReportPacket::decode, C2SClientDiagReportPacket::handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraftforge.fml.network.PacketDistributor
|
||||
*/
|
||||
package com.example.soundmod.net;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.S2CFileChunkPacket;
|
||||
import com.example.soundmod.net.packet.S2CFileTransferBeginPacket;
|
||||
import com.example.soundmod.net.packet.S2CFileTransferEndPacket;
|
||||
import com.example.soundmod.net.packet.S2CFileTransferErrorPacket;
|
||||
import com.example.soundmod.sounddata.server.ServerSoundRepository;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
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 net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public final class SoundTransferManager {
|
||||
private static final SoundTransferManager INSTANCE = new SoundTransferManager();
|
||||
private static final int MAX_NEW_TRANSFERS_PER_TICK = 2;
|
||||
private static final int MAX_CHUNKS_PER_PLAYER_TICK = 16;
|
||||
private final Map<UUID, Deque<PendingTransfer>> queue = new HashMap<UUID, Deque<PendingTransfer>>();
|
||||
private final Map<UUID, Set<String>> queuedSoundIds = new HashMap<UUID, Set<String>>();
|
||||
private final Map<UUID, Set<String>> inFlightSoundIds = new HashMap<UUID, Set<String>>();
|
||||
private final Map<UUID, Map<String, TransferSession>> active = new HashMap<UUID, Map<String, TransferSession>>();
|
||||
|
||||
public static SoundTransferManager get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private SoundTransferManager() {
|
||||
}
|
||||
|
||||
public synchronized void enqueueMissing(ServerPlayerEntity player, List<String> requestedSoundIds) {
|
||||
UUID uid = player.getUUID();
|
||||
int enqueued = 0;
|
||||
for (String soundId : requestedSoundIds) {
|
||||
if (!this.enqueue(uid, soundId, 0, false)) continue;
|
||||
++enqueued;
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", player.getScoreboardName(), "enqueueMissing requested={} accepted={} queueSize={}", requestedSoundIds.size(), enqueued, this.queueSize(uid));
|
||||
this.tickPlayer(player);
|
||||
}
|
||||
|
||||
public synchronized void enqueueSingle(ServerPlayerEntity player, String soundId) {
|
||||
boolean accepted = this.enqueue(player.getUUID(), soundId, 0, false);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", player.getScoreboardName(), "enqueueSingle soundId={} accepted={} queueSize={}", soundId, accepted, this.queueSize(player.getUUID()));
|
||||
this.tickPlayer(player);
|
||||
}
|
||||
|
||||
public synchronized void onChunkAck(ServerPlayerEntity player, String transferId, int chunkIndex) {
|
||||
Map<String, TransferSession> sessions = this.active.get(player.getUUID());
|
||||
if (sessions == null) {
|
||||
return;
|
||||
}
|
||||
TransferSession session = sessions.get(transferId);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
session.ackedChunks.add(chunkIndex);
|
||||
session.lastTouchedMillis = System.currentTimeMillis();
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRANSFER", transferId, "ack chunk={} acked={}/{}", chunkIndex, session.ackedChunks.size(), session.totalChunks);
|
||||
}
|
||||
|
||||
public synchronized void onTransferCompleteAck(ServerPlayerEntity player, String transferId, boolean success) {
|
||||
UUID uid = player.getUUID();
|
||||
Map<String, TransferSession> sessions = this.active.get(uid);
|
||||
if (sessions == null) {
|
||||
return;
|
||||
}
|
||||
TransferSession session = sessions.remove(transferId);
|
||||
if (session != null) {
|
||||
this.inFlightSoundIds.computeIfAbsent(uid, x -> new HashSet()).remove(session.soundId);
|
||||
if (success) {
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", transferId, "completeAck success by {}", player.getScoreboardName());
|
||||
} else {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", transferId, "completeAck failed by {}", player.getScoreboardName());
|
||||
}
|
||||
}
|
||||
this.tickPlayer(player);
|
||||
}
|
||||
|
||||
public synchronized void tickPlayer(ServerPlayerEntity player) {
|
||||
UUID uid = player.getUUID();
|
||||
Map sessions = this.active.computeIfAbsent(uid, x -> new HashMap());
|
||||
Deque q = this.queue.computeIfAbsent(uid, x -> new ArrayDeque());
|
||||
Set queued = this.queuedSoundIds.computeIfAbsent(uid, x -> new HashSet());
|
||||
Set inFlight = this.inFlightSoundIds.computeIfAbsent(uid, x -> new HashSet());
|
||||
this.cleanupTimedOut(player, sessions, q, queued, inFlight);
|
||||
int started = 0;
|
||||
while (sessions.size() < (Integer)ModConfig.MAX_PARALLEL_TRANSFERS.get() && started < 2 && !q.isEmpty()) {
|
||||
PendingTransfer pending = (PendingTransfer)q.pollFirst();
|
||||
if (pending == null) continue;
|
||||
queued.remove(pending.soundId);
|
||||
if (inFlight.contains(pending.soundId) || !this.startTransfer(player, pending, sessions)) continue;
|
||||
inFlight.add(pending.soundId);
|
||||
++started;
|
||||
}
|
||||
this.dispatchChunkBurst(player, sessions);
|
||||
}
|
||||
|
||||
public synchronized void clearPlayer(ServerPlayerEntity player) {
|
||||
UUID uid = player.getUUID();
|
||||
this.queue.remove(uid);
|
||||
this.queuedSoundIds.remove(uid);
|
||||
this.inFlightSoundIds.remove(uid);
|
||||
this.active.remove(uid);
|
||||
}
|
||||
|
||||
private void cleanupTimedOut(ServerPlayerEntity player, Map<String, TransferSession> sessions, Deque<PendingTransfer> q, Set<String> queued, Set<String> inFlight) {
|
||||
long now = System.currentTimeMillis();
|
||||
long timeoutMs = (long)((Integer)ModConfig.TRANSFER_TIMEOUT_SECONDS.get()).intValue() * 1000L;
|
||||
ArrayList<TransferSession> timedOut = new ArrayList<TransferSession>();
|
||||
for (TransferSession session : sessions.values()) {
|
||||
if (now - session.lastTouchedMillis <= timeoutMs) continue;
|
||||
timedOut.add(session);
|
||||
}
|
||||
for (TransferSession session : timedOut) {
|
||||
sessions.remove(session.transferId);
|
||||
inFlight.remove(session.soundId);
|
||||
int nextRetry = session.retryCount + 1;
|
||||
if (nextRetry <= (Integer)ModConfig.RETRY_LIMIT.get()) {
|
||||
this.enqueue(player.getUUID(), session.soundId, nextRetry, true);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", session.transferId, "timeout -> retry {} soundId={}", nextRetry, session.soundId);
|
||||
continue;
|
||||
}
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CFileTransferErrorPacket(session.transferId, "timeout"));
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", session.transferId, "timeout retries exhausted soundId={}", session.soundId);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchChunkBurst(ServerPlayerEntity player, Map<String, TransferSession> sessions) {
|
||||
if (sessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int chunkBudget = 16;
|
||||
for (TransferSession session : sessions.values()) {
|
||||
while (chunkBudget > 0 && session.nextChunkToSend < session.totalChunks) {
|
||||
int offset = session.nextChunkToSend * session.chunkSize;
|
||||
int end = Math.min(offset + session.chunkSize, session.data.length);
|
||||
int len = end - offset;
|
||||
byte[] chunk = new byte[len];
|
||||
System.arraycopy(session.data, offset, chunk, 0, len);
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CFileChunkPacket(session.transferId, session.nextChunkToSend, chunk));
|
||||
++session.nextChunkToSend;
|
||||
session.lastTouchedMillis = System.currentTimeMillis();
|
||||
--chunkBudget;
|
||||
SoundDebug.log(SoundDebug.Level.L3, "TRANSFER", session.transferId, "chunk send idx={} len={} sent={}/{}", session.nextChunkToSend - 1, len, session.nextChunkToSend, session.totalChunks);
|
||||
}
|
||||
if (session.nextChunkToSend >= session.totalChunks && !session.endSent) {
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CFileTransferEndPacket(session.transferId, session.soundId, true, "ok"));
|
||||
session.endSent = true;
|
||||
session.lastTouchedMillis = System.currentTimeMillis();
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", session.transferId, "send complete bytes={} chunks={}", session.data.length, session.totalChunks);
|
||||
}
|
||||
if (chunkBudget > 0) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startTransfer(ServerPlayerEntity player, PendingTransfer pending, Map<String, TransferSession> sessions) {
|
||||
String soundId = pending.soundId;
|
||||
ServerSoundRepository repo = ServerSoundRepository.get();
|
||||
Path path = repo.resolveSound(soundId);
|
||||
if (path == null || !Files.isRegularFile(path, new LinkOption[0])) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", soundId, "resolveSound missing path", new Object[0]);
|
||||
return false;
|
||||
}
|
||||
SoundManifestEntry entry = null;
|
||||
for (SoundManifestEntry e : repo.getManifest()) {
|
||||
if (!e.soundId.equals(soundId)) continue;
|
||||
entry = e;
|
||||
break;
|
||||
}
|
||||
if (entry == null || entry.flags != 0) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", soundId, "manifest entry not ready", new Object[0]);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(path);
|
||||
String transferId = UUID.randomUUID().toString();
|
||||
int chunkSize = (Integer)ModConfig.CHUNK_BYTES.get();
|
||||
TransferSession session = new TransferSession(transferId, soundId, entry.sha256, entry.size, pending.retryCount, chunkSize, data);
|
||||
sessions.put(transferId, session);
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)new S2CFileTransferBeginPacket(transferId, soundId, entry.size, entry.sha256, chunkSize));
|
||||
SoundDebug.log(SoundDebug.Level.L2, "TRANSFER", transferId, "send begin player={} soundId={} size={} chunkSize={} retry={}", player.getScoreboardName(), soundId, entry.size, chunkSize, pending.retryCount);
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.error("Failed to read sound {} for {}", (Object)soundId, (Object)player.getScoreboardName(), (Object)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "TRANSFER", soundId, "read failed: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean enqueue(UUID uid, String soundId, int retryCount, boolean front) {
|
||||
if (soundId == null || soundId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Set inFlight = this.inFlightSoundIds.computeIfAbsent(uid, x -> new HashSet());
|
||||
if (inFlight.contains(soundId)) {
|
||||
return false;
|
||||
}
|
||||
Set queued = this.queuedSoundIds.computeIfAbsent(uid, x -> new HashSet());
|
||||
if (queued.contains(soundId)) {
|
||||
return false;
|
||||
}
|
||||
Deque q = this.queue.computeIfAbsent(uid, x -> new ArrayDeque());
|
||||
PendingTransfer pending = new PendingTransfer(soundId, retryCount);
|
||||
if (front) {
|
||||
q.addFirst(pending);
|
||||
} else {
|
||||
q.addLast(pending);
|
||||
}
|
||||
queued.add(soundId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private int queueSize(UUID uid) {
|
||||
Deque<PendingTransfer> q = this.queue.get(uid);
|
||||
return q == null ? 0 : q.size();
|
||||
}
|
||||
|
||||
private static class TransferSession {
|
||||
final String transferId;
|
||||
final String soundId;
|
||||
final String hash;
|
||||
final long expectedSize;
|
||||
final int retryCount;
|
||||
final int chunkSize;
|
||||
final int totalChunks;
|
||||
final byte[] data;
|
||||
final Set<Integer> ackedChunks = new HashSet<Integer>();
|
||||
long lastTouchedMillis = System.currentTimeMillis();
|
||||
int nextChunkToSend;
|
||||
boolean endSent;
|
||||
|
||||
TransferSession(String transferId, String soundId, String hash, long expectedSize, int retryCount, int chunkSize, byte[] data) {
|
||||
this.transferId = transferId;
|
||||
this.soundId = soundId;
|
||||
this.hash = hash;
|
||||
this.expectedSize = expectedSize;
|
||||
this.retryCount = retryCount;
|
||||
this.chunkSize = chunkSize;
|
||||
this.data = data;
|
||||
this.totalChunks = Math.max(1, (int)Math.ceil((double)data.length / (double)chunkSize));
|
||||
}
|
||||
}
|
||||
|
||||
private static class PendingTransfer {
|
||||
final String soundId;
|
||||
final int retryCount;
|
||||
|
||||
PendingTransfer(String soundId, int retryCount) {
|
||||
this.soundId = soundId;
|
||||
this.retryCount = retryCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.SoundTransferManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class C2SCacheReportPacket {
|
||||
private final List<String> localHashes;
|
||||
private final List<String> missingSoundIds;
|
||||
|
||||
public C2SCacheReportPacket(List<String> localHashes, List<String> missingSoundIds) {
|
||||
this.localHashes = localHashes;
|
||||
this.missingSoundIds = missingSoundIds;
|
||||
}
|
||||
|
||||
public static void encode(C2SCacheReportPacket msg, PacketBuffer buf) {
|
||||
buf.writeVarInt(msg.localHashes.size());
|
||||
for (String hash : msg.localHashes) {
|
||||
buf.writeUtf(hash);
|
||||
}
|
||||
buf.writeVarInt(msg.missingSoundIds.size());
|
||||
for (String id : msg.missingSoundIds) {
|
||||
buf.writeUtf(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static C2SCacheReportPacket decode(PacketBuffer buf) {
|
||||
int hashSize = buf.readVarInt();
|
||||
ArrayList<String> hashes = new ArrayList<String>();
|
||||
for (int i = 0; i < hashSize; ++i) {
|
||||
hashes.add(buf.readUtf(128));
|
||||
}
|
||||
int missSize = buf.readVarInt();
|
||||
ArrayList<String> missing = new ArrayList<String>();
|
||||
for (int i = 0; i < missSize; ++i) {
|
||||
missing.add(buf.readUtf(256));
|
||||
}
|
||||
return new C2SCacheReportPacket(hashes, missing);
|
||||
}
|
||||
|
||||
public static void handle(C2SCacheReportPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayerEntity sender = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (sender != null) {
|
||||
SoundDebug.log(SoundDebug.Level.L1, "MANIFEST", sender.getScoreboardName(), "cache report localHashes={} missing={}", msg.localHashes.size(), msg.missingSoundIds.size());
|
||||
SoundTransferManager.get().enqueueMissing(sender, msg.missingSoundIds);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.command.ModCommands;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class C2SClientDiagReportPacket {
|
||||
public final String requestId;
|
||||
public final String soundId;
|
||||
public final String status;
|
||||
public final String localPath;
|
||||
public final String actualHash;
|
||||
public final long size;
|
||||
public final String note;
|
||||
|
||||
public C2SClientDiagReportPacket(String requestId, String soundId, String status, String localPath, String actualHash, long size, String note) {
|
||||
this.requestId = requestId;
|
||||
this.soundId = soundId;
|
||||
this.status = status;
|
||||
this.localPath = localPath;
|
||||
this.actualHash = actualHash;
|
||||
this.size = size;
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
public static void encode(C2SClientDiagReportPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.requestId);
|
||||
buf.writeUtf(msg.soundId);
|
||||
buf.writeUtf(msg.status);
|
||||
buf.writeUtf(msg.localPath);
|
||||
buf.writeUtf(msg.actualHash);
|
||||
buf.writeLong(msg.size);
|
||||
buf.writeUtf(msg.note);
|
||||
}
|
||||
|
||||
public static C2SClientDiagReportPacket decode(PacketBuffer buf) {
|
||||
return new C2SClientDiagReportPacket(buf.readUtf(128), buf.readUtf(256), buf.readUtf(64), buf.readUtf(Short.MAX_VALUE), buf.readUtf(128), buf.readLong(), buf.readUtf(Short.MAX_VALUE));
|
||||
}
|
||||
|
||||
public static void handle(C2SClientDiagReportPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayerEntity sender = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (sender != null) {
|
||||
ModCommands.handleClientDiagReport(sender, msg);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.SoundTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class C2SFileChunkAckPacket {
|
||||
private final String transferId;
|
||||
private final int chunkIndex;
|
||||
|
||||
public C2SFileChunkAckPacket(String transferId, int chunkIndex) {
|
||||
this.transferId = transferId;
|
||||
this.chunkIndex = chunkIndex;
|
||||
}
|
||||
|
||||
public static void encode(C2SFileChunkAckPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.transferId);
|
||||
buf.writeVarInt(msg.chunkIndex);
|
||||
}
|
||||
|
||||
public static C2SFileChunkAckPacket decode(PacketBuffer buf) {
|
||||
return new C2SFileChunkAckPacket(buf.readUtf(128), buf.readVarInt());
|
||||
}
|
||||
|
||||
public static void handle(C2SFileChunkAckPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayerEntity sender = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (sender != null) {
|
||||
SoundTransferManager.get().onChunkAck(sender, msg.transferId, msg.chunkIndex);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.SoundTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class C2SFileTransferCompleteAckPacket {
|
||||
private final String transferId;
|
||||
private final boolean success;
|
||||
|
||||
public C2SFileTransferCompleteAckPacket(String transferId, boolean success) {
|
||||
this.transferId = transferId;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public static void encode(C2SFileTransferCompleteAckPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.transferId);
|
||||
buf.writeBoolean(msg.success);
|
||||
}
|
||||
|
||||
public static C2SFileTransferCompleteAckPacket decode(PacketBuffer buf) {
|
||||
return new C2SFileTransferCompleteAckPacket(buf.readUtf(128), buf.readBoolean());
|
||||
}
|
||||
|
||||
public static void handle(C2SFileTransferCompleteAckPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayerEntity sender = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (sender != null) {
|
||||
SoundTransferManager.get().onTransferCompleteAck(sender, msg.transferId, msg.success);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.SoundTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class C2SRequestSoundPacket {
|
||||
private final String soundId;
|
||||
|
||||
public C2SRequestSoundPacket(String soundId) {
|
||||
this.soundId = soundId;
|
||||
}
|
||||
|
||||
public static void encode(C2SRequestSoundPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.soundId);
|
||||
}
|
||||
|
||||
public static C2SRequestSoundPacket decode(PacketBuffer buf) {
|
||||
return new C2SRequestSoundPacket(buf.readUtf(256));
|
||||
}
|
||||
|
||||
public static void handle(C2SRequestSoundPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayerEntity sender = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (sender != null) {
|
||||
SoundDebug.log(SoundDebug.Level.L1, "TRANSFER", sender.getScoreboardName(), "on-demand request soundId={}", msg.soundId);
|
||||
SoundTransferManager.get().enqueueSingle(sender, msg.soundId);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraft.tileentity.TileEntity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class C2SUpdateConfigPacket {
|
||||
private final BlockPos pos;
|
||||
private final CompoundNBT payload;
|
||||
|
||||
public C2SUpdateConfigPacket(BlockPos pos, CompoundNBT payload) {
|
||||
this.pos = pos;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public static void encode(C2SUpdateConfigPacket msg, PacketBuffer buf) {
|
||||
buf.writeBlockPos(msg.pos);
|
||||
buf.writeNbt(msg.payload);
|
||||
}
|
||||
|
||||
public static C2SUpdateConfigPacket decode(PacketBuffer buf) {
|
||||
return new C2SUpdateConfigPacket(buf.readBlockPos(), buf.readNbt());
|
||||
}
|
||||
|
||||
public static void handle(C2SUpdateConfigPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ServerPlayerEntity player = ((NetworkEvent.Context)ctx.get()).getSender();
|
||||
if (player == null || player.level == null) {
|
||||
return;
|
||||
}
|
||||
TileEntity te = player.level.getBlockEntity(msg.pos);
|
||||
if (te instanceof SoundPlayerTileEntity) {
|
||||
SoundPlayerTileEntity sound = (SoundPlayerTileEntity)te;
|
||||
sound.updateFromClient(msg.payload);
|
||||
if (msg.payload.getBoolean("requestPlayNow")) {
|
||||
sound.play();
|
||||
}
|
||||
if (msg.payload.getBoolean("requestStopNow")) {
|
||||
sound.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.client.Minecraft
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraft.tileentity.TileEntity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.client.audio.AudioPlaybackService;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CBlockStateSyncPacket {
|
||||
private final BlockPos pos;
|
||||
private final CompoundNBT payload;
|
||||
private final boolean triggerPlay;
|
||||
|
||||
public S2CBlockStateSyncPacket(BlockPos pos, CompoundNBT payload, boolean triggerPlay) {
|
||||
this.pos = pos;
|
||||
this.payload = payload;
|
||||
this.triggerPlay = triggerPlay;
|
||||
}
|
||||
|
||||
public static void encode(S2CBlockStateSyncPacket msg, PacketBuffer buf) {
|
||||
buf.writeBlockPos(msg.pos);
|
||||
buf.writeNbt(msg.payload);
|
||||
buf.writeBoolean(msg.triggerPlay);
|
||||
}
|
||||
|
||||
public static S2CBlockStateSyncPacket decode(PacketBuffer buf) {
|
||||
return new S2CBlockStateSyncPacket(buf.readBlockPos(), buf.readNbt(), buf.readBoolean());
|
||||
}
|
||||
|
||||
public static void handle(S2CBlockStateSyncPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || msg.payload == null) {
|
||||
return;
|
||||
}
|
||||
TileEntity te = mc.level.getBlockEntity(msg.pos);
|
||||
if (te instanceof SoundPlayerTileEntity) {
|
||||
te.load(te.getBlockState(), msg.payload);
|
||||
String sourceId = "sound_player:" + msg.pos.asLong();
|
||||
SoundPlayerTileEntity soundTe = (SoundPlayerTileEntity)te;
|
||||
if (msg.triggerPlay) {
|
||||
if (SoundDebug.enabled(SoundDebug.Level.L3)) {
|
||||
SoundSettings settings = soundTe.getSettings();
|
||||
SoundDebug.log(SoundDebug.Level.L3, "PLAYBACK", "SYNC", "block sync trigger pos={} {} {} soundId={} vol={} pitch={} radius={}", msg.pos.getX(), msg.pos.getY(), msg.pos.getZ(), settings.soundId, Float.valueOf(settings.volume), Float.valueOf(settings.pitch), Float.valueOf(settings.radius));
|
||||
}
|
||||
AudioPlaybackService.get().play(sourceId, msg.pos, soundTe.getSettings());
|
||||
} else if (!soundTe.isPlaying()) {
|
||||
if (SoundDebug.enabled(SoundDebug.Level.L3)) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "PLAYBACK", "SYNC", "block sync stop pos={} {} {} playing={}", msg.pos.getX(), msg.pos.getY(), msg.pos.getZ(), soundTe.isPlaying());
|
||||
}
|
||||
AudioPlaybackService.get().stop(sourceId);
|
||||
} else {
|
||||
if (SoundDebug.enabled(SoundDebug.Level.L3)) {
|
||||
SoundDebug.log(SoundDebug.Level.L3, "PLAYBACK", "SYNC", "block sync live-update pos={} {} {} soundId={}", msg.pos.getX(), msg.pos.getY(), msg.pos.getZ(), soundTe.getSettings().soundId);
|
||||
}
|
||||
AudioPlaybackService.get().updateLive(sourceId, msg.pos, soundTe.getSettings());
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.sounddata.client.ClientSoundCache;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CClearCachePacket {
|
||||
public static void encode(S2CClearCachePacket msg, PacketBuffer buf) {
|
||||
}
|
||||
|
||||
public static S2CClearCachePacket decode(PacketBuffer buf) {
|
||||
return new S2CClearCachePacket();
|
||||
}
|
||||
|
||||
public static void handle(S2CClearCachePacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> ClientSoundCache.get().clearAll());
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.C2SClientDiagReportPacket;
|
||||
import com.example.soundmod.sounddata.client.ClientSoundCache;
|
||||
import com.example.soundmod.util.HashUtil;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CClientDiagRequestPacket {
|
||||
public final String requestId;
|
||||
public final String soundId;
|
||||
|
||||
public S2CClientDiagRequestPacket(String requestId, String soundId) {
|
||||
this.requestId = requestId;
|
||||
this.soundId = soundId;
|
||||
}
|
||||
|
||||
public static void encode(S2CClientDiagRequestPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.requestId);
|
||||
buf.writeUtf(msg.soundId);
|
||||
}
|
||||
|
||||
public static S2CClientDiagRequestPacket decode(PacketBuffer buf) {
|
||||
return new S2CClientDiagRequestPacket(buf.readUtf(128), buf.readUtf(256));
|
||||
}
|
||||
|
||||
public static void handle(S2CClientDiagRequestPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ClientSoundCache cache = ClientSoundCache.get();
|
||||
ClientSoundCache.Status status = cache.status(msg.soundId);
|
||||
Path path = cache.resolveBySoundId(msg.soundId);
|
||||
String pathRaw = path == null ? "" : path.toAbsolutePath().toString();
|
||||
long size = -1L;
|
||||
String actualHash = "";
|
||||
String note = "ok";
|
||||
try {
|
||||
if (path != null && Files.exists(path, new LinkOption[0])) {
|
||||
size = Files.size(path);
|
||||
actualHash = HashUtil.sha256(path);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
note = "hash_or_size_error:" + e.getMessage();
|
||||
}
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SClientDiagReportPacket(msg.requestId, msg.soundId, status.name(), pathRaw, actualHash, size, note));
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.client.debug.DebugState;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CDebugTogglePacket {
|
||||
private final boolean enabled;
|
||||
|
||||
public S2CDebugTogglePacket(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public static void encode(S2CDebugTogglePacket msg, PacketBuffer buf) {
|
||||
buf.writeBoolean(msg.enabled);
|
||||
}
|
||||
|
||||
public static S2CDebugTogglePacket decode(PacketBuffer buf) {
|
||||
return new S2CDebugTogglePacket(buf.readBoolean());
|
||||
}
|
||||
|
||||
public static void handle(S2CDebugTogglePacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> DebugState.setEnabled(msg.enabled));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.ClientTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CFileChunkPacket {
|
||||
private final String transferId;
|
||||
private final int chunkIndex;
|
||||
private final byte[] bytes;
|
||||
|
||||
public S2CFileChunkPacket(String transferId, int chunkIndex, byte[] bytes) {
|
||||
this.transferId = transferId;
|
||||
this.chunkIndex = chunkIndex;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public static void encode(S2CFileChunkPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.transferId);
|
||||
buf.writeVarInt(msg.chunkIndex);
|
||||
buf.writeVarInt(msg.bytes.length);
|
||||
buf.writeByteArray(msg.bytes);
|
||||
}
|
||||
|
||||
public static S2CFileChunkPacket decode(PacketBuffer buf) {
|
||||
String transferId = buf.readUtf(128);
|
||||
int chunkIndex = buf.readVarInt();
|
||||
int size = buf.readVarInt();
|
||||
byte[] bytes = buf.readByteArray(size);
|
||||
return new S2CFileChunkPacket(transferId, chunkIndex, bytes);
|
||||
}
|
||||
|
||||
public static void handle(S2CFileChunkPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> ClientTransferManager.get().chunk(msg.transferId, msg.chunkIndex, msg.bytes));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.ClientTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CFileTransferBeginPacket {
|
||||
private final String transferId;
|
||||
private final String soundId;
|
||||
private final long size;
|
||||
private final String hash;
|
||||
private final int chunkSize;
|
||||
|
||||
public S2CFileTransferBeginPacket(String transferId, String soundId, long size, String hash, int chunkSize) {
|
||||
this.transferId = transferId;
|
||||
this.soundId = soundId;
|
||||
this.size = size;
|
||||
this.hash = hash;
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public static void encode(S2CFileTransferBeginPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.transferId);
|
||||
buf.writeUtf(msg.soundId);
|
||||
buf.writeLong(msg.size);
|
||||
buf.writeUtf(msg.hash);
|
||||
buf.writeVarInt(msg.chunkSize);
|
||||
}
|
||||
|
||||
public static S2CFileTransferBeginPacket decode(PacketBuffer buf) {
|
||||
return new S2CFileTransferBeginPacket(buf.readUtf(128), buf.readUtf(256), buf.readLong(), buf.readUtf(128), buf.readVarInt());
|
||||
}
|
||||
|
||||
public static void handle(S2CFileTransferBeginPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> ClientTransferManager.get().begin(msg.transferId, msg.soundId, msg.size, msg.hash, msg.chunkSize));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.ClientTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CFileTransferEndPacket {
|
||||
private final String transferId;
|
||||
private final String soundId;
|
||||
private final boolean success;
|
||||
private final String reason;
|
||||
|
||||
public S2CFileTransferEndPacket(String transferId, String soundId, boolean success, String reason) {
|
||||
this.transferId = transferId;
|
||||
this.soundId = soundId;
|
||||
this.success = success;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public static void encode(S2CFileTransferEndPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.transferId);
|
||||
buf.writeUtf(msg.soundId);
|
||||
buf.writeBoolean(msg.success);
|
||||
buf.writeUtf(msg.reason);
|
||||
}
|
||||
|
||||
public static S2CFileTransferEndPacket decode(PacketBuffer buf) {
|
||||
return new S2CFileTransferEndPacket(buf.readUtf(128), buf.readUtf(256), buf.readBoolean(), buf.readUtf(256));
|
||||
}
|
||||
|
||||
public static void handle(S2CFileTransferEndPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> ClientTransferManager.get().end(msg.transferId, msg.success, msg.reason));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.net.ClientTransferManager;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CFileTransferErrorPacket {
|
||||
private final String transferId;
|
||||
private final String reason;
|
||||
|
||||
public S2CFileTransferErrorPacket(String transferId, String reason) {
|
||||
this.transferId = transferId;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public static void encode(S2CFileTransferErrorPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.transferId);
|
||||
buf.writeUtf(msg.reason);
|
||||
}
|
||||
|
||||
public static S2CFileTransferErrorPacket decode(PacketBuffer buf) {
|
||||
return new S2CFileTransferErrorPacket(buf.readUtf(128), buf.readUtf(256));
|
||||
}
|
||||
|
||||
public static void handle(S2CFileTransferErrorPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> ClientTransferManager.get().error(msg.transferId, msg.reason));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.client.audio.AudioPlaybackService;
|
||||
import com.example.soundmod.common.SoundSettings;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CPlaySoundPacket {
|
||||
private final String sourceId;
|
||||
private final BlockPos pos;
|
||||
private final SoundSettings settings;
|
||||
|
||||
public S2CPlaySoundPacket(String sourceId, BlockPos pos, SoundSettings settings) {
|
||||
this.sourceId = sourceId;
|
||||
this.pos = pos;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public static void encode(S2CPlaySoundPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.sourceId);
|
||||
buf.writeBlockPos(msg.pos);
|
||||
buf.writeNbt(msg.settings.toNbt());
|
||||
}
|
||||
|
||||
public static S2CPlaySoundPacket decode(PacketBuffer buf) {
|
||||
String sourceId = buf.readUtf(256);
|
||||
BlockPos pos = buf.readBlockPos();
|
||||
CompoundNBT tag = buf.readNbt();
|
||||
return new S2CPlaySoundPacket(sourceId, pos, SoundSettings.fromNbt(tag == null ? new CompoundNBT() : tag));
|
||||
}
|
||||
|
||||
public static void handle(S2CPlaySoundPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> AudioPlaybackService.get().play(msg.sourceId, msg.pos, msg.settings));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CSoundDebugLevelPacket {
|
||||
public final int levelCode;
|
||||
|
||||
public S2CSoundDebugLevelPacket(int levelCode) {
|
||||
this.levelCode = levelCode;
|
||||
}
|
||||
|
||||
public static void encode(S2CSoundDebugLevelPacket msg, PacketBuffer buf) {
|
||||
buf.writeVarInt(msg.levelCode);
|
||||
}
|
||||
|
||||
public static S2CSoundDebugLevelPacket decode(PacketBuffer buf) {
|
||||
return new S2CSoundDebugLevelPacket(buf.readVarInt());
|
||||
}
|
||||
|
||||
public static void handle(S2CSoundDebugLevelPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> SoundDebug.setLevelCode(msg.levelCode));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.C2SCacheReportPacket;
|
||||
import com.example.soundmod.sounddata.client.ClientSoundCache;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CSoundManifestPacket {
|
||||
private final int version;
|
||||
private final List<SoundManifestEntry> entries;
|
||||
|
||||
public S2CSoundManifestPacket(int version, List<SoundManifestEntry> entries) {
|
||||
this.version = version;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
public static void encode(S2CSoundManifestPacket msg, PacketBuffer buf) {
|
||||
buf.writeVarInt(msg.version);
|
||||
buf.writeVarInt(msg.entries.size());
|
||||
for (SoundManifestEntry entry : msg.entries) {
|
||||
SoundManifestEntry.write(buf, entry);
|
||||
}
|
||||
}
|
||||
|
||||
public static S2CSoundManifestPacket decode(PacketBuffer buf) {
|
||||
int version = buf.readVarInt();
|
||||
int size = buf.readVarInt();
|
||||
ArrayList<SoundManifestEntry> list = new ArrayList<SoundManifestEntry>();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
list.add(SoundManifestEntry.read(buf));
|
||||
}
|
||||
return new S2CSoundManifestPacket(version, list);
|
||||
}
|
||||
|
||||
public static void handle(S2CSoundManifestPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ClientSoundCache cache = ClientSoundCache.get();
|
||||
cache.applyManifest(msg.entries);
|
||||
SoundDebug.log(SoundDebug.Level.L1, "MANIFEST", "CLIENT", "received version={} entries={}", msg.version, msg.entries.size());
|
||||
HashSet<String> local = new HashSet<String>(cache.localHashes());
|
||||
ArrayList<String> missing = new ArrayList<String>();
|
||||
for (SoundManifestEntry entry : msg.entries) {
|
||||
if (entry.flags != 0 || local.contains(entry.sha256)) continue;
|
||||
missing.add(entry.soundId);
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L2, "MANIFEST", "CLIENT", "cache report localHashes={} missing={}", local.size(), missing.size());
|
||||
NetworkHandler.CHANNEL.sendToServer((Object)new C2SCacheReportPacket(new ArrayList<String>(local), missing));
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.client.audio.AudioPlaybackService;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CStopSoundPacket {
|
||||
private final String sourceId;
|
||||
|
||||
public S2CStopSoundPacket(String sourceId) {
|
||||
this.sourceId = sourceId;
|
||||
}
|
||||
|
||||
public static void encode(S2CStopSoundPacket msg, PacketBuffer buf) {
|
||||
buf.writeUtf(msg.sourceId);
|
||||
}
|
||||
|
||||
public static S2CStopSoundPacket decode(PacketBuffer buf) {
|
||||
return new S2CStopSoundPacket(buf.readUtf(256));
|
||||
}
|
||||
|
||||
public static void handle(S2CStopSoundPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> AudioPlaybackService.get().stop(msg.sourceId));
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.client.Minecraft
|
||||
* net.minecraft.nbt.CompoundNBT
|
||||
* net.minecraft.network.PacketBuffer
|
||||
* net.minecraft.tileentity.TileEntity
|
||||
* net.minecraft.util.math.BlockPos
|
||||
* net.minecraftforge.fml.network.NetworkEvent$Context
|
||||
*/
|
||||
package com.example.soundmod.net.packet;
|
||||
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
public class S2CZoneStateSyncPacket {
|
||||
private final BlockPos pos;
|
||||
private final CompoundNBT payload;
|
||||
|
||||
public S2CZoneStateSyncPacket(BlockPos pos, CompoundNBT payload) {
|
||||
this.pos = pos;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public static void encode(S2CZoneStateSyncPacket msg, PacketBuffer buf) {
|
||||
buf.writeBlockPos(msg.pos);
|
||||
buf.writeNbt(msg.payload);
|
||||
}
|
||||
|
||||
public static S2CZoneStateSyncPacket decode(PacketBuffer buf) {
|
||||
return new S2CZoneStateSyncPacket(buf.readBlockPos(), buf.readNbt());
|
||||
}
|
||||
|
||||
public static void handle(S2CZoneStateSyncPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) {
|
||||
return;
|
||||
}
|
||||
TileEntity te = mc.level.getBlockEntity(msg.pos);
|
||||
if (te instanceof SoundPlayerTileEntity) {
|
||||
((SoundPlayerTileEntity)te).loadZoneSyncPayload(msg.payload);
|
||||
}
|
||||
});
|
||||
ctx.get().setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.block.AbstractBlock$Properties
|
||||
* net.minecraft.block.Block
|
||||
* net.minecraft.block.material.Material
|
||||
* net.minecraftforge.fml.RegistryObject
|
||||
* net.minecraftforge.registries.DeferredRegister
|
||||
* net.minecraftforge.registries.ForgeRegistries
|
||||
* net.minecraftforge.registries.IForgeRegistry
|
||||
*/
|
||||
package com.example.soundmod.registry;
|
||||
|
||||
import com.example.soundmod.gameplay.block.SoundPlayerBlock;
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraftforge.fml.RegistryObject;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
|
||||
public final class ModBlocks {
|
||||
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create((IForgeRegistry)ForgeRegistries.BLOCKS, (String)"soundmod");
|
||||
public static final RegistryObject<Block> SOUND_PLAYER = BLOCKS.register("sound_player", () -> new SoundPlayerBlock(AbstractBlock.Properties.of((Material)Material.METAL).strength(2.0f)));
|
||||
|
||||
private ModBlocks() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.inventory.container.ContainerType
|
||||
* net.minecraftforge.common.extensions.IForgeContainerType
|
||||
* net.minecraftforge.fml.RegistryObject
|
||||
* net.minecraftforge.registries.DeferredRegister
|
||||
* net.minecraftforge.registries.ForgeRegistries
|
||||
* net.minecraftforge.registries.IForgeRegistry
|
||||
*/
|
||||
package com.example.soundmod.registry;
|
||||
|
||||
import com.example.soundmod.gameplay.container.SoundPlayerContainer;
|
||||
import net.minecraft.inventory.container.ContainerType;
|
||||
import net.minecraftforge.common.extensions.IForgeContainerType;
|
||||
import net.minecraftforge.fml.RegistryObject;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
|
||||
public final class ModContainers {
|
||||
public static final DeferredRegister<ContainerType<?>> CONTAINERS = DeferredRegister.create((IForgeRegistry)ForgeRegistries.CONTAINERS, (String)"soundmod");
|
||||
public static final RegistryObject<ContainerType<SoundPlayerContainer>> SOUND_PLAYER = CONTAINERS.register("sound_player", () -> IForgeContainerType.create(SoundPlayerContainer::new));
|
||||
|
||||
private ModContainers() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.block.Block
|
||||
* net.minecraft.item.BlockItem
|
||||
* net.minecraft.item.Item
|
||||
* net.minecraft.item.Item$Properties
|
||||
* net.minecraft.item.ItemGroup
|
||||
* net.minecraftforge.fml.RegistryObject
|
||||
* net.minecraftforge.registries.DeferredRegister
|
||||
* net.minecraftforge.registries.ForgeRegistries
|
||||
* net.minecraftforge.registries.IForgeRegistry
|
||||
*/
|
||||
package com.example.soundmod.registry;
|
||||
|
||||
import com.example.soundmod.registry.ModBlocks;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraftforge.fml.RegistryObject;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
|
||||
public final class ModItems {
|
||||
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create((IForgeRegistry)ForgeRegistries.ITEMS, (String)"soundmod");
|
||||
public static final RegistryObject<Item> SOUND_PLAYER_ITEM = ITEMS.register("sound_player", () -> new BlockItem((Block)ModBlocks.SOUND_PLAYER.get(), new Item.Properties().tab(ItemGroup.TAB_REDSTONE)));
|
||||
|
||||
private ModItems() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.block.Block
|
||||
* net.minecraft.tileentity.TileEntityType
|
||||
* net.minecraft.tileentity.TileEntityType$Builder
|
||||
* net.minecraftforge.fml.RegistryObject
|
||||
* net.minecraftforge.registries.DeferredRegister
|
||||
* net.minecraftforge.registries.ForgeRegistries
|
||||
* net.minecraftforge.registries.IForgeRegistry
|
||||
*/
|
||||
package com.example.soundmod.registry;
|
||||
|
||||
import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity;
|
||||
import com.example.soundmod.registry.ModBlocks;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.tileentity.TileEntityType;
|
||||
import net.minecraftforge.fml.RegistryObject;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
|
||||
public final class ModTileEntities {
|
||||
public static final DeferredRegister<TileEntityType<?>> TILES = DeferredRegister.create((IForgeRegistry)ForgeRegistries.TILE_ENTITIES, (String)"soundmod");
|
||||
public static final RegistryObject<TileEntityType<SoundPlayerTileEntity>> SOUND_PLAYER_TILE = TILES.register("sound_player", () -> TileEntityType.Builder.of(SoundPlayerTileEntity::new, (Block[])new Block[]{(Block)ModBlocks.SOUND_PLAYER.get()}).build(null));
|
||||
|
||||
private ModTileEntities() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraftforge.fml.loading.FMLPaths
|
||||
*/
|
||||
package com.example.soundmod.sounddata.client;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.util.HashUtil;
|
||||
import com.example.soundmod.util.PathSafety;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
|
||||
public final class ClientSoundCache {
|
||||
private static final ClientSoundCache INSTANCE = new ClientSoundCache();
|
||||
private final Path cacheRoot;
|
||||
private final Path tempRoot;
|
||||
private final Map<String, SoundManifestEntry> manifest = new HashMap<String, SoundManifestEntry>();
|
||||
private final Map<String, Status> statuses = new HashMap<String, Status>();
|
||||
|
||||
public static ClientSoundCache get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private ClientSoundCache() {
|
||||
this.cacheRoot = FMLPaths.GAMEDIR.get().resolve("soundmod-cache");
|
||||
this.tempRoot = this.cacheRoot.resolve("incoming");
|
||||
try {
|
||||
Files.createDirectories(this.cacheRoot, new FileAttribute[0]);
|
||||
Files.createDirectories(this.tempRoot, new FileAttribute[0]);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Cannot initialize client sound cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void applyManifest(List<SoundManifestEntry> entries) {
|
||||
this.manifest.clear();
|
||||
int available = 0;
|
||||
int missing = 0;
|
||||
int unsupported = 0;
|
||||
for (SoundManifestEntry e : entries) {
|
||||
this.manifest.put(e.soundId, e);
|
||||
if (e.flags == 1) {
|
||||
this.statuses.put(e.soundId, Status.UNSUPPORTED);
|
||||
++unsupported;
|
||||
continue;
|
||||
}
|
||||
if (this.has(e.sha256)) {
|
||||
this.statuses.put(e.soundId, Status.AVAILABLE);
|
||||
++available;
|
||||
continue;
|
||||
}
|
||||
this.statuses.put(e.soundId, Status.MISSING);
|
||||
++missing;
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L1, "CACHE", "CLIENT", "manifest applied entries={} available={} missing={} unsupported={}", entries.size(), available, missing, unsupported);
|
||||
this.evictIfNeeded();
|
||||
}
|
||||
|
||||
private static String extFor(SoundManifestEntry entry) {
|
||||
if (entry != null && entry.relativePath != null) {
|
||||
int dot = entry.relativePath.lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
return entry.relativePath.substring(dot).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
return ".ogg";
|
||||
}
|
||||
|
||||
private static boolean isAudioFile(String name) {
|
||||
String n = name.toLowerCase(Locale.ROOT);
|
||||
return n.endsWith(".ogg") || n.endsWith(".wav") || n.endsWith(".mp3");
|
||||
}
|
||||
|
||||
private static String stripExtension(String name) {
|
||||
int dot = name.lastIndexOf('.');
|
||||
return dot > 0 ? name.substring(0, dot) : name;
|
||||
}
|
||||
|
||||
public synchronized boolean has(String hash) {
|
||||
if (hash == null || hash.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (String ext : new String[]{".ogg", ".wav", ".mp3"}) {
|
||||
if (Files.isRegularFile(this.cacheRoot.resolve(hash + ext), new LinkOption[0])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized Path resolveBySoundId(String soundId) {
|
||||
SoundManifestEntry e = this.manifest.get(soundId);
|
||||
if (e == null || e.sha256.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Path path = this.cacheRoot.resolve(e.sha256 + extFor(e));
|
||||
if (Files.isRegularFile(path, new LinkOption[0])) {
|
||||
return path;
|
||||
}
|
||||
for (String alt : new String[]{".ogg", ".wav", ".mp3"}) {
|
||||
Path p = this.cacheRoot.resolve(e.sha256 + alt);
|
||||
if (Files.isRegularFile(p, new LinkOption[0])) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized Path tempPath(String transferId) {
|
||||
return PathSafety.normalizeInside(this.tempRoot, this.tempRoot.resolve(transferId + ".part"));
|
||||
}
|
||||
|
||||
public synchronized void put(String transferId, SoundManifestEntry entry) throws IOException {
|
||||
Path temp = this.tempPath(transferId);
|
||||
if (!Files.exists(temp, new LinkOption[0])) {
|
||||
throw new IOException("temp file missing for transfer " + transferId);
|
||||
}
|
||||
String actualHash = HashUtil.sha256(temp);
|
||||
if (!actualHash.equals(entry.sha256)) {
|
||||
Files.deleteIfExists(temp);
|
||||
this.statuses.put(entry.soundId, Status.ERROR);
|
||||
throw new IOException("hash mismatch for " + entry.soundId);
|
||||
}
|
||||
Path finalPath = this.cacheRoot.resolve(entry.sha256 + extFor(entry));
|
||||
try {
|
||||
Files.move(temp, finalPath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch (AtomicMoveNotSupportedException ex) {
|
||||
Files.move(temp, finalPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
this.statuses.put(entry.soundId, Status.AVAILABLE);
|
||||
SoundDebug.log(SoundDebug.Level.L1, "CACHE", entry.soundId, "put done transferId={} final={} hash={}", transferId, finalPath, entry.sha256);
|
||||
this.evictIfNeeded();
|
||||
}
|
||||
|
||||
public synchronized void beginTransfer(String soundId) {
|
||||
this.statuses.put(soundId, Status.TRANSFERRING);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "CACHE", soundId, "status=TRANSFERRING", new Object[0]);
|
||||
}
|
||||
|
||||
public synchronized void markMissing(String soundId) {
|
||||
this.statuses.put(soundId, Status.MISSING);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "CACHE", soundId, "status=MISSING", new Object[0]);
|
||||
}
|
||||
|
||||
public synchronized void markError(String soundId) {
|
||||
this.statuses.put(soundId, Status.ERROR);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "CACHE", soundId, "status=ERROR", new Object[0]);
|
||||
}
|
||||
|
||||
public synchronized Status status(String soundId) {
|
||||
return this.statuses.getOrDefault(soundId, Status.MISSING);
|
||||
}
|
||||
|
||||
public synchronized List<String> localHashes() {
|
||||
try {
|
||||
if (!Files.isDirectory(this.cacheRoot, new LinkOption[0])) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Files.list(this.cacheRoot).filter(x$0 -> Files.isRegularFile(x$0, new LinkOption[0])).map(path -> path.getFileName().toString()).filter(ClientSoundCache::isAudioFile).map(ClientSoundCache::stripExtension).collect(Collectors.toList());
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.warn("Failed to list local hashes", (Throwable)e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized SoundManifestEntry entry(String soundId) {
|
||||
return this.manifest.get(soundId);
|
||||
}
|
||||
|
||||
public synchronized List<SoundManifestEntry> entries() {
|
||||
ArrayList<SoundManifestEntry> out = new ArrayList<SoundManifestEntry>(this.manifest.values());
|
||||
out.sort(Comparator.comparing(entry -> entry.soundId));
|
||||
return out;
|
||||
}
|
||||
|
||||
public synchronized void evictIfNeeded() {
|
||||
long maxBytes = (long)((Integer)ModConfig.CACHE_MAX_MB.get()).intValue() * 1024L * 1024L;
|
||||
try {
|
||||
List<Path> files = Files.list(this.cacheRoot).filter(x$0 -> Files.isRegularFile(x$0, new LinkOption[0])).filter(path -> ClientSoundCache.isAudioFile(path.getFileName().toString())).sorted(Comparator.comparingLong(this::safeMtime)).collect(Collectors.toList());
|
||||
long total = 0L;
|
||||
for (Path file : files) {
|
||||
total += Files.size(file);
|
||||
}
|
||||
int idx = 0;
|
||||
while (total > maxBytes && idx < files.size()) {
|
||||
Path victim = files.get(idx++);
|
||||
long bytes = Files.size(victim);
|
||||
Files.deleteIfExists(victim);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "CACHE", "EVICT", "deleted={} bytes={} totalNow={}", victim.getFileName(), bytes, total -= bytes);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.warn("Cache eviction failed", (Throwable)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "CACHE", "EVICT", "eviction failed: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private long safeMtime(Path path) {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path, new LinkOption[0]).toMillis();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void clearAll() {
|
||||
try {
|
||||
Files.list(this.cacheRoot).filter(x$0 -> Files.isRegularFile(x$0, new LinkOption[0])).forEach(file -> {
|
||||
try {
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.warn("Failed to delete {}", file);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.warn("Failed to clear cache", (Throwable)e);
|
||||
}
|
||||
this.statuses.replaceAll((k, v) -> Status.MISSING);
|
||||
}
|
||||
|
||||
public static enum Status {
|
||||
AVAILABLE,
|
||||
TRANSFERRING,
|
||||
MISSING,
|
||||
ERROR,
|
||||
UNSUPPORTED;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*
|
||||
* Could not load the following classes:
|
||||
* net.minecraft.entity.player.ServerPlayerEntity
|
||||
* net.minecraft.server.MinecraftServer
|
||||
* net.minecraft.world.storage.FolderName
|
||||
* net.minecraftforge.fml.network.PacketDistributor
|
||||
*/
|
||||
package com.example.soundmod.sounddata.server;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.net.NetworkHandler;
|
||||
import com.example.soundmod.net.packet.S2CSoundManifestPacket;
|
||||
import com.example.soundmod.sounddata.server.WorldSoundIndex;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.storage.FolderName;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
public final class ServerSoundRepository {
|
||||
private static final ServerSoundRepository INSTANCE = new ServerSoundRepository();
|
||||
private MinecraftServer server;
|
||||
private WorldSoundIndex index;
|
||||
private final ConcurrentMap<UUID, Integer> playerManifestVersions = new ConcurrentHashMap<UUID, Integer>();
|
||||
private int manifestVersion;
|
||||
private long nextAutoReindexMillis;
|
||||
private long lastFingerprint;
|
||||
private long pendingFingerprint;
|
||||
private boolean reindexPending;
|
||||
|
||||
public static ServerSoundRepository get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private ServerSoundRepository() {
|
||||
}
|
||||
|
||||
public synchronized void bindServer(MinecraftServer server) {
|
||||
this.server = server;
|
||||
Path worldRoot = server.getWorldPath(FolderName.ROOT).toAbsolutePath().normalize();
|
||||
this.index = new WorldSoundIndex(worldRoot);
|
||||
this.reload();
|
||||
this.pendingFingerprint = this.lastFingerprint = this.computeFingerprintSafe();
|
||||
this.reindexPending = false;
|
||||
this.nextAutoReindexMillis = System.currentTimeMillis() + Math.max(1000L, (long)((Integer)ModConfig.AUTO_REINDEX_SECONDS.get()).intValue() * 1000L);
|
||||
SoundMod.LOGGER.info("Server sound repository bound to {}", (Object)worldRoot);
|
||||
}
|
||||
|
||||
public synchronized void unbindServer() {
|
||||
this.server = null;
|
||||
this.index = null;
|
||||
this.manifestVersion = 0;
|
||||
this.nextAutoReindexMillis = 0L;
|
||||
this.lastFingerprint = 0L;
|
||||
this.pendingFingerprint = 0L;
|
||||
this.reindexPending = false;
|
||||
this.playerManifestVersions.clear();
|
||||
}
|
||||
|
||||
public synchronized void reload() {
|
||||
if (this.index == null) {
|
||||
return;
|
||||
}
|
||||
HashMap<String, String> before = new HashMap<String, String>();
|
||||
for (SoundManifestEntry e : this.index.getManifest()) {
|
||||
before.put(e.soundId, e.sha256);
|
||||
}
|
||||
this.index.scanWorldFolder();
|
||||
++this.manifestVersion;
|
||||
int added = 0;
|
||||
int updated = 0;
|
||||
int removed = 0;
|
||||
HashMap<String, String> after = new HashMap<String, String>();
|
||||
for (SoundManifestEntry e : this.index.getManifest()) {
|
||||
after.put(e.soundId, e.sha256);
|
||||
if (!before.containsKey(e.soundId)) {
|
||||
++added;
|
||||
continue;
|
||||
}
|
||||
if (((String)before.get(e.soundId)).equals(e.sha256)) continue;
|
||||
++updated;
|
||||
}
|
||||
for (String oldId : before.keySet()) {
|
||||
if (after.containsKey(oldId)) continue;
|
||||
++removed;
|
||||
}
|
||||
SoundDebug.log(SoundDebug.Level.L1, "MANIFEST", "SERVER", "reload version={} total={} added={} updated={} removed={}", this.manifestVersion, after.size(), added, updated, removed);
|
||||
}
|
||||
|
||||
public synchronized void tick() {
|
||||
if (this.index == null) {
|
||||
return;
|
||||
}
|
||||
int intervalSec = (Integer)ModConfig.AUTO_REINDEX_SECONDS.get();
|
||||
if (intervalSec <= 0) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
if (now < this.nextAutoReindexMillis) {
|
||||
return;
|
||||
}
|
||||
this.nextAutoReindexMillis = now + (long)intervalSec * 1000L;
|
||||
long fingerprint = this.computeFingerprintSafe();
|
||||
if (fingerprint == this.lastFingerprint) {
|
||||
this.reindexPending = false;
|
||||
this.pendingFingerprint = fingerprint;
|
||||
return;
|
||||
}
|
||||
if (!this.reindexPending) {
|
||||
this.reindexPending = true;
|
||||
this.pendingFingerprint = fingerprint;
|
||||
SoundDebug.log(SoundDebug.Level.L2, "INDEX", "WORLD", "change detected, waiting settle fingerprint={}", fingerprint);
|
||||
return;
|
||||
}
|
||||
if (fingerprint != this.pendingFingerprint) {
|
||||
this.pendingFingerprint = fingerprint;
|
||||
SoundDebug.log(SoundDebug.Level.L3, "INDEX", "WORLD", "change still in progress fingerprint={}", fingerprint);
|
||||
return;
|
||||
}
|
||||
this.reindexPending = false;
|
||||
this.lastFingerprint = fingerprint;
|
||||
this.reload();
|
||||
this.sendManifestToAll();
|
||||
SoundDebug.log(SoundDebug.Level.L1, "INDEX", "WORLD", "auto reindex applied fingerprint={}", fingerprint);
|
||||
}
|
||||
|
||||
public synchronized List<SoundManifestEntry> getManifest() {
|
||||
return this.index == null ? Collections.emptyList() : this.index.getManifest();
|
||||
}
|
||||
|
||||
public synchronized int getManifestVersion() {
|
||||
return this.manifestVersion;
|
||||
}
|
||||
|
||||
public synchronized Path resolveSound(String soundId) {
|
||||
return this.index == null ? null : this.index.resolveSound(soundId);
|
||||
}
|
||||
|
||||
public synchronized void sendManifestToAll() {
|
||||
if (this.server == null) {
|
||||
return;
|
||||
}
|
||||
S2CSoundManifestPacket packet = new S2CSoundManifestPacket(this.manifestVersion, this.getManifest());
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), (Object)packet);
|
||||
}
|
||||
|
||||
public synchronized void sendManifestTo(ServerPlayerEntity player) {
|
||||
S2CSoundManifestPacket packet = new S2CSoundManifestPacket(this.manifestVersion, this.getManifest());
|
||||
NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), (Object)packet);
|
||||
this.playerManifestVersions.put(player.getUUID(), this.manifestVersion);
|
||||
SoundDebug.log(SoundDebug.Level.L2, "MANIFEST", player.getScoreboardName(), "sent version={} entries={}", this.manifestVersion, this.getManifest().size());
|
||||
}
|
||||
|
||||
/*
|
||||
* WARNING - Removed try catching itself - possible behaviour change.
|
||||
*/
|
||||
public boolean isAvailable(String soundId) {
|
||||
ServerSoundRepository serverSoundRepository = this;
|
||||
synchronized (serverSoundRepository) {
|
||||
Path p = this.resolveSound(soundId);
|
||||
return p != null;
|
||||
}
|
||||
}
|
||||
|
||||
private long computeFingerprintSafe() {
|
||||
if (this.index == null) {
|
||||
return 0L;
|
||||
}
|
||||
long fingerprint = 1125899906842597L;
|
||||
try {
|
||||
Path root = this.index.getSoundsRoot();
|
||||
Files.createDirectories(root, new FileAttribute[0]);
|
||||
try (Stream<Path> walk = Files.walk(root, new FileVisitOption[0])) {
|
||||
for (java.util.Iterator<Path> it = walk.iterator(); it.hasNext(); ) {
|
||||
Path p = it.next();
|
||||
if (!Files.isRegularFile(p, new LinkOption[0])) continue;
|
||||
long size = Files.size(p);
|
||||
long mtime = Files.getLastModifiedTime(p, new LinkOption[0]).toMillis();
|
||||
String rel = root.relativize(p).toString().replace('\\', '/');
|
||||
fingerprint = 31L * fingerprint + (long)rel.hashCode();
|
||||
fingerprint = 31L * fingerprint + size;
|
||||
fingerprint = 31L * fingerprint + mtime;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "INDEX", "WORLD", "fingerprint failed: {}", e.getMessage());
|
||||
}
|
||||
return fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.sounddata.server;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import com.example.soundmod.common.SoundManifestEntry;
|
||||
import com.example.soundmod.core.ModConfig;
|
||||
import com.example.soundmod.core.SoundDebug;
|
||||
import com.example.soundmod.util.HashUtil;
|
||||
import com.example.soundmod.util.PathSafety;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class WorldSoundIndex {
|
||||
private final Path worldRoot;
|
||||
private final Path soundsRoot;
|
||||
private final List<SoundManifestEntry> manifest = new ArrayList<SoundManifestEntry>();
|
||||
|
||||
public WorldSoundIndex(Path worldRoot) {
|
||||
this.worldRoot = worldRoot.toAbsolutePath().normalize();
|
||||
this.soundsRoot = this.worldRoot.resolve("customsounds").toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public synchronized void scanWorldFolder() {
|
||||
this.manifest.clear();
|
||||
SoundDebug.log(SoundDebug.Level.L1, "INDEX", "WORLD", "scan start soundsRoot={}", this.soundsRoot);
|
||||
try {
|
||||
Files.createDirectories(this.soundsRoot, new FileAttribute[0]);
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.error("Could not create customsounds folder {}", (Object)this.soundsRoot, (Object)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "INDEX", "WORLD", "create directory failed: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Files.walk(this.soundsRoot, new FileVisitOption[0]).filter(x$0 -> Files.isRegularFile(x$0, new LinkOption[0])).forEach(this::indexFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.error("Failed to scan customsounds folder {}", (Object)this.soundsRoot, (Object)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "INDEX", "WORLD", "walk failed: {}", e.getMessage());
|
||||
}
|
||||
this.manifest.sort(Comparator.comparing(entry -> entry.soundId));
|
||||
SoundMod.LOGGER.info("Indexed {} custom sounds in {}", (Object)this.manifest.size(), (Object)this.soundsRoot);
|
||||
SoundDebug.log(SoundDebug.Level.L1, "INDEX", "WORLD", "scan done entries={}", this.manifest.size());
|
||||
}
|
||||
|
||||
private void indexFile(Path file) {
|
||||
long lastModified;
|
||||
long size;
|
||||
String relative;
|
||||
Path safe;
|
||||
try {
|
||||
safe = PathSafety.normalizeInside(this.soundsRoot, file);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
SoundMod.LOGGER.warn("Rejected path {} due to traversal guard", (Object)file);
|
||||
SoundDebug.warn(SoundDebug.Level.L2, "INDEX", "FILE", "path rejected raw={}", file);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
relative = this.soundsRoot.relativize(safe).toString().replace('\\', '/');
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
SoundMod.LOGGER.warn("Failed to relativize {} against {}: {}", (Object)safe, (Object)this.soundsRoot, (Object)ex.getMessage());
|
||||
SoundDebug.warn(SoundDebug.Level.L2, "INDEX", "FILE", "relativize failed safe={} root={} err={}", safe, this.soundsRoot, ex.getMessage());
|
||||
return;
|
||||
}
|
||||
String ext = WorldSoundIndex.extension(relative);
|
||||
int flags = 0;
|
||||
String hash = "";
|
||||
try {
|
||||
size = Files.size(safe);
|
||||
lastModified = Files.getLastModifiedTime(safe, new LinkOption[0]).toMillis();
|
||||
}
|
||||
catch (IOException e) {
|
||||
SoundMod.LOGGER.warn("Failed to stat {}", (Object)safe, (Object)e);
|
||||
SoundDebug.warn(SoundDebug.Level.L2, "INDEX", relative, "stat failed: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
if (size > (long)((Integer)ModConfig.MAX_SOUND_FILE_BYTES.get()).intValue()) {
|
||||
SoundMod.LOGGER.warn("Rejected oversized file {} ({} bytes)", (Object)relative, (Object)size);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "INDEX", relative, "oversized size={} limit={}", size, ModConfig.MAX_SOUND_FILE_BYTES.get());
|
||||
return;
|
||||
}
|
||||
if (!"ogg".equals(ext)) {
|
||||
flags = 1;
|
||||
this.manifest.add(new SoundManifestEntry(WorldSoundIndex.soundIdFor(relative), relative, "", size, lastModified, flags));
|
||||
SoundMod.LOGGER.warn("Unsupported sound format in v1: {}", (Object)relative);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "INDEX", relative, "unsupported ext={}", ext);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
hash = HashUtil.sha256(safe);
|
||||
}
|
||||
catch (Exception e) {
|
||||
flags = 2;
|
||||
HashUtil.logHashError(safe, e);
|
||||
SoundDebug.warn(SoundDebug.Level.L1, "INDEX", relative, "hash failed: {}", e.getMessage());
|
||||
}
|
||||
this.manifest.add(new SoundManifestEntry(WorldSoundIndex.soundIdFor(relative), relative, hash, size, lastModified, flags));
|
||||
SoundDebug.log(SoundDebug.Level.L2, "INDEX", WorldSoundIndex.soundIdFor(relative), "ready relative={} size={} hash={}", relative, size, hash);
|
||||
}
|
||||
|
||||
private static String extension(String path) {
|
||||
int i = path.lastIndexOf(46);
|
||||
return i >= 0 ? path.substring(i + 1).toLowerCase(Locale.ROOT) : "";
|
||||
}
|
||||
|
||||
private static String soundIdFor(String relativePath) {
|
||||
String base = relativePath;
|
||||
int dot = base.lastIndexOf(46);
|
||||
if (dot > 0) {
|
||||
base = base.substring(0, dot);
|
||||
}
|
||||
return base.toLowerCase(Locale.ROOT).replace(' ', '_').replace('/', '.');
|
||||
}
|
||||
|
||||
public synchronized List<SoundManifestEntry> getManifest() {
|
||||
return Collections.unmodifiableList(new ArrayList<SoundManifestEntry>(this.manifest));
|
||||
}
|
||||
|
||||
public synchronized Path resolveSound(String soundId) {
|
||||
for (SoundManifestEntry e : this.manifest) {
|
||||
if (!e.soundId.equals(soundId) || e.flags != 0) continue;
|
||||
return PathSafety.normalizeInside(this.soundsRoot, this.soundsRoot.resolve(e.relativePath));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path getSoundsRoot() {
|
||||
return this.soundsRoot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.util;
|
||||
|
||||
import com.example.soundmod.SoundMod;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public final class HashUtil {
|
||||
private HashUtil() {
|
||||
}
|
||||
|
||||
public static String sha256(Path file) throws IOException {
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256");
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException("SHA-256 unavailable", e);
|
||||
}
|
||||
byte[] buf = new byte[8192];
|
||||
try (InputStream in = Files.newInputStream(file, new OpenOption[0])) {
|
||||
int read;
|
||||
while ((read = in.read(buf)) != -1) {
|
||||
digest.update(buf, 0, read);
|
||||
}
|
||||
}
|
||||
StringBuilder out = new StringBuilder();
|
||||
for (byte b : digest.digest()) {
|
||||
out.append(String.format("%02x", b));
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public static void logHashError(Path file, Exception e) {
|
||||
SoundMod.LOGGER.error("Hash failure {} -> {}", (Object)file, (Object)e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Decompiled with CFR 0.152.
|
||||
*/
|
||||
package com.example.soundmod.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class PathSafety {
|
||||
private PathSafety() {
|
||||
}
|
||||
|
||||
public static Path normalizeInside(Path root, Path candidate) {
|
||||
Path nr = root.toAbsolutePath().normalize();
|
||||
Path nc = candidate.toAbsolutePath().normalize();
|
||||
if (!nc.startsWith(nr)) {
|
||||
throw new IllegalArgumentException("Path traversal: " + candidate);
|
||||
}
|
||||
return nc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
Summary for /tmp/soundmod-remapped.jar
|
||||
Decompiled with CFR 0.152
|
||||
|
||||
|
||||
com.example.soundmod.client.audio.AudioPlaybackService
|
||||
----------------------------
|
||||
|
||||
decodeVorbis(java.nio.file.Path )
|
||||
Loose catch block
|
||||
@@ -0,0 +1,8 @@
|
||||
Manifest-Version: 1.0
|
||||
Specification-Title: soundmod
|
||||
Specification-Vendor: soundmod
|
||||
Specification-Version: 1
|
||||
Implementation-Title: soundmod
|
||||
Implementation-Version: 1.0.0
|
||||
Implementation-Vendor: soundmod
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
modLoader="javafml"
|
||||
loaderVersion="[36,)"
|
||||
license="MIT"
|
||||
|
||||
[[mods]]
|
||||
modId="soundmod"
|
||||
version="${file.jarVersion}"
|
||||
displayName="SoundMod"
|
||||
authors="Codex"
|
||||
description='''
|
||||
Advanced world-scoped custom sound system for Forge 1.16.5.
|
||||
'''
|
||||
|
||||
[[dependencies.soundmod]]
|
||||
modId="forge"
|
||||
mandatory=true
|
||||
versionRange="[36,)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
|
||||
[[dependencies.soundmod]]
|
||||
modId="minecraft"
|
||||
mandatory=true
|
||||
versionRange="[1.16.5,1.17)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"variants": {
|
||||
"": { "model": "soundmod:block/sound_player" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"itemGroup.soundmod": "SoundMod",
|
||||
"block.soundmod.sound_player": "Sound Player",
|
||||
"screen.soundmod.sound_player": "Sound Player",
|
||||
"message.soundmod.unsupported_format": "Unsupported format: %s",
|
||||
"message.soundmod.sync_started": "Sound sync started",
|
||||
"message.soundmod.sync_done": "Sound sync completed"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "minecraft:block/note_block"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "soundmod:block/sound_player"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"rolls": 1,
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "soundmod:sound_player"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"pattern": [
|
||||
"IRI",
|
||||
"RCR",
|
||||
"IRI"
|
||||
],
|
||||
"key": {
|
||||
"I": { "item": "minecraft:iron_ingot" },
|
||||
"R": { "item": "minecraft:redstone" },
|
||||
"C": { "item": "minecraft:note_block" }
|
||||
},
|
||||
"result": {
|
||||
"item": "soundmod:sound_player",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "SoundMod resources",
|
||||
"pack_format": 6
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user