3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 (c) 2010 Perttu Ahola <celeron55@gmail.com>
27 #include "clientserver.h"
29 #include "jmutexautolock.h"
31 #include "constants.h"
33 #include "materials.h"
35 #define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)
37 void * ServerThread::Thread()
41 DSTACK(__FUNCTION_NAME);
43 BEGIN_DEBUG_EXCEPTION_HANDLER
48 m_server->AsyncRunStep();
50 //dout_server<<"Running m_server->Receive()"<<std::endl;
53 catch(con::NoIncomingDataException &e)
58 END_DEBUG_EXCEPTION_HANDLER
63 void * EmergeThread::Thread()
67 DSTACK(__FUNCTION_NAME);
71 BEGIN_DEBUG_EXCEPTION_HANDLER
74 Get block info from queue, emerge them and send them
77 After queue is empty, exit.
81 QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
85 SharedPtr<QueuedBlockEmerge> q(qptr);
89 //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
91 //TimeTaker timer("block emerge", g_device);
94 Try to emerge it from somewhere.
96 If it is only wanted as optional, only loading from disk
101 Check if any peer wants it as non-optional. In that case it
104 Also decrement the emerge queue count in clients.
107 bool optional = true;
110 core::map<u16, u8>::Iterator i;
111 for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
113 //u16 peer_id = i.getNode()->getKey();
116 u8 flags = i.getNode()->getValue();
117 if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
123 /*dstream<<"EmergeThread: p="
124 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
125 <<"optional="<<optional<<std::endl;*/
127 ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
129 core::map<v3s16, MapBlock*> changed_blocks;
130 core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
132 MapBlock *block = NULL;
133 bool got_block = true;
134 core::map<v3s16, MapBlock*> modified_blocks;
138 JMutexAutoLock envlock(m_server->m_env_mutex);
140 //TimeTaker timer("block emerge envlock", g_device);
143 bool only_from_disk = false;
146 only_from_disk = true;
148 block = map.emergeBlock(
152 lighting_invalidated_blocks);
154 // If it is a dummy, block was not found on disk
157 //dstream<<"EmergeThread: Got a dummy block"<<std::endl;
161 catch(InvalidPositionException &e)
164 // This happens when position is over limit.
170 if(debug && changed_blocks.size() > 0)
172 dout_server<<DTIME<<"Got changed_blocks: ";
173 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
174 i.atEnd() == false; i++)
176 MapBlock *block = i.getNode()->getValue();
177 v3s16 p = block->getPos();
178 dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
180 dout_server<<std::endl;
184 Update water pressure
187 m_server->UpdateBlockWaterPressure(block, modified_blocks);
189 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
190 i.atEnd() == false; i++)
192 MapBlock *block = i.getNode()->getValue();
193 m_server->UpdateBlockWaterPressure(block, modified_blocks);
194 //v3s16 p = i.getNode()->getKey();
195 //m_server->UpdateBlockWaterPressure(p, modified_blocks);
199 Collect a list of blocks that have been modified in
200 addition to the fetched one.
203 // Add all the "changed blocks" to modified_blocks
204 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
205 i.atEnd() == false; i++)
207 MapBlock *block = i.getNode()->getValue();
208 modified_blocks.insert(block->getPos(), block);
211 /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
212 <<" blocks"<<std::endl;
213 TimeTaker timer("** updateLighting", g_device);*/
215 // Update lighting without locking the environment mutex,
216 // add modified blocks to changed blocks
217 map.updateLighting(lighting_invalidated_blocks, modified_blocks);
219 // If we got no block, there should be no invalidated blocks
222 assert(lighting_invalidated_blocks.size() == 0);
228 Set sent status of modified blocks on clients
231 // NOTE: Server's clients are also behind the connection mutex
232 JMutexAutoLock lock(m_server->m_con_mutex);
235 Add the originally fetched block to the modified list
239 modified_blocks.insert(p, block);
243 Set the modified blocks unsent for all the clients
246 for(core::map<u16, RemoteClient*>::Iterator
247 i = m_server->m_clients.getIterator();
248 i.atEnd() == false; i++)
250 RemoteClient *client = i.getNode()->getValue();
252 if(modified_blocks.size() > 0)
254 // Remove block from sent history
255 client->SetBlocksNotSent(modified_blocks);
261 END_DEBUG_EXCEPTION_HANDLER
266 void RemoteClient::GetNextBlocks(Server *server, float dtime,
267 core::array<PrioritySortedBlockTransfer> &dest)
269 DSTACK(__FUNCTION_NAME);
273 JMutexAutoLock lock(m_blocks_sent_mutex);
274 m_nearest_unsent_reset_timer += dtime;
277 // Won't send anything if already sending
279 JMutexAutoLock lock(m_blocks_sending_mutex);
281 if(m_blocks_sending.size() >= g_settings.getU16
282 ("max_simultaneous_block_sends_per_client"))
284 //dstream<<"Not sending any blocks, Queue full."<<std::endl;
289 Player *player = server->m_env.getPlayer(peer_id);
291 v3f playerpos = player->getPosition();
292 v3f playerspeed = player->getSpeed();
294 v3s16 center_nodepos = floatToInt(playerpos);
296 v3s16 center = getNodeBlockPos(center_nodepos);
299 Get the starting value of the block finder radius.
301 s16 last_nearest_unsent_d;
304 JMutexAutoLock lock(m_blocks_sent_mutex);
306 if(m_last_center != center)
308 m_nearest_unsent_d = 0;
309 m_last_center = center;
312 /*dstream<<"m_nearest_unsent_reset_timer="
313 <<m_nearest_unsent_reset_timer<<std::endl;*/
314 if(m_nearest_unsent_reset_timer > 5.0)
316 m_nearest_unsent_reset_timer = 0;
317 m_nearest_unsent_d = 0;
318 //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
321 last_nearest_unsent_d = m_nearest_unsent_d;
323 d_start = m_nearest_unsent_d;
326 u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
327 ("max_simultaneous_block_sends_per_client");
328 u16 maximum_simultaneous_block_sends =
329 maximum_simultaneous_block_sends_setting;
332 Check the time from last addNode/removeNode.
334 Decrease send rate if player is building stuff.
337 SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
338 m_time_from_building.m_value += dtime;
339 /*if(m_time_from_building.m_value
340 < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)*/
341 if(m_time_from_building.m_value < g_settings.getFloat(
342 "full_block_send_enable_min_time_from_building"))
344 maximum_simultaneous_block_sends
345 = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
349 u32 num_blocks_selected;
351 JMutexAutoLock lock(m_blocks_sending_mutex);
352 num_blocks_selected = m_blocks_sending.size();
356 next time d will be continued from the d from which the nearest
357 unsent block was found this time.
359 This is because not necessarily any of the blocks found this
360 time are actually sent.
362 s32 new_nearest_unsent_d = -1;
364 // Serialization version used
365 //u8 ser_version = serialization_version;
367 //bool has_incomplete_blocks = false;
369 s16 d_max = g_settings.getS16("max_block_send_distance");
370 s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
372 //dstream<<"Starting from "<<d_start<<std::endl;
374 for(s16 d = d_start; d <= d_max; d++)
376 //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
378 //if(has_incomplete_blocks == false)
380 JMutexAutoLock lock(m_blocks_sent_mutex);
382 If m_nearest_unsent_d was changed by the EmergeThread
383 (it can change it to 0 through SetBlockNotSent),
385 Else update m_nearest_unsent_d
387 if(m_nearest_unsent_d != last_nearest_unsent_d)
389 d = m_nearest_unsent_d;
390 last_nearest_unsent_d = m_nearest_unsent_d;
395 Get the border/face dot coordinates of a "d-radiused"
398 core::list<v3s16> list;
399 getFacePositions(list, d);
401 core::list<v3s16>::Iterator li;
402 for(li=list.begin(); li!=list.end(); li++)
404 v3s16 p = *li + center;
408 - Don't allow too many simultaneous transfers
409 - EXCEPT when the blocks are very close
411 Also, don't send blocks that are already flying.
414 u16 maximum_simultaneous_block_sends_now =
415 maximum_simultaneous_block_sends;
417 if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
419 maximum_simultaneous_block_sends_now =
420 maximum_simultaneous_block_sends_setting;
424 JMutexAutoLock lock(m_blocks_sending_mutex);
426 // Limit is dynamically lowered when building
427 if(num_blocks_selected
428 >= maximum_simultaneous_block_sends_now)
430 /*dstream<<"Not sending more blocks. Queue full. "
431 <<m_blocks_sending.size()
436 if(m_blocks_sending.find(p) != NULL)
443 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
444 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
445 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
446 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
447 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
448 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
451 bool generate = d <= d_max_gen;
453 // Limit the generating area vertically to half
454 if(abs(p.Y - center.Y) > d_max_gen / 2)
458 Don't send already sent blocks
461 JMutexAutoLock lock(m_blocks_sent_mutex);
463 if(m_blocks_sent.find(p) != NULL)
468 Check if map has this block
470 MapBlock *block = NULL;
473 block = server->m_env.getMap().getBlockNoCreate(p);
475 catch(InvalidPositionException &e)
479 bool surely_not_found_on_disk = false;
482 /*if(block->isIncomplete())
484 has_incomplete_blocks = true;
490 surely_not_found_on_disk = true;
495 If block has been marked to not exist on disk (dummy)
496 and generating new ones is not wanted, skip block.
498 if(generate == false && surely_not_found_on_disk == true)
505 Record the lowest d from which a a block has been
506 found being not sent and possibly to exist
508 if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
510 new_nearest_unsent_d = d;
514 Add inexistent block to emerge queue.
516 if(block == NULL || surely_not_found_on_disk)
518 /*SharedPtr<JMutexAutoLock> lock
519 (m_num_blocks_in_emerge_queue.getLock());*/
521 //TODO: Get value from somewhere
522 // Allow only one block in emerge queue
523 if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
525 // Add it to the emerge queue and trigger the thread
528 if(generate == false)
529 flags |= BLOCK_EMERGE_FLAG_FROMDISK;
531 server->m_emerge_queue.addBlock(peer_id, p, flags);
532 server->m_emergethread.trigger();
543 PrioritySortedBlockTransfer q((float)d, p, peer_id);
547 num_blocks_selected += 1;
552 if(new_nearest_unsent_d != -1)
554 JMutexAutoLock lock(m_blocks_sent_mutex);
555 m_nearest_unsent_d = new_nearest_unsent_d;
559 void RemoteClient::SendObjectData(
562 core::map<v3s16, bool> &stepped_blocks
565 DSTACK(__FUNCTION_NAME);
567 // Can't send anything without knowing version
568 if(serialization_version == SER_FMT_VER_INVALID)
570 dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
576 Send a TOCLIENT_OBJECTDATA packet.
580 u16 number of player positions
591 std::ostringstream os(std::ios_base::binary);
595 writeU16(buf, TOCLIENT_OBJECTDATA);
596 os.write((char*)buf, 2);
599 Get and write player data
602 core::list<Player*> players = server->m_env.getPlayers();
604 // Write player count
605 u16 playercount = players.size();
606 writeU16(buf, playercount);
607 os.write((char*)buf, 2);
609 core::list<Player*>::Iterator i;
610 for(i = players.begin();
611 i != players.end(); i++)
615 v3f pf = player->getPosition();
616 v3f sf = player->getSpeed();
618 v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
619 v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100);
620 s32 pitch_i (player->getPitch() * 100);
621 s32 yaw_i (player->getYaw() * 100);
623 writeU16(buf, player->peer_id);
624 os.write((char*)buf, 2);
625 writeV3S32(buf, position_i);
626 os.write((char*)buf, 12);
627 writeV3S32(buf, speed_i);
628 os.write((char*)buf, 12);
629 writeS32(buf, pitch_i);
630 os.write((char*)buf, 4);
631 writeS32(buf, yaw_i);
632 os.write((char*)buf, 4);
636 Get and write object data
642 For making players to be able to build to their nearby
643 environment (building is not possible on blocks that are not
646 - Add blocks to emerge queue if they are not found
648 SUGGESTION: These could be ignored from the backside of the player
651 Player *player = server->m_env.getPlayer(peer_id);
653 v3f playerpos = player->getPosition();
654 v3f playerspeed = player->getSpeed();
656 v3s16 center_nodepos = floatToInt(playerpos);
657 v3s16 center = getNodeBlockPos(center_nodepos);
659 s16 d_max = g_settings.getS16("active_object_range");
661 // Number of blocks whose objects were written to bos
664 std::ostringstream bos(std::ios_base::binary);
666 for(s16 d = 0; d <= d_max; d++)
668 core::list<v3s16> list;
669 getFacePositions(list, d);
671 core::list<v3s16>::Iterator li;
672 for(li=list.begin(); li!=list.end(); li++)
674 v3s16 p = *li + center;
677 Ignore blocks that haven't been sent to the client
680 JMutexAutoLock sentlock(m_blocks_sent_mutex);
681 if(m_blocks_sent.find(p) == NULL)
685 // Try stepping block and add it to a send queue
690 MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
693 Step block if not in stepped_blocks and add to stepped_blocks.
695 if(stepped_blocks.find(p) == NULL)
697 block->stepObjects(dtime, true, server->getDayNightRatio());
698 stepped_blocks.insert(p, true);
699 block->setChangedFlag();
702 // Skip block if there are no objects
703 if(block->getObjectCount() == 0)
712 bos.write((char*)buf, 6);
715 block->serializeObjects(bos, serialization_version);
720 Stop collecting objects if data is already too big
722 // Sum of player and object data sizes
723 s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
724 // break out if data too big
725 if(sum > MAX_OBJECTDATA_SIZE)
727 goto skip_subsequent;
731 catch(InvalidPositionException &e)
734 // Add it to the emerge queue and trigger the thread.
735 // Fetch the block only if it is on disk.
737 // Grab and increment counter
738 /*SharedPtr<JMutexAutoLock> lock
739 (m_num_blocks_in_emerge_queue.getLock());
740 m_num_blocks_in_emerge_queue.m_value++;*/
742 // Add to queue as an anonymous fetch from disk
743 u8 flags = BLOCK_EMERGE_FLAG_FROMDISK;
744 server->m_emerge_queue.addBlock(0, p, flags);
745 server->m_emergethread.trigger();
753 writeU16(buf, blockcount);
754 os.write((char*)buf, 2);
756 // Write block objects
763 //dstream<<"Server: Sending object data to "<<peer_id<<std::endl;
766 std::string s = os.str();
767 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
768 // Send as unreliable
769 server->m_con.Send(peer_id, 0, data, false);
772 void RemoteClient::GotBlock(v3s16 p)
774 JMutexAutoLock lock(m_blocks_sending_mutex);
775 JMutexAutoLock lock2(m_blocks_sent_mutex);
776 if(m_blocks_sending.find(p) != NULL)
777 m_blocks_sending.remove(p);
779 dstream<<"RemoteClient::GotBlock(): Didn't find in"
780 " m_blocks_sending"<<std::endl;
781 m_blocks_sent.insert(p, true);
784 void RemoteClient::SentBlock(v3s16 p)
786 JMutexAutoLock lock(m_blocks_sending_mutex);
787 if(m_blocks_sending.size() > 15)
789 dstream<<"RemoteClient::SentBlock(): "
790 <<"m_blocks_sending.size()="
791 <<m_blocks_sending.size()<<std::endl;
793 if(m_blocks_sending.find(p) == NULL)
794 m_blocks_sending.insert(p, 0.0);
796 dstream<<"RemoteClient::SentBlock(): Sent block"
797 " already in m_blocks_sending"<<std::endl;
800 void RemoteClient::SetBlockNotSent(v3s16 p)
802 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
803 JMutexAutoLock sentlock(m_blocks_sent_mutex);
805 m_nearest_unsent_d = 0;
807 if(m_blocks_sending.find(p) != NULL)
808 m_blocks_sending.remove(p);
809 if(m_blocks_sent.find(p) != NULL)
810 m_blocks_sent.remove(p);
813 void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
815 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
816 JMutexAutoLock sentlock(m_blocks_sent_mutex);
818 m_nearest_unsent_d = 0;
820 for(core::map<v3s16, MapBlock*>::Iterator
821 i = blocks.getIterator();
822 i.atEnd()==false; i++)
824 v3s16 p = i.getNode()->getKey();
826 if(m_blocks_sending.find(p) != NULL)
827 m_blocks_sending.remove(p);
828 if(m_blocks_sent.find(p) != NULL)
829 m_blocks_sent.remove(p);
837 PlayerInfo::PlayerInfo()
842 void PlayerInfo::PrintLine(std::ostream *s)
844 (*s)<<id<<": \""<<name<<"\" ("
845 <<position.X<<","<<position.Y
846 <<","<<position.Z<<") ";
848 (*s)<<" avg_rtt="<<avg_rtt;
852 u32 PIChecksum(core::list<PlayerInfo> &l)
854 core::list<PlayerInfo>::Iterator i;
857 for(i=l.begin(); i!=l.end(); i++)
859 checksum += a * (i->id+1);
860 checksum ^= 0x435aafcd;
871 std::string mapsavedir,
875 m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
876 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
878 m_emergethread(this),
881 m_time_of_day_send_timer(0),
884 m_flowwater_timer = 0.0;
885 m_print_info_timer = 0.0;
886 m_objectdata_timer = 0.0;
887 m_emergethread_trigger_timer = 0.0;
888 m_savemap_timer = 0.0;
892 m_step_dtime_mutex.Init();
901 JMutexAutoLock clientslock(m_con_mutex);
903 for(core::map<u16, RemoteClient*>::Iterator
904 i = m_clients.getIterator();
905 i.atEnd() == false; i++)
907 u16 peer_id = i.getNode()->getKey();
911 JMutexAutoLock envlock(m_env_mutex);
912 m_env.removePlayer(peer_id);
916 delete i.getNode()->getValue();
920 void Server::start(unsigned short port)
922 DSTACK(__FUNCTION_NAME);
923 // Stop thread if already running
926 // Initialize connection
927 m_con.setTimeoutMs(30);
931 m_thread.setRun(true);
934 dout_server<<"Server started on port "<<port<<std::endl;
939 DSTACK(__FUNCTION_NAME);
940 // Stop threads (set run=false first so both start stopping)
941 m_thread.setRun(false);
942 m_emergethread.setRun(false);
944 m_emergethread.stop();
946 dout_server<<"Server threads stopped"<<std::endl;
949 void Server::step(float dtime)
951 DSTACK(__FUNCTION_NAME);
956 JMutexAutoLock lock(m_step_dtime_mutex);
957 m_step_dtime += dtime;
961 void Server::AsyncRunStep()
963 DSTACK(__FUNCTION_NAME);
967 JMutexAutoLock lock1(m_step_dtime_mutex);
968 dtime = m_step_dtime;
971 // Send blocks to clients
977 //dstream<<"Server steps "<<dtime<<std::endl;
978 //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
981 JMutexAutoLock lock1(m_step_dtime_mutex);
982 m_step_dtime -= dtime;
989 m_uptime.set(m_uptime.get() + dtime);
996 m_time_counter += dtime;
997 f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
998 u32 units = (u32)(m_time_counter*speed);
999 m_time_counter -= (f32)units / speed;
1000 m_time_of_day.set((m_time_of_day.get() + units) % 24000);
1002 //dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
1005 Send to clients at constant intervals
1008 m_time_of_day_send_timer -= dtime;
1009 if(m_time_of_day_send_timer < 0.0)
1011 m_time_of_day_send_timer = g_settings.getFloat("time_send_interval");
1013 //JMutexAutoLock envlock(m_env_mutex);
1014 JMutexAutoLock conlock(m_con_mutex);
1016 for(core::map<u16, RemoteClient*>::Iterator
1017 i = m_clients.getIterator();
1018 i.atEnd() == false; i++)
1020 RemoteClient *client = i.getNode()->getValue();
1021 //Player *player = m_env.getPlayer(client->peer_id);
1023 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1024 m_time_of_day.get());
1026 m_con.Send(client->peer_id, 0, data, true);
1032 // Process connection's timeouts
1033 JMutexAutoLock lock2(m_con_mutex);
1034 m_con.RunTimeouts(dtime);
1038 // This has to be called so that the client list gets synced
1039 // with the peer list of the connection
1040 handlePeerChanges();
1045 // This also runs Map's timers
1046 JMutexAutoLock lock(m_env_mutex);
1060 if(g_settings.getBool("endless_water") == false)
1065 float &counter = m_flowwater_timer;
1067 if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
1072 core::map<v3s16, MapBlock*> modified_blocks;
1076 JMutexAutoLock envlock(m_env_mutex);
1078 MapVoxelManipulator v(&m_env.getMap());
1079 v.m_disable_water_climb =
1080 g_settings.getBool("disable_water_climb");
1082 if(g_settings.getBool("endless_water") == false)
1083 v.flowWater(m_flow_active_nodes, 0, false, 250);
1085 v.flowWater(m_flow_active_nodes, 0, false, 50);
1087 v.blitBack(modified_blocks);
1089 ServerMap &map = ((ServerMap&)m_env.getMap());
1092 core::map<v3s16, MapBlock*> lighting_modified_blocks;
1093 map.updateLighting(modified_blocks, lighting_modified_blocks);
1095 // Add blocks modified by lighting to modified_blocks
1096 for(core::map<v3s16, MapBlock*>::Iterator
1097 i = lighting_modified_blocks.getIterator();
1098 i.atEnd() == false; i++)
1100 MapBlock *block = i.getNode()->getValue();
1101 modified_blocks.insert(block->getPos(), block);
1106 Set the modified blocks unsent for all the clients
1109 JMutexAutoLock lock2(m_con_mutex);
1111 for(core::map<u16, RemoteClient*>::Iterator
1112 i = m_clients.getIterator();
1113 i.atEnd() == false; i++)
1115 RemoteClient *client = i.getNode()->getValue();
1117 if(modified_blocks.size() > 0)
1119 // Remove block from sent history
1120 client->SetBlocksNotSent(modified_blocks);
1124 } // interval counter
1127 // Periodically print some info
1129 float &counter = m_print_info_timer;
1135 JMutexAutoLock lock2(m_con_mutex);
1137 for(core::map<u16, RemoteClient*>::Iterator
1138 i = m_clients.getIterator();
1139 i.atEnd() == false; i++)
1141 //u16 peer_id = i.getNode()->getKey();
1142 RemoteClient *client = i.getNode()->getValue();
1143 client->PrintInfo(std::cout);
1151 NOTE: Some of this could be moved to RemoteClient
1155 JMutexAutoLock envlock(m_env_mutex);
1156 JMutexAutoLock conlock(m_con_mutex);
1158 for(core::map<u16, RemoteClient*>::Iterator
1159 i = m_clients.getIterator();
1160 i.atEnd() == false; i++)
1162 RemoteClient *client = i.getNode()->getValue();
1163 Player *player = m_env.getPlayer(client->peer_id);
1165 JMutexAutoLock digmutex(client->m_dig_mutex);
1167 if(client->m_dig_tool_item == -1)
1170 client->m_dig_time_remaining -= dtime;
1172 if(client->m_dig_time_remaining > 0)
1174 client->m_time_from_building.set(0.0);
1178 v3s16 p_under = client->m_dig_position;
1180 // Mandatory parameter; actually used for nothing
1181 core::map<v3s16, MapBlock*> modified_blocks;
1187 // Get material at position
1188 material = m_env.getMap().getNode(p_under).d;
1189 // If it's not diggable, do nothing
1190 if(content_diggable(material) == false)
1192 derr_server<<"Server: Not finishing digging: Node not diggable"
1194 client->m_dig_tool_item = -1;
1198 catch(InvalidPositionException &e)
1200 derr_server<<"Server: Not finishing digging: Node not found"
1202 client->m_dig_tool_item = -1;
1208 SharedBuffer<u8> reply(replysize);
1209 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1210 writeS16(&reply[2], p_under.X);
1211 writeS16(&reply[4], p_under.Y);
1212 writeS16(&reply[6], p_under.Z);
1214 m_con.SendToAll(0, reply, true);
1216 if(g_settings.getBool("creative_mode") == false)
1218 // Add to inventory and send inventory
1219 InventoryItem *item = new MaterialItem(material, 1);
1220 player->inventory.addItem("main", item);
1221 SendInventory(player->peer_id);
1226 (this takes some time so it is done after the quick stuff)
1228 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1234 // Update water pressure around modification
1235 // This also adds it to m_flow_active_nodes if appropriate
1237 MapVoxelManipulator v(&m_env.getMap());
1238 v.m_disable_water_climb =
1239 g_settings.getBool("disable_water_climb");
1241 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1245 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1247 catch(ProcessingLimitException &e)
1249 dstream<<"Processing limit reached (1)"<<std::endl;
1252 v.blitBack(modified_blocks);
1257 // Send object positions
1259 float &counter = m_objectdata_timer;
1261 if(counter >= g_settings.getFloat("objectdata_interval"))
1263 JMutexAutoLock lock1(m_env_mutex);
1264 JMutexAutoLock lock2(m_con_mutex);
1265 SendObjectData(counter);
1271 // Trigger emergethread (it gets somehow gets to a
1272 // non-triggered but bysy state sometimes)
1274 float &counter = m_emergethread_trigger_timer;
1280 m_emergethread.trigger();
1286 float &counter = m_savemap_timer;
1288 if(counter >= g_settings.getFloat("server_map_save_interval"))
1292 JMutexAutoLock lock(m_env_mutex);
1294 // Save only changed parts
1295 m_env.getMap().save(true);
1297 // Delete unused sectors
1298 u32 deleted_count = m_env.getMap().deleteUnusedSectors(
1299 g_settings.getFloat("server_unload_unused_sectors_timeout"));
1300 if(deleted_count > 0)
1302 dout_server<<"Server: Unloaded "<<deleted_count
1303 <<" sectors from memory"<<std::endl;
1309 void Server::Receive()
1311 DSTACK(__FUNCTION_NAME);
1312 u32 data_maxsize = 10000;
1313 Buffer<u8> data(data_maxsize);
1318 JMutexAutoLock conlock(m_con_mutex);
1319 datasize = m_con.Receive(peer_id, *data, data_maxsize);
1322 // This has to be called so that the client list gets synced
1323 // with the peer list of the connection
1324 handlePeerChanges();
1326 ProcessData(*data, datasize, peer_id);
1328 catch(con::InvalidIncomingDataException &e)
1330 derr_server<<"Server::Receive(): "
1331 "InvalidIncomingDataException: what()="
1332 <<e.what()<<std::endl;
1334 catch(con::PeerNotFoundException &e)
1336 //NOTE: This is not needed anymore
1338 // The peer has been disconnected.
1339 // Find the associated player and remove it.
1341 /*JMutexAutoLock envlock(m_env_mutex);
1343 dout_server<<"ServerThread: peer_id="<<peer_id
1344 <<" has apparently closed connection. "
1345 <<"Removing player."<<std::endl;
1347 m_env.removePlayer(peer_id);*/
1351 void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
1353 DSTACK(__FUNCTION_NAME);
1354 // Environment is locked first.
1355 JMutexAutoLock envlock(m_env_mutex);
1356 JMutexAutoLock conlock(m_con_mutex);
1360 peer = m_con.GetPeer(peer_id);
1362 catch(con::PeerNotFoundException &e)
1364 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
1365 <<peer_id<<" not found"<<std::endl;
1369 //u8 peer_ser_ver = peer->serialization_version;
1370 u8 peer_ser_ver = getClient(peer->id)->serialization_version;
1378 ToServerCommand command = (ToServerCommand)readU16(&data[0]);
1380 if(command == TOSERVER_INIT)
1382 // [0] u16 TOSERVER_INIT
1383 // [2] u8 SER_FMT_VER_HIGHEST
1384 // [3] u8[20] player_name
1389 derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
1390 <<peer->id<<std::endl;
1392 // First byte after command is maximum supported
1393 // serialization version
1394 u8 client_max = data[2];
1395 u8 our_max = SER_FMT_VER_HIGHEST;
1396 // Use the highest version supported by both
1397 u8 deployed = core::min_(client_max, our_max);
1398 // If it's lower than the lowest supported, give up.
1399 if(deployed < SER_FMT_VER_LOWEST)
1400 deployed = SER_FMT_VER_INVALID;
1402 //peer->serialization_version = deployed;
1403 getClient(peer->id)->pending_serialization_version = deployed;
1405 if(deployed == SER_FMT_VER_INVALID)
1407 derr_server<<DTIME<<"Server: Cannot negotiate "
1408 "serialization version with peer "
1409 <<peer_id<<std::endl;
1417 Player *player = m_env.getPlayer(peer_id);
1419 // Check if player doesn't exist
1421 throw con::InvalidIncomingDataException
1422 ("Server::ProcessData(): INIT: Player doesn't exist");
1424 // update name if it was supplied
1425 if(datasize >= 20+3)
1428 player->updateName((const char*)&data[3]);
1431 // Now answer with a TOCLIENT_INIT
1433 SharedBuffer<u8> reply(2+1+6);
1434 writeU16(&reply[0], TOCLIENT_INIT);
1435 writeU8(&reply[2], deployed);
1436 writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
1438 m_con.Send(peer_id, 0, reply, true);
1442 if(command == TOSERVER_INIT2)
1444 derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
1445 <<peer->id<<std::endl;
1448 getClient(peer->id)->serialization_version
1449 = getClient(peer->id)->pending_serialization_version;
1452 Send some initialization data
1455 // Send player info to all players
1458 // Send inventory to player
1459 SendInventory(peer->id);
1463 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1464 m_time_of_day.get());
1465 m_con.Send(peer->id, 0, data, true);
1468 // Send information about server to player in chat
1470 std::wostringstream os(std::ios_base::binary);
1473 os<<L"uptime="<<m_uptime.get();
1474 // Information about clients
1476 for(core::map<u16, RemoteClient*>::Iterator
1477 i = m_clients.getIterator();
1478 i.atEnd() == false; i++)
1480 // Get client and check that it is valid
1481 RemoteClient *client = i.getNode()->getValue();
1482 assert(client->peer_id == i.getNode()->getKey());
1483 if(client->serialization_version == SER_FMT_VER_INVALID)
1485 // Get name of player
1486 std::wstring name = L"unknown";
1487 Player *player = m_env.getPlayer(client->peer_id);
1489 name = narrow_to_wide(player->getName());
1490 // Add name to information string
1495 SendChatMessage(peer_id, os.str());
1498 // Send information about joining in chat
1500 std::wstring name = L"unknown";
1501 Player *player = m_env.getPlayer(peer_id);
1503 name = narrow_to_wide(player->getName());
1505 std::wstring message;
1508 message += L" joined game";
1509 BroadcastChatMessage(message);
1515 if(peer_ser_ver == SER_FMT_VER_INVALID)
1517 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
1518 " serialization format invalid or not initialized."
1519 " Skipping incoming command="<<command<<std::endl;
1523 Player *player = m_env.getPlayer(peer_id);
1526 derr_server<<"Server::ProcessData(): Cancelling: "
1527 "No player for peer_id="<<peer_id
1531 if(command == TOSERVER_PLAYERPOS)
1533 if(datasize < 2+12+12+4+4)
1537 v3s32 ps = readV3S32(&data[start+2]);
1538 v3s32 ss = readV3S32(&data[start+2+12]);
1539 f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
1540 f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
1541 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
1542 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
1543 pitch = wrapDegrees(pitch);
1544 yaw = wrapDegrees(yaw);
1545 player->setPosition(position);
1546 player->setSpeed(speed);
1547 player->setPitch(pitch);
1548 player->setYaw(yaw);
1550 /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
1551 <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
1552 <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
1554 else if(command == TOSERVER_GOTBLOCKS)
1567 u16 count = data[2];
1568 for(u16 i=0; i<count; i++)
1570 if((s16)datasize < 2+1+(i+1)*6)
1571 throw con::InvalidIncomingDataException
1572 ("GOTBLOCKS length is too short");
1573 v3s16 p = readV3S16(&data[2+1+i*6]);
1574 /*dstream<<"Server: GOTBLOCKS ("
1575 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1576 RemoteClient *client = getClient(peer_id);
1577 client->GotBlock(p);
1580 else if(command == TOSERVER_DELETEDBLOCKS)
1593 u16 count = data[2];
1594 for(u16 i=0; i<count; i++)
1596 if((s16)datasize < 2+1+(i+1)*6)
1597 throw con::InvalidIncomingDataException
1598 ("DELETEDBLOCKS length is too short");
1599 v3s16 p = readV3S16(&data[2+1+i*6]);
1600 /*dstream<<"Server: DELETEDBLOCKS ("
1601 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1602 RemoteClient *client = getClient(peer_id);
1603 client->SetBlockNotSent(p);
1606 else if(command == TOSERVER_CLICK_OBJECT)
1613 [2] u8 button (0=left, 1=right)
1618 u8 button = readU8(&data[2]);
1620 p.X = readS16(&data[3]);
1621 p.Y = readS16(&data[5]);
1622 p.Z = readS16(&data[7]);
1623 s16 id = readS16(&data[9]);
1624 //u16 item_i = readU16(&data[11]);
1626 MapBlock *block = NULL;
1629 block = m_env.getMap().getBlockNoCreate(p);
1631 catch(InvalidPositionException &e)
1633 derr_server<<"CLICK_OBJECT block not found"<<std::endl;
1637 MapBlockObject *obj = block->getObject(id);
1641 derr_server<<"CLICK_OBJECT object not found"<<std::endl;
1645 //TODO: Check that object is reasonably close
1650 InventoryList *ilist = player->inventory.getList("main");
1651 if(g_settings.getBool("creative_mode") == false && ilist != NULL)
1654 // Skip if inventory has no free space
1655 if(ilist->getUsedSlots() == ilist->getSize())
1657 dout_server<<"Player inventory has no free space"<<std::endl;
1662 Create the inventory item
1664 InventoryItem *item = NULL;
1665 // If it is an item-object, take the item from it
1666 if(obj->getTypeId() == MAPBLOCKOBJECT_TYPE_ITEM)
1668 item = ((ItemObject*)obj)->createInventoryItem();
1670 // Else create an item of the object
1673 item = new MapBlockObjectItem
1674 (obj->getInventoryString());
1677 // Add to inventory and send inventory
1678 ilist->addItem(item);
1679 SendInventory(player->peer_id);
1682 // Remove from block
1683 block->removeObject(id);
1686 else if(command == TOSERVER_GROUND_ACTION)
1694 [3] v3s16 nodepos_undersurface
1695 [9] v3s16 nodepos_abovesurface
1700 2: stop digging (all parameters ignored)
1702 u8 action = readU8(&data[2]);
1704 p_under.X = readS16(&data[3]);
1705 p_under.Y = readS16(&data[5]);
1706 p_under.Z = readS16(&data[7]);
1708 p_over.X = readS16(&data[9]);
1709 p_over.Y = readS16(&data[11]);
1710 p_over.Z = readS16(&data[13]);
1711 u16 item_i = readU16(&data[15]);
1713 //TODO: Check that target is reasonably close
1721 NOTE: This can be used in the future to check if
1722 somebody is cheating, by checking the timing.
1729 else if(action == 2)
1732 RemoteClient *client = getClient(peer->id);
1733 JMutexAutoLock digmutex(client->m_dig_mutex);
1734 client->m_dig_tool_item = -1;
1739 3: Digging completed
1741 else if(action == 3)
1743 // Mandatory parameter; actually used for nothing
1744 core::map<v3s16, MapBlock*> modified_blocks;
1750 // Get material at position
1751 material = m_env.getMap().getNode(p_under).d;
1752 // If it's not diggable, do nothing
1753 if(content_diggable(material) == false)
1755 derr_server<<"Server: Not finishing digging: Node not diggable"
1760 catch(InvalidPositionException &e)
1762 derr_server<<"Server: Not finishing digging: Node not found"
1767 //TODO: Send to only other clients
1770 Send the removal to all other clients
1775 SharedBuffer<u8> reply(replysize);
1776 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1777 writeS16(&reply[2], p_under.X);
1778 writeS16(&reply[4], p_under.Y);
1779 writeS16(&reply[6], p_under.Z);
1781 for(core::map<u16, RemoteClient*>::Iterator
1782 i = m_clients.getIterator();
1783 i.atEnd() == false; i++)
1785 // Get client and check that it is valid
1786 RemoteClient *client = i.getNode()->getValue();
1787 assert(client->peer_id == i.getNode()->getKey());
1788 if(client->serialization_version == SER_FMT_VER_INVALID)
1791 // Don't send if it's the same one
1792 if(peer_id == client->peer_id)
1796 m_con.Send(client->peer_id, 0, reply, true);
1800 Update and send inventory
1803 if(g_settings.getBool("creative_mode") == false)
1808 InventoryList *mlist = player->inventory.getList("main");
1811 InventoryItem *item = mlist->getItem(item_i);
1812 if(item && (std::string)item->getName() == "ToolItem")
1814 ToolItem *titem = (ToolItem*)item;
1815 std::string toolname = titem->getToolName();
1817 // Get digging properties for material and tool
1818 DiggingProperties prop =
1819 getDiggingProperties(material, toolname);
1821 if(prop.diggable == false)
1823 derr_server<<"Server: WARNING: Player digged"
1824 <<" with impossible material + tool"
1825 <<" combination"<<std::endl;
1828 bool weared_out = titem->addWear(prop.wear);
1832 mlist->deleteItem(item_i);
1838 Add digged item to inventory
1840 InventoryItem *item = new MaterialItem(material, 1);
1841 player->inventory.addItem("main", item);
1846 SendInventory(player->peer_id);
1851 (this takes some time so it is done after the quick stuff)
1853 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1859 // Update water pressure around modification
1860 // This also adds it to m_flow_active_nodes if appropriate
1862 MapVoxelManipulator v(&m_env.getMap());
1863 v.m_disable_water_climb =
1864 g_settings.getBool("disable_water_climb");
1866 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1870 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1872 catch(ProcessingLimitException &e)
1874 dstream<<"Processing limit reached (1)"<<std::endl;
1877 v.blitBack(modified_blocks);
1883 else if(action == 1)
1886 InventoryList *ilist = player->inventory.getList("main");
1891 InventoryItem *item = ilist->getItem(item_i);
1893 // If there is no item, it is not possible to add it anywhere
1898 Handle material items
1900 if(std::string("MaterialItem") == item->getName())
1903 // Don't add a node if this is not a free space
1904 MapNode n2 = m_env.getMap().getNode(p_over);
1905 if(content_buildable_to(n2.d) == false)
1908 catch(InvalidPositionException &e)
1910 derr_server<<"Server: Ignoring ADDNODE: Node not found"
1915 // Reset build time counter
1916 getClient(peer->id)->m_time_from_building.set(0.0);
1919 MaterialItem *mitem = (MaterialItem*)item;
1921 n.d = mitem->getMaterial();
1922 if(content_directional(n.d))
1923 n.dir = packDir(p_under - p_over);
1927 u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
1928 SharedBuffer<u8> reply(replysize);
1929 writeU16(&reply[0], TOCLIENT_ADDNODE);
1930 writeS16(&reply[2], p_over.X);
1931 writeS16(&reply[4], p_over.Y);
1932 writeS16(&reply[6], p_over.Z);
1933 n.serialize(&reply[8], peer_ser_ver);
1935 m_con.SendToAll(0, reply, true);
1940 InventoryList *ilist = player->inventory.getList("main");
1941 if(g_settings.getBool("creative_mode") == false && ilist)
1943 // Remove from inventory and send inventory
1944 if(mitem->getCount() == 1)
1945 ilist->deleteItem(item_i);
1949 SendInventory(peer_id);
1955 This takes some time so it is done after the quick stuff
1957 core::map<v3s16, MapBlock*> modified_blocks;
1958 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
1964 InventoryList *ilist = player->inventory.getList("main");
1965 if(g_settings.getBool("creative_mode") == false && ilist)
1967 // Remove from inventory and send inventory
1968 if(mitem->getCount() == 1)
1969 ilist->deleteItem(item_i);
1973 SendInventory(peer_id);
1979 This takes some time so it is done after the quick stuff
1981 core::map<v3s16, MapBlock*> modified_blocks;
1982 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
1985 Set the modified blocks unsent for all the clients
1988 //JMutexAutoLock lock2(m_con_mutex);
1990 for(core::map<u16, RemoteClient*>::Iterator
1991 i = m_clients.getIterator();
1992 i.atEnd() == false; i++)
1994 RemoteClient *client = i.getNode()->getValue();
1996 if(modified_blocks.size() > 0)
1998 // Remove block from sent history
1999 client->SetBlocksNotSent(modified_blocks);
2008 // Update water pressure around modification
2009 // This also adds it to m_flow_active_nodes if appropriate
2011 MapVoxelManipulator v(&m_env.getMap());
2012 v.m_disable_water_climb =
2013 g_settings.getBool("disable_water_climb");
2015 VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
2019 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2021 catch(ProcessingLimitException &e)
2023 dstream<<"Processing limit reached (1)"<<std::endl;
2026 v.blitBack(modified_blocks);
2033 v3s16 blockpos = getNodeBlockPos(p_over);
2035 MapBlock *block = NULL;
2038 block = m_env.getMap().getBlockNoCreate(blockpos);
2040 catch(InvalidPositionException &e)
2042 derr_server<<"Error while placing object: "
2043 "block not found"<<std::endl;
2047 v3s16 block_pos_i_on_map = block->getPosRelative();
2048 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
2050 v3f pos = intToFloat(p_over);
2051 pos -= block_pos_f_on_map;
2053 /*dout_server<<"pos="
2054 <<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
2057 MapBlockObject *obj = NULL;
2060 Handle block object items
2062 if(std::string("MBOItem") == item->getName())
2064 MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;
2066 /*dout_server<<"Trying to place a MapBlockObjectItem: "
2067 "inventorystring=\""
2068 <<oitem->getInventoryString()
2069 <<"\""<<std::endl;*/
2071 obj = oitem->createObject
2072 (pos, player->getYaw(), player->getPitch());
2079 dout_server<<"Placing a miscellaneous item on map"
2082 Create an ItemObject that contains the item.
2084 ItemObject *iobj = new ItemObject(NULL, -1, pos);
2085 std::ostringstream os(std::ios_base::binary);
2086 item->serialize(os);
2087 dout_server<<"Item string is \""<<os.str()<<"\""<<std::endl;
2088 iobj->setItemString(os.str());
2094 derr_server<<"WARNING: item resulted in NULL object, "
2095 <<"not placing onto map"
2100 block->addObject(obj);
2102 dout_server<<"Placed object"<<std::endl;
2104 InventoryList *ilist = player->inventory.getList("main");
2105 if(g_settings.getBool("creative_mode") == false && ilist)
2107 // Remove from inventory and send inventory
2108 ilist->deleteItem(item_i);
2110 SendInventory(peer_id);
2118 Catch invalid actions
2122 derr_server<<"WARNING: Server: Invalid action "
2123 <<action<<std::endl;
2127 else if(command == TOSERVER_RELEASE)
2136 dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
2139 else if(command == TOSERVER_SIGNTEXT)
2148 std::string datastring((char*)&data[2], datasize-2);
2149 std::istringstream is(datastring, std::ios_base::binary);
2152 is.read((char*)buf, 6);
2153 v3s16 blockpos = readV3S16(buf);
2154 is.read((char*)buf, 2);
2155 s16 id = readS16(buf);
2156 is.read((char*)buf, 2);
2157 u16 textlen = readU16(buf);
2159 for(u16 i=0; i<textlen; i++)
2161 is.read((char*)buf, 1);
2162 text += (char)buf[0];
2165 MapBlock *block = NULL;
2168 block = m_env.getMap().getBlockNoCreate(blockpos);
2170 catch(InvalidPositionException &e)
2172 derr_server<<"Error while setting sign text: "
2173 "block not found"<<std::endl;
2177 MapBlockObject *obj = block->getObject(id);
2180 derr_server<<"Error while setting sign text: "
2181 "object not found"<<std::endl;
2185 if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
2187 derr_server<<"Error while setting sign text: "
2188 "object is not a sign"<<std::endl;
2192 ((SignObject*)obj)->setText(text);
2194 obj->getBlock()->setChangedFlag();
2196 else if(command == TOSERVER_INVENTORY_ACTION)
2198 /*// Ignore inventory changes if in creative mode
2199 if(g_settings.getBool("creative_mode") == true)
2201 dstream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode"
2205 // Strip command and create a stream
2206 std::string datastring((char*)&data[2], datasize-2);
2207 dstream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
2208 std::istringstream is(datastring, std::ios_base::binary);
2210 InventoryAction *a = InventoryAction::deSerialize(is);
2214 Handle craftresult specially if not in creative mode
2216 bool disable_action = false;
2217 if(a->getType() == IACTION_MOVE
2218 && g_settings.getBool("creative_mode") == false)
2220 IMoveAction *ma = (IMoveAction*)a;
2221 // Don't allow moving anything to craftresult
2222 if(ma->to_name == "craftresult")
2225 disable_action = true;
2227 // When something is removed from craftresult
2228 if(ma->from_name == "craftresult")
2230 disable_action = true;
2231 // Remove stuff from craft
2232 InventoryList *clist = player->inventory.getList("craft");
2235 u16 count = ma->count;
2238 clist->decrementMaterials(count);
2241 // Feed action to player inventory
2242 a->apply(&player->inventory);
2245 // If something appeared in craftresult, throw it
2247 InventoryList *rlist = player->inventory.getList("craftresult");
2248 InventoryList *mlist = player->inventory.getList("main");
2249 if(rlist && mlist && rlist->getUsedSlots() == 1)
2251 InventoryItem *item1 = rlist->changeItem(0, NULL);
2252 mlist->addItem(item1);
2256 if(disable_action == false)
2258 // Feed action to player inventory
2259 a->apply(&player->inventory);
2264 SendInventory(player->peer_id);
2268 dstream<<"TOSERVER_INVENTORY_ACTION: "
2269 <<"InventoryAction::deSerialize() returned NULL"
2273 else if(command == TOSERVER_CHAT_MESSAGE)
2281 std::string datastring((char*)&data[2], datasize-2);
2282 std::istringstream is(datastring, std::ios_base::binary);
2285 is.read((char*)buf, 2);
2286 u16 len = readU16(buf);
2288 std::wstring message;
2289 for(u16 i=0; i<len; i++)
2291 is.read((char*)buf, 2);
2292 message += (wchar_t)readU16(buf);
2295 // Get player name of this client
2296 std::wstring name = narrow_to_wide(player->getName());
2298 std::wstring line = std::wstring(L"<")+name+L"> "+message;
2300 dstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl;
2303 Send the message to all other clients
2305 for(core::map<u16, RemoteClient*>::Iterator
2306 i = m_clients.getIterator();
2307 i.atEnd() == false; i++)
2309 // Get client and check that it is valid
2310 RemoteClient *client = i.getNode()->getValue();
2311 assert(client->peer_id == i.getNode()->getKey());
2312 if(client->serialization_version == SER_FMT_VER_INVALID)
2315 // Don't send if it's the same one
2316 if(peer_id == client->peer_id)
2319 SendChatMessage(client->peer_id, line);
2324 derr_server<<"WARNING: Server::ProcessData(): Ignoring "
2325 "unknown command "<<command<<std::endl;
2329 catch(SendFailedException &e)
2331 derr_server<<"Server::ProcessData(): SendFailedException: "
2337 /*void Server::Send(u16 peer_id, u16 channelnum,
2338 SharedBuffer<u8> data, bool reliable)
2340 JMutexAutoLock lock(m_con_mutex);
2341 m_con.Send(peer_id, channelnum, data, reliable);
2344 void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
2346 DSTACK(__FUNCTION_NAME);
2348 Create a packet with the block in the right format
2351 std::ostringstream os(std::ios_base::binary);
2352 block->serialize(os, ver);
2353 std::string s = os.str();
2354 SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
2356 u32 replysize = 8 + blockdata.getSize();
2357 SharedBuffer<u8> reply(replysize);
2358 v3s16 p = block->getPos();
2359 writeU16(&reply[0], TOCLIENT_BLOCKDATA);
2360 writeS16(&reply[2], p.X);
2361 writeS16(&reply[4], p.Y);
2362 writeS16(&reply[6], p.Z);
2363 memcpy(&reply[8], *blockdata, blockdata.getSize());
2365 /*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2366 <<": \tpacket size: "<<replysize<<std::endl;*/
2371 m_con.Send(peer_id, 1, reply, true);
2374 core::list<PlayerInfo> Server::getPlayerInfo()
2376 DSTACK(__FUNCTION_NAME);
2377 JMutexAutoLock envlock(m_env_mutex);
2378 JMutexAutoLock conlock(m_con_mutex);
2380 core::list<PlayerInfo> list;
2382 core::list<Player*> players = m_env.getPlayers();
2384 core::list<Player*>::Iterator i;
2385 for(i = players.begin();
2386 i != players.end(); i++)
2390 Player *player = *i;
2392 con::Peer *peer = m_con.GetPeer(player->peer_id);
2394 info.address = peer->address;
2395 info.avg_rtt = peer->avg_rtt;
2397 catch(con::PeerNotFoundException &e)
2399 // Outdated peer info
2401 info.address = Address(0,0,0,0,0);
2405 snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
2406 info.position = player->getPosition();
2408 list.push_back(info);
2414 void Server::peerAdded(con::Peer *peer)
2416 DSTACK(__FUNCTION_NAME);
2417 dout_server<<"Server::peerAdded(): peer->id="
2418 <<peer->id<<std::endl;
2421 c.type = PEER_ADDED;
2422 c.peer_id = peer->id;
2424 m_peer_change_queue.push_back(c);
2427 void Server::deletingPeer(con::Peer *peer, bool timeout)
2429 DSTACK(__FUNCTION_NAME);
2430 dout_server<<"Server::deletingPeer(): peer->id="
2431 <<peer->id<<", timeout="<<timeout<<std::endl;
2434 c.type = PEER_REMOVED;
2435 c.peer_id = peer->id;
2436 c.timeout = timeout;
2437 m_peer_change_queue.push_back(c);
2440 void Server::SendObjectData(float dtime)
2442 DSTACK(__FUNCTION_NAME);
2444 core::map<v3s16, bool> stepped_blocks;
2446 for(core::map<u16, RemoteClient*>::Iterator
2447 i = m_clients.getIterator();
2448 i.atEnd() == false; i++)
2450 u16 peer_id = i.getNode()->getKey();
2451 RemoteClient *client = i.getNode()->getValue();
2452 assert(client->peer_id == peer_id);
2454 if(client->serialization_version == SER_FMT_VER_INVALID)
2457 client->SendObjectData(this, dtime, stepped_blocks);
2461 void Server::SendPlayerInfos()
2463 DSTACK(__FUNCTION_NAME);
2465 //JMutexAutoLock envlock(m_env_mutex);
2467 core::list<Player*> players = m_env.getPlayers();
2469 u32 player_count = players.getSize();
2470 u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
2472 SharedBuffer<u8> data(datasize);
2473 writeU16(&data[0], TOCLIENT_PLAYERINFO);
2476 core::list<Player*>::Iterator i;
2477 for(i = players.begin();
2478 i != players.end(); i++)
2480 Player *player = *i;
2482 /*dstream<<"Server sending player info for player with "
2483 "peer_id="<<player->peer_id<<std::endl;*/
2485 writeU16(&data[start], player->peer_id);
2486 snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
2487 start += 2+PLAYERNAME_SIZE;
2490 //JMutexAutoLock conlock(m_con_mutex);
2493 m_con.SendToAll(0, data, true);
2511 ItemSpec(enum ItemSpecType a_type, std::string a_name):
2517 ItemSpec(enum ItemSpecType a_type, u16 a_num):
2523 enum ItemSpecType type;
2524 // Only other one of these is used
2530 items: a pointer to an array of 9 pointers to items
2531 specs: a pointer to an array of 9 ItemSpecs
2533 bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
2535 u16 items_min_x = 100;
2536 u16 items_max_x = 100;
2537 u16 items_min_y = 100;
2538 u16 items_max_y = 100;
2539 for(u16 y=0; y<3; y++)
2540 for(u16 x=0; x<3; x++)
2542 if(items[y*3 + x] == NULL)
2544 if(items_min_x == 100 || x < items_min_x)
2546 if(items_min_y == 100 || y < items_min_y)
2548 if(items_max_x == 100 || x > items_max_x)
2550 if(items_max_y == 100 || y > items_max_y)
2553 // No items at all, just return false
2554 if(items_min_x == 100)
2557 u16 items_w = items_max_x - items_min_x + 1;
2558 u16 items_h = items_max_y - items_min_y + 1;
2560 u16 specs_min_x = 100;
2561 u16 specs_max_x = 100;
2562 u16 specs_min_y = 100;
2563 u16 specs_max_y = 100;
2564 for(u16 y=0; y<3; y++)
2565 for(u16 x=0; x<3; x++)
2567 if(specs[y*3 + x].type == ITEM_NONE)
2569 if(specs_min_x == 100 || x < specs_min_x)
2571 if(specs_min_y == 100 || y < specs_min_y)
2573 if(specs_max_x == 100 || x > specs_max_x)
2575 if(specs_max_y == 100 || y > specs_max_y)
2578 // No specs at all, just return false
2579 if(specs_min_x == 100)
2582 u16 specs_w = specs_max_x - specs_min_x + 1;
2583 u16 specs_h = specs_max_y - specs_min_y + 1;
2586 if(items_w != specs_w || items_h != specs_h)
2589 for(u16 y=0; y<specs_h; y++)
2590 for(u16 x=0; x<specs_w; x++)
2592 u16 items_x = items_min_x + x;
2593 u16 items_y = items_min_y + y;
2594 u16 specs_x = specs_min_x + x;
2595 u16 specs_y = specs_min_y + y;
2596 InventoryItem *item = items[items_y * 3 + items_x];
2597 ItemSpec &spec = specs[specs_y * 3 + specs_x];
2599 if(spec.type == ITEM_NONE)
2601 // Has to be no item
2607 // There should be an item
2611 std::string itemname = item->getName();
2613 if(spec.type == ITEM_MATERIAL)
2615 if(itemname != "MaterialItem")
2617 MaterialItem *mitem = (MaterialItem*)item;
2618 if(mitem->getMaterial() != spec.num)
2621 else if(spec.type == ITEM_CRAFT)
2623 if(itemname != "CraftItem")
2625 CraftItem *mitem = (CraftItem*)item;
2626 if(mitem->getSubName() != spec.name)
2629 else if(spec.type == ITEM_TOOL)
2631 // Not supported yet
2634 else if(spec.type == ITEM_MBO)
2636 // Not supported yet
2641 // Not supported yet
2649 void Server::SendInventory(u16 peer_id)
2651 DSTACK(__FUNCTION_NAME);
2653 Player* player = m_env.getPlayer(peer_id);
2656 Calculate crafting stuff
2658 if(g_settings.getBool("creative_mode") == false)
2660 InventoryList *clist = player->inventory.getList("craft");
2661 InventoryList *rlist = player->inventory.getList("craftresult");
2664 rlist->clearItems();
2668 InventoryItem *items[9];
2669 for(u16 i=0; i<9; i++)
2671 items[i] = clist->getItem(i);
2680 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
2681 if(checkItemCombination(items, specs))
2683 rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
2692 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2693 if(checkItemCombination(items, specs))
2695 rlist->addItem(new CraftItem("Stick", 4));
2704 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2705 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2706 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2707 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2708 specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2709 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2710 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2711 if(checkItemCombination(items, specs))
2713 rlist->addItem(new MapBlockObjectItem("Sign"));
2722 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COALSTONE);
2723 specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
2724 if(checkItemCombination(items, specs))
2726 rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
2735 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2736 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2737 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2738 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2739 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2740 if(checkItemCombination(items, specs))
2742 rlist->addItem(new ToolItem("WPick", 0));
2751 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2752 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2753 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2754 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2755 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2756 if(checkItemCombination(items, specs))
2758 rlist->addItem(new ToolItem("STPick", 0));
2767 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2768 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2769 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2770 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2771 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2772 if(checkItemCombination(items, specs))
2774 rlist->addItem(new ToolItem("MesePick", 0));
2779 } // if creative_mode == false
2785 std::ostringstream os;
2786 //os.imbue(std::locale("C"));
2788 player->inventory.serialize(os);
2790 std::string s = os.str();
2792 SharedBuffer<u8> data(s.size()+2);
2793 writeU16(&data[0], TOCLIENT_INVENTORY);
2794 memcpy(&data[2], s.c_str(), s.size());
2797 m_con.Send(peer_id, 0, data, true);
2800 void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
2802 DSTACK(__FUNCTION_NAME);
2804 std::ostringstream os(std::ios_base::binary);
2808 writeU16(buf, TOCLIENT_CHAT_MESSAGE);
2809 os.write((char*)buf, 2);
2812 writeU16(buf, message.size());
2813 os.write((char*)buf, 2);
2816 for(u32 i=0; i<message.size(); i++)
2820 os.write((char*)buf, 2);
2824 std::string s = os.str();
2825 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
2827 m_con.Send(peer_id, 0, data, true);
2830 void Server::BroadcastChatMessage(const std::wstring &message)
2832 for(core::map<u16, RemoteClient*>::Iterator
2833 i = m_clients.getIterator();
2834 i.atEnd() == false; i++)
2836 // Get client and check that it is valid
2837 RemoteClient *client = i.getNode()->getValue();
2838 assert(client->peer_id == i.getNode()->getKey());
2839 if(client->serialization_version == SER_FMT_VER_INVALID)
2842 SendChatMessage(client->peer_id, message);
2846 void Server::SendBlocks(float dtime)
2848 DSTACK(__FUNCTION_NAME);
2850 JMutexAutoLock envlock(m_env_mutex);
2852 core::array<PrioritySortedBlockTransfer> queue;
2854 s32 total_sending = 0;
2856 for(core::map<u16, RemoteClient*>::Iterator
2857 i = m_clients.getIterator();
2858 i.atEnd() == false; i++)
2860 RemoteClient *client = i.getNode()->getValue();
2861 assert(client->peer_id == i.getNode()->getKey());
2863 total_sending += client->SendingCount();
2865 if(client->serialization_version == SER_FMT_VER_INVALID)
2868 client->GetNextBlocks(this, dtime, queue);
2872 // Lowest priority number comes first.
2873 // Lowest is most important.
2876 JMutexAutoLock conlock(m_con_mutex);
2878 for(u32 i=0; i<queue.size(); i++)
2880 //TODO: Calculate limit dynamically
2881 if(total_sending >= g_settings.getS32
2882 ("max_simultaneous_block_sends_server_total"))
2885 PrioritySortedBlockTransfer q = queue[i];
2887 MapBlock *block = NULL;
2890 block = m_env.getMap().getBlockNoCreate(q.pos);
2892 catch(InvalidPositionException &e)
2897 RemoteClient *client = getClient(q.peer_id);
2899 SendBlockNoLock(q.peer_id, block, client->serialization_version);
2901 client->SentBlock(q.pos);
2908 RemoteClient* Server::getClient(u16 peer_id)
2910 DSTACK(__FUNCTION_NAME);
2911 //JMutexAutoLock lock(m_con_mutex);
2912 core::map<u16, RemoteClient*>::Node *n;
2913 n = m_clients.find(peer_id);
2914 // A client should exist for all peers
2916 return n->getValue();
2919 void Server::UpdateBlockWaterPressure(MapBlock *block,
2920 core::map<v3s16, MapBlock*> &modified_blocks)
2922 MapVoxelManipulator v(&m_env.getMap());
2923 v.m_disable_water_climb =
2924 g_settings.getBool("disable_water_climb");
2926 VoxelArea area(block->getPosRelative(),
2927 block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
2931 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2933 catch(ProcessingLimitException &e)
2935 dstream<<"Processing limit reached (1)"<<std::endl;
2938 v.blitBack(modified_blocks);
2941 void Server::handlePeerChange(PeerChange &c)
2943 JMutexAutoLock envlock(m_env_mutex);
2944 JMutexAutoLock conlock(m_con_mutex);
2946 if(c.type == PEER_ADDED)
2953 core::map<u16, RemoteClient*>::Node *n;
2954 n = m_clients.find(c.peer_id);
2955 // The client shouldn't already exist
2959 RemoteClient *client = new RemoteClient();
2960 client->peer_id = c.peer_id;
2961 m_clients.insert(client->peer_id, client);
2965 Player *player = m_env.getPlayer(c.peer_id);
2967 // The player shouldn't already exist
2968 assert(player == NULL);
2970 player = new ServerRemotePlayer();
2971 player->peer_id = c.peer_id;
2977 // We're going to throw the player to this position
2978 //v2s16 nodepos(29990,29990);
2979 //v2s16 nodepos(9990,9990);
2981 v2s16 sectorpos = getNodeSectorPos(nodepos);
2982 // Get zero sector (it could have been unloaded to disk)
2983 m_env.getMap().emergeSector(sectorpos);
2984 // Get ground height at origin
2985 f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
2986 // The sector should have been generated -> groundheight exists
2987 assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
2988 // Don't go underwater
2989 if(groundheight < WATER_LEVEL)
2990 groundheight = WATER_LEVEL;
2992 player->setPosition(intToFloat(v3s16(
2999 Add player to environment
3002 m_env.addPlayer(player);
3005 Add stuff to inventory
3008 if(g_settings.getBool("creative_mode"))
3010 // Give some good picks
3012 InventoryItem *item = new ToolItem("STPick", 0);
3013 void* r = player->inventory.addItem("main", item);
3017 InventoryItem *item = new ToolItem("MesePick", 0);
3018 void* r = player->inventory.addItem("main", item);
3025 assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
3028 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
3029 player->inventory.addItem("main", item);
3032 for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
3034 // Skip some materials
3035 if(i == CONTENT_OCEAN || i == CONTENT_TORCH)
3038 InventoryItem *item = new MaterialItem(i, 1);
3039 player->inventory.addItem("main", item);
3043 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3044 void* r = player->inventory.addItem("main", item);
3051 InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
3052 void* r = player->inventory.addItem("main", item);
3056 InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
3057 void* r = player->inventory.addItem("main", item);
3061 InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
3062 void* r = player->inventory.addItem("main", item);
3066 InventoryItem *item = new CraftItem("Stick", 4);
3067 void* r = player->inventory.addItem("main", item);
3071 InventoryItem *item = new ToolItem("WPick", 32000);
3072 void* r = player->inventory.addItem("main", item);
3076 InventoryItem *item = new ToolItem("STPick", 32000);
3077 void* r = player->inventory.addItem("main", item);
3080 /*// Give some lights
3082 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
3083 bool r = player->inventory.addItem("main", item);
3087 for(u16 i=0; i<4; i++)
3089 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3090 bool r = player->inventory.addItem("main", item);
3093 /*// Give some other stuff
3095 InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
3096 bool r = player->inventory.addItem("main", item);
3103 else if(c.type == PEER_REMOVED)
3110 core::map<u16, RemoteClient*>::Node *n;
3111 n = m_clients.find(c.peer_id);
3112 // The client should exist
3115 // Collect information about leaving in chat
3116 std::wstring message;
3118 std::wstring name = L"unknown";
3119 Player *player = m_env.getPlayer(c.peer_id);
3121 name = narrow_to_wide(player->getName());
3125 message += L" left game";
3127 message += L" (timed out)";
3132 m_env.removePlayer(c.peer_id);
3136 delete m_clients[c.peer_id];
3137 m_clients.remove(c.peer_id);
3139 // Send player info to all remaining clients
3142 // Send leave chat message to all remaining clients
3143 BroadcastChatMessage(message);
3152 void Server::handlePeerChanges()
3154 while(m_peer_change_queue.size() > 0)
3156 PeerChange c = m_peer_change_queue.pop_front();
3158 dout_server<<"Server: Handling peer change: "
3159 <<"id="<<c.peer_id<<", timeout="<<c.timeout
3162 handlePeerChange(c);