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");
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 //TimeTaker envlockwaittimer("block emerge envlock wait time");
141 JMutexAutoLock envlock(m_server->m_env_mutex);
143 //envlockwaittimer.stop();
145 //TimeTaker timer("block emerge (while env locked)");
148 bool only_from_disk = false;
151 only_from_disk = true;
153 block = map.emergeBlock(
157 lighting_invalidated_blocks);
159 // If it is a dummy, block was not found on disk
162 //dstream<<"EmergeThread: Got a dummy block"<<std::endl;
166 catch(InvalidPositionException &e)
169 // This happens when position is over limit.
175 if(debug && changed_blocks.size() > 0)
177 dout_server<<DTIME<<"Got changed_blocks: ";
178 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
179 i.atEnd() == false; i++)
181 MapBlock *block = i.getNode()->getValue();
182 v3s16 p = block->getPos();
183 dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
185 dout_server<<std::endl;
189 Update water pressure
192 m_server->UpdateBlockWaterPressure(block, modified_blocks);
194 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
195 i.atEnd() == false; i++)
197 MapBlock *block = i.getNode()->getValue();
198 m_server->UpdateBlockWaterPressure(block, modified_blocks);
199 //v3s16 p = i.getNode()->getKey();
200 //m_server->UpdateBlockWaterPressure(p, modified_blocks);
204 Collect a list of blocks that have been modified in
205 addition to the fetched one.
208 // Add all the "changed blocks" to modified_blocks
209 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
210 i.atEnd() == false; i++)
212 MapBlock *block = i.getNode()->getValue();
213 modified_blocks.insert(block->getPos(), block);
216 /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
217 <<" blocks"<<std::endl;*/
219 //TimeTaker timer("** updateLighting", g_device);
221 // Update lighting without locking the environment mutex,
222 // add modified blocks to changed blocks
223 map.updateLighting(lighting_invalidated_blocks, modified_blocks);
225 // If we got no block, there should be no invalidated blocks
228 assert(lighting_invalidated_blocks.size() == 0);
234 Set sent status of modified blocks on clients
237 // NOTE: Server's clients are also behind the connection mutex
238 JMutexAutoLock lock(m_server->m_con_mutex);
241 Add the originally fetched block to the modified list
245 modified_blocks.insert(p, block);
249 Set the modified blocks unsent for all the clients
252 for(core::map<u16, RemoteClient*>::Iterator
253 i = m_server->m_clients.getIterator();
254 i.atEnd() == false; i++)
256 RemoteClient *client = i.getNode()->getValue();
258 if(modified_blocks.size() > 0)
260 // Remove block from sent history
261 client->SetBlocksNotSent(modified_blocks);
267 END_DEBUG_EXCEPTION_HANDLER
272 void RemoteClient::GetNextBlocks(Server *server, float dtime,
273 core::array<PrioritySortedBlockTransfer> &dest)
275 DSTACK(__FUNCTION_NAME);
279 JMutexAutoLock lock(m_blocks_sent_mutex);
280 m_nearest_unsent_reset_timer += dtime;
283 // Won't send anything if already sending
285 JMutexAutoLock lock(m_blocks_sending_mutex);
287 if(m_blocks_sending.size() >= g_settings.getU16
288 ("max_simultaneous_block_sends_per_client"))
290 //dstream<<"Not sending any blocks, Queue full."<<std::endl;
295 Player *player = server->m_env.getPlayer(peer_id);
297 v3f playerpos = player->getPosition();
298 v3f playerspeed = player->getSpeed();
300 v3s16 center_nodepos = floatToInt(playerpos);
302 v3s16 center = getNodeBlockPos(center_nodepos);
305 Get the starting value of the block finder radius.
307 s16 last_nearest_unsent_d;
310 JMutexAutoLock lock(m_blocks_sent_mutex);
312 if(m_last_center != center)
314 m_nearest_unsent_d = 0;
315 m_last_center = center;
318 /*dstream<<"m_nearest_unsent_reset_timer="
319 <<m_nearest_unsent_reset_timer<<std::endl;*/
320 if(m_nearest_unsent_reset_timer > 5.0)
322 m_nearest_unsent_reset_timer = 0;
323 m_nearest_unsent_d = 0;
324 //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
327 last_nearest_unsent_d = m_nearest_unsent_d;
329 d_start = m_nearest_unsent_d;
332 u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
333 ("max_simultaneous_block_sends_per_client");
334 u16 maximum_simultaneous_block_sends =
335 maximum_simultaneous_block_sends_setting;
338 Check the time from last addNode/removeNode.
340 Decrease send rate if player is building stuff.
343 SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
344 m_time_from_building.m_value += dtime;
345 /*if(m_time_from_building.m_value
346 < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)*/
347 if(m_time_from_building.m_value < g_settings.getFloat(
348 "full_block_send_enable_min_time_from_building"))
350 maximum_simultaneous_block_sends
351 = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
355 u32 num_blocks_selected;
357 JMutexAutoLock lock(m_blocks_sending_mutex);
358 num_blocks_selected = m_blocks_sending.size();
362 next time d will be continued from the d from which the nearest
363 unsent block was found this time.
365 This is because not necessarily any of the blocks found this
366 time are actually sent.
368 s32 new_nearest_unsent_d = -1;
370 // Serialization version used
371 //u8 ser_version = serialization_version;
373 //bool has_incomplete_blocks = false;
375 s16 d_max = g_settings.getS16("max_block_send_distance");
376 s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
378 //dstream<<"Starting from "<<d_start<<std::endl;
380 for(s16 d = d_start; d <= d_max; d++)
382 //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
384 //if(has_incomplete_blocks == false)
386 JMutexAutoLock lock(m_blocks_sent_mutex);
388 If m_nearest_unsent_d was changed by the EmergeThread
389 (it can change it to 0 through SetBlockNotSent),
391 Else update m_nearest_unsent_d
393 if(m_nearest_unsent_d != last_nearest_unsent_d)
395 d = m_nearest_unsent_d;
396 last_nearest_unsent_d = m_nearest_unsent_d;
401 Get the border/face dot coordinates of a "d-radiused"
404 core::list<v3s16> list;
405 getFacePositions(list, d);
407 core::list<v3s16>::Iterator li;
408 for(li=list.begin(); li!=list.end(); li++)
410 v3s16 p = *li + center;
414 - Don't allow too many simultaneous transfers
415 - EXCEPT when the blocks are very close
417 Also, don't send blocks that are already flying.
420 u16 maximum_simultaneous_block_sends_now =
421 maximum_simultaneous_block_sends;
423 if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
425 maximum_simultaneous_block_sends_now =
426 maximum_simultaneous_block_sends_setting;
430 JMutexAutoLock lock(m_blocks_sending_mutex);
432 // Limit is dynamically lowered when building
433 if(num_blocks_selected
434 >= maximum_simultaneous_block_sends_now)
436 /*dstream<<"Not sending more blocks. Queue full. "
437 <<m_blocks_sending.size()
442 if(m_blocks_sending.find(p) != NULL)
449 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
450 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
451 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
452 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
453 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
454 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
457 bool generate = d <= d_max_gen;
459 // Limit the generating area vertically to 2/3
460 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
464 Don't send already sent blocks
467 JMutexAutoLock lock(m_blocks_sent_mutex);
469 if(m_blocks_sent.find(p) != NULL)
474 Check if map has this block
476 MapBlock *block = NULL;
479 block = server->m_env.getMap().getBlockNoCreate(p);
481 catch(InvalidPositionException &e)
485 bool surely_not_found_on_disk = false;
488 /*if(block->isIncomplete())
490 has_incomplete_blocks = true;
496 surely_not_found_on_disk = true;
501 If block has been marked to not exist on disk (dummy)
502 and generating new ones is not wanted, skip block.
504 if(generate == false && surely_not_found_on_disk == true)
511 Record the lowest d from which a a block has been
512 found being not sent and possibly to exist
514 if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
516 new_nearest_unsent_d = d;
520 Add inexistent block to emerge queue.
522 if(block == NULL || surely_not_found_on_disk)
524 /*SharedPtr<JMutexAutoLock> lock
525 (m_num_blocks_in_emerge_queue.getLock());*/
527 //TODO: Get value from somewhere
528 // Allow only one block in emerge queue
529 if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
531 // Add it to the emerge queue and trigger the thread
534 if(generate == false)
535 flags |= BLOCK_EMERGE_FLAG_FROMDISK;
537 server->m_emerge_queue.addBlock(peer_id, p, flags);
538 server->m_emergethread.trigger();
549 PrioritySortedBlockTransfer q((float)d, p, peer_id);
553 num_blocks_selected += 1;
558 if(new_nearest_unsent_d != -1)
560 JMutexAutoLock lock(m_blocks_sent_mutex);
561 m_nearest_unsent_d = new_nearest_unsent_d;
565 void RemoteClient::SendObjectData(
568 core::map<v3s16, bool> &stepped_blocks
571 DSTACK(__FUNCTION_NAME);
573 // Can't send anything without knowing version
574 if(serialization_version == SER_FMT_VER_INVALID)
576 dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
582 Send a TOCLIENT_OBJECTDATA packet.
586 u16 number of player positions
597 std::ostringstream os(std::ios_base::binary);
601 writeU16(buf, TOCLIENT_OBJECTDATA);
602 os.write((char*)buf, 2);
605 Get and write player data
608 core::list<Player*> players = server->m_env.getPlayers();
610 // Write player count
611 u16 playercount = players.size();
612 writeU16(buf, playercount);
613 os.write((char*)buf, 2);
615 core::list<Player*>::Iterator i;
616 for(i = players.begin();
617 i != players.end(); i++)
621 v3f pf = player->getPosition();
622 v3f sf = player->getSpeed();
624 v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
625 v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100);
626 s32 pitch_i (player->getPitch() * 100);
627 s32 yaw_i (player->getYaw() * 100);
629 writeU16(buf, player->peer_id);
630 os.write((char*)buf, 2);
631 writeV3S32(buf, position_i);
632 os.write((char*)buf, 12);
633 writeV3S32(buf, speed_i);
634 os.write((char*)buf, 12);
635 writeS32(buf, pitch_i);
636 os.write((char*)buf, 4);
637 writeS32(buf, yaw_i);
638 os.write((char*)buf, 4);
642 Get and write object data
648 For making players to be able to build to their nearby
649 environment (building is not possible on blocks that are not
652 - Add blocks to emerge queue if they are not found
654 SUGGESTION: These could be ignored from the backside of the player
657 Player *player = server->m_env.getPlayer(peer_id);
659 v3f playerpos = player->getPosition();
660 v3f playerspeed = player->getSpeed();
662 v3s16 center_nodepos = floatToInt(playerpos);
663 v3s16 center = getNodeBlockPos(center_nodepos);
665 s16 d_max = g_settings.getS16("active_object_range");
667 // Number of blocks whose objects were written to bos
670 std::ostringstream bos(std::ios_base::binary);
672 for(s16 d = 0; d <= d_max; d++)
674 core::list<v3s16> list;
675 getFacePositions(list, d);
677 core::list<v3s16>::Iterator li;
678 for(li=list.begin(); li!=list.end(); li++)
680 v3s16 p = *li + center;
683 Ignore blocks that haven't been sent to the client
686 JMutexAutoLock sentlock(m_blocks_sent_mutex);
687 if(m_blocks_sent.find(p) == NULL)
691 // Try stepping block and add it to a send queue
696 MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
699 Step block if not in stepped_blocks and add to stepped_blocks.
701 if(stepped_blocks.find(p) == NULL)
703 block->stepObjects(dtime, true, server->getDayNightRatio());
704 stepped_blocks.insert(p, true);
705 block->setChangedFlag();
708 // Skip block if there are no objects
709 if(block->getObjectCount() == 0)
718 bos.write((char*)buf, 6);
721 block->serializeObjects(bos, serialization_version);
726 Stop collecting objects if data is already too big
728 // Sum of player and object data sizes
729 s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
730 // break out if data too big
731 if(sum > MAX_OBJECTDATA_SIZE)
733 goto skip_subsequent;
737 catch(InvalidPositionException &e)
740 // Add it to the emerge queue and trigger the thread.
741 // Fetch the block only if it is on disk.
743 // Grab and increment counter
744 /*SharedPtr<JMutexAutoLock> lock
745 (m_num_blocks_in_emerge_queue.getLock());
746 m_num_blocks_in_emerge_queue.m_value++;*/
748 // Add to queue as an anonymous fetch from disk
749 u8 flags = BLOCK_EMERGE_FLAG_FROMDISK;
750 server->m_emerge_queue.addBlock(0, p, flags);
751 server->m_emergethread.trigger();
759 writeU16(buf, blockcount);
760 os.write((char*)buf, 2);
762 // Write block objects
769 //dstream<<"Server: Sending object data to "<<peer_id<<std::endl;
772 std::string s = os.str();
773 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
774 // Send as unreliable
775 server->m_con.Send(peer_id, 0, data, false);
778 void RemoteClient::GotBlock(v3s16 p)
780 JMutexAutoLock lock(m_blocks_sending_mutex);
781 JMutexAutoLock lock2(m_blocks_sent_mutex);
782 if(m_blocks_sending.find(p) != NULL)
783 m_blocks_sending.remove(p);
785 dstream<<"RemoteClient::GotBlock(): Didn't find in"
786 " m_blocks_sending"<<std::endl;
787 m_blocks_sent.insert(p, true);
790 void RemoteClient::SentBlock(v3s16 p)
792 JMutexAutoLock lock(m_blocks_sending_mutex);
793 if(m_blocks_sending.size() > 15)
795 dstream<<"RemoteClient::SentBlock(): "
796 <<"m_blocks_sending.size()="
797 <<m_blocks_sending.size()<<std::endl;
799 if(m_blocks_sending.find(p) == NULL)
800 m_blocks_sending.insert(p, 0.0);
802 dstream<<"RemoteClient::SentBlock(): Sent block"
803 " already in m_blocks_sending"<<std::endl;
806 void RemoteClient::SetBlockNotSent(v3s16 p)
808 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
809 JMutexAutoLock sentlock(m_blocks_sent_mutex);
811 m_nearest_unsent_d = 0;
813 if(m_blocks_sending.find(p) != NULL)
814 m_blocks_sending.remove(p);
815 if(m_blocks_sent.find(p) != NULL)
816 m_blocks_sent.remove(p);
819 void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
821 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
822 JMutexAutoLock sentlock(m_blocks_sent_mutex);
824 m_nearest_unsent_d = 0;
826 for(core::map<v3s16, MapBlock*>::Iterator
827 i = blocks.getIterator();
828 i.atEnd()==false; i++)
830 v3s16 p = i.getNode()->getKey();
832 if(m_blocks_sending.find(p) != NULL)
833 m_blocks_sending.remove(p);
834 if(m_blocks_sent.find(p) != NULL)
835 m_blocks_sent.remove(p);
843 PlayerInfo::PlayerInfo()
848 void PlayerInfo::PrintLine(std::ostream *s)
850 (*s)<<id<<": \""<<name<<"\" ("
851 <<position.X<<","<<position.Y
852 <<","<<position.Z<<") ";
854 (*s)<<" avg_rtt="<<avg_rtt;
858 u32 PIChecksum(core::list<PlayerInfo> &l)
860 core::list<PlayerInfo>::Iterator i;
863 for(i=l.begin(); i!=l.end(); i++)
865 checksum += a * (i->id+1);
866 checksum ^= 0x435aafcd;
877 std::string mapsavedir,
881 m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
882 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
884 m_emergethread(this),
887 m_time_of_day_send_timer(0),
890 m_flowwater_timer = 0.0;
891 m_print_info_timer = 0.0;
892 m_objectdata_timer = 0.0;
893 m_emergethread_trigger_timer = 0.0;
894 m_savemap_timer = 0.0;
898 m_step_dtime_mutex.Init();
907 JMutexAutoLock clientslock(m_con_mutex);
909 for(core::map<u16, RemoteClient*>::Iterator
910 i = m_clients.getIterator();
911 i.atEnd() == false; i++)
913 u16 peer_id = i.getNode()->getKey();
917 JMutexAutoLock envlock(m_env_mutex);
918 m_env.removePlayer(peer_id);
922 delete i.getNode()->getValue();
926 void Server::start(unsigned short port)
928 DSTACK(__FUNCTION_NAME);
929 // Stop thread if already running
932 // Initialize connection
933 m_con.setTimeoutMs(30);
937 m_thread.setRun(true);
940 dout_server<<"Server started on port "<<port<<std::endl;
945 DSTACK(__FUNCTION_NAME);
946 // Stop threads (set run=false first so both start stopping)
947 m_thread.setRun(false);
948 m_emergethread.setRun(false);
950 m_emergethread.stop();
952 dout_server<<"Server threads stopped"<<std::endl;
955 void Server::step(float dtime)
957 DSTACK(__FUNCTION_NAME);
962 JMutexAutoLock lock(m_step_dtime_mutex);
963 m_step_dtime += dtime;
967 void Server::AsyncRunStep()
969 DSTACK(__FUNCTION_NAME);
973 JMutexAutoLock lock1(m_step_dtime_mutex);
974 dtime = m_step_dtime;
977 // Send blocks to clients
983 //dstream<<"Server steps "<<dtime<<std::endl;
984 //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
987 JMutexAutoLock lock1(m_step_dtime_mutex);
988 m_step_dtime -= dtime;
995 m_uptime.set(m_uptime.get() + dtime);
1002 m_time_counter += dtime;
1003 f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
1004 u32 units = (u32)(m_time_counter*speed);
1005 m_time_counter -= (f32)units / speed;
1006 m_time_of_day.set((m_time_of_day.get() + units) % 24000);
1008 //dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
1011 Send to clients at constant intervals
1014 m_time_of_day_send_timer -= dtime;
1015 if(m_time_of_day_send_timer < 0.0)
1017 m_time_of_day_send_timer = g_settings.getFloat("time_send_interval");
1019 //JMutexAutoLock envlock(m_env_mutex);
1020 JMutexAutoLock conlock(m_con_mutex);
1022 for(core::map<u16, RemoteClient*>::Iterator
1023 i = m_clients.getIterator();
1024 i.atEnd() == false; i++)
1026 RemoteClient *client = i.getNode()->getValue();
1027 //Player *player = m_env.getPlayer(client->peer_id);
1029 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1030 m_time_of_day.get());
1032 m_con.Send(client->peer_id, 0, data, true);
1038 // Process connection's timeouts
1039 JMutexAutoLock lock2(m_con_mutex);
1040 m_con.RunTimeouts(dtime);
1044 // This has to be called so that the client list gets synced
1045 // with the peer list of the connection
1046 handlePeerChanges();
1051 // This also runs Map's timers
1052 JMutexAutoLock lock(m_env_mutex);
1066 if(g_settings.getBool("endless_water") == false)
1071 float &counter = m_flowwater_timer;
1073 if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
1078 core::map<v3s16, MapBlock*> modified_blocks;
1082 JMutexAutoLock envlock(m_env_mutex);
1084 MapVoxelManipulator v(&m_env.getMap());
1085 v.m_disable_water_climb =
1086 g_settings.getBool("disable_water_climb");
1088 if(g_settings.getBool("endless_water") == false)
1089 v.flowWater(m_flow_active_nodes, 0, false, 250);
1091 v.flowWater(m_flow_active_nodes, 0, false, 50);
1093 v.blitBack(modified_blocks);
1095 ServerMap &map = ((ServerMap&)m_env.getMap());
1098 core::map<v3s16, MapBlock*> lighting_modified_blocks;
1099 map.updateLighting(modified_blocks, lighting_modified_blocks);
1101 // Add blocks modified by lighting to modified_blocks
1102 for(core::map<v3s16, MapBlock*>::Iterator
1103 i = lighting_modified_blocks.getIterator();
1104 i.atEnd() == false; i++)
1106 MapBlock *block = i.getNode()->getValue();
1107 modified_blocks.insert(block->getPos(), block);
1112 Set the modified blocks unsent for all the clients
1115 JMutexAutoLock lock2(m_con_mutex);
1117 for(core::map<u16, RemoteClient*>::Iterator
1118 i = m_clients.getIterator();
1119 i.atEnd() == false; i++)
1121 RemoteClient *client = i.getNode()->getValue();
1123 if(modified_blocks.size() > 0)
1125 // Remove block from sent history
1126 client->SetBlocksNotSent(modified_blocks);
1130 } // interval counter
1133 // Periodically print some info
1135 float &counter = m_print_info_timer;
1141 JMutexAutoLock lock2(m_con_mutex);
1143 for(core::map<u16, RemoteClient*>::Iterator
1144 i = m_clients.getIterator();
1145 i.atEnd() == false; i++)
1147 //u16 peer_id = i.getNode()->getKey();
1148 RemoteClient *client = i.getNode()->getValue();
1149 client->PrintInfo(std::cout);
1157 NOTE: Some of this could be moved to RemoteClient
1161 JMutexAutoLock envlock(m_env_mutex);
1162 JMutexAutoLock conlock(m_con_mutex);
1164 for(core::map<u16, RemoteClient*>::Iterator
1165 i = m_clients.getIterator();
1166 i.atEnd() == false; i++)
1168 RemoteClient *client = i.getNode()->getValue();
1169 Player *player = m_env.getPlayer(client->peer_id);
1171 JMutexAutoLock digmutex(client->m_dig_mutex);
1173 if(client->m_dig_tool_item == -1)
1176 client->m_dig_time_remaining -= dtime;
1178 if(client->m_dig_time_remaining > 0)
1180 client->m_time_from_building.set(0.0);
1184 v3s16 p_under = client->m_dig_position;
1186 // Mandatory parameter; actually used for nothing
1187 core::map<v3s16, MapBlock*> modified_blocks;
1193 // Get material at position
1194 material = m_env.getMap().getNode(p_under).d;
1195 // If it's not diggable, do nothing
1196 if(content_diggable(material) == false)
1198 derr_server<<"Server: Not finishing digging: Node not diggable"
1200 client->m_dig_tool_item = -1;
1204 catch(InvalidPositionException &e)
1206 derr_server<<"Server: Not finishing digging: Node not found"
1208 client->m_dig_tool_item = -1;
1214 SharedBuffer<u8> reply(replysize);
1215 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1216 writeS16(&reply[2], p_under.X);
1217 writeS16(&reply[4], p_under.Y);
1218 writeS16(&reply[6], p_under.Z);
1220 m_con.SendToAll(0, reply, true);
1222 if(g_settings.getBool("creative_mode") == false)
1224 // Add to inventory and send inventory
1225 InventoryItem *item = new MaterialItem(material, 1);
1226 player->inventory.addItem("main", item);
1227 SendInventory(player->peer_id);
1232 (this takes some time so it is done after the quick stuff)
1234 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1240 // Update water pressure around modification
1241 // This also adds it to m_flow_active_nodes if appropriate
1243 MapVoxelManipulator v(&m_env.getMap());
1244 v.m_disable_water_climb =
1245 g_settings.getBool("disable_water_climb");
1247 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1251 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1253 catch(ProcessingLimitException &e)
1255 dstream<<"Processing limit reached (1)"<<std::endl;
1258 v.blitBack(modified_blocks);
1263 // Send object positions
1265 float &counter = m_objectdata_timer;
1267 if(counter >= g_settings.getFloat("objectdata_interval"))
1269 JMutexAutoLock lock1(m_env_mutex);
1270 JMutexAutoLock lock2(m_con_mutex);
1271 SendObjectData(counter);
1277 // Trigger emergethread (it gets somehow gets to a
1278 // non-triggered but bysy state sometimes)
1280 float &counter = m_emergethread_trigger_timer;
1286 m_emergethread.trigger();
1292 float &counter = m_savemap_timer;
1294 if(counter >= g_settings.getFloat("server_map_save_interval"))
1298 JMutexAutoLock lock(m_env_mutex);
1300 // Save only changed parts
1301 m_env.getMap().save(true);
1303 // Delete unused sectors
1304 u32 deleted_count = m_env.getMap().deleteUnusedSectors(
1305 g_settings.getFloat("server_unload_unused_sectors_timeout"));
1306 if(deleted_count > 0)
1308 dout_server<<"Server: Unloaded "<<deleted_count
1309 <<" sectors from memory"<<std::endl;
1315 void Server::Receive()
1317 DSTACK(__FUNCTION_NAME);
1318 u32 data_maxsize = 10000;
1319 Buffer<u8> data(data_maxsize);
1324 JMutexAutoLock conlock(m_con_mutex);
1325 datasize = m_con.Receive(peer_id, *data, data_maxsize);
1328 // This has to be called so that the client list gets synced
1329 // with the peer list of the connection
1330 handlePeerChanges();
1332 ProcessData(*data, datasize, peer_id);
1334 catch(con::InvalidIncomingDataException &e)
1336 derr_server<<"Server::Receive(): "
1337 "InvalidIncomingDataException: what()="
1338 <<e.what()<<std::endl;
1340 catch(con::PeerNotFoundException &e)
1342 //NOTE: This is not needed anymore
1344 // The peer has been disconnected.
1345 // Find the associated player and remove it.
1347 /*JMutexAutoLock envlock(m_env_mutex);
1349 dout_server<<"ServerThread: peer_id="<<peer_id
1350 <<" has apparently closed connection. "
1351 <<"Removing player."<<std::endl;
1353 m_env.removePlayer(peer_id);*/
1357 void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
1359 DSTACK(__FUNCTION_NAME);
1360 // Environment is locked first.
1361 JMutexAutoLock envlock(m_env_mutex);
1362 JMutexAutoLock conlock(m_con_mutex);
1366 peer = m_con.GetPeer(peer_id);
1368 catch(con::PeerNotFoundException &e)
1370 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
1371 <<peer_id<<" not found"<<std::endl;
1375 //u8 peer_ser_ver = peer->serialization_version;
1376 u8 peer_ser_ver = getClient(peer->id)->serialization_version;
1384 ToServerCommand command = (ToServerCommand)readU16(&data[0]);
1386 if(command == TOSERVER_INIT)
1388 // [0] u16 TOSERVER_INIT
1389 // [2] u8 SER_FMT_VER_HIGHEST
1390 // [3] u8[20] player_name
1395 derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
1396 <<peer->id<<std::endl;
1398 // First byte after command is maximum supported
1399 // serialization version
1400 u8 client_max = data[2];
1401 u8 our_max = SER_FMT_VER_HIGHEST;
1402 // Use the highest version supported by both
1403 u8 deployed = core::min_(client_max, our_max);
1404 // If it's lower than the lowest supported, give up.
1405 if(deployed < SER_FMT_VER_LOWEST)
1406 deployed = SER_FMT_VER_INVALID;
1408 //peer->serialization_version = deployed;
1409 getClient(peer->id)->pending_serialization_version = deployed;
1411 if(deployed == SER_FMT_VER_INVALID)
1413 derr_server<<DTIME<<"Server: Cannot negotiate "
1414 "serialization version with peer "
1415 <<peer_id<<std::endl;
1423 Player *player = m_env.getPlayer(peer_id);
1425 // Check if player doesn't exist
1427 throw con::InvalidIncomingDataException
1428 ("Server::ProcessData(): INIT: Player doesn't exist");
1430 // update name if it was supplied
1431 if(datasize >= 20+3)
1434 player->updateName((const char*)&data[3]);
1437 // Now answer with a TOCLIENT_INIT
1439 SharedBuffer<u8> reply(2+1+6);
1440 writeU16(&reply[0], TOCLIENT_INIT);
1441 writeU8(&reply[2], deployed);
1442 writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
1444 m_con.Send(peer_id, 0, reply, true);
1448 if(command == TOSERVER_INIT2)
1450 derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
1451 <<peer->id<<std::endl;
1454 getClient(peer->id)->serialization_version
1455 = getClient(peer->id)->pending_serialization_version;
1458 Send some initialization data
1461 // Send player info to all players
1464 // Send inventory to player
1465 SendInventory(peer->id);
1469 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1470 m_time_of_day.get());
1471 m_con.Send(peer->id, 0, data, true);
1474 // Send information about server to player in chat
1476 std::wostringstream os(std::ios_base::binary);
1479 os<<L"uptime="<<m_uptime.get();
1480 // Information about clients
1482 for(core::map<u16, RemoteClient*>::Iterator
1483 i = m_clients.getIterator();
1484 i.atEnd() == false; i++)
1486 // Get client and check that it is valid
1487 RemoteClient *client = i.getNode()->getValue();
1488 assert(client->peer_id == i.getNode()->getKey());
1489 if(client->serialization_version == SER_FMT_VER_INVALID)
1491 // Get name of player
1492 std::wstring name = L"unknown";
1493 Player *player = m_env.getPlayer(client->peer_id);
1495 name = narrow_to_wide(player->getName());
1496 // Add name to information string
1501 SendChatMessage(peer_id, os.str());
1504 // Send information about joining in chat
1506 std::wstring name = L"unknown";
1507 Player *player = m_env.getPlayer(peer_id);
1509 name = narrow_to_wide(player->getName());
1511 std::wstring message;
1514 message += L" joined game";
1515 BroadcastChatMessage(message);
1521 if(peer_ser_ver == SER_FMT_VER_INVALID)
1523 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
1524 " serialization format invalid or not initialized."
1525 " Skipping incoming command="<<command<<std::endl;
1529 Player *player = m_env.getPlayer(peer_id);
1532 derr_server<<"Server::ProcessData(): Cancelling: "
1533 "No player for peer_id="<<peer_id
1537 if(command == TOSERVER_PLAYERPOS)
1539 if(datasize < 2+12+12+4+4)
1543 v3s32 ps = readV3S32(&data[start+2]);
1544 v3s32 ss = readV3S32(&data[start+2+12]);
1545 f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
1546 f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
1547 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
1548 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
1549 pitch = wrapDegrees(pitch);
1550 yaw = wrapDegrees(yaw);
1551 player->setPosition(position);
1552 player->setSpeed(speed);
1553 player->setPitch(pitch);
1554 player->setYaw(yaw);
1556 /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
1557 <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
1558 <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
1560 else if(command == TOSERVER_GOTBLOCKS)
1573 u16 count = data[2];
1574 for(u16 i=0; i<count; i++)
1576 if((s16)datasize < 2+1+(i+1)*6)
1577 throw con::InvalidIncomingDataException
1578 ("GOTBLOCKS length is too short");
1579 v3s16 p = readV3S16(&data[2+1+i*6]);
1580 /*dstream<<"Server: GOTBLOCKS ("
1581 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1582 RemoteClient *client = getClient(peer_id);
1583 client->GotBlock(p);
1586 else if(command == TOSERVER_DELETEDBLOCKS)
1599 u16 count = data[2];
1600 for(u16 i=0; i<count; i++)
1602 if((s16)datasize < 2+1+(i+1)*6)
1603 throw con::InvalidIncomingDataException
1604 ("DELETEDBLOCKS length is too short");
1605 v3s16 p = readV3S16(&data[2+1+i*6]);
1606 /*dstream<<"Server: DELETEDBLOCKS ("
1607 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1608 RemoteClient *client = getClient(peer_id);
1609 client->SetBlockNotSent(p);
1612 else if(command == TOSERVER_CLICK_OBJECT)
1619 [2] u8 button (0=left, 1=right)
1624 u8 button = readU8(&data[2]);
1626 p.X = readS16(&data[3]);
1627 p.Y = readS16(&data[5]);
1628 p.Z = readS16(&data[7]);
1629 s16 id = readS16(&data[9]);
1630 //u16 item_i = readU16(&data[11]);
1632 MapBlock *block = NULL;
1635 block = m_env.getMap().getBlockNoCreate(p);
1637 catch(InvalidPositionException &e)
1639 derr_server<<"CLICK_OBJECT block not found"<<std::endl;
1643 MapBlockObject *obj = block->getObject(id);
1647 derr_server<<"CLICK_OBJECT object not found"<<std::endl;
1651 //TODO: Check that object is reasonably close
1656 InventoryList *ilist = player->inventory.getList("main");
1657 if(g_settings.getBool("creative_mode") == false && ilist != NULL)
1660 // Skip if inventory has no free space
1661 if(ilist->getUsedSlots() == ilist->getSize())
1663 dout_server<<"Player inventory has no free space"<<std::endl;
1668 Create the inventory item
1670 InventoryItem *item = NULL;
1671 // If it is an item-object, take the item from it
1672 if(obj->getTypeId() == MAPBLOCKOBJECT_TYPE_ITEM)
1674 item = ((ItemObject*)obj)->createInventoryItem();
1676 // Else create an item of the object
1679 item = new MapBlockObjectItem
1680 (obj->getInventoryString());
1683 // Add to inventory and send inventory
1684 ilist->addItem(item);
1685 SendInventory(player->peer_id);
1688 // Remove from block
1689 block->removeObject(id);
1692 else if(command == TOSERVER_GROUND_ACTION)
1700 [3] v3s16 nodepos_undersurface
1701 [9] v3s16 nodepos_abovesurface
1706 2: stop digging (all parameters ignored)
1708 u8 action = readU8(&data[2]);
1710 p_under.X = readS16(&data[3]);
1711 p_under.Y = readS16(&data[5]);
1712 p_under.Z = readS16(&data[7]);
1714 p_over.X = readS16(&data[9]);
1715 p_over.Y = readS16(&data[11]);
1716 p_over.Z = readS16(&data[13]);
1717 u16 item_i = readU16(&data[15]);
1719 //TODO: Check that target is reasonably close
1727 NOTE: This can be used in the future to check if
1728 somebody is cheating, by checking the timing.
1735 else if(action == 2)
1738 RemoteClient *client = getClient(peer->id);
1739 JMutexAutoLock digmutex(client->m_dig_mutex);
1740 client->m_dig_tool_item = -1;
1745 3: Digging completed
1747 else if(action == 3)
1749 // Mandatory parameter; actually used for nothing
1750 core::map<v3s16, MapBlock*> modified_blocks;
1756 // Get material at position
1757 material = m_env.getMap().getNode(p_under).d;
1758 // If it's not diggable, do nothing
1759 if(content_diggable(material) == false)
1761 derr_server<<"Server: Not finishing digging: Node not diggable"
1766 catch(InvalidPositionException &e)
1768 derr_server<<"Server: Not finishing digging: Node not found"
1773 //TODO: Send to only other clients
1776 Send the removal to all other clients
1781 SharedBuffer<u8> reply(replysize);
1782 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1783 writeS16(&reply[2], p_under.X);
1784 writeS16(&reply[4], p_under.Y);
1785 writeS16(&reply[6], p_under.Z);
1787 for(core::map<u16, RemoteClient*>::Iterator
1788 i = m_clients.getIterator();
1789 i.atEnd() == false; i++)
1791 // Get client and check that it is valid
1792 RemoteClient *client = i.getNode()->getValue();
1793 assert(client->peer_id == i.getNode()->getKey());
1794 if(client->serialization_version == SER_FMT_VER_INVALID)
1797 // Don't send if it's the same one
1798 if(peer_id == client->peer_id)
1802 m_con.Send(client->peer_id, 0, reply, true);
1806 Update and send inventory
1809 if(g_settings.getBool("creative_mode") == false)
1814 InventoryList *mlist = player->inventory.getList("main");
1817 InventoryItem *item = mlist->getItem(item_i);
1818 if(item && (std::string)item->getName() == "ToolItem")
1820 ToolItem *titem = (ToolItem*)item;
1821 std::string toolname = titem->getToolName();
1823 // Get digging properties for material and tool
1824 DiggingProperties prop =
1825 getDiggingProperties(material, toolname);
1827 if(prop.diggable == false)
1829 derr_server<<"Server: WARNING: Player digged"
1830 <<" with impossible material + tool"
1831 <<" combination"<<std::endl;
1834 bool weared_out = titem->addWear(prop.wear);
1838 mlist->deleteItem(item_i);
1844 Add digged item to inventory
1846 InventoryItem *item = new MaterialItem(material, 1);
1847 player->inventory.addItem("main", item);
1852 SendInventory(player->peer_id);
1857 (this takes some time so it is done after the quick stuff)
1859 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1865 // Update water pressure around modification
1866 // This also adds it to m_flow_active_nodes if appropriate
1868 MapVoxelManipulator v(&m_env.getMap());
1869 v.m_disable_water_climb =
1870 g_settings.getBool("disable_water_climb");
1872 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1876 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1878 catch(ProcessingLimitException &e)
1880 dstream<<"Processing limit reached (1)"<<std::endl;
1883 v.blitBack(modified_blocks);
1889 else if(action == 1)
1892 InventoryList *ilist = player->inventory.getList("main");
1897 InventoryItem *item = ilist->getItem(item_i);
1899 // If there is no item, it is not possible to add it anywhere
1904 Handle material items
1906 if(std::string("MaterialItem") == item->getName())
1909 // Don't add a node if this is not a free space
1910 MapNode n2 = m_env.getMap().getNode(p_over);
1911 if(content_buildable_to(n2.d) == false)
1914 catch(InvalidPositionException &e)
1916 derr_server<<"Server: Ignoring ADDNODE: Node not found"
1921 // Reset build time counter
1922 getClient(peer->id)->m_time_from_building.set(0.0);
1925 MaterialItem *mitem = (MaterialItem*)item;
1927 n.d = mitem->getMaterial();
1928 if(content_directional(n.d))
1929 n.dir = packDir(p_under - p_over);
1933 u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
1934 SharedBuffer<u8> reply(replysize);
1935 writeU16(&reply[0], TOCLIENT_ADDNODE);
1936 writeS16(&reply[2], p_over.X);
1937 writeS16(&reply[4], p_over.Y);
1938 writeS16(&reply[6], p_over.Z);
1939 n.serialize(&reply[8], peer_ser_ver);
1941 m_con.SendToAll(0, reply, true);
1946 InventoryList *ilist = player->inventory.getList("main");
1947 if(g_settings.getBool("creative_mode") == false && ilist)
1949 // Remove from inventory and send inventory
1950 if(mitem->getCount() == 1)
1951 ilist->deleteItem(item_i);
1955 SendInventory(peer_id);
1961 This takes some time so it is done after the quick stuff
1963 core::map<v3s16, MapBlock*> modified_blocks;
1964 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
1970 InventoryList *ilist = player->inventory.getList("main");
1971 if(g_settings.getBool("creative_mode") == false && ilist)
1973 // Remove from inventory and send inventory
1974 if(mitem->getCount() == 1)
1975 ilist->deleteItem(item_i);
1979 SendInventory(peer_id);
1985 This takes some time so it is done after the quick stuff
1987 core::map<v3s16, MapBlock*> modified_blocks;
1988 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
1991 Set the modified blocks unsent for all the clients
1994 //JMutexAutoLock lock2(m_con_mutex);
1996 for(core::map<u16, RemoteClient*>::Iterator
1997 i = m_clients.getIterator();
1998 i.atEnd() == false; i++)
2000 RemoteClient *client = i.getNode()->getValue();
2002 if(modified_blocks.size() > 0)
2004 // Remove block from sent history
2005 client->SetBlocksNotSent(modified_blocks);
2014 // Update water pressure around modification
2015 // This also adds it to m_flow_active_nodes if appropriate
2017 MapVoxelManipulator v(&m_env.getMap());
2018 v.m_disable_water_climb =
2019 g_settings.getBool("disable_water_climb");
2021 VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
2025 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2027 catch(ProcessingLimitException &e)
2029 dstream<<"Processing limit reached (1)"<<std::endl;
2032 v.blitBack(modified_blocks);
2039 v3s16 blockpos = getNodeBlockPos(p_over);
2041 MapBlock *block = NULL;
2044 block = m_env.getMap().getBlockNoCreate(blockpos);
2046 catch(InvalidPositionException &e)
2048 derr_server<<"Error while placing object: "
2049 "block not found"<<std::endl;
2053 v3s16 block_pos_i_on_map = block->getPosRelative();
2054 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
2056 v3f pos = intToFloat(p_over);
2057 pos -= block_pos_f_on_map;
2059 /*dout_server<<"pos="
2060 <<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
2063 MapBlockObject *obj = NULL;
2066 Handle block object items
2068 if(std::string("MBOItem") == item->getName())
2070 MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;
2072 /*dout_server<<"Trying to place a MapBlockObjectItem: "
2073 "inventorystring=\""
2074 <<oitem->getInventoryString()
2075 <<"\""<<std::endl;*/
2077 obj = oitem->createObject
2078 (pos, player->getYaw(), player->getPitch());
2085 dout_server<<"Placing a miscellaneous item on map"
2088 Create an ItemObject that contains the item.
2090 ItemObject *iobj = new ItemObject(NULL, -1, pos);
2091 std::ostringstream os(std::ios_base::binary);
2092 item->serialize(os);
2093 dout_server<<"Item string is \""<<os.str()<<"\""<<std::endl;
2094 iobj->setItemString(os.str());
2100 derr_server<<"WARNING: item resulted in NULL object, "
2101 <<"not placing onto map"
2106 block->addObject(obj);
2108 dout_server<<"Placed object"<<std::endl;
2110 InventoryList *ilist = player->inventory.getList("main");
2111 if(g_settings.getBool("creative_mode") == false && ilist)
2113 // Remove from inventory and send inventory
2114 ilist->deleteItem(item_i);
2116 SendInventory(peer_id);
2124 Catch invalid actions
2128 derr_server<<"WARNING: Server: Invalid action "
2129 <<action<<std::endl;
2133 else if(command == TOSERVER_RELEASE)
2142 dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
2145 else if(command == TOSERVER_SIGNTEXT)
2154 std::string datastring((char*)&data[2], datasize-2);
2155 std::istringstream is(datastring, std::ios_base::binary);
2158 is.read((char*)buf, 6);
2159 v3s16 blockpos = readV3S16(buf);
2160 is.read((char*)buf, 2);
2161 s16 id = readS16(buf);
2162 is.read((char*)buf, 2);
2163 u16 textlen = readU16(buf);
2165 for(u16 i=0; i<textlen; i++)
2167 is.read((char*)buf, 1);
2168 text += (char)buf[0];
2171 MapBlock *block = NULL;
2174 block = m_env.getMap().getBlockNoCreate(blockpos);
2176 catch(InvalidPositionException &e)
2178 derr_server<<"Error while setting sign text: "
2179 "block not found"<<std::endl;
2183 MapBlockObject *obj = block->getObject(id);
2186 derr_server<<"Error while setting sign text: "
2187 "object not found"<<std::endl;
2191 if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
2193 derr_server<<"Error while setting sign text: "
2194 "object is not a sign"<<std::endl;
2198 ((SignObject*)obj)->setText(text);
2200 obj->getBlock()->setChangedFlag();
2202 else if(command == TOSERVER_INVENTORY_ACTION)
2204 /*// Ignore inventory changes if in creative mode
2205 if(g_settings.getBool("creative_mode") == true)
2207 dstream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode"
2211 // Strip command and create a stream
2212 std::string datastring((char*)&data[2], datasize-2);
2213 dstream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
2214 std::istringstream is(datastring, std::ios_base::binary);
2216 InventoryAction *a = InventoryAction::deSerialize(is);
2220 Handle craftresult specially if not in creative mode
2222 bool disable_action = false;
2223 if(a->getType() == IACTION_MOVE
2224 && g_settings.getBool("creative_mode") == false)
2226 IMoveAction *ma = (IMoveAction*)a;
2227 // Don't allow moving anything to craftresult
2228 if(ma->to_name == "craftresult")
2231 disable_action = true;
2233 // When something is removed from craftresult
2234 if(ma->from_name == "craftresult")
2236 disable_action = true;
2237 // Remove stuff from craft
2238 InventoryList *clist = player->inventory.getList("craft");
2241 u16 count = ma->count;
2244 clist->decrementMaterials(count);
2247 // Feed action to player inventory
2248 a->apply(&player->inventory);
2251 // If something appeared in craftresult, throw it
2253 InventoryList *rlist = player->inventory.getList("craftresult");
2254 InventoryList *mlist = player->inventory.getList("main");
2255 if(rlist && mlist && rlist->getUsedSlots() == 1)
2257 InventoryItem *item1 = rlist->changeItem(0, NULL);
2258 mlist->addItem(item1);
2262 if(disable_action == false)
2264 // Feed action to player inventory
2265 a->apply(&player->inventory);
2270 SendInventory(player->peer_id);
2274 dstream<<"TOSERVER_INVENTORY_ACTION: "
2275 <<"InventoryAction::deSerialize() returned NULL"
2279 else if(command == TOSERVER_CHAT_MESSAGE)
2287 std::string datastring((char*)&data[2], datasize-2);
2288 std::istringstream is(datastring, std::ios_base::binary);
2291 is.read((char*)buf, 2);
2292 u16 len = readU16(buf);
2294 std::wstring message;
2295 for(u16 i=0; i<len; i++)
2297 is.read((char*)buf, 2);
2298 message += (wchar_t)readU16(buf);
2301 // Get player name of this client
2302 std::wstring name = narrow_to_wide(player->getName());
2304 std::wstring line = std::wstring(L"<")+name+L"> "+message;
2306 dstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl;
2309 Send the message to all other clients
2311 for(core::map<u16, RemoteClient*>::Iterator
2312 i = m_clients.getIterator();
2313 i.atEnd() == false; i++)
2315 // Get client and check that it is valid
2316 RemoteClient *client = i.getNode()->getValue();
2317 assert(client->peer_id == i.getNode()->getKey());
2318 if(client->serialization_version == SER_FMT_VER_INVALID)
2321 // Don't send if it's the same one
2322 if(peer_id == client->peer_id)
2325 SendChatMessage(client->peer_id, line);
2330 derr_server<<"WARNING: Server::ProcessData(): Ignoring "
2331 "unknown command "<<command<<std::endl;
2335 catch(SendFailedException &e)
2337 derr_server<<"Server::ProcessData(): SendFailedException: "
2343 /*void Server::Send(u16 peer_id, u16 channelnum,
2344 SharedBuffer<u8> data, bool reliable)
2346 JMutexAutoLock lock(m_con_mutex);
2347 m_con.Send(peer_id, channelnum, data, reliable);
2350 void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
2352 DSTACK(__FUNCTION_NAME);
2354 Create a packet with the block in the right format
2357 std::ostringstream os(std::ios_base::binary);
2358 block->serialize(os, ver);
2359 std::string s = os.str();
2360 SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
2362 u32 replysize = 8 + blockdata.getSize();
2363 SharedBuffer<u8> reply(replysize);
2364 v3s16 p = block->getPos();
2365 writeU16(&reply[0], TOCLIENT_BLOCKDATA);
2366 writeS16(&reply[2], p.X);
2367 writeS16(&reply[4], p.Y);
2368 writeS16(&reply[6], p.Z);
2369 memcpy(&reply[8], *blockdata, blockdata.getSize());
2371 /*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2372 <<": \tpacket size: "<<replysize<<std::endl;*/
2377 m_con.Send(peer_id, 1, reply, true);
2380 core::list<PlayerInfo> Server::getPlayerInfo()
2382 DSTACK(__FUNCTION_NAME);
2383 JMutexAutoLock envlock(m_env_mutex);
2384 JMutexAutoLock conlock(m_con_mutex);
2386 core::list<PlayerInfo> list;
2388 core::list<Player*> players = m_env.getPlayers();
2390 core::list<Player*>::Iterator i;
2391 for(i = players.begin();
2392 i != players.end(); i++)
2396 Player *player = *i;
2398 con::Peer *peer = m_con.GetPeer(player->peer_id);
2400 info.address = peer->address;
2401 info.avg_rtt = peer->avg_rtt;
2403 catch(con::PeerNotFoundException &e)
2405 // Outdated peer info
2407 info.address = Address(0,0,0,0,0);
2411 snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
2412 info.position = player->getPosition();
2414 list.push_back(info);
2420 void Server::peerAdded(con::Peer *peer)
2422 DSTACK(__FUNCTION_NAME);
2423 dout_server<<"Server::peerAdded(): peer->id="
2424 <<peer->id<<std::endl;
2427 c.type = PEER_ADDED;
2428 c.peer_id = peer->id;
2430 m_peer_change_queue.push_back(c);
2433 void Server::deletingPeer(con::Peer *peer, bool timeout)
2435 DSTACK(__FUNCTION_NAME);
2436 dout_server<<"Server::deletingPeer(): peer->id="
2437 <<peer->id<<", timeout="<<timeout<<std::endl;
2440 c.type = PEER_REMOVED;
2441 c.peer_id = peer->id;
2442 c.timeout = timeout;
2443 m_peer_change_queue.push_back(c);
2446 void Server::SendObjectData(float dtime)
2448 DSTACK(__FUNCTION_NAME);
2450 core::map<v3s16, bool> stepped_blocks;
2452 for(core::map<u16, RemoteClient*>::Iterator
2453 i = m_clients.getIterator();
2454 i.atEnd() == false; i++)
2456 u16 peer_id = i.getNode()->getKey();
2457 RemoteClient *client = i.getNode()->getValue();
2458 assert(client->peer_id == peer_id);
2460 if(client->serialization_version == SER_FMT_VER_INVALID)
2463 client->SendObjectData(this, dtime, stepped_blocks);
2467 void Server::SendPlayerInfos()
2469 DSTACK(__FUNCTION_NAME);
2471 //JMutexAutoLock envlock(m_env_mutex);
2473 core::list<Player*> players = m_env.getPlayers();
2475 u32 player_count = players.getSize();
2476 u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
2478 SharedBuffer<u8> data(datasize);
2479 writeU16(&data[0], TOCLIENT_PLAYERINFO);
2482 core::list<Player*>::Iterator i;
2483 for(i = players.begin();
2484 i != players.end(); i++)
2486 Player *player = *i;
2488 /*dstream<<"Server sending player info for player with "
2489 "peer_id="<<player->peer_id<<std::endl;*/
2491 writeU16(&data[start], player->peer_id);
2492 snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
2493 start += 2+PLAYERNAME_SIZE;
2496 //JMutexAutoLock conlock(m_con_mutex);
2499 m_con.SendToAll(0, data, true);
2517 ItemSpec(enum ItemSpecType a_type, std::string a_name):
2523 ItemSpec(enum ItemSpecType a_type, u16 a_num):
2529 enum ItemSpecType type;
2530 // Only other one of these is used
2536 items: a pointer to an array of 9 pointers to items
2537 specs: a pointer to an array of 9 ItemSpecs
2539 bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
2541 u16 items_min_x = 100;
2542 u16 items_max_x = 100;
2543 u16 items_min_y = 100;
2544 u16 items_max_y = 100;
2545 for(u16 y=0; y<3; y++)
2546 for(u16 x=0; x<3; x++)
2548 if(items[y*3 + x] == NULL)
2550 if(items_min_x == 100 || x < items_min_x)
2552 if(items_min_y == 100 || y < items_min_y)
2554 if(items_max_x == 100 || x > items_max_x)
2556 if(items_max_y == 100 || y > items_max_y)
2559 // No items at all, just return false
2560 if(items_min_x == 100)
2563 u16 items_w = items_max_x - items_min_x + 1;
2564 u16 items_h = items_max_y - items_min_y + 1;
2566 u16 specs_min_x = 100;
2567 u16 specs_max_x = 100;
2568 u16 specs_min_y = 100;
2569 u16 specs_max_y = 100;
2570 for(u16 y=0; y<3; y++)
2571 for(u16 x=0; x<3; x++)
2573 if(specs[y*3 + x].type == ITEM_NONE)
2575 if(specs_min_x == 100 || x < specs_min_x)
2577 if(specs_min_y == 100 || y < specs_min_y)
2579 if(specs_max_x == 100 || x > specs_max_x)
2581 if(specs_max_y == 100 || y > specs_max_y)
2584 // No specs at all, just return false
2585 if(specs_min_x == 100)
2588 u16 specs_w = specs_max_x - specs_min_x + 1;
2589 u16 specs_h = specs_max_y - specs_min_y + 1;
2592 if(items_w != specs_w || items_h != specs_h)
2595 for(u16 y=0; y<specs_h; y++)
2596 for(u16 x=0; x<specs_w; x++)
2598 u16 items_x = items_min_x + x;
2599 u16 items_y = items_min_y + y;
2600 u16 specs_x = specs_min_x + x;
2601 u16 specs_y = specs_min_y + y;
2602 InventoryItem *item = items[items_y * 3 + items_x];
2603 ItemSpec &spec = specs[specs_y * 3 + specs_x];
2605 if(spec.type == ITEM_NONE)
2607 // Has to be no item
2613 // There should be an item
2617 std::string itemname = item->getName();
2619 if(spec.type == ITEM_MATERIAL)
2621 if(itemname != "MaterialItem")
2623 MaterialItem *mitem = (MaterialItem*)item;
2624 if(mitem->getMaterial() != spec.num)
2627 else if(spec.type == ITEM_CRAFT)
2629 if(itemname != "CraftItem")
2631 CraftItem *mitem = (CraftItem*)item;
2632 if(mitem->getSubName() != spec.name)
2635 else if(spec.type == ITEM_TOOL)
2637 // Not supported yet
2640 else if(spec.type == ITEM_MBO)
2642 // Not supported yet
2647 // Not supported yet
2655 void Server::SendInventory(u16 peer_id)
2657 DSTACK(__FUNCTION_NAME);
2659 Player* player = m_env.getPlayer(peer_id);
2662 Calculate crafting stuff
2664 if(g_settings.getBool("creative_mode") == false)
2666 InventoryList *clist = player->inventory.getList("craft");
2667 InventoryList *rlist = player->inventory.getList("craftresult");
2670 rlist->clearItems();
2674 InventoryItem *items[9];
2675 for(u16 i=0; i<9; i++)
2677 items[i] = clist->getItem(i);
2686 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
2687 if(checkItemCombination(items, specs))
2689 rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
2698 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2699 if(checkItemCombination(items, specs))
2701 rlist->addItem(new CraftItem("Stick", 4));
2710 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2711 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2712 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2713 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2714 specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2715 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2716 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2717 if(checkItemCombination(items, specs))
2719 rlist->addItem(new MapBlockObjectItem("Sign"));
2728 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COALSTONE);
2729 specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
2730 if(checkItemCombination(items, specs))
2732 rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
2741 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2742 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2743 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2744 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2745 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2746 if(checkItemCombination(items, specs))
2748 rlist->addItem(new ToolItem("WPick", 0));
2757 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2758 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2759 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2760 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2761 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2762 if(checkItemCombination(items, specs))
2764 rlist->addItem(new ToolItem("STPick", 0));
2773 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2774 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2775 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2776 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2777 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2778 if(checkItemCombination(items, specs))
2780 rlist->addItem(new ToolItem("MesePick", 0));
2785 } // if creative_mode == false
2791 std::ostringstream os;
2792 //os.imbue(std::locale("C"));
2794 player->inventory.serialize(os);
2796 std::string s = os.str();
2798 SharedBuffer<u8> data(s.size()+2);
2799 writeU16(&data[0], TOCLIENT_INVENTORY);
2800 memcpy(&data[2], s.c_str(), s.size());
2803 m_con.Send(peer_id, 0, data, true);
2806 void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
2808 DSTACK(__FUNCTION_NAME);
2810 std::ostringstream os(std::ios_base::binary);
2814 writeU16(buf, TOCLIENT_CHAT_MESSAGE);
2815 os.write((char*)buf, 2);
2818 writeU16(buf, message.size());
2819 os.write((char*)buf, 2);
2822 for(u32 i=0; i<message.size(); i++)
2826 os.write((char*)buf, 2);
2830 std::string s = os.str();
2831 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
2833 m_con.Send(peer_id, 0, data, true);
2836 void Server::BroadcastChatMessage(const std::wstring &message)
2838 for(core::map<u16, RemoteClient*>::Iterator
2839 i = m_clients.getIterator();
2840 i.atEnd() == false; i++)
2842 // Get client and check that it is valid
2843 RemoteClient *client = i.getNode()->getValue();
2844 assert(client->peer_id == i.getNode()->getKey());
2845 if(client->serialization_version == SER_FMT_VER_INVALID)
2848 SendChatMessage(client->peer_id, message);
2852 void Server::SendBlocks(float dtime)
2854 DSTACK(__FUNCTION_NAME);
2856 JMutexAutoLock envlock(m_env_mutex);
2858 core::array<PrioritySortedBlockTransfer> queue;
2860 s32 total_sending = 0;
2862 for(core::map<u16, RemoteClient*>::Iterator
2863 i = m_clients.getIterator();
2864 i.atEnd() == false; i++)
2866 RemoteClient *client = i.getNode()->getValue();
2867 assert(client->peer_id == i.getNode()->getKey());
2869 total_sending += client->SendingCount();
2871 if(client->serialization_version == SER_FMT_VER_INVALID)
2874 client->GetNextBlocks(this, dtime, queue);
2878 // Lowest priority number comes first.
2879 // Lowest is most important.
2882 JMutexAutoLock conlock(m_con_mutex);
2884 for(u32 i=0; i<queue.size(); i++)
2886 //TODO: Calculate limit dynamically
2887 if(total_sending >= g_settings.getS32
2888 ("max_simultaneous_block_sends_server_total"))
2891 PrioritySortedBlockTransfer q = queue[i];
2893 MapBlock *block = NULL;
2896 block = m_env.getMap().getBlockNoCreate(q.pos);
2898 catch(InvalidPositionException &e)
2903 RemoteClient *client = getClient(q.peer_id);
2905 SendBlockNoLock(q.peer_id, block, client->serialization_version);
2907 client->SentBlock(q.pos);
2914 RemoteClient* Server::getClient(u16 peer_id)
2916 DSTACK(__FUNCTION_NAME);
2917 //JMutexAutoLock lock(m_con_mutex);
2918 core::map<u16, RemoteClient*>::Node *n;
2919 n = m_clients.find(peer_id);
2920 // A client should exist for all peers
2922 return n->getValue();
2925 void Server::UpdateBlockWaterPressure(MapBlock *block,
2926 core::map<v3s16, MapBlock*> &modified_blocks)
2928 MapVoxelManipulator v(&m_env.getMap());
2929 v.m_disable_water_climb =
2930 g_settings.getBool("disable_water_climb");
2932 VoxelArea area(block->getPosRelative(),
2933 block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
2937 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2939 catch(ProcessingLimitException &e)
2941 dstream<<"Processing limit reached (1)"<<std::endl;
2944 v.blitBack(modified_blocks);
2947 void Server::handlePeerChange(PeerChange &c)
2949 JMutexAutoLock envlock(m_env_mutex);
2950 JMutexAutoLock conlock(m_con_mutex);
2952 if(c.type == PEER_ADDED)
2959 core::map<u16, RemoteClient*>::Node *n;
2960 n = m_clients.find(c.peer_id);
2961 // The client shouldn't already exist
2965 RemoteClient *client = new RemoteClient();
2966 client->peer_id = c.peer_id;
2967 m_clients.insert(client->peer_id, client);
2971 Player *player = m_env.getPlayer(c.peer_id);
2973 // The player shouldn't already exist
2974 assert(player == NULL);
2976 player = new ServerRemotePlayer(true);
2977 player->peer_id = c.peer_id;
2983 // We're going to throw the player to this position
2984 //v2s16 nodepos(29990,29990);
2985 //v2s16 nodepos(9990,9990);
2987 v2s16 sectorpos = getNodeSectorPos(nodepos);
2988 // Get zero sector (it could have been unloaded to disk)
2989 m_env.getMap().emergeSector(sectorpos);
2990 // Get ground height at origin
2991 f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
2992 // The sector should have been generated -> groundheight exists
2993 assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
2994 // Don't go underwater
2995 if(groundheight < WATER_LEVEL)
2996 groundheight = WATER_LEVEL;
2998 player->setPosition(intToFloat(v3s16(
3005 Add player to environment
3008 m_env.addPlayer(player);
3011 Add stuff to inventory
3014 if(g_settings.getBool("creative_mode"))
3016 // Give some good picks
3018 InventoryItem *item = new ToolItem("STPick", 0);
3019 void* r = player->inventory.addItem("main", item);
3023 InventoryItem *item = new ToolItem("MesePick", 0);
3024 void* r = player->inventory.addItem("main", item);
3031 assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
3034 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
3035 player->inventory.addItem("main", item);
3038 for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
3040 // Skip some materials
3041 if(i == CONTENT_OCEAN || i == CONTENT_TORCH)
3044 InventoryItem *item = new MaterialItem(i, 1);
3045 player->inventory.addItem("main", item);
3049 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3050 void* r = player->inventory.addItem("main", item);
3057 InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
3058 void* r = player->inventory.addItem("main", item);
3062 InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
3063 void* r = player->inventory.addItem("main", item);
3067 InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
3068 void* r = player->inventory.addItem("main", item);
3072 InventoryItem *item = new CraftItem("Stick", 4);
3073 void* r = player->inventory.addItem("main", item);
3077 InventoryItem *item = new ToolItem("WPick", 32000);
3078 void* r = player->inventory.addItem("main", item);
3082 InventoryItem *item = new ToolItem("STPick", 32000);
3083 void* r = player->inventory.addItem("main", item);
3086 /*// Give some lights
3088 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
3089 bool r = player->inventory.addItem("main", item);
3093 for(u16 i=0; i<4; i++)
3095 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3096 bool r = player->inventory.addItem("main", item);
3099 /*// Give some other stuff
3101 InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
3102 bool r = player->inventory.addItem("main", item);
3109 else if(c.type == PEER_REMOVED)
3116 core::map<u16, RemoteClient*>::Node *n;
3117 n = m_clients.find(c.peer_id);
3118 // The client should exist
3121 // Collect information about leaving in chat
3122 std::wstring message;
3124 std::wstring name = L"unknown";
3125 Player *player = m_env.getPlayer(c.peer_id);
3127 name = narrow_to_wide(player->getName());
3131 message += L" left game";
3133 message += L" (timed out)";
3138 m_env.removePlayer(c.peer_id);
3142 delete m_clients[c.peer_id];
3143 m_clients.remove(c.peer_id);
3145 // Send player info to all remaining clients
3148 // Send leave chat message to all remaining clients
3149 BroadcastChatMessage(message);
3158 void Server::handlePeerChanges()
3160 while(m_peer_change_queue.size() > 0)
3162 PeerChange c = m_peer_change_queue.pop_front();
3164 dout_server<<"Server: Handling peer change: "
3165 <<"id="<<c.peer_id<<", timeout="<<c.timeout
3168 handlePeerChange(c);