1 Commits
Author SHA1 Message Date
asminme 71396a86a0 asminvoicechat second commit
Build AsminVoicechat / build-and-release (push) Canceled after 12s
2026-10-24 15:42:08 +02:00
581 changed files with 9011 additions and 38640 deletions

No files matched your search

+22 -118
View File
@@ -13,18 +13,20 @@ concurrency:
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
prepare-release: build-and-release:
runs-on: docker-builder runs-on: docker-builder
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
release_id: ${{ steps.release.outputs.release_id }}
steps: steps:
- name: Checkout repository and tags - name: Checkout repository and tags
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Java 25
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
- name: Calculate next version - name: Calculate next version
id: version id: version
shell: bash shell: bash
@@ -44,134 +46,36 @@ jobs:
echo "version=$version" >> "$GITHUB_OUTPUT" echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT" echo "tag=v$version" >> "$GITHUB_OUTPUT"
- name: Create Gitea release - name: Build plugin and run crypto test
id: release shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
chmod +x ./gradlew
./gradlew --no-daemon clean build -PbuildVersion="$VERSION"
- name: Create Gitea release and upload plugin
shell: bash
env: env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_SERVER_URL: ${{ gitea.server_url }} GITEA_SERVER_URL: ${{ gitea.server_url }}
GITEA_REPOSITORY: ${{ gitea.repository }} GITEA_REPOSITORY: ${{ gitea.repository }}
VERSION: ${{ steps.version.outputs.version }} VERSION: ${{ steps.version.outputs.version }}
TAG: ${{ steps.version.outputs.tag }} TAG: ${{ steps.version.outputs.tag }}
shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
if [[ -z "$GITEA_TOKEN" ]]; then if [[ -z "$GITEA_TOKEN" ]]; then
echo "Add a repository write token as the GITEA_TOKEN secret." echo "Add a repository write token as the GITEA_TOKEN secret."
exit 1 exit 1
fi fi
jar="build/libs/AsminVoiceChat-${VERSION}.jar"
api="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY" api="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY"
release="$(curl --fail-with-body -sS -X POST "$api/releases" \ release="$(curl --fail-with-body -sS -X POST "$api/releases" \
-H "Authorization: token $GITEA_TOKEN" \ -H "Authorization: token $GITEA_TOKEN" \
-H 'Content-Type: application/json' \ -H 'Content-Type: application/json' \
-d "{\"tag_name\":\"$TAG\",\"name\":\"AsminVoiceChat $VERSION\",\"body\":\"Dual-protocol Simple Voice Chat server release.\",\"draft\":false,\"prerelease\":false}")" -d "{\"tag_name\":\"$TAG\",\"name\":\"AsminVoiceChat $VERSION\",\"body\":\"AsminVoiceChat $VERSION\",\"draft\":false,\"prerelease\":false}")"
release_id="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<< "$release")" release_id="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<< "$release")"
echo "release_id=$release_id" >> "$GITHUB_OUTPUT" curl --fail-with-body -sS -X POST "$api/releases/$release_id/assets?name=AsminVoiceChat-${VERSION}.jar" \
build-and-test:
needs: prepare-release
runs-on: docker-builder
strategy:
fail-fast: false
matrix:
java:
- '16'
- '17'
- '18'
- '19'
- '20'
- '21'
- '22'
- '23'
- '24'
- '25'
- '26'
- '27-ea'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Java ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
- name: Download 2.6.25 base and compile dependencies
shell: bash
run: |
set -euo pipefail
mkdir -p build/deps build/classes build/test-classes
hangar="https://hangarcdn.papermc.io/plugins/henkelmax/SimpleVoiceChat/versions/bukkit-2.6.25/PAPER/voicechat-bukkit-2.6.25.jar"
curl --fail --location --silent --show-error "$hangar" -o build/deps/voicechat-bukkit-2.6.25.jar
repo="https://repo.papermc.io/repository/maven-public"
curl --fail --location --silent --show-error "$repo/io/papermc/paper/paper-api/1.17.1-R0.1-SNAPSHOT/paper-api-1.17.1-R0.1-20220414.034903-311.jar" -o build/deps/paper-api.jar
curl --fail --location --silent --show-error "$repo/net/md-5/bungeecord-chat/1.16-R0.4/bungeecord-chat-1.16-R0.4.jar" -o build/deps/bungeecord-chat.jar
curl --fail --location --silent --show-error "$repo/net/kyori/adventure-api/4.9.3/adventure-api-4.9.3.jar" -o build/deps/adventure-api.jar
curl --fail --location --silent --show-error "$repo/net/kyori/adventure-key/4.9.3/adventure-key-4.9.3.jar" -o build/deps/adventure-key.jar
curl --fail --location --silent --show-error "$repo/net/kyori/examination-api/1.3.0/examination-api-1.3.0.jar" -o build/deps/examination-api.jar
curl --fail --location --silent --show-error "$repo/net/kyori/examination-string/1.3.0/examination-string-1.3.0.jar" -o build/deps/examination-string.jar
curl --fail --location --silent --show-error "$repo/org/jetbrains/annotations/22.0.0/annotations-22.0.0.jar" -o build/deps/annotations.jar
curl --fail --location --silent --show-error "$repo/io/netty/netty-buffer/4.1.63.Final/netty-buffer-4.1.63.Final.jar" -o build/deps/netty-buffer.jar
curl --fail --location --silent --show-error "$repo/io/netty/netty-common/4.1.63.Final/netty-common-4.1.63.Final.jar" -o build/deps/netty-common.jar
- name: Compile Java 8-compatible patch classes
env:
VERSION: ${{ needs.prepare-release.outputs.version }}
shell: bash
run: |
set -euo pipefail
cp="build/deps/voicechat-bukkit-2.6.25.jar:$(printf '%s:' build/deps/*.jar)"
javac --release 8 -cp "$cp" -d build/classes \
source/patch/de/maxhenkel/voicechat/voice/common/Secret.java \
source/patch/de/maxhenkel/voicechat/voice/server/ServerVoiceEvents.java
sed "s/^version:.*/version: '$VERSION'/" plugin.yml > build/plugin.yml
python3 - <<'PY'
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile
base = Path("build/deps/voicechat-bukkit-2.6.25.jar")
target = Path("build/AsminVoiceChat-${{ needs.prepare-release.outputs.version }}.jar")
replacements = {
"de/maxhenkel/voicechat/voice/common/Secret.class": Path("build/classes/de/maxhenkel/voicechat/voice/common/Secret.class"),
"de/maxhenkel/voicechat/voice/server/ServerVoiceEvents.class": Path("build/classes/de/maxhenkel/voicechat/voice/server/ServerVoiceEvents.class"),
"plugin.yml": Path("build/plugin.yml"),
}
with ZipFile(base) as source, ZipFile(target, "w", ZIP_DEFLATED) as output:
for item in source.infolist():
replacement = replacements.get(item.filename)
output.writestr(item, replacement.read_bytes() if replacement else source.read(item.filename))
print(f"Built {target}")
PY
- name: Run CBC/GCM interoperability test
shell: bash
run: |
set -euo pipefail
cp="build/AsminVoiceChat-${{ needs.prepare-release.outputs.version }}.jar:build/deps/netty-buffer.jar:build/deps/netty-common.jar"
javac --release 8 -cp "$cp" -d build/test-classes tests/ProtocolCryptoSelfTest.java
java -cp "build/test-classes:$cp" ProtocolCryptoSelfTest
- name: Upload JVM-specific build to release
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_SERVER_URL: ${{ gitea.server_url }}
GITEA_REPOSITORY: ${{ gitea.repository }}
RELEASE_ID: ${{ needs.prepare-release.outputs.release_id }}
VERSION: ${{ needs.prepare-release.outputs.version }}
JAVA_VERSION: ${{ matrix.java }}
shell: bash
run: |
set -euo pipefail
api="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY"
file="build/AsminVoiceChat-${VERSION}.jar"
asset="AsminVoiceChat-${VERSION}-Java${JAVA_VERSION}.jar"
curl --fail-with-body -sS -X POST "$api/releases/$RELEASE_ID/assets?name=$asset" \
-H "Authorization: token $GITEA_TOKEN" \ -H "Authorization: token $GITEA_TOKEN" \
-F "attachment=@$file;filename=$asset" -F "attachment=@$jar;filename=AsminVoiceChat-${VERSION}.jar"
if [[ "$JAVA_VERSION" == "21" ]]; then
curl --fail-with-body -sS -X POST "$api/releases/$RELEASE_ID/assets?name=AsminVoiceChat-${VERSION}.jar" \
-H "Authorization: token $GITEA_TOKEN" \
-F "attachment=@$file;filename=AsminVoiceChat-${VERSION}.jar"
fi
+4
View File
@@ -0,0 +1,4 @@
.gradle/
build/
*.class
*.log
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
Copyright (c) Max Henkel - All Rights Reserved
+4 -2
View File
@@ -3,6 +3,7 @@
Simple Voice Chat server fork based on 2.6.25. It accepts protocol compatibility Simple Voice Chat server fork based on 2.6.25. It accepts protocol compatibility
18 (2.5.x clients) and 20 (2.6.x clients) at the same time. Clients remain 18 (2.5.x clients) and 20 (2.6.x clients) at the same time. Clients remain
unmodified; each player still needs a mod release for their Minecraft version. unmodified; each player still needs a mod release for their Minecraft version.
The plugin is compiled to Java 8 bytecode and runs on newer JVMs.
## Architecture ## Architecture
@@ -12,8 +13,9 @@ uses AES-CBC for 2.5 clients and AES-GCM for 2.6 clients. Shared packet layouts
are passed through the existing 2.6 voice routing. The fork changes only the are passed through the existing 2.6 voice routing. The fork changes only the
per-client cipher selection and compatibility-version acceptance. per-client cipher selection and compatibility-version acceptance.
The full source is in `source/decompiled/`. The two changed classes are also The Bukkit plugin source and API source are in `source/bukkit/` and
available separately in `source/patch/`. `source/api/`. The cipher and compatibility changes are in the regular source
files for those modules.
## Goxy ## Goxy
+160
View File
@@ -0,0 +1,160 @@
import groovy.json.JsonSlurper
plugins {
id 'java'
id 'com.gradleup.shadow' version '9.6.0'
}
group = 'de.maxhenkel.voicechat'
version = findProperty('buildVersion') ?: '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
maven { url = 'https://hub.spigotmc.org/nexus/content/groups/public/' }
maven { url = 'https://libraries.minecraft.net/' }
maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' }
maven { url = 'https://maven.maxhenkel.de/repository/public' }
maven { url = 'https://repo.viaversion.com/' }
}
configurations {
shadow
}
dependencies {
compileOnly 'org.bukkit:bukkit:1.8.8-R0.1-SNAPSHOT'
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
compileOnly 'me.clip:placeholderapi:2.12.2'
compileOnly 'com.viaversion:viaversion-bukkit:5.7.2'
compileOnly 'com.viaversion:viaversion-common:5.7.2'
implementation 'io.netty:netty-all:4.1.132.Final'
implementation 'org.json:json:20251224'
shadow 'org.json:json:20251224'
implementation 'me.lucko:commodore:2.2'
shadow 'me.lucko:commodore:2.2'
implementation 'org.concentus:Concentus:1.0.1'
shadow 'org.concentus:Concentus:1.0.1'
implementation 'de.maxhenkel.configbuilder:configbuilder:2.1.0'
shadow 'de.maxhenkel.configbuilder:configbuilder:2.1.0'
}
sourceSets {
main {
java {
srcDirs = [
'source/api/src/main/java',
'source/bukkit/src/main/java',
layout.buildDirectory.dir('generated/sources/java').get().asFile
]
}
resources {
srcDirs = ['source/bukkit/src/main/resources']
exclude 'plugin.yml'
}
}
test {
java {
srcDirs = ['tests']
}
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.release = 8
}
tasks.register('generateBuildConstants', Copy) {
from 'source/bukkit/src/template/java'
into layout.buildDirectory.dir('generated/sources/java')
expand compatibility_version: '20',
mod_compatible_version: '2.6.x',
target_bukkit_version: '1.8.8-R0.1-SNAPSHOT',
plugin_name: 'Simple Voice Chat'
}
compileJava.dependsOn tasks.named('generateBuildConstants')
tasks.register('generateFallbackTranslations') {
def inputFile = file('source/common/src/main/resources/assets/voicechat/lang/en_us.json')
def outputFile = layout.buildDirectory.file('generated/sources/java/de/maxhenkel/voicechat/FallbackTranslations.java')
inputs.file inputFile
outputs.file outputFile
doLast {
def translations = new JsonSlurper().parse(inputFile)
def output = new StringBuilder('''package de.maxhenkel.voicechat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class FallbackTranslations {
public static final Map<String, String> FALLBACK_TRANSLATIONS;
static {
Map<String, String> translations = new HashMap<>();
''')
translations.each { key, value ->
def k = key.toString().replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
def v = value.toString().replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
output.append(" translations.put(\"${k}\", \"${v}\");\n")
}
output.append(''' FALLBACK_TRANSLATIONS = Collections.unmodifiableMap(translations);
}
}
''')
outputFile.get().asFile.parentFile.mkdirs()
outputFile.get().asFile.text = output.toString()
}
}
compileJava.dependsOn tasks.named('generateFallbackTranslations')
processResources {
from('plugin.yml') {
filter { String line ->
line.startsWith('version:') ? "version: '${project.version}'" : line
}
}
}
tasks.named('jar') {
archiveClassifier = 'dev'
}
shadowJar {
configurations = [project.configurations.shadow]
archiveClassifier = ''
archiveFileName = "AsminVoiceChat-${project.version}.jar"
from('LICENSE-upstream.txt') {
rename { 'LICENSE_SimpleVoiceChat' }
}
relocate 'org.json', 'de.maxhenkel.voicechat.org.json'
relocate 'me.lucko.commodore', 'de.maxhenkel.voicechat.commodore'
relocate 'org.concentus', 'de.maxhenkel.voicechat.concentus'
relocate 'de.maxhenkel.configbuilder', 'de.maxhenkel.voicechat.configbuilder'
dependencies {
exclude(dependency('com.mojang:brigadier:.*'))
exclude(dependency('com.google.code.findbugs:jsr305:.*'))
}
}
tasks.named('build') {
dependsOn 'shadowJar'
dependsOn 'cryptoTest'
}
tasks.register('cryptoTest', JavaExec) {
dependsOn tasks.named('testClasses')
classpath = sourceSets.test.runtimeClasspath + files(shadowJar.archiveFile)
mainClass = 'ProtocolCryptoSelfTest'
}
tasks.withType(Test).configureEach {
failOnNoDiscoveredTests = false
}
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx2G
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-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+82
View File
@@ -0,0 +1,82 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
+8
View File
@@ -0,0 +1,8 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
}
}
rootProject.name = 'AsminVoicechat'
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api;
public interface BukkitVoicechatService {
/**
* Registers the voice chat plugin on bukkit based servers.
*
* @param plugin the voicechat plugin
*/
void registerPlugin(VoicechatPlugin plugin);
}
@@ -0,0 +1,21 @@
package de.maxhenkel.voicechat.api;
import java.net.SocketAddress;
/**
* A socket used for client side voice chat traffic.
* Can be set using {@link de.maxhenkel.voicechat.api.events.ClientVoicechatInitializationEvent#setSocketImplementation(ClientVoicechatSocket)}.
*/
public interface ClientVoicechatSocket {
void open() throws Exception;
RawUdpPacket read() throws Exception;
void send(byte[] data, SocketAddress address) throws Exception;
void close();
boolean isClosed();
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api;
import java.util.UUID;
public interface Entity {
/**
* @return the UUID of the entity
*/
UUID getUuid();
/**
* @return the actual entity object
*/
Object getEntity();
/**
* @return the current position of the entity
*/
Position getPosition();
}
@@ -1,12 +1,8 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.api; package de.maxhenkel.voicechat.api;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@Retention(value=RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface ForgeVoicechatPlugin { public @interface ForgeVoicechatPlugin {
} }
@@ -0,0 +1,115 @@
package de.maxhenkel.voicechat.api;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* Groups can be created using {@link VoicechatServerApi#groupBuilder()}.
*/
public interface Group {
/**
* @return the visual name of the group
*/
String getName();
/**
* @return if the group has a password
*/
boolean hasPassword();
/**
* @return the ID of the group
*/
UUID getId();
/**
* @return if the group is persistent
*/
boolean isPersistent();
/**
* @return if the group is hidden in the group list UI
*/
boolean isHidden();
/**
* @return the group type
*/
Type getType();
public interface Type {
/**
* Players in a group can hear nearby players that are not in a group
*/
public static final Type NORMAL = new Type() {
};
/**
* Players in a group can hear nearby players and nearby players can hear players in the group
*/
public static final Type OPEN = new Type() {
};
/**
* Players in a group can only hear other players in the group
*/
public static final Type ISOLATED = new Type() {
};
}
public interface Builder {
/**
* Sets the ID of the group.
* If this is not set, the ID will be randomly generated.
* <br/>
* <b>NOTE</b>: If there is already a group with the same ID, the group will be overwritten.
*
* @param id the group ID or <code>null</code> if the ID should be randomly generated
* @return the builder
*/
Builder setId(@Nullable UUID id);
/**
* <b>NOTE</b>: The name might be stripped of special characters and whitespace.
*
* @param name the name of the group
* @return the builder
*/
Builder setName(String name);
/**
* @param password the group password
* @return the builder
*/
Builder setPassword(@Nullable String password);
/**
* @param persistent if the group should be persistent
* @return the builder
*/
Builder setPersistent(boolean persistent);
/**
* @param hidden if the group should be hidden in the group list UI
* @return the builder
*/
Builder setHidden(boolean hidden);
/**
* @param type the group type
* @return the builder
*/
Builder setType(Type type);
/**
* @return the built group
* @throws IllegalStateException if the name is not set or invalid
*/
Group build();
}
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api;
public interface Player extends Entity {
/**
* @return the actual player object
*/
Object getPlayer();
}
@@ -0,0 +1,20 @@
package de.maxhenkel.voicechat.api;
public interface Position {
/**
* @return the X position
*/
double getX();
/**
* @return the Y position
*/
double getY();
/**
* @return the Z position
*/
double getZ();
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api;
import java.net.SocketAddress;
public interface RawUdpPacket {
byte[] getData();
long getTimestamp();
SocketAddress getSocketAddress();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api;
public interface ServerLevel {
/**
* @return the actual level object
*/
Object getServerLevel();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api;
public interface ServerPlayer extends Player {
/**
* @return the level of the player
*/
ServerLevel getServerLevel();
}
@@ -0,0 +1,116 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.audio.AudioConverter;
import de.maxhenkel.voicechat.api.mp3.Mp3Decoder;
import de.maxhenkel.voicechat.api.mp3.Mp3Encoder;
import de.maxhenkel.voicechat.api.opus.OpusDecoder;
import de.maxhenkel.voicechat.api.opus.OpusEncoder;
import de.maxhenkel.voicechat.api.opus.OpusEncoderMode;
import javax.annotation.Nullable;
import javax.sound.sampled.AudioFormat;
import java.io.InputStream;
import java.io.OutputStream;
public interface VoicechatApi {
/**
* Creates a new opus encoder.
* Note that the encoder needs to be closed after you are finished using it.
*/
OpusEncoder createEncoder();
/**
* Creates a new opus encoder.
* Note that the encoder needs to be closed after you are finished using it.
*
* @param mode the opus encoder mode
* @return the opus encoder
*/
OpusEncoder createEncoder(OpusEncoderMode mode);
/**
* Creates a new opus decoder.
* Note that the decoder needs to be closed after you are finished using it.
*
* @return the opus decoder
*/
OpusDecoder createDecoder();
/**
* <b>NOTE</b>: This is not available for Bukkit! It will always return null.
*
* @param audioFormat the audio format
* @param bitrate the bitrate in kbps
* @param quality the quality from 0 (highest) to 9 (lowest)
* @param outputStream the output stream to write the mp3 to
* @return the mp3 encoder or null if the encoder could not be initialized or is not available
*/
@Nullable
Mp3Encoder createMp3Encoder(AudioFormat audioFormat, int bitrate, int quality, OutputStream outputStream);
/**
* <b>NOTE</b>: This is not available for Bukkit! It will always return null.
*
* @param inputStream the input stream to read the mp3 from
* @return the mp3 decoder or null if the decoder could not be initialized or is not available
*/
@Nullable
Mp3Decoder createMp3Decoder(InputStream inputStream);
/**
* @return the audio converter
*/
AudioConverter getAudioConverter();
/**
* Creates an entity object from an actual entity.
*
* @param entity the entity implementation of your mod/plugin loader
* @return the entity object
*/
Entity fromEntity(Object entity);
/**
* Creates a level object from an actual level.
*
* @param serverLevel the level implementation of your mod/plugin loader
* @return the level
*/
ServerLevel fromServerLevel(Object serverLevel);
/**
* Creates a player object from an actual player.
*
* @param serverPlayer the player implementation of your mod/plugin loader
* @return the player
*/
ServerPlayer fromServerPlayer(Object serverPlayer);
/**
* Creates a new position object.
*
* @param x the X position
* @param y the Y position
* @param z the Z position
* @return the position with the provided coordinates
*/
Position createPosition(double x, double y, double z);
/**
* Don't forget to register your category with {@link VoicechatServerApi#registerVolumeCategory(VolumeCategory)} or {@link VoicechatClientApi#registerClientVolumeCategory(VolumeCategory)}
*
* @return a builder to build a category
*/
VolumeCategory.Builder volumeCategoryBuilder();
/**
* <b>NOTE</b>: Voice chat plugins can change this.
* This is only the default value.
* This value might not be correct if you are not connected to a server.
*
* @return the default distance, audio from voice chat is heard from
*/
double getVoiceChatDistance();
}
@@ -0,0 +1,166 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.audiochannel.ClientEntityAudioChannel;
import de.maxhenkel.voicechat.api.audiochannel.ClientLocationalAudioChannel;
import de.maxhenkel.voicechat.api.audiochannel.ClientStaticAudioChannel;
import de.maxhenkel.voicechat.api.config.ConfigAccessor;
import de.maxhenkel.voicechat.api.config.VolumeConfigAccessor;
import javax.annotation.Nullable;
import java.util.UUID;
public interface VoicechatClientApi extends VoicechatApi {
/**
* @return if the local voice chat is muted
*/
boolean isMuted();
/**
* Does the same as {@link #isDisabled(UUID)} with <code>null</code> as the player UUID.
*
* @return if the local voice chat is disabled
*/
default boolean isDisabled() {
return isDisabled(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID has the voice chat disabled
*/
boolean isDisabled(@Nullable UUID playerId);
/**
* Does the same as {@link #isDisconnected(UUID)} with <code>null</code> as the player UUID.
*
* @return if the voice chat is disconnected from the server
*/
default boolean isDisconnected() {
return isDisconnected(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID is disconnected from voice chat
*/
boolean isDisconnected(@Nullable UUID playerId);
/**
* Does the same as {@link #isTalking(UUID)} with <code>null</code> as the player UUID.
*
* @return if the local player is talking
*/
default boolean isTalking() {
return isTalking(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID is currently talking
*/
boolean isTalking(@Nullable UUID playerId);
/**
* Does the same as {@link #isWhispering(UUID)} with <code>null</code> as the player UUID.
*
* @return if the local player is whispering
*/
default boolean isWhispering() {
return isWhispering(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID is currently whispering
*/
boolean isWhispering(@Nullable UUID playerId);
/**
* This method returns if the push to talk key is pressed, even when using voice activation.
*
* @return if the push to talk key is pressed
*/
boolean isPushToTalkKeyPressed();
/**
* @return if the whisper key is pressed
*/
boolean isWhisperKeyPressed();
@Nullable
Group getGroup();
/**
* Creates a client side entity audio channel.
*
* @param uuid the UUID of the entity
* @return the audio channel
* @deprecated use {@link #createEntityAudioChannel(UUID, Entity)}
*/
@Deprecated
ClientEntityAudioChannel createEntityAudioChannel(UUID uuid);
/**
* Creates a client side entity audio channel.
*
* @param uuid the ID of the channel
* @param entity the entity to attach the channel to
* @return the audio channel
*/
ClientEntityAudioChannel createEntityAudioChannel(UUID uuid, Entity entity);
/**
* Creates a client side locational audio channel.
*
* @param uuid the ID of the channel
* @return the audio channel
*/
ClientLocationalAudioChannel createLocationalAudioChannel(UUID uuid, Position position);
/**
* Creates a client side static audio channel.
*
* @param uuid the ID of the channel
* @return the audio channel
*/
ClientStaticAudioChannel createStaticAudioChannel(UUID uuid);
/**
* Registers a volume category just for this client.
* A category can be created with {@link VoicechatApi#volumeCategoryBuilder()}.
* The category can be unregistered with {@link #unregisterClientVolumeCategory}.
*
* @param category the category to register
*/
void registerClientVolumeCategory(VolumeCategory category);
/**
* Unregisters a category on this client.
* This will release the texture ID for the icon if one exists.
*
* @param category the category to remove
*/
default void unregisterClientVolumeCategory(VolumeCategory category) {
unregisterClientVolumeCategory(category.getId());
}
/**
* Unregisters a category on this client.
* This will release the texture ID for the icon if one exists.
*
* @param categoryId the category ID to remove
*/
void unregisterClientVolumeCategory(String categoryId);
/**
* @return a read-only config accessor for the mods client config
*/
ConfigAccessor getClientConfig();
/**
* @return a read-only accessor for player and category volumes
*/
VolumeConfigAccessor getVolumeConfig();
}
@@ -0,0 +1,80 @@
package de.maxhenkel.voicechat.api;
import javax.annotation.Nullable;
/**
* <b>Note</b>: It is not guaranteed that the state of this connection object is up to date.
* Please re-fetch the connection object to get the latest state.
*/
public interface VoicechatConnection {
/**
* <b>Note</b>: This only returns the group the player was in when fetching this connection object.
* Calling {@link #setGroup(Group)} won't update the return value of this method.
*
* @return the group of the player - <code>null</code> if the player is not in a group
*/
@Nullable
Group getGroup();
/**
* <b>Note</b>: This only returns if the player was in a group when fetching this connection object.
* Calling {@link #setGroup(Group)} won't update the return value of this method.
*
* @return if the player is in a group
*/
boolean isInGroup();
/**
* Joins this player to the provided group.
*
* @param group the group to join or <code>null</code> to leave the current group
*/
void setGroup(@Nullable Group group);
/**
* This might not represent the actual state of the voice chat connection, since other voice chat plugins can fake having a player connected.
* <br/>
* <b>Note</b>: This only returns if the player was connected fetching this connection object.
* Calling {@link #setConnected(boolean)} won't update the return value of this method.
*
* @return if the player is connected to voice chat
*/
boolean isConnected();
/**
* The players disconnected state will reset if its actual disconnected state changes or if the player reconnects.
* <br/>
* <b>NOTE</b>: This method will only work for players that don't have the mod installed. See {@link #isInstalled()}.
*
* @param connected if the player should be shown as connected to voice chat
*/
void setConnected(boolean connected);
/**
* <b>Note</b>: This only returns if the player has the voice chat disabled when fetching this connection object.
* Calling {@link #setDisabled(boolean)} won't update the return value of this method.
*
* @return If the player muted the sound
*/
boolean isDisabled();
/**
* Sets the players disabled state.
* <b>NOTE</b>: This method will only work for players that don't have the mod installed. See {@link #isInstalled()}.
*
* @param disabled if the player should have the voice chat disabled icon
*/
void setDisabled(boolean disabled);
/**
* @return if the player has a version of voice chat installed that is compatible with the server
*/
boolean isInstalled();
/**
* @return the player
*/
ServerPlayer getPlayer();
}
@@ -0,0 +1,26 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.events.EventRegistration;
public interface VoicechatPlugin {
/**
* @return the ID of this plugin - Has to be unique
*/
String getPluginId();
/**
* Called after loading the plugin.
*/
default void initialize(VoicechatApi api) {
}
/**
* Register your events here - Only here!
*
* @param registration the event registration object, used to register events
*/
default void registerEvents(EventRegistration registration) {
}
}
@@ -0,0 +1,343 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.audiochannel.*;
import de.maxhenkel.voicechat.api.audiolistener.AudioListener;
import de.maxhenkel.voicechat.api.audiolistener.PlayerAudioListener;
import de.maxhenkel.voicechat.api.audiosender.AudioSender;
import de.maxhenkel.voicechat.api.config.ConfigAccessor;
import de.maxhenkel.voicechat.api.opus.OpusEncoder;
import de.maxhenkel.voicechat.api.packets.ConvertablePacket;
import de.maxhenkel.voicechat.api.packets.EntitySoundPacket;
import de.maxhenkel.voicechat.api.packets.LocationalSoundPacket;
import de.maxhenkel.voicechat.api.packets.StaticSoundPacket;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface VoicechatServerApi extends VoicechatApi {
/**
* Sends the sound packet to the provided connection.
*
* @param connection the connection to send the packet to
* @param packet the packet to send
*/
void sendEntitySoundPacketTo(VoicechatConnection connection, EntitySoundPacket packet);
/**
* Sends the sound packet to the provided connection.
*
* @param connection the connection to send the packet to
* @param packet the packet to send
*/
void sendLocationalSoundPacketTo(VoicechatConnection connection, LocationalSoundPacket packet);
/**
* Sends the sound packet to the provided connection.
*
* @param connection the connection to send the packet to
* @param packet the packet to send
*/
void sendStaticSoundPacketTo(VoicechatConnection connection, StaticSoundPacket packet);
/**
* Creates a sound packet that can be converted to any type.
* <b>NOTE</b>: When creating a packet using this method, the sequence number must be set manually using {@link de.maxhenkel.voicechat.api.packets.SoundPacket.Builder#sequenceNumber(long)}.
*
* @return a convertable packet without any data
*/
ConvertablePacket createPacket();
/**
* Creates a sound channel for the specified entity.
*
* @param channelId the ID of the channel - Has to be unique
* @param entity the entity to attach the channel to
* @return the channel
*/
@Nullable
EntityAudioChannel createEntityAudioChannel(UUID channelId, Entity entity);
/**
* Creates a sound channel at the provided location.
*
* @param channelId the ID of the channel - Has to be unique
* @param level the world
* @param initialPosition the position where the sound should be played
* @return the channel
*/
@Nullable
LocationalAudioChannel createLocationalAudioChannel(UUID channelId, ServerLevel level, Position initialPosition);
/**
* Creates a static audio channel.
*
* @param channelId the ID of the channel - Has to be unique
* @param level the level
* @param connection the connection that should hear the audio
* @return the channel
* @deprecated use {@link #createStaticAudioChannel(UUID)} and add a target using {@link StaticAudioChannel#addTarget(VoicechatConnection)}
*/
@Deprecated
@Nullable
StaticAudioChannel createStaticAudioChannel(UUID channelId, ServerLevel level, VoicechatConnection connection);
/**
* Creates a static audio channel.
*
* @param channelId the ID of the channel - Has to be unique
* @return the channel
*/
@Nullable
StaticAudioChannel createStaticAudioChannel(UUID channelId);
/**
* Creates a new audio player.
* <br/>
* <br/>
* <b>NOTE</b>: Never use more than one audio player for every audio channel.
*
* @param audioChannel the channel where the audio player should send the audio to
* @param encoder the opus encoder used to encode the audio data
* @param audioSupplier this gets called whenever a new audio frame needs to be sent. The size of the array always needs to be 960. To end the playback, return <code>null</code>
* @return the audio player
*/
AudioPlayer createAudioPlayer(AudioChannel audioChannel, OpusEncoder encoder, Supplier<short[]> audioSupplier);
/**
* Creates a new audio player.
* <br/>
* <br/>
* <b>NOTE</b>: Never use more than one audio player for every audio channel.
*
* @param audioChannel the channel where the audio player should send the audio to
* @param encoder the opus encoder used to encode the audio data
* @param audio the audio data
* @return the audio player
*/
AudioPlayer createAudioPlayer(AudioChannel audioChannel, OpusEncoder encoder, short[] audio);
/**
* Creates a new audio sender.
* <br/>
* This can be used to simulate a player sending microphone packets.
* This needs to be registered using {@link #registerAudioSender(AudioSender)} and unregistered using {@link #unregisterAudioSender(AudioSender)}.
*
* @param connection the connection of the player
* @return the audio sender
*/
AudioSender createAudioSender(VoicechatConnection connection);
/**
* <b>NOTE</b>: Only one instance of this can exist per player. This will return <code>false</code> if an audio sender for this player already exists.
* <br/>
* <b>NOTE</b>: The audio sender will only work for players that are connected to the server and don't have the mod installed. Otherwise, it will return <code>false</code>.
*
* @param sender the sender to register
* @return if the sender was registered
*/
boolean registerAudioSender(AudioSender sender);
/**
* Unregisters an audio sender.
*
* @param sender the sender to unregister
* @return <code>false</code> if the audio sender was not registered.
*/
boolean unregisterAudioSender(AudioSender sender);
/**
* @return a {@link PlayerAudioListener} builder
*/
PlayerAudioListener.Builder playerAudioListenerBuilder();
/**
* Registers a new {@link AudioListener}.
* <br/>
* Returns false if the listener is already registered.
*
* @param listener the listener to register
* @return if the listener was registered
*/
boolean registerAudioListener(AudioListener listener);
/**
* Unregisters an {@link AudioListener}.
* <br/>
* Returns false if the listener is already unregistered.
*
* @param listener the listener to unregister
* @return if the listener was unregistered
*/
boolean unregisterAudioListener(AudioListener listener);
/**
* Unregisters an {@link AudioListener}.
* <br/>
* Returns false if the listener is already unregistered.
*
* @param listenerId the {@link AudioListener#getListenerId()} of the listener to unregister
* @return if the listener was unregistered
*/
boolean unregisterAudioListener(UUID listenerId);
/**
* Gets the connection of the player with this UUID.
*
* @param playerUuid the players UUID
* @return the connection or <code>null</code> if the player is not connected
*/
@Nullable
VoicechatConnection getConnectionOf(UUID playerUuid);
/**
* Gets the connection of the player.
*
* @param player the player
* @return the connection or <code>null</code> if the player is not connected
*/
@Nullable
default VoicechatConnection getConnectionOf(ServerPlayer player) {
return getConnectionOf(player.getUuid());
}
/**
* Creates a new group.
*
* @param name the name of the group
* @param password the password of the group - <code>null</code> for no password
* @return the group
* @deprecated use {@link #groupBuilder()} instead
*/
@Deprecated
Group createGroup(String name, @Nullable String password);
/**
* Creates a new group.
*
* @param name the name of the group
* @param password the password of the group - <code>null</code> for no password
* @param persistent if the group should be persistent
* @return the group
* @deprecated use {@link #groupBuilder()} instead
*/
@Deprecated
Group createGroup(String name, @Nullable String password, boolean persistent);
/**
* @return a new group builder
*/
Group.Builder groupBuilder();
/**
* Removes a persistent group.
*
* <b>NOTE</b>: You can't remove a group that is not persistent or has players in it.
*
* @param groupId the ID of the group
* @return if the group was removed
*/
boolean removeGroup(UUID groupId);
/**
* Gets a group by its ID.
*
* @param groupId the ID of the group
* @return the group or <code>null</code> if the group doesn't exist
*/
@Nullable
Group getGroup(UUID groupId);
/**
* @return all groups
*/
Collection<Group> getGroups();
/**
* Gets the secret for the user with this <code>userId</code>.
* Calling this function with a players UUID, that is already connected, this will return the secret of that player.
* Calling it with a new random UUID this will return a new secret.
* Use this with caution - Keep this secret confidential, as this allows connecting to the voice chat as this person.
* Note that if this <code>userId</code> is a players UUID and this player disconnects, the secret will get removed and thus is no longer valid to connect with.
*
* @param userId the user ID
* @return the secret or <code>null</code> if no server is running
* @deprecated this will just return <code>null</code>
*/
@Nullable
@Deprecated
UUID getSecret(UUID userId);
/**
* A convenience method to get all players in the range of a specific location.
*
* @param level the world
* @param pos the location
* @param range the range
* @param filter the filter to exclude specific players
* @return all players in the provided location
*/
Collection<ServerPlayer> getPlayersInRange(ServerLevel level, Position pos, double range, Predicate<ServerPlayer> filter);
/**
* The distance at which audio packets are broadcast.
* <b>NOTE</b>: This is not the distance it can be heard.
*
* @return the maximum distance, voice chat audio packets can be received
*/
double getBroadcastRange();
/**
* A convenience method to get all players in the range of a specific location.
*
* @param level the world
* @param pos the location
* @param range the range
* @return all players in the provided location
*/
default Collection<ServerPlayer> getPlayersInRange(ServerLevel level, Position pos, double range) {
return getPlayersInRange(level, pos, range, null);
}
/**
* Registers a volume category.
* This will get synchronized to all connected clients and all future clients that will connect.
* A category can be created with {@link VoicechatApi#volumeCategoryBuilder()}.
* The category can be unregistered with {@link #unregisterVolumeCategory}.
*
* @param category the category to register
*/
void registerVolumeCategory(VolumeCategory category);
/**
* Unregisters a category for all connected players.
* This will release the texture ID for the icon if one exists on all clients.
*
* @param category the category to remove
*/
default void unregisterVolumeCategory(VolumeCategory category) {
unregisterVolumeCategory(category.getId());
}
/**
* Unregisters a category for all connected players.
* This will release the texture ID for the icon if one exists on all clients.
*
* @param categoryId the category ID to remove
*/
void unregisterVolumeCategory(String categoryId);
/**
* @return all registered volume categories
*/
Collection<VolumeCategory> getVolumeCategories();
/**
* @return a read-only config accessor for the mods server config
*/
ConfigAccessor getServerConfig();
}
@@ -0,0 +1,23 @@
package de.maxhenkel.voicechat.api;
import java.net.SocketAddress;
/**
* A socket used for server side voice chat traffic.
* Can be set using {@link de.maxhenkel.voicechat.api.events.VoicechatServerStartingEvent#setSocketImplementation(VoicechatSocket)}.
*/
public interface VoicechatSocket {
void open(int port, String bindAddress) throws Exception;
RawUdpPacket read() throws Exception;
void send(byte[] data, SocketAddress address) throws Exception;
int getLocalPort();
void close();
boolean isClosed();
}
@@ -0,0 +1,97 @@
package de.maxhenkel.voicechat.api;
import javax.annotation.Nullable;
/**
* A custom volume category for the "adjust volumes screen".
* Create it with {@link VoicechatApi#volumeCategoryBuilder()}.
* Register it with {@link VoicechatServerApi#registerVolumeCategory(VolumeCategory)} or {@link VoicechatClientApi#registerClientVolumeCategory(VolumeCategory)}.
* Unregister it with {@link VoicechatServerApi#unregisterVolumeCategory} or {@link VoicechatClientApi#unregisterClientVolumeCategory}.
*/
public interface VolumeCategory {
/**
* @return the unique ID of the category
*/
String getId();
/**
* @return the full name of the category, that is displayed for the end user
*/
String getName();
/**
* @return the translation key for the full name of the category
*/
@Nullable
String getNameTranslationKey();
/**
* @return the hover tooltip that is shown for this category
*/
@Nullable
String getDescription();
/**
* @return the translation key for the hover tooltip of the category
*/
@Nullable
String getDescriptionTranslationKey();
/**
* @return the icon that is shown for this category
*/
@Nullable
int[][] getIcon();
public interface Builder {
/**
* This ID has to be between 1 and 16 characters and can only contain lowercase <code>a-z</code> and <code>_</code>.
*
* @param id the unique ID of the category
* @return the builder
*/
Builder setId(String id);
/**
* @param name the full name of the category, that is displayed for the end user if no translation is present
* @return the builder
*/
Builder setName(String name);
/**
* @param translationKey the translation key for the full name of the category
* @return the builder
*/
Builder setNameTranslationKey(@Nullable String translationKey);
/**
* @param description the hover tooltip that is shown for this category if no translation is present
* @return the builder
*/
Builder setDescription(@Nullable String description);
/**
* @param translationKey the translation key for the hover tooltip
* @return the builder
*/
Builder setDescriptionTranslationKey(@Nullable String translationKey);
/**
* The array has to be 16x16.
* Each integer represents a pixel in the RGBA format.
*
* @param icon the icon that is shown for this category
* @return the builder
*/
Builder setIcon(@Nullable int[][] icon);
/**
* @return the built category
*/
VolumeCategory build();
}
}
@@ -0,0 +1,27 @@
package de.maxhenkel.voicechat.api.audio;
import de.maxhenkel.voicechat.api.VoicechatApi;
/**
* A utility class to convert audio between different representations.
* <br/>
* Can be obtained by calling {@link VoicechatApi#getAudioConverter()}.
* <br/>
* <br/>
* <b>NOTE</b>: Everything is assumed to be 16 bit PCM audio.
*/
public interface AudioConverter {
short[] bytesToShorts(byte[] bytes);
byte[] shortsToBytes(short[] shorts);
short[] floatsToShorts(float[] floats);
float[] shortsToFloats(short[] shorts);
byte[] floatsToBytes(float[] floats);
float[] bytesToFloats(byte[] bytes);
}
@@ -0,0 +1,4 @@
/**
* Everything related to audio conversion and manipulation.
*/
package de.maxhenkel.voicechat.api.audio;
@@ -0,0 +1,65 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.ServerPlayer;
import de.maxhenkel.voicechat.api.VolumeCategory;
import de.maxhenkel.voicechat.api.packets.MicrophonePacket;
import javax.annotation.Nullable;
import java.util.UUID;
import java.util.function.Predicate;
public interface AudioChannel {
/**
* Sends the audio data to this audio channel.
*
* @param opusData Opus encoded audio data
*/
void send(byte[] opusData);
/**
* Forwards the provided microphone packet to this audio channel.
*
* @param packet the microphone packet containing the audio data
*/
void send(MicrophonePacket packet);
/**
* Applies a filter to the audio channel.
*
* @param filter only sends the data to players where the filter applies
*/
void setFilter(Predicate<ServerPlayer> filter);
/**
* Call this if you are finished sending data.
* You can still use this audio channel after calling this function,
* just don't call it after every packet, only after you are finished sending data.
*/
void flush();
/**
* @return if the audio channel is closed
*/
boolean isClosed();
/**
* @return the ID of the audio channel
*/
UUID getId();
/**
* @return the category ID of the audio channel
*/
@Nullable
String getCategory();
/**
* Make sure you registered your category before using it.
* See {@link de.maxhenkel.voicechat.api.VoicechatServerApi#registerVolumeCategory(VolumeCategory)}.
*
* @param category the category ID of the audio channel
*/
void setCategory(@Nullable String category);
}
@@ -0,0 +1,39 @@
package de.maxhenkel.voicechat.api.audiochannel;
/**
* Streams audio data from the server to clients.
* A player instance can be obtained by calling {@link de.maxhenkel.voicechat.api.VoicechatServerApi#createAudioPlayer}.
*/
public interface AudioPlayer {
/**
* Starts playing/streaming the audio.
*/
void startPlaying();
/**
* Stops playing/streaming the audio.
*/
void stopPlaying();
/**
* @return if the player has been started
*/
boolean isStarted();
/**
* @return if the audio is still playing
*/
boolean isPlaying();
/**
* @return if the player stopped playing
*/
boolean isStopped();
/**
* @param onStopped a runnable that's called when the player is finished playing
*/
void setOnStopped(Runnable onStopped);
}
@@ -0,0 +1,38 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.VolumeCategory;
import javax.annotation.Nullable;
import java.util.UUID;
public interface ClientAudioChannel {
/**
* @return the ID of the channel
*/
UUID getId();
/**
* Plays this audio data on this audio channel.
* <br>
* Providing audio that's not 960 samples will be ignored.
*
* @param rawAudio the raw 16 bit PCM audio frame
*/
void play(short[] rawAudio);
/**
* @return the category ID of the audio channel
*/
@Nullable
String getCategory();
/**
* Make sure you registered your category before using it.
* See {@link de.maxhenkel.voicechat.api.VoicechatServerApi#registerVolumeCategory(VolumeCategory)}.
*
* @param category the category of the audio channel
*/
void setCategory(@Nullable String category);
}
@@ -0,0 +1,32 @@
package de.maxhenkel.voicechat.api.audiochannel;
import java.util.UUID;
public interface ClientEntityAudioChannel extends ClientAudioChannel {
/**
* @return the UUID of the entity
*/
UUID getEntityId();
/**
* @param whispering if the entity should whisper
*/
void setWhispering(boolean whispering);
/**
* @return if the entity is whispering
*/
boolean isWhispering();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,29 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.Position;
public interface ClientLocationalAudioChannel extends ClientAudioChannel {
/**
* @return the current location of the audio channel
*/
Position getLocation();
/**
* Updates the location of the audio channel.
*
* @param position the location of the audio channel
*/
void setLocation(Position position);
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.audiochannel;
public interface ClientStaticAudioChannel extends ClientAudioChannel {
}
@@ -0,0 +1,43 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.Entity;
/**
* An audio channel that is bound to an entity.
* <b>NOTE</b>: If you are using this for a player, you need to use {@link #updateEntity(Entity)} to update the player after it died.
*/
public interface EntityAudioChannel extends AudioChannel {
/**
* @return if the entity is whispering
*/
boolean isWhispering();
/**
* @param whispering if the entity should whisper
*/
void setWhispering(boolean whispering);
/**
* Sets a new entity where this channel is attached to.
*
* @param entity the entity to attach the channel to
*/
void updateEntity(Entity entity);
/**
* @return the entity where the channel is attached to
*/
Entity getEntity();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,29 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.Position;
public interface LocationalAudioChannel extends AudioChannel {
/**
* Updates the location of the audio.
*
* @param position the audio location
*/
void updateLocation(Position position);
/**
* @return the current location of this channel
*/
Position getLocation();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,38 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.VoicechatConnection;
public interface StaticAudioChannel extends AudioChannel {
/**
* Sets whether this audio channel should be received by targets in isolated groups.
*
* @param bypassGroupIsolation if group isolation should be bypassed
*/
void setBypassGroupIsolation(boolean bypassGroupIsolation);
/**
* @return if this audio channel is received by targets in isolated groups
*/
boolean bypassesGroupIsolation();
/**
* Adds a target to the audio channel.
*
* @param target the target to add
*/
void addTarget(VoicechatConnection target);
/**
* Removes a target from the audio channel.
*
* @param target the target to remove
*/
void removeTarget(VoicechatConnection target);
/**
* Removes all targets from the audio channel.
*/
void clearTargets();
}
@@ -0,0 +1,4 @@
/**
* Everything related to audio channels and serverside audio.
*/
package de.maxhenkel.voicechat.api.audiochannel;
@@ -1,11 +1,12 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.api.audiolistener; package de.maxhenkel.voicechat.api.audiolistener;
import java.util.UUID; import java.util.UUID;
public interface AudioListener { public interface AudioListener {
public UUID getListenerId();
}
/**
* @return the ID of the listener
*/
UUID getListenerId();
}
@@ -0,0 +1,47 @@
package de.maxhenkel.voicechat.api.audiolistener;
import de.maxhenkel.voicechat.api.ServerPlayer;
import de.maxhenkel.voicechat.api.packets.SoundPacket;
import java.util.UUID;
import java.util.function.Consumer;
public interface PlayerAudioListener extends AudioListener {
/**
* @return the {@link UUID} of the player that is listened to
*/
UUID getPlayerUuid();
public interface Builder {
/**
* <b>Note</b>: It is required to either set a player with this method or with {@link #setPlayer(UUID)}
*
* @param player the player that should be listened to
*/
Builder setPlayer(ServerPlayer player);
/**
* <b>Note</b>: It is required to either set a player with this method or with {@link #setPlayer(ServerPlayer)}
*
* @param playerUuid the player UUID that should be listened to
*/
Builder setPlayer(UUID playerUuid);
/**
* <b>Note</b>: It is required to set a listener
*
* @param listener the listener
*/
Builder setPacketListener(Consumer<SoundPacket> listener);
/**
* @return the built listener
* @throws IllegalStateException if the player or the listener is not set
*/
PlayerAudioListener build();
}
}
@@ -0,0 +1,6 @@
/**
* Utilities for listening to all audio a player would theoretically receive.
* <p>
* Can be obtained by calling {@link de.maxhenkel.voicechat.api.VoicechatServerApi#playerAudioListenerBuilder()}.
*/
package de.maxhenkel.voicechat.api.audiolistener;
@@ -0,0 +1,52 @@
package de.maxhenkel.voicechat.api.audiosender;
public interface AudioSender {
/**
* @param whispering if the player should whisper
* @return the audio sender itself
*/
AudioSender whispering(boolean whispering);
/**
* @return if the player is whispering
*/
boolean isWhispering();
/**
* Sets the sequence number of the packet.
* <br/>
* Setting this will override the automatic sequence numbering.
* <br/>
* If you set this value, consequent calls to {@link AudioSender#send(byte[])} will increase this number by 1.
* If you don't intend this, you need to set the sequence number every time before calling {@link AudioSender#send(byte[])}.
* <br/>
* Calling {@link AudioSender#reset()} will also reset the sequence number to start with <code>0</code> again.
*
* @param sequenceNumber the sequence number (Must be >= 0)
* @return the audio sender itself
*/
AudioSender sequenceNumber(long sequenceNumber);
/**
* @return if a microphone packet can be sent. This will return false if the player has the mod installed or the {@link AudioSender} is not registered.
*/
boolean canSend();
/**
* Acts as if the player has sent a microphone packet.
*
* @param opusEncodedAudioData the opus encoded audio data
* @return <code>true</code> if the packet was sent, <code>false</code> if the sender does have the mod installed or the {@link AudioSender} is not registered
*/
boolean send(byte[] opusEncodedAudioData);
/**
* Resets the sequence number and indicates to clients that the current audio stream is paused/stopped.
* <br/>
* This method should be called when the sending of audio is paused or stopped.
* You can still send audio after this method is called.
*/
boolean reset();
}
@@ -0,0 +1,6 @@
/**
* Utilities for simulating players sending audio without the mod.
* <p>
* Can be obtained by calling {@link de.maxhenkel.voicechat.api.VoicechatServerApi#createAudioSender(de.maxhenkel.voicechat.api.VoicechatConnection)}.
*/
package de.maxhenkel.voicechat.api.audiosender;
@@ -0,0 +1,100 @@
package de.maxhenkel.voicechat.api.config;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import javax.annotation.Nullable;
/**
* An interface to access a config.
* This is kept very generic, so it can be used for any config type.
* <br/>
* This contains a few default methods to get primitive values from the config.
* <br/>
* <b>NOTE</b>: You need to implement more complex values yourself (e.g. enums).
* <br/>
* Can be obtained by calling {@link VoicechatServerApi#getServerConfig()} or {@link VoicechatClientApi#getClientConfig()}.
*/
public interface ConfigAccessor {
/**
* Checks if the config has the given key.
*
* @param key the key
* @return <code>true</code> if the config has the key, <code>false</code> otherwise
*/
boolean hasKey(String key);
/**
* Gets the raw value of the given key.
* <b>NOTE</b>: This gets the <i>raw</i> value from the config, so it will be a {@link String} even if the value is a number, a boolean or an enum.
*
* @param key the key
* @return the value or <code>null</code> if the key doesn't exist
*/
@Nullable
String getValue(String key);
/**
* Gets the string value of the given key.
*
* @param key the key
* @param def the default value
* @return the value or the default value if the key doesn't exist
*/
default String getString(String key, String def) {
String value = getValue(key);
if (value == null) {
return def;
}
return value;
}
/**
* Gets the boolean value of the given key.
*
* @param key the key
* @param def the default value
* @return the boolean value, <code>false</code> if the value is not <code>"true"</code> or the default value if the key doesn't exist
*/
default boolean getBoolean(String key, boolean def) {
String value = getValue(key);
if (value == null) {
return def;
}
return Boolean.parseBoolean(value);
}
/**
* Gets the integer value of the given key.
*
* @param key the key
* @param def the default value
* @return the value or the default value if the key doesn't exist
* @throws NumberFormatException if the value is not an integer
*/
default int getInt(String key, int def) {
String value = getValue(key);
if (value == null) {
return def;
}
return Integer.parseInt(value);
}
/**
* Gets the double value of the given key.
*
* @param key the key
* @param def the default value
* @return the value or the default value if the key doesn't exist
* @throws NumberFormatException if the value is not a double
*/
default double getDouble(String key, double def) {
String value = getValue(key);
if (value == null) {
return def;
}
return Double.parseDouble(value);
}
}
@@ -0,0 +1,51 @@
package de.maxhenkel.voicechat.api.config;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
import java.util.UUID;
/**
* An interface to access client side adjusted volumes.
* <br/>
* Can be obtained by calling {@link VoicechatClientApi#getVolumeConfig()}.
*/
public interface VolumeConfigAccessor {
/**
* Gets the volume of a player by its UUID.
* <br/>
* <br/>
* This will always return a value, even if the player is not in the volume config.
* <br/>
* By default, the volume will be <code>1.0</code>.
* A volume of <code>0.0</code> means this player is muted.
* Can be larger than <code>1.0</code>.
* Also works for players that are not online.
* <br/>
* <br/>
* If you want the <code>Other</code> volume (All volumes that are not covered by categories or players), use the UUID <code>00000000-0000-0000-0000-000000000000</code>.
*
* @param uuid the UUID of the player
* @return the volume of the player
* @throws NullPointerException if the UUID is null
*/
double getVolume(UUID uuid);
/**
* Gets the volume of a category by its UUID.
* <br/>
* <br/>
* This will always return a value, even if the category doesn't exist.
* <br/>
* By default, the volume will be <code>1.0</code>.
* A volume of <code>0.0</code> means this category is muted.
* Can be larger than <code>1.0</code>.
* Also works for categories that are currently not registered.
*
* @param category the category
* @return the volume of the category
* @throws NullPointerException if the category is null
*/
double getCategoryVolume(String category);
}
@@ -0,0 +1,4 @@
/**
* Everything configuration related.
*/
package de.maxhenkel.voicechat.api.config;
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
public interface ClientEvent extends Event {
/**
* @return the voice chat client API
*/
VoicechatClientApi getVoicechat();
}
@@ -0,0 +1,73 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Position;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* This event is emitted before the sound is played on the client.
*/
public interface ClientReceiveSoundEvent extends ClientEvent {
/**
* @return the ID of the channel
*/
UUID getId();
/**
* The unencoded audio data.
* <br/>
* Returns an empty array in case of the end of transmission
*
* @return the raw 16 bit PCM audio frame
*/
short[] getRawAudio();
/**
* Overrides the actual audio data that is played.
* <p>
* This is ignored, if the {@link #getRawAudio} array is empty
* <br>
* Providing audio that's not 960 samples will be ignored.
* An empty array skips this frame, which adds latency. To mute the audio, use an array filled with zeros instead.
*
* @param rawAudio the raw 16 bit PCM audio frame
*/
void setRawAudio(@Nullable short[] rawAudio);
public static interface EntitySound extends ClientReceiveSoundEvent {
/**
* @return the UUID of the entity
*/
UUID getEntityId();
/**
* @return if the player is whispering
*/
boolean isWhispering();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
}
public static interface LocationalSound extends ClientReceiveSoundEvent {
/**
* @return the position of the sound
*/
Position getPosition();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
}
public static interface StaticSound extends ClientReceiveSoundEvent {
}
}
@@ -0,0 +1,29 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted before the client encodes the audio and sends it to the server.
*/
public interface ClientSoundEvent extends ClientEvent {
/**
* The unencoded audio data.
* <br/>
* <s>Returns an empty array in case of the end of transmission</s>
*
* @return the raw 16 bit PCM audio frame
*/
short[] getRawAudio();
/**
* Overrides the actual audio data that's sent to the server.
*
* @param rawAudio the raw 16 bit PCM audio frame
*/
void setRawAudio(short[] rawAudio);
/**
* @return if the player is whispering
*/
boolean isWhispering();
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted on the client when the voice chat connects/disconnects.
*/
public interface ClientVoicechatConnectionEvent extends ClientEvent {
/**
* @return if the voice chat is connected to the server
*/
boolean isConnected();
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.ClientVoicechatSocket;
import javax.annotation.Nullable;
public interface ClientVoicechatInitializationEvent extends ClientEvent {
/**
* Sets a custom implementation of the socket used for client side voice chat traffic.
*
* @param socket the custom socket implementation
*/
void setSocketImplementation(ClientVoicechatSocket socket);
/**
* @return the custom socket implementation or <code>null</code> to use voice chats default one
*/
@Nullable
ClientVoicechatSocket getSocketImplementation();
}
@@ -0,0 +1,21 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
public interface CreateGroupEvent extends GroupEvent {
/**
* @return the group that was created
*/
Group getGroup();
/**
* @return the connection of the player that created the group or <code>null</code> if the group was not created by a player
*/
@Nullable
VoicechatConnection getConnection();
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted when the voice chats OpenAL context is created.
*/
public interface CreateOpenALContextEvent extends ClientEvent {
/**
* @return the OpenAL context
*/
long getContext();
/**
* @return the OpenAL device
*/
long getDevice();
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted when the voice chats OpenAL context is destroyed.
*/
public interface DestroyOpenALContextEvent extends ClientEvent {
/**
* @return the OpenAL context - Might be <code>0L</code> in some cases
*/
long getContext();
/**
* @return the OpenAL device - Might be <code>0L</code> in some cases
*/
long getDevice();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.EntitySoundPacket;
/**
* This event is emitted when an entity sound packet is about to get sent to a client.
*/
public interface EntitySoundPacketEvent extends SoundPacketEvent<EntitySoundPacket> {
}
@@ -0,0 +1,23 @@
package de.maxhenkel.voicechat.api.events;
public interface Event {
/**
* @return if this event can be cancelled
*/
boolean isCancellable();
/**
* Cancels this event.
* Does nothing if the event isn't cancellable.
*
* @return if the event was actually cancelled
*/
boolean cancel();
/**
* @return if the event was cancelled
*/
boolean isCancelled();
}
@@ -0,0 +1,28 @@
package de.maxhenkel.voicechat.api.events;
import java.util.function.Consumer;
public interface EventRegistration {
/**
* Registers an event.
*
* @param eventClass the class of the event you want to receive
* @param onPacket the consumer that is called when the event was dispatched
* @param priority the event priority - Higher values mean a higher priority
* @param <T> the event type
*/
<T extends Event> void registerEvent(Class<T> eventClass, Consumer<T> onPacket, int priority);
/**
* Registers an event.
*
* @param eventClass the class of the event you want to receive
* @param onPacket the consumer that is called when the event was dispatched
* @param <T> the event type
*/
default <T extends Event> void registerEvent(Class<T> eventClass, Consumer<T> onPacket) {
registerEvent(eventClass, onPacket, 0);
}
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
public interface GroupEvent extends ServerEvent {
/**
* @return the group - <code>null</code> if there is no group
*/
@Nullable
Group getGroup();
/**
* @return the connection of the player
*/
@Nullable
VoicechatConnection getConnection();
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
public interface JoinGroupEvent extends GroupEvent {
/**
* @return the group that was joined
*/
Group getGroup();
/**
* @return the connection of the player
*/
VoicechatConnection getConnection();
}
@@ -0,0 +1,21 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
public interface LeaveGroupEvent extends GroupEvent {
/**
* @return the group that was left or <code>null</code> if the player was not in a group
*/
@Nullable
Group getGroup();
/**
* @return the connection of the player
*/
VoicechatConnection getConnection();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.LocationalSoundPacket;
/**
* This event is emitted when a locational sound packet is about to get sent to a client.
*/
public interface LocationalSoundPacketEvent extends SoundPacketEvent<LocationalSoundPacket> {
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted before the {@link ClientSoundEvent} is getting called.
* It allows you to merge audio into the audio that is captured from the microphone.
* <br/>
* This is called even when the player is muted or not talking.
* <br/>
* When the voice chat it disabled, this event is not called.
*/
public interface MergeClientSoundEvent extends ClientEvent {
/**
* Merges the audio into the audio that is captured from the microphone.
* <br/>
* Calling this multiple times will merge the audio multiple times into the same audio frame.
*
* @param audio the audio that should get merged
*/
void mergeAudio(short[] audio);
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted on the client when the state of the microphone changes.
*/
public interface MicrophoneMuteEvent extends ClientEvent {
/**
* @return if the microphone is muted
*/
boolean isDisabled();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.MicrophonePacket;
/**
* This event is emitted when a microphone packet arrives at the server.
*/
public interface MicrophonePacketEvent extends PacketEvent<MicrophonePacket> {
}
@@ -0,0 +1,30 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
import java.util.UUID;
/**
* This event is emitted when an icon is getting rendered.
* <br/>
* This event should not be used to do any actual rendering.
* Just use this event to cancel the rendering of the icon.
* <br/>
* <b>Warning: </b> Don't do heavy operations in this event, as it will block the render thread!
* <br/>
* <br/>
* <b>Note: </b> Don't keep references to this event. It will be reused for every icon render to avoid allocations.
* <br/>
* <br/>
* Use {@link VoicechatClientApi#isTalking(UUID)}, {@link VoicechatClientApi#isWhispering(UUID)},
* {@link VoicechatClientApi#isDisabled(UUID)} and {@link VoicechatClientApi#isDisconnected(UUID)}
* to get the status of the player and render it using your mod loaders render event system.
*/
public interface NameTagIconRenderEvent extends ClientEvent {
/**
* @return the UUID of the entity the icon is being rendered for
*/
UUID getEntityId();
}
@@ -0,0 +1,59 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Position;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* This event is emitted for every audio chunk for every audio channel.
* These events are meant to be used to modify OpenAL effects.
* <br/>
* This specific event is emitted after the position of the sound is set, but before the any other OpenAL calls.
* Use {@link Pre} or {@link Post} get earlier or later events respectively.
*/
public interface OpenALSoundEvent extends ClientEvent {
/**
* Returns the position of the sound.
* This returns <code>null</code> for static sounds.
*
* @return the position of the sound
*/
@Nullable
Position getPosition();
/**
* This returns <code>null</code> for non audio channel sounds, like microphone testing.
*
* @return the unique ID of the audio channel
*/
@Nullable
UUID getChannelId();
/**
* @return the OpenAL source
*/
int getSource();
/**
* @return the category of the sound
*/
@Nullable
String getCategory();
/**
* This event is emitted before any OpenAL calls were made by the voice chat.
*/
public interface Pre extends OpenALSoundEvent {
}
/**
* This event is emitted after all OpenAL calls by the voice chat and the audio is added to the buffer queue.
*/
public interface Post extends OpenALSoundEvent {
}
}
@@ -0,0 +1,27 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import de.maxhenkel.voicechat.api.packets.Packet;
import javax.annotation.Nullable;
public interface PacketEvent<T extends Packet> extends ServerEvent {
/**
* @return the packet
*/
T getPacket();
/**
* @return the connection of the player that should receive this packet
*/
@Nullable
VoicechatConnection getReceiverConnection();
/**
* @return the connection of the player that sent this packet or <code>null</code> if it wasn't sent by a player
*/
@Nullable
VoicechatConnection getSenderConnection();
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
public interface PlayerConnectedEvent extends ServerEvent {
/**
* @return the connection of the player
*/
VoicechatConnection getConnection();
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import java.util.UUID;
public interface PlayerDisconnectedEvent extends ServerEvent {
/**
* @return the UUID of the player
*/
UUID getPlayerUuid();
}
@@ -0,0 +1,35 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* Called when a player changes its state.
* For example if voice chat connects/disconnects, the player joins/leaves a group or disables voice chat.
*/
public interface PlayerStateChangedEvent extends ServerEvent {
/**
* @return if the player disabled voice chat
*/
boolean isDisabled();
/**
* @return if the player is disconnected from voice chat
*/
boolean isDisconnected();
/**
* @return the uuid of the player
*/
UUID getPlayerUuid();
/**
* @return the connection of the player
*/
@Nullable
VoicechatConnection getConnection();
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface RegisterVolumeCategoryEvent extends VolumeCategoryEvent {
}
@@ -0,0 +1,25 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
/**
* This event is only cancellable if the group is persistent
*/
public interface RemoveGroupEvent extends GroupEvent {
/**
* @return the group that was removed
*/
Group getGroup();
/**
* @return <code>null</code>
*/
@Nullable
@Deprecated
VoicechatConnection getConnection();
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
public interface ServerEvent extends Event {
/**
* @return the voice chat server API
*/
VoicechatServerApi getVoicechat();
}
@@ -0,0 +1,23 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.Packet;
public interface SoundPacketEvent<T extends Packet> extends PacketEvent<T> {
String SOURCE_GROUP = "group";
String SOURCE_PROXIMITY = "proximity";
String SOURCE_SPECTATOR = "spectator";
String SOURCE_PLUGIN = "plugin";
/**
* Where the packet originated from.
* It can be either {@value SOURCE_GROUP} for group chats,
* {@value SOURCE_PROXIMITY} for proximity sound,
* {@value SOURCE_SPECTATOR} for spectator sound
* or {@value SOURCE_PLUGIN} if the packet was sent from a plugin.
*
* @return the source of the packet
*/
String getSource();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.StaticSoundPacket;
/**
* This event is emitted when a static sound packet is about to get sent to a client.
*/
public interface StaticSoundPacketEvent extends SoundPacketEvent<StaticSoundPacket> {
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface UnregisterVolumeCategoryEvent extends VolumeCategoryEvent {
}
@@ -0,0 +1,39 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import de.maxhenkel.voicechat.api.packets.MicrophonePacket;
/**
* This event is emitted after a microphone packet arrives at the server and the distance is processed.
* This can be used to modify the distance at which other players can hear this packet.
*/
public interface VoiceDistanceEvent extends ServerEvent {
/**
* @return the microphone packet
*/
MicrophonePacket getPacket();
/**
* @return the connection of the player
*/
VoicechatConnection getSenderConnection();
/**
* This returns either the default distance that's set on the server or the modified distance if another subscriber changed it already.
* In case a player is whispering, this will return the whisper distance.
* The whisper state can be obtained by calling {@link #getPacket()} and getting {@link MicrophonePacket#isWhispering()}.
*
* @return the distance in blocks
*/
float getDistance();
/**
* Sets the distance this player sends audio at.
* Note that this also includes the whisper distance, if the player is whispering.
*
* @param distance the distance in blocks
*/
void setDistance(float distance);
}
@@ -0,0 +1,24 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.ServerPlayer;
public interface VoiceHostEvent extends ServerEvent {
/**
* @return the voice host string
*/
String getVoiceHost();
/**
* Overwrites voice chats voice host - This is sent to the client and used by it to connect to the server.
*
* @param voiceHost the voice host string
*/
void setVoiceHost(String voiceHost);
/**
* @return the player that is connecting
*/
ServerPlayer getPlayer();
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted on the client when the voice chat is getting enabled/disabled.
*/
public interface VoicechatDisableEvent extends ClientEvent {
/**
* @return if the voice chat is disabled
*/
boolean isDisabled();
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface VoicechatServerStartedEvent extends ServerEvent {
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatSocket;
import javax.annotation.Nullable;
public interface VoicechatServerStartingEvent extends ServerEvent {
/**
* Sets a custom implementation of the socket used for voice chat traffic.
*
* @param socket the custom socket implementation
*/
void setSocketImplementation(VoicechatSocket socket);
/**
* @return the custom socket implementation or <code>null</code> to use voice chats default one
*/
@Nullable
VoicechatSocket getSocketImplementation();
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface VoicechatServerStoppedEvent extends ServerEvent {
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VolumeCategory;
public interface VolumeCategoryEvent extends ServerEvent {
/**
* @return the volume category
*/
VolumeCategory getVolumeCategory();
}
@@ -0,0 +1,6 @@
/**
* All registerable events.
* <p>
* Events can be registered by calling {@link de.maxhenkel.voicechat.api.events.EventRegistration#registerEvent}.
*/
package de.maxhenkel.voicechat.api.events;
@@ -0,0 +1,37 @@
package de.maxhenkel.voicechat.api.mp3;
import de.maxhenkel.voicechat.api.VoicechatApi;
import javax.sound.sampled.AudioFormat;
import java.io.IOException;
/**
* You can obtain an instance of this class by calling {@link VoicechatApi#createMp3Decoder}.
*/
public interface Mp3Decoder {
/**
* Decodes the MP3 file and returns the decoded audio data as PCM samples.
*
* @return the decoded audio data as PCM samples
* @throws IOException if an I/O error occurs
*/
short[] decode() throws IOException;
/**
* Decodes the MP3 file if {@link #decode()} has not been called before.
*
* @return the audio format of the decoded audio data
* @throws IOException if an I/O error occurs
*/
AudioFormat getAudioFormat() throws IOException;
/**
* Decodes the MP3 file if {@link #decode()} has not been called before.
*
* @return the bitrate of the mp3 file
* @throws IOException if an I/O error occurs
*/
int getBitrate() throws IOException;
}
@@ -0,0 +1,30 @@
package de.maxhenkel.voicechat.api.mp3;
import de.maxhenkel.voicechat.api.VoicechatApi;
import java.io.IOException;
/**
* You can obtain an instance of this class by calling {@link VoicechatApi#createMp3Encoder}.
*/
public interface Mp3Encoder {
/**
* Encodes the given PCM samples and writes it to the provided output stream.
*
* @param samples the PCM samples to encode
* @throws IOException if an I/O error occurs
*/
void encode(short[] samples) throws IOException;
/**
* Closes the encoder and flushes the output stream.
* Also writes leftover mp3 data to the output stream.
*
* <b>NOTE</b>: Not closing encoders will cause a memory leak in voice chat versions <code>&lt;2.6.3</code>!
*
* @throws IOException if an I/O error occurs
*/
void close() throws IOException;
}
@@ -0,0 +1,4 @@
/**
* Wrappers for mp3 encoding and decoding.
*/
package de.maxhenkel.voicechat.api.mp3;
@@ -0,0 +1,45 @@
package de.maxhenkel.voicechat.api.opus;
import de.maxhenkel.voicechat.api.VoicechatApi;
import javax.annotation.Nullable;
/**
* Instances can be obtained by calling {@link VoicechatApi#createDecoder()}.
*/
public interface OpusDecoder {
/**
* Decodes opus encoded audio data to 16 bit PCM audio.
*
* @param data the opus encoded data or <code>null</code> to do PLC
* @return the 16 bit PCM audio data
*/
short[] decode(@Nullable byte[] data);
/**
* Decodes the provided packet and recovers previous lost frames using FEC.
*
* @param input the input packet
* @param frames the number of frames to return (min 1 for just the current frame)
* @return an array containing the decoded frames - the length of the array is equal to {@param frames}
*/
short[][] decode(byte[] input, int frames);
/**
* Resets the decoders state.
*/
void resetState();
/**
* @return if the decoder is closed
*/
boolean isClosed();
/**
* Closes the decoder.
* <b>NOTE</b>: Not closing encoders will cause a memory leak in voice chat versions <code>&lt;2.6.3</code>!
*/
void close();
}
@@ -0,0 +1,34 @@
package de.maxhenkel.voicechat.api.opus;
import de.maxhenkel.voicechat.api.VoicechatApi;
/**
* Instances can be obtained by calling {@link VoicechatApi#createEncoder()}.
*/
public interface OpusEncoder {
/**
* Encodes 16 bit PCM audio with opus.
*
* @param rawAudio the raw 16 bit PCM audio
* @return the opus encoded data
*/
byte[] encode(short[] rawAudio);
/**
* Resets the encoders state.
*/
void resetState();
/**
* @return if the encoder is closed
*/
boolean isClosed();
/**
* Closes the encoder.
* <b>NOTE</b>: Not closing encoders will cause a memory leak in voice chat versions <code>&lt;2.6.3</code>!
*/
void close();
}
@@ -0,0 +1,8 @@
package de.maxhenkel.voicechat.api.opus;
/**
* The different Opus encoder modes
*/
public enum OpusEncoderMode {
VOIP, AUDIO, RESTRICTED_LOWDELAY;
}
@@ -0,0 +1,4 @@
/**
* Wrappers for the Opus codec.
*/
package de.maxhenkel.voicechat.api.opus;
@@ -0,0 +1,4 @@
/**
* The main Simple Voice Chat API package.
*/
package de.maxhenkel.voicechat.api;
@@ -0,0 +1,64 @@
package de.maxhenkel.voicechat.api.packets;
import de.maxhenkel.voicechat.api.Position;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import java.util.UUID;
public interface ConvertablePacket {
/**
* <b>NOTE</b>: Make sure to set {@link SoundPacket.Builder#channelId(UUID)} to avoid conflicts with other channels.
*
* @return a builder to build an entity sound packet based on this packet
*/
EntitySoundPacket.Builder<?> entitySoundPacketBuilder();
/**
* <b>NOTE</b>: Make sure to set {@link SoundPacket.Builder#channelId(UUID)} to avoid conflicts with other channels.
*
* @return a builder to build a locational sound packet based on this packet
*/
LocationalSoundPacket.Builder<?> locationalSoundPacketBuilder();
/**
* <b>NOTE</b>: Make sure to set {@link SoundPacket.Builder#channelId(UUID)} to avoid conflicts with other channels.
*
* @return a builder to build a static sound packet based on this packet
*/
StaticSoundPacket.Builder<?> staticSoundPacketBuilder();
/**
* Converts this packet to an entity sound packet.
*
* @param entityUuid the UUID of the entity
* @param whispering if the entity is whispering
* @return the entity sound packet
* @throws UnsupportedOperationException if this is called on a packet created by {@link VoicechatServerApi#createPacket()}
* @deprecated use {@link #entitySoundPacketBuilder()}
*/
@Deprecated
EntitySoundPacket toEntitySoundPacket(UUID entityUuid, boolean whispering);
/**
* Converts this packet to a locational sound packet.
*
* @param position the position of the audio
* @return the locational sound packet
* @throws UnsupportedOperationException if this is called on a packet created by {@link VoicechatServerApi#createPacket()}
* @deprecated use {@link #locationalSoundPacketBuilder()}
*/
@Deprecated
LocationalSoundPacket toLocationalSoundPacket(Position position);
/**
* Converts this packet to a static sound packet.
*
* @return the static sound packet
* @throws UnsupportedOperationException if this is called on a packet created by {@link VoicechatServerApi#createPacket()}
* @deprecated use {@link #staticSoundPacketBuilder()}
*/
@Deprecated
StaticSoundPacket toStaticSoundPacket();
}
@@ -0,0 +1,60 @@
package de.maxhenkel.voicechat.api.packets;
import java.util.UUID;
/**
* The receiver of this event will hear the sound from the specified entity.
* This can be done for all entities, but only players will show the speaker icon.
* If the entity is further away than the maximum voice distance, the receiving player won't actually hear the sound.
*/
public interface EntitySoundPacket extends SoundPacket {
/**
* @return the UUID of the entity
*/
UUID getEntityUuid();
/**
* @return if the entity is whispering
*/
boolean isWhispering();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* A builder to build an entity sound packet.
*
* <b>NOTE</b>: Some values are required to be set.
*
* @param <T> the builder itself
*/
public interface Builder<T extends Builder<T>> extends SoundPacket.Builder<T, EntitySoundPacket> {
/**
* This is required to be set!
* <br>
* The entity UUID is also used as the sender of the packet.
*
* @param entityUuid the UUID of the entity
* @return the builder
*/
T entityUuid(UUID entityUuid);
/**
* @param whispering if the entity should be whispering
* @return the builder
*/
T whispering(boolean whispering);
/**
* @param distance the distance, this packet can be heard
* @return the builder
*/
T distance(float distance);
}
}
@@ -0,0 +1,46 @@
package de.maxhenkel.voicechat.api.packets;
import de.maxhenkel.voicechat.api.Position;
/**
* The receiver of this event will hear the sound from the specified location.
* If the location is further away than the maximum voice distance, the receiving player won't actually hear the sound.
*/
public interface LocationalSoundPacket extends SoundPacket {
/**
* @return the audio location
*/
Position getPosition();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* A builder to build a locational sound packet.
*
* <b>NOTE</b>: Some values are required to be set.
*
* @param <T> the builder itself
*/
public interface Builder<T extends Builder<T>> extends SoundPacket.Builder<T, LocationalSoundPacket> {
/**
* This is required to be set!
*
* @param position the position of the sound
* @return the builder
*/
T position(Position position);
/**
* @param distance the distance, this packet can be heard
* @return the builder
*/
T distance(float distance);
}
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.packets;
public interface MicrophonePacket extends Packet, ConvertablePacket {
/**
* @return if the player is whispering
*/
boolean isWhispering();
/**
* @return the opus encoded audio data from the player
*/
byte[] getOpusEncodedData();
/**
* Allows you to modify or replace the opus encoded audio data.
*
* @param data the opus encoded audio data to replace
*/
void setOpusEncodedData(byte[] data);
}
@@ -1,8 +1,4 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.api.packets; package de.maxhenkel.voicechat.api.packets;
public interface Packet { public interface Packet {
} }
Loaded 100 of 581 files, more files were not shown because too many files have changed in this diff. Show more