Compare commits

...

3 Commits

Author SHA1 Message Date
sfence
342ed4d665
Merge 0da16f99373ccbbc27b075fb1fe968102008c45c into 9a1501ae89ffe79c38dbd6756c9e7ed647dd7dc1 2024-06-27 21:54:08 +00:00
grorp
9a1501ae89
CIrrDeviceSDL: Fix numpad key events not having correct KeyInput.Char (#14780)
Allows you to change viewing range using numpad +/- again. This fix also works with the current unreleased version of SDL 3.

The keycodes for numpad keys (both SDL keycodes and Irrlicht keycodes) are not the same as the keycodes for the equivalent non-numpad keys and don't correspond to chars, so I mapped them to chars manually.

Since I think the resolution of https://github.com/minetest/minetest/issues/13770 was "just disable numlock", I made sure to only do this for the numpad number keys if numlock is enabled.
2024-06-27 14:44:44 +02:00
SFENCE
0da16f9937 Send sound to players which comes to hear distance. 2024-06-23 12:11:29 +02:00
10 changed files with 249 additions and 42 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 = 0.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),

@ -129,9 +129,9 @@ EM_BOOL CIrrDeviceSDL::MouseLeaveCallback(int eventType, const EmscriptenMouseEv
}
#endif
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE irrlichtKey)
{
switch (key) {
switch (irrlichtKey) {
// keys which are known to have safe special character interpretation
// could need changes over time (removals and additions!)
case KEY_RETURN:
@ -189,24 +189,68 @@ bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
}
}
int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock)
{
switch (irrlichtKey) {
// special cases that always return a char regardless of how the SDL keycode
// looks
switch (key) {
case KEY_RETURN:
case KEY_ESCAPE:
return (int)key;
return (int)irrlichtKey;
// This is necessary for keys on the numpad because they don't use the same
// keycodes as their non-numpad versions (whose keycodes correspond to chars),
// but have their own SDL keycodes and their own Irrlicht keycodes (which
// don't correspond to chars).
case KEY_MULTIPLY:
return '*';
case KEY_ADD:
return '+';
case KEY_SUBTRACT:
return '-';
case KEY_DIVIDE:
return '/';
default:
break;
}
if (numlock) {
// Number keys on the numpad are also affected, but we only want them
// to produce number chars when numlock is enabled.
switch (irrlichtKey) {
case KEY_NUMPAD0:
return '0';
case KEY_NUMPAD1:
return '1';
case KEY_NUMPAD2:
return '2';
case KEY_NUMPAD3:
return '3';
case KEY_NUMPAD4:
return '4';
case KEY_NUMPAD5:
return '5';
case KEY_NUMPAD6:
return '6';
case KEY_NUMPAD7:
return '7';
case KEY_NUMPAD8:
return '8';
case KEY_NUMPAD9:
return '9';
default:
break;
}
}
// SDL in-place ORs values with no character representation with 1<<30
// https://wiki.libsdl.org/SDL2/SDLKeycodeLookup
if (assumedChar & (1 << 30))
// This also affects the numpad keys btw.
if (sdlKey & (1 << 30))
return 0;
switch (key) {
switch (irrlichtKey) {
case KEY_PRIOR:
case KEY_NEXT:
case KEY_HOME:
@ -218,7 +262,7 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
case KEY_NUMLOCK:
return 0;
default:
return assumedChar;
return sdlKey;
}
}
@ -825,7 +869,8 @@ bool CIrrDeviceSDL::run()
irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN);
irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0;
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key);
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
postEventFromUser(irrevent);
} break;

@ -273,10 +273,10 @@ class CIrrDeviceSDL : public CIrrDeviceStub
#endif
// Check if a key is a known special character with no side effects on text boxes.
static bool keyIsKnownSpecial(EKEY_CODE key);
static bool keyIsKnownSpecial(EKEY_CODE irrlichtKey);
// Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0).
static int findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key);
static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock);
// Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus.
void resetReceiveTextInputEvents();

@ -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 = 0.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.