Improve core.sound_play with ephemeral sounds and player exclusion

This commit is contained in:
sfan5 2020-01-25 21:19:29 +01:00
parent ea5e231959
commit ace3c76112
9 changed files with 78 additions and 29 deletions

@ -826,7 +826,7 @@ Examples of sound parameter tables:
gain = 1.0, -- default gain = 1.0, -- default
loop = true, loop = true,
} }
-- Play in a location -- Play at a location
{ {
pos = {x = 1, y = 2, z = 3}, pos = {x = 1, y = 2, z = 3},
gain = 1.0, -- default gain = 1.0, -- default
@ -839,13 +839,22 @@ Examples of sound parameter tables:
max_hear_distance = 32, -- default, uses an euclidean metric max_hear_distance = 32, -- default, uses an euclidean metric
loop = true, loop = true,
} }
-- Play at a location, heard by anyone *but* the given player
{
pos = {x = 32, y = 0, z = 100},
max_hear_distance = 40,
exclude_player = name,
}
Looped sounds must either be connected to an object or played locationless to Looped sounds must either be connected to an object or played locationless to
one player using `to_player = name,`. one player using `to_player = name`.
A positional sound will only be heard by players that are within A positional sound will only be heard by players that are within
`max_hear_distance` of the sound position, at the start of the sound. `max_hear_distance` of the sound position, at the start of the sound.
`exclude_player = name` can be applied to locationless, positional and object-
bound sounds to exclude a single player from hearing them.
`SimpleSoundSpec` `SimpleSoundSpec`
----------------- -----------------
@ -4929,10 +4938,15 @@ Defaults for the `on_punch` and `on_dig` node definition callbacks
Sounds Sounds
------ ------
* `minetest.sound_play(spec, parameters)`: returns a handle * `minetest.sound_play(spec, parameters, [ephemeral])`: returns a handle
* `spec` is a `SimpleSoundSpec` * `spec` is a `SimpleSoundSpec`
* `parameters` is a sound parameter table * `parameters` is a sound parameter table
* `ephemeral` is a boolean (default: false)
Ephemeral sounds will not return a handle and can't be stopped or faded.
It is recommend to use this for short sounds that happen in response to
player actions (e.g. door closing).
* `minetest.sound_stop(handle)` * `minetest.sound_stop(handle)`
* `handle` is a handle returned by `minetest.sound_play`
* `minetest.sound_fade(handle, step, gain)` * `minetest.sound_fade(handle, step, gain)`
* `handle` is a handle returned by `minetest.sound_play` * `handle` is a handle returned by `minetest.sound_play`
* `step` determines how fast a sound will fade. * `step` determines how fast a sound will fade.

@ -1106,7 +1106,7 @@ void Client::sendRemovedSounds(std::vector<s32> &soundList)
pkt << (u16) (server_ids & 0xFFFF); pkt << (u16) (server_ids & 0xFFFF);
for (int sound_id : soundList) for (s32 sound_id : soundList)
pkt << sound_id; pkt << sound_id;
Send(&pkt); Send(&pkt);

@ -561,7 +561,7 @@ private:
std::unordered_map<s32, int> m_sounds_server_to_client; std::unordered_map<s32, int> m_sounds_server_to_client;
// And the other way! // And the other way!
std::unordered_map<int, s32> m_sounds_client_to_server; std::unordered_map<int, s32> m_sounds_client_to_server;
// And relations to objects // Relation of client id to object id
std::unordered_map<int, u16> m_sounds_to_objects; std::unordered_map<int, u16> m_sounds_to_objects;
// Map server hud ids to client hud ids // Map server hud ids to client hud ids

