Compare commits

...

3 Commits

8 changed files with 193 additions and 31 deletions

@ -42,6 +42,7 @@ core.features = {
node_interaction_actor = true,
moveresult_new_pos = true,
override_item_remove_fields = true,
sounds_updating = true,
}
function core.has_feature(arg)

@ -1101,6 +1101,11 @@ Table used to specify how a sound is played:
-- Available since feature `sound_params_start_time`.
resend_time = 600.0,
-- Approximate playback duration (from `start_time` to end) in seconds.
-- This is needed to re-send sounds to new players in hearing distance.
-- Unused for looped sounds.
loop = false,
-- If true, sound is played in a loop.
@ -1136,10 +1141,10 @@ Table used to specify how a sound is played:
-- Can't be used together with `to_player`.
max_hear_distance = 32,
-- Only play for players that are at most this far away when the sound
-- starts playing.
-- Only play for players that are at most this far away.
-- Needs `pos` or `object` to be set.
-- `32` is the default.
-- See `sounds_updating`.
}
```
@ -5452,6 +5457,9 @@ Utilities
moveresult_new_pos = true,
-- Allow removing definition fields in `minetest.override_item`
override_item_remove_fields = true,
-- Sounds can be send to players which comes to hear distance.
-- Field `resend_time` added to sound parameters. (5.9.0)
sounds_updating = true,
}
```
@ -6652,6 +6660,8 @@ Sounds
------
* `minetest.sound_play(spec, parameters, [ephemeral])`: returns a handle
* Returned handle is positive number if function sucessufully
create sound and `ephernal` if `false`.
* `spec` is a `SimpleSoundSpec`
* `parameters` is a sound parameter table
* `ephemeral` is a boolean (default: false)

