/* Minetest-c55 Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "server.h" #include "utility.h" #include #include #include "clientserver.h" #include "map.h" #include "jmutexautolock.h" #include "main.h" #include "constants.h" #include "voxel.h" #include "config.h" #include "servercommand.h" #include "filesys.h" #include "mapblock.h" #include "serverobject.h" #include "settings.h" #include "profiler.h" #include "log.h" #include "script.h" #include "scriptapi.h" #include "nodedef.h" #include "itemdef.h" #include "craftdef.h" #include "mapgen.h" #include "content_mapnode.h" #include "content_nodemeta.h" #include "content_abm.h" #include "content_sao.h" #include "mods.h" #include "sha1.h" #include "base64.h" #include "tool.h" #include "utility_string.h" #include "sound.h" // dummySoundManager #include "event_manager.h" #include "hex.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" #define BLOCK_EMERGE_FLAG_FROMDISK (1<<0) class MapEditEventIgnorer { public: MapEditEventIgnorer(bool *flag): m_flag(flag) { if(*m_flag == false) *m_flag = true; else m_flag = NULL; } ~MapEditEventIgnorer() { if(m_flag) { assert(*m_flag); *m_flag = false; } } private: bool *m_flag; }; class MapEditEventAreaIgnorer { public: MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): m_ignorevariable(ignorevariable) { if(m_ignorevariable->getVolume() == 0) *m_ignorevariable = a; else m_ignorevariable = NULL; } ~MapEditEventAreaIgnorer() { if(m_ignorevariable) { assert(m_ignorevariable->getVolume() != 0); *m_ignorevariable = VoxelArea(); } } private: VoxelArea *m_ignorevariable; }; void * ServerThread::Thread() { ThreadStarted(); log_register_thread("ServerThread"); DSTACK(__FUNCTION_NAME); BEGIN_DEBUG_EXCEPTION_HANDLER while(getRun()) { try{ //TimeTaker timer("AsyncRunStep() + Receive()"); { //TimeTaker timer("AsyncRunStep()"); m_server->AsyncRunStep(); } //infostream<<"Running m_server->Receive()"<Receive(); } catch(con::NoIncomingDataException &e) { } catch(con::PeerNotFoundException &e) { infostream<<"Server: PeerNotFoundException"<setAsyncFatalError(e.what()); } catch(LuaError &e) { m_server->setAsyncFatalError(e.what()); } } END_DEBUG_EXCEPTION_HANDLER(errorstream) return NULL; } void * EmergeThread::Thread() { ThreadStarted(); log_register_thread("EmergeThread"); DSTACK(__FUNCTION_NAME); BEGIN_DEBUG_EXCEPTION_HANDLER bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); /* Get block info from queue, emerge them and send them to clients. After queue is empty, exit. */ while(getRun()) { QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop(); if(qptr == NULL) break; SharedPtr q(qptr); v3s16 &p = q->pos; v2s16 p2d(p.X,p.Z); /* Do not generate over-limit */ if(blockpos_over_limit(p)) continue; //infostream<<"EmergeThread::Thread(): running"<::Iterator i; for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++) { //u16 peer_id = i.getNode()->getKey(); // Check flags u8 flags = i.getNode()->getValue(); if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false) only_from_disk = false; } } if(enable_mapgen_debug_info) infostream<<"EmergeThread: p=" <<"("<isGenerated() == false)){ if(enable_mapgen_debug_info) infostream<<"EmergeThread: generating"<m_env_mutex); ScopeProfiler sp(g_profiler, "EmergeThread: after " "mapgen::make_block (envlock)", SPT_AVG); // Blit data back on map, update lighting, add mobs and // whatever this does map.finishBlockMake(&data, modified_blocks); // Get central block block = map.getBlockNoCreateNoEx(p); // If block doesn't exist, don't try doing anything with it // This happens if the block is not in generation boundaries if(!block) break; /* Do some post-generate stuff */ v3s16 minp = data.blockpos_min*MAP_BLOCKSIZE; v3s16 maxp = data.blockpos_max*MAP_BLOCKSIZE + v3s16(1,1,1)*(MAP_BLOCKSIZE-1); /* Ignore map edit events, they will not need to be sent to anybody because the block hasn't been sent to anybody */ //MapEditEventIgnorer ign(&m_server->m_ignore_map_edit_events); MapEditEventAreaIgnorer ign( &m_server->m_ignore_map_edit_events_area, VoxelArea(minp, maxp)); { TimeTaker timer("on_generated"); scriptapi_environment_on_generated(m_server->m_lua, minp, maxp, mapgen::get_blockseed(data.seed, minp)); /*int t = timer.stop(true); dstream<<"on_generated took "<m_env->activateBlock(block, 0); }while(false); } if(block == NULL) got_block = false; /* Set sent status of modified blocks on clients */ // NOTE: Server's clients are also behind the connection mutex JMutexAutoLock lock(m_server->m_con_mutex); /* Add the originally fetched block to the modified list */ if(got_block) { modified_blocks.insert(p, block); } /* Set the modified blocks unsent for all the clients */ for(core::map::Iterator i = m_server->m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); if(modified_blocks.size() > 0) { // Remove block from sent history client->SetBlocksNotSent(modified_blocks); } } } END_DEBUG_EXCEPTION_HANDLER(errorstream) log_deregister_thread(); return NULL; } v3f ServerSoundParams::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); } void RemoteClient::GetNextBlocks(Server *server, float dtime, core::array &dest) { DSTACK(__FUNCTION_NAME); /*u32 timer_result; TimeTaker timer("RemoteClient::GetNextBlocks", &timer_result);*/ // Increment timers m_nothing_to_send_pause_timer -= dtime; m_nearest_unsent_reset_timer += dtime; if(m_nothing_to_send_pause_timer >= 0) { return; } // Won't send anything if already sending if(m_blocks_sending.size() >= g_settings->getU16 ("max_simultaneous_block_sends_per_client")) { //infostream<<"Not sending any blocks, Queue full."<m_env->getPlayer(peer_id); assert(player != NULL); v3f playerpos = player->getPosition(); v3f playerspeed = player->getSpeed(); v3f playerspeeddir(0,0,0); if(playerspeed.getLength() > 1.0*BS) playerspeeddir = playerspeed / playerspeed.getLength(); // Predict to next block v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS; v3s16 center_nodepos = floatToInt(playerpos_predicted, BS); v3s16 center = getNodeBlockPos(center_nodepos); // Camera position and direction v3f camera_pos = player->getEyePosition(); v3f camera_dir = v3f(0,0,1); camera_dir.rotateYZBy(player->getPitch()); camera_dir.rotateXZBy(player->getYaw()); /*infostream<<"camera_dir=("<getPlayerName(peer_id)<getFloat( "full_block_send_enable_min_time_from_building")) { max_simul_sends_usually = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS; } /* Number of blocks sending + number of blocks selected for sending */ u32 num_blocks_selected = m_blocks_sending.size(); /* next time d will be continued from the d from which the nearest unsent block was found this time. This is because not necessarily any of the blocks found this time are actually sent. */ s32 new_nearest_unsent_d = -1; s16 d_max = g_settings->getS16("max_block_send_distance"); s16 d_max_gen = g_settings->getS16("max_block_generate_distance"); // Don't loop very much at a time s16 max_d_increment_at_time = 2; if(d_max > d_start + max_d_increment_at_time) d_max = d_start + max_d_increment_at_time; /*if(d_max_gen > d_start+2) d_max_gen = d_start+2;*/ //infostream<<"Starting from "<getPlayerName(peer_id)< list; getFacePositions(list, d); core::list::Iterator li; for(li=list.begin(); li!=list.end(); li++) { v3s16 p = *li + center; /* Send throttling - Don't allow too many simultaneous transfers - EXCEPT when the blocks are very close Also, don't send blocks that are already flying. */ // Start with the usual maximum u16 max_simul_dynamic = max_simul_sends_usually; // If block is very close, allow full maximum if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D) max_simul_dynamic = max_simul_sends_setting; // Don't select too many blocks for sending if(num_blocks_selected >= max_simul_dynamic) { queue_is_full = true; goto queue_full_break; } // Don't send blocks that are currently being transferred if(m_blocks_sending.find(p) != NULL) continue; /* Do not go over-limit */ if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) continue; // If this is true, inexistent block will be made from scratch bool generate = d <= d_max_gen; { /*// Limit the generating area vertically to 2/3 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3) generate = false;*/ // Limit the send area vertically to 1/2 if(abs(p.Y - center.Y) > d_max / 2) continue; } #if 0 /* If block is far away, don't generate it unless it is near ground level. */ if(d >= 4) { #if 1 // Block center y in nodes f32 y = (f32)(p.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE/2); // Don't generate if it's very high or very low if(y < -64 || y > 64) generate = false; #endif #if 0 v2s16 p2d_nodes_center( MAP_BLOCKSIZE*p.X, MAP_BLOCKSIZE*p.Z); // Get ground height in nodes s16 gh = server->m_env->getServerMap().findGroundLevel( p2d_nodes_center); // If differs a lot, don't generate if(fabs(gh - y) > MAP_BLOCKSIZE*2) generate = false; // Actually, don't even send it //continue; #endif } #endif //infostream<<"d="<m_env->getMap().getBlockNoCreateNoEx(p); bool surely_not_found_on_disk = false; bool block_is_invalid = false; if(block != NULL) { // Reset usage timer, this block will be of use in the future. block->resetUsageTimer(); // Block is dummy if data doesn't exist. // It means it has been not found from disk and not generated if(block->isDummy()) { surely_not_found_on_disk = true; } // Block is valid if lighting is up-to-date and data exists if(block->isValid() == false) { block_is_invalid = true; } /*if(block->isFullyGenerated() == false) { block_is_invalid = true; }*/ #if 0 v2s16 p2d(p.X, p.Z); ServerMap *map = (ServerMap*)(&server->m_env->getMap()); v2s16 chunkpos = map->sector_to_chunk(p2d); if(map->chunkNonVolatile(chunkpos) == false) block_is_invalid = true; #endif if(block->isGenerated() == false) block_is_invalid = true; #if 1 /* If block is not close, don't send it unless it is near ground level. Block is near ground level if night-time mesh differs from day-time mesh. */ if(d >= 4) { if(block->getDayNightDiff() == false) continue; } #endif } /* If block has been marked to not exist on disk (dummy) and generating new ones is not wanted, skip block. */ if(generate == false && surely_not_found_on_disk == true) { // get next one. continue; } /* Add inexistent block to emerge queue. */ if(block == NULL || surely_not_found_on_disk || block_is_invalid) { //TODO: Get value from somewhere // Allow only one block in emerge queue //if(server->m_emerge_queue.peerItemCount(peer_id) < 1) // Allow two blocks in queue per client //if(server->m_emerge_queue.peerItemCount(peer_id) < 2) u32 max_emerge = 5; // Make it more responsive when needing to generate stuff if(surely_not_found_on_disk) max_emerge = 1; if(server->m_emerge_queue.peerItemCount(peer_id) < max_emerge) { //infostream<<"Adding block to emerge queue"<m_emerge_queue.addBlock(peer_id, p, flags); server->m_emergethread.trigger(); if(nearest_emerged_d == -1) nearest_emerged_d = d; } else { if(nearest_emergefull_d == -1) nearest_emergefull_d = d; } // get next one. continue; } if(nearest_sent_d == -1) nearest_sent_d = d; /* Add block to send queue */ /*errorstream<<"sending from d="<getPlayerName(peer_id)< g_settings->getS16("max_block_send_distance")){ new_nearest_unsent_d = 0; m_nothing_to_send_pause_timer = 2.0; /*infostream<<"GetNextBlocks(): d wrapped around for " <getPlayerName(peer_id) <<"; setting to 0 and pausing"< &blocks) { m_nearest_unsent_d = 0; for(core::map::Iterator i = blocks.getIterator(); i.atEnd()==false; i++) { v3s16 p = i.getNode()->getKey(); if(m_blocks_sending.find(p) != NULL) m_blocks_sending.remove(p); if(m_blocks_sent.find(p) != NULL) m_blocks_sent.remove(p); } } /* PlayerInfo */ PlayerInfo::PlayerInfo() { name[0] = 0; avg_rtt = 0; } void PlayerInfo::PrintLine(std::ostream *s) { (*s)<::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::Iterator i = m_modspaths.begin(); i != m_modspaths.end(); i++){ std::string modspath = *i; infostream<<"- mods: "<::Iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; infostream<::Iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; std::string scriptpath = mod.path + DIR_DELIM + "init.lua"; infostream<<" ["<updateAliases(m_itemdef); // Initialize Environment m_env = new ServerEnvironment(new ServerMap(path_world, this), m_lua, this, this); // Give environment reference to scripting api scriptapi_add_environment(m_lua, m_env); // Register us to receive map edit events m_env->getMap().addEventReceiver(this); // If file exists, load environment metadata if(fs::PathExists(m_path_world+DIR_DELIM+"env_meta.txt")) { infostream<<"Server: Loading environment metadata"<loadMeta(m_path_world); } // Load players infostream<<"Server: Loading players"<deSerializePlayers(m_path_world); /* Add some test ActiveBlockModifiers to environment */ add_legacy_abms(m_env, m_nodedef); } Server::~Server() { infostream<<"Server destructing"<::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { // Get client and check that it is valid RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; try{ SendChatMessage(client->peer_id, line); } catch(con::PeerNotFoundException &e) {} } } { JMutexAutoLock envlock(m_env_mutex); /* Save players */ infostream<<"Server: Saving players"<serializePlayers(m_path_world); /* Save environment metadata */ infostream<<"Server: Saving environment metadata"<saveMeta(m_path_world); } /* Stop threads */ stop(); /* Delete clients */ { JMutexAutoLock clientslock(m_con_mutex); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { /*// Delete player // NOTE: These are removed by env destructor { u16 peer_id = i.getNode()->getKey(); JMutexAutoLock envlock(m_env_mutex); m_env->removePlayer(peer_id); }*/ // Delete client delete i.getNode()->getValue(); } } // Delete things in the reverse order of creation delete m_env; delete m_event; delete m_itemdef; delete m_nodedef; delete m_craftdef; // Deinitialize scripting infostream<<"Server: Deinitializing scripting"<__| \\___ >____ > |__| "< 2.0) dtime = 2.0; { 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() { DSTACK(__FUNCTION_NAME); g_profiler->add("Server::AsyncRunStep (num)", 1); float dtime; { JMutexAutoLock lock1(m_step_dtime_mutex); dtime = m_step_dtime; } { // Send blocks to clients SendBlocks(dtime); } if(dtime < 0.001) return; g_profiler->add("Server::AsyncRunStep with dtime (num)", 1); //infostream<<"Server steps "<setTimeOfDaySpeed(g_settings->getFloat("time_speed")); /* Send to clients at constant intervals */ m_time_of_day_send_timer -= dtime; if(m_time_of_day_send_timer < 0.0) { m_time_of_day_send_timer = g_settings->getFloat("time_send_interval"); //JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); SharedBuffer data = makePacket_TOCLIENT_TIME_OF_DAY( m_env->getTimeOfDay(), g_settings->getFloat("time_speed")); // Send as reliable m_con.Send(client->peer_id, 0, data, true); } } } { JMutexAutoLock lock(m_env_mutex); // Step environment ScopeProfiler sp(g_profiler, "SEnv step"); ScopeProfiler sp2(g_profiler, "SEnv step avg", SPT_AVG); m_env->step(dtime); } const float map_timer_and_unload_dtime = 2.92; if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { JMutexAutoLock lock(m_env_mutex); // Run Map's timers and unload unused data ScopeProfiler sp(g_profiler, "Server: map timer and unload"); m_env->getMap().timerUpdate(map_timer_and_unload_dtime, g_settings->getFloat("server_unload_unused_data_timeout")); } /* Do background stuff */ /* Handle players */ { JMutexAutoLock lock(m_env_mutex); JMutexAutoLock lock2(m_con_mutex); ScopeProfiler sp(g_profiler, "Server: handle players"); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); PlayerSAO *playersao = getPlayerSAO(client->peer_id); if(playersao == NULL) continue; /* Handle player HPs (die if hp=0) */ if(playersao->getHP() == 0 && playersao->m_hp_not_sent) DiePlayer(client->peer_id); /* Send player inventories and HPs if necessary */ if(playersao->m_teleported){ SendMovePlayer(client->peer_id); playersao->m_teleported = false; } if(playersao->m_inventory_not_sent){ UpdateCrafting(client->peer_id); SendInventory(client->peer_id); } if(playersao->m_hp_not_sent){ SendPlayerHP(client->peer_id); } } } /* Transform liquids */ m_liquid_transform_timer += dtime; if(m_liquid_transform_timer >= 1.00) { m_liquid_transform_timer -= 1.00; JMutexAutoLock lock(m_env_mutex); ScopeProfiler sp(g_profiler, "Server: liquid transform"); core::map modified_blocks; m_env->getMap().transformLiquids(modified_blocks); #if 0 /* Update lighting */ core::map lighting_modified_blocks; ServerMap &map = ((ServerMap&)m_env->getMap()); map.updateLighting(modified_blocks, lighting_modified_blocks); // Add blocks modified by lighting to modified_blocks for(core::map::Iterator i = lighting_modified_blocks.getIterator(); i.atEnd() == false; i++) { MapBlock *block = i.getNode()->getValue(); modified_blocks.insert(block->getPos(), block); } #endif /* Set the modified blocks unsent for all the clients */ JMutexAutoLock lock2(m_con_mutex); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); if(modified_blocks.size() > 0) { // Remove block from sent history client->SetBlocksNotSent(modified_blocks); } } } // Periodically print some info { float &counter = m_print_info_timer; counter += dtime; if(counter >= 30.0) { counter = 0.0; JMutexAutoLock lock2(m_con_mutex); if(m_clients.size() != 0) infostream<<"Players:"<::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { //u16 peer_id = i.getNode()->getKey(); RemoteClient *client = i.getNode()->getValue(); Player *player = m_env->getPlayer(client->peer_id); if(player==NULL) continue; infostream<<"* "<getName()<<"\t"; client->PrintInfo(infostream); } } } //if(g_settings->getBool("enable_experimental")) { /* Check added and deleted active objects */ { //infostream<<"Server: Checking added and deleted active objects"<getS16("active_object_send_range_blocks"); radius *= MAP_BLOCKSIZE; for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); // If definitions and textures have not been sent, don't // send objects either if(!client->definitions_sent) continue; Player *player = m_env->getPlayer(client->peer_id); if(player==NULL) { // This can happen if the client timeouts somehow /*infostream<<"WARNING: "<<__FUNCTION_NAME<<": Client " <peer_id <<" has no associated player"<getPosition(), BS); core::map removed_objects; core::map added_objects; m_env->getRemovedActiveObjects(pos, radius, client->m_known_objects, removed_objects); m_env->getAddedActiveObjects(pos, radius, client->m_known_objects, added_objects); // Ignore if nothing happened if(removed_objects.size() == 0 && added_objects.size() == 0) { //infostream<<"active objects: none changed"<::Iterator i = removed_objects.getIterator(); i.atEnd()==false; i++) { // Get object u16 id = i.getNode()->getKey(); ServerActiveObject* obj = m_env->getActiveObject(id); // Add to data buffer for sending writeU16((u8*)buf, i.getNode()->getKey()); data_buffer.append(buf, 2); // Remove from known objects client->m_known_objects.remove(i.getNode()->getKey()); if(obj && obj->m_known_by_count > 0) obj->m_known_by_count--; } // Handle added objects writeU16((u8*)buf, added_objects.size()); data_buffer.append(buf, 2); for(core::map::Iterator i = added_objects.getIterator(); i.atEnd()==false; i++) { // Get object u16 id = i.getNode()->getKey(); ServerActiveObject* obj = m_env->getActiveObject(id); // Get object type u8 type = ACTIVEOBJECT_TYPE_INVALID; if(obj == NULL) infostream<<"WARNING: "<<__FUNCTION_NAME <<": NULL object"<getSendType(); // Add to data buffer for sending writeU16((u8*)buf, id); data_buffer.append(buf, 2); writeU8((u8*)buf, type); data_buffer.append(buf, 1); if(obj) data_buffer.append(serializeLongString( obj->getClientInitializationData())); else data_buffer.append(serializeLongString("")); // Add to known objects client->m_known_objects.insert(i.getNode()->getKey(), false); if(obj) obj->m_known_by_count++; } // Send packet SharedBuffer reply(2 + data_buffer.size()); writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD); memcpy((char*)&reply[2], data_buffer.c_str(), data_buffer.size()); // Send as reliable m_con.Send(client->peer_id, 0, reply, true); verbosestream<<"Server: Sent object remove/add: " < all_known_objects; for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); // Go through all known objects of client for(core::map::Iterator i = client->m_known_objects.getIterator(); i.atEnd()==false; i++) { u16 id = i.getNode()->getKey(); all_known_objects[id] = true; } } m_env->setKnownActiveObjects(whatever); #endif } /* Send object messages */ { JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); ScopeProfiler sp(g_profiler, "Server: sending object messages"); // Key = object id // Value = data sent by object core::map* > buffered_messages; // Get active object messages from environment for(;;) { ActiveObjectMessage aom = m_env->getActiveObjectMessage(); if(aom.id == 0) break; core::list* message_list = NULL; core::map* >::Node *n; n = buffered_messages.find(aom.id); if(n == NULL) { message_list = new core::list; buffered_messages.insert(aom.id, message_list); } else { message_list = n->getValue(); } message_list->push_back(aom); } // Route data to every client for(core::map::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); std::string reliable_data; std::string unreliable_data; // Go through all objects in message buffer for(core::map* >::Iterator j = buffered_messages.getIterator(); j.atEnd()==false; j++) { // If object is not known by client, skip it u16 id = j.getNode()->getKey(); if(client->m_known_objects.find(id) == NULL) continue; // Get message list of object core::list* list = j.getNode()->getValue(); // Go through every message for(core::list::Iterator k = list->begin(); k != list->end(); k++) { // Compose the full new data with header ActiveObjectMessage aom = *k; std::string new_data; // Add object id char buf[2]; writeU16((u8*)&buf[0], aom.id); new_data.append(buf, 2); // Add data new_data += serializeString(aom.datastring); // Add data to buffer if(aom.reliable) reliable_data += new_data; else unreliable_data += new_data; } } /* reliable_data and unreliable_data are now ready. Send them. */ if(reliable_data.size() > 0) { SharedBuffer reply(2 + reliable_data.size()); writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES); memcpy((char*)&reply[2], reliable_data.c_str(), reliable_data.size()); // Send as reliable m_con.Send(client->peer_id, 0, reply, true); } if(unreliable_data.size() > 0) { SharedBuffer reply(2 + unreliable_data.size()); writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES); memcpy((char*)&reply[2], unreliable_data.c_str(), unreliable_data.size()); // Send as unreliable m_con.Send(client->peer_id, 0, reply, false); } /*if(reliable_data.size() > 0 || unreliable_data.size() > 0) { infostream<<"Server: Size of object message data: " <<"reliable: "<* >::Iterator i = buffered_messages.getIterator(); i.atEnd()==false; i++) { delete i.getNode()->getValue(); } } } // enable_experimental /* Send queued-for-sending map edit events. */ { // We will be accessing the environment and the connection JMutexAutoLock lock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); // Don't send too many at a time //u32 count = 0; // Single change sending is disabled if queue size is not small bool disable_single_change_sending = false; if(m_unsent_map_edit_queue.size() >= 4) disable_single_change_sending = true; int event_count = m_unsent_map_edit_queue.size(); // We'll log the amount of each Profiler prof; while(m_unsent_map_edit_queue.size() != 0) { MapEditEvent* event = m_unsent_map_edit_queue.pop_front(); // Players far away from the change are stored here. // Instead of sending the changes, MapBlocks are set not sent // for them. core::list far_players; if(event->type == MEET_ADDNODE) { //infostream<<"Server: MEET_ADDNODE"<p, event->n, event->already_known_by_peer, &far_players, 5); else sendAddNode(event->p, event->n, event->already_known_by_peer, &far_players, 30); } else if(event->type == MEET_REMOVENODE) { //infostream<<"Server: MEET_REMOVENODE"<p, event->already_known_by_peer, &far_players, 5); else sendRemoveNode(event->p, event->already_known_by_peer, &far_players, 30); } else if(event->type == MEET_BLOCK_NODE_METADATA_CHANGED) { infostream<<"Server: MEET_BLOCK_NODE_METADATA_CHANGED"<p); } else if(event->type == MEET_OTHER) { infostream<<"Server: MEET_OTHER"<::Iterator i = event->modified_blocks.getIterator(); i.atEnd()==false; i++) { v3s16 p = i.getNode()->getKey(); setBlockNotSent(p); } } else { prof.add("unknown", 1); infostream<<"WARNING: Server: Unknown MapEditEvent " <<((u32)event->type)< 0) { // Convert list format to that wanted by SetBlocksNotSent core::map modified_blocks2; for(core::map::Iterator i = event->modified_blocks.getIterator(); i.atEnd()==false; i++) { v3s16 p = i.getNode()->getKey(); modified_blocks2.insert(p, m_env->getMap().getBlockNoCreateNoEx(p)); } // Set blocks not sent for(core::list::Iterator i = far_players.begin(); i != far_players.end(); i++) { u16 peer_id = *i; RemoteClient *client = getClient(peer_id); if(client==NULL) continue; client->SetBlocksNotSent(modified_blocks2); } } delete event; /*// Don't send too many at a time count++; if(count >= 1 && m_unsent_map_edit_queue.size() < 100) break;*/ } if(event_count >= 5){ infostream<<"Server: MapEditEvents:"<= 2.0) { counter = 0.0; m_emergethread.trigger(); } } // Save map, players and auth stuff { float &counter = m_savemap_timer; counter += dtime; if(counter >= g_settings->getFloat("server_map_save_interval")) { counter = 0.0; JMutexAutoLock lock(m_env_mutex); ScopeProfiler sp(g_profiler, "Server: saving stuff"); //Ban stuff if(m_banmanager.isModified()) m_banmanager.save(); // Save changed parts of map m_env->getMap().save(MOD_STATE_WRITE_NEEDED); // Save players m_env->serializePlayers(m_path_world); // Save environment metadata m_env->saveMeta(m_path_world); } } } void Server::Receive() { DSTACK(__FUNCTION_NAME); SharedBuffer data; u16 peer_id; u32 datasize; try{ { JMutexAutoLock conlock(m_con_mutex); datasize = m_con.Receive(peer_id, data); } // This has to be called so that the client list gets synced // with the peer list of the connection handlePeerChanges(); ProcessData(*data, datasize, peer_id); } catch(con::InvalidIncomingDataException &e) { infostream<<"Server::Receive(): " "InvalidIncomingDataException: what()=" <removePlayer(peer_id);*/ } } void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) { DSTACK(__FUNCTION_NAME); // Environment is locked first. JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); ScopeProfiler sp(g_profiler, "Server::ProcessData"); 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(addr_s)){ infostream<<"Server: A banned client tried to connect from " <serialization_version; try { if(datasize < 2) return; ToServerCommand command = (ToServerCommand)readU16(&data[0]); if(command == TOSERVER_INIT) { // [0] u16 TOSERVER_INIT // [2] u8 SER_FMT_VER_HIGHEST // [3] u8[20] player_name // [23] u8[28] password <--- can be sent without this, from old versions if(datasize < 2+1+PLAYERNAME_SIZE) return; verbosestream<<"Server: Got TOSERVER_INIT from " <serialization_version = deployed; getClient(peer_id)->pending_serialization_version = deployed; if(deployed == SER_FMT_VER_INVALID) { actionstream<<"Server: A mismatched client tried to connect from " <= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2) { net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE]); } getClient(peer_id)->net_proto_version = net_proto_version; if(net_proto_version == 0) { actionstream<<"Server: An old tried to connect from "<getBool("strict_protocol_version_checking")) { if(net_proto_version != PROTOCOL_VERSION) { actionstream<<"Server: A mismatched client tried to connect" <<" from "<get("default_password")); std::string use_password = translatePassword(playername, raw_default_password); // If default_password is empty, allow any initial password if (raw_default_password.length() == 0) use_password = password; scriptapi_create_auth(m_lua, playername, use_password); } has_auth = scriptapi_get_auth(m_lua, playername, &checkpwd, NULL); if(!has_auth){ SendAccessDenied(m_con, peer_id, L"Not allowed to login"); return; } if(password != checkpwd){ infostream<<"Server: peer_id="< 1){ infostream<<"Server: Not allowing another client to connect in" <<" simple singleplayer mode"<= g_settings->getU16("max_users") && !checkPriv(playername, "server") && !checkPriv(playername, "ban") && !checkPriv(playername, "privs") && !checkPriv(playername, "password") && playername != g_settings->get("name")) { actionstream<<"Server: "< reply(2+1+6+8); writeU16(&reply[0], TOCLIENT_INIT); writeU8(&reply[2], deployed); writeV3S16(&reply[2+1], floatToInt(playersao->getPlayer()->getPosition()+v3f(0,BS/2,0), BS)); writeU64(&reply[2+1+6], m_env->getServerMap().getSeed()); // Send as reliable m_con.Send(peer_id, 0, reply, true); } /* Send complete position information */ SendMovePlayer(peer_id); return; } if(command == TOSERVER_INIT2) { verbosestream<<"Server: Got TOSERVER_INIT2 from " <serialization_version = getClient(peer_id)->pending_serialization_version; /* Send some initialization data */ infostream<<"Server: Sending content to " <getPlayer(peer_id); // Send HP SendPlayerHP(peer_id); // Show death screen if necessary if(player->hp == 0) SendDeathscreen(m_con, peer_id, false, v3f(0,0,0)); // Send time of day { SharedBuffer data = makePacket_TOCLIENT_TIME_OF_DAY( m_env->getTimeOfDay(), g_settings->getFloat("time_speed")); m_con.Send(peer_id, 0, data, true); } // Note things in chat if not in simple singleplayer mode if(!m_simple_singleplayer_mode) { // Send information about server to player in chat SendChatMessage(peer_id, getStatusString()); // 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 if(getClient(peer_id)->net_proto_version < PROTOCOL_VERSION) { SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT IS OLD AND MAY WORK PROPERLY WITH THIS SERVER"); } /* Print out action */ { std::ostringstream os(std::ios_base::binary); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; // Get player Player *player = m_env->getPlayer(client->peer_id); if(!player) continue; // Get name of player os<getName()<<" "; } actionstream<getName()<<" joins game. List of players: " <GotBlock(p); } } else if(command == TOSERVER_DELETEDBLOCKS) { if(datasize < 2+1) return; /* [0] u16 command [2] u8 count [3] v3s16 pos_0 [3+6] v3s16 pos_1 ... */ u16 count = data[2]; for(u16 i=0; iSetBlockNotSent(p); } } else if(command == TOSERVER_CLICK_OBJECT) { infostream<<"Server: CLICK_OBJECT not supported anymore"<getName(), "interact")) return; /* u16 command v3s16 p u16 textlen textdata */ std::string datastring((char*)&data[2], datasize-2); std::istringstream is(datastring, std::ios_base::binary); u8 buf[6]; // Read stuff is.read((char*)buf, 6); v3s16 p = readV3S16(buf); is.read((char*)buf, 2); u16 textlen = readU16(buf); std::string text; for(u16 i=0; igetMap().getNodeMetadata(p); if(!meta) return; meta->setText(text); actionstream<getName()<<" writes \""<getMap().getBlockNoCreateNoEx(blockpos); if(block) { block->raiseModified(MOD_STATE_WRITE_NEEDED, "sign node text"); } setBlockNotSent(blockpos); } else if(command == TOSERVER_INVENTORY_ACTION) { // Strip command and create a stream std::string datastring((char*)&data[2], datasize-2); verbosestream<<"TOSERVER_INVENTORY_ACTION: data="<getType() == IACTION_MOVE) { IMoveAction *ma = (IMoveAction*)a; ma->from_inv.applyCurrentPlayer(player->getName()); ma->to_inv.applyCurrentPlayer(player->getName()); setInventoryModified(ma->from_inv); setInventoryModified(ma->to_inv); bool from_inv_is_current_player = (ma->from_inv.type == InventoryLocation::PLAYER) && (ma->from_inv.name == player->getName()); bool to_inv_is_current_player = (ma->to_inv.type == InventoryLocation::PLAYER) && (ma->to_inv.name == player->getName()); /* Disable moving items out of craftpreview */ if(ma->from_list == "craftpreview") { infostream<<"Ignoring IMoveAction from " <<(ma->from_inv.dump())<<":"<from_list <<" to "<<(ma->to_inv.dump())<<":"<to_list <<" because src is "<from_list<to_list == "craftpreview" || ma->to_list == "craftresult") { infostream<<"Ignoring IMoveAction from " <<(ma->from_inv.dump())<<":"<from_list <<" to "<<(ma->to_inv.dump())<<":"<to_list <<" because dst is "<to_list<getName(), "interact") && (!from_inv_is_current_player || !to_inv_is_current_player)) { infostream<<"Cannot move outside of player's inventory: " <<"No interact privilege"<getName(), "server")) { std::string owner_from = getInventoryOwner(ma->from_inv); if(owner_from != "" && owner_from != player->getName()) { infostream<<"WARNING: "<getName() <<" tried to access an inventory that" <<" belongs to "<to_inv); if(owner_to != "" && owner_to != player->getName()) { infostream<<"WARNING: "<getName() <<" tried to access an inventory that" <<" belongs to "<getType() == IACTION_DROP) { IDropAction *da = (IDropAction*)a; da->from_inv.applyCurrentPlayer(player->getName()); setInventoryModified(da->from_inv); // Disallow dropping items if not allowed to interact if(!checkPriv(player->getName(), "interact")) { delete a; return; } // If player is not an admin, check for ownership else if(!checkPriv(player->getName(), "server")) { std::string owner_from = getInventoryOwner(da->from_inv); if(owner_from != "" && owner_from != player->getName()) { infostream<<"WARNING: "<getName() <<" tried to access an inventory that" <<" belongs to "<getType() == IACTION_CRAFT) { ICraftAction *ca = (ICraftAction*)a; ca->craft_inv.applyCurrentPlayer(player->getName()); setInventoryModified(ca->craft_inv); //bool craft_inv_is_current_player = // (ca->craft_inv.type == InventoryLocation::PLAYER) && // (ca->craft_inv.name == player->getName()); // Disallow crafting if not allowed to interact if(!checkPriv(player->getName(), "interact")) { infostream<<"Cannot craft: " <<"No interact privilege"<getName(), "server")) { std::string owner_craft = getInventoryOwner(ca->craft_inv); if(owner_craft != "" && owner_craft != player->getName()) { infostream<<"WARNING: "<getName() <<" tried to access an inventory that" <<" belongs to "<apply(this, playersao, this); // Eat the action delete a; } else if(command == TOSERVER_CHAT_MESSAGE) { /* u16 command u16 length wstring message */ u8 buf[6]; std::string datastring((char*)&data[2], datasize-2); std::istringstream is(datastring, std::ios_base::binary); // Read stuff is.read((char*)buf, 2); u16 len = readU16(buf); std::wstring message; for(u16 i=0; igetName()); // Run script hook bool ate = scriptapi_on_chat_message(m_lua, player->getName(), wide_to_narrow(message)); // If script ate the message, don't proceed if(ate) return; // Line to send to players std::wstring line; // Whether to send to the player that sent the line bool send_to_sender = false; // Whether to send to other players bool send_to_others = false; // Parse commands if(message[0] == L'/') { size_t strip_size = 1; if (message[1] == L'#') // support old-style commans ++strip_size; message = message.substr(strip_size); WStrfnd f1(message); f1.next(L" "); // Skip over /#whatever std::wstring paramstring = f1.next(L""); ServerCommandContext *ctx = new ServerCommandContext( str_split(message, L' '), paramstring, this, m_env, player); std::wstring reply(processServerCommand(ctx)); send_to_sender = ctx->flags & SEND_TO_SENDER; send_to_others = ctx->flags & SEND_TO_OTHERS; if (ctx->flags & SEND_NO_PREFIX) line += reply; else line += L"Server: " + reply; delete ctx; } else { if(checkPriv(player->getName(), "shout")){ line += L"<"; line += name; line += L"> "; line += message; send_to_others = true; } else { line += L"Server: You are not allowed to shout"; send_to_sender = true; } } if(line != L"") { if(send_to_others) actionstream<<"CHAT: "<::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { // Get client and check that it is valid RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; // Filter recipient bool sender_selected = (peer_id == client->peer_id); if(sender_selected == true && send_to_sender == false) continue; if(sender_selected == false && send_to_others == false) continue; SendChatMessage(client->peer_id, line); } } } else if(command == TOSERVER_DAMAGE) { std::string datastring((char*)&data[2], datasize-2); std::istringstream is(datastring, std::ios_base::binary); u8 damage = readU8(is); actionstream<getName()<<" damaged by " <<(int)damage<<" hp at "<getPosition()/BS) <setHP(playersao->getHP() - damage); if(playersao->getHP() == 0 && playersao->m_hp_not_sent) DiePlayer(peer_id); if(playersao->m_hp_not_sent) SendPlayerHP(peer_id); } else if(command == TOSERVER_PASSWORD) { /* [0] u16 TOSERVER_PASSWORD [2] u8[28] old password [30] u8[28] new password */ if(datasize != 2+PASSWORD_SIZE*2) return; /*char password[PASSWORD_SIZE]; for(u32 i=0; igetName(); std::string checkpwd; scriptapi_get_auth(m_lua, playername, &checkpwd, NULL); if(oldpwd != checkpwd) { infostream<<"Server: invalid old password"<getName()<<" changes password"<getName()<<" tries to change password but " <<"it fails"<setWieldIndex(item); } else if(command == TOSERVER_RESPAWN) { if(player->hp != 0) return; RespawnPlayer(peer_id); actionstream<getName()<<" respawns at " <getPosition()/BS)< tosend; u16 numfiles = readU16(is); infostream<<"Sending "<definitions_sent = true; } else if(command == TOSERVER_INTERACT) { std::string datastring((char*)&data[2], datasize-2); std::istringstream is(datastring, std::ios_base::binary); /* [0] u16 command [2] u8 action [3] u16 item [5] u32 length of the next item [9] serialized PointedThing actions: 0: start digging (from undersurface) or use 1: stop digging (all parameters ignored) 2: digging completed 3: place block or item (to abovesurface) 4: use item */ u8 action = readU8(is); u16 item_i = readU16(is); std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); PointedThing pointed; pointed.deSerialize(tmp_is); verbosestream<<"TOSERVER_INTERACT: action="<<(int)action<<", item=" <getName() <<" tried to interact, but is dead!"<getLastGoodPosition(); // Update wielded item playersao->setWieldIndex(item_i); // Get pointed to node (undefined if not POINTEDTYPE_NODE) v3s16 p_under = pointed.node_undersurface; v3s16 p_above = pointed.node_abovesurface; // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) ServerActiveObject *pointed_object = NULL; if(pointed.type == POINTEDTHING_OBJECT) { pointed_object = m_env->getActiveObject(pointed.object_id); if(pointed_object == NULL) { verbosestream<<"TOSERVER_INTERACT: " "pointed object is NULL"<getBasePosition(); pointed_pos_above = pointed_pos_under; } /* Check that target is reasonably close (only when digging or placing things) */ if(action == 0 || action == 2 || action == 3) { float d = player_pos.getDistanceFrom(pointed_pos_under); float max_d = BS * 14; // Just some large enough value if(d > max_d){ actionstream<<"Player "<getName() <<" tried to access "<SetBlockNotSent(blockpos); // Do nothing else return; } } /* Make sure the player is allowed to do it */ if(!checkPriv(player->getName(), "interact")) { infostream<<"Ignoring interaction from player "<getName() <<" (no interact privilege)"<getMap().getNode(p_under); } catch(InvalidPositionException &e) { infostream<<"Server: Not punching: Node not found." <<" Adding block to emerge queue." <m_removed) return; actionstream<getName()<<" punches object " <getDescription()<getWieldedItem(); ToolCapabilities toolcap = punchitem.getToolCapabilities(m_itemdef); v3f dir = (pointed_object->getBasePosition() - (player->getPosition() + player->getEyeOffset()) ).normalize(); float time_from_last_punch = playersao->resetTimeFromLastPunch(); pointed_object->punch(dir, &toolcap, playersao, time_from_last_punch); } } // action == 0 /* 1: stop digging */ else if(action == 1) { } // action == 1 /* 2: Digging completed */ else if(action == 2) { // Only complete digging of nodes if(pointed.type == POINTEDTHING_NODE) { MapNode n(CONTENT_IGNORE); try { n = m_env->getMap().getNode(p_under); } catch(InvalidPositionException &e) { infostream<<"Server: Not finishing digging: Node not found." <<" Adding block to emerge queue." <getWieldedItem(); // Reset build time counter if(pointed.type == POINTEDTHING_NODE && item.getDefinition(m_itemdef).type == ITEM_NODE) getClient(peer_id)->m_time_from_building = 0.0; if(pointed.type == POINTEDTHING_OBJECT) { // Right click object // Skip if object has been removed if(pointed_object->m_removed) return; actionstream<getName()<<" right-clicks object " <getDescription()<rightClick(playersao); } else if(scriptapi_item_on_place(m_lua, item, playersao, pointed)) { // Placement was handled in lua // Apply returned ItemStack if(g_settings->getBool("creative_mode") == false) playersao->setWieldedItem(item); } } // action == 3 /* 4: use */ else if(action == 4) { ItemStack item = playersao->getWieldedItem(); actionstream<getName()<<" uses "<getBool("creative_mode") == false) playersao->setWieldedItem(item); } } // action == 4 /* Catch invalid actions */ else { infostream<<"WARNING: Server: Invalid action " <::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 " "unknown command "<getArea())) return; MapEditEvent *e = event->clone(); m_unsent_map_edit_queue.push_back(e); } Inventory* Server::getInventory(const InventoryLocation &loc) { switch(loc.type){ case InventoryLocation::UNDEFINED: {} break; case InventoryLocation::CURRENT_PLAYER: {} break; case InventoryLocation::PLAYER: { Player *player = m_env->getPlayer(loc.name.c_str()); if(!player) return NULL; PlayerSAO *playersao = player->getPlayerSAO(); if(!playersao) return NULL; return playersao->getInventory(); } break; case InventoryLocation::NODEMETA: { NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p); if(!meta) return NULL; return meta->getInventory(); } break; default: assert(0); } return NULL; } std::string Server::getInventoryOwner(const InventoryLocation &loc) { switch(loc.type){ case InventoryLocation::UNDEFINED: {} break; case InventoryLocation::CURRENT_PLAYER: {} break; case InventoryLocation::PLAYER: { return loc.name; } break; case InventoryLocation::NODEMETA: { NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p); if(!meta) return ""; return meta->getOwner(); } break; default: assert(0); } return ""; } void Server::setInventoryModified(const InventoryLocation &loc) { switch(loc.type){ case InventoryLocation::UNDEFINED: {} break; case InventoryLocation::PLAYER: { Player *player = m_env->getPlayer(loc.name.c_str()); if(!player) return; PlayerSAO *playersao = player->getPlayerSAO(); if(!playersao) return; playersao->m_inventory_not_sent = true; playersao->m_wielded_item_not_sent = true; } break; case InventoryLocation::NODEMETA: { v3s16 blockpos = getNodeBlockPos(loc.p); NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p); if(meta) meta->inventoryModified(); MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos); if(block) block->raiseModified(MOD_STATE_WRITE_NEEDED); setBlockNotSent(blockpos); } break; default: assert(0); } } core::list Server::getPlayerInfo() { DSTACK(__FUNCTION_NAME); JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); core::list list; core::list players = m_env->getPlayers(); core::list::Iterator i; for(i = players.begin(); i != players.end(); i++) { PlayerInfo info; Player *player = *i; try{ // Copy info from connection to info struct info.id = player->peer_id; info.address = m_con.GetPeerAddress(player->peer_id); info.avg_rtt = m_con.GetPeerAvgRTT(player->peer_id); } catch(con::PeerNotFoundException &e) { // Set dummy peer info info.id = 0; info.address = Address(0,0,0,0,0); info.avg_rtt = 0.0; } snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName()); info.position = player->getPosition(); list.push_back(info); } return list; } void Server::peerAdded(con::Peer *peer) { DSTACK(__FUNCTION_NAME); verbosestream<<"Server::peerAdded(): peer->id=" <id<id; c.timeout = false; m_peer_change_queue.push_back(c); } void Server::deletingPeer(con::Peer *peer, bool timeout) { DSTACK(__FUNCTION_NAME); verbosestream<<"Server::deletingPeer(): peer->id=" <id<<", timeout="<id; c.timeout = timeout; m_peer_change_queue.push_back(c); } /* Static send methods */ void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_HP); writeU8(os, hp); // Make data buffer std::string s = os.str(); SharedBuffer data((u8*)s.c_str(), s.size()); // Send as reliable con.Send(peer_id, 0, data, true); } void Server::SendAccessDenied(con::Connection &con, u16 peer_id, const std::wstring &reason) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_ACCESS_DENIED); os< data((u8*)s.c_str(), s.size()); // Send as reliable con.Send(peer_id, 0, data, true); } void Server::SendDeathscreen(con::Connection &con, u16 peer_id, bool set_camera_point_target, v3f camera_point_target) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_DEATHSCREEN); writeU8(os, set_camera_point_target); writeV3F1000(os, camera_point_target); // Make data buffer std::string s = os.str(); SharedBuffer data((u8*)s.c_str(), s.size()); // Send as reliable con.Send(peer_id, 0, data, true); } void Server::SendItemDef(con::Connection &con, u16 peer_id, IItemDefManager *itemdef) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); /* u16 command u32 length of the next item zlib-compressed serialized ItemDefManager */ writeU16(os, TOCLIENT_ITEMDEF); std::ostringstream tmp_os(std::ios::binary); itemdef->serialize(tmp_os); std::ostringstream tmp_os2(std::ios::binary); compressZlib(tmp_os.str(), tmp_os2); os<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) { float maxd = far_d_nodes*BS; v3f p_f = intToFloat(p, BS); // Create packet u32 replysize = 8; SharedBuffer reply(replysize); writeU16(&reply[0], TOCLIENT_REMOVENODE); writeS16(&reply[2], p.X); writeS16(&reply[4], p.Y); writeS16(&reply[6], p.Z); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { // Get client and check that it is valid RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; // Don't send if it's the same one if(client->peer_id == ignore_id) continue; if(far_players) { // Get player Player *player = m_env->getPlayer(client->peer_id); if(player) { // If player is far away, only set modified blocks not sent v3f player_pos = player->getPosition(); if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(client->peer_id); continue; } } } // Send as reliable m_con.Send(client->peer_id, 0, reply, true); } } void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id, core::list *far_players, float far_d_nodes) { float maxd = far_d_nodes*BS; v3f p_f = intToFloat(p, BS); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { // Get client and check that it is valid RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; // Don't send if it's the same one if(client->peer_id == ignore_id) continue; if(far_players) { // Get player Player *player = m_env->getPlayer(client->peer_id); if(player) { // If player is far away, only set modified blocks not sent v3f player_pos = player->getPosition(); if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(client->peer_id); continue; } } } // Create packet u32 replysize = 8 + MapNode::serializedLength(client->serialization_version); SharedBuffer reply(replysize); writeU16(&reply[0], TOCLIENT_ADDNODE); writeS16(&reply[2], p.X); writeS16(&reply[4], p.Y); writeS16(&reply[6], p.Z); n.serialize(&reply[8], client->serialization_version); // Send as reliable m_con.Send(client->peer_id, 0, reply, true); } } void Server::setBlockNotSent(v3s16 p) { for(core::map::Iterator i = m_clients.getIterator(); i.atEnd()==false; i++) { RemoteClient *client = i.getNode()->getValue(); client->SetBlockNotSent(p); } } void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver) { DSTACK(__FUNCTION_NAME); v3s16 p = block->getPos(); #if 0 // Analyze it a bit bool completely_air = true; for(s16 z0=0; z0getNodeNoEx(v3s16(x0,y0,z0)).d != CONTENT_AIR) { completely_air = false; x0 = y0 = z0 = MAP_BLOCKSIZE; // Break out } } // Print result infostream<<"Server: Sending block ("<serialize(os, ver, false); std::string s = os.str(); SharedBuffer blockdata((u8*)s.c_str(), s.size()); u32 replysize = 8 + blockdata.getSize(); SharedBuffer reply(replysize); writeU16(&reply[0], TOCLIENT_BLOCKDATA); writeS16(&reply[2], p.X); writeS16(&reply[4], p.Y); writeS16(&reply[6], p.Z); memcpy(&reply[8], *blockdata, blockdata.getSize()); /*infostream<<"Server: Sending block ("< queue; s32 total_sending = 0; { ScopeProfiler sp(g_profiler, "Server: selecting blocks for sending"); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); // If definitions and textures have not been sent, don't // send MapBlocks either if(!client->definitions_sent) continue; total_sending += client->SendingCount(); if(client->serialization_version == SER_FMT_VER_INVALID) continue; client->GetNextBlocks(this, dtime, queue); } } // Sort. // Lowest priority number comes first. // Lowest is most important. queue.sort(); for(u32 i=0; i= g_settings->getS32 ("max_simultaneous_block_sends_server_total")) break; PrioritySortedBlockTransfer q = queue[i]; MapBlock *block = NULL; try { block = m_env->getMap().getBlockNoCreate(q.pos); } catch(InvalidPositionException &e) { continue; } RemoteClient *client = getClient(q.peer_id); SendBlockNoLock(q.peer_id, block, client->serialization_version); client->SentBlock(q.pos); total_sending++; } } void Server::fillMediaCache() { DSTACK(__FUNCTION_NAME); infostream<<"Server: Calculating media file checksums"< paths; for(core::list::Iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; paths.push_back(mod.path + DIR_DELIM + "textures"); paths.push_back(mod.path + DIR_DELIM + "sounds"); paths.push_back(mod.path + DIR_DELIM + "media"); } // Collect media file information from paths into cache for(std::list::iterator i = paths.begin(); i != paths.end(); i++) { std::string mediapath = *i; std::vector dirlist = fs::GetDirListing(mediapath); for(u32 j=0; jm_media[filename] = MediaInfo(filepath, sha1_base64); verbosestream<<"Server: "< file_announcements; for(std::map::iterator i = m_media.begin(); i != m_media.end(); i++){ // Put in list file_announcements.push_back( SendableMediaAnnouncement(i->first, i->second.sha1_digest)); } // Make packet std::ostringstream os(std::ios_base::binary); /* u16 command u32 number of files for each texture { u16 length of name string name u16 length of sha1_digest string sha1_digest } */ writeU16(os, TOCLIENT_ANNOUNCE_MEDIA); writeU16(os, file_announcements.size()); for(core::list::Iterator j = file_announcements.begin(); j != file_announcements.end(); j++){ os<name); os<sha1_digest); } // Make data buffer std::string s = os.str(); SharedBuffer data((u8*)s.c_str(), s.size()); // Send as reliable m_con.Send(peer_id, 0, data, true); } struct SendableMedia { std::string name; std::string path; std::string data; Sendab output = "moreblocks:tar", recipe = "default:gravel", }) -- 2019-06-01 -- minetest.register_craft({ -- type = "cooking", -- cooktime = 45, -- 5*9 -- output = "default:stone 81", -- recipe = "moreblocks:cobble_condensed" -- }) -- minetest.register_craft({ -- type = "grinding", -- time = 27, -- 3*9 -- output = "default:cobble 81", -- recipe = "moreblocks:cobble_condensed" -- }) minetest.register_craft({ type = "shapeless", output = "moreblocks:copperpatina", recipe = {"bucket:bucket_water", "default:copperblock"}, replacements = { {"bucket:bucket_water", "bucket:bucket_empty"} } }) minetest.register_craft({ type = "shapeless", output = "moreblocks:copperpatina", recipe = {"bucket:bucket_river_water", "default:copperblock"}, replacements = { {"bucket:bucket_river_water", "bucket:bucket_empty"} } }) minetest.register_craft({ output = "default:copper_ingot 9", recipe = { {"moreblocks:copperpatina"}, } }) if minetest.settings:get_bool("moreblocks.circular_saw_crafting") ~= false then -- “If nil or true then” minetest.register_craft({ output = "moreblocks:circular_saw", recipe = { { "", "default:steel_ingot", "" },st = player->inventory.getList("craftpreview"); assert(plist); assert(plist->getSize() >= 1); plist->changeItem(0, preview); } RemoteClient* Server::getClient(u16 peer_id) { DSTACK(__FUNCTION_NAME); //JMutexAutoLock lock(m_con_mutex); core::map::Node *n; n = m_clients.find(peer_id); // A client should exist for all peers assert(n != NULL); return n->getValue(); } std::wstring Server::getStatusString() { std::wostringstream os(std::ios_base::binary); os<::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { // Get client and check that it is valid RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; // Get player Player *player = m_env->getPlayer(client->peer_id); // Get name of player std::wstring name = L"unknown"; if(player != NULL) name = narrow_to_wide(player->getName()); // Add name to information string os<getMap()))->isSavingEnabled() == false) os<get("motd") != "") os<get("motd")); return os.str(); } std::set Server::getPlayerEffectivePrivs(const std::string &name) { std::set privs; scriptapi_get_auth(m_lua, name, NULL, &privs); return privs; } bool Server::checkPriv(const std::string &name, const std::string &priv) { std::set privs = getPlayerEffectivePrivs(name); return (privs.count(priv) != 0); } void Server::reportPrivsModified(const std::string &name) { if(name == ""){ 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); reportPrivsModified(player->getName()); } } else { Player *player = m_env->getPlayer(name.c_str()); if(!player) return; SendPlayerPrivileges(player->peer_id); PlayerSAO *sao = player->getPlayerSAO(); if(!sao) return; sao->updatePrivileges( getPlayerEffectivePrivs(name), isSingleplayer()); } } // Saves g_settings to configpath given at initialization void Server::saveConfig() { if(m_path_config != "") g_settings->updateConfigFile(m_path_config.c_str()); } void Server::notifyPlayer(const char *name, const std::wstring msg) { Player *player = m_env->getPlayer(name); if(!player) return; SendChatMessage(player->peer_id, std::wstring(L"Server: -!- ")+msg); } void Server::notifyPlayers(const std::wstring msg) { BroadcastChatMessage(msg); } void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate) { u8 flags = 0; if(!allow_generate) flags |= BLOCK_EMERGE_FLAG_FROMDISK; m_emerge_queue.addBlock(PEER_ID_INEXISTENT, blockpos, flags); } // IGameDef interface // Under envlock IItemDefManager* Server::getItemDefManager() { return m_itemdef; } INodeDefManager* Server::getNodeDefManager() { return m_nodedef; } ICraftDefManager* Server::getCraftDefManager() { return m_craftdef; } ITextureSource* Server::getTextureSource() { return NULL; } 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() { return m_itemdef; } IWritableNodeDefManager* Server::getWritableNodeDefManager() { return m_nodedef; } IWritableCraftDefManager* Server::getWritableCraftDefManager() { return m_craftdef; } const ModSpec* Server::getModSpec(const std::string &modname) { for(core::list::Iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; if(mod.name == modname) return &mod; } return NULL; } std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; } v3f findSpawnPos(ServerMap &map) { //return v3f(50,50,50)*BS; v3s16 nodepos; #if 0 nodepos = v2s16(0,0); groundheight = 20; #endif #if 1 // Try to find a good place a few times for(s32 i=0; i<1000; i++) { s32 range = 1 + i; // We're going to try to throw the player to this position v2s16 nodepos2d = v2s16(-range + (myrand()%(range*2)), -range + (myrand()%(range*2))); //v2s16 sectorpos = getNodeSectorPos(nodepos2d); // Get ground height at point (fallbacks to heightmap function) s16 groundheight = map.findGroundLevel(nodepos2d); // Don't go underwater if(groundheight < WATER_LEVEL) { //infostream<<"-> Underwater"< WATER_LEVEL + 4) { //infostream<<"-> Underwater"<= 2){ is_good = true; nodepos.Y -= 1; break; } } nodepos.Y++; } if(is_good){ // Found a good place //infostream<<"Searched through "<(m_env->getPlayer(name)); // If player is already connected, cancel if(player != NULL && player->peer_id != 0) { infostream<<"emergePlayer(): Player already connected"<getPlayer(peer_id) != NULL) { infostream<<"emergePlayer(): Player with wrong name but same" " peer_id already exists"<updateName(name); /* Set player position */ infostream<<"Server: Finding spawn place for player \"" <getServerMap()); player->setPosition(pos); /* Add player to environment */ m_env->addPlayer(player); } /* Create a new player active object */ PlayerSAO *playersao = new PlayerSAO(m_env, player, peer_id, getPlayerEffectivePrivs(player->getName()), isSingleplayer()); /* Add object to environment */ m_env->addActiveObject(playersao); /* Run scripts */ if(newplayer) scriptapi_on_newplayer(m_lua, playersao); scriptapi_on_joinplayer(m_lua, playersao); /* Creative mode */ if(g_settings->getBool("creative_mode")) playersao->createCreativeInventory(); return playersao; } void Server::handlePeerChange(PeerChange &c) { JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); if(c.type == PEER_ADDED) { /* Add */ // Error check core::map::Node *n; n = m_clients.find(c.peer_id); // The client shouldn't already exist assert(n == NULL); // Create client RemoteClient *client = new RemoteClient(); client->peer_id = c.peer_id; m_clients.insert(client->peer_id, client); } // PEER_ADDED else if(c.type == PEER_REMOVED) { /* Delete */ // Error check core::map::Node *n; n = m_clients.find(c.peer_id); // The client should exist assert(n != NULL); /* Mark objects to be not known by the client */ RemoteClient *client = n->getValue(); // Handle objects for(core::map::Iterator i = client->m_known_objects.getIterator(); i.atEnd()==false; i++) { // Get object u16 id = i.getNode()->getKey(); ServerActiveObject* obj = m_env->getActiveObject(id); if(obj && obj->m_known_by_count > 0) 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++; } Player *player = m_env->getPlayer(c.peer_id); // Collect information about leaving in chat std::wstring message; { if(player != NULL) { std::wstring name = narrow_to_wide(player->getName()); message += L"*** "; message += name; message += L" left game"; if(c.timeout) message += L" (timed out)"; } } /* Run scripts and remove from environment */ { if(player != NULL) { PlayerSAO *playersao = player->getPlayerSAO(); assert(playersao); scriptapi_on_leaveplayer(m_lua, playersao); playersao->disconnected(); } } /* Print out action */ { if(player != NULL) { std::ostringstream os(std::ios_base::binary); for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); assert(client->peer_id == i.getNode()->getKey()); if(client->serialization_version == SER_FMT_VER_INVALID) continue; // Get player Player *player = m_env->getPlayer(client->peer_id); if(!player) continue; // Get name of player os<getName()<<" "; } actionstream<getName()<<" " <<(c.timeout?"times out.":"leaves game.") <<" List of players: " < 0) { PeerChange c = m_peer_change_queue.pop_front(); verbosestream<<"Server: Handling peer change: " <<"id="<getFloat("dedicated_server_step"); // This is kind of a hack but can be done like this // because server.step() is very light { ScopeProfiler sp(g_profiler, "dedicated server sleep"); sleep_ms((int)(steplen*1000.0)); } server.step(steplen); if(server.getShutdownRequested() || kill) { infostream<<"Dedicated server quitting"<getFloat("profiler_print_interval"); if(profiler_print_interval != 0) { if(m_profiler_interval.step(steplen, profiler_print_interval)) { infostream<<"Profiler:"<print(infostream); g_profiler->clear(); } } } }