Build configuration fixes/improvements on Windows
[oweals/minetest.git] / src / server.cpp
index 1eb618f7c9d578fa3d7195aa434797372f9dbf1e..745e55f831cf3231d90d789c202c2194c8d04993 100644 (file)
@@ -48,6 +48,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "sha1.h"
 #include "base64.h"
 #include "tool.h"
+#include "utility_string.h"
+#include "sound.h" // dummySoundManager
+#include "event_manager.h"
 
 #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
 
@@ -108,6 +111,10 @@ void * ServerThread::Thread()
                {
                        infostream<<"Server: PeerNotFoundException"<<std::endl;
                }
+               catch(con::ConnectionBindFailed &e)
+               {
+                       m_server->setAsyncFatalError(e.what());
+               }
        }
        
        END_DEBUG_EXCEPTION_HANDLER(errorstream)
@@ -824,25 +831,21 @@ void PlayerInfo::PrintLine(std::ostream *s)
        (*s)<<std::endl;
 }
 
-static std::string padStringRight(std::string s, size_t len)
-{
-       if(len > s.size())
-               s.insert(s.end(), len - s.size(), ' ');
-       return s;
-}
-
 /*
        Server
 */
 
 Server::Server(
-               std::string path_world,
-               std::string path_config,
-               std::string gamename
+               const std::string &path_world,
+               const std::string &path_config,
+               const SubgameSpec &gamespec,
+               bool simple_singleplayer_mode
        ):
-       m_gamename(gamename),
        m_path_world(path_world),
        m_path_config(path_config),
+       m_gamespec(gamespec),
+       m_simple_singleplayer_mode(simple_singleplayer_mode),
+       m_async_fatal_error(""),
        m_env(NULL),
        m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
        m_authmanager(path_world+DIR_DELIM+"auth.txt"),
@@ -851,9 +854,9 @@ Server::Server(
        m_itemdef(createItemDefManager()),
        m_nodedef(createNodeDefManager()),
        m_craftdef(createCraftDefManager()),
+       m_event(new EventManager()),
        m_thread(this),
        m_emergethread(this),
-       m_time_counter(0),
        m_time_of_day_send_timer(0),
        m_uptime(0),
        m_shutdown_requested(false),
@@ -871,57 +874,43 @@ Server::Server(
        m_step_dtime_mutex.Init();
        m_step_dtime = 0.0;
 
-       // Figure out some paths
-       // share/server
-       m_path_share = porting::path_share + DIR_DELIM + "server";
-       // game
-       m_path_game = porting::path_user + DIR_DELIM + "server" + DIR_DELIM
-                       + "games" + DIR_DELIM + m_gamename;
-       bool user_game = true; // Game is in user's directory
-       if(!fs::PathExists(m_path_game)){
-               m_path_game = m_path_share + DIR_DELIM + "games" + DIR_DELIM
-                               + m_gamename;
-               user_game = false;
-       }
-       if(!fs::PathExists(m_path_game)){
-               throw ServerError("Could not find game files for game \""
-                               +gamename+"\"");
-       }
-       // addons
-       if(!user_game)
-               m_path_addons.insert(m_path_share + DIR_DELIM + "addons"
-                               + DIR_DELIM + m_gamename);
-       m_path_addons.insert(porting::path_user + DIR_DELIM + "server"
-                       + DIR_DELIM + "addons" + DIR_DELIM + m_gamename);
-
-       infostream<<"Server created for gamename=\""<<gamename<<"\""<<std::endl;
-       infostream<<"- path_world  = "<<m_path_world<<std::endl;
-       infostream<<"- path_config = "<<m_path_config<<std::endl;
-       infostream<<"- path_game   = "<<m_path_game<<std::endl;
-       for(std::set<std::string>::const_iterator i = m_path_addons.begin();
-                       i != m_path_addons.end(); i++)
-               infostream<<"- path_addons+= "<<(*i)<<std::endl;
-
-       // Path to builtin.lua
-       std::string builtinpath = m_path_share + DIR_DELIM + "builtin.lua";
+       if(path_world == "")
+               throw ServerError("Supplied empty world path");
+       
+       if(!gamespec.isValid())
+               throw ServerError("Supplied invalid gamespec");
+       
+       infostream<<"Server created for gameid \""<<m_gamespec.id<<"\"";
+       if(m_simple_singleplayer_mode)
+               infostream<<" in simple singleplayer mode"<<std::endl;
+       else
+               infostream<<std::endl;
+       infostream<<"- world:  "<<m_path_world<<std::endl;
+       infostream<<"- config: "<<m_path_config<<std::endl;
+       infostream<<"- game:   "<<m_gamespec.path<<std::endl;
 
-       // Add default global mod search path
-       m_modspaths.push_front(m_path_game + DIR_DELIM "mods");
        // Add world mod search path
        m_modspaths.push_front(m_path_world + DIR_DELIM + "worldmods");
        // Add addon mod search path
-       for(std::set<std::string>::const_iterator i = m_path_addons.begin();
-                       i != m_path_addons.end(); i++){
-               m_modspaths.push_front((*i) + DIR_DELIM + "mods");
-       }
+       for(std::set<std::string>::const_iterator i = m_gamespec.mods_paths.begin();
+                       i != m_gamespec.mods_paths.end(); i++)
+               m_modspaths.push_front((*i));
 
        // Print out mod search paths
        for(core::list<std::string>::Iterator i = m_modspaths.begin();
                        i != m_modspaths.end(); i++){
                std::string modspath = *i;
-               infostream<<"- modspath   += "<<modspath<<std::endl;
+               infostream<<"- mods:   "<<modspath<<std::endl;
        }
        
+       // Path to builtin.lua
+       std::string builtinpath = porting::path_share + DIR_DELIM + "builtin"
+                       + DIR_DELIM + "builtin.lua";
+
+       // Create world if it doesn't exist
+       if(!initializeWorld(m_path_world, m_gamespec.id))
+               throw ServerError("Failed to initialize world");
+
        // Lock environment
        JMutexAutoLock envlock(m_env_mutex);
        JMutexAutoLock conlock(m_con_mutex);
@@ -1077,10 +1066,10 @@ Server::~Server()
                        delete i.getNode()->getValue();
                }
        }
