From 601d1936c9ab4787d43f55d67900ed7c46fd3452 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 24 Mar 2012 19:01:26 +0200 Subject: [PATCH] Lua API for playing sounds --- doc/lua_api.txt | 46 +++++++- games/mesetint/mods/experimental/init.lua | 36 ++++++ src/client.cpp | 111 +++++++++++++++++- src/client.h | 9 ++ src/clientserver.h | 41 +++++-- src/content_cao.cpp | 20 +++- src/scriptapi.cpp | 71 +++++++++++- src/server.cpp | 134 ++++++++++++++++++++++ src/server.h | 64 +++++++++++ src/sound.h | 4 + src/sound_openal.cpp | 18 +++ 11 files changed, 533 insertions(+), 21 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 53857ee3c..43f49dde6 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -363,8 +363,40 @@ dump2(obj, name="_", dumped={}) dump(obj, dumped={}) ^ Return object serialized as a string +Sounds +------- +Examples of sound parameter tables: +-- Play locationless on all clients +{ + gain = 1.0, -- default +} +-- Play locationless to a player +{ + to_player = name, + gain = 1.0, -- default +} +-- Play in a location +{ + pos = {x=1,y=2,z=3}, + gain = 1.0, -- default + max_hear_distance = 32, -- default +} +-- Play connected to an object, looped +{ + object = , + gain = 1.0, -- default + max_hear_distance = 32, -- default + loop = true, -- only sounds connected to objects can be looped +} + minetest namespace reference ----------------------------- +minetest.get_current_modname() -> string +minetest.get_modpath(modname) -> eg. "/home/user/.minetest/usermods/modname" +^ Useful for loading additional .lua modules or static data from mod +minetest.get_worldpath(modname) -> eg. "/home/user/.minetest/world" +^ Useful for storing custom data + minetest.register_entity(name, prototype table) minetest.register_abm(abm definition) minetest.register_node(name, node definition) @@ -372,6 +404,7 @@ minetest.register_tool(name, item definition) minetest.register_craftitem(name, item definition) minetest.register_alias(name, convert_to) minetest.register_craft(recipe) + minetest.register_globalstep(func(dtime)) minetest.register_on_placenode(func(pos, newnode, placer)) minetest.register_on_dignode(func(pos, oldnode, digger)) @@ -383,20 +416,22 @@ minetest.register_on_respawnplayer(func(ObjectRef)) ^ return true in func to disable regular player placement ^ currently called _before_ repositioning of player occurs minetest.register_on_chat_message(func(name, message)) + minetest.add_to_creative_inventory(itemstring) minetest.setting_get(name) -> string or nil minetest.setting_getbool(name) -> boolean value or nil + minetest.chat_send_all(text) minetest.chat_send_player(name, text) minetest.get_player_privs(name) -> set of privs minetest.get_inventory(location) -> InvRef ^ location = eg. {type="player", name="celeron55"} {type="node", pos={x=, y=, z=}} -minetest.get_current_modname() -> string -minetest.get_modpath(modname) -> eg. "/home/user/.minetest/usermods/modname" -^ Useful for loading additional .lua modules or static data from mod -minetest.get_worldpath(modname) -> eg. "/home/user/.minetest/world" -^ Useful for storing custom data + +minetest.sound_play(spec, parameters) -> handle +^ spec = SimpleSoundSpec +^ parameters = sound parameter table +minetest.sound_stop(handle) minetest.debug(line) ^ Goes to dstream @@ -681,6 +716,7 @@ Node definition (register_node) legacy_wallmounted = false, -- Support maps made in and before January 2012 sounds = { footstep = , + dig = , -- "__group" = group-based sound (default) dug = , }, } diff --git a/games/mesetint/mods/experimental/init.lua b/games/mesetint/mods/experimental/init.lua index 364eeb10c..e11086274 100644 --- a/games/mesetint/mods/experimental/init.lua +++ b/games/mesetint/mods/experimental/init.lua @@ -6,6 +6,42 @@ experimental = {} +timers_to_add = {} +timers = {} +minetest.register_globalstep(function(dtime) + for indes, timer in ipairs(timers_to_add) do + table.insert(timers, timer) + end + timers_to_add = {} + for index, timer in ipairs(timers) do + timer.time = timer.time - dtime + if timer.time <= 0 then + timer.func() + timers[index] = nil + end + end +end) + +after = function(time, func) + table.insert(timers_to_add, {time=time, func=func}) +end + +--[[ +stepsound = -1 +function test_sound() + print("test_sound") + stepsound = minetest.sound_play("default_grass_footstep", {gain=1.0}) + after(2.0, test_sound) + --after(0.1, test_sound_stop) +end +function test_sound_stop() + print("test_sound_stop") + minetest.sound_stop(stepsound) + after(2.0, test_sound) +end +test_sound() +--]] + function on_step(dtime) -- print("experimental on_step") --[[ diff --git a/src/client.cpp b/src/client.cpp index d8fb4eb77..89070d66b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -261,7 +261,8 @@ Client::Client( m_nodedef_received(false), m_time_of_day_set(false), m_last_time_of_day_f(-1), - m_time_of_day_update_timer(0) + m_time_of_day_update_timer(0), + m_removed_sounds_check_timer(0) { m_packetcounter_timer = 0.0; //m_delete_unused_sectors_timer = 0.0; @@ -733,6 +734,63 @@ void Client::step(float dtime) m_inventory_updated = true; } } + + /* + Update positions of sounds attached to objects + */ + { + for(std::map::iterator + i = m_sounds_to_objects.begin(); + i != m_sounds_to_objects.end(); i++) + { + int client_id = i->first; + u16 object_id = i->second; + ClientActiveObject *cao = m_env.getActiveObject(object_id); + if(!cao) + continue; + v3f pos = cao->getPosition(); + m_sound->updateSoundPosition(client_id, pos); + } + } + + /* + Handle removed remotely initiated sounds + */ + m_removed_sounds_check_timer += dtime; + if(m_removed_sounds_check_timer >= 2.32) + { + m_removed_sounds_check_timer = 0; + // Find removed sounds and clear references to them + std::set removed_server_ids; + for(std::map::iterator + i = m_sounds_server_to_client.begin(); + i != m_sounds_server_to_client.end();) + { + s32 server_id = i->first; + int client_id = i->second; + i++; + if(!m_sound->soundExists(client_id)){ + m_sounds_server_to_client.erase(server_id); + m_sounds_client_to_server.erase(client_id); + m_sounds_to_objects.erase(client_id); + removed_server_ids.insert(server_id); + } + } + // Sync to server + if(removed_server_ids.size() != 0) + { + std::ostringstream os(std::ios_base::binary); + writeU16(os, TOSERVER_REMOVED_SOUNDS); + writeU16(os, removed_server_ids.size()); + for(std::set::iterator i = removed_server_ids.begin(); + i != removed_server_ids.end(); i++) + writeS32(os, *i); + std::string s = os.str(); + SharedBuffer data((u8*)s.c_str(), s.size()); + // Send as reliable + Send(0, data, true); + } + } } // Virtual methods from con::PeerHandler @@ -1610,6 +1668,57 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) m_itemdef->deSerialize(tmp_is2); m_itemdef_received = true; } + else if(command == TOCLIENT_PLAY_SOUND) + { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + + s32 server_id = readS32(is); + std::string name = deSerializeString(is); + float gain = readF1000(is); + int type = readU8(is); // 0=local, 1=positional, 2=object + v3f pos = readV3F1000(is); + u16 object_id = readU16(is); + bool loop = readU8(is); + // Start playing + int client_id = -1; + switch(type){ + case 0: // local + client_id = m_sound->playSound(name, false, gain); + break; + case 1: // positional + client_id = m_sound->playSoundAt(name, false, gain, pos); + break; + case 2: { // object + ClientActiveObject *cao = m_env.getActiveObject(object_id); + if(cao) + pos = cao->getPosition(); + client_id = m_sound->playSoundAt(name, loop, gain, pos); + // TODO: Set up sound to move with object + break; } + default: + break; + } + if(client_id != -1){ + m_sounds_server_to_client[server_id] = client_id; + m_sounds_client_to_server[client_id] = server_id; + if(object_id != 0) + m_sounds_to_objects[client_id] = object_id; + } + } + else if(command == TOCLIENT_STOP_SOUND) + { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + + s32 server_id = readS32(is); + std::map::iterator i = + m_sounds_server_to_client.find(server_id); + if(i != m_sounds_server_to_client.end()){ + int client_id = i->second; + m_sound->stopSound(client_id); + } + } else { infostream<<"Client: Ignoring unknown command " diff --git a/src/client.h b/src/client.h index 13b36106c..3a47a08f6 100644 --- a/src/client.h +++ b/src/client.h @@ -376,6 +376,15 @@ private: bool m_time_of_day_set; float m_last_time_of_day_f; float m_time_of_day_update_timer; + + // Sounds + float m_removed_sounds_check_timer; + // Mapping from server sound ids to our sound ids + std::map m_sounds_server_to_client; + // And the other way! + std::map m_sounds_client_to_server; + // And relations to objects + std::map m_sounds_to_objects; }; #endif // !SERVER diff --git a/src/clientserver.h b/src/clientserver.h index b4e9ccfc8..9cbb7a685 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Compress the contents of TOCLIENT_ITEMDEF and TOCLIENT_NODEDEF PROTOCOL_VERSION 8: Digging based on item groups + Many things */ #define PROTOCOL_VERSION 8 @@ -268,7 +269,25 @@ enum ToClientCommand u32 length of next item serialized ItemDefManager */ + + TOCLIENT_PLAY_SOUND = 0x3f, + /* + u16 command + s32 sound_id + u16 len + u8[len] sound name + s32 gain*1000 + u8 type (0=local, 1=positional, 2=object) + s32[3] pos_nodes*10000 + u16 object_id + u8 loop (bool) + */ + TOCLIENT_STOP_SOUND = 0x40, + /* + u16 command + s32 sound_id + */ }; enum ToServerCommand @@ -442,15 +461,21 @@ enum ToServerCommand (Obsoletes TOSERVER_GROUND_ACTION and TOSERVER_CLICK_ACTIVEOBJECT.) */ - TOSERVER_REQUEST_TEXTURES = 0x40, - + TOSERVER_REMOVED_SOUNDS = 0x3a, /* - u16 command - u16 number of textures requested - for each texture { - u16 length of name - string name - } + u16 command + u16 len + s32[len] sound_id + */ + + TOSERVER_REQUEST_TEXTURES = 0x40, + /* + u16 command + u16 number of textures requested + for each texture { + u16 length of name + string name + } */ }; diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 3aba4c7cb..fc1df377a 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #include "tool.h" #include "content_cso.h" +#include "sound.h" +#include "nodedef.h" class Settings; struct ToolCapabilities; @@ -1008,6 +1010,7 @@ private: LocalPlayer *m_local_player; float m_damage_visual_timer; bool m_dead; + float m_step_distance_counter; public: PlayerCAO(IGameDef *gamedef, ClientEnvironment *env): @@ -1020,7 +1023,8 @@ public: m_is_local_player(false), m_local_player(NULL), m_damage_visual_timer(0), - m_dead(false) + m_dead(false), + m_step_distance_counter(0) { if(gamedef == NULL) ClientActiveObject::registerType(getType(), create); @@ -1202,7 +1206,9 @@ public: void step(float dtime, ClientEnvironment *env) { + v3f lastpos = pos_translator.vect_show; pos_translator.translate(dtime); + float moved = lastpos.getDistanceFrom(pos_translator.vect_show); updateVisibility(); updateNodePos(); @@ -1212,6 +1218,18 @@ public: updateTextures(""); } } + + m_step_distance_counter += moved; + if(m_step_distance_counter > 1.5*BS){ + m_step_distance_counter = 0; + if(!m_is_local_player){ + INodeDefManager *ndef = m_gamedef->ndef(); + v3s16 p = floatToInt(getPosition()+v3f(0,-0.5*BS, 0), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + SimpleSoundSpec spec = ndef->get(n).sound_footstep; + m_gamedef->sound()->playSoundAt(spec, false, getPosition()); + } + } } void processMessage(const std::string &data) diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 5ce5f3b29..3c7b9bb3e 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -2222,7 +2222,7 @@ private: static const char className[]; static const luaL_reg methods[]; - +public: static ObjectRef *checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -2236,7 +2236,7 @@ private: ServerActiveObject *co = ref->m_object; return co; } - +private: static LuaEntitySAO* getluaobject(ObjectRef *ref) { ServerActiveObject *obj = getobject(ref); @@ -3134,10 +3134,6 @@ const luaL_reg EnvRef::methods[] = { {0,0} }; -/* - Global functions -*/ - class LuaABM : public ActiveBlockModifier { private: @@ -3211,6 +3207,47 @@ public: } }; +/* + ServerSoundParams +*/ + +static void read_server_sound_params(lua_State *L, int index, + ServerSoundParams ¶ms) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + // Clear + params = ServerSoundParams(); + if(lua_istable(L, index)){ + getfloatfield(L, index, "gain", params.gain); + getstringfield(L, index, "to_player", params.to_player); + lua_getfield(L, index, "pos"); + if(!lua_isnil(L, -1)){ + v3f p = read_v3f(L, -1)*BS; + params.pos = p; + params.type = ServerSoundParams::SSP_POSITIONAL; + } + lua_pop(L, 1); + lua_getfield(L, index, "object"); + if(!lua_isnil(L, -1)){ + ObjectRef *ref = ObjectRef::checkobject(L, -1); + ServerActiveObject *sao = ObjectRef::getobject(ref); + if(sao){ + params.object = sao->getId(); + params.type = ServerSoundParams::SSP_OBJECT; + } + } + lua_pop(L, 1); + params.max_hear_distance = BS*getfloatfield_default(L, index, + "max_hear_distance", params.max_hear_distance/BS); + getboolfield(L, index, "loop", params.loop); + } +} + +/* + Global functions +*/ + // debug(text) // Writes a line to dstream static int l_debug(lua_State *L) @@ -3674,6 +3711,26 @@ static int l_get_worldpath(lua_State *L) return 1; } +// sound_play(spec, parameters) +static int l_sound_play(lua_State *L) +{ + SimpleSoundSpec spec; + read_soundspec(L, 1, spec); + ServerSoundParams params; + read_server_sound_params(L, 2, params); + s32 handle = get_server(L)->playSound(spec, params); + lua_pushinteger(L, handle); + return 1; +} + +// sound_stop(handle) +static int l_sound_stop(lua_State *L) +{ + int handle = luaL_checkinteger(L, 1); + get_server(L)->stopSound(handle); + return 0; +} + static const struct luaL_Reg minetest_f [] = { {"debug", l_debug}, {"log", l_log}, @@ -3691,6 +3748,8 @@ static const struct luaL_Reg minetest_f [] = { {"get_current_modname", l_get_current_modname}, {"get_modpath", l_get_modpath}, {"get_worldpath", l_get_worldpath}, + {"sound_play", l_sound_play}, + {"sound_stop", l_sound_stop}, {NULL, NULL} }; diff --git a/src/server.cpp b/src/server.cpp index e781f1284..745e55f83 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -3126,6 +3126,24 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) <::iterator i = + m_playing_sounds.find(id); + if(i == m_playing_sounds.end()) + continue; + ServerPlayingSound &psound = i->second; + psound.clients.erase(peer_id); + if(psound.clients.size() == 0) + m_playing_sounds.erase(i++); + } + } else { infostream<<"Server::ProcessData(): Ignoring " @@ -3575,6 +3593,107 @@ void Server::SendMovePlayer(Player *player) m_con.Send(player->peer_id, 0, data, true); } +s32 Server::playSound(const SimpleSoundSpec &spec, + const ServerSoundParams ¶ms) +{ + // Find out initial position of sound + bool pos_exists = false; + v3f pos = params.getPos(m_env, &pos_exists); + // If position is not found while it should be, cancel sound + if(pos_exists != (params.type != ServerSoundParams::SSP_LOCAL)) + return -1; + // Filter destination clients + std::set dst_clients; + if(params.to_player != "") + { + Player *player = m_env->getPlayer(params.to_player.c_str()); + if(!player){ + infostream<<"Server::playSound: Player \""<peer_id == PEER_ID_INEXISTENT){ + infostream<<"Server::playSound: Player \""<peer_id); + dst_clients.insert(client); + } + else + { + for(core::map::Iterator + i = m_clients.getIterator(); i.atEnd() == false; i++) + { + RemoteClient *client = i.getNode()->getValue(); + Player *player = m_env->getPlayer(client->peer_id); + if(!player) + continue; + if(pos_exists){ + if(player->getPosition().getDistanceFrom(pos) > + params.max_hear_distance) + continue; + } + dst_clients.insert(client); + } + } + if(dst_clients.size() == 0) + return -1; + // Create the sound + s32 id = m_next_sound_id++; + // The sound will exist as a reference in m_playing_sounds + m_playing_sounds[id] = ServerPlayingSound(); + ServerPlayingSound &psound = m_playing_sounds[id]; + psound.params = params; + for(std::set::iterator i = dst_clients.begin(); + i != dst_clients.end(); i++) + psound.clients.insert((*i)->peer_id); + // Create packet + std::ostringstream os(std::ios_base::binary); + writeU16(os, TOCLIENT_PLAY_SOUND); + writeS32(os, id); + os< data((u8*)s.c_str(), s.size()); + // Send + for(std::set::iterator i = dst_clients.begin(); + i != dst_clients.end(); i++){ + // Send as reliable + m_con.Send((*i)->peer_id, 0, data, true); + } + return id; +} +void Server::stopSound(s32 handle) +{ + // Get sound reference + std::map::iterator i = + m_playing_sounds.find(handle); + if(i == m_playing_sounds.end()) + return; + ServerPlayingSound &psound = i->second; + // Create packet + std::ostringstream os(std::ios_base::binary); + writeU16(os, TOCLIENT_STOP_SOUND); + writeS32(os, handle); + // Make data buffer + std::string s = os.str(); + SharedBuffer data((u8*)s.c_str(), s.size()); + // Send + for(std::set::iterator i = psound.clients.begin(); + i != psound.clients.end(); i++){ + // Send as reliable + m_con.Send(*i, 0, data, true); + } + // Remove sound reference + m_playing_sounds.erase(i); +} + void Server::sendRemoveNode(v3s16 p, u16 ignore_id, core::list *far_players, float far_d_nodes) { @@ -4511,6 +4630,21 @@ void Server::handlePeerChange(PeerChange &c) obj->m_known_by_count--; } + /* + Clear references to playing sounds + */ + for(std::map::iterator + i = m_playing_sounds.begin(); + i != m_playing_sounds.end();) + { + ServerPlayingSound &psound = i->second; + psound.clients.erase(c.peer_id); + if(psound.clients.size() == 0) + m_playing_sounds.erase(i++); + else + i++; + } + ServerRemotePlayer* player = static_cast(m_env->getPlayer(c.peer_id)); diff --git a/src/server.h b/src/server.h index 3baeb433d..ae50af15b 100644 --- a/src/server.h +++ b/src/server.h @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mods.h" #include "inventorymanager.h" #include "subgame.h" +#include "sound.h" struct LuaState; typedef struct lua_State lua_State; class IWritableItemDefManager; @@ -274,6 +275,58 @@ struct TextureInformation } }; +struct ServerSoundParams +{ + float gain; + std::string to_player; + enum Type{ + SSP_LOCAL=0, + SSP_POSITIONAL=1, + SSP_OBJECT=2 + } type; + v3f pos; + u16 object; + float max_hear_distance; + bool loop; + + ServerSoundParams(): + gain(1.0), + to_player(""), + type(SSP_LOCAL), + pos(0,0,0), + object(0), + max_hear_distance(32*BS), + loop(false) + {} + + v3f getPos(ServerEnvironment *env, bool *pos_exists) const + { + if(pos_exists) *pos_exists = false; + switch(type){ + case SSP_LOCAL: + return v3f(0,0,0); + case SSP_POSITIONAL: + if(pos_exists) *pos_exists = true; + return pos; + case SSP_OBJECT: { + if(object == 0) + return v3f(0,0,0); + ServerActiveObject *sao = env->getActiveObject(object); + if(!sao) + return v3f(0,0,0); + if(pos_exists) *pos_exists = true; + return sao->getBasePosition(); } + } + return v3f(0,0,0); + } +}; + +struct ServerPlayingSound +{ + ServerSoundParams params; + std::set clients; // peer ids +}; + class RemoteClient { public: @@ -464,6 +517,11 @@ public: // Envlock and conlock should be locked when calling this void SendMovePlayer(Player *player); + // Returns -1 if failed, sound handle on success + // Envlock + conlock + s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams ¶ms); + void stopSound(s32 handle); + // Thread-safe u64 getPlayerAuthPrivs(const std::string &name); void setPlayerAuthPrivs(const std::string &name, u64 privs); @@ -775,6 +833,12 @@ private: friend class RemoteClient; std::map m_Textures; + + /* + Sounds + */ + std::map m_playing_sounds; + s32 m_next_sound_id; }; /* diff --git a/src/sound.h b/src/sound.h index 7f6e4141e..f9c1ea0ce 100644 --- a/src/sound.h +++ b/src/sound.h @@ -67,6 +67,8 @@ public: virtual int playSoundAt(const std::string &name, bool loop, float volume, v3f pos) = 0; virtual void stopSound(int sound) = 0; + virtual bool soundExists(int sound) = 0; + virtual void updateSoundPosition(int sound, v3f pos) = 0; int playSound(const SimpleSoundSpec &spec, bool loop) { return playSound(spec.name, loop, spec.gain); } @@ -87,6 +89,8 @@ public: int playSoundAt(const std::string &name, bool loop, float volume, v3f pos) {return 0;} void stopSound(int sound) {} + bool soundExists(int sound) {return false;} + void updateSoundPosition(int sound, v3f pos) {} }; // Global DummySoundManager singleton diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index edcb9e8d4..26ad6fa4c 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -482,6 +482,24 @@ public: maintain(); deleteSound(sound); } + bool soundExists(int sound) + { + maintain(); + return (m_sounds_playing.count(sound) != 0); + } + void updateSoundPosition(int id, v3f pos) + { + std::map::iterator i = + m_sounds_playing.find(id); + if(i == m_sounds_playing.end()) + return; + PlayingSound *sound = i->second; + + alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false); + alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z); + alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0); + alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0); + } }; ISoundManager *createOpenALSoundManager(OnDemandSoundFetcher *fetcher)