/* 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 "clientserver.h" #include "map.h" #include "jmutexautolock.h" #include "main.h" #include "constants.h" #include "voxel.h" #include "materials.h" #include "mineral.h" #include "config.h" #include "servercommand.h" #include "filesys.h" #include "content_mapnode.h" #include "content_craft.h" #include "content_nodemeta.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 "tooldef.h" #include "craftdef.h" #include "mapgen.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; }; 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"<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); /* Do some post-generate stuff */ v3s16 minp = block->getPos()*MAP_BLOCKSIZE; v3s16 maxp = minp + v3s16(1,1,1)*(MAP_BLOCKSIZE-1); scriptapi_environment_on_generated(m_server->m_lua, minp, maxp); if(enable_mapgen_debug_info) infostream<<"EmergeThread: ended up with: " <m_ignore_map_edit_events); // Activate objects and stuff m_server->m_env->activateBlock(block, 3600); } } 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) return NULL; } 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->dayNightDiffed() == 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 = 25; // Make it more responsive when needing to generate stuff if(surely_not_found_on_disk) max_emerge = 5; 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"< &stepped_blocks ) { DSTACK(__FUNCTION_NAME); // Can't send anything without knowing version if(serialization_version == SER_FMT_VER_INVALID) { infostream<<"RemoteClient::SendObjectData(): Not sending, no version." < players = server->m_env->getPlayers(true); // Write player count u16 playercount = players.size(); writeU16(buf, playercount); os.write((char*)buf, 2); core::list::Iterator i; for(i = players.begin(); i != players.end(); i++) { Player *player = *i; v3f pf = player->getPosition(); v3f sf = player->getSpeed(); v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100); v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100); s32 pitch_i (player->getPitch() * 100); s32 yaw_i (player->getYaw() * 100); writeU16(buf, player->peer_id); os.write((char*)buf, 2); writeV3S32(buf, position_i); os.write((char*)buf, 12); writeV3S32(buf, speed_i); os.write((char*)buf, 12); writeS32(buf, pitch_i); os.write((char*)buf, 4); writeS32(buf, yaw_i); os.write((char*)buf, 4); } /* Get and write object data (dummy, for compatibility) */ // Write block count writeU16(buf, 0); os.write((char*)buf, 2); /* Send data */ //infostream<<"Server: Sending object data to "< data((u8*)s.c_str(), s.size()); // Send as unreliable server->m_con.Send(peer_id, 0, data, false); } void RemoteClient::GotBlock(v3s16 p) { if(m_blocks_sending.find(p) != NULL) m_blocks_sending.remove(p); else { /*infostream<<"RemoteClient::GotBlock(): Didn't find in" " m_blocks_sending"< &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)< getMods(core::list &modspaths) { core::list mods; for(core::list::Iterator i = modspaths.begin(); i != modspaths.end(); i++){ std::string modspath = *i; std::vector dirlist = fs::GetDirListing(modspath); for(u32 j=0; j mods = getMods(m_modspaths); for(core::list::Iterator i = mods.begin(); i != mods.end(); i++){ ModSpec mod = *i; infostream<<"Server: Loading mod \""<getMap().addEventReceiver(this); // If file exists, load environment metadata if(fs::PathExists(m_mapsavedir+DIR_DELIM+"env_meta.txt")) { infostream<<"Server: Loading environment metadata"<loadMeta(m_mapsavedir); } // Load players infostream<<"Server: Loading players"<deSerializePlayers(m_mapsavedir); } Server::~Server() { infostream<<"Server::~Server()"<::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_mapsavedir); /* Save environment metadata */ infostream<<"Server: Saving environment metadata"<saveMeta(m_mapsavedir); } /* 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 Environment delete m_env; delete m_toolmgr; delete m_nodedef; // Deinitialize scripting infostream<<"Server: Deinitializing scripting"< 2.0) dtime = 2.0; { JMutexAutoLock lock(m_step_dtime_mutex); m_step_dtime += dtime; } } 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; } { ScopeProfiler sp(g_profiler, "Server: sel and send blocks to clients"); // Send blocks to clients SendBlocks(dtime); } if(dtime < 0.001) return; g_profiler->add("Server::AsyncRunStep with dtime (num)", 1); //infostream<<"Server steps "<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 = "<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(); //Player *player = m_env->getPlayer(client->peer_id); SharedBuffer data = makePacket_TOCLIENT_TIME_OF_DAY( m_env->getTimeOfDay()); // 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 */ /* Check player movements NOTE: Actually the server should handle player physics like the client does and compare player's position to what is calculated on our side. This is required when eg. players fly due to an explosion. */ { JMutexAutoLock lock(m_env_mutex); JMutexAutoLock lock2(m_con_mutex); //float player_max_speed = BS * 4.0; // Normal speed float player_max_speed = BS * 20; // Fast speed float player_max_speed_up = BS * 20; player_max_speed *= 1.7; // Tolerance player_max_speed_up *= 1.7; for(core::map::Iterator i = m_clients.getIterator(); i.atEnd() == false; i++) { RemoteClient *client = i.getNode()->getValue(); ServerRemotePlayer *player = (ServerRemotePlayer*)m_env->getPlayer(client->peer_id); if(player==NULL) continue; player->m_last_good_position_age += dtime; if(player->m_last_good_position_age >= 2.0){ float age = player->m_last_good_position_age; v3f diff = (player->getPosition() - player->m_last_good_position); float d_vert = diff.Y; diff.Y = 0; float d_horiz = diff.getLength(); /*infostream<getName()<<"'s horizontal speed is " <<(d_horiz/age)<m_last_good_position = player->getPosition(); } else { actionstream<<"Player "<getName() <<" moved too fast; resetting position" <setPosition(player->m_last_good_position); SendMovePlayer(player); } player->m_last_good_position_age = 0; } } } /* 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(); 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"<getType(); // 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); infostream<<"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. */ { // 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; bool got_any_events = false; // We'll log the amount of each Profiler prof; while(m_unsent_map_edit_queue.size() != 0) { got_any_events = true; 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(got_any_events) { infostream<<"Server: MapEditEvents:"<= g_settings->getFloat("objectdata_interval")) { JMutexAutoLock lock1(m_env_mutex); JMutexAutoLock lock2(m_con_mutex); //ScopeProfiler sp(g_profiler, "Server: sending player positions"); SendObjectData(counter); counter = 0.0; } } /* Trigger emergethread (it somehow gets to a non-triggered but bysy state sometimes) */ { float &counter = m_emergethread_trigger_timer; counter += dtime; if(counter >= 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; ScopeProfiler sp(g_profiler, "Server: saving stuff"); // Auth stuff if(m_authmanager.isModified()) m_authmanager.save(); //Bann stuff if(m_banmanager.isModified()) m_banmanager.save(); // Map JMutexAutoLock lock(m_env_mutex); /*// Unload unused data (delete from memory) m_env->getMap().unloadUnusedData( g_settings->getFloat("server_unload_unused_sectors_timeout")); */ /*u32 deleted_count = m_env->getMap().unloadUnusedData( g_settings->getFloat("server_unload_unused_sectors_timeout")); */ // Save only changed parts m_env->getMap().save(true); /*if(deleted_count > 0) { infostream<<"Server: Unloaded "<serializePlayers(m_mapsavedir); // Save environment metadata m_env->saveMeta(m_mapsavedir); } } } 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); try{ Address address = m_con.GetPeerAddress(peer_id); // drop player if is ip is banned if(m_banmanager.isIpBanned(address.serializeString())){ SendAccessDenied(m_con, peer_id, L"Your ip is banned. Banned name was " +narrow_to_wide(m_banmanager.getBanName( address.serializeString()))); m_con.DeletePeer(peer_id); return; } } catch(con::PeerNotFoundException &e) { infostream<<"Server::ProcessData(): Cancelling: peer " <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; infostream<<"Server: Got TOSERVER_INIT from " <serialization_version = deployed; getClient(peer_id)->pending_serialization_version = deployed; if(deployed == SER_FMT_VER_INVALID) { infostream<<"Server: Cannot negotiate " "serialization version with peer " <= 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) { SendAccessDenied(m_con, peer_id, L"Your client is too old. Please upgrade."); return; } /* Uhh... this should actually be a warning but let's do it like this */ if(g_settings->getBool("strict_protocol_version_checking")) { if(net_proto_version < PROTOCOL_VERSION) { SendAccessDenied(m_con, peer_id, L"Your client is too old. Please upgrade."); return; } } /* Set up player */ // Get player name char playername[PLAYERNAME_SIZE]; for(u32 i=0; iget("default_password"); } /*infostream<<"Server: Client gave password '"<get("default_privs"))); m_authmanager.save(); } // Enforce user limit. // Don't enforce for users that have some admin right if(m_clients.size() >= g_settings->getU16("max_users") && (m_authmanager.getPrivs(playername) & (PRIV_SERVER|PRIV_BAN|PRIV_PRIVS)) == 0 && playername != g_settings->get("name")) { SendAccessDenied(m_con, peer_id, L"Too many users."); return; } // Get player Player *player = emergePlayer(playername, password, peer_id); // If failed, cancel if(player == NULL) { infostream<<"Server: peer_id="< reply(2+1+6+8); writeU16(&reply[0], TOCLIENT_INIT); writeU8(&reply[2], deployed); writeV3S16(&reply[2+1], floatToInt(player->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(player); return; } if(command == TOSERVER_INIT2) { infostream<<"Server: Got TOSERVER_INIT2 from " <serialization_version = getClient(peer_id)->pending_serialization_version; /* Send some initialization data */ // Send tool definitions SendToolDef(m_con, peer_id, m_toolmgr); // Send node definitions SendNodeDef(m_con, peer_id, m_nodedef); // Send textures SendTextures(peer_id); // Send player info to all players SendPlayerInfos(); // Send inventory to player UpdateCrafting(peer_id); SendInventory(peer_id); // Send player items to all players SendPlayerItems(); Player *player = m_env->getPlayer(peer_id); // Send HP SendPlayerHP(player); // Send time of day { SharedBuffer data = makePacket_TOCLIENT_TIME_OF_DAY( m_env->getTimeOfDay()); 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 { 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"); } /* Check HP, respawn if necessary */ HandlePlayerHP(player, 0); /* 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"<getActiveObject(id); if(obj == NULL) { infostream<<"Server: CLICK_ACTIVEOBJECT: object not found" <m_removed) return; //TODO: Check that object is reasonably close // Get ServerRemotePlayer ServerRemotePlayer *srp = (ServerRemotePlayer*)player; // Update wielded item srp->wieldItem(item_i); // Left click, pick/punch if(button == 0) { actionstream<getName()<<" punches object " <getId()<punch(srp); #if 0 /* Try creating inventory item */ InventoryItem *item = obj->createPickedUpItem(); if(item) { InventoryList *ilist = player->inventory.getList("main"); if(ilist != NULL) { actionstream<getName()<<" picked up " <getName()<getBool("creative_mode") == false) { // Skip if inventory has no free space if(ilist->roomForItem(item) == false) { infostream<<"Player inventory has no free space"<addItem(item); UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } // Remove object from environment obj->m_removed = true; } } else { /* Item cannot be picked up. Punch it instead. */ actionstream<getName()<<" punches object " <getId()<inventory.getList("main"); if(mlist != NULL) { InventoryItem *item = mlist->getItem(item_i); if(item && (std::string)item->getName() == "ToolItem") { titem = (ToolItem*)item; toolname = titem->getToolName(); } } v3f playerpos = player->getPosition(); v3f objpos = obj->getBasePosition(); v3f dir = (objpos - playerpos).normalize(); u16 wear = obj->punch(toolname, dir, player->getName()); if(titem) { bool weared_out = titem->addWear(wear); if(weared_out) mlist->deleteItem(item_i); SendInventory(player->peer_id); } } #endif } // Right click, do something with object if(button == 1) { actionstream<getName()<<" right clicks object " <getId()<rightClick(srp); } /* Update player state to client */ SendPlayerHP(player); UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } else if(command == TOSERVER_GROUND_ACTION) { if(datasize < 17) return; /* length: 17 [0] u16 command [2] u8 action [3] v3s16 nodepos_undersurface [9] v3s16 nodepos_abovesurface [15] u16 item actions: 0: start digging 1: place block 2: stop digging (all parameters ignored) 3: digging completed */ u8 action = readU8(&data[2]); v3s16 p_under; p_under.X = readS16(&data[3]); p_under.Y = readS16(&data[5]); p_under.Z = readS16(&data[7]); v3s16 p_over; p_over.X = readS16(&data[9]); p_over.Y = readS16(&data[11]); p_over.Z = readS16(&data[13]); u16 item_i = readU16(&data[15]); ServerRemotePlayer *srp = (ServerRemotePlayer*)player; /* Check that target is reasonably close */ { v3f np_f = intToFloat(p_under, BS); float max_d = BS * 8; // Just some large enough value float d = srp->m_last_good_position.getDistanceFrom(np_f); if(d > max_d){ actionstream<<"Player "<getName() <<" tried to access node from too far: " <<"d="<SetBlockNotSent(blockpos); // Do nothing else return; } } /* 0: start digging */ if(action == 0) { /* NOTE: This can be used in the future to check if somebody is cheating, by checking the timing. */ bool cannot_punch_node = false; MapNode n(CONTENT_IGNORE); try { n = m_env->getMap().getNode(p_under); } catch(InvalidPositionException &e) { infostream<<"Server: Not punching: Node not found." <<" Adding block to emerge queue." <m_dig_mutex); client->m_dig_tool_item = -1; #endif } /* 3: Digging completed */ else if(action == 3) { // Mandatory parameter; actually used for nothing core::map modified_blocks; content_t material = CONTENT_IGNORE; u8 mineral = MINERAL_NONE; bool cannot_remove_node = false; MapNode n(CONTENT_IGNORE); try { n = m_env->getMap().getNode(p_under); // Get mineral mineral = n.getMineral(m_nodedef); // Get material at position material = n.getContent(); // If not yet cancelled if(cannot_remove_node == false) { // If it's not diggable, do nothing if(m_nodedef->get(material).diggable == false) { infostream<<"Server: Not finishing digging: " <<"Node not diggable" <getMap().getNodeMetadata(p_under); if(meta && meta->nodeRemovalDisabled() == true) { infostream<<"Server: Not finishing digging: " <<"Node metadata disables removal" <getName()<<" cannot remove node" <<" because privileges are "<SetBlockNotSent(blockpos); return; } actionstream<getName()<<" digs "< far_players; sendRemoveNode(p_under, peer_id, &far_players, 30); /* Update and send inventory */ if(g_settings->getBool("creative_mode") == false) { /* Wear out tool */ InventoryList *mlist = player->inventory.getList("main"); if(mlist != NULL) { InventoryItem *item = mlist->getItem(item_i); if(item && (std::string)item->getName() == "ToolItem") { ToolItem *titem = (ToolItem*)item; std::string toolname = titem->getToolName(); // Get digging properties for material and tool ToolDiggingProperties tp = m_toolmgr->getDiggingProperties(toolname); DiggingProperties prop = getDiggingProperties(material, &tp, m_nodedef); if(prop.diggable == false) { infostream<<"Server: WARNING: Player digged" <<" with impossible material + tool" <<" combination"<addWear(prop.wear); if(weared_out) { mlist->deleteItem(item_i); } } } /* Add dug item to inventory */ InventoryItem *item = NULL; if(mineral != MINERAL_NONE) item = getDiggedMineralItem(mineral, this); // If not mineral if(item == NULL) { const std::string &dug_s = m_nodedef->get(material).dug_item; if(dug_s != "") { std::istringstream is(dug_s, std::ios::binary); item = InventoryItem::deSerialize(is, this); } } if(item != NULL) { // Add a item to inventory player->inventory.addItem("main", item); // Send inventory UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } item = NULL; if(mineral != MINERAL_NONE) item = getDiggedMineralItem(mineral, this); // If not mineral if(item == NULL) { const std::string &extra_dug_s = m_nodedef->get(material).extra_dug_item; s32 extra_rarity = m_nodedef->get(material).extra_dug_item_rarity; if(extra_dug_s != "" && extra_rarity != 0 && myrand() % extra_rarity == 0) { std::istringstream is(extra_dug_s, std::ios::binary); item = InventoryItem::deSerialize(is, this); } } if(item != NULL) { // Add a item to inventory player->inventory.addItem("main", item); // Send inventory UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } } /* Remove the node (this takes some time so it is done after the quick stuff) */ { MapEditEventIgnorer ign(&m_ignore_map_edit_events); m_env->getMap().removeNodeAndUpdate(p_under, modified_blocks); } /* Set blocks not sent to far players */ 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_blocks); } /* Run script hook */ scriptapi_environment_on_dignode(m_lua, p_under, n, srp); } /* 1: place block */ else if(action == 1) { InventoryList *ilist = player->inventory.getList("main"); if(ilist == NULL) return; // Get item InventoryItem *item = ilist->getItem(item_i); // If there is no item, it is not possible to add it anywhere if(item == NULL) return; /* Handle material items */ if(std::string("MaterialItem") == item->getName()) { try{ // Don't add a node if this is not a free space MapNode n2 = m_env->getMap().getNode(p_over); bool no_enough_privs = ((getPlayerPrivs(player) & PRIV_BUILD)==0); if(no_enough_privs) infostream<<"Player "<getName()<<" cannot add node" <<" because privileges are "<get(n2).buildable_to == false || no_enough_privs) { // Client probably has wrong data. // Set block not sent, so that client will get // a valid one. infostream<<"Client "<SetBlockNotSent(blockpos); return; } } catch(InvalidPositionException &e) { infostream<<"Server: Ignoring ADDNODE: Node not found" <<" Adding block to emerge queue." <m_time_from_building = 0.0; // Create node data MaterialItem *mitem = (MaterialItem*)item; MapNode n; n.setContent(mitem->getMaterial()); actionstream<getName()<<" places material " <<(int)mitem->getMaterial() <<" at "<get(n).wall_mounted) n.param2 = packDir(p_under - p_over); // Calculate the direction for furnaces and chests and stuff if(m_nodedef->get(n).param_type == CPT_FACEDIR_SIMPLE) { v3f playerpos = player->getPosition(); v3f blockpos = intToFloat(p_over, BS) - playerpos; blockpos = blockpos.normalize(); n.param1 = 0; if (fabs(blockpos.X) > fabs(blockpos.Z)) { if (blockpos.X < 0) n.param1 = 3; else n.param1 = 1; } else { if (blockpos.Z < 0) n.param1 = 2; else n.param1 = 0; } } /* Send to all close-by players */ core::list far_players; sendAddNode(p_over, n, 0, &far_players, 30); /* Handle inventory */ InventoryList *ilist = player->inventory.getList("main"); if(g_settings->getBool("creative_mode") == false && ilist) { // Remove from inventory and send inventory if(mitem->getCount() == 1) ilist->deleteItem(item_i); else mitem->remove(1); // Send inventory UpdateCrafting(peer_id); SendInventory(peer_id); } /* Add node. This takes some time so it is done after the quick stuff */ core::map modified_blocks; { MapEditEventIgnorer ign(&m_ignore_map_edit_events); std::string p_name = std::string(player->getName()); m_env->getMap().addNodeAndUpdate(p_over, n, modified_blocks, p_name); } /* Set blocks not sent to far players */ 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_blocks); } /* Run script hook */ scriptapi_environment_on_placenode(m_lua, p_over, n, srp); /* Calculate special events */ /*if(n.d == LEGN(m_nodedef, "CONTENT_MESE")) { u32 count = 0; for(s16 z=-1; z<=1; z++) for(s16 y=-1; y<=1; y++) for(s16 x=-1; x<=1; x++) { } }*/ } /* Place other item (not a block) */ else { v3s16 blockpos = getNodeBlockPos(p_over); /* Check that the block is loaded so that the item can properly be added to the static list too */ MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos); if(block==NULL) { infostream<<"Error while placing object: " "block not found"<getBool("creative_mode") && (getPlayerPrivs(player) & PRIV_BUILD) == 0) { infostream<<"Not allowing player to drop item: " "creative mode and no build privs"<createSAO(m_env, pos); if(obj == NULL) { infostream<<"WARNING: item resulted in NULL object, " <<"not placing onto map" <getName()<<" places "<getName() <<" at "<addActiveObject(obj); infostream<<"Placed object"<getBool("creative_mode") == false) { // Delete the right amount of items from the slot u16 dropcount = item->getDropCount(); // Delete item if all gone if(item->getCount() <= dropcount) { if(item->getCount() < dropcount) infostream<<"WARNING: Server: dropped more items" <<" than the slot contains"<inventory.getList("main"); if(ilist) // Remove from inventory and send inventory ilist->deleteItem(item_i); } // Else decrement it else item->remove(dropcount); // Send inventory UpdateCrafting(peer_id); SendInventory(peer_id); } } } } // action == 1 /* Catch invalid actions */ else { infostream<<"WARNING: Server: Invalid action " <getMap().getNodeMetadata(p); if(!meta) return; if(meta->typeId() != LEGN(m_nodedef, "CONTENT_SIGN_WALL")) return; SignNodeMetadata *signmeta = (SignNodeMetadata*)meta; signmeta->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) { /*// Ignore inventory changes if in creative mode if(g_settings->getBool("creative_mode") == true) { infostream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode" <to_inv == "current_player" && ma->from_inv == "current_player") { InventoryList *rlist = player->inventory.getList("craftresult"); assert(rlist); InventoryList *clist = player->inventory.getList("craft"); assert(clist); InventoryList *mlist = player->inventory.getList("main"); assert(mlist); /* Craftresult is no longer preview if something is moved into it */ if(ma->to_list == "craftresult" && ma->from_list != "craftresult") { // If it currently is a preview, remove // its contents if(player->craftresult_is_preview) { rlist->deleteItem(0); } player->craftresult_is_preview = false; } /* Crafting takes place if this condition is true. */ if(player->craftresult_is_preview && ma->from_list == "craftresult") { player->craftresult_is_preview = false; clist->decrementMaterials(1); /* Print out action */ InventoryList *list = player->inventory.getList("craftresult"); assert(list); InventoryItem *item = list->getItem(0); std::string itemname = "NULL"; if(item) itemname = item->getName(); actionstream<getName()<<" crafts " <to_list == "craftresult" && ma->from_list == "craftresult") { disable_action = true; InventoryItem *item1 = rlist->changeItem(0, NULL); mlist->addItem(item1); } } // Disallow moving items if not allowed to build else if((getPlayerPrivs(player) & PRIV_BUILD) == 0) { disable_action = true; } // if it's a locking chest, only allow the owner or server admins to move items else if (ma->from_inv != "current_player" && (getPlayerPrivs(player) & PRIV_SERVER) == 0) { Strfnd fn(ma->from_inv); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); NodeMetadata *meta = m_env->getMap().getNodeMetadata(p); if(meta && meta->typeId() == LEGN(m_nodedef, "CONTENT_LOCKABLE_CHEST")) { LockingChestNodeMetadata *lcm = (LockingChestNodeMetadata*)meta; if (lcm->getOwner() != player->getName()) disable_action = true; } } } else if (ma->to_inv != "current_player" && (getPlayerPrivs(player) & PRIV_SERVER) == 0) { Strfnd fn(ma->to_inv); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); NodeMetadata *meta = m_env->getMap().getNodeMetadata(p); if(meta && meta->typeId() == LEGN(m_nodedef, "CONTENT_LOCKABLE_CHEST")) { LockingChestNodeMetadata *lcm = (LockingChestNodeMetadata*)meta; if (lcm->getOwner() != player->getName()) disable_action = true; } } } } if(a->getType() == IACTION_DROP) { IDropAction *da = (IDropAction*)a; // Disallow dropping items if not allowed to build if((getPlayerPrivs(player) & PRIV_BUILD) == 0) { disable_action = true; } // if it's a locking chest, only allow the owner or server admins to drop items else if (da->from_inv != "current_player" && (getPlayerPrivs(player) & PRIV_SERVER) == 0) { Strfnd fn(da->from_inv); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); NodeMetadata *meta = m_env->getMap().getNodeMetadata(p); if(meta && meta->typeId() == LEGN(m_nodedef, "CONTENT_LOCKABLE_CHEST")) { LockingChestNodeMetadata *lcm = (LockingChestNodeMetadata*)meta; if (lcm->getOwner() != player->getName()) disable_action = true; } } } } if(disable_action == false) { // Feed action to player inventory a->apply(&c, this, m_env); } else { // Send inventory UpdateCrafting(player->peer_id); SendInventory(player->peer_id); } // Eat the action delete a; } else { infostream<<"TOSERVER_INVENTORY_ACTION: " <<"InventoryAction::deSerialize() returned NULL" <getName()); // 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; // Local player gets all privileges regardless of // what's set on their account. u64 privs = getPlayerPrivs(player); // 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, privs); 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(privs & PRIV_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); if(g_settings->getBool("enable_damage")) { actionstream<getName()<<" damaged by " <<(int)damage<<" hp at "<getPosition()/BS) <getName(); if(m_authmanager.exists(playername) == false) { infostream<<"Server: playername not found in authmanager"<getName()<<" changes password"<wieldItem(item); SendWieldedItem(player); } else if(command == TOSERVER_RESPAWN) { if(player->hp != 0) return; RespawnPlayer(player); actionstream<getName()<<" respawns at " <getPosition()/BS)<clone(); m_unsent_map_edit_queue.push_back(e); } Inventory* Server::getInventory(InventoryContext *c, std::string id) { if(id == "current_player") { assert(c->current_player); return &(c->current_player->inventory); } Strfnd fn(id); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); NodeMetadata *meta = m_env->getMap().getNodeMetadata(p); if(meta) return meta->getInventory(); infostream<<"nodemeta at ("<current_player); // Send inventory UpdateCrafting(c->current_player->peer_id); SendInventory(c->current_player->peer_id); return; } Strfnd fn(id); std::string id0 = fn.next(":"); if(id0 == "nodemeta") { v3s16 p; p.X = stoi(fn.next(",")); p.Y = stoi(fn.next(",")); p.Z = stoi(fn.next(",")); v3s16 blockpos = getNodeBlockPos(p); NodeMetadata *meta = m_env->getMap().getNodeMetadata(p); if(meta) meta->inventoryModified(); MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos); if(block) block->raiseModified(MOD_STATE_WRITE_NEEDED); setBlockNotSent(blockpos); return; } infostream<<__FUNCTION_NAME<<": unknown id "< 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); infostream<<"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); infostream<<"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::SendToolDef(con::Connection &con, u16 peer_id, IToolDefManager *tooldef) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); /* u16 command u32 length of the next item serialized ToolDefManager */ writeU16(os, TOCLIENT_TOOLDEF); std::ostringstream tmp_os(std::ios::binary); tooldef->serialize(tmp_os); os<getName()); start += 2+PLAYERNAME_SIZE; } //JMutexAutoLock conlock(m_con_mutex); // Send as reliable m_con.SendToAll(0, data, true); } void Server::SendInventory(u16 peer_id) { DSTACK(__FUNCTION_NAME); Player* player = m_env->getPlayer(peer_id); assert(player); /* Serialize it */ std::ostringstream os; //os.imbue(std::locale("C")); player->inventory.serialize(os); std::string s = os.str(); SharedBuffer data(s.size()+2); writeU16(&data[0], TOCLIENT_INVENTORY); memcpy(&data[2], s.c_str(), s.size()); // Send as reliable m_con.Send(peer_id, 0, data, true); } std::string getWieldedItemString(const Player *player) { const InventoryItem *item = player->getWieldItem(); if (item == NULL) return std::string(""); std::ostringstream os(std::ios_base::binary); item->serialize(os); return os.str(); } void Server::SendWieldedItem(const Player* player) { DSTACK(__FUNCTION_NAME); assert(player); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_PLAYERITEM); writeU16(os, 1); writeU16(os, player->peer_id); os< data((u8*)s.c_str(), s.size()); m_con.SendToAll(0, data, true); } void Server::SendPlayerItems() { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); core::list players = m_env->getPlayers(true); writeU16(os, TOCLIENT_PLAYERITEM); writeU16(os, players.size()); core::list::Iterator i; for(i = players.begin(); i != players.end(); ++i) { Player *p = *i; writeU16(os, p->peer_id); os< data((u8*)s.c_str(), s.size()); m_con.SendToAll(0, data, true); } void Server::SendChatMessage(u16 peer_id, const std::wstring &message) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); u8 buf[12]; // Write command writeU16(buf, TOCLIENT_CHAT_MESSAGE); os.write((char*)buf, 2); // Write length writeU16(buf, message.size()); os.write((char*)buf, 2); // Write string for(u32 i=0; i data((u8*)s.c_str(), s.size()); // Send as reliable m_con.Send(peer_id, 0, data, true); } void Server::BroadcastChatMessage(const std::wstring &message) { 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; SendChatMessage(client->peer_id, message); } } void Server::SendPlayerHP(Player *player) { SendHP(m_con, player->peer_id, player->hp); } void Server::SendMovePlayer(Player *player) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); writeU16(os, TOCLIENT_MOVE_PLAYER); writeV3F1000(os, player->getPosition()); writeF1000(os, player->getPitch()); writeF1000(os, player->getYaw()); { v3f pos = player->getPosition(); f32 pitch = player->getPitch(); f32 yaw = player->getYaw(); infostream<<"Server sending TOCLIENT_MOVE_PLAYER" <<" pos=("<serialize(os, ver); 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()); 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++; } } struct SendableTexture { std::string name; std::string path; std::string data; SendableTexture(const std::string &name_="", const std::string path_="", const std::string &data_=""): name(name_), path(path_), data(data_) {} }; void Server::SendTextures(u16 peer_id) { DSTACK(__FUNCTION_NAME); infostream<<"Server::SendTextures(): Sending textures to client"< > texture_bunches; texture_bunches.push_back(core::list()); u32 texture_size_bunch_total = 0; core::list mods = getMods(m_modspaths); for(core::list::Iterator i = mods.begin(); i != mods.end(); i++){ ModSpec mod = *i; std::string texturepath = mod.path + DIR_DELIM + "textures"; std::vector dirlist = fs::GetDirListing(texturepath); for(u32 j=0; j= bytes_per_bunch){ texture_bunches.push_back(core::list()); texture_size_bunch_total = 0; } } } /* Create and send packets */ u32 num_bunches = texture_bunches.size(); for(u32 i=0; i::Iterator j = texture_bunches[i].begin(); j != texture_bunches[i].end(); j++){ os<name); os<data); } // Make data buffer std::string s = os.str(); infostream<<"Server::SendTextures(): bunch "<getName()<<" dies"<hp = 0; //TODO: Throw items around // Handle players that are not connected if(player->peer_id == PEER_ID_INEXISTENT){ RespawnPlayer(player); return; } SendPlayerHP(player); RemoteClient *client = getClient(player->peer_id); if(client->net_proto_version >= 3) { SendDeathscreen(m_con, player->peer_id, false, v3f(0,0,0)); } else { RespawnPlayer(player); } } } void Server::RespawnPlayer(Player *player) { player->hp = 20; ServerRemotePlayer *srp = (ServerRemotePlayer*)player; bool repositioned = scriptapi_on_respawnplayer(m_lua, srp); if(!repositioned){ v3f pos = findSpawnPos(m_env->getServerMap()); player->setPosition(pos); srp->m_last_good_position = pos; srp->m_last_good_position_age = 0; } SendMovePlayer(player); SendPlayerHP(player); } void Server::UpdateCrafting(u16 peer_id) { DSTACK(__FUNCTION_NAME); Player* player = m_env->getPlayer(peer_id); assert(player); /* Calculate crafting stuff */ if(g_settings->getBool("creative_mode") == false) { InventoryList *clist = player->inventory.getList("craft"); InventoryList *rlist = player->inventory.getList("craftresult"); if(rlist && rlist->getUsedSlots() == 0) player->craftresult_is_preview = true; if(rlist && player->craftresult_is_preview) { rlist->clearItems(); } if(clist && rlist && player->craftresult_is_preview) { // Get result of crafting grid std::vector items; for(u16 i=0; i<9; i++){ if(clist->getItem(i) == NULL) items.push_back(NULL); else items.push_back(clist->getItem(i)->clone()); } CraftPointerInput cpi(3, items); InventoryItem *result = m_craftdef->getCraftResult(cpi, this); //InventoryItem *result = craft_get_result(items, this); if(result) rlist->addItem(result); } } // if creative_mode == false } 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(); } // Saves g_settings to configpath given at initialization void Server::saveConfig() { if(m_configpath != "") g_settings->updateConfigFile(m_configpath.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); } // IGameDef interface // Under envlock IToolDefManager* Server::getToolDefManager() { return m_toolmgr; } 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); } IWritableToolDefManager* Server::getWritableToolDefManager() { return m_toolmgr; } IWritableNodeDefManager* Server::getWritableNodeDefManager() { return m_nodedef; } IWritableCraftDefManager* Server::getWritableCraftDefManager() { return m_craftdef; } 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 "<getPlayer(name); if(player != NULL) { // If player is already connected, cancel if(player->peer_id != 0) { infostream<<"emergePlayer(): Player already connected"<peer_id = peer_id; // Reset inventory to creative if in creative mode if(g_settings->getBool("creative_mode")) { // Warning: double code below // Backup actual inventory player->inventory_backup = new Inventory(); *(player->inventory_backup) = player->inventory; // Set creative inventory craft_set_creative_inventory(player, this); } return player; } /* If player with the wanted peer_id already exists, cancel. */ if(m_env->getPlayer(peer_id) != NULL) { infostream<<"emergePlayer(): Player with wrong name but same" " peer_id already exists"<get("default_privs"))); /* Set player position */ infostream<<"Server: Finding spawn place for player \"" <getServerMap()); player = new ServerRemotePlayer(m_env, pos, peer_id, name); /* Add player to environment */ m_env->addPlayer(player); /* Run scripts */ ServerRemotePlayer *srp = (ServerRemotePlayer*)player; scriptapi_on_newplayer(m_lua, srp); /* Add stuff to inventory */ if(g_settings->getBool("creative_mode")) { // Warning: double code above // Backup actual inventory player->inventory_backup = new Inventory(); *(player->inventory_backup) = player->inventory; // Set creative inventory craft_set_creative_inventory(player, this); } return player; } // create new player } 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--; } // Collect information about leaving in chat std::wstring message; { Player *player = m_env->getPlayer(c.peer_id); 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)"; } } /*// Delete player { m_env->removePlayer(c.peer_id); }*/ // Set player client disconnected { Player *player = m_env->getPlayer(c.peer_id); if(player != NULL) player->peer_id = 0; /* 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(); infostream<<"Server: Handling peer change: " <<"id="<getFloat("profiler_print_interval"); if(profiler_print_interval != 0) { if(m_profiler_interval.step(0.030, profiler_print_interval)) { infostream<<"Profiler:"<print(infostream); g_profiler->clear(); } } /* Player info */ static int counter = 0; counter--; if(counter <= 0) { counter = 10; core::list list = server.getPlayerInfo(); core::list::Iterator i; static u32 sum_old = 0; u32 sum = PIChecksum(list); if(sum != sum_old) { infostream<PrintLine(&infostream); } } sum_old = sum; } } }