@ -778,6 +778,7 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt)
[25 + len] bool loop [25 + len] bool loop
[26 + len] f32 fade [26 + len] f32 fade
[30 + len] f32 pitch [30 + len] f32 pitch
[34 + len] bool ephemeral
*/ */
s32 server_id; s32 server_id;
@ -790,12 +791,14 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt)
bool loop; bool loop;
float fade = 0.0f; float fade = 0.0f;
float pitch = 1.0f; float pitch = 1.0f;
bool ephemeral = false;
*pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop; *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop;
try { try {
*pkt >> fade; *pkt >> fade;
*pkt >> pitch; *pkt >> pitch;
*pkt >> ephemeral;
} catch (PacketError &e) {}; } catch (PacketError &e) {};
// Start playing // Start playing
@ -813,7 +816,6 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt)
if (cao) if (cao)
pos = cao->getPosition(); pos = cao->getPosition();
client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch); client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch);
// TODO: Set up sound to move with object
break; break;
} }
default: default:
@ -821,8 +823,11 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt)
} }
if (client_id != -1) { if (client_id != -1) {
m_sounds_server_to_client[server_id] = client_id; // for ephemeral sounds, server_id is not meaningful
m_sounds_client_to_server[client_id] = server_id; if (!ephemeral) {
m_sounds_server_to_client[server_id] = client_id;
m_sounds_client_to_server[client_id] = server_id;
}
if (object_id != 0) if (object_id != 0)
m_sounds_to_objects[client_id] = object_id; m_sounds_to_objects[client_id] = object_id;
} }

@ -200,6 +200,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Unknown inventory serialization fields no longer throw an error Unknown inventory serialization fields no longer throw an error
Mod-specific formspec version Mod-specific formspec version
Player FOV override API Player FOV override API
"ephemeral" added to TOCLIENT_PLAY_SOUND
*/ */
#define LATEST_PROTOCOL_VERSION 38 #define LATEST_PROTOCOL_VERSION 38
@ -450,6 +451,7 @@ enum ToClientCommand
s32[3] pos_nodes*10000 s32[3] pos_nodes*10000
u16 object_id u16 object_id
u8 loop (bool) u8 loop (bool)
u8 ephemeral (bool)
*/ */
TOCLIENT_STOP_SOUND = 0x40, TOCLIENT_STOP_SOUND = 0x40,

@ -1019,6 +1019,7 @@ void read_server_sound_params(lua_State *L, int index,
params.max_hear_distance = BS*getfloatfield_default(L, index, params.max_hear_distance = BS*getfloatfield_default(L, index,
"max_hear_distance", params.max_hear_distance/BS); "max_hear_distance", params.max_hear_distance/BS);
getboolfield(L, index, "loop", params.loop); getboolfield(L, index, "loop", params.loop);
getstringfield(L, index, "exclude_player", params.exclude_player);
} }
} }

@ -429,7 +429,7 @@ int ModApiServer::l_get_worldpath(lua_State *L)
return 1; return 1;
} }
// sound_play(spec, parameters) // sound_play(spec, parameters, [ephemeral])
int ModApiServer::l_sound_play(lua_State *L) int ModApiServer::l_sound_play(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
@ -437,8 +437,14 @@ int ModApiServer::l_sound_play(lua_State *L)
read_soundspec(L, 1, spec); read_soundspec(L, 1, spec);
ServerSoundParams params; ServerSoundParams params;
read_server_sound_params(L, 2, params); read_server_sound_params(L, 2, params);
s32 handle = getServer(L)->playSound(spec, params); bool ephemeral = lua_gettop(L) > 2 && readParam<bool>(L, 3);
lua_pushinteger(L, handle); if (ephemeral) {
getServer(L)->playSound(spec, params, true);
lua_pushnil(L);
} else {
s32 handle = getServer(L)->playSound(spec, params);
lua_pushinteger(L, handle);
}
return 1; return 1;
} }
@ -446,7 +452,7 @@ int ModApiServer::l_sound_play(lua_State *L)
int ModApiServer::l_sound_stop(lua_State *L) int ModApiServer::l_sound_stop(lua_State *L)
{ {
NO_MAP_LOCK_REQUIRED; NO_MAP_LOCK_REQUIRED;
int handle = luaL_checkinteger(L, 1); s32 handle = luaL_checkinteger(L, 1);
getServer(L)->stopSound(handle); getServer(L)->stopSound(handle);
return 0; return 0;
} }

