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 = <an ObjectRef>,
+ 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)
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))
^ 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
legacy_wallmounted = false, -- Support maps made in and before January 2012
sounds = {
footstep = <SimpleSoundSpec>,
+ dig = <SimpleSoundSpec>, -- "__group" = group-based sound (default)
dug = <SimpleSoundSpec>,
},
}
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")
--[[
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;
m_inventory_updated = true;
}
}
+
+ /*
+ Update positions of sounds attached to objects
+ */
+ {
+ for(std::map<int, u16>::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<s32> removed_server_ids;
+ for(std::map<s32, int>::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<s32>::iterator i = removed_server_ids.begin();
+ i != removed_server_ids.end(); i++)
+ writeS32(os, *i);
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ Send(0, data, true);
+ }
+ }
}
// Virtual methods from con::PeerHandler
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<s32, int>::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 "
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<s32, int> m_sounds_server_to_client;
+ // And the other way!
+ std::map<int, s32> m_sounds_client_to_server;
+ // And relations to objects
+ std::map<int, u16> m_sounds_to_objects;
};
#endif // !SERVER
Compress the contents of TOCLIENT_ITEMDEF and TOCLIENT_NODEDEF
PROTOCOL_VERSION 8:
Digging based on item groups
+ Many things
*/
#define PROTOCOL_VERSION 8
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
(Obsoletes TOSERVER_GROUND_ACTION and TOSERVER_CLICK_ACTIVEOBJECT.)
*/
- TOSERVER_REQUEST_TEXTURES = 0x40,
+ TOSERVER_REMOVED_SOUNDS = 0x3a,
+ /*
+ 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
- }
+ u16 command
+ u16 number of textures requested
+ for each texture {
+ u16 length of name
+ string name
+ }
*/
};
#include "itemdef.h"
#include "tool.h"
#include "content_cso.h"
+#include "sound.h"
+#include "nodedef.h"
class Settings;
struct ToolCapabilities;
LocalPlayer *m_local_player;
float m_damage_visual_timer;
bool m_dead;
+ float m_step_distance_counter;
public:
PlayerCAO(IGameDef *gamedef, ClientEnvironment *env):
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);
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();
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)
static const char className[];
static const luaL_reg methods[];
-
+public:
static ObjectRef *checkobject(lua_State *L, int narg)
{
luaL_checktype(L, narg, LUA_TUSERDATA);
ServerActiveObject *co = ref->m_object;
return co;
}
-
+private:
static LuaEntitySAO* getluaobject(ObjectRef *ref)
{
ServerActiveObject *obj = getobject(ref);
{0,0}
};
-/*
- Global functions
-*/
-
class LuaABM : public ActiveBlockModifier
{
private:
}
};
+/*
+ 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)
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},
{"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}
};
<<action<<std::endl;
}
}
+ else if(command == TOSERVER_REMOVED_SOUNDS)
+ {
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+
+ int num = readU16(is);
+ for(int k=0; k<num; k++){
+ s32 id = readS32(is);
+ std::map<s32, ServerPlayingSound>::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 "
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<RemoteClient*> dst_clients;
+ if(params.to_player != "")
+ {
+ Player *player = m_env->getPlayer(params.to_player.c_str());
+ if(!player){
+ infostream<<"Server::playSound: Player \""<<params.to_player
+ <<"\" not found"<<std::endl;
+ return -1;
+ }
+ if(player->peer_id == PEER_ID_INEXISTENT){
+ infostream<<"Server::playSound: Player \""<<params.to_player
+ <<"\" not connected"<<std::endl;
+ return -1;
+ }
+ RemoteClient *client = getClient(player->peer_id);
+ dst_clients.insert(client);
+ }
+ else
+ {
+ for(core::map<u16, RemoteClient*>::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<RemoteClient*>::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<<serializeString(spec.name);
+ writeF1000(os, spec.gain * params.gain);
+ writeU8(os, params.type);
+ writeV3F1000(os, pos);
+ writeU16(os, params.object);
+ writeU8(os, params.loop);
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send
+ for(std::set<RemoteClient*>::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<s32, ServerPlayingSound>::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<u8> data((u8*)s.c_str(), s.size());
+ // Send
+ for(std::set<u16>::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<u16> *far_players, float far_d_nodes)
{
obj->m_known_by_count--;
}
+ /*
+ Clear references to playing sounds
+ */
+ for(std::map<s32, ServerPlayingSound>::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<ServerRemotePlayer*>(m_env->getPlayer(c.peer_id));
#include "mods.h"
#include "inventorymanager.h"
#include "subgame.h"
+#include "sound.h"
struct LuaState;
typedef struct lua_State lua_State;
class IWritableItemDefManager;
}
};
+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<u16> clients; // peer ids
+};
+
class RemoteClient
{
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);
friend class RemoteClient;
std::map<std::string,TextureInformation> m_Textures;
+
+ /*
+ Sounds
+ */
+ std::map<s32, ServerPlayingSound> m_playing_sounds;
+ s32 m_next_sound_id;
};
/*
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); }
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
maintain();
deleteSound(sound);
}
+ bool soundExists(int sound)
+ {
+ maintain();
+ return (m_sounds_playing.count(sound) != 0);
+ }
+ void updateSoundPosition(int id, v3f pos)
+ {
+ std::map<int, PlayingSound*>::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)