Initial commit: recovered Forge 1.16.5 project, improved GUI, added WAV/MP3 support

This commit is contained in:
rardog
2026-09-24 13:00:20 +03:00
commit fba6ffacd4
73 changed files with 6874 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
.gradle/
build/
run/
out/
.idea/
*.ipr
*.iws
*.iml
.project
.classpath
.settings/
*.log
bin/
+72
View File
@@ -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")
])
}
}
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# 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
View File
@@ -0,0 +1,82 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem 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;
}
}
+9
View File
@@ -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
+8
View File
@@ -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
+26
View File
@@ -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
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"pack": {
"description": "SoundMod resources",
"pack_format": 6
}
}