@ -2013,8 +2013,18 @@ void Server::SendPlayerSpeed(session_t peer_id, const v3f &added_vel)
Send(&pkt); Send(&pkt);
} }
inline s32 Server::nextSoundId()
{
s32 ret = m_next_sound_id;
if (m_next_sound_id == INT32_MAX)
m_next_sound_id = 0; // signed overflow is undefined
else
m_next_sound_id++;
return ret;
}
s32 Server::playSound(const SimpleSoundSpec &spec, s32 Server::playSound(const SimpleSoundSpec &spec,
const ServerSoundParams &params) const ServerSoundParams &params, bool ephemeral)
{ {
// Find out initial position of sound // Find out initial position of sound
bool pos_exists = false; bool pos_exists = false;
@ -2025,7 +2035,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec,
// Filter destination clients // Filter destination clients
std::vector<session_t> dst_clients; std::vector<session_t> dst_clients;
if(!params.to_player.empty()) { if (!params.to_player.empty()) {
RemotePlayer *player = m_env->getPlayer(params.to_player.c_str()); RemotePlayer *player = m_env->getPlayer(params.to_player.c_str());
if(!player){ if(!player){
infostream<<"Server::playSound: Player \""<<params.to_player infostream<<"Server::playSound: Player \""<<params.to_player
@ -2045,6 +2055,9 @@ s32 Server::playSound(const SimpleSoundSpec &spec,
RemotePlayer *player = m_env->getPlayer(client_id); RemotePlayer *player = m_env->getPlayer(client_id);
if (!player) if (!player)
continue; continue;
if (!params.exclude_player.empty() &&
params.exclude_player == player->getName())
continue;
PlayerSAO *sao = player->getPlayerSAO(); PlayerSAO *sao = player->getPlayerSAO();
if (!sao) if (!sao)
@ -2063,27 +2076,32 @@ s32 Server::playSound(const SimpleSoundSpec &spec,
return -1; return -1;
// Create the sound // Create the sound
s32 id = m_next_sound_id++; s32 id;
// The sound will exist as a reference in m_playing_sounds ServerPlayingSound *psound = nullptr;
m_playing_sounds[id] = ServerPlayingSound(); if (ephemeral) {
ServerPlayingSound &psound = m_playing_sounds[id]; id = -1; // old clients will still use this, so pick a reserved ID
psound.params = params; } else {
psound.spec = spec; id = nextSoundId();
// The sound will exist as a reference in m_playing_sounds
m_playing_sounds[id] = ServerPlayingSound();
psound = &m_playing_sounds[id];
psound->params = params;
psound->spec = spec;
}
float gain = params.gain * spec.gain; float gain = params.gain * spec.gain;
NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0);
pkt << id << spec.name << gain pkt << id << spec.name << gain
<< (u8) params.type << pos << params.object << (u8) params.type << pos << params.object
<< params.loop << params.fade << params.pitch; << params.loop << params.fade << params.pitch
<< ephemeral;
// Backwards compability bool as_reliable = !ephemeral;
bool play_sound = gain > 0;
for (const u16 dst_client : dst_clients) { for (const u16 dst_client : dst_clients) {
if (play_sound || m_clients.getProtocolVersion(dst_client) >= 32) { if (psound)
psound.clients.insert(dst_client); psound->clients.insert(dst_client);
m_clients.send(dst_client, 0, &pkt, true); m_clients.send(dst_client, 0, &pkt, as_reliable);
}
} }
return id; return id;
} }

@ -98,6 +98,7 @@ struct ServerSoundParams
v3f pos; v3f pos;
u16 object = 0; u16 object = 0;
std::string to_player = ""; std::string to_player = "";
std::string exclude_player = "";
v3f getPos(ServerEnvironment *env, bool *pos_exists) const; v3f getPos(ServerEnvironment *env, bool *pos_exists) const;
}; };
@ -209,7 +210,8 @@ public:
// Returns -1 if failed, sound handle on success // Returns -1 if failed, sound handle on success
// Envlock // Envlock
s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams &params); s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams &params,
bool ephemeral=false);
void stopSound(s32 handle); void stopSound(s32 handle);
void fadeSound(s32 handle, float step, float gain); void fadeSound(s32 handle, float step, float gain);
@ -646,7 +648,8 @@ private:
Sounds Sounds
*/ */
std::unordered_map<s32, ServerPlayingSound> m_playing_sounds; std::unordered_map<s32, ServerPlayingSound> m_playing_sounds;
s32 m_next_sound_id = 0; s32 m_next_sound_id = 0; // positive values only
s32 nextSoundId();
/* /*
Detached inventories (behind m_env_mutex) Detached inventories (behind m_env_mutex)