It has a very flexible and user-friendly interface, requires less resources. As opposed to other VoIP apps, it offers a brand-new feature named 'Positioning Audio' supporting many modern competetive games like CSS, CoD, etc via multiple plugins.
We've implemented 'Link plugin for Mumble' support into CNQ3 v1.46. So players can experience full voice connection with the gaming process. Mumble has a very flexible configuration of how player should hear his teammates (even for balancing voice server and game server pings). Link plugin is designed for positioning voices of your actual teammates. So if you talk to another guy playing another match, you won't be confused.
This feature is surely not a must-have one. As voice communication is a replacement for \say_team commands, voice positioning replaces team-overplay hud elements. But all these features are questioned by player's use.
I wonder if this feature can be implemented into CNQ3 by official.
Source: http://www.overplay.cc/files/cnq3/mumble_link_source.rar
Client: http://www.overplay.cc/files/cnq3/cnq3-v1.46_mumble.rar
Source code can still contain some issues due to our weak expierence. But the client works well.
mumble_link.h
- Code: Select all
/* * *
/* * *
* This code is for link with mumble plugin, and must be embedded in CNQ3.
*
* Assembled by DaTa, with help of CAiNE.
*
* Licensed with GPL v2, see enclosed ./COPYING.txt.
*
* Sources of information:
* http://mumble.sourceforge.net/HackPositionalAudio
* http://mumble.sourceforge.net/Link
* http://svn.icculus.org/quake3/trunk/code/client/libmumblelink.c?view=markup idia of unlinking and more correct points of call link functions
*
* Functional:
* Just turn on mumble, enable positional plugin, connect to server and join team.
*
* Cvars:
* mum_enable - (default 1) for switch linking on/off.
* mum_scale - (default 1.0, cheat) scale factor for coordinates, must be same for all player.
* mum_debug - (default 0) switch debug info output on/off on Update() event.
*
* Commands:
* mum_relink - force relink, mum_enable must be 1.
* This is the only way for switch-on linking in-game, in case, for example, if client forgot to turn-on mumble.
* mum_state - print current link state to client.
*
* Implementation on cnq3 guide:
* ../client/cl_cgame.cpp
* After:
* #include "client.h"
* #include "../qcommon/vm_local.h"
* #include "../qcommon/vm_shim.h"
* add:
* #include "../mumble_link/mumble_link.h"
*
* In the end of function CL_FirstSnapshot() add:
* mumbleLink.Link();
*
* ../client/cl_main.cpp
* After:
* #include "client.h"
* add:
* #include "../mumble_link/mumble_link.h"
* In the end of function CL_Init() add:
* mumbleLink.AddCvars();
* mumbleLink.AddCommands();
* In the end of function CL_Frame() add:
* mumbleLink.Update();
*
* In the end of function CL_Disconnect() add:
* mumbleLink.Unlink();
*
* Finally, add ./mumble_link/mumble_link.cpp (dont forget about mumble_link.h) to cnq3 project.
*/
#pragma once
#ifndef MUMBLE_LINK_H
#define MUMBLE_LINK_H
#include <wchar.h> //wchar_t
#ifdef WIN32
#include <windows.h> //UINT32, DWORD, HANDLE
#else
#include <stdint.h> //uint32_t
typedef uint32_t UINT32, DWORD;
#endif
#include "../qcommon/q_shared.h" //qbool
#include "../qcommon/qcommon.h" //cvar_t
void Mum_AddCommands();
class MumbleLink
{
public:
MumbleLink(): m_linkedMem(0) {}
void AddCvars();
void AddCommands();
qbool Link();
void Unlink();
void Update();
//not need to add this to cnq3 source
qbool IsLinked();
qbool IsEnabled();
private:
struct LinkedMem
{
UINT32 uiVersion;
DWORD uiTick;
float fAvatarPosition[3];
float fAvatarFront[3];
float fAvatarTop[3];
wchar_t name[256];
float fCameraPosition[3];
float fCameraFront[3];
float fCameraTop[3];
wchar_t identity[256];
UINT32 context_len;
unsigned char context[256];
wchar_t description[2048];
}
*m_linkedMem;
#ifdef WIN32
HANDLE m_hMapObject;
#endif
cvar_t *m_cvar_mum_enable;
cvar_t *m_cvar_mum_scale;
cvar_t *m_cvar_mum_debug;
int m_GetTeam();
void m_DisplayDebug();
};
extern MumbleLink mumbleLink;
#endif //MUMBLE_LINK_H
*/
mumble_link.cpp
- Code: Select all
//details at:
#include "mumble_link.h"
#include <stdio.h> //sprintf, snprintf
#include <stdlib.h> //mbstowcs, atoi
#include <string.h> //strlen
#include <wchar.h> //wcsncpy
#ifdef _MSC_VER
#pragma warning(disable : 4996) //unsafe wcsncpy, sprintf
#endif
#ifdef WIN32
#include <windows.h> //OpenFileMappingW, MapViewOfFile, UnmapViewOfFile, CloseHandle, CloseHandle
#else
#include <unistd.h> //getuid
#include <sys/mman.h> //shm_open, mmap, munmap
#endif
#include "../qcommon/q_shared.h" //Com_Printf, AngleVectors, MAX_MODELS, MAX_SOUNDS
#include "../qcommon/qcommon.h" //BigShort, Cvar_Get, Cmd_AddCommand
#include "../client/client.h" //cls, clc, cl
/*
Q3Radiant202 manual:
Player Dimensions
Model size: The player model’s actual size is a bounding box 30 units by 30 units square with a height of 56 units.
In the game world, eight units roughly equal one foot (30.5 cm).
From this, we deduce that the characters are a heroic 7 feet tall (2.13 meters).
*/
//#define UNIT_PER_METER 26.229508196721311475409836065574 //Q3Radiant manual
#define UNIT_PER_METER 26.246719160104986876640419947507 //Q3Radiant manual + wiki 0.3048m foot
MumbleLink mumbleLink;
void MumbleLink::AddCvars()
{
m_cvar_mum_enable = Cvar_Get("mum_enable", "1", CVAR_ARCHIVE);
m_cvar_mum_scale = Cvar_Get("mum_scale", "1.0", CVAR_CHEAT);//FIXME: remove?, affect all clients
m_cvar_mum_debug = Cvar_Get("mum_debug", "0", CVAR_TEMP);
}
static void Mum_State();
static void Mum_Relink();
void MumbleLink::AddCommands()
{
Cmd_AddCommand("mum_relink", Mum_Relink);//cpp conversion at cnq3
Cmd_AddCommand("mum_state", Mum_State);
}
qbool MumbleLink::Link()
{
if( !m_cvar_mum_enable->integer )
return qfalse;
if( m_linkedMem )
this->Unlink();
#ifdef WIN32
m_hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink");
if( !m_hMapObject )
goto label_failed_to_link;
m_linkedMem = (LinkedMem *) MapViewOfFile(m_hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem));
if( !m_linkedMem )
{
CloseHandle(m_hMapObject);
m_hMapObject = NULL;
goto label_failed_to_link;
}
#else
char szMemName[256];
snprintf(szMemName, 256, "/MumbleLink.%d", getuid());
int shmfd = shm_open(szMemName, O_RDWR, S_IRUSR|S_IWUSR);//opens existing descriptor
if( shmfd < 0 )
goto label_failed_to_link;
m_linkedMem = (LinkedMem *) mmap(0, sizeof(LinkedMem), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
if( m_linkedMem == (void *)(-1) )
{
m_linkedMem = 0;
goto label_failed_to_link;
}
#endif
m_linkedMem->uiVersion = 2;
wcsncpy(m_linkedMem->name, L"Challenge Quake3", 256);
wcsncpy(m_linkedMem->description, L"Challenge Quake3 - modified q3/ioq3 engine for CPMA mod.", 2048);
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_GREEN " linked" S_COLOR_WHITE ".\n");
return qtrue;
label_failed_to_link:
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_WHITE "Failed to link(mumble is off). For linking, type" S_COLOR_YELLOW " mum_relink" S_COLOR_WHITE " command.\n");
return qfalse;
}
void MumbleLink::Unlink()
{
if( !m_linkedMem )
return;
#ifdef WIN32
UnmapViewOfFile(m_linkedMem);
CloseHandle(m_hMapObject);
m_hMapObject = NULL;
#else
munmap(m_linkedMem, sizeof(LinkedMem));
#endif
m_linkedMem = 0;
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_RED " unlinked" S_COLOR_WHITE ".\n");
}
void MumbleLink::Update()
{
if( !m_linkedMem )
return;//unlinked
if( !m_cvar_mum_enable->integer )//may be unlinked by cvar
{
this->Unlink();
return;
}
LinkedMem &lm = *m_linkedMem;
lm.uiTick++;
if( cls.state != CA_ACTIVE || clc.serverAddress.type != NA_IP || !cl.snap.valid )//not connected or local or waiting for first snapshot
{
lm.fAvatarPosition[0] =
lm.fAvatarPosition[1] =
lm.fAvatarPosition[2] = 0.0;
return;
}
lm.uiTick++;
// Left handed coordinate system.
// X positive towards "left".
// Y positive towards "up".
// Z positive towards "into screen".
//
// 1 unit = 1 meter
// DaTa: for visual details, look at enclosed ./coords.jpg
lm.fCameraPosition[0] = lm.fAvatarPosition[0] = cl.snap.ps.origin[0]/UNIT_PER_METER * m_cvar_mum_scale->value;
lm.fCameraPosition[1] = lm.fAvatarPosition[1] = ( cl.snap.ps.origin[2] + cl.snap.ps.viewheight )/UNIT_PER_METER * m_cvar_mum_scale->value;
lm.fCameraPosition[2] = lm.fAvatarPosition[2] = cl.snap.ps.origin[1]/UNIT_PER_METER * m_cvar_mum_scale->value;
if( lm.fAvatarPosition[1] == 0.0 ) //TODO: check
lm.fAvatarPosition[1] = 0.001f; //prevent disable the link plugin, z axis less lagy
vec3_t forward, up;
AngleVectors(cl.snap.ps.viewangles, forward, NULL, up);
lm.fCameraFront[0] = lm.fAvatarFront[0] = forward[0];
lm.fCameraFront[1] = lm.fAvatarFront[1] = forward[2];
lm.fCameraFront[2] = lm.fAvatarFront[2] = forward[1];
lm.fCameraTop[0] = lm.fAvatarTop[0] = up[0];
lm.fCameraTop[1] = lm.fAvatarTop[1] = up[2];
lm.fCameraTop[2] = lm.fAvatarTop[2] = up[1];
// Identifier which uniquely identifies a certain player in a context (e.g. the ingame Name).
//wprintf(lm.identity, L"%i", clc.clientNum);//3 chars, OH SH- doesnt work @ VC2008
char szID[10];
sprintf(szID, "%i", clc.clientNum); //DaTa: some sort of crap
mbstowcs(lm.identity, szID, sizeof(lm.identity));
//hate unicode :(
// Context should be equal for players which should be able to hear each other positional and
// differ for those who shouldn't (e.g. it could contain the server+port and team)
const netadr_t &sa = clc.serverAddress;
//HACK: port is in big endian
sprintf((char *)lm.context, "%i.%i.%i.%i:%hu:%i",
sa.ip[0], sa.ip[1], sa.ip[2], sa.ip[3],
BigShort(sa.port),
m_GetTeam()); //~24 chars < 256;
lm.context_len = strlen((char *)lm.context);
//DEBUG
if( m_cvar_mum_debug->integer )
m_DisplayDebug();
}
qbool MumbleLink::IsLinked()
{
return (qbool) m_linkedMem;
}
qbool MumbleLink::IsEnabled()
{
return (qbool) m_cvar_mum_enable->integer;
}
#define CS_MODELS 32
#define CS_SOUNDS (CS_MODELS+MAX_MODELS)
#define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS)
int MumbleLink::m_GetTeam()//HACK HACK HACK
{
const char *pcszClientInfo = cl.gameState.stringData + cl.gameState.stringOffsets[CS_PLAYERS + clc.clientNum];
return atoi(Info_ValueForKey(pcszClientInfo, "t"));//FIXME: find better way?
}
void MumbleLink::m_DisplayDebug()
{
char szID[10];
wcstombs(szID, m_linkedMem->identity, sizeof(szID));
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_WHITE " Pos %.3f %.3f %.3f; Fwd ang %.3f %.3f %.3f;\n",
m_linkedMem->fAvatarPosition[0],
m_linkedMem->fAvatarPosition[1],
m_linkedMem->fAvatarPosition[2],
m_linkedMem->fAvatarFront[0],
m_linkedMem->fAvatarFront[1],
m_linkedMem->fAvatarFront[2]);
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_WHITE " ID %s; Context %s;\n", szID, (char *)m_linkedMem->context);
}
static void Mum_Relink()
{
if( !mumbleLink.IsEnabled() )
{
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_WHITE " is " S_COLOR_RED "disabled" S_COLOR_WHITE ". To enable turn" S_COLOR_YELLOW " mum_enable" S_COLOR_WHITE " cvar on.\n");
}
mumbleLink.Link();
}
static void Mum_State()
{
Com_Printf( S_COLOR_YELLOW "[MUMBLE]" S_COLOR_WHITE " is %s" S_COLOR_WHITE ".\n", mumbleLink.IsLinked() ? S_COLOR_GREEN "linked" : S_COLOR_RED "unlinked" );
}
P.S. This CNQ3 feature is nothing to do with the VoIP in-game functionality designed for ioQ3. It just adds a support for 'Positional Audio' originally designed by Mumble team.