-
-       // Delete Environment
+       
+       // Delete things in the reverse order of creation
        delete m_env;
-
+       delete m_event;
        delete m_itemdef;
        delete m_nodedef;
        delete m_craftdef;
@@ -1093,6 +1082,8 @@ Server::~Server()
 void Server::start(unsigned short port)
 {
        DSTACK(__FUNCTION_NAME);
+       infostream<<"Starting server on port "<<port<<"..."<<std::endl;
+
        // Stop thread if already running
        m_thread.stop();
        
@@ -1112,7 +1103,9 @@ void Server::start(unsigned short port)
        <<"|  Y Y  \\  |   |  \\  ___/|  | \\  ___/ \\___ \\  |  |  "<<std::endl
        <<"|__|_|  /__|___|  /\\___  >__|  \\___  >____  > |__|  "<<std::endl
        <<"      \\/        \\/     \\/          \\/     \\/        "<<std::endl;
-       actionstream<<"Server listening on port "<<port<<"."<<std::endl;
+       actionstream<<"World at ["<<m_path_world<<"]"<<std::endl;
+       actionstream<<"Server for gameid=\""<<m_gamespec.id
+                       <<"\" listening on port "<<port<<"."<<std::endl;
 }
 
 void Server::stop()
@@ -1140,6 +1133,11 @@ void Server::step(float dtime)
                JMutexAutoLock lock(m_step_dtime_mutex);
                m_step_dtime += dtime;
        }
+       // Throw if fatal error occurred in thread
+       std::string async_err = m_async_fatal_error.get();
+       if(async_err != ""){
+               throw ServerError(async_err);
+       }
 }
 
 void Server::AsyncRunStep()
@@ -1193,19 +1191,12 @@ void Server::AsyncRunStep()
        }
 
        /*
-               Update m_time_of_day and overall game time
+               Update time of day and overall game time
        */
        {
                JMutexAutoLock envlock(m_env_mutex);
 
-               m_time_counter += dtime;
-               f32 speed = g_settings->getFloat("time_speed") * 24000./(24.*3600);
-               u32 units = (u32)(m_time_counter*speed);
-               m_time_counter -= (f32)units / speed;
-               
-               m_env->setTimeOfDay((m_env->getTimeOfDay() + units) % 24000);
-               
-               //infostream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
+               m_env->setTimeOfDaySpeed(g_settings->getFloat("time_speed"));
 
                /*
                        Send to clients at constant intervals
@@ -1227,7 +1218,7 @@ void Server::AsyncRunStep()
                                //Player *player = m_env->getPlayer(client->peer_id);
                                
                                SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
-                                               m_env->getTimeOfDay());
+                                               m_env->getTimeOfDay(), g_settings->getFloat("time_speed"));
                                // Send as reliable
                                m_con.Send(client->peer_id, 0, data, true);
                        }
@@ -1910,13 +1901,17 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
        
        try{
                Address address = m_con.GetPeerAddress(peer_id);
+               std::string addr_s = address.serializeString();
 
                // drop player if is ip is banned
-               if(m_banmanager.isIpBanned(address.serializeString())){
+               if(m_banmanager.isIpBanned(addr_s)){
+                       infostream<<"Server: A banned client tried to connect from "
+                                       <<addr_s<<"; banned name was "
+                                       <<m_banmanager.getBanName(addr_s)<<std::endl;
+                       // This actually doesn't seem to transfer to the client
                        SendAccessDenied(m_con, peer_id,
                                        L"Your ip is banned. Banned name was "
-                                       +narrow_to_wide(m_banmanager.getBanName(
-                                               address.serializeString())));
+                                       +narrow_to_wide(m_banmanager.getBanName(addr_s)));
                        m_con.DeletePeer(peer_id);
                        return;
                }
@@ -2107,6 +2102,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                        SendAccessDenied(m_con, peer_id, L"Invalid password");
                        return;
                }
+
+               // Do not allow multiple players in simple singleplayer mode.
+               // This isn't a perfect way to do it, but will suffice for now.
+               if(m_simple_singleplayer_mode && m_clients.size() > 1){
+                       infostream<<"Server: Not allowing another client to connect in"
+                                       <<" simple singleplayer mode"<<std::endl;
+                       SendAccessDenied(m_con, peer_id,
+                                       L"Running in simple singleplayer mode.");
+                       return;
+               }
                
                // Enforce user limit.
                // Don't enforce for users that have some admin right
@@ -2202,25 +2207,29 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                // Send time of day
                {
                        SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
-                                       m_env->getTimeOfDay());
+                                       m_env->getTimeOfDay(), g_settings->getFloat("time_speed"));
                        m_con.Send(peer_id, 0, data, true);
                }
                
-               // Send information about server to player in chat
-               SendChatMessage(peer_id, getStatusString());
-               
-               // Send information about joining in chat
+               // Note things in chat if not in simple singleplayer mode
+               if(!m_simple_singleplayer_mode)
                {
-                       std::wstring name = L"unknown";
-                       Player *player = m_env->getPlayer(peer_id);
-                       if(player != NULL)
-                               name = narrow_to_wide(player->getName());
+                       // Send information about server to player in chat
+                       SendChatMessage(peer_id, getStatusString());
                        
-                       std::wstring message;
-                       message += L"*** ";
-                       message += name;
-                       message += L" joined game";
-                       BroadcastChatMessage(message);
+                       // Send information about joining in chat
+                       {
+                               std::wstring name = L"unknown";
+                               Player *player = m_env->getPlayer(peer_id);
+                               if(player != NULL)
+                                       name = narrow_to_wide(player->getName());
+                               
+                               std::wstring message;
+                               message += L"*** ";
+                               message += name;
+                               message += L" joined game";
+                               BroadcastChatMessage(message);
+                       }
                }
                
                // Warnings about protocol version can be issued here
@@ -3117,6 +3126,24 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                        <<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 "
@@ -3566,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 &params)
+{
+       // 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)
 {
@@ -4164,6 +4292,40 @@ std::wstring Server::getStatusString()
        return os.str();
 }
 
+u64 Server::getPlayerAuthPrivs(const std::string &name)
+{
+       try{
+               return m_authmanager.getPrivs(name);
+       }
+       catch(AuthNotFoundException &e)
+       {
+               dstream<<"WARNING: Auth not found for "<<name<<std::endl;
+               return 0;
+       }
+}
+
+void Server::setPlayerAuthPrivs(const std::string &name, u64 privs)
+{
+       try{
+               return m_authmanager.setPrivs(name, privs);
+       }
+       catch(AuthNotFoundException &e)
+       {
+               dstream<<"WARNING: Auth not found for "<<name<<std::endl;
+       }
+}
+
+u64 Server::getPlayerEffectivePrivs(const std::string &name)
+{
+       // Local player gets all privileges regardless of
+       // what's set on their account.
+       if(m_simple_singleplayer_mode)
+               return PRIV_ALL;
+       if(name == g_settings->get("name"))
+               return PRIV_ALL;
+       return getPlayerAuthPrivs(name);
+}
+
 void Server::setPlayerPassword(const std::string &name, const std::wstring &password)
 {
        // Add player to auth manager
@@ -4230,6 +4392,14 @@ u16 Server::allocateUnknownNodeId(const std::string &name)
 {
        return m_nodedef->allocateDummy(name);
 }
+ISoundManager* Server::getSoundManager()
+{
+       return &dummySoundManager;
+}
+MtEventManager* Server::getEventManager()
+{
+       return m_event;
+}
 
 IWritableItemDefManager* Server::getWritableItemDefManager()
 {
@@ -4460,6 +4630,21 @@ void Server::handlePeerChange(PeerChange &c)
                                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));
 
@@ -4552,16 +4737,7 @@ u64 Server::getPlayerPrivs(Player *player)
        if(player==NULL)
                return 0;
        std::string playername = player->getName();
-       // Local player gets all privileges regardless of
-       // what's set on their account.
-       if(g_settings->get("name") == playername)
-       {
-               return PRIV_ALL;
-       }
-       else
-       {
-               return getPlayerAuthPrivs(playername);
-       }
+       return getPlayerEffectivePrivs(playername);
 }
 
 void dedicated_server_loop(Server &server, bool &kill)