@ -15,6 +15,7 @@ local meta_keys = {
"sparam.pitch",
"sparam.fade",
"sparam.start_time",
"sparam.resend_time",
"sparam.loop",
"sparam.pos",
"sparam.object",
@ -40,7 +41,8 @@ local function get_all_metadata(meta)
gain = meta:get_string("sparam.gain"),
pitch = meta:get_string("sparam.pitch"),
fade = meta:get_string("sparam.fade"),
start_time = meta:get_string("sparam.start_time"),
start_time = meta:get_string("sparam.start_time"),
resend_time = meta:get_string("sparam.resend_time"),
loop = meta:get_string("sparam.loop"),
pos = meta:get_string("sparam.pos"),
object = meta:get_string("sparam.object"),
@ -88,7 +90,7 @@ local function show_formspec(pos, player)
fs_add([[
formspec_version[6]
size[14,12]
size[14,13]
]])
-- SimpleSoundSpec
@ -120,17 +122,19 @@ local function show_formspec(pos, player)
field[1.25,1;1,0.75;sparam.pitch;pitch;%s]
field[2.50,1;1,0.75;sparam.fade;fade;%s]
field[0,2.25;4,0.75;sparam.start_time;start_time;%s]
field[0,3.50;4,0.75;sparam.loop;loop;%s]
field[0,4.75;4,0.75;sparam.pos;pos;%s]
field[0,6.00;4,0.75;sparam.object;object;%s]
field[0,7.25;4,0.75;sparam.to_player;to_player;%s]
field[0,8.50;4,0.75;sparam.exclude_player;exclude_player;%s]
field[0,9.75;4,0.75;sparam.max_hear_distance;max_hear_distance;%s]
field[0,3.50;4,0.75;sparam.resend_time;resend_time;%s]
field[0,4.75;4,0.75;sparam.loop;loop;%s]
field[0,6.00;4,0.75;sparam.pos;pos;%s]
field[0,7.25;4,0.75;sparam.object;object;%s]
field[0,8.50;4,0.75;sparam.to_player;to_player;%s]
field[0,9.75;4,0.75;sparam.exclude_player;exclude_player;%s]
field[0,11.00;4,0.75;sparam.max_hear_distance;max_hear_distance;%s]
container_end[]
field_close_on_enter[sparam.gain;false]
field_close_on_enter[sparam.pitch;false]
field_close_on_enter[sparam.fade;false]
field_close_on_enter[sparam.start_time;false]
field_close_on_enter[sparam.resend_time;false]
field_close_on_enter[sparam.loop;false]
field_close_on_enter[sparam.pos;false]
field_close_on_enter[sparam.object;false]
@ -139,7 +143,8 @@ local function show_formspec(pos, player)
field_close_on_enter[sparam.max_hear_distance;false]
tooltip[sparam.object;Get a name with the Branding Iron.]
]], F(md.sparam.gain), F(md.sparam.pitch), F(md.sparam.fade),
F(md.sparam.start_time), F(md.sparam.loop), F(md.sparam.pos),
F(md.sparam.start_time), F(md.sparam.resend_time),
F(md.sparam.loop), F(md.sparam.pos),
F(md.sparam.object), F(md.sparam.to_player), F(md.sparam.exclude_player),
F(md.sparam.max_hear_distance)))
@ -192,7 +197,7 @@ local function show_formspec(pos, player)
-- save and quit button
fs_add([[
button_exit[10.75,11;3,0.75;btn_save_quit;Save & Quit]
button_exit[10.75,11.5;3,0.75;btn_save_quit;Save & Quit]
]])
minetest.show_formspec(player:get_player_name(), "soundstuff:jukebox@"..pos:to_string(),
@ -216,6 +221,7 @@ minetest.register_node("soundstuff:jukebox", {
meta:set_string("sparam.pitch", "")
meta:set_string("sparam.fade", "")
meta:set_string("sparam.start_time", "")
meta:set_string("sparam.resend_time", "")
meta:set_string("sparam.loop", "")
meta:set_string("sparam.pos", pos:to_string())
meta:set_string("sparam.object", "")
@ -274,6 +280,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
pitch = tonumber(md.sparam.pitch),
fade = tonumber(md.sparam.fade),
start_time = tonumber(md.sparam.start_time),
resend_time = tonumber(md.sparam.resend_time),
loop = minetest.is_yes(md.sparam.loop),
pos = vector.from_string(md.sparam.pos),
object = testtools.get_branded_object(md.sparam.object),
@ -287,9 +294,10 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
"[soundstuff:jukebox] Playing sound: minetest.sound_play(%s, %s, %s)",
string.format("{name=\"%s\", gain=%s, pitch=%s, fade=%s}",
sss.name, sss.gain, sss.pitch, sss.fade),
string.format("{gain=%s, pitch=%s, fade=%s, start_time=%s, loop=%s, pos=%s, "
string.format("{gain=%s, pitch=%s, fade=%s, start_time=%s, resend_time=%s, loop=%s, pos=%s, "
.."object=%s, to_player=\"%s\", exclude_player=\"%s\", max_hear_distance=%s}",
sparam.gain, sparam.pitch, sparam.fade, sparam.start_time,
sparam.gain, sparam.pitch, sparam.fade,
sparam.start_time, sparam.resend_time,
sparam.loop, sparam.pos, sparam.object and "<objref>",
sparam.to_player, sparam.exclude_player,
sparam.max_hear_distance),

@ -1339,10 +1339,39 @@ void Server::handleCommand_RemovedSounds(NetworkPacket* pkt)
if (i == m_playing_sounds.end())
continue;
session_t peer_id = pkt->getPeerId();
ServerPlayingSound &psound = i->second;
psound.clients.erase(pkt->getPeerId());
if (psound.clients.empty())
psound.clients.erase(peer_id);
verbosestream << "Server: Sound " << id << " erase peer "
<< peer_id << "." << std::endl;
if (!psound.allow_resend && psound.clients.empty()) {
m_playing_sounds.erase(i);
verbosestream << "Server: Erase sound " << id << "." << std::endl;
}
else if (psound.allow_resend && !psound.spec.loop) {
if (psound.resend_time < m_playing_sounds_time) {
/* Sound resend time reached, done_clients do not need to be updated. */
verbosestream << "Server: Done sound " << id << " for peer "
<< peer_id << "." << std::endl;
continue;
}
PlayerSAO *plrsao = getPlayerSAO(peer_id);
if (!plrsao)
continue;
bool pos_exists;
v3f pos = psound.getPos(m_env, &pos_exists);
assert(pos_exists);
if (pos.getDistanceFrom(plrsao->getBasePosition()) <= psound.max_hear_distance) {
/* If client remove sound in hear distance,
we are sure, that sound has been fully played. */
psound.done_clients.insert(peer_id);
verbosestream << "Server: Done sound " << id << " for peer "
<< peer_id << "." << std::endl;
}
}
}
}

@ -1179,6 +1179,7 @@ void read_server_sound_params(lua_State *L, int index,
getfloatfield(L, index, "fade", params.spec.fade);
getfloatfield(L, index, "pitch", params.spec.pitch);
getfloatfield(L, index, "start_time", params.spec.start_time);
getfloatfield(L, index, "resend_time", params.spec.resend_time);
getboolfield(L, index, "loop", params.spec.loop);
getfloatfield(L, index, "gain", params.gain);

@ -915,6 +915,65 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
}
}
/*
Send sounds if needed
*/
{
m_playing_sounds_time += dtime;
MutexAutoLock lock(m_env_mutex);
ClientInterface::AutoLock clientlock(m_clients);
const RemoteClientMap &clients = m_clients.getClientList();
for (auto it = m_playing_sounds.begin(); it != m_playing_sounds.end(); it++) {
ServerPlayingSound &sound = it->second;
if (!sound.allow_resend)
continue;
if (!sound.spec.loop && (sound.resend_time < m_playing_sounds_time)) {
sound.allow_resend = false;
verbosestream << "Server: Marking sound " << it->first
<< " as expired." << std::endl;
continue;
}
// this should be never called for sound not attached to pos or object
bool pos_exists;
v3f pos = sound.getPos(m_env, &pos_exists);
assert(pos_exists);
for (const auto &client_it : clients) {
RemoteClient *client = client_it.second;
if (client->getState() < CS_DefinitionsSent)
continue;
PlayerSAO *playersao = getPlayerSAO(client->peer_id);
if (!playersao)
continue;
if (sound.clients.find(client->peer_id) != sound.clients.end())
continue;
if (sound.done_clients.find(client->peer_id) != sound.clients.end())
continue;
if (!sound.exclude_player.empty() &&
sound.exclude_player == client->getName())
continue;
if (pos.getDistanceFrom(playersao->getBasePosition()) <= sound.max_hear_distance) {
SendSound(client->peer_id, it->first, sound, pos);
sound.clients.insert(client->peer_id);
verbosestream << "Server: Sound " << it->first << " add peer "
<< client->peer_id << "." << std::endl;
}
}
}
}
/*
Send queued-for-sending map edit events.
*/
@ -2118,6 +2177,15 @@ void Server::SendActiveObjectMessages(session_t peer_id, const std::string &data
m_clients.sendCustom(pkt.getPeerId(), reliable ? ccf.channel : 1, &pkt, reliable);
}
void Server::SendSound(session_t peer_id, s32 sound_id,
const ServerPlayingSound &params, const v3f &pos)
{
NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0, peer_id);
createSoundPacket(pkt, sound_id, params, pos);
Send(&pkt);
}
void Server::SendCSMRestrictionFlags(session_t peer_id)
{
NetworkPacket pkt(TOCLIENT_CSM_RESTRICTION_FLAGS,
@ -2159,6 +2227,8 @@ s32 Server::playSound(ServerPlayingSound &params, bool ephemeral)
if(pos_exists != (params.type != SoundLocation::Local))
return -1;
params.allow_resend = pos_exists;
// Filter destination clients
std::vector<session_t> dst_clients;
if (!params.to_player.empty()) {
@ -2169,6 +2239,7 @@ s32 Server::playSound(ServerPlayingSound &params, bool ephemeral)
return -1;
}
dst_clients.push_back(player->getPeerId());
params.allow_resend = false;
} else {
std::vector<session_t> clients = m_clients.getClientIDs();
@ -2193,7 +2264,10 @@ s32 Server::playSound(ServerPlayingSound &params, bool ephemeral)
}
}
if(dst_clients.empty())
// lets finish only for ephmeral sounds, non-looped sounds and
// sounds without resend_time if no clients in hear distance.
if (dst_clients.empty() &&
(ephemeral || (params.spec.resend_time == 0 && !params.spec.loop)))
return -1;
// old clients will still use this, so pick a reserved ID (-1)
@ -2201,23 +2275,27 @@ s32 Server::playSound(ServerPlayingSound &params, bool ephemeral)
if (id == 0)
return 0;
float gain = params.gain * params.spec.gain;
NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0);
pkt << id << params.spec.name << gain
<< (u8) params.type << pos << params.object
<< params.spec.loop << params.spec.fade << params.spec.pitch
<< ephemeral << params.spec.start_time;
params.start_time = m_playing_sounds_time;
params.resend_time = m_playing_sounds_time + params.spec.resend_time;
const bool as_reliable = !ephemeral;
if (!dst_clients.empty()) {
NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0);
createSoundPacket(pkt, id, params, pos, ephemeral);
for (const session_t peer_id : dst_clients) {
if (!ephemeral)
params.clients.insert(peer_id);
m_clients.sendCustom(peer_id, 0, &pkt, as_reliable);
const bool as_reliable = !ephemeral;
for (const session_t peer_id : dst_clients) {
if (!ephemeral)
params.clients.insert(peer_id);
m_clients.sendCustom(peer_id, 0, &pkt, as_reliable);
}
}
if (!ephemeral)
if (!ephemeral) {
m_playing_sounds[id] = std::move(params);
verbosestream << "Server:playSound: Create sound "
<< id << "." << std::endl;
}
return id;
}
void Server::stopSound(s32 handle)
@ -2237,6 +2315,9 @@ void Server::stopSound(s32 handle)
// Remove sound reference
m_playing_sounds.erase(it);
verbosestream << "Server:stopSound: Stop sound "
<< handle << "." << std::endl;
}
void Server::fadeSound(s32 handle, float step, float gain)
@ -2280,17 +2361,33 @@ void Server::stopAttachedSounds(session_t peer_id,
sound.clients.erase(clients_it);
// delete if client list empty
return sound.clients.empty();
return !sound.allow_resend && sound.clients.empty();
};
for (auto it = m_playing_sounds.begin(); it != m_playing_sounds.end(); ) {
if (cb(it->first, it->second))
if (cb(it->first, it->second)) {
// Remove sound reference
verbosestream << "Server:stopAttachedSounds: Stop sound "
<< it->first << "." << std::endl;
it = m_playing_sounds.erase(it);
}
else
++it;
}
}
void Server::createSoundPacket(NetworkPacket &pkt, s32 sound_id,
const ServerPlayingSound &params, const v3f &pos, bool ephemeral)
{
float gain = params.gain * params.spec.gain;
float start_time = params.spec.start_time +
m_playing_sounds_time - params.start_time;
pkt << sound_id << params.spec.name << gain
<< (u8) params.type << pos << params.object
<< params.spec.loop << params.spec.fade << params.spec.pitch
<< ephemeral << start_time;
}
void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players,
float far_d_nodes)
{

@ -119,6 +119,16 @@ struct ServerPlayingSound
SoundSpec spec;
// server processing values
// relative server time when sound has been added
// used for update spec.start_time when sending sound to new client
float start_time;
// hold time limit for sound sending to new clients
float resend_time;
bool allow_resend = false;
std::unordered_set<session_t> done_clients; // peer ids
std::unordered_set<session_t> clients; // peer ids
};
@ -242,6 +252,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
// Stop all sounds attached to given objects, for a certain client
void stopAttachedSounds(session_t peer_id,
const std::vector<u16> &object_ids);
void createSoundPacket(NetworkPacket &pkt, s32 sound_id, const ServerPlayingSound &params, const v3f &pos, bool ephemeral = false);
// Envlock
std::set<std::string> getPlayerEffectivePrivs(const std::string &name);
@ -568,6 +579,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
void SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao);
void SendActiveObjectMessages(session_t peer_id, const std::string &datas,
bool reliable = true);
void SendSound(session_t peer_id, s32 sound_id, const ServerPlayingSound &params, const v3f &pos);
void SendCSMRestrictionFlags(session_t peer_id);
/*
@ -733,6 +745,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
std::unordered_map<s32, ServerPlayingSound> m_playing_sounds;
s32 m_playing_sounds_id_last_used = 0; // positive values only
s32 nextSoundId();
float m_playing_sounds_time = 0.0f;
ModStorageDatabase *m_mod_storage_database = nullptr;
float m_mod_storage_save_timer = 10.0f;

@ -71,6 +71,9 @@ struct SoundSpec
float fade = 0.0f;
float pitch = 1.0f;
float start_time = 0.0f;
// keep time defines a time windows, where sound cannot be removed
// and can be resend, takes no effect for looped sounds
float resend_time = 600.0f;
bool loop = false;
// If true, a local fallback (ie. from the user's sound pack) is used if the
// sound-group does not exist.