mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Add netgame voice chat
Implemented using libopus for the Opus codec, same as is used in Discord. This adds the following cvars: - `voice_chat` On/Off, triggers self-deafen state on server via weaponprefs - `voice_mode` Activity/PTT - `voice_selfmute` On/Off, triggers self-mute state on server via weaponprefs - `voice_inputamp` -30 to 30, scales input by value in decibels - `voice_activationthreshold` -30 to 0, if any peak in a frame is higher, activates voice - `voice_loopback` On/Off, plays back local transcoded voice - `voice_proximity` On/Off, enables proximity effects for server - `voice_distanceattenuation_distance` distance in fracunits to scale voice volume over - `voice_distanceattenuation_factor` distance in logarithmic factor to scale voice volume by distance to. e.g. 0.5 for "half as loud" at or above max distance - `voice_stereopanning_factor` at 1.0, player voices are panned to left or right speaker, scaling to no effect at 0.0 - `voice_concurrentattenuation_factor` the logarithmic factor to attenuate player voices with concurrent speakers - `voice_concurrentattenuation_min` the minimum concurrent speakers before global concurrent speaker attenuation - `voice_concurrentattenuation_max` the maximum concurrent speakers for full global concurrent speaker attenuation - `voice_servermute` whether voice chat is enabled on this server. visible from MS via bitflag - `voicevolume` local volume of all voice playback A Voice Options menu is added with a subset of these options, and Server Options has server mute.
This commit is contained in:
parent
6ffdeb6c44
commit
22b20b5877
40 changed files with 15772 additions and 14142 deletions
|
|
@ -100,6 +100,7 @@ find_package(ZLIB REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
find_package(SDL2 CONFIG REQUIRED)
|
find_package(SDL2 CONFIG REQUIRED)
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
find_package(Opus REQUIRED)
|
||||||
# Use the one in thirdparty/fmt to guarantee a minimum version
|
# Use the one in thirdparty/fmt to guarantee a minimum version
|
||||||
#find_package(FMT CONFIG REQUIRED)
|
#find_package(FMT CONFIG REQUIRED)
|
||||||
|
|
||||||
|
|
|
||||||
7
cmake/Modules/FindOpus.cmake
Normal file
7
cmake/Modules/FindOpus.cmake
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
find_package(Opus CONFIG)
|
||||||
|
if(NOT TARGET Opus::opus)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus)
|
||||||
|
set_target_properties(PkgConfig::Opus PROPERTIES IMPORTED_GLOBAL TRUE)
|
||||||
|
add_library(Opus::opus ALIAS PkgConfig::Opus)
|
||||||
|
endif()
|
||||||
|
|
@ -258,6 +258,7 @@ endif()
|
||||||
target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB)
|
target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB)
|
||||||
target_link_libraries(SRB2SDL2 PRIVATE PNG::PNG)
|
target_link_libraries(SRB2SDL2 PRIVATE PNG::PNG)
|
||||||
target_link_libraries(SRB2SDL2 PRIVATE CURL::libcurl)
|
target_link_libraries(SRB2SDL2 PRIVATE CURL::libcurl)
|
||||||
|
target_link_libraries(SRB2SDL2 PRIVATE Opus::opus)
|
||||||
if("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD")
|
if("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD")
|
||||||
target_link_libraries(SRB2SDL2 PRIVATE -lexecinfo)
|
target_link_libraries(SRB2SDL2 PRIVATE -lexecinfo)
|
||||||
target_link_libraries(SRB2SDL2 PRIVATE -lpthread)
|
target_link_libraries(SRB2SDL2 PRIVATE -lpthread)
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,7 @@ consvar_t cv_controlperkey = Player("controlperkey", "One").values({{1, "One"},
|
||||||
consvar_t cv_mastervolume = Player("volume", "80").min_max(0, 100);
|
consvar_t cv_mastervolume = Player("volume", "80").min_max(0, 100);
|
||||||
consvar_t cv_digmusicvolume = Player("musicvolume", "80").min_max(0, 100);
|
consvar_t cv_digmusicvolume = Player("musicvolume", "80").min_max(0, 100);
|
||||||
consvar_t cv_soundvolume = Player("soundvolume", "80").min_max(0, 100);
|
consvar_t cv_soundvolume = Player("soundvolume", "80").min_max(0, 100);
|
||||||
|
consvar_t cv_voicevolume = Player("voicevolume", "100").min_max(0, 100);
|
||||||
|
|
||||||
#ifdef HAVE_DISCORDRPC
|
#ifdef HAVE_DISCORDRPC
|
||||||
void DRPC_UpdatePresence(void);
|
void DRPC_UpdatePresence(void);
|
||||||
|
|
@ -1351,8 +1352,71 @@ consvar_t cv_chatwidth = Player("chatwidth", "150").min_max(64, 150);
|
||||||
// old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
|
// old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
|
||||||
consvar_t cv_consolechat = Player("chatmode", "Yes").values({{0, "Yes"}, {2, "No"}});
|
consvar_t cv_consolechat = Player("chatmode", "Yes").values({{0, "Yes"}, {2, "No"}});
|
||||||
|
|
||||||
|
// When off, inbound voice packets are ignored
|
||||||
|
void VoiceChat_OnChange(void);
|
||||||
|
consvar_t cv_voice_chat = Player("voice_chat", "Off")
|
||||||
|
.on_off()
|
||||||
|
.onchange(VoiceChat_OnChange)
|
||||||
|
.description("Whether voice chat is played or not. Shown as self-deafen to others.");
|
||||||
|
|
||||||
|
// When on, local player won't transmit voice
|
||||||
|
consvar_t cv_voice_mode = Player("voice_mode", "Activity")
|
||||||
|
.values({{0, "Activity"}, {1, "PTT"}})
|
||||||
|
.description("How to activate voice transmission");
|
||||||
|
|
||||||
|
consvar_t cv_voice_selfmute = Player("voice_selfmute", "Off")
|
||||||
|
.on_off()
|
||||||
|
.onchange(weaponPrefChange)
|
||||||
|
.description("Whether the local microphone is muted. Shown as self-mute to others.");
|
||||||
|
|
||||||
|
consvar_t cv_voice_inputamp = Player("voice_inputamp", "14")
|
||||||
|
.min_max(-30, 30)
|
||||||
|
.description("How much louder or quieter to make voice input, in decibels.");
|
||||||
|
|
||||||
|
consvar_t cv_voice_activationthreshold = Player("voice_activationthreshold", "-20")
|
||||||
|
.min_max(-30, 0)
|
||||||
|
.description("The voice activation threshold, in decibels from maximum amplitude.");
|
||||||
|
|
||||||
|
// When on, local voice is played back out
|
||||||
|
consvar_t cv_voice_loopback = Player("voice_loopback", "Off")
|
||||||
|
.on_off()
|
||||||
|
.dont_save()
|
||||||
|
.description("When on, plays the local player's voice");
|
||||||
|
|
||||||
|
consvar_t cv_voice_proximity = NetVar("voice_proximity", "On")
|
||||||
|
.on_off()
|
||||||
|
.description("Whether proximity effects for voice chat are enabled on the server.");
|
||||||
|
|
||||||
|
// The relative distance for maximum voice attenuation
|
||||||
|
consvar_t cv_voice_distanceattenuation_distance = NetVar("voice_distanceattenuation_distance", "4096")
|
||||||
|
.floating_point()
|
||||||
|
.description("Voice speaker's distance from listener at which positional voice is fully attenuated");
|
||||||
|
|
||||||
|
// The volume factor (scaled logarithmically, i.e. 0.5 = "half as loud") for voice distance attenuation
|
||||||
|
consvar_t cv_voice_distanceattenuation_factor = NetVar("voice_distanceattenuation_factor", "0.2")
|
||||||
|
.floating_point()
|
||||||
|
.description("Maximum attenuation, in perceived loudness, when a voice speaker is at voice_distanceattenuation_distance units or further from the listener");
|
||||||
|
|
||||||
|
// The scale factor applied to stereo separation for voice panning
|
||||||
|
consvar_t cv_voice_stereopanning_factor = NetVar("voice_stereopanning_factor", "1.0")
|
||||||
|
.floating_point()
|
||||||
|
.description("Scale of stereo panning applied to a voice speaker relative to their in-game position, from 0.0-1.0");
|
||||||
|
|
||||||
|
consvar_t cv_voice_concurrentattenuation_factor = NetVar("voice_concurrentattenuation_factor", "0.6")
|
||||||
|
.floating_point()
|
||||||
|
.description("The maximum attenuation factor, in perceived loudness, when at or exceeding voice_concurrentattenuation_max speakers");
|
||||||
|
consvar_t cv_voice_concurrentattenuation_min = NetVar("voice_concurrentattenuation_min", "3")
|
||||||
|
.description("Minimum concurrent speakers before global attenuation starts");
|
||||||
|
consvar_t cv_voice_concurrentattenuation_max = NetVar("voice_concurrentattenuation_max", "8")
|
||||||
|
.description("Maximum concurrent speakers at which full global attenuation is applied");
|
||||||
|
|
||||||
void Mute_OnChange(void);
|
void Mute_OnChange(void);
|
||||||
|
void VoiceMute_OnChange(void);
|
||||||
consvar_t cv_mute = UnsavedNetVar("mute", "Off").on_off().onchange(Mute_OnChange);
|
consvar_t cv_mute = UnsavedNetVar("mute", "Off").on_off().onchange(Mute_OnChange);
|
||||||
|
consvar_t cv_voice_servermute = NetVar("voice_servermute", "On")
|
||||||
|
.on_off()
|
||||||
|
.onchange(VoiceMute_OnChange)
|
||||||
|
.description("If On, the server will not broadcast voice chat to clients");
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
||||||
528
src/d_clisrv.c
528
src/d_clisrv.c
|
|
@ -16,6 +16,8 @@
|
||||||
#include <unistd.h> //for unlink
|
#include <unistd.h> //for unlink
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <opus.h>
|
||||||
|
|
||||||
#include "i_time.h"
|
#include "i_time.h"
|
||||||
#include "i_net.h"
|
#include "i_net.h"
|
||||||
#include "i_system.h"
|
#include "i_system.h"
|
||||||
|
|
@ -190,6 +192,17 @@ uint8_t priorKeys[MAXPLAYERS][PUBKEYLENGTH]; // Make a note of keys before consu
|
||||||
boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not
|
boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not
|
||||||
tic_t firstconnectattempttime = 0;
|
tic_t firstconnectattempttime = 0;
|
||||||
|
|
||||||
|
static OpusDecoder *g_player_opus_decoders[MAXPLAYERS];
|
||||||
|
static UINT64 g_player_opus_lastframe[MAXPLAYERS];
|
||||||
|
static OpusEncoder *g_local_opus_encoder;
|
||||||
|
static UINT64 g_local_opus_frame = 0;
|
||||||
|
#define SRB2_VOICE_OPUS_FRAME_SIZE 480
|
||||||
|
static float g_local_voice_buffer[SRB2_VOICE_OPUS_FRAME_SIZE];
|
||||||
|
static INT32 g_local_voice_buffer_len = 0;
|
||||||
|
static INT32 g_local_voice_threshold_time = 0;
|
||||||
|
float g_local_voice_last_peak = 0;
|
||||||
|
boolean g_local_voice_detected = false;
|
||||||
|
|
||||||
// engine
|
// engine
|
||||||
|
|
||||||
// Must be a power of two
|
// Must be a power of two
|
||||||
|
|
@ -1050,7 +1063,8 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
|
||||||
|
|
||||||
netbuffer->u.serverinfo.kartvars = (UINT8) (
|
netbuffer->u.serverinfo.kartvars = (UINT8) (
|
||||||
(gamespeed & SV_SPEEDMASK) |
|
(gamespeed & SV_SPEEDMASK) |
|
||||||
(dedicated ? SV_DEDICATED : 0)
|
(dedicated ? SV_DEDICATED : 0) |
|
||||||
|
(!cv_voice_servermute.value ? SV_VOICEENABLED : 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
D_ParseCarets(netbuffer->u.serverinfo.servername, cv_servername.string, MAXSERVERNAME);
|
D_ParseCarets(netbuffer->u.serverinfo.servername, cv_servername.string, MAXSERVERNAME);
|
||||||
|
|
@ -2353,6 +2367,11 @@ static void CL_ConnectToServer(void)
|
||||||
}
|
}
|
||||||
SL_ClearServerList(servernode);
|
SL_ClearServerList(servernode);
|
||||||
|
|
||||||
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
CL_ClearPlayer(i);
|
||||||
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// If the connection was aborted for some reason, leave
|
// If the connection was aborted for some reason, leave
|
||||||
|
|
@ -2564,6 +2583,27 @@ void CL_ClearPlayer(INT32 playernum)
|
||||||
|
|
||||||
// Handle post-cleanup.
|
// Handle post-cleanup.
|
||||||
RemoveAdminPlayer(playernum); // don't stay admin after you're gone
|
RemoveAdminPlayer(playernum); // don't stay admin after you're gone
|
||||||
|
|
||||||
|
// Clear voice chat data
|
||||||
|
S_ResetVoiceQueue(playernum);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Destroy and recreate the opus decoder for this playernum
|
||||||
|
OpusDecoder *opusdecoder = g_player_opus_decoders[playernum];
|
||||||
|
if (opusdecoder)
|
||||||
|
{
|
||||||
|
opus_decoder_destroy(opusdecoder);
|
||||||
|
opusdecoder = NULL;
|
||||||
|
}
|
||||||
|
int error;
|
||||||
|
opusdecoder = opus_decoder_create(48000, 1, &error);
|
||||||
|
if (error != OPUS_OK)
|
||||||
|
{
|
||||||
|
CONS_Alert(CONS_WARNING, "Failed to create Opus decoder for player %d: opus error %d\n", playernum, error);
|
||||||
|
opusdecoder = NULL;
|
||||||
|
}
|
||||||
|
g_player_opus_decoders[playernum] = opusdecoder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
@ -3242,9 +3282,123 @@ static void Command_ResendGamestate(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Command_ServerMute(void)
|
||||||
|
{
|
||||||
|
SINT8 playernum;
|
||||||
|
UINT8 buf[2];
|
||||||
|
|
||||||
|
if (!netgame)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COM_Argc() == 1)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("servermute <playername/playernum>: server mute a player's voice\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!(server || IsPlayerAdmin(consoleplayer)))
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("Only the server or an admin can use this.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playernum = nametonum(COM_Argv(1));
|
||||||
|
if (playernum == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
buf[0] = playernum;
|
||||||
|
buf[1] = 1;
|
||||||
|
SendNetXCmd(XD_SERVERMUTEPLAYER, buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Command_ServerUnmute(void)
|
||||||
|
{
|
||||||
|
SINT8 playernum;
|
||||||
|
UINT8 buf[2];
|
||||||
|
|
||||||
|
if (!netgame)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("This only works in a netgame.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (COM_Argc() == 1)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("serverunmute <playername/playernum>: server unmute a player's voice\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!(server || IsPlayerAdmin(consoleplayer)))
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("Only the server or an admin can use this.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playernum = nametonum(COM_Argv(1));
|
||||||
|
if (playernum == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
buf[0] = playernum;
|
||||||
|
buf[1] = 0;
|
||||||
|
SendNetXCmd(XD_SERVERMUTEPLAYER, &buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Command_ServerDeafen(void)
|
||||||
|
{
|
||||||
|
SINT8 playernum;
|
||||||
|
UINT8 buf[2];
|
||||||
|
|
||||||
|
if (COM_Argc() == 1)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("serverdeafen <playername/playernum>: server deafen a player\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (client)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("Only the server can use this.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playernum = nametonum(COM_Argv(1));
|
||||||
|
if (playernum == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
buf[0] = playernum;
|
||||||
|
buf[1] = 1;
|
||||||
|
SendNetXCmd(XD_SERVERDEAFENPLAYER, &buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Command_ServerUndeafen(void)
|
||||||
|
{
|
||||||
|
SINT8 playernum;
|
||||||
|
UINT8 buf[2];
|
||||||
|
|
||||||
|
if (COM_Argc() == 1)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("serverundeafen <playername/playernum>: server undeafen a player\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (client)
|
||||||
|
{
|
||||||
|
CONS_Printf(M_GetText("Only the server can use this.\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playernum = nametonum(COM_Argv(1));
|
||||||
|
if (playernum == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
buf[0] = playernum;
|
||||||
|
buf[1] = 0;
|
||||||
|
SendNetXCmd(XD_SERVERDEAFENPLAYER, buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
static void Got_AddPlayer(const UINT8 **p, INT32 playernum);
|
static void Got_AddPlayer(const UINT8 **p, INT32 playernum);
|
||||||
static void Got_RemovePlayer(const UINT8 **p, INT32 playernum);
|
static void Got_RemovePlayer(const UINT8 **p, INT32 playernum);
|
||||||
static void Got_AddBot(const UINT8 **p, INT32 playernum);
|
static void Got_AddBot(const UINT8 **p, INT32 playernum);
|
||||||
|
static void Got_ServerMutePlayer(const UINT8 **p, INT32 playernum);
|
||||||
|
static void Got_ServerDeafenPlayer(const UINT8 **p, INT32 playernum);
|
||||||
|
|
||||||
void Joinable_OnChange(void);
|
void Joinable_OnChange(void);
|
||||||
void Joinable_OnChange(void)
|
void Joinable_OnChange(void)
|
||||||
|
|
@ -3295,6 +3449,13 @@ void D_ClientServerInit(void)
|
||||||
RegisterNetXCmd(XD_REMOVEPLAYER, Got_RemovePlayer);
|
RegisterNetXCmd(XD_REMOVEPLAYER, Got_RemovePlayer);
|
||||||
RegisterNetXCmd(XD_ADDBOT, Got_AddBot);
|
RegisterNetXCmd(XD_ADDBOT, Got_AddBot);
|
||||||
|
|
||||||
|
COM_AddCommand("servermute", Command_ServerMute);
|
||||||
|
COM_AddCommand("serverunmute", Command_ServerUnmute);
|
||||||
|
COM_AddCommand("serverdeafen", Command_ServerDeafen);
|
||||||
|
COM_AddCommand("serverundeafen", Command_ServerUndeafen);
|
||||||
|
RegisterNetXCmd(XD_SERVERMUTEPLAYER, Got_ServerMutePlayer);
|
||||||
|
RegisterNetXCmd(XD_SERVERDEAFENPLAYER, Got_ServerDeafenPlayer);
|
||||||
|
|
||||||
gametic = 0;
|
gametic = 0;
|
||||||
localgametic = 0;
|
localgametic = 0;
|
||||||
|
|
||||||
|
|
@ -3484,6 +3645,27 @@ void D_QuitNetGame(void)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void InitializeLocalVoiceEncoder(void)
|
||||||
|
{
|
||||||
|
// Reset voice opus encoder for local "player 1"
|
||||||
|
OpusEncoder *encoder = g_local_opus_encoder;
|
||||||
|
if (encoder != NULL)
|
||||||
|
{
|
||||||
|
opus_encoder_destroy(encoder);
|
||||||
|
encoder = NULL;
|
||||||
|
}
|
||||||
|
int error;
|
||||||
|
encoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error);
|
||||||
|
opus_encoder_ctl(encoder, OPUS_SET_VBR(0));
|
||||||
|
if (error != OPUS_OK)
|
||||||
|
{
|
||||||
|
CONS_Alert(CONS_WARNING, "Failed to create Opus voice encoder: opus error %d\n", error);
|
||||||
|
encoder = NULL;
|
||||||
|
}
|
||||||
|
g_local_opus_encoder = encoder;
|
||||||
|
g_local_opus_frame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a node to the game (player will follow at map change or at savegame....)
|
// Adds a node to the game (player will follow at map change or at savegame....)
|
||||||
static inline void SV_AddNode(INT32 node)
|
static inline void SV_AddNode(INT32 node)
|
||||||
{
|
{
|
||||||
|
|
@ -3563,6 +3745,8 @@ static void Got_AddPlayer(const UINT8 **p, INT32 playernum)
|
||||||
g_localplayers[i] = newplayernum;
|
g_localplayers[i] = newplayernum;
|
||||||
}
|
}
|
||||||
DEBFILE("spawning me\n");
|
DEBFILE("spawning me\n");
|
||||||
|
|
||||||
|
InitializeLocalVoiceEncoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
P_ForceLocalAngle(newplayer, newplayer->angleturn);
|
P_ForceLocalAngle(newplayer, newplayer->angleturn);
|
||||||
|
|
@ -3662,6 +3846,56 @@ static void Got_AddBot(const UINT8 **p, INT32 playernum)
|
||||||
K_SetBot(newplayernum, skinnum, difficulty, style);
|
K_SetBot(newplayernum, skinnum, difficulty, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Xcmd XD_SERVERMUTEPLAYER
|
||||||
|
static void Got_ServerMutePlayer(const UINT8 **p, INT32 playernum)
|
||||||
|
{
|
||||||
|
UINT8 forplayer = READUINT8(*p);
|
||||||
|
UINT8 muted = READUINT8(*p);
|
||||||
|
if (playernum != serverplayer)
|
||||||
|
{
|
||||||
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal server mute player cmd from %s\n"), player_names[playernum]);
|
||||||
|
if (server)
|
||||||
|
{
|
||||||
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (muted && !(players[forplayer].pflags2 & PF2_SERVERMUTE))
|
||||||
|
{
|
||||||
|
players[forplayer].pflags2 |= PF2_SERVERMUTE;
|
||||||
|
HU_AddChatText(va("\x82* %s was server muted.", player_names[forplayer]), false);
|
||||||
|
}
|
||||||
|
else if (!muted && players[forplayer].pflags2 & PF2_SERVERMUTE)
|
||||||
|
{
|
||||||
|
players[forplayer].pflags2 &= ~PF2_SERVERMUTE;
|
||||||
|
HU_AddChatText(va("\x82* %s was server unmuted.", player_names[forplayer]), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xcmd XD_SERVERDEAFENPLAYER
|
||||||
|
static void Got_ServerDeafenPlayer(const UINT8 **p, INT32 playernum)
|
||||||
|
{
|
||||||
|
UINT8 forplayer = READUINT8(*p);
|
||||||
|
UINT8 deafened = READUINT8(*p);
|
||||||
|
if (playernum != serverplayer)
|
||||||
|
{
|
||||||
|
CONS_Alert(CONS_WARNING, M_GetText("Illegal server deafen player cmd from %s\n"), player_names[playernum]);
|
||||||
|
if (server)
|
||||||
|
{
|
||||||
|
SendKick(playernum, KICK_MSG_CON_FAIL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deafened && !(players[forplayer].pflags2 & PF2_SERVERDEAFEN))
|
||||||
|
{
|
||||||
|
players[forplayer].pflags2 |= PF2_SERVERDEAFEN;
|
||||||
|
HU_AddChatText(va("\x82* %s was server deafened.", player_names[forplayer]), false);
|
||||||
|
}
|
||||||
|
else if (!deafened && players[forplayer].pflags2 & PF2_SERVERDEAFEN)
|
||||||
|
{
|
||||||
|
players[forplayer].pflags2 &= ~PF2_SERVERDEAFEN;
|
||||||
|
HU_AddChatText(va("\x82* %s was server undeafened.", player_names[forplayer]), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities,
|
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities,
|
||||||
const char *name, uint8_t *key, UINT16 *pwr,
|
const char *name, uint8_t *key, UINT16 *pwr,
|
||||||
const char *name2, uint8_t *key2, UINT16 *pwr2,
|
const char *name2, uint8_t *key2, UINT16 *pwr2,
|
||||||
|
|
@ -4934,6 +5168,149 @@ static void PT_ReqMapQueue(int node)
|
||||||
SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf);
|
SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void PT_HandleVoiceClient(SINT8 node, boolean isserver)
|
||||||
|
{
|
||||||
|
if (!isserver && node != servernode)
|
||||||
|
{
|
||||||
|
// We should never receive voice packets from anything other than the server
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dedicated)
|
||||||
|
{
|
||||||
|
// don't bother decoding on dedicated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
doomdata_t *pak = (doomdata_t*)(doomcom->data);
|
||||||
|
voice_pak *pl = &pak->u.voice;
|
||||||
|
|
||||||
|
UINT64 framenum = (UINT64)LONGLONG(pl->frame);
|
||||||
|
INT32 playernum = pl->flags & VOICE_PAK_FLAGS_PLAYERNUM_BITS;
|
||||||
|
if (playernum >= MAXPLAYERS || playernum < 0)
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean terminal = (pl->flags & VOICE_PAK_FLAGS_TERMINAL_BIT) > 0;
|
||||||
|
UINT32 framesize = doomcom->datalength - BASEPACKETSIZE - sizeof(voice_pak);
|
||||||
|
UINT8 *frame = (UINT8*)(pl) + sizeof(voice_pak);
|
||||||
|
|
||||||
|
OpusDecoder *decoder = g_player_opus_decoders[playernum];
|
||||||
|
if (decoder == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float *decoded_out = Z_Malloc(sizeof(float) * SRB2_VOICE_OPUS_FRAME_SIZE, PU_STATIC, NULL);
|
||||||
|
|
||||||
|
INT32 decoded_samples = 0;
|
||||||
|
UINT64 missedframes = 0;
|
||||||
|
if (framenum > g_player_opus_lastframe[playernum])
|
||||||
|
{
|
||||||
|
missedframes = min((framenum - g_player_opus_lastframe[playernum]) - 1, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UINT64 i = 0; i < missedframes; i++)
|
||||||
|
{
|
||||||
|
decoded_samples = opus_decode_float(decoder, NULL, 0, decoded_out, SRB2_VOICE_OPUS_FRAME_SIZE, 0);
|
||||||
|
if (decoded_samples < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cv_voice_chat.value != 0 && playernum != g_localplayers[0])
|
||||||
|
{
|
||||||
|
S_QueueVoiceFrameFromPlayer(playernum, (void*)decoded_out, decoded_samples * sizeof(float), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_player_opus_lastframe[playernum] = framenum;
|
||||||
|
|
||||||
|
decoded_samples = opus_decode_float(decoder, frame, framesize, decoded_out, SRB2_VOICE_OPUS_FRAME_SIZE, 0);
|
||||||
|
if (decoded_samples < 0)
|
||||||
|
{
|
||||||
|
Z_Free(decoded_out);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cv_voice_chat.value != 0 && playernum != g_localplayers[0])
|
||||||
|
{
|
||||||
|
S_QueueVoiceFrameFromPlayer(playernum, (void*)decoded_out, decoded_samples * sizeof(float), terminal);
|
||||||
|
}
|
||||||
|
S_SetPlayerVoiceActive(playernum);
|
||||||
|
|
||||||
|
Z_Free(decoded_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PT_HandleVoiceServer(SINT8 node)
|
||||||
|
{
|
||||||
|
// Relay to client nodes except the sender
|
||||||
|
doomdata_t *pak = (doomdata_t*)(doomcom->data);
|
||||||
|
voice_pak *pl = &pak->u.voice;
|
||||||
|
int playernum = -1;
|
||||||
|
player_t *player;
|
||||||
|
|
||||||
|
if (cv_voice_servermute.value != 0)
|
||||||
|
{
|
||||||
|
// Don't even relay voice packets if voice_servermute is on
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pl->flags & VOICE_PAK_FLAGS_PLAYERNUM_BITS) > 0 || (pl->flags & VOICE_PAK_FLAGS_RESERVED_BITS) > 0)
|
||||||
|
{
|
||||||
|
// All bits except the terminal bit must be unset when sending to client
|
||||||
|
// Anything else is an illegal message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playernum = nodetoplayer[node];
|
||||||
|
if (!(playernum >= 0 && playernum < MAXPLAYERS))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player = &players[playernum];
|
||||||
|
|
||||||
|
if (player->pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN))
|
||||||
|
{
|
||||||
|
// ignore, they should not be able to broadcast voice
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve terminal bit, blank all other bits
|
||||||
|
pl->flags &= VOICE_PAK_FLAGS_TERMINAL_BIT;
|
||||||
|
// Add playernum to lower bits
|
||||||
|
pl->flags |= (playernum & VOICE_PAK_FLAGS_PLAYERNUM_BITS);
|
||||||
|
|
||||||
|
for (int i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
UINT8 pnode = playernode[i];
|
||||||
|
if (pnode == UINT8_MAX)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this node P1 on that node?
|
||||||
|
boolean isp1onnode = nodetoplayer[pnode] >= 0 && nodetoplayer[pnode] < MAXPLAYERS;
|
||||||
|
|
||||||
|
if (pnode != node && pnode != servernode && isp1onnode && !(players[i].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN)))
|
||||||
|
{
|
||||||
|
HSendPacket(pnode, false, 0, doomcom->datalength - BASEPACKETSIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PT_HandleVoiceClient(node, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PT_HandleVoice(SINT8 node)
|
||||||
|
{
|
||||||
|
if (server)
|
||||||
|
{
|
||||||
|
PT_HandleVoiceServer(node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PT_HandleVoiceClient(node, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static char NodeToSplitPlayer(int node, int split)
|
static char NodeToSplitPlayer(int node, int split)
|
||||||
{
|
{
|
||||||
if (split == 0)
|
if (split == 0)
|
||||||
|
|
@ -5612,6 +5989,9 @@ static void HandlePacketFromPlayer(SINT8 node)
|
||||||
csprng(lastChallengeAll, sizeof(lastChallengeAll));
|
csprng(lastChallengeAll, sizeof(lastChallengeAll));
|
||||||
expectChallenge = false;
|
expectChallenge = false;
|
||||||
break;
|
break;
|
||||||
|
case PT_VOICE:
|
||||||
|
PT_HandleVoice(node);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
|
DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
|
||||||
netbuffer->packettype, node));
|
netbuffer->packettype, node));
|
||||||
|
|
@ -6759,6 +7139,9 @@ void NetKeepAlive(void)
|
||||||
Net_AckTicker();
|
Net_AckTicker();
|
||||||
HandleNodeTimeouts();
|
HandleNodeTimeouts();
|
||||||
FileSendTicker();
|
FileSendTicker();
|
||||||
|
|
||||||
|
// Update voice whenever possible.
|
||||||
|
NetVoiceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a tree falls in the forest but nobody is around to hear it, does it make a tic?
|
// If a tree falls in the forest but nobody is around to hear it, does it make a tic?
|
||||||
|
|
@ -6946,6 +7329,138 @@ void NetUpdate(void)
|
||||||
FileSendTicker();
|
FileSendTicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetVoiceUpdate(void)
|
||||||
|
{
|
||||||
|
UINT8 *encoded = NULL;
|
||||||
|
|
||||||
|
if (dedicated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This necessarily runs every frame, not every tic
|
||||||
|
S_SoundInputSetEnabled(true);
|
||||||
|
|
||||||
|
UINT32 bytes_dequed = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// We need to drain the input queue completely, so do this in a full loop
|
||||||
|
|
||||||
|
INT32 to_read = (SRB2_VOICE_OPUS_FRAME_SIZE - g_local_voice_buffer_len) * sizeof(float);
|
||||||
|
if (to_read > 0)
|
||||||
|
{
|
||||||
|
// Attempt to fill the voice frame buffer
|
||||||
|
|
||||||
|
bytes_dequed = S_SoundInputDequeueSamples((void*)(g_local_voice_buffer + g_local_voice_buffer_len), to_read);
|
||||||
|
g_local_voice_buffer_len += bytes_dequed / 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytes_dequed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_local_voice_buffer_len < SRB2_VOICE_OPUS_FRAME_SIZE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amp of +10 dB is appromiately "twice as loud"
|
||||||
|
float ampfactor = powf(10, (float) cv_voice_inputamp.value / 20.f);
|
||||||
|
for (int i = 0; i < g_local_voice_buffer_len; i++)
|
||||||
|
{
|
||||||
|
g_local_voice_buffer[i] *= ampfactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float softmem = 0.f;
|
||||||
|
opus_pcm_soft_clip(g_local_voice_buffer, SRB2_VOICE_OPUS_FRAME_SIZE, 1, &softmem);
|
||||||
|
|
||||||
|
// Voice detection gate open/close
|
||||||
|
float maxamplitude = 0.f;
|
||||||
|
for (int i = 0; i < g_local_voice_buffer_len; i++)
|
||||||
|
{
|
||||||
|
maxamplitude = max(fabsf(g_local_voice_buffer[i]), maxamplitude);
|
||||||
|
}
|
||||||
|
// 20. * log_10(amplitude) -> decibels (up to 0)
|
||||||
|
// lower than -30 dB is usually inaudible
|
||||||
|
g_local_voice_last_peak = maxamplitude;
|
||||||
|
maxamplitude = 20.f * logf(maxamplitude);
|
||||||
|
if (maxamplitude > (float) cv_voice_activationthreshold.value)
|
||||||
|
{
|
||||||
|
g_local_voice_threshold_time = I_GetTime();
|
||||||
|
g_local_voice_detected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cv_voice_mode.value)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (I_GetTime() - g_local_voice_threshold_time > 15)
|
||||||
|
{
|
||||||
|
g_local_voice_buffer_len = 0;
|
||||||
|
g_local_voice_detected = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (!g_voicepushtotalk_on)
|
||||||
|
{
|
||||||
|
g_local_voice_buffer_len = 0;
|
||||||
|
g_local_voice_detected = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
g_local_voice_detected = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_local_voice_buffer_len = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cv_voice_chat.value == 0)
|
||||||
|
{
|
||||||
|
g_local_voice_buffer_len = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!encoded)
|
||||||
|
{
|
||||||
|
encoded = Z_Malloc(sizeof(UINT8) * 1400, PU_STATIC, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_local_opus_encoder == NULL)
|
||||||
|
{
|
||||||
|
InitializeLocalVoiceEncoder();
|
||||||
|
}
|
||||||
|
OpusEncoder *encoder = g_local_opus_encoder;
|
||||||
|
|
||||||
|
INT32 result = opus_encode_float(encoder, g_local_voice_buffer, SRB2_VOICE_OPUS_FRAME_SIZE, encoded, 1400);
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only send a voice packet and set local player voice active if:
|
||||||
|
// 1. In a netgame,
|
||||||
|
// 2. Not self-muted by cvar
|
||||||
|
// 3. The consoleplayer is not server or self muted or deafened
|
||||||
|
if (netgame && !cv_voice_selfmute.value && !(players[consoleplayer].pflags2 & (PF2_SERVERMUTE | PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERDEAFEN)))
|
||||||
|
{
|
||||||
|
DoVoicePacket(servernode, g_local_opus_frame, encoded, result);
|
||||||
|
S_SetPlayerVoiceActive(consoleplayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cv_voice_loopback.value)
|
||||||
|
{
|
||||||
|
result = opus_decode_float(g_player_opus_decoders[consoleplayer], encoded, result, g_local_voice_buffer, SRB2_VOICE_OPUS_FRAME_SIZE, 0);
|
||||||
|
S_QueueVoiceFrameFromPlayer(consoleplayer, g_local_voice_buffer, result * sizeof(float), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_local_voice_buffer_len = 0;
|
||||||
|
g_local_opus_frame += 1;
|
||||||
|
} while (bytes_dequed > 0);
|
||||||
|
|
||||||
|
if (encoded) Z_Free(encoded);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the number of players playing.
|
/** Returns the number of players playing.
|
||||||
* \return Number of players. Can be zero if we're running a ::dedicated
|
* \return Number of players. Can be zero if we're running a ::dedicated
|
||||||
* server.
|
* server.
|
||||||
|
|
@ -7126,6 +7641,17 @@ void DoSayPacketFromCommand(SINT8 target, size_t usedargs, UINT8 flags)
|
||||||
DoSayPacket(target, flags, consoleplayer, msg);
|
DoSayPacket(target, flags, consoleplayer, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DoVoicePacket(SINT8 target, UINT64 frame, const UINT8* opusdata, size_t len)
|
||||||
|
{
|
||||||
|
voice_pak *pl = &netbuffer->u.voice;
|
||||||
|
netbuffer->packettype = PT_VOICE;
|
||||||
|
pl->frame = (UINT64)LONGLONG(frame);
|
||||||
|
pl->flags = 0;
|
||||||
|
I_Assert(MAXPACKETLENGTH - sizeof(voice_pak) - BASEPACKETSIZE >= len);
|
||||||
|
memcpy((UINT8*)netbuffer + BASEPACKETSIZE + sizeof(voice_pak), opusdata, len);
|
||||||
|
HSendPacket(target, false, 0, sizeof(voice_pak) + len);
|
||||||
|
}
|
||||||
|
|
||||||
// This is meant to be targeted at player indices, not whatever the hell XD_SAY is doing with 1-indexed players.
|
// This is meant to be targeted at player indices, not whatever the hell XD_SAY is doing with 1-indexed players.
|
||||||
void SendServerNotice(SINT8 target, char *message)
|
void SendServerNotice(SINT8 target, char *message)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ typedef enum
|
||||||
|
|
||||||
PT_REQMAPQUEUE, // Client requesting a roundqueue operation
|
PT_REQMAPQUEUE, // Client requesting a roundqueue operation
|
||||||
|
|
||||||
|
PT_VOICE, // Voice packet for either side
|
||||||
|
|
||||||
NUMPACKETTYPE
|
NUMPACKETTYPE
|
||||||
} packettype_t;
|
} packettype_t;
|
||||||
|
|
||||||
|
|
@ -283,6 +285,7 @@ struct clientconfig_pak
|
||||||
|
|
||||||
#define SV_SPEEDMASK 0x03 // used to send kartspeed
|
#define SV_SPEEDMASK 0x03 // used to send kartspeed
|
||||||
#define SV_DEDICATED 0x40 // server is dedicated
|
#define SV_DEDICATED 0x40 // server is dedicated
|
||||||
|
#define SV_VOICEENABLED 0x80 // voice_mute is off/voice chat is enabled
|
||||||
#define SV_LOTSOFADDONS 0x20 // flag used to ask for full file list in d_netfil
|
#define SV_LOTSOFADDONS 0x20 // flag used to ask for full file list in d_netfil
|
||||||
|
|
||||||
#define MAXFILENEEDED 915
|
#define MAXFILENEEDED 915
|
||||||
|
|
@ -418,6 +421,22 @@ struct netinfo_pak
|
||||||
UINT32 delay[MAXPLAYERS+1];
|
UINT32 delay[MAXPLAYERS+1];
|
||||||
} ATTRPACK;
|
} ATTRPACK;
|
||||||
|
|
||||||
|
// Sent by both sides. Contains Opus-encoded voice packet
|
||||||
|
// flags bitset map (left to right, low to high)
|
||||||
|
// | PPPPPTRR | -- P = Player num, T = Terminal, R = Reserved (0)
|
||||||
|
// Data following voice header is a single Opus frame
|
||||||
|
struct voice_pak
|
||||||
|
{
|
||||||
|
UINT64 frame;
|
||||||
|
UINT8 flags;
|
||||||
|
} ATTRPACK;
|
||||||
|
|
||||||
|
#define VOICE_PAK_FLAGS_PLAYERNUM_BITS 0x1F
|
||||||
|
#define VOICE_PAK_FLAGS_TERMINAL_BIT 0x20
|
||||||
|
#define VOICE_PAK_FLAGS_RESERVED0_BIT 0x40
|
||||||
|
#define VOICE_PAK_FLAGS_RESERVED1_BIT 0x80
|
||||||
|
#define VOICE_PAK_FLAGS_RESERVED_BITS (VOICE_PAK_FLAGS_RESERVED0_BIT | VOICE_PAK_FLAGS_RESERVED1_BIT)
|
||||||
|
|
||||||
//
|
//
|
||||||
// Network packet data
|
// Network packet data
|
||||||
//
|
//
|
||||||
|
|
@ -462,6 +481,7 @@ struct doomdata_t
|
||||||
resultsall_pak resultsall; // 1024 bytes. Also, you really shouldn't trust anything here.
|
resultsall_pak resultsall; // 1024 bytes. Also, you really shouldn't trust anything here.
|
||||||
say_pak say; // I don't care anymore.
|
say_pak say; // I don't care anymore.
|
||||||
reqmapqueue_pak reqmapqueue; // Formerly XD_REQMAPQUEUE
|
reqmapqueue_pak reqmapqueue; // Formerly XD_REQMAPQUEUE
|
||||||
|
voice_pak voice; // Unreliable voice data, variable length
|
||||||
} u; // This is needed to pack diff packet types data together
|
} u; // This is needed to pack diff packet types data together
|
||||||
} ATTRPACK;
|
} ATTRPACK;
|
||||||
|
|
||||||
|
|
@ -606,6 +626,7 @@ void SendKick(UINT8 playernum, UINT8 msg);
|
||||||
// Create any new ticcmds and broadcast to other players.
|
// Create any new ticcmds and broadcast to other players.
|
||||||
void NetKeepAlive(void);
|
void NetKeepAlive(void);
|
||||||
void NetUpdate(void);
|
void NetUpdate(void);
|
||||||
|
void NetVoiceUpdate(void);
|
||||||
|
|
||||||
void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame);
|
void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame);
|
||||||
boolean SV_SpawnServer(void);
|
boolean SV_SpawnServer(void);
|
||||||
|
|
@ -710,6 +731,7 @@ void HandleSigfail(const char *string);
|
||||||
|
|
||||||
void DoSayPacket(SINT8 target, UINT8 flags, UINT8 source, char *message);
|
void DoSayPacket(SINT8 target, UINT8 flags, UINT8 source, char *message);
|
||||||
void DoSayPacketFromCommand(SINT8 target, size_t usedargs, UINT8 flags);
|
void DoSayPacketFromCommand(SINT8 target, size_t usedargs, UINT8 flags);
|
||||||
|
void DoVoicePacket(SINT8 target, UINT64 frame, const UINT8* opusdata, size_t len);
|
||||||
void SendServerNotice(SINT8 target, char *message);
|
void SendServerNotice(SINT8 target, char *message);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ INT32 postimgparam[MAXSPLITSCREENPLAYERS];
|
||||||
|
|
||||||
boolean sound_disabled = false;
|
boolean sound_disabled = false;
|
||||||
boolean digital_disabled = false;
|
boolean digital_disabled = false;
|
||||||
|
boolean g_voice_disabled = false;
|
||||||
|
|
||||||
#ifdef DEBUGFILE
|
#ifdef DEBUGFILE
|
||||||
INT32 debugload = 0;
|
INT32 debugload = 0;
|
||||||
|
|
@ -1079,6 +1080,7 @@ void D_SRB2Loop(void)
|
||||||
|
|
||||||
// consoleplayer -> displayplayers (hear sounds from viewpoint)
|
// consoleplayer -> displayplayers (hear sounds from viewpoint)
|
||||||
S_UpdateSounds(); // move positional sounds
|
S_UpdateSounds(); // move positional sounds
|
||||||
|
NetVoiceUpdate(); // update voice recording whenever possible
|
||||||
if (realtics > 0 || singletics)
|
if (realtics > 0 || singletics)
|
||||||
{
|
{
|
||||||
S_UpdateClosedCaptions();
|
S_UpdateClosedCaptions();
|
||||||
|
|
@ -1095,6 +1097,7 @@ void D_SRB2Loop(void)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Music_Tick();
|
Music_Tick();
|
||||||
|
S_UpdateVoicePositionalProperties();
|
||||||
|
|
||||||
// Fully completed frame made.
|
// Fully completed frame made.
|
||||||
finishprecise = I_GetPreciseTime();
|
finishprecise = I_GetPreciseTime();
|
||||||
|
|
@ -1885,12 +1888,14 @@ void D_SRB2Main(void)
|
||||||
{
|
{
|
||||||
sound_disabled = true;
|
sound_disabled = true;
|
||||||
digital_disabled = true;
|
digital_disabled = true;
|
||||||
|
g_voice_disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
|
if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
|
||||||
{
|
{
|
||||||
sound_disabled = true;
|
sound_disabled = true;
|
||||||
digital_disabled = true;
|
digital_disabled = true;
|
||||||
|
g_voice_disabled = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1905,9 +1910,13 @@ void D_SRB2Main(void)
|
||||||
if (M_CheckParm("-nodigmusic"))
|
if (M_CheckParm("-nodigmusic"))
|
||||||
digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
|
digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
|
||||||
}
|
}
|
||||||
|
if (M_CheckParm("-novoice"))
|
||||||
|
{
|
||||||
|
g_voice_disabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!( sound_disabled && digital_disabled ))
|
if (!( sound_disabled && digital_disabled && g_voice_disabled ))
|
||||||
{
|
{
|
||||||
CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
|
CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
|
||||||
I_StartupSound();
|
I_StartupSound();
|
||||||
|
|
|
||||||
|
|
@ -1175,6 +1175,8 @@ enum {
|
||||||
WP_AUTOROULETTE = 1<<2,
|
WP_AUTOROULETTE = 1<<2,
|
||||||
WP_ANALOGSTICK = 1<<3,
|
WP_ANALOGSTICK = 1<<3,
|
||||||
WP_AUTORING = 1<<4,
|
WP_AUTORING = 1<<4,
|
||||||
|
WP_SELFMUTE = 1<<5,
|
||||||
|
WP_SELFDEAFEN = 1<<6
|
||||||
};
|
};
|
||||||
|
|
||||||
void WeaponPref_Send(UINT8 ssplayer)
|
void WeaponPref_Send(UINT8 ssplayer)
|
||||||
|
|
@ -1196,6 +1198,15 @@ void WeaponPref_Send(UINT8 ssplayer)
|
||||||
if (cv_autoring[ssplayer].value)
|
if (cv_autoring[ssplayer].value)
|
||||||
prefs |= WP_AUTORING;
|
prefs |= WP_AUTORING;
|
||||||
|
|
||||||
|
if (ssplayer == 0)
|
||||||
|
{
|
||||||
|
if (cv_voice_selfmute.value)
|
||||||
|
prefs |= WP_SELFMUTE;
|
||||||
|
|
||||||
|
if (!cv_voice_chat.value)
|
||||||
|
prefs |= WP_SELFDEAFEN;
|
||||||
|
}
|
||||||
|
|
||||||
UINT8 buf[2];
|
UINT8 buf[2];
|
||||||
buf[0] = prefs;
|
buf[0] = prefs;
|
||||||
buf[1] = cv_mindelay.value;
|
buf[1] = cv_mindelay.value;
|
||||||
|
|
@ -1235,6 +1246,7 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum)
|
||||||
UINT8 prefs = READUINT8(p);
|
UINT8 prefs = READUINT8(p);
|
||||||
|
|
||||||
player->pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME|PF_AUTOROULETTE|PF_AUTORING);
|
player->pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME|PF_AUTOROULETTE|PF_AUTORING);
|
||||||
|
player->pflags2 &= ~(PF2_SELFMUTE | PF2_SELFDEAFEN);
|
||||||
|
|
||||||
if (prefs & WP_KICKSTARTACCEL)
|
if (prefs & WP_KICKSTARTACCEL)
|
||||||
player->pflags |= PF_KICKSTARTACCEL;
|
player->pflags |= PF_KICKSTARTACCEL;
|
||||||
|
|
@ -1253,6 +1265,12 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum)
|
||||||
if (prefs & WP_AUTORING)
|
if (prefs & WP_AUTORING)
|
||||||
player->pflags |= PF_AUTORING;
|
player->pflags |= PF_AUTORING;
|
||||||
|
|
||||||
|
if (prefs & WP_SELFMUTE)
|
||||||
|
player->pflags2 |= PF2_SELFMUTE;
|
||||||
|
|
||||||
|
if (prefs & WP_SELFDEAFEN)
|
||||||
|
player->pflags2 |= PF2_SELFDEAFEN;
|
||||||
|
|
||||||
if (leveltime < 2)
|
if (leveltime < 2)
|
||||||
{
|
{
|
||||||
// BAD HACK: No other place I tried to slot this in
|
// BAD HACK: No other place I tried to slot this in
|
||||||
|
|
@ -7032,6 +7050,18 @@ void Mute_OnChange(void)
|
||||||
HU_AddChatText(M_GetText("\x82*Chat is no longer muted."), false);
|
HU_AddChatText(M_GetText("\x82*Chat is no longer muted."), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VoiceMute_OnChange(void);
|
||||||
|
void VoiceMute_OnChange(void)
|
||||||
|
{
|
||||||
|
if (leveltime <= 1)
|
||||||
|
return; // avoid having this notification put in our console / log when we boot the server.
|
||||||
|
|
||||||
|
if (cv_voice_servermute.value)
|
||||||
|
HU_AddChatText(M_GetText("\x82*Voice chat has been muted."), false);
|
||||||
|
else
|
||||||
|
HU_AddChatText(M_GetText("\x82*Voice chat is no longer muted."), false);
|
||||||
|
}
|
||||||
|
|
||||||
/** Hack to clear all changed flags after game start.
|
/** Hack to clear all changed flags after game start.
|
||||||
* A lot of code (written by dummies, obviously) uses COM_BufAddText() to run
|
* A lot of code (written by dummies, obviously) uses COM_BufAddText() to run
|
||||||
* commands and change consvars, especially on game start. This is problematic
|
* commands and change consvars, especially on game start. This is problematic
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ extern consvar_t cv_netstat;
|
||||||
|
|
||||||
extern consvar_t cv_countdowntime;
|
extern consvar_t cv_countdowntime;
|
||||||
extern consvar_t cv_mute;
|
extern consvar_t cv_mute;
|
||||||
|
extern consvar_t cv_voice_servermute;
|
||||||
extern consvar_t cv_pause;
|
extern consvar_t cv_pause;
|
||||||
|
|
||||||
extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers;
|
extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers;
|
||||||
|
|
@ -185,6 +186,8 @@ typedef enum
|
||||||
XD_CALLZVOTE, // 39
|
XD_CALLZVOTE, // 39
|
||||||
XD_SETZVOTE, // 40
|
XD_SETZVOTE, // 40
|
||||||
XD_TEAMCHANGE, // 41
|
XD_TEAMCHANGE, // 41
|
||||||
|
XD_SERVERMUTEPLAYER, // 42
|
||||||
|
XD_SERVERDEAFENPLAYER, // 43
|
||||||
|
|
||||||
MAXNETXCMD
|
MAXNETXCMD
|
||||||
} netxcmd_t;
|
} netxcmd_t;
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,14 @@ typedef enum
|
||||||
PF_NOFASTFALL = (INT32)(1U<<31), // Has already done ebrake/fastfall behavior for this input. Fastfalling needs a new input to prevent unwanted bounces on unexpected airtime.
|
PF_NOFASTFALL = (INT32)(1U<<31), // Has already done ebrake/fastfall behavior for this input. Fastfalling needs a new input to prevent unwanted bounces on unexpected airtime.
|
||||||
} pflags_t;
|
} pflags_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PF2_SELFMUTE = 1<<1,
|
||||||
|
PF2_SELFDEAFEN = 1<<2,
|
||||||
|
PF2_SERVERMUTE = 1<<3,
|
||||||
|
PF2_SERVERDEAFEN = 1<<4,
|
||||||
|
} pflags2_t;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
// Are animation frames playing?
|
// Are animation frames playing?
|
||||||
|
|
@ -634,6 +642,7 @@ struct player_t
|
||||||
// Bit flags.
|
// Bit flags.
|
||||||
// See pflags_t, above.
|
// See pflags_t, above.
|
||||||
UINT32 pflags;
|
UINT32 pflags;
|
||||||
|
UINT32 pflags2;
|
||||||
|
|
||||||
// playing animation.
|
// playing animation.
|
||||||
panim_t panim;
|
panim_t panim;
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,7 @@ extern boolean forceresetplayers, deferencoremode, forcespecialstage;
|
||||||
|
|
||||||
extern boolean sound_disabled;
|
extern boolean sound_disabled;
|
||||||
extern boolean digital_disabled;
|
extern boolean digital_disabled;
|
||||||
|
extern boolean g_voice_disabled;
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// Status flags for refresh.
|
// Status flags for refresh.
|
||||||
|
|
|
||||||
|
|
@ -224,3 +224,30 @@ boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
|
||||||
(void)looping;
|
(void)looping;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean I_SoundInputIsEnabled(void)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean I_SoundInputSetEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_ResetVoiceQueue(INT32 playernum)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2201,6 +2201,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
||||||
UINT32 followitem;
|
UINT32 followitem;
|
||||||
|
|
||||||
INT32 pflags;
|
INT32 pflags;
|
||||||
|
INT32 pflags2;
|
||||||
|
|
||||||
UINT8 team;
|
UINT8 team;
|
||||||
|
|
||||||
|
|
@ -2348,6 +2349,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
||||||
xtralife = players[player].xtralife;
|
xtralife = players[player].xtralife;
|
||||||
|
|
||||||
pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_ANALOGSTICK|PF_AUTORING));
|
pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_ANALOGSTICK|PF_AUTORING));
|
||||||
|
pflags2 = (players[player].pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN));
|
||||||
|
|
||||||
// SRB2kart
|
// SRB2kart
|
||||||
memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette));
|
memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette));
|
||||||
|
|
@ -2539,6 +2541,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
||||||
p->roundscore = roundscore;
|
p->roundscore = roundscore;
|
||||||
p->lives = lives;
|
p->lives = lives;
|
||||||
p->pflags = pflags;
|
p->pflags = pflags;
|
||||||
|
p->pflags2 = pflags2;
|
||||||
p->team = team;
|
p->team = team;
|
||||||
p->jointime = jointime;
|
p->jointime = jointime;
|
||||||
p->splitscreenindex = splitscreenindex;
|
p->splitscreenindex = splitscreenindex;
|
||||||
|
|
|
||||||
|
|
@ -910,6 +910,7 @@ static const char *gamecontrolname[num_gamecontrols] =
|
||||||
"screenshot",
|
"screenshot",
|
||||||
"startmovie",
|
"startmovie",
|
||||||
"startlossless",
|
"startlossless",
|
||||||
|
"voicepushtotalk"
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t))
|
#define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t))
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ typedef enum
|
||||||
gc_screenshot,
|
gc_screenshot,
|
||||||
gc_startmovie,
|
gc_startmovie,
|
||||||
gc_startlossless,
|
gc_startlossless,
|
||||||
|
gc_voicepushtotalk,
|
||||||
|
|
||||||
num_gamecontrols,
|
num_gamecontrols,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ patch_t *frameslash; // framerate stuff. Used in screen.c
|
||||||
static player_t *plr;
|
static player_t *plr;
|
||||||
boolean hu_keystrokes; // :)
|
boolean hu_keystrokes; // :)
|
||||||
boolean chat_on; // entering a chat message?
|
boolean chat_on; // entering a chat message?
|
||||||
|
boolean g_voicepushtotalk_on; // holding PTT?
|
||||||
static char w_chat[HU_MAXMSGLEN + 1];
|
static char w_chat[HU_MAXMSGLEN + 1];
|
||||||
static size_t c_input = 0; // let's try to make the chat input less shitty.
|
static size_t c_input = 0; // let's try to make the chat input less shitty.
|
||||||
static boolean headsupactive = false;
|
static boolean headsupactive = false;
|
||||||
|
|
@ -1102,6 +1103,24 @@ void HU_clearChatChars(void)
|
||||||
//
|
//
|
||||||
boolean HU_Responder(event_t *ev)
|
boolean HU_Responder(event_t *ev)
|
||||||
{
|
{
|
||||||
|
// Handle Push-to-Talk
|
||||||
|
if (ev->data1 == gamecontrol[0][gc_voicepushtotalk][0]
|
||||||
|
|| ev->data1 == gamecontrol[0][gc_voicepushtotalk][1]
|
||||||
|
|| ev->data1 == gamecontrol[0][gc_voicepushtotalk][2]
|
||||||
|
|| ev->data1 == gamecontrol[0][gc_voicepushtotalk][3])
|
||||||
|
{
|
||||||
|
if (ev->type == ev_keydown)
|
||||||
|
{
|
||||||
|
g_voicepushtotalk_on = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (ev->type == ev_keyup)
|
||||||
|
{
|
||||||
|
g_voicepushtotalk_on = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ev->type != ev_keydown)
|
if (ev->type != ev_keydown)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ void HU_AddChatText(const char *text, boolean playsound);
|
||||||
// set true when entering a chat message
|
// set true when entering a chat message
|
||||||
extern boolean chat_on;
|
extern boolean chat_on;
|
||||||
|
|
||||||
|
// set true when push-to-talk is held
|
||||||
|
extern boolean g_voicepushtotalk_on;
|
||||||
|
|
||||||
// keystrokes in the console or chat window
|
// keystrokes in the console or chat window
|
||||||
extern boolean hu_keystrokes;
|
extern boolean hu_keystrokes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
#include "../k_hud.h"
|
#include "../k_hud.h"
|
||||||
#include "../p_local.h"
|
#include "../p_local.h"
|
||||||
#include "../r_fps.h"
|
#include "../r_fps.h"
|
||||||
|
#include "../s_sound.h"
|
||||||
|
|
||||||
extern "C" consvar_t cv_maxplayers;
|
extern "C" consvar_t cv_maxplayers;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,8 @@ void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch);
|
||||||
*/
|
*/
|
||||||
void I_SetSfxVolume(int volume);
|
void I_SetSfxVolume(int volume);
|
||||||
|
|
||||||
|
void I_SetVoiceVolume(int volume);
|
||||||
|
|
||||||
/// ------------------------
|
/// ------------------------
|
||||||
// MUSIC SYSTEM
|
// MUSIC SYSTEM
|
||||||
/// ------------------------
|
/// ------------------------
|
||||||
|
|
@ -246,6 +248,22 @@ boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
|
||||||
boolean I_FadeOutStopSong(UINT32 ms);
|
boolean I_FadeOutStopSong(UINT32 ms);
|
||||||
boolean I_FadeInPlaySong(UINT32 ms, boolean looping);
|
boolean I_FadeInPlaySong(UINT32 ms, boolean looping);
|
||||||
|
|
||||||
|
// AUDIO INPUT (Microphones)
|
||||||
|
boolean I_SoundInputIsEnabled(void);
|
||||||
|
boolean I_SoundInputSetEnabled(boolean enabled);
|
||||||
|
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len);
|
||||||
|
|
||||||
|
// VOICE CHAT
|
||||||
|
|
||||||
|
/// Queue a frame of samples of voice data from a player. Voice format is MONO F32 SYSTEM ENDIANNESS.
|
||||||
|
/// If there is too much data being queued, old samples will be truncated
|
||||||
|
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal);
|
||||||
|
|
||||||
|
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep);
|
||||||
|
|
||||||
|
/// Reset the voice queue for the given player. Use when server connection ends
|
||||||
|
void I_ResetVoiceQueue(INT32 playernum);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
126
src/k_hud.cpp
126
src/k_hud.cpp
|
|
@ -14,7 +14,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
#include "v_draw.hpp"
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include "k_hud.h"
|
#include "k_hud.h"
|
||||||
#include "k_kart.h"
|
#include "k_kart.h"
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
#include "k_dialogue.h"
|
#include "k_dialogue.h"
|
||||||
#include "f_finale.h"
|
#include "f_finale.h"
|
||||||
#include "m_easing.h"
|
#include "m_easing.h"
|
||||||
|
#include "v_draw.hpp"
|
||||||
|
|
||||||
//{ Patch Definitions
|
//{ Patch Definitions
|
||||||
static patch_t *kp_nodraw;
|
static patch_t *kp_nodraw;
|
||||||
|
|
@ -215,6 +216,17 @@ static patch_t *kp_bossret[4];
|
||||||
|
|
||||||
static patch_t *kp_trickcool[2];
|
static patch_t *kp_trickcool[2];
|
||||||
|
|
||||||
|
static patch_t *kp_voice_localactive[16];
|
||||||
|
static patch_t *kp_voice_localactiveoverlay[16];
|
||||||
|
static patch_t *kp_voice_localopen;
|
||||||
|
static patch_t *kp_voice_localmuted;
|
||||||
|
static patch_t *kp_voice_localdeafened;
|
||||||
|
static patch_t *kp_voice_remoteactive;
|
||||||
|
static patch_t *kp_voice_remoteopen;
|
||||||
|
static patch_t *kp_voice_remotemuted;
|
||||||
|
static patch_t *kp_voice_remotedeafened;
|
||||||
|
static patch_t *kp_voice_tagactive[3];
|
||||||
|
|
||||||
patch_t *kp_autoroulette;
|
patch_t *kp_autoroulette;
|
||||||
patch_t *kp_autoring;
|
patch_t *kp_autoring;
|
||||||
|
|
||||||
|
|
@ -1010,6 +1022,23 @@ void K_LoadKartHUDGraphics(void)
|
||||||
K_LoadGenericButtonGraphics(gen_button_rs, "R3");
|
K_LoadGenericButtonGraphics(gen_button_rs, "R3");
|
||||||
K_LoadGenericButtonGraphics(gen_button_start, "S");
|
K_LoadGenericButtonGraphics(gen_button_start, "S");
|
||||||
K_LoadGenericButtonGraphics(gen_button_back, "I");
|
K_LoadGenericButtonGraphics(gen_button_back, "I");
|
||||||
|
|
||||||
|
HU_UpdatePatch(&kp_voice_localopen, "VOXCLO");
|
||||||
|
for (i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
HU_UpdatePatch(&kp_voice_localactive[i], "VOXCLA%d", i);
|
||||||
|
HU_UpdatePatch(&kp_voice_localactiveoverlay[i], "VOXCLB%d", i);
|
||||||
|
}
|
||||||
|
HU_UpdatePatch(&kp_voice_localmuted, "VOXCLM");
|
||||||
|
HU_UpdatePatch(&kp_voice_localdeafened, "VOXCLD");
|
||||||
|
HU_UpdatePatch(&kp_voice_remoteopen, "VOXCRO");
|
||||||
|
HU_UpdatePatch(&kp_voice_remoteactive, "VOXCRA");
|
||||||
|
HU_UpdatePatch(&kp_voice_remotemuted, "VOXCRM");
|
||||||
|
HU_UpdatePatch(&kp_voice_remotedeafened, "VOXCRD");
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
HU_UpdatePatch(&kp_voice_tagactive[i], "VOXCTA%d", i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the item toggle menu
|
// For the item toggle menu
|
||||||
|
|
@ -2711,6 +2740,30 @@ void PositionFacesInfo::draw_1p()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Voice speaking indicator
|
||||||
|
if (netgame && !players[rankplayer[i]].bot && cv_voice_servermute.value == 0)
|
||||||
|
{
|
||||||
|
patch_t *voxmic;
|
||||||
|
if (S_IsPlayerVoiceActive(rankplayer[i]))
|
||||||
|
{
|
||||||
|
voxmic = kp_voice_remoteactive;
|
||||||
|
}
|
||||||
|
else if (players[rankplayer[i]].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN))
|
||||||
|
{
|
||||||
|
voxmic = kp_voice_remotedeafened;
|
||||||
|
}
|
||||||
|
else if (players[rankplayer[i]].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE))
|
||||||
|
{
|
||||||
|
voxmic = kp_voice_remotemuted;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voxmic = kp_voice_remoteopen;
|
||||||
|
}
|
||||||
|
|
||||||
|
V_DrawScaledPatch(FACE_X + 10, Y - 4, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, voxmic);
|
||||||
|
}
|
||||||
|
|
||||||
Y -= 18;
|
Y -= 18;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4106,15 +4159,23 @@ static void K_DrawTypingDot(fixed_t x, fixed_t y, UINT8 duration, player_t *p, I
|
||||||
|
|
||||||
static void K_DrawTypingNotifier(fixed_t x, fixed_t y, player_t *p, INT32 flags)
|
static void K_DrawTypingNotifier(fixed_t x, fixed_t y, player_t *p, INT32 flags)
|
||||||
{
|
{
|
||||||
if (p->cmd.flags & TICCMD_TYPING)
|
int playernum = p - players;
|
||||||
|
if (p->cmd.flags & TICCMD_TYPING || S_IsPlayerVoiceActive(playernum))
|
||||||
{
|
{
|
||||||
V_DrawFixedPatch(x, y, FRACUNIT, V_SPLITSCREEN|flags, kp_talk, NULL);
|
V_DrawFixedPatch(x, y, FRACUNIT, V_SPLITSCREEN|flags, kp_talk, NULL);
|
||||||
|
}
|
||||||
|
if (p->cmd.flags & TICCMD_TYPING)
|
||||||
|
{
|
||||||
/* spacing closer with the last two looks a better most of the time */
|
/* spacing closer with the last two looks a better most of the time */
|
||||||
K_DrawTypingDot(x + 3*FRACUNIT, y, 15, p, flags);
|
K_DrawTypingDot(x + 3*FRACUNIT, y, 15, p, flags);
|
||||||
K_DrawTypingDot(x + 6*FRACUNIT - FRACUNIT/3, y, 31, p, flags);
|
K_DrawTypingDot(x + 6*FRACUNIT - FRACUNIT/3, y, 31, p, flags);
|
||||||
K_DrawTypingDot(x + 9*FRACUNIT - FRACUNIT/3, y, 47, p, flags);
|
K_DrawTypingDot(x + 9*FRACUNIT - FRACUNIT/3, y, 47, p, flags);
|
||||||
}
|
}
|
||||||
|
else if (S_IsPlayerVoiceActive(playernum))
|
||||||
|
{
|
||||||
|
patch_t* voxmic = kp_voice_tagactive[(leveltime / 3) % 3];
|
||||||
|
V_DrawFixedPatch(x + 6*FRACUNIT, y - 12*FRACUNIT, FRACUNIT, V_SPLITSCREEN|flags, voxmic, NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see also K_drawKartItem
|
// see also K_drawKartItem
|
||||||
|
|
@ -6806,6 +6867,23 @@ void K_drawKartHUD(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO better voice chat speaking indicator integration for spectators
|
||||||
|
{
|
||||||
|
char speakingstring[2048];
|
||||||
|
memset(speakingstring, 0, sizeof(speakingstring));
|
||||||
|
|
||||||
|
for (int i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
if (playeringame[i] && players[i].spectator && S_IsPlayerVoiceActive(i))
|
||||||
|
{
|
||||||
|
strcat(speakingstring, player_names[i]);
|
||||||
|
strcat(speakingstring, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT, speakingstring);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the countdowns after everything else.
|
// Draw the countdowns after everything else.
|
||||||
if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD)
|
if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD)
|
||||||
{
|
{
|
||||||
|
|
@ -6917,6 +6995,48 @@ void K_drawKartHUD(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (netgame && cv_voice_servermute.value == 0)
|
||||||
|
{
|
||||||
|
if (players[consoleplayer].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE | PF2_SELFDEAFEN | PF2_SERVERDEAFEN))
|
||||||
|
{
|
||||||
|
patch_t* micmuted = kp_voice_localmuted;
|
||||||
|
V_DrawFixedPatch(-1 * FRACUNIT, (BASEVIDHEIGHT - 21) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micmuted, NULL);
|
||||||
|
}
|
||||||
|
else if (S_IsPlayerVoiceActive(consoleplayer))
|
||||||
|
{
|
||||||
|
patch_t* micactivebase = kp_voice_localactive[(leveltime / 2) % 16];
|
||||||
|
patch_t* micactivetop = kp_voice_localactiveoverlay[(leveltime / 2) % 16];
|
||||||
|
|
||||||
|
UINT8* micactivecolormap = NULL;
|
||||||
|
if (g_local_voice_last_peak < 0.7)
|
||||||
|
{
|
||||||
|
micactivecolormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_CACHE);
|
||||||
|
}
|
||||||
|
else if (g_local_voice_last_peak < 0.95)
|
||||||
|
{
|
||||||
|
micactivecolormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
micactivecolormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_CACHE);
|
||||||
|
}
|
||||||
|
V_DrawFixedPatch(-15 * FRACUNIT, (BASEVIDHEIGHT - 34) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micactivebase, micactivecolormap);
|
||||||
|
V_DrawFixedPatch(-15 * FRACUNIT, (BASEVIDHEIGHT - 34) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micactivetop, micactivecolormap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
patch_t* micopen = kp_voice_localopen;
|
||||||
|
V_DrawFixedPatch(-1 * FRACUNIT, (BASEVIDHEIGHT - 21) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micopen, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deafen indicator
|
||||||
|
if (players[consoleplayer].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN))
|
||||||
|
{
|
||||||
|
patch_t* deafened = kp_voice_localdeafened;
|
||||||
|
V_DrawFixedPatch(16 * FRACUNIT, (BASEVIDHEIGHT - 15) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, deafened, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
K_DrawWaypointDebugger();
|
K_DrawWaypointDebugger();
|
||||||
K_DrawBotDebugger();
|
K_DrawBotDebugger();
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,7 @@ typedef enum
|
||||||
mopt_profiles = 0,
|
mopt_profiles = 0,
|
||||||
mopt_video,
|
mopt_video,
|
||||||
mopt_sound,
|
mopt_sound,
|
||||||
|
mopt_voice,
|
||||||
mopt_hud,
|
mopt_hud,
|
||||||
mopt_gameplay,
|
mopt_gameplay,
|
||||||
mopt_server,
|
mopt_server,
|
||||||
|
|
@ -468,6 +469,9 @@ extern menu_t OPTIONS_VideoAdvancedDef;
|
||||||
extern menuitem_t OPTIONS_Sound[];
|
extern menuitem_t OPTIONS_Sound[];
|
||||||
extern menu_t OPTIONS_SoundDef;
|
extern menu_t OPTIONS_SoundDef;
|
||||||
|
|
||||||
|
extern menuitem_t OPTIONS_Voice[];
|
||||||
|
extern menu_t OPTIONS_VoiceDef;
|
||||||
|
|
||||||
extern menuitem_t OPTIONS_HUD[];
|
extern menuitem_t OPTIONS_HUD[];
|
||||||
extern menu_t OPTIONS_HUDDef;
|
extern menu_t OPTIONS_HUDDef;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4178,6 +4178,8 @@ void M_DrawMPServerBrowser(void)
|
||||||
servpats[i] = W_CachePatchName(va("M_SERV%c", i + '1'), PU_CACHE);
|
servpats[i] = W_CachePatchName(va("M_SERV%c", i + '1'), PU_CACHE);
|
||||||
gearpats[i] = W_CachePatchName(va("M_SGEAR%c", i + '1'), PU_CACHE);
|
gearpats[i] = W_CachePatchName(va("M_SGEAR%c", i + '1'), PU_CACHE);
|
||||||
}
|
}
|
||||||
|
patch_t *voicepat;
|
||||||
|
voicepat = W_CachePatchName("VOCRMU", PU_CACHE);
|
||||||
|
|
||||||
fixed_t text1loop = SHORT(text1->height)*FRACUNIT;
|
fixed_t text1loop = SHORT(text1->height)*FRACUNIT;
|
||||||
fixed_t text2loop = SHORT(text2->width)*FRACUNIT;
|
fixed_t text2loop = SHORT(text2->width)*FRACUNIT;
|
||||||
|
|
@ -4279,6 +4281,12 @@ void M_DrawMPServerBrowser(void)
|
||||||
V_DrawFixedPatch((startx + 251)*FRACUNIT, (starty + ypos + 9)*FRACUNIT, FRACUNIT, transflag, gearpats[speed], NULL);
|
V_DrawFixedPatch((startx + 251)*FRACUNIT, (starty + ypos + 9)*FRACUNIT, FRACUNIT, transflag, gearpats[speed], NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// voice chat enabled
|
||||||
|
if (serverlist[i].info.kartvars & SV_VOICEENABLED)
|
||||||
|
{
|
||||||
|
V_DrawFixedPatch((startx - 3) * FRACUNIT, (starty + 2) * FRACUNIT, FRACUNIT, 0, voicepat, NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ypos += SERVERSPACE;
|
ypos += SERVERSPACE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -310,10 +310,12 @@ void PR_SaveProfiles(void)
|
||||||
|
|
||||||
for (size_t j = 0; j < num_gamecontrols; j++)
|
for (size_t j = 0; j < num_gamecontrols; j++)
|
||||||
{
|
{
|
||||||
|
std::vector<int32_t> mappings;
|
||||||
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
||||||
{
|
{
|
||||||
jsonprof.controls[j][k] = cprof->controls[j][k];
|
mappings.push_back(cprof->controls[j][k]);
|
||||||
}
|
}
|
||||||
|
jsonprof.controls.emplace_back(std::move(mappings));
|
||||||
}
|
}
|
||||||
|
|
||||||
ng.profiles.emplace_back(std::move(jsonprof));
|
ng.profiles.emplace_back(std::move(jsonprof));
|
||||||
|
|
@ -497,10 +499,25 @@ void PR_LoadProfiles(void)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (size_t j = 0; j < num_gamecontrols; j++)
|
for (size_t j = 0; j < num_gamecontrols; j++)
|
||||||
|
{
|
||||||
|
if (jsprof.controls.size() <= j)
|
||||||
{
|
{
|
||||||
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
||||||
{
|
{
|
||||||
newprof->controls[j][k] = jsprof.controls.at(j).at(k);
|
newprof->controls[j][k] = gamecontroldefault[j][k];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& mappings = jsprof.controls.at(j);
|
||||||
|
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
||||||
|
{
|
||||||
|
if (mappings.size() <= k)
|
||||||
|
{
|
||||||
|
newprof->controls[j][k] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
newprof->controls[j][k] = mappings.at(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ struct ProfileJson
|
||||||
std::string followercolorname;
|
std::string followercolorname;
|
||||||
ProfileRecordsJson records;
|
ProfileRecordsJson records;
|
||||||
ProfilePreferencesJson preferences;
|
ProfilePreferencesJson preferences;
|
||||||
std::array<std::array<int32_t, MAXINPUTMAPPING>, gamecontrols_e::num_gamecontrols> controls = {{{{}}}};
|
std::vector<std::vector<int32_t>> controls = {};
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
|
||||||
ProfileJson,
|
ProfileJson,
|
||||||
|
|
|
||||||
17
src/k_vote.c
17
src/k_vote.c
|
|
@ -1015,6 +1015,23 @@ void Y_VoteDrawer(void)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO better voice chat speaking indicator integration
|
||||||
|
{
|
||||||
|
char speakingstring[2048];
|
||||||
|
memset(speakingstring, 0, sizeof(speakingstring));
|
||||||
|
|
||||||
|
for (int i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
if (S_IsPlayerVoiceActive(i))
|
||||||
|
{
|
||||||
|
strcat(speakingstring, player_names[i]);
|
||||||
|
strcat(speakingstring, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V_DrawThinString(0, 0, 0, speakingstring);
|
||||||
|
}
|
||||||
|
|
||||||
M_DrawMenuForeground();
|
M_DrawMenuForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,25 @@ static void K_MidVoteKick(void)
|
||||||
SendKick(g_midVote.victim - players, KICK_MSG_VOTE_KICK);
|
SendKick(g_midVote.victim - players, KICK_MSG_VOTE_KICK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------
|
||||||
|
static void K_MidVoteMute(void)
|
||||||
|
|
||||||
|
MVT_MUTE's success function.
|
||||||
|
--------------------------------------------------*/
|
||||||
|
static void K_MidVoteMute(void)
|
||||||
|
{
|
||||||
|
UINT8 buf[2];
|
||||||
|
|
||||||
|
if (g_midVote.victim == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[0] = g_midVote.victim - players;
|
||||||
|
buf[1] = 1;
|
||||||
|
SendNetXCmd(XD_SERVERMUTEPLAYER, &buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------
|
/*--------------------------------------------------
|
||||||
static void K_MidVoteRockTheVote(void)
|
static void K_MidVoteRockTheVote(void)
|
||||||
|
|
||||||
|
|
@ -99,6 +118,13 @@ static midVoteTypeDef_t g_midVoteTypeDefs[MVT__MAX] =
|
||||||
K_MidVoteKick
|
K_MidVoteKick
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{ // MVT_MUTE
|
||||||
|
"MUTE",
|
||||||
|
"Mute Player?",
|
||||||
|
CVAR_INIT ("zvote_mute_allowed", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL),
|
||||||
|
K_MidVoteMute
|
||||||
|
},
|
||||||
|
|
||||||
{ // MVT_RTV
|
{ // MVT_RTV
|
||||||
"RTV",
|
"RTV",
|
||||||
"Skip Level?",
|
"Skip Level?",
|
||||||
|
|
@ -127,6 +153,10 @@ boolean K_MidVoteTypeUsesVictim(midVoteType_e voteType)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case MVT_MUTE:
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1186,6 +1216,7 @@ void K_DrawMidVote(void)
|
||||||
switch (g_midVote.type)
|
switch (g_midVote.type)
|
||||||
{
|
{
|
||||||
case MVT_KICK:
|
case MVT_KICK:
|
||||||
|
case MVT_MUTE:
|
||||||
{
|
{
|
||||||
// Draw victim name
|
// Draw victim name
|
||||||
if (g_midVote.victim != NULL)
|
if (g_midVote.victim != NULL)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ extern "C" {
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
MVT_KICK, // Kick another player in the server
|
MVT_KICK, // Kick another player in the server
|
||||||
|
MVT_MUTE, // Mute another player in the server (Voice Chat)
|
||||||
MVT_RTV, // Exit level early
|
MVT_RTV, // Exit level early
|
||||||
MVT_RUNITBACK, // Restart level fresh
|
MVT_RUNITBACK, // Restart level fresh
|
||||||
MVT__MAX, // Total number of vote types
|
MVT__MAX, // Total number of vote types
|
||||||
|
|
|
||||||
21
src/m_swap.h
21
src/m_swap.h
|
|
@ -35,18 +35,39 @@ extern "C" {
|
||||||
| \
|
| \
|
||||||
(((UINT32)(x) & (UINT32)0xff000000UL) >> 24)))
|
(((UINT32)(x) & (UINT32)0xff000000UL) >> 24)))
|
||||||
|
|
||||||
|
#define SWAP_LONGLONG(x) ((INT64)(\
|
||||||
|
(((UINT64)(x) & (UINT64)0x00000000000000ffULL) << 56) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0x000000000000ff00ULL) << 40) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0x0000000000ff0000ULL) << 24) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0x00000000ff000000ULL) << 8) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0x000000ff00000000ULL) >> 8) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0x0000ff0000000000ULL) >> 24) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0x00ff000000000000ULL) >> 40) \
|
||||||
|
| \
|
||||||
|
(((UINT64)(x) & (UINT64)0xff00000000000000ULL) >> 56)))
|
||||||
|
|
||||||
// Endianess handling.
|
// Endianess handling.
|
||||||
// WAD files are stored little endian.
|
// WAD files are stored little endian.
|
||||||
#ifdef SRB2_BIG_ENDIAN
|
#ifdef SRB2_BIG_ENDIAN
|
||||||
#define SHORT SWAP_SHORT
|
#define SHORT SWAP_SHORT
|
||||||
#define LONG SWAP_LONG
|
#define LONG SWAP_LONG
|
||||||
|
#define LONGLON SWAP_LONGLONG
|
||||||
#define MSBF_SHORT(x) ((INT16)(x))
|
#define MSBF_SHORT(x) ((INT16)(x))
|
||||||
#define MSBF_LONG(x) ((INT32)(x))
|
#define MSBF_LONG(x) ((INT32)(x))
|
||||||
|
#define MSBF_LONGLONG(x) ((INT64)(x))
|
||||||
#else
|
#else
|
||||||
#define SHORT(x) ((INT16)(x))
|
#define SHORT(x) ((INT16)(x))
|
||||||
#define LONG(x) ((INT32)(x))
|
#define LONG(x) ((INT32)(x))
|
||||||
|
#define LONGLONG(x) ((INT64)(x))
|
||||||
#define MSBF_SHORT SWAP_SHORT
|
#define MSBF_SHORT SWAP_SHORT
|
||||||
#define MSBF_LONG SWAP_LONG
|
#define MSBF_LONG SWAP_LONG
|
||||||
|
#define MSBF_LONGLONG SWAP_LONGLONG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Big to little endian
|
// Big to little endian
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ target_sources(SRB2SDL2 PRIVATE
|
||||||
options-video-1.c
|
options-video-1.c
|
||||||
options-video-advanced.c
|
options-video-advanced.c
|
||||||
options-video-modes.c
|
options-video-modes.c
|
||||||
|
options-voice.cpp
|
||||||
play-1.c
|
play-1.c
|
||||||
play-char-select.c
|
play-char-select.c
|
||||||
play-local-1.c
|
play-local-1.c
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ menuitem_t OPTIONS_Main[] =
|
||||||
{IT_STRING | IT_CALL, "Sound Options", "Adjust the volume.",
|
{IT_STRING | IT_CALL, "Sound Options", "Adjust the volume.",
|
||||||
NULL, {.routine = M_SoundOptions}, 0, 0},
|
NULL, {.routine = M_SoundOptions}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_SUBMENU, "Voice Options", "Adjust voice chat.",
|
||||||
|
NULL, {.submenu = &OPTIONS_VoiceDef}, 0, 0},
|
||||||
|
|
||||||
{IT_STRING | IT_SUBMENU, "HUD Options", "Tweak the Heads-Up Display.",
|
{IT_STRING | IT_SUBMENU, "HUD Options", "Tweak the Heads-Up Display.",
|
||||||
NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0},
|
NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,9 @@ menuitem_t OPTIONS_ProfileControls[] = {
|
||||||
{IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.",
|
{IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.",
|
||||||
NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0},
|
NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0},
|
||||||
|
|
||||||
|
{IT_CONTROL, "PUSH-TO-TALK", "Activates voice chat transmission in Push-to-Talk (PTT) mode.",
|
||||||
|
NULL, {.routine = M_ProfileSetControl}, gc_voicepushtotalk, 0},
|
||||||
|
|
||||||
{IT_CONTROL, "LUA/1", "May be used by add-ons.",
|
{IT_CONTROL, "LUA/1", "May be used by add-ons.",
|
||||||
NULL, {.routine = M_ProfileSetControl}, gc_lua1, 0},
|
NULL, {.routine = M_ProfileSetControl}, gc_lua1, 0},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ menuitem_t OPTIONS_Server[] =
|
||||||
{IT_STRING | IT_CVAR, "Mute Chat", "Prevent everyone but admins from sending chat messages.",
|
{IT_STRING | IT_CVAR, "Mute Chat", "Prevent everyone but admins from sending chat messages.",
|
||||||
NULL, {.cvar = &cv_mute}, 0, 0},
|
NULL, {.cvar = &cv_mute}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Mute Voice Chat", "Prevent everyone from sending voice chat.",
|
||||||
|
NULL, {.cvar = &cv_voice_servermute}, 0, 0},
|
||||||
|
|
||||||
{IT_STRING | IT_CVAR, "Chat Spam Protection", "Prevent too many messages from a single player.",
|
{IT_STRING | IT_CVAR, "Chat Spam Protection", "Prevent too many messages from a single player.",
|
||||||
NULL, {.cvar = &cv_chatspamprotection}, 0, 0},
|
NULL, {.cvar = &cv_chatspamprotection}, 0, 0},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ struct Slider
|
||||||
kMasterVolume,
|
kMasterVolume,
|
||||||
kMusicVolume,
|
kMusicVolume,
|
||||||
kSfxVolume,
|
kSfxVolume,
|
||||||
|
kVoiceVolume,
|
||||||
kNumSliders
|
kNumSliders
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -120,6 +121,7 @@ std::array<Slider, Slider::kNumSliders> sliders{{
|
||||||
n = !n;
|
n = !n;
|
||||||
CV_SetValue(&cv_gamedigimusic, n);
|
CV_SetValue(&cv_gamedigimusic, n);
|
||||||
CV_SetValue(&cv_gamesounds, n);
|
CV_SetValue(&cv_gamesounds, n);
|
||||||
|
CV_SetValue(&cv_voice_chat, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
|
|
@ -150,6 +152,18 @@ std::array<Slider, Slider::kNumSliders> sliders{{
|
||||||
},
|
},
|
||||||
cv_soundvolume,
|
cv_soundvolume,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
[](bool toggle) -> bool
|
||||||
|
{
|
||||||
|
if (toggle)
|
||||||
|
{
|
||||||
|
CV_AddValue(&cv_voice_chat, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !S_VoiceDisabled();
|
||||||
|
},
|
||||||
|
cv_voicevolume,
|
||||||
|
},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
void slider_routine(INT32 c)
|
void slider_routine(INT32 c)
|
||||||
|
|
@ -266,6 +280,9 @@ menuitem_t OPTIONS_Sound[] =
|
||||||
{IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Music Volume", "Loudness of music.",
|
{IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Music Volume", "Loudness of music.",
|
||||||
NULL, {.routine = slider_routine}, 0, Slider::kMusicVolume},
|
NULL, {.routine = slider_routine}, 0, Slider::kMusicVolume},
|
||||||
|
|
||||||
|
{IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Voice Volume", "Loudness of voice chat.",
|
||||||
|
NULL, {.routine = slider_routine}, 0, Slider::kVoiceVolume},
|
||||||
|
|
||||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||||
NULL, {NULL}, 0, 0},
|
NULL, {NULL}, 0, 0},
|
||||||
|
|
||||||
|
|
|
||||||
91
src/menus/options-voice.cpp
Normal file
91
src/menus/options-voice.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
// DR. ROBOTNIK'S RING RACERS
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Copyright (C) 2024 by Kart Krew.
|
||||||
|
//
|
||||||
|
// This program is free software distributed under the
|
||||||
|
// terms of the GNU General Public License, version 2.
|
||||||
|
// See the 'LICENSE' file for more details.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
/// \file menus/options-voice.cpp
|
||||||
|
/// \brief Voice Options
|
||||||
|
|
||||||
|
#include "../m_easing.h"
|
||||||
|
#include "../k_menu.h"
|
||||||
|
#include "../s_sound.h" // sounds consvars
|
||||||
|
#include "../v_video.h"
|
||||||
|
|
||||||
|
menuitem_t OPTIONS_Voice[] =
|
||||||
|
{
|
||||||
|
{IT_STRING | IT_CVAR, "Voice Chat", "Turn on or off all voice chat for yourself.",
|
||||||
|
NULL, {.cvar = &cv_voice_chat}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Voice Mode", "When to transmit your own voice.",
|
||||||
|
NULL, {.cvar = &cv_voice_mode}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Input Amplifier", "Amplify your voice, in decibels. Negative values are quieter.",
|
||||||
|
NULL, {.cvar = &cv_voice_inputamp}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Activation Threshold", "Voice higher than this threshold will transmit, in decibels.",
|
||||||
|
NULL, {.cvar = &cv_voice_activationthreshold}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Self Voice Mute", "Whether your voice is transmitted or not.",
|
||||||
|
NULL, {.cvar = &cv_voice_selfmute}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Voice Loopback", "Play your own recording voice back.",
|
||||||
|
NULL, {.cvar = &cv_voice_loopback}, 0, 0},
|
||||||
|
|
||||||
|
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||||
|
NULL, {NULL}, 0, 0},
|
||||||
|
|
||||||
|
{IT_HEADER, "Server Voice Options...", NULL,
|
||||||
|
NULL, {NULL}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Server Voice Mute", "All voice chat will be disabled on your server.",
|
||||||
|
NULL, {.cvar = &cv_voice_servermute}, 0, 0},
|
||||||
|
|
||||||
|
{IT_STRING | IT_CVAR, "Proximity Effects", "Player voices will be adjusted relative to you.",
|
||||||
|
NULL, {.cvar = &cv_voice_proximity}, 0, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void draw_routine()
|
||||||
|
{
|
||||||
|
M_DrawGenericOptions();
|
||||||
|
|
||||||
|
int x = currentMenu->x - M_EaseWithTransition(Easing_Linear, 5 * 48);
|
||||||
|
int y = currentMenu->y - 12;
|
||||||
|
int range = 220;
|
||||||
|
float last_peak = g_local_voice_last_peak * range;
|
||||||
|
boolean detected = g_local_voice_detected;
|
||||||
|
INT32 color = detected ? 65 : 23;
|
||||||
|
|
||||||
|
V_DrawFill(x, y, range + 2, 10, 31);
|
||||||
|
V_DrawFill(x + 1, y + 1, (int) last_peak, 8, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tick_routine()
|
||||||
|
{
|
||||||
|
M_OptionsTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean input_routine(INT32)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_t OPTIONS_VoiceDef = {
|
||||||
|
sizeof (OPTIONS_Voice) / sizeof (menuitem_t),
|
||||||
|
&OPTIONS_MainDef,
|
||||||
|
0,
|
||||||
|
OPTIONS_Voice,
|
||||||
|
48, 80,
|
||||||
|
SKINCOLOR_ULTRAMARINE, 0,
|
||||||
|
MBF_DRAWBGWHILEPLAYING,
|
||||||
|
NULL,
|
||||||
|
2, 5,
|
||||||
|
draw_routine,
|
||||||
|
M_DrawOptionsCogs,
|
||||||
|
tick_routine,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
input_routine,
|
||||||
|
};
|
||||||
234
src/s_sound.c
234
src/s_sound.c
|
|
@ -266,6 +266,14 @@ boolean S_SoundDisabled(void)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean S_VoiceDisabled(void)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
g_voice_disabled ||
|
||||||
|
(g_fast_forward > 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Stop all sounds, load level info, THEN start sounds.
|
// Stop all sounds, load level info, THEN start sounds.
|
||||||
void S_StopSounds(void)
|
void S_StopSounds(void)
|
||||||
{
|
{
|
||||||
|
|
@ -676,6 +684,13 @@ void S_StopSound(void *origin)
|
||||||
static INT32 actualsfxvolume; // check for change through console
|
static INT32 actualsfxvolume; // check for change through console
|
||||||
static INT32 actualdigmusicvolume;
|
static INT32 actualdigmusicvolume;
|
||||||
static INT32 actualmastervolume;
|
static INT32 actualmastervolume;
|
||||||
|
static INT32 actualvoicevolume;
|
||||||
|
|
||||||
|
static boolean PointIsLeft(float ax, float ay, float bx, float by)
|
||||||
|
{
|
||||||
|
// return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x) > 0;
|
||||||
|
return ax * by - ay * bx > 0;
|
||||||
|
}
|
||||||
|
|
||||||
void S_UpdateSounds(void)
|
void S_UpdateSounds(void)
|
||||||
{
|
{
|
||||||
|
|
@ -694,6 +709,8 @@ void S_UpdateSounds(void)
|
||||||
S_SetMusicVolume();
|
S_SetMusicVolume();
|
||||||
if (actualmastervolume != cv_mastervolume.value)
|
if (actualmastervolume != cv_mastervolume.value)
|
||||||
S_SetMasterVolume();
|
S_SetMasterVolume();
|
||||||
|
if (actualvoicevolume != cv_voicevolume.value)
|
||||||
|
S_SetVoiceVolume();
|
||||||
|
|
||||||
// We're done now, if we're not in a level.
|
// We're done now, if we're not in a level.
|
||||||
if (gamestate != GS_LEVEL)
|
if (gamestate != GS_LEVEL)
|
||||||
|
|
@ -853,6 +870,154 @@ notinlevel:
|
||||||
I_UpdateSound();
|
I_UpdateSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void S_UpdateVoicePositionalProperties(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (gamestate != GS_LEVEL)
|
||||||
|
{
|
||||||
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
I_SetPlayerVoiceProperties(i, 1.0f, 0.0f);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player_t *consoleplr = &players[consoleplayer];
|
||||||
|
listener_t listener = {0};
|
||||||
|
mobj_t *listenmobj = NULL;
|
||||||
|
|
||||||
|
if (consoleplr)
|
||||||
|
{
|
||||||
|
if (consoleplr->awayview.tics)
|
||||||
|
{
|
||||||
|
listenmobj = consoleplr->awayview.mobj;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
listenmobj = consoleplr->mo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera[0].chase && !consoleplr->awayview.tics)
|
||||||
|
{
|
||||||
|
listener.x = camera[0].x;
|
||||||
|
listener.y = camera[0].y;
|
||||||
|
listener.z = camera[0].z;
|
||||||
|
listener.angle = camera[0].angle;
|
||||||
|
}
|
||||||
|
else if (listenmobj)
|
||||||
|
{
|
||||||
|
listener.x = listenmobj->x;
|
||||||
|
listener.y = listenmobj->y;
|
||||||
|
listener.z = listenmobj->z;
|
||||||
|
listener.angle = listenmobj->angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float playerdistances[MAXPLAYERS];
|
||||||
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
player_t *plr = &players[i];
|
||||||
|
mobj_t *mo = plr->mo;
|
||||||
|
if (plr->spectator || !mo)
|
||||||
|
{
|
||||||
|
playerdistances[i] = 0.f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float px = FixedToFloat(mo->x - listener.x);
|
||||||
|
float py = FixedToFloat(mo->y - listener.y);
|
||||||
|
float pz = FixedToFloat(mo->z - listener.z);
|
||||||
|
playerdistances[i] = sqrtf(px * px + py * py + pz * pz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positional voice audio
|
||||||
|
boolean voice_proximity_enabled = cv_voice_proximity.value == 1;
|
||||||
|
float voice_distanceattenuation_distance = FixedToFloat(cv_voice_distanceattenuation_distance.value) * FixedToFloat(mapheaderinfo[gamemap-1]->mobj_scale);
|
||||||
|
float voice_distanceattenuation_factor = FixedToFloat(cv_voice_distanceattenuation_factor.value);
|
||||||
|
float voice_stereopanning_factor = FixedToFloat(cv_voice_stereopanning_factor.value);
|
||||||
|
float voice_concurrentattenuation_min = max(0, min(MAXPLAYERS, cv_voice_concurrentattenuation_min.value));
|
||||||
|
float voice_concurrentattenuation_max = max(0, min(MAXPLAYERS, cv_voice_concurrentattenuation_max.value));
|
||||||
|
voice_concurrentattenuation_min = min(voice_concurrentattenuation_max, voice_concurrentattenuation_min);
|
||||||
|
float voice_concurrentattenuation_factor = FixedToFloat(cv_voice_concurrentattenuation_factor.value);
|
||||||
|
|
||||||
|
// Derive concurrent speaker attenuation
|
||||||
|
float speakingplayers = 0;
|
||||||
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
if (S_IsPlayerVoiceActive(i))
|
||||||
|
{
|
||||||
|
if (voice_distanceattenuation_distance > 0)
|
||||||
|
{
|
||||||
|
speakingplayers += 1.f - (playerdistances[i] / voice_distanceattenuation_distance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// invalid distance attenuation
|
||||||
|
speakingplayers += 1.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
speakingplayers = min(voice_concurrentattenuation_max, max(0, speakingplayers - voice_concurrentattenuation_min));
|
||||||
|
float speakingplayerattenuation = 1.f - (speakingplayers * (voice_concurrentattenuation_factor / voice_concurrentattenuation_max));
|
||||||
|
|
||||||
|
for (i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
if (!playeringame[i] || !voice_proximity_enabled)
|
||||||
|
{
|
||||||
|
I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == consoleplayer)
|
||||||
|
{
|
||||||
|
I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
player_t *plr = &players[i];
|
||||||
|
if (plr->spectator)
|
||||||
|
{
|
||||||
|
I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mobj_t *plrmobj = plr->mo;
|
||||||
|
|
||||||
|
if (!plrmobj)
|
||||||
|
{
|
||||||
|
I_SetPlayerVoiceProperties(i, 1.0f, 0.0f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float lx = FixedToFloat(listener.x);
|
||||||
|
float ly = FixedToFloat(listener.y);
|
||||||
|
float lz = FixedToFloat(listener.z);
|
||||||
|
float px = FixedToFloat(plrmobj->x) - lx;
|
||||||
|
float py = FixedToFloat(plrmobj->y) - ly;
|
||||||
|
float pz = FixedToFloat(plrmobj->z) - lz;
|
||||||
|
|
||||||
|
float ldirx = cosf(ANG2RAD(listener.angle));
|
||||||
|
float ldiry = sinf(ANG2RAD(listener.angle));
|
||||||
|
float pdistance = sqrtf(px * px + py * py + pz * pz);
|
||||||
|
float p2ddistance = sqrtf(px * px + py * py);
|
||||||
|
float pdirx = px / p2ddistance;
|
||||||
|
float pdiry = py / p2ddistance;
|
||||||
|
float angle = acosf(pdirx * ldirx + pdiry * ldiry);
|
||||||
|
angle = PointIsLeft(ldirx, ldiry, pdirx, pdiry) ? -angle : angle;
|
||||||
|
|
||||||
|
float plrvolume = 1.0f;
|
||||||
|
if (voice_distanceattenuation_distance > 0 && voice_distanceattenuation_factor >= 0 && voice_distanceattenuation_factor <= 1.0f)
|
||||||
|
{
|
||||||
|
float invfactor = 1.0f - voice_distanceattenuation_factor;
|
||||||
|
float distfactor = max(0.f, min(voice_distanceattenuation_distance, pdistance)) / voice_distanceattenuation_distance;
|
||||||
|
plrvolume = max(0.0f, min(1.0f, 1.0f - (invfactor * distfactor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float fsep = sinf(angle) * max(0.0f, min(1.0f, voice_stereopanning_factor));
|
||||||
|
I_SetPlayerVoiceProperties(i, plrvolume * speakingplayerattenuation, fsep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void S_UpdateClosedCaptions(void)
|
void S_UpdateClosedCaptions(void)
|
||||||
{
|
{
|
||||||
UINT8 i;
|
UINT8 i;
|
||||||
|
|
@ -887,6 +1052,13 @@ void S_SetSfxVolume(void)
|
||||||
I_SetSfxVolume(actualsfxvolume);
|
I_SetSfxVolume(actualsfxvolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void S_SetVoiceVolume(void)
|
||||||
|
{
|
||||||
|
actualvoicevolume = cv_voicevolume.value;
|
||||||
|
|
||||||
|
I_SetVoiceVolume(actualvoicevolume);
|
||||||
|
}
|
||||||
|
|
||||||
void S_SetMasterVolume(void)
|
void S_SetMasterVolume(void)
|
||||||
{
|
{
|
||||||
actualmastervolume = cv_mastervolume.value;
|
actualmastervolume = cv_mastervolume.value;
|
||||||
|
|
@ -2639,6 +2811,18 @@ void GameDigiMusic_OnChange(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VoiceChat_OnChange(void);
|
||||||
|
void weaponPrefChange(INT32 ssplayer);
|
||||||
|
void VoiceChat_OnChange(void)
|
||||||
|
{
|
||||||
|
if (M_CheckParm("-novoice") || M_CheckParm("-noaudio"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_voice_disabled = !cv_voice_chat.value;
|
||||||
|
|
||||||
|
weaponPrefChange(0);
|
||||||
|
}
|
||||||
|
|
||||||
void BGAudio_OnChange(void);
|
void BGAudio_OnChange(void);
|
||||||
void BGAudio_OnChange(void)
|
void BGAudio_OnChange(void)
|
||||||
{
|
{
|
||||||
|
|
@ -2656,3 +2840,53 @@ void BGAudio_OnChange(void)
|
||||||
if (window_notinfocus && !(cv_bgaudio.value & 2))
|
if (window_notinfocus && !(cv_bgaudio.value & 2))
|
||||||
S_StopSounds();
|
S_StopSounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean S_SoundInputIsEnabled(void)
|
||||||
|
{
|
||||||
|
return I_SoundInputIsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean S_SoundInputSetEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
return I_SoundInputSetEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 S_SoundInputDequeueSamples(void *data, UINT32 len)
|
||||||
|
{
|
||||||
|
return I_SoundInputDequeueSamples(data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static INT32 g_playerlastvoiceactive[MAXPLAYERS];
|
||||||
|
|
||||||
|
void S_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
||||||
|
{
|
||||||
|
if (dedicated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cv_voice_chat.value != 0)
|
||||||
|
{
|
||||||
|
I_QueueVoiceFrameFromPlayer(playernum, data, len, terminal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void S_SetPlayerVoiceActive(INT32 playernum)
|
||||||
|
{
|
||||||
|
g_playerlastvoiceactive[playernum] = I_GetTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean S_IsPlayerVoiceActive(INT32 playernum)
|
||||||
|
{
|
||||||
|
return I_GetTime() - g_playerlastvoiceactive[playernum] < 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void S_ResetVoiceQueue(INT32 playernum)
|
||||||
|
{
|
||||||
|
if (dedicated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
I_ResetVoiceQueue(playernum);
|
||||||
|
g_playerlastvoiceactive[playernum] = 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ extern "C" {
|
||||||
|
|
||||||
extern consvar_t stereoreverse;
|
extern consvar_t stereoreverse;
|
||||||
extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume;
|
extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume;
|
||||||
|
extern consvar_t cv_voicevolume;
|
||||||
|
|
||||||
extern consvar_t surround;
|
extern consvar_t surround;
|
||||||
extern consvar_t cv_numChannels;
|
extern consvar_t cv_numChannels;
|
||||||
|
|
@ -47,6 +48,22 @@ extern consvar_t cv_gamesounds;
|
||||||
extern consvar_t cv_bgaudio;
|
extern consvar_t cv_bgaudio;
|
||||||
extern consvar_t cv_streamersafemusic;
|
extern consvar_t cv_streamersafemusic;
|
||||||
|
|
||||||
|
extern consvar_t cv_voice_chat;
|
||||||
|
extern consvar_t cv_voice_mode;
|
||||||
|
extern consvar_t cv_voice_selfmute;
|
||||||
|
extern consvar_t cv_voice_loopback;
|
||||||
|
extern consvar_t cv_voice_inputamp;
|
||||||
|
extern consvar_t cv_voice_activationthreshold;
|
||||||
|
extern consvar_t cv_voice_proximity;
|
||||||
|
extern consvar_t cv_voice_distanceattenuation_distance;
|
||||||
|
extern consvar_t cv_voice_distanceattenuation_factor;
|
||||||
|
extern consvar_t cv_voice_stereopanning_factor;
|
||||||
|
extern consvar_t cv_voice_concurrentattenuation_factor;
|
||||||
|
extern consvar_t cv_voice_concurrentattenuation_min;
|
||||||
|
extern consvar_t cv_voice_concurrentattenuation_max;
|
||||||
|
extern float g_local_voice_last_peak;
|
||||||
|
extern boolean g_local_voice_detected;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
SF_TOTALLYSINGLE = 1, // Only play one of these sounds at a time...GLOBALLY
|
SF_TOTALLYSINGLE = 1, // Only play one of these sounds at a time...GLOBALLY
|
||||||
|
|
@ -122,6 +139,8 @@ lumpnum_t S_GetSfxLumpNum(sfxinfo_t *sfx);
|
||||||
|
|
||||||
boolean S_SoundDisabled(void);
|
boolean S_SoundDisabled(void);
|
||||||
|
|
||||||
|
boolean S_VoiceDisabled(void);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Start sound for thing at <origin> using <sound_id> from sounds.h
|
// Start sound for thing at <origin> using <sound_id> from sounds.h
|
||||||
//
|
//
|
||||||
|
|
@ -249,6 +268,7 @@ void S_AttemptToRestoreMusic(void);
|
||||||
//
|
//
|
||||||
void S_UpdateSounds(void);
|
void S_UpdateSounds(void);
|
||||||
void S_UpdateClosedCaptions(void);
|
void S_UpdateClosedCaptions(void);
|
||||||
|
void S_UpdateVoicePositionalProperties(void);
|
||||||
|
|
||||||
FUNCMATH fixed_t S_CalculateSoundDistance(fixed_t px1, fixed_t py1, fixed_t pz1, fixed_t px2, fixed_t py2, fixed_t pz2);
|
FUNCMATH fixed_t S_CalculateSoundDistance(fixed_t px1, fixed_t py1, fixed_t pz1, fixed_t px2, fixed_t py2, fixed_t pz2);
|
||||||
|
|
||||||
|
|
@ -257,6 +277,7 @@ INT32 S_GetSoundVolume(sfxinfo_t *sfx, INT32 volume);
|
||||||
void S_SetSfxVolume(void);
|
void S_SetSfxVolume(void);
|
||||||
void S_SetMusicVolume(void);
|
void S_SetMusicVolume(void);
|
||||||
void S_SetMasterVolume(void);
|
void S_SetMasterVolume(void);
|
||||||
|
void S_SetVoiceVolume(void);
|
||||||
|
|
||||||
INT32 S_OriginPlaying(void *origin);
|
INT32 S_OriginPlaying(void *origin);
|
||||||
INT32 S_IdPlaying(sfxenum_t id);
|
INT32 S_IdPlaying(sfxenum_t id);
|
||||||
|
|
@ -270,6 +291,15 @@ void S_StopSoundByNum(sfxenum_t sfxnum);
|
||||||
#define S_StartAttackSound S_StartSound
|
#define S_StartAttackSound S_StartSound
|
||||||
#define S_StartScreamSound S_StartSound
|
#define S_StartScreamSound S_StartSound
|
||||||
|
|
||||||
|
boolean S_SoundInputIsEnabled(void);
|
||||||
|
boolean S_SoundInputSetEnabled(boolean enabled);
|
||||||
|
UINT32 S_SoundInputDequeueSamples(void *data, UINT32 len);
|
||||||
|
|
||||||
|
void S_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal);
|
||||||
|
void S_SetPlayerVoiceActive(INT32 playernum);
|
||||||
|
boolean S_IsPlayerVoiceActive(INT32 playernum);
|
||||||
|
void S_ResetVoiceQueue(INT32 playernum);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,125 @@ using srb2::audio::Source;
|
||||||
using namespace srb2;
|
using namespace srb2;
|
||||||
using namespace srb2::io;
|
using namespace srb2::io;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class SdlAudioStream final
|
||||||
|
{
|
||||||
|
SDL_AudioStream* stream_;
|
||||||
|
public:
|
||||||
|
SdlAudioStream(const SDL_AudioFormat format, const Uint8 channels, const int src_rate, const SDL_AudioFormat dst_format, const Uint8 dst_channels, const int dst_rate) noexcept
|
||||||
|
{
|
||||||
|
stream_ = SDL_NewAudioStream(format, channels, src_rate, dst_format, dst_channels, dst_rate);
|
||||||
|
}
|
||||||
|
SdlAudioStream(const SdlAudioStream&) = delete;
|
||||||
|
SdlAudioStream(SdlAudioStream&&) = default;
|
||||||
|
SdlAudioStream& operator=(const SdlAudioStream&) = delete;
|
||||||
|
SdlAudioStream& operator=(SdlAudioStream&&) = default;
|
||||||
|
~SdlAudioStream()
|
||||||
|
{
|
||||||
|
SDL_FreeAudioStream(stream_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(tcb::span<const std::byte> buf)
|
||||||
|
{
|
||||||
|
int result = SDL_AudioStreamPut(stream_, buf.data(), buf.size_bytes());
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
char errbuf[512];
|
||||||
|
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
||||||
|
throw std::runtime_error(errbuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t available() const
|
||||||
|
{
|
||||||
|
int result = SDL_AudioStreamAvailable(stream_);
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
char errbuf[512];
|
||||||
|
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
||||||
|
throw std::runtime_error(errbuf);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get(tcb::span<std::byte> out)
|
||||||
|
{
|
||||||
|
int result = SDL_AudioStreamGet(stream_, out.data(), out.size_bytes());
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
char errbuf[512];
|
||||||
|
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
||||||
|
throw std::runtime_error(errbuf);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
SDL_AudioStreamClear(stream_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SdlVoiceStreamPlayer : public Source<2>
|
||||||
|
{
|
||||||
|
SdlAudioStream stream_;
|
||||||
|
float volume_ = 1.0f;
|
||||||
|
float sep_ = 0.0f;
|
||||||
|
bool terminal_ = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SdlVoiceStreamPlayer() : stream_(AUDIO_F32SYS, 1, 48000, AUDIO_F32SYS, 2, 44100) {}
|
||||||
|
virtual ~SdlVoiceStreamPlayer() = default;
|
||||||
|
|
||||||
|
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override
|
||||||
|
{
|
||||||
|
size_t written = stream_.get(tcb::as_writable_bytes(buffer)) / sizeof(Sample<2>);
|
||||||
|
|
||||||
|
for (size_t i = written; i < buffer.size(); i++)
|
||||||
|
{
|
||||||
|
buffer[i] = {0.f, 0.f};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply gain de-popping if the last generation was terminal
|
||||||
|
if (terminal_)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < std::min<size_t>(16, written); i++)
|
||||||
|
{
|
||||||
|
buffer[i].amplitudes[0] *= (float)(i) / 16;
|
||||||
|
buffer[i].amplitudes[1] *= (float)(i) / 16;
|
||||||
|
}
|
||||||
|
terminal_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (written < buffer.size())
|
||||||
|
{
|
||||||
|
terminal_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < written; i++)
|
||||||
|
{
|
||||||
|
float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f);
|
||||||
|
|
||||||
|
float left_scale = std::cos(sep_pan);
|
||||||
|
float right_scale = std::sin(sep_pan);
|
||||||
|
buffer[i] = {buffer[i].amplitudes[0] * volume_ * left_scale, buffer[i].amplitudes[1] * volume_ * right_scale};
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.size();
|
||||||
|
};
|
||||||
|
|
||||||
|
SdlAudioStream& stream() noexcept { return stream_; }
|
||||||
|
|
||||||
|
void set_properties(float volume, float sep) noexcept
|
||||||
|
{
|
||||||
|
volume_ = volume;
|
||||||
|
sep_ = sep;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// extern in i_sound.h
|
// extern in i_sound.h
|
||||||
UINT8 sound_started = false;
|
UINT8 sound_started = false;
|
||||||
|
|
||||||
|
|
@ -60,13 +179,16 @@ static unique_ptr<Gain<2>> master_gain;
|
||||||
static shared_ptr<Mixer<2>> master;
|
static shared_ptr<Mixer<2>> master;
|
||||||
static shared_ptr<Mixer<2>> mixer_sound_effects;
|
static shared_ptr<Mixer<2>> mixer_sound_effects;
|
||||||
static shared_ptr<Mixer<2>> mixer_music;
|
static shared_ptr<Mixer<2>> mixer_music;
|
||||||
|
static shared_ptr<Mixer<2>> mixer_voice;
|
||||||
static shared_ptr<MusicPlayer> music_player;
|
static shared_ptr<MusicPlayer> music_player;
|
||||||
static shared_ptr<Resampler<2>> resample_music_player;
|
static shared_ptr<Resampler<2>> resample_music_player;
|
||||||
static shared_ptr<Gain<2>> gain_sound_effects;
|
static shared_ptr<Gain<2>> gain_sound_effects;
|
||||||
static shared_ptr<Gain<2>> gain_music_player;
|
static shared_ptr<Gain<2>> gain_music_player;
|
||||||
static shared_ptr<Gain<2>> gain_music_channel;
|
static shared_ptr<Gain<2>> gain_music_channel;
|
||||||
|
static shared_ptr<Gain<2>> gain_voice_channel;
|
||||||
|
|
||||||
static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
|
static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
|
||||||
|
static vector<shared_ptr<SdlVoiceStreamPlayer>> player_voice_channels;
|
||||||
|
|
||||||
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
||||||
static shared_ptr<srb2::media::AVRecorder> av_recorder;
|
static shared_ptr<srb2::media::AVRecorder> av_recorder;
|
||||||
|
|
@ -74,6 +196,10 @@ static shared_ptr<srb2::media::AVRecorder> av_recorder;
|
||||||
|
|
||||||
static void (*music_fade_callback)();
|
static void (*music_fade_callback)();
|
||||||
|
|
||||||
|
static SDL_AudioDeviceID g_device_id;
|
||||||
|
static SDL_AudioDeviceID g_input_device_id;
|
||||||
|
static boolean g_input_device_paused;
|
||||||
|
|
||||||
void* I_GetSfx(sfxinfo_t* sfx)
|
void* I_GetSfx(sfxinfo_t* sfx)
|
||||||
{
|
{
|
||||||
if (sfx->lumpnum == LUMPERROR)
|
if (sfx->lumpnum == LUMPERROR)
|
||||||
|
|
@ -120,8 +246,8 @@ namespace
|
||||||
class SdlAudioLockHandle
|
class SdlAudioLockHandle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SdlAudioLockHandle() { SDL_LockAudio(); }
|
SdlAudioLockHandle() { SDL_LockAudioDevice(g_device_id); }
|
||||||
~SdlAudioLockHandle() { SDL_UnlockAudio(); }
|
~SdlAudioLockHandle() { SDL_UnlockAudioDevice(g_device_id); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
|
|
@ -180,21 +306,21 @@ void initialize_sound()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AudioSpec desired;
|
SDL_AudioSpec desired {};
|
||||||
desired.format = AUDIO_F32SYS;
|
desired.format = AUDIO_F32SYS;
|
||||||
desired.channels = 2;
|
desired.channels = 2;
|
||||||
desired.samples = cv_soundmixingbuffersize.value;
|
desired.samples = cv_soundmixingbuffersize.value;
|
||||||
desired.freq = 44100;
|
desired.freq = 44100;
|
||||||
desired.callback = audio_callback;
|
desired.callback = audio_callback;
|
||||||
|
|
||||||
if (SDL_OpenAudio(&desired, NULL) < 0)
|
if ((g_device_id = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, NULL, 0)) == 0)
|
||||||
{
|
{
|
||||||
CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError());
|
CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError());
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_PauseAudio(SDL_FALSE);
|
SDL_PauseAudioDevice(g_device_id, SDL_FALSE);
|
||||||
|
|
||||||
{
|
{
|
||||||
SdlAudioLockHandle _;
|
SdlAudioLockHandle _;
|
||||||
|
|
@ -204,16 +330,20 @@ void initialize_sound()
|
||||||
master_gain->bind(master);
|
master_gain->bind(master);
|
||||||
mixer_sound_effects = make_shared<Mixer<2>>();
|
mixer_sound_effects = make_shared<Mixer<2>>();
|
||||||
mixer_music = make_shared<Mixer<2>>();
|
mixer_music = make_shared<Mixer<2>>();
|
||||||
|
mixer_voice = make_shared<Mixer<2>>();
|
||||||
music_player = make_shared<MusicPlayer>();
|
music_player = make_shared<MusicPlayer>();
|
||||||
resample_music_player = make_shared<Resampler<2>>(music_player, 1.f);
|
resample_music_player = make_shared<Resampler<2>>(music_player, 1.f);
|
||||||
gain_sound_effects = make_shared<Gain<2>>();
|
gain_sound_effects = make_shared<Gain<2>>();
|
||||||
gain_music_player = make_shared<Gain<2>>();
|
gain_music_player = make_shared<Gain<2>>();
|
||||||
gain_music_channel = make_shared<Gain<2>>();
|
gain_music_channel = make_shared<Gain<2>>();
|
||||||
|
gain_voice_channel = make_shared<Gain<2>>();
|
||||||
gain_sound_effects->bind(mixer_sound_effects);
|
gain_sound_effects->bind(mixer_sound_effects);
|
||||||
gain_music_player->bind(resample_music_player);
|
gain_music_player->bind(resample_music_player);
|
||||||
gain_music_channel->bind(mixer_music);
|
gain_music_channel->bind(mixer_music);
|
||||||
|
gain_voice_channel->bind(mixer_voice);
|
||||||
master->add_source(gain_sound_effects);
|
master->add_source(gain_sound_effects);
|
||||||
master->add_source(gain_music_channel);
|
master->add_source(gain_music_channel);
|
||||||
|
master->add_source(gain_voice_channel);
|
||||||
mixer_music->add_source(gain_music_player);
|
mixer_music->add_source(gain_music_player);
|
||||||
sound_effect_channels.clear();
|
sound_effect_channels.clear();
|
||||||
for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++)
|
for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++)
|
||||||
|
|
@ -222,6 +352,13 @@ void initialize_sound()
|
||||||
sound_effect_channels.push_back(player);
|
sound_effect_channels.push_back(player);
|
||||||
mixer_sound_effects->add_source(player);
|
mixer_sound_effects->add_source(player);
|
||||||
}
|
}
|
||||||
|
player_voice_channels.clear();
|
||||||
|
for (size_t i = 0; i < MAXPLAYERS; i++)
|
||||||
|
{
|
||||||
|
shared_ptr<SdlVoiceStreamPlayer> player = make_shared<SdlVoiceStreamPlayer>();
|
||||||
|
player_voice_channels.push_back(player);
|
||||||
|
mixer_voice->add_source(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sound_started = true;
|
sound_started = true;
|
||||||
|
|
@ -237,7 +374,17 @@ void I_StartupSound(void)
|
||||||
|
|
||||||
void I_ShutdownSound(void)
|
void I_ShutdownSound(void)
|
||||||
{
|
{
|
||||||
SDL_CloseAudio();
|
if (g_device_id)
|
||||||
|
{
|
||||||
|
SDL_CloseAudioDevice(g_device_id);
|
||||||
|
g_device_id = 0;
|
||||||
|
}
|
||||||
|
if (g_input_device_id)
|
||||||
|
{
|
||||||
|
SDL_CloseAudioDevice(g_input_device_id);
|
||||||
|
g_input_device_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
|
|
||||||
sound_started = false;
|
sound_started = false;
|
||||||
|
|
@ -380,6 +527,17 @@ void I_SetSfxVolume(int volume)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void I_SetVoiceVolume(int volume)
|
||||||
|
{
|
||||||
|
SdlAudioLockHandle _;
|
||||||
|
float vol = static_cast<float>(volume) / 100.f;
|
||||||
|
|
||||||
|
if (gain_voice_channel)
|
||||||
|
{
|
||||||
|
gain_voice_channel->gain(std::clamp(vol * vol * vol, 0.f, 1.f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void I_SetMasterVolume(int volume)
|
void I_SetMasterVolume(int volume)
|
||||||
{
|
{
|
||||||
SdlAudioLockHandle _;
|
SdlAudioLockHandle _;
|
||||||
|
|
@ -824,3 +982,93 @@ void I_UpdateAudioRecorder(void)
|
||||||
av_recorder = g_av_recorder;
|
av_recorder = g_av_recorder;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean I_SoundInputIsEnabled(void)
|
||||||
|
{
|
||||||
|
return g_input_device_id != 0 && !g_input_device_paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean I_SoundInputSetEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
if (g_input_device_id == 0 && enabled)
|
||||||
|
{
|
||||||
|
SDL_AudioSpec input_desired {};
|
||||||
|
input_desired.format = AUDIO_F32SYS;
|
||||||
|
input_desired.channels = 1;
|
||||||
|
input_desired.samples = 2048;
|
||||||
|
input_desired.freq = 48000;
|
||||||
|
SDL_AudioSpec input_obtained {};
|
||||||
|
g_input_device_id = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &input_desired, &input_obtained, 0);
|
||||||
|
if (!g_input_device_id)
|
||||||
|
{
|
||||||
|
CONS_Alert(CONS_WARNING, "Failed to open input audio device: %s\n", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_input_device_paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled && g_input_device_paused)
|
||||||
|
{
|
||||||
|
SDL_PauseAudioDevice(g_input_device_id, SDL_FALSE);
|
||||||
|
g_input_device_paused = false;
|
||||||
|
}
|
||||||
|
else if (!enabled && !g_input_device_paused)
|
||||||
|
{
|
||||||
|
SDL_PauseAudioDevice(g_input_device_id, SDL_TRUE);
|
||||||
|
SDL_ClearQueuedAudio(g_input_device_id);
|
||||||
|
g_input_device_paused = true;
|
||||||
|
}
|
||||||
|
return !g_input_device_paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len)
|
||||||
|
{
|
||||||
|
if (!g_input_device_id)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
UINT32 avail = SDL_GetQueuedAudioSize(g_input_device_id);
|
||||||
|
if (avail == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 ret = SDL_DequeueAudio(g_input_device_id, data, std::min(len, avail));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
||||||
|
{
|
||||||
|
if (!sound_started)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SdlAudioLockHandle _;
|
||||||
|
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
||||||
|
player->stream().put(tcb::span((std::byte*)data, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep)
|
||||||
|
{
|
||||||
|
if (!sound_started)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SdlAudioLockHandle _;
|
||||||
|
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
||||||
|
player->set_properties(volume * volume * volume, sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_ResetVoiceQueue(INT32 playernum)
|
||||||
|
{
|
||||||
|
if (!sound_started)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SdlAudioLockHandle _;
|
||||||
|
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
||||||
|
player->stream().clear();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ TYPEDEF (resultsall_pak);
|
||||||
TYPEDEF (say_pak);
|
TYPEDEF (say_pak);
|
||||||
TYPEDEF (reqmapqueue_pak);
|
TYPEDEF (reqmapqueue_pak);
|
||||||
TYPEDEF (netinfo_pak);
|
TYPEDEF (netinfo_pak);
|
||||||
|
TYPEDEF (voice_pak);
|
||||||
|
|
||||||
// d_event.h
|
// d_event.h
|
||||||
TYPEDEF (event_t);
|
TYPEDEF (event_t);
|
||||||
|
|
|
||||||
|
|
@ -799,6 +799,39 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
|
||||||
player_names[pnum]
|
player_names[pnum]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
patch_t *voxpat;
|
||||||
|
int voxxoffs = 0;
|
||||||
|
int voxyoffs = 0;
|
||||||
|
if (players[pnum].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN))
|
||||||
|
{
|
||||||
|
voxpat = (patch_t*) W_CachePatchName("VOXCRD", PU_HUDGFX);
|
||||||
|
voxxoffs = 1;
|
||||||
|
voxyoffs = -5;
|
||||||
|
}
|
||||||
|
else if (players[pnum].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE))
|
||||||
|
{
|
||||||
|
voxpat = (patch_t*) W_CachePatchName("VOXCRM", PU_HUDGFX);
|
||||||
|
voxxoffs = 1;
|
||||||
|
voxyoffs = -6;
|
||||||
|
}
|
||||||
|
else if (S_IsPlayerVoiceActive(pnum))
|
||||||
|
{
|
||||||
|
voxpat = (patch_t*) W_CachePatchName("VOXCRA", PU_HUDGFX);
|
||||||
|
voxyoffs = -4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voxpat = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voxpat)
|
||||||
|
{
|
||||||
|
int namewidth = V_ThinStringWidth(player_names[pnum], 0);
|
||||||
|
V_DrawFixedPatch((x + 27 + namewidth + voxxoffs) * FRACUNIT, (y + voxyoffs) * FRACUNIT, FRACUNIT, 0, voxpat, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
V_DrawRightAlignedThinString(
|
V_DrawRightAlignedThinString(
|
||||||
x+118, y-2,
|
x+118, y-2,
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"libvpx",
|
"libvpx",
|
||||||
"libvorbis",
|
"libvorbis",
|
||||||
"libyuv",
|
"libyuv",
|
||||||
|
"opus",
|
||||||
"zlib"
|
"zlib"
|
||||||
],
|
],
|
||||||
"builtin-baseline": "c591ac6466a55ef0a05a3d56bb1489ca36e50102"
|
"builtin-baseline": "c591ac6466a55ef0a05a3d56bb1489ca36e50102"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue