diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 6fee617d..22963e1f 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -59,6 +59,8 @@ jobs: 1.20.4 1.21 1.21.1 + 1.21.2 + 1.21.3 modrinth-unfeature-mode: 'subset' modrinth-id: 1bZhdhsH @@ -84,6 +86,8 @@ jobs: 1.20.4 1.21 1.21.1 + 1.21.2 + 1.21.3 modrinth-unfeature-mode: 'subset' modrinth-id: 1bZhdhsH diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 612f17be..ac49c768 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,6 +58,8 @@ jobs: 1.20.4 1.21 1.21.1 + 1.21.2 + 1.21.3 modrinth-unfeature-mode: 'subset' modrinth-id: 1bZhdhsH @@ -82,6 +84,8 @@ jobs: 1.20.4 1.21 1.21.1 + 1.21.2 + 1.21.3 modrinth-unfeature-mode: 'subset' modrinth-id: 1bZhdhsH diff --git a/api/client/src/main/java/su/plo/voice/api/client/audio/device/source/AlSource.java b/api/client/src/main/java/su/plo/voice/api/client/audio/device/source/AlSource.java index f190f4c3..9e9906ca 100644 --- a/api/client/src/main/java/su/plo/voice/api/client/audio/device/source/AlSource.java +++ b/api/client/src/main/java/su/plo/voice/api/client/audio/device/source/AlSource.java @@ -157,6 +157,11 @@ public interface AlSource extends DeviceSource { */ void setCloseTimeoutMs(long timeoutMs); + /** + * Updates last buffer time to current time provided by the time supplier. + */ + void updateLastBufferTime(); + /** * Gets the OpenAL format of the source. * diff --git a/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt b/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt index 5e807ca1..65fcd4d3 100644 --- a/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt +++ b/api/client/src/main/kotlin/su/plo/voice/api/client/config/ClientConfig.kt @@ -79,6 +79,8 @@ interface ClientConfig { val panning: BooleanConfigEntry + val mutePlayerOnDirect: BooleanConfigEntry + val cameraSoundListener: BooleanConfigEntry val exponentialVolumeSlider: BooleanConfigEntry diff --git a/api/server-proxy-common/src/main/kotlin/su/plo/voice/api/server/audio/source/AudioSender.kt b/api/server-proxy-common/src/main/kotlin/su/plo/voice/api/server/audio/source/AudioSender.kt index 9eedfff9..7009e027 100644 --- a/api/server-proxy-common/src/main/kotlin/su/plo/voice/api/server/audio/source/AudioSender.kt +++ b/api/server-proxy-common/src/main/kotlin/su/plo/voice/api/server/audio/source/AudioSender.kt @@ -39,7 +39,7 @@ class AudioSender( val job = CoroutineScope(Dispatchers.Default).launch { var sequenceNumber = 0L - val startTime = System.nanoTime() + var startTime = 0L var endOfStream = false @@ -49,6 +49,7 @@ class AudioSender( if (!endOfStream) { endOfStream = true onEnd.invoke(sequenceNumber++) + startTime = 0L } delay(10L) @@ -61,6 +62,7 @@ class AudioSender( endOfStream = true onEnd.invoke(sequenceNumber++) + startTime = 0L continue } @@ -76,6 +78,10 @@ class AudioSender( continue } + if (startTime == 0L) { + startTime = System.nanoTime() + } + val frameTime = 20_000_000 * sequenceNumber val waitTime = startTime + frameTime - System.nanoTime() diff --git a/client/1.21.2-neoforge/gradle.properties b/client/1.21.2-neoforge/gradle.properties deleted file mode 100644 index 809aed51..00000000 --- a/client/1.21.2-neoforge/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -essential.defaults.loom.neoForge=net.neoforged:neoforge:21.2.0-beta diff --git a/client/1.21.2-fabric/gradle.properties b/client/1.21.3-fabric/gradle.properties similarity index 100% rename from client/1.21.2-fabric/gradle.properties rename to client/1.21.3-fabric/gradle.properties diff --git a/client/1.21.3-neoforge/gradle.properties b/client/1.21.3-neoforge/gradle.properties new file mode 100644 index 00000000..bdfefe60 --- /dev/null +++ b/client/1.21.3-neoforge/gradle.properties @@ -0,0 +1 @@ +essential.defaults.loom.neoForge=net.neoforged:neoforge:21.3.57 diff --git a/client/1.21.4-fabric/gradle.properties b/client/1.21.4-fabric/gradle.properties new file mode 100644 index 00000000..88fcece3 --- /dev/null +++ b/client/1.21.4-fabric/gradle.properties @@ -0,0 +1 @@ +essential.defaults.loom.minecraft=com.mojang:minecraft:1.21.4 diff --git a/client/1.21.4-neoforge/gradle.properties b/client/1.21.4-neoforge/gradle.properties new file mode 100644 index 00000000..ddf38cf8 --- /dev/null +++ b/client/1.21.4-neoforge/gradle.properties @@ -0,0 +1 @@ +essential.defaults.loom.neoForge=net.neoforged:neoforge:21.4.2-beta diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 2b06b583..841ed1f6 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -60,6 +60,7 @@ val shadowCommon by configurations.creating fun slibArtifact(): String { val minecraftVersion = when (platform.mcVersion) { 11904 -> "1.19.3" + 12103, 12104 -> "1.21.2" else -> platform.mcVersionStr } @@ -88,15 +89,21 @@ dependencies { 12004 -> "0.95.4+1.20.4" 12006 -> "0.97.7+1.20.6" 12100 -> "0.100.4+1.21" - 12102 -> "0.105.3+1.21.2" + 12103 -> "0.110.0+1.21.3" + 12104 -> "0.110.5+1.21.4" else -> throw GradleException("Unsupported platform $platform") } - modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricApiVersion}") + fun fabricApiModules(vararg module: String) { + module.forEach { + modImplementation(fabricApi.module("fabric-$it", fabricApiVersion)) + } + } + + fabricApiModules("rendering-v1", "networking-api-v1", "lifecycle-events-v1", "key-binding-api-v1") if (platform.mcVersion >= 12102) { - // https://github.com/lucko/fabric-permissions-api/pull/26 - "include"("com.github.sakura-ryoko:fabric-permissions-api:b43d33efb8") + "include"("me.lucko:fabric-permissions-api:0.3.3") } else { "include"("me.lucko:fabric-permissions-api:0.2-SNAPSHOT") } diff --git a/client/changelog.md b/client/changelog.md index 7a1f88cd..db41edc9 100644 --- a/client/changelog.md +++ b/client/changelog.md @@ -5,12 +5,10 @@ If you encounter any issues, please report them on Discord: https://discord.gg/u Versions 2.0.x and 2.1.x are protocol-compatible, so there’s no need to worry if the server hasn't been updated to 2.1.x. -### Changes in 2.1.1 -- Build for 1.19.3 was reintroduced. -- Soften Minecraft version bounds: - - 1.20.4 now allows 1.20.2, 1.20.3 and 1.20.4 - - 1.19.2 now allows 1.19, 1.19.1 and 1.19.2 -- Updated to 1.21.2. -- Updated [slib](https://github.com/plasmoapp/mc-slib) to fix crash with EssentialAddons on world join. -- Fixed audio sources causing a high CPU load. [#421](https://github.com/plasmoapp/plasmo-voice/issues/421) -- Attempt to fix "Cannot measure distance between worlds" exception, see [#422](https://github.com/plasmoapp/plasmo-voice/issues/422). \ No newline at end of file +### Changes in 2.1.2 +- 1.21.4 fabric/neoforge. +- Fixed buffer overflow when using AudioSender with delayed first frame. +- Fixed EncoderException on server switch on servers with proxy and proxy-side addons (e.g, groups addon). +- Fixed `pv.activation.*` permission is not being updated on the client without reconnect. +- Fixed textfield input not being handled. +- Player sources are now automatically muted if the direct source from the same player becomes active. Can be disabled in menu `Advanced`/`Mute Player On Direct`. \ No newline at end of file diff --git a/client/root.gradle.kts b/client/root.gradle.kts index f9ef6974..61c49960 100644 --- a/client/root.gradle.kts +++ b/client/root.gradle.kts @@ -6,8 +6,11 @@ group = "$group.client-root" preprocess { - val neoForge12102 = createNode("1.21.2-neoforge", 12102, "official") - val fabric12102 = createNode("1.21.2-fabric", 12102, "official") + val neoForge12104 = createNode("1.21.4-neoforge", 12104, "official") + val fabric12104 = createNode("1.21.4-fabric", 12104, "official") + + val neoForge12103 = createNode("1.21.3-neoforge", 12103, "official") + val fabric12103 = createNode("1.21.3-fabric", 12103, "official") val neoForge12100 = createNode("1.21-neoforge", 12100, "official") val fabric12100 = createNode("1.21-fabric", 12100, "official") @@ -37,8 +40,11 @@ preprocess { val forge11605 = createNode("1.16.5-forge", 11605, "official") val fabric11605 = createNode("1.16.5-fabric", 11605, "official") - fabric12102.link(fabric12100) - neoForge12102.link(neoForge12100) + fabric12104.link(fabric12103) + neoForge12104.link(neoForge12103) + + fabric12103.link(fabric12100) + neoForge12103.link(neoForge12100) neoForge12100.link(fabric12100) fabric12100.link(fabric12004, file("1.21-1.20.6.txt")) diff --git a/client/src/main/java/su/plo/lib/mod/client/gui/screen/ScreenWrapper.java b/client/src/main/java/su/plo/lib/mod/client/gui/screen/ScreenWrapper.java index 765ed03a..d38c98a1 100644 --- a/client/src/main/java/su/plo/lib/mod/client/gui/screen/ScreenWrapper.java +++ b/client/src/main/java/su/plo/lib/mod/client/gui/screen/ScreenWrapper.java @@ -186,10 +186,14 @@ public boolean mouseScrolled(double mouseX, double mouseY, double delta) { // return screen.mouseScrolled(mouseX, mouseY, delta); // } + @Override + public boolean charTyped(char typedChar, int modifiers) { + return screen.charTyped(typedChar, modifiers); + } + @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (keyCode == 0) { - screen.charTyped((char) 0, modifiers); return false; } diff --git a/client/src/main/java/su/plo/voice/client/audio/capture/VoiceAudioCapture.java b/client/src/main/java/su/plo/voice/client/audio/capture/VoiceAudioCapture.java index 7cfaf2b2..dfa3bd7f 100644 --- a/client/src/main/java/su/plo/voice/client/audio/capture/VoiceAudioCapture.java +++ b/client/src/main/java/su/plo/voice/client/audio/capture/VoiceAudioCapture.java @@ -21,15 +21,18 @@ import su.plo.voice.api.client.audio.capture.AudioCapture; import su.plo.voice.api.client.audio.capture.ClientActivation; import su.plo.voice.api.client.audio.capture.ClientActivationManager; -import su.plo.voice.api.client.audio.device.AudioDevice; import su.plo.voice.api.client.audio.device.DeviceManager; -import su.plo.voice.api.client.audio.device.DeviceType; import su.plo.voice.api.client.audio.device.InputDevice; import su.plo.voice.api.client.connection.ServerInfo; -import su.plo.voice.api.client.event.audio.capture.*; +import su.plo.voice.api.client.event.audio.capture.AudioCaptureEvent; +import su.plo.voice.api.client.event.audio.capture.AudioCaptureInitializeEvent; +import su.plo.voice.api.client.event.audio.capture.AudioCaptureProcessedEvent; +import su.plo.voice.api.client.event.audio.capture.AudioCaptureStartEvent; +import su.plo.voice.api.client.event.audio.capture.AudioCaptureStopEvent; import su.plo.voice.api.encryption.Encryption; import su.plo.voice.api.encryption.EncryptionException; import su.plo.voice.api.util.AudioUtil; +import su.plo.voice.client.audio.device.JavaxInputDeviceFactory; import su.plo.voice.client.audio.filter.StereoToMonoFilter; import su.plo.voice.client.config.VoiceClientConfig; import su.plo.voice.client.mac.AVAuthorizationStatus; @@ -41,7 +44,10 @@ import su.plo.voice.proto.packets.udp.serverbound.PlayerAudioPacket; import javax.sound.sampled.AudioFormat; -import java.util.*; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; public final class VoiceAudioCapture implements AudioCapture { @@ -123,6 +129,7 @@ public void initialize(@NotNull ServerInfo serverInfo) { devices.setInputDevice(inputDevice); } catch (Exception e) { LOGGER.error("Failed to open input device", e); + JavaxInputDeviceFactory.printSupportedLines(); } } diff --git a/client/src/main/java/su/plo/voice/client/audio/device/JavaxInputDeviceFactory.java b/client/src/main/java/su/plo/voice/client/audio/device/JavaxInputDeviceFactory.java index de288db1..ec9d4773 100644 --- a/client/src/main/java/su/plo/voice/client/audio/device/JavaxInputDeviceFactory.java +++ b/client/src/main/java/su/plo/voice/client/audio/device/JavaxInputDeviceFactory.java @@ -4,12 +4,18 @@ import com.google.common.collect.ImmutableList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import su.plo.voice.BaseVoice; import su.plo.voice.api.client.PlasmoVoiceClient; import su.plo.voice.api.client.audio.device.AudioDevice; import su.plo.voice.api.client.audio.device.DeviceException; import su.plo.voice.api.client.audio.device.DeviceFactory; -import javax.sound.sampled.*; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.Line; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.TargetDataLine; import java.util.ArrayList; import java.util.List; @@ -17,6 +23,26 @@ public final class JavaxInputDeviceFactory implements DeviceFactory { + public static void printSupportedLines() { + BaseVoice.DEBUG_LOGGER.log("Supported target data lines:"); + + Mixer.Info[] mixers = AudioSystem.getMixerInfo(); + for (Mixer.Info mixerInfo : mixers) { + Mixer mixer = AudioSystem.getMixer(mixerInfo); + Line.Info lineInfo = new Line.Info(TargetDataLine.class); + + if (mixer.isLineSupported(lineInfo)) { + for (Line.Info info : mixer.getTargetLineInfo()) { + BaseVoice.DEBUG_LOGGER.log(info.toString()); + DataLine.Info dataInfo = (DataLine.Info) info; + for (AudioFormat dataFormat : dataInfo.getFormats()) { + BaseVoice.DEBUG_LOGGER.log(dataFormat.toString()); + } + } + } + } + } + private final PlasmoVoiceClient client; public JavaxInputDeviceFactory(PlasmoVoiceClient client) { diff --git a/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java b/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java index 2005ab0c..ad1ce62e 100644 --- a/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java +++ b/client/src/main/java/su/plo/voice/client/config/VoiceClientConfig.java @@ -400,6 +400,9 @@ public static class Advanced implements ClientConfig.Advanced { @ConfigField private BooleanConfigEntry panning = new BooleanConfigEntry(true); + @ConfigField + private BooleanConfigEntry mutePlayerOnDirect = new BooleanConfigEntry(true); + @ConfigField private BooleanConfigEntry cameraSoundListener = new BooleanConfigEntry(true); diff --git a/client/src/main/java/su/plo/voice/client/connection/ModServerConnection.java b/client/src/main/java/su/plo/voice/client/connection/ModServerConnection.java index 5300b21c..652d3ef5 100644 --- a/client/src/main/java/su/plo/voice/client/connection/ModServerConnection.java +++ b/client/src/main/java/su/plo/voice/client/connection/ModServerConnection.java @@ -122,6 +122,9 @@ public ModServerConnection(@NotNull BaseVoiceClient voiceClient, @Override public void sendPacket(@NotNull Packet packet, boolean checkUdpConnection) { if (!connection.isConnected()) return; + //#if MC>=12004 + //$$ if (Minecraft.getInstance().getConnection() == null) return; + //#endif if (checkUdpConnection && !voiceClient.getUdpClientManager().isConnected()) return; diff --git a/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java b/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java index e4caa401..2c36715c 100644 --- a/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java +++ b/client/src/main/java/su/plo/voice/client/gui/settings/tab/AdvancedTabWidget.java @@ -55,6 +55,11 @@ public void init() { )); addEntry(createStereoToMonoSources()); addEntry(createPanning()); + addEntry(createToggleEntry( + McTextComponent.translatable("gui.plasmovoice.advanced.mute_player_on_direct"), + McTextComponent.translatable("gui.plasmovoice.advanced.mute_player_on_direct.tooltip"), + config.getAdvanced().getMutePlayerOnDirect() + )); addEntry(new CategoryEntry(McTextComponent.translatable("gui.plasmovoice.advanced.exponential_volume"))); addEntry(createToggleEntry( diff --git a/client/src/main/java/su/plo/voice/client/mixin/MixinSkinManager.java b/client/src/main/java/su/plo/voice/client/mixin/MixinSkinManager.java index df83bf30..d8e8b163 100644 --- a/client/src/main/java/su/plo/voice/client/mixin/MixinSkinManager.java +++ b/client/src/main/java/su/plo/voice/client/mixin/MixinSkinManager.java @@ -9,7 +9,6 @@ //#if MC>=12002 //$$ import com.mojang.authlib.minecraft.MinecraftSessionService; -//$$ import net.minecraft.client.renderer.texture.TextureManager; //$$ //$$ import org.spongepowered.asm.mixin.Unique; //$$ import org.spongepowered.asm.mixin.injection.At; @@ -18,6 +17,11 @@ //$$ //$$ import java.nio.file.Path; //$$ import java.util.concurrent.Executor; + +//#if MC<12104 +//$$ import net.minecraft.client.renderer.texture.TextureManager; +//#endif + //#else import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Shadow; @@ -26,7 +30,19 @@ @Mixin(SkinManager.class) public abstract class MixinSkinManager implements SkinManagerAccessor { - //#if MC>=12002 + //#if MC>=12104 + //$$ @Unique + //$$ private static File SKINS_DIRECTORY; + //$$ @Inject(at = @At("RETURN"), method = "") + //$$ private void init(Path path, MinecraftSessionService minecraftSessionService, Executor executor, CallbackInfo ci) { + //$$ SKINS_DIRECTORY = path.toFile(); + //$$ } + //$$ @NotNull + //$$ @Override + //$$ public File getSkinsCacheFolder() { + //$$ return SKINS_DIRECTORY; + //$$ } + //#elseif MC>=12002 //$$ @Unique //$$ private static File SKINS_DIRECTORY; //$$ @Inject(at = @At("RETURN"), method = "") diff --git a/client/src/main/kotlin/gg/essential/universal/DummyPack.kt b/client/src/main/kotlin/gg/essential/universal/DummyPack.kt index 22fd61cd..453bcdc2 100644 --- a/client/src/main/kotlin/gg/essential/universal/DummyPack.kt +++ b/client/src/main/kotlin/gg/essential/universal/DummyPack.kt @@ -5,7 +5,6 @@ import gg.essential.universal.shader.MCShader import net.minecraft.resources.ResourceLocation import net.minecraft.server.packs.PackResources import net.minecraft.server.packs.PackType -import net.minecraft.server.packs.metadata.MetadataSectionSerializer import net.minecraft.server.packs.resources.IoSupplier import java.io.InputStream @@ -13,6 +12,12 @@ import java.io.InputStream //$$ import net.minecraft.server.packs.PackLocationInfo //#endif +//#if MC>=12104 +//$$ import net.minecraft.server.packs.metadata.MetadataSectionType +//#else +import net.minecraft.server.packs.metadata.MetadataSectionSerializer +//#endif + /** * A dummy resource pack for use in [MCShader], since the [Resource] constructor * on 1.19.3+ requires a [PackResources] instead of a String name. @@ -46,9 +51,15 @@ object DummyPack : PackResources { throw UnsupportedOperationException() } + //#if MC>=12104 + //$$ override fun getMetadataSection(metadataSectionType: MetadataSectionType): T? { + //$$ throw UnsupportedOperationException() + //$$ } + //#else override fun getMetadataSection(metadataSectionSerializer: MetadataSectionSerializer): T? { throw UnsupportedOperationException() } + //#endif //#if MC>=12005 //$$ override fun location(): PackLocationInfo { diff --git a/client/src/main/kotlin/su/plo/voice/client/audio/device/source/StreamAlSource.kt b/client/src/main/kotlin/su/plo/voice/client/audio/device/source/StreamAlSource.kt index 4a6ab435..21a4aaac 100644 --- a/client/src/main/kotlin/su/plo/voice/client/audio/device/source/StreamAlSource.kt +++ b/client/src/main/kotlin/su/plo/voice/client/audio/device/source/StreamAlSource.kt @@ -99,6 +99,10 @@ class StreamAlSource private constructor( this.closeTimeoutMs = timeoutMs } + override fun updateLastBufferTime() { + lastBufferTime = timeSupplier.currentTimeMillis + } + override fun write(samples: ShortArray, applyFilters: Boolean) { val processedSamples = if (applyFilters) { device.processFilters(samples) @@ -202,7 +206,7 @@ class StreamAlSource private constructor( queueWithEmptyBuffers() fillQueue() - lastBufferTime = timeSupplier.currentTimeMillis + updateLastBufferTime() availableBuffer[0] = -1 while (isStreaming.get()) { diff --git a/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt b/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt index 96cc5670..07da4bcd 100644 --- a/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt +++ b/client/src/main/kotlin/su/plo/voice/client/audio/source/BaseClientAudioSource.kt @@ -344,42 +344,46 @@ abstract class BaseClientAudioSource( // so we need to make sure that source is not closed rn if (closed.get()) return - // packet compensation - if (lastSequenceNumber >= 0) { - val packetsToCompensate = (packet.sequenceNumber - (lastSequenceNumber + 1)).toInt() - if (packetsToCompensate in 1..4) { - BaseVoice.DEBUG_LOGGER.warn("Compensate {} lost packets", packetsToCompensate) - for (i in 0 until packetsToCompensate) { - val compensatedSequenceNumber = lastSequenceNumber + i + 1 - - if (decoder != null && decoder is AudioDecoderPlc && !sourceInfo.isStereo) { - try { - write((decoder as AudioDecoderPlc).decodePLC(), compensatedSequenceNumber) - } catch (e: CodecException) { - LOGGER.warn("Failed to decode source audio", e) - return + if (shouldWrite()) { + // packet compensation + if (lastSequenceNumber >= 0) { + val packetsToCompensate = (packet.sequenceNumber - (lastSequenceNumber + 1)).toInt() + if (packetsToCompensate in 1..4) { + BaseVoice.DEBUG_LOGGER.warn("Compensate {} lost packets", packetsToCompensate) + for (i in 0 until packetsToCompensate) { + val compensatedSequenceNumber = lastSequenceNumber + i + 1 + + if (decoder != null && decoder is AudioDecoderPlc && !sourceInfo.isStereo) { + try { + write((decoder as AudioDecoderPlc).decodePLC(), compensatedSequenceNumber) + } catch (e: CodecException) { + LOGGER.warn("Failed to decode source audio", e) + return + } + } else { + write(ShortArray(0), compensatedSequenceNumber) } - } else { - write(ShortArray(0), compensatedSequenceNumber) } } } - } - // decrypt & decode samples - try { - val decrypted = encryption?.decrypt(packet.data) ?: packet.data - val decoded = decoder?.decode(decrypted) ?: AudioUtil.bytesToShorts(decrypted) + // decrypt & decode samples + try { + val decrypted = encryption?.decrypt(packet.data) ?: packet.data + val decoded = decoder?.decode(decrypted) ?: AudioUtil.bytesToShorts(decrypted) - if (sourceInfo.isStereo && config.advanced.stereoSourcesToMono.value()) { - write(AudioUtil.convertToMonoShorts(decoded), packet.sequenceNumber) - } else { - write(decoded, packet.sequenceNumber) + if (sourceInfo.isStereo && config.advanced.stereoSourcesToMono.value()) { + write(AudioUtil.convertToMonoShorts(decoded), packet.sequenceNumber) + } else { + write(decoded, packet.sequenceNumber) + } + } catch (e: EncryptionException) { + BaseVoice.DEBUG_LOGGER.warn("Failed to decrypt source audio", e) + } catch (e: CodecException) { + BaseVoice.DEBUG_LOGGER.warn("Failed to decode source audio", e) } - } catch (e: EncryptionException) { - BaseVoice.DEBUG_LOGGER.warn("Failed to decrypt source audio", e) - } catch (e: CodecException) { - BaseVoice.DEBUG_LOGGER.warn("Failed to decode source audio", e) + } else { + source.updateLastBufferTime() } lastSequenceNumber = packet.sequenceNumber @@ -548,6 +552,14 @@ abstract class BaseClientAudioSource( return sourceInfo.isStereo && !config.advanced.stereoSourcesToMono.value() } + /** + * Determines if audio should be written to the underlying AL source. + * + * Because we don't want to disable source icons in some cases, + * we should just disable writing samples to the actual source. + */ + protected open fun shouldWrite(): Boolean = true + companion object { private val OUTER_ANGLE: Double = 180.0 private val LOGGER: Logger = LogManager.getLogger(BaseClientAudioSource::class.java) diff --git a/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt b/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt index 66e010ad..e40e64e2 100644 --- a/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt +++ b/client/src/main/kotlin/su/plo/voice/client/audio/source/ClientPlayerSource.kt @@ -41,6 +41,13 @@ class ClientPlayerSource( override fun isPanningDisabled(): Boolean = sourcePlayer == getListener() || super.isPanningDisabled() + override fun shouldWrite(): Boolean = + !voiceClient.config.advanced.mutePlayerOnDirect.value() || + voiceClient.sourceManager.sources + .filterIsInstance() + .filter { it.isActivated() } + .none { it.sourceInfo.sender?.id == sourceInfo.playerInfo.playerId } + private val sourceMute: BooleanConfigEntry get() { return config.voice diff --git a/client/src/main/kotlin/su/plo/voice/client/crowdin/PlasmoCrowdinPack.kt b/client/src/main/kotlin/su/plo/voice/client/crowdin/PlasmoCrowdinPack.kt index 95158a46..6e4022e8 100644 --- a/client/src/main/kotlin/su/plo/voice/client/crowdin/PlasmoCrowdinPack.kt +++ b/client/src/main/kotlin/su/plo/voice/client/crowdin/PlasmoCrowdinPack.kt @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableSet import net.minecraft.resources.ResourceLocation import net.minecraft.server.packs.PackResources import net.minecraft.server.packs.PackType -import net.minecraft.server.packs.metadata.MetadataSectionSerializer import java.io.File import java.io.InputStream @@ -20,6 +19,12 @@ import net.minecraft.server.packs.resources.IoSupplier //$$ import java.util.function.Predicate //#endif +//#if MC>=12104 +//$$ import net.minecraft.server.packs.metadata.MetadataSectionType +//#else +import net.minecraft.server.packs.metadata.MetadataSectionSerializer +//#endif + class PlasmoCrowdinPack( private val crowdinFolder: File ) : PackResources { @@ -88,7 +93,11 @@ class PlasmoCrowdinPack( override fun getNamespaces(packType: PackType): Set = NAMESPACES - override fun getMetadataSection(metadataSectionSerializer: MetadataSectionSerializer) = null + //#if MC>=12104 + //$$ override fun getMetadataSection(metadataSectionType: MetadataSectionType): T? = null + //#else + override fun getMetadataSection(metadataSectionSerializer: MetadataSectionSerializer): T? = null + //#endif //#if MC>11902 override fun packId() = "Plasmo Crowdin resource pack" diff --git a/client/src/main/kotlin/su/plo/voice/client/render/cape/DeveloperCapeManager.kt b/client/src/main/kotlin/su/plo/voice/client/render/cape/DeveloperCapeManager.kt index 19fbf82e..331ececd 100644 --- a/client/src/main/kotlin/su/plo/voice/client/render/cape/DeveloperCapeManager.kt +++ b/client/src/main/kotlin/su/plo/voice/client/render/cape/DeveloperCapeManager.kt @@ -6,7 +6,6 @@ import com.google.common.cache.CacheBuilder import com.google.common.hash.Hashing import com.mojang.authlib.minecraft.MinecraftProfileTexture import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.texture.HttpTexture import net.minecraft.resources.ResourceLocation import su.plo.lib.mod.client.ResourceLocationUtil import su.plo.lib.mod.client.render.texture.ModPlayerSkins @@ -22,6 +21,12 @@ import java.util.function.Supplier //$$ import net.minecraft.client.resources.PlayerSkin //#endif +//#if MC>=12104 +//$$ import net.minecraft.client.renderer.texture.SkinTextureDownloader +//#else +import net.minecraft.client.renderer.texture.HttpTexture +//#endif + object DeveloperCapeManager { private val loadedCapes: Cache>> = CacheBuilder.newBuilder() @@ -93,10 +98,22 @@ object DeveloperCapeManager { capeFile.delete() } + //#if MC>=12104 + //$$ try { + //$$ SkinTextureDownloader.downloadAndRegisterSkin( + //$$ capeLocation, + //$$ capeFile.toPath(), + //$$ texture.url, + //$$ false + //$$ ).get() + //$$ } catch (ignored: Exception) { + //$$ } + //#else Minecraft.getInstance().textureManager.register( capeLocation, HttpTexture(capeFile, texture.url, ModPlayerSkins.getDefaultSkin(UUID.randomUUID()), false) {} ) + //#endif capeLocation } diff --git a/client/src/main/resources/assets/plasmovoice/lang/en_us.json b/client/src/main/resources/assets/plasmovoice/lang/en_us.json index d4ee048c..78168afc 100644 --- a/client/src/main/resources/assets/plasmovoice/lang/en_us.json +++ b/client/src/main/resources/assets/plasmovoice/lang/en_us.json @@ -70,7 +70,9 @@ "gui.plasmovoice.advanced.directional_sources_angle.tooltip": "An angle in which you will hear the source at 100% if directional sources option is enabled.\n\n360 is like if the option is disabled.", "gui.plasmovoice.advanced.stereo_sources_to_mono": "Mono Stereo Sources", "gui.plasmovoice.advanced.stereo_sources_to_mono.tooltip": "Addons use stereo sources for better audio quality. The quality is much better, but facing doesn't affect the panning. Sound only fades out with distance.\n\nWhen this option is enabled, stereo sources are converted to the usual mono sources. Quality is worse, but they will now have panning.", - "gui.plasmovoice.advanced.panning": "Stereo positioning", + "gui.plasmovoice.advanced.panning": "Stereo Positioning", + "gui.plasmovoice.advanced.mute_player_on_direct": "Mute Player On Direct", + "gui.plasmovoice.advanced.mute_player_on_direct.tooltip": "When enabled, the player's source will be automatically muted if the direct source from the same player becomes active. Use this to avoid overlapping audio or conflicting playback from multiple sources.", "gui.plasmovoice.advanced.exponential_volume": "Exponential Volume", "gui.plasmovoice.advanced.exponential_volume.volume_slider": "Volume Slider", diff --git a/client/versions.toml b/client/versions.toml index 2889c47f..ea9b85c8 100644 --- a/client/versions.toml +++ b/client/versions.toml @@ -1,5 +1,9 @@ -[12102] -mcVersions = ">1.21.1" +[12104] +mcVersions = ">1.21.3" +neoForgeVersion = "[21,)" + +[12103] +mcVersions = ">=1.21.2 <=1.21.3" neoForgeVersion = "[21,)" [12100] diff --git a/gradle.properties b/gradle.properties index 859b048b..5633cbfe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Version targetJavaVersion=8 group=su.plo.voice -version=2.1.1 +version=2.1.2 # Gradle args org.gradle.jvmargs=-Xmx4G -Dkotlin.daemon.jvm.options=-Xmx512M diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d202fca..3f30c30d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ gson = "2.10.1" netty = "4.1.77.Final" crowdin = "1.1.0-SNAPSHOT" config = "1.0.2" -slib = "1.0.0-SNAPSHOT" +slib = "1.0.2-SNAPSHOT" bstats = "3.0.2" luckperms = "5.4" papi = "2.11.6" diff --git a/proxy/changelog.md b/proxy/changelog.md index e0a8907e..adb58ce4 100644 --- a/proxy/changelog.md +++ b/proxy/changelog.md @@ -5,4 +5,9 @@ If you encounter any issues, please report them on Discord: https://discord.gg/u Versions 2.0.x and 2.1.x are protocol-compatible, so there’s no need to worry if the server hasn't been updated to 2.1.x. -### Changes in 2.1.1 \ No newline at end of file +### Changes in 2.1.2 +- Fixed buffer overflow when using AudioSender with delayed first frame. +- Fixed client EncoderException on server switch caused by UDP packets sent to a client when client's server is null. +- Fixed ArrayIndexOutOfBoundsException exception when using `/` command on Velocity. +- Fixed `pv.activation.*` permission is not being updated on the client without reconnect. +- Fixed permission check on BungeeCord. \ No newline at end of file diff --git a/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyPacketHandler.java b/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyPacketHandler.java index ef091696..8a27bd41 100644 --- a/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyPacketHandler.java +++ b/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyPacketHandler.java @@ -128,6 +128,8 @@ private boolean sendPacket(ChannelHandlerContext ctx, NettyPacketUdp nettyPacket } else { receiver = connection.getRemoteAddress(); receiverSecret = connection.getSecret(); + + if (!connection.isConnected()) return true; } // rewrite to backend server diff --git a/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyUdpProxyConnection.java b/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyUdpProxyConnection.java index ae90a5b7..f483db06 100644 --- a/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyUdpProxyConnection.java +++ b/proxy/common/src/main/java/su/plo/voice/proxy/socket/NettyUdpProxyConnection.java @@ -42,7 +42,6 @@ public final class NettyUdpProxyConnection implements UdpProxyConnection, Server private InetSocketAddress remoteAddress; @Setter private RemoteServer remoteServer; - @Getter private boolean connected = true; @Override @@ -52,6 +51,8 @@ public Optional getRemoteServer() { @Override public void sendPacket(Packet packet) { + if (!isConnected()) return; + byte[] encoded = PacketUdpCodec.encode(packet, secret); if (encoded == null) return; @@ -70,6 +71,11 @@ public void disconnect() { this.connected = false; } + @Override + public boolean isConnected() { + return connected && player.getInstance().getServer() != null; + } + @Override public void handle(@NotNull PingPacket packet) { } diff --git a/server-proxy-common/src/main/kotlin/su/plo/voice/server/audio/capture/VoiceServerActivationManager.kt b/server-proxy-common/src/main/kotlin/su/plo/voice/server/audio/capture/VoiceServerActivationManager.kt index 1991e817..890818a4 100644 --- a/server-proxy-common/src/main/kotlin/su/plo/voice/server/audio/capture/VoiceServerActivationManager.kt +++ b/server-proxy-common/src/main/kotlin/su/plo/voice/server/audio/capture/VoiceServerActivationManager.kt @@ -191,11 +191,8 @@ class VoiceServerActivationManager( val player = event.player val permission = event.permission - if (activationById.values.none { it.permissions.contains(permission) }) return - if (permission == WILDCARD_ACTIVATION_PERMISSION) { when (player.instance.getPermission(WILDCARD_ACTIVATION_PERMISSION)) { - PermissionTristate.TRUE -> activationById.values.forEach { player.sendPacket(ActivationRegisterPacket(it as VoiceActivation)) } @@ -214,6 +211,9 @@ class VoiceServerActivationManager( } return } + + if (activationById.values.none { it.permissions.contains(permission) }) return + val permissionSplit = permission.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val activation = getActivationByName(permissionSplit[permissionSplit.size - 1]).orElse(null) ?: return diff --git a/server/changelog.md b/server/changelog.md index 7b905611..a4400ed4 100644 --- a/server/changelog.md +++ b/server/changelog.md @@ -5,5 +5,8 @@ If you encounter any issues, please report them on Discord: https://discord.gg/u Versions 2.0.x and 2.1.x are protocol-compatible, so there’s no need to worry if the server hasn't been updated to 2.1.x. -### Changes in 2.1.1 -- Attempt to fix "Cannot measure distance between worlds" exception, see [#422](https://github.com/plasmoapp/plasmo-voice/issues/422). \ No newline at end of file +### Changes in 2.1.2 +- Fixed buffer overflow when using AudioSender with delayed first frame. +- Fixed `pv.activation.*` permission is not being updated on the client without reconnect. +- Fixed server translations missing in kick messages. [#428](https://github.com/plasmoapp/plasmo-voice/issues/428) +- `/vmute` without arguments now sends command usage instead of "no permissions" error. \ No newline at end of file diff --git a/server/common/src/main/java/su/plo/voice/server/command/VoiceMuteCommand.java b/server/common/src/main/java/su/plo/voice/server/command/VoiceMuteCommand.java index c29c1cd9..8ac3d850 100644 --- a/server/common/src/main/java/su/plo/voice/server/command/VoiceMuteCommand.java +++ b/server/common/src/main/java/su/plo/voice/server/command/VoiceMuteCommand.java @@ -33,7 +33,7 @@ public final class VoiceMuteCommand implements McCommand { @Override public void execute(@NotNull McCommandSource source, @NotNull String[] arguments) { if (arguments.length == 0) { - source.sendMessage(McTextComponent.translatable("pv.error.no_permissions")); + source.sendMessage(McTextComponent.translatable("pv.command.mute.usage")); return; }