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);
46 m_server->AsyncRunStep();
48 //dout_server<<"Running m_server->Receive()"<<std::endl;
51 catch(con::NoIncomingDataException &e)
54 #if CATCH_UNHANDLED_EXCEPTIONS
56 This is what has to be done in threads to get suitable debug info
58 catch(std::exception &e)
60 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
61 <<e.what()<<std::endl;
71 void * EmergeThread::Thread()
75 DSTACK(__FUNCTION_NAME);
78 #if CATCH_UNHANDLED_EXCEPTIONS
84 Get block info from queue, emerge them and send them
87 After queue is empty, exit.
91 QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
95 SharedPtr<QueuedBlockEmerge> q(qptr);
99 //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
101 //TimeTaker timer("block emerge", g_device);
104 Try to emerge it from somewhere.
106 If it is only wanted as optional, only loading from disk
111 Check if any peer wants it as non-optional. In that case it
114 Also decrement the emerge queue count in clients.
117 bool optional = true;
120 core::map<u16, u8>::Iterator i;
121 for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
123 //u16 peer_id = i.getNode()->getKey();
126 u8 flags = i.getNode()->getValue();
127 if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
133 /*dstream<<"EmergeThread: p="
134 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
135 <<"optional="<<optional<<std::endl;*/
137 ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
139 core::map<v3s16, MapBlock*> changed_blocks;
140 core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
142 MapBlock *block = NULL;
143 bool got_block = true;
144 core::map<v3s16, MapBlock*> modified_blocks;
148 JMutexAutoLock envlock(m_server->m_env_mutex);
150 //TimeTaker timer("block emerge envlock", g_device);
153 bool only_from_disk = false;
156 only_from_disk = true;
158 block = map.emergeBlock(
162 lighting_invalidated_blocks);
164 // If it is a dummy, block was not found on disk
167 //dstream<<"EmergeThread: Got a dummy block"<<std::endl;
171 catch(InvalidPositionException &e)
174 // This happens when position is over limit.
180 if(debug && changed_blocks.size() > 0)
182 dout_server<<DTIME<<"Got changed_blocks: ";
183 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
184 i.atEnd() == false; i++)
186 MapBlock *block = i.getNode()->getValue();
187 v3s16 p = block->getPos();
188 dout_server<<"("<<p.X<<","<<p.Y<<","<<p.Z<<") ";
190 dout_server<<std::endl;
194 Update water pressure
197 m_server->UpdateBlockWaterPressure(block, modified_blocks);
199 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
200 i.atEnd() == false; i++)
202 MapBlock *block = i.getNode()->getValue();
203 m_server->UpdateBlockWaterPressure(block, modified_blocks);
204 //v3s16 p = i.getNode()->getKey();
205 //m_server->UpdateBlockWaterPressure(p, modified_blocks);
209 Collect a list of blocks that have been modified in
210 addition to the fetched one.
213 // Add all the "changed blocks" to modified_blocks
214 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
215 i.atEnd() == false; i++)
217 MapBlock *block = i.getNode()->getValue();
218 modified_blocks.insert(block->getPos(), block);
221 /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
222 <<" blocks"<<std::endl;
223 TimeTaker timer("** updateLighting", g_device);*/
225 // Update lighting without locking the environment mutex,
226 // add modified blocks to changed blocks
227 map.updateLighting(lighting_invalidated_blocks, modified_blocks);
229 // If we got no block, there should be no invalidated blocks
232 assert(lighting_invalidated_blocks.size() == 0);
238 Set sent status of modified blocks on clients
241 // NOTE: Server's clients are also behind the connection mutex
242 JMutexAutoLock lock(m_server->m_con_mutex);
245 Add the originally fetched block to the modified list
249 modified_blocks.insert(p, block);
253 Set the modified blocks unsent for all the clients
256 for(core::map<u16, RemoteClient*>::Iterator
257 i = m_server->m_clients.getIterator();
258 i.atEnd() == false; i++)
260 RemoteClient *client = i.getNode()->getValue();
262 if(modified_blocks.size() > 0)
264 // Remove block from sent history
265 client->SetBlocksNotSent(modified_blocks);
270 #if CATCH_UNHANDLED_EXCEPTIONS
273 This is what has to be done in threads to get suitable debug info
275 catch(std::exception &e)
277 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
278 <<e.what()<<std::endl;
286 void RemoteClient::GetNextBlocks(Server *server, float dtime,
287 core::array<PrioritySortedBlockTransfer> &dest)
289 DSTACK(__FUNCTION_NAME);
293 JMutexAutoLock lock(m_blocks_sent_mutex);
294 m_nearest_unsent_reset_timer += dtime;
297 // Won't send anything if already sending
299 JMutexAutoLock lock(m_blocks_sending_mutex);
301 if(m_blocks_sending.size() >= g_settings.getU16
302 ("max_simultaneous_block_sends_per_client"))
304 //dstream<<"Not sending any blocks, Queue full."<<std::endl;
309 Player *player = server->m_env.getPlayer(peer_id);
311 v3f playerpos = player->getPosition();
312 v3f playerspeed = player->getSpeed();
314 v3s16 center_nodepos = floatToInt(playerpos);
316 v3s16 center = getNodeBlockPos(center_nodepos);
319 Get the starting value of the block finder radius.
321 s16 last_nearest_unsent_d;
324 JMutexAutoLock lock(m_blocks_sent_mutex);
326 if(m_last_center != center)
328 m_nearest_unsent_d = 0;
329 m_last_center = center;
332 /*dstream<<"m_nearest_unsent_reset_timer="
333 <<m_nearest_unsent_reset_timer<<std::endl;*/
334 if(m_nearest_unsent_reset_timer > 5.0)
336 m_nearest_unsent_reset_timer = 0;
337 m_nearest_unsent_d = 0;
338 //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
341 last_nearest_unsent_d = m_nearest_unsent_d;
343 d_start = m_nearest_unsent_d;
346 u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
347 ("max_simultaneous_block_sends_per_client");
348 u16 maximum_simultaneous_block_sends =
349 maximum_simultaneous_block_sends_setting;
352 Check the time from last addNode/removeNode.
354 Decrease send rate if player is building stuff.
357 SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
358 m_time_from_building.m_value += dtime;
359 /*if(m_time_from_building.m_value
360 < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)*/
361 if(m_time_from_building.m_value < g_settings.getFloat(
362 "full_block_send_enable_min_time_from_building"))
364 maximum_simultaneous_block_sends
365 = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
369 u32 num_blocks_selected;
371 JMutexAutoLock lock(m_blocks_sending_mutex);
372 num_blocks_selected = m_blocks_sending.size();
376 next time d will be continued from the d from which the nearest
377 unsent block was found this time.
379 This is because not necessarily any of the blocks found this
380 time are actually sent.
382 s32 new_nearest_unsent_d = -1;
384 // Serialization version used
385 //u8 ser_version = serialization_version;
387 //bool has_incomplete_blocks = false;
389 s16 d_max = g_settings.getS16("max_block_send_distance");
390 s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
392 //dstream<<"Starting from "<<d_start<<std::endl;
394 for(s16 d = d_start; d <= d_max; d++)
396 //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
398 //if(has_incomplete_blocks == false)
400 JMutexAutoLock lock(m_blocks_sent_mutex);
402 If m_nearest_unsent_d was changed by the EmergeThread
403 (it can change it to 0 through SetBlockNotSent),
405 Else update m_nearest_unsent_d
407 if(m_nearest_unsent_d != last_nearest_unsent_d)
409 d = m_nearest_unsent_d;
410 last_nearest_unsent_d = m_nearest_unsent_d;
415 Get the border/face dot coordinates of a "d-radiused"
418 core::list<v3s16> list;
419 getFacePositions(list, d);
421 core::list<v3s16>::Iterator li;
422 for(li=list.begin(); li!=list.end(); li++)
424 v3s16 p = *li + center;
428 - Don't allow too many simultaneous transfers
429 - EXCEPT when the blocks are very close
431 Also, don't send blocks that are already flying.
434 u16 maximum_simultaneous_block_sends_now =
435 maximum_simultaneous_block_sends;
437 if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
439 maximum_simultaneous_block_sends_now =
440 maximum_simultaneous_block_sends_setting;
444 JMutexAutoLock lock(m_blocks_sending_mutex);
446 // Limit is dynamically lowered when building
447 if(num_blocks_selected
448 >= maximum_simultaneous_block_sends_now)
450 /*dstream<<"Not sending more blocks. Queue full. "
451 <<m_blocks_sending.size()
456 if(m_blocks_sending.find(p) != NULL)
463 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
464 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
465 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
466 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
467 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
468 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
471 bool generate = d <= d_max_gen;
473 // Limit the generating area vertically to half
474 if(abs(p.Y - center.Y) > d_max_gen / 2)
478 Don't send already sent blocks
481 JMutexAutoLock lock(m_blocks_sent_mutex);
483 if(m_blocks_sent.find(p) != NULL)
488 Check if map has this block
490 MapBlock *block = NULL;
493 block = server->m_env.getMap().getBlockNoCreate(p);
495 catch(InvalidPositionException &e)
499 bool surely_not_found_on_disk = false;
502 /*if(block->isIncomplete())
504 has_incomplete_blocks = true;
510 surely_not_found_on_disk = true;
515 If block has been marked to not exist on disk (dummy)
516 and generating new ones is not wanted, skip block.
518 if(generate == false && surely_not_found_on_disk == true)
525 Record the lowest d from which a a block has been
526 found being not sent and possibly to exist
528 if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
530 new_nearest_unsent_d = d;
534 Add inexistent block to emerge queue.
536 if(block == NULL || surely_not_found_on_disk)
538 /*SharedPtr<JMutexAutoLock> lock
539 (m_num_blocks_in_emerge_queue.getLock());*/
541 //TODO: Get value from somewhere
542 // Allow only one block in emerge queue
543 if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
545 // Add it to the emerge queue and trigger the thread
548 if(generate == false)
549 flags |= BLOCK_EMERGE_FLAG_FROMDISK;
551 server->m_emerge_queue.addBlock(peer_id, p, flags);
552 server->m_emergethread.trigger();
563 PrioritySortedBlockTransfer q((float)d, p, peer_id);
567 num_blocks_selected += 1;
572 if(new_nearest_unsent_d != -1)
574 JMutexAutoLock lock(m_blocks_sent_mutex);
575 m_nearest_unsent_d = new_nearest_unsent_d;
579 void RemoteClient::SendObjectData(
582 core::map<v3s16, bool> &stepped_blocks
585 DSTACK(__FUNCTION_NAME);
587 // Can't send anything without knowing version
588 if(serialization_version == SER_FMT_VER_INVALID)
590 dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
596 Send a TOCLIENT_OBJECTDATA packet.
600 u16 number of player positions
611 std::ostringstream os(std::ios_base::binary);
615 writeU16(buf, TOCLIENT_OBJECTDATA);
616 os.write((char*)buf, 2);
619 Get and write player data
622 core::list<Player*> players = server->m_env.getPlayers();
624 // Write player count
625 u16 playercount = players.size();
626 writeU16(buf, playercount);
627 os.write((char*)buf, 2);
629 core::list<Player*>::Iterator i;
630 for(i = players.begin();
631 i != players.end(); i++)
635 v3f pf = player->getPosition();
636 v3f sf = player->getSpeed();
638 v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
639 v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100);
640 s32 pitch_i (player->getPitch() * 100);
641 s32 yaw_i (player->getYaw() * 100);
643 writeU16(buf, player->peer_id);
644 os.write((char*)buf, 2);
645 writeV3S32(buf, position_i);
646 os.write((char*)buf, 12);
647 writeV3S32(buf, speed_i);
648 os.write((char*)buf, 12);
649 writeS32(buf, pitch_i);
650 os.write((char*)buf, 4);
651 writeS32(buf, yaw_i);
652 os.write((char*)buf, 4);
656 Get and write object data
662 For making players to be able to build to their nearby
663 environment (building is not possible on blocks that are not
666 - Add blocks to emerge queue if they are not found
668 SUGGESTION: These could be ignored from the backside of the player
671 Player *player = server->m_env.getPlayer(peer_id);
673 v3f playerpos = player->getPosition();
674 v3f playerspeed = player->getSpeed();
676 v3s16 center_nodepos = floatToInt(playerpos);
677 v3s16 center = getNodeBlockPos(center_nodepos);
679 s16 d_max = g_settings.getS16("active_object_range");
681 // Number of blocks whose objects were written to bos
684 std::ostringstream bos(std::ios_base::binary);
686 for(s16 d = 0; d <= d_max; d++)
688 core::list<v3s16> list;
689 getFacePositions(list, d);
691 core::list<v3s16>::Iterator li;
692 for(li=list.begin(); li!=list.end(); li++)
694 v3s16 p = *li + center;
697 Ignore blocks that haven't been sent to the client
700 JMutexAutoLock sentlock(m_blocks_sent_mutex);
701 if(m_blocks_sent.find(p) == NULL)
705 // Try stepping block and add it to a send queue
710 MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
713 Step block if not in stepped_blocks and add to stepped_blocks.
715 if(stepped_blocks.find(p) == NULL)
717 block->stepObjects(dtime, true, server->getDayNightRatio());
718 stepped_blocks.insert(p, true);
719 block->setChangedFlag();
722 // Skip block if there are no objects
723 if(block->getObjectCount() == 0)
732 bos.write((char*)buf, 6);
735 block->serializeObjects(bos, serialization_version);
740 Stop collecting objects if data is already too big
742 // Sum of player and object data sizes
743 s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
744 // break out if data too big
745 if(sum > MAX_OBJECTDATA_SIZE)
747 goto skip_subsequent;
751 catch(InvalidPositionException &e)
754 // Add it to the emerge queue and trigger the thread.
755 // Fetch the block only if it is on disk.
757 // Grab and increment counter
758 /*SharedPtr<JMutexAutoLock> lock
759 (m_num_blocks_in_emerge_queue.getLock());
760 m_num_blocks_in_emerge_queue.m_value++;*/
762 // Add to queue as an anonymous fetch from disk
763 u8 flags = BLOCK_EMERGE_FLAG_FROMDISK;
764 server->m_emerge_queue.addBlock(0, p, flags);
765 server->m_emergethread.trigger();
773 writeU16(buf, blockcount);
774 os.write((char*)buf, 2);
776 // Write block objects
783 //dstream<<"Server: Sending object data to "<<peer_id<<std::endl;
786 std::string s = os.str();
787 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
788 // Send as unreliable
789 server->m_con.Send(peer_id, 0, data, false);
792 void RemoteClient::GotBlock(v3s16 p)
794 JMutexAutoLock lock(m_blocks_sending_mutex);
795 JMutexAutoLock lock2(m_blocks_sent_mutex);
796 if(m_blocks_sending.find(p) != NULL)
797 m_blocks_sending.remove(p);
799 dstream<<"RemoteClient::GotBlock(): Didn't find in"
800 " m_blocks_sending"<<std::endl;
801 m_blocks_sent.insert(p, true);
804 void RemoteClient::SentBlock(v3s16 p)
806 JMutexAutoLock lock(m_blocks_sending_mutex);
807 if(m_blocks_sending.size() > 15)
809 dstream<<"RemoteClient::SentBlock(): "
810 <<"m_blocks_sending.size()="
811 <<m_blocks_sending.size()<<std::endl;
813 if(m_blocks_sending.find(p) == NULL)
814 m_blocks_sending.insert(p, 0.0);
816 dstream<<"RemoteClient::SentBlock(): Sent block"
817 " already in m_blocks_sending"<<std::endl;
820 void RemoteClient::SetBlockNotSent(v3s16 p)
822 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
823 JMutexAutoLock sentlock(m_blocks_sent_mutex);
825 m_nearest_unsent_d = 0;
827 if(m_blocks_sending.find(p) != NULL)
828 m_blocks_sending.remove(p);
829 if(m_blocks_sent.find(p) != NULL)
830 m_blocks_sent.remove(p);
833 void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
835 JMutexAutoLock sendinglock(m_blocks_sending_mutex);
836 JMutexAutoLock sentlock(m_blocks_sent_mutex);
838 m_nearest_unsent_d = 0;
840 for(core::map<v3s16, MapBlock*>::Iterator
841 i = blocks.getIterator();
842 i.atEnd()==false; i++)
844 v3s16 p = i.getNode()->getKey();
846 if(m_blocks_sending.find(p) != NULL)
847 m_blocks_sending.remove(p);
848 if(m_blocks_sent.find(p) != NULL)
849 m_blocks_sent.remove(p);
857 PlayerInfo::PlayerInfo()
862 void PlayerInfo::PrintLine(std::ostream *s)
864 (*s)<<id<<": \""<<name<<"\" ("
865 <<position.X<<","<<position.Y
866 <<","<<position.Z<<") ";
868 (*s)<<" avg_rtt="<<avg_rtt;
872 u32 PIChecksum(core::list<PlayerInfo> &l)
874 core::list<PlayerInfo>::Iterator i;
877 for(i=l.begin(); i!=l.end(); i++)
879 checksum += a * (i->id+1);
880 checksum ^= 0x435aafcd;
891 std::string mapsavedir,
895 m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
896 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
898 m_emergethread(this),
901 m_time_of_day_send_timer(0),
904 m_flowwater_timer = 0.0;
905 m_print_info_timer = 0.0;
906 m_objectdata_timer = 0.0;
907 m_emergethread_trigger_timer = 0.0;
908 m_savemap_timer = 0.0;
912 m_step_dtime_mutex.Init();
921 JMutexAutoLock clientslock(m_con_mutex);
923 for(core::map<u16, RemoteClient*>::Iterator
924 i = m_clients.getIterator();
925 i.atEnd() == false; i++)
927 u16 peer_id = i.getNode()->getKey();
931 JMutexAutoLock envlock(m_env_mutex);
932 m_env.removePlayer(peer_id);
936 delete i.getNode()->getValue();
940 void Server::start(unsigned short port)
942 DSTACK(__FUNCTION_NAME);
943 // Stop thread if already running
946 // Initialize connection
947 m_con.setTimeoutMs(30);
951 m_thread.setRun(true);
954 dout_server<<"Server started on port "<<port<<std::endl;
959 DSTACK(__FUNCTION_NAME);
960 // Stop threads (set run=false first so both start stopping)
961 m_thread.setRun(false);
962 m_emergethread.setRun(false);
964 m_emergethread.stop();
966 dout_server<<"Server threads stopped"<<std::endl;
969 void Server::step(float dtime)
971 DSTACK(__FUNCTION_NAME);
976 JMutexAutoLock lock(m_step_dtime_mutex);
977 m_step_dtime += dtime;
981 void Server::AsyncRunStep()
983 DSTACK(__FUNCTION_NAME);
987 JMutexAutoLock lock1(m_step_dtime_mutex);
988 dtime = m_step_dtime;
991 // Send blocks to clients
997 //dstream<<"Server steps "<<dtime<<std::endl;
998 //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
1001 JMutexAutoLock lock1(m_step_dtime_mutex);
1002 m_step_dtime -= dtime;
1009 m_uptime.set(m_uptime.get() + dtime);
1013 Update m_time_of_day
1016 m_time_counter += dtime;
1017 f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
1018 u32 units = (u32)(m_time_counter*speed);
1019 m_time_counter -= (f32)units / speed;
1020 m_time_of_day.set((m_time_of_day.get() + units) % 24000);
1022 //dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
1025 Send to clients at constant intervals
1028 m_time_of_day_send_timer -= dtime;
1029 if(m_time_of_day_send_timer < 0.0)
1031 m_time_of_day_send_timer = g_settings.getFloat("time_send_interval");
1033 //JMutexAutoLock envlock(m_env_mutex);
1034 JMutexAutoLock conlock(m_con_mutex);
1036 for(core::map<u16, RemoteClient*>::Iterator
1037 i = m_clients.getIterator();
1038 i.atEnd() == false; i++)
1040 RemoteClient *client = i.getNode()->getValue();
1041 //Player *player = m_env.getPlayer(client->peer_id);
1043 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1044 m_time_of_day.get());
1046 m_con.Send(client->peer_id, 0, data, true);
1052 // Process connection's timeouts
1053 JMutexAutoLock lock2(m_con_mutex);
1054 m_con.RunTimeouts(dtime);
1058 // This has to be called so that the client list gets synced
1059 // with the peer list of the connection
1060 handlePeerChanges();
1065 // This also runs Map's timers
1066 JMutexAutoLock lock(m_env_mutex);
1080 if(g_settings.getBool("endless_water") == false)
1085 float &counter = m_flowwater_timer;
1087 if(counter >= 0.25 && m_flow_active_nodes.size() > 0)
1092 core::map<v3s16, MapBlock*> modified_blocks;
1096 JMutexAutoLock envlock(m_env_mutex);
1098 MapVoxelManipulator v(&m_env.getMap());
1099 v.m_disable_water_climb =
1100 g_settings.getBool("disable_water_climb");
1102 if(g_settings.getBool("endless_water") == false)
1103 v.flowWater(m_flow_active_nodes, 0, false, 250);
1105 v.flowWater(m_flow_active_nodes, 0, false, 50);
1107 v.blitBack(modified_blocks);
1109 ServerMap &map = ((ServerMap&)m_env.getMap());
1112 core::map<v3s16, MapBlock*> lighting_modified_blocks;
1113 map.updateLighting(modified_blocks, lighting_modified_blocks);
1115 // Add blocks modified by lighting to modified_blocks
1116 for(core::map<v3s16, MapBlock*>::Iterator
1117 i = lighting_modified_blocks.getIterator();
1118 i.atEnd() == false; i++)
1120 MapBlock *block = i.getNode()->getValue();
1121 modified_blocks.insert(block->getPos(), block);
1126 Set the modified blocks unsent for all the clients
1129 JMutexAutoLock lock2(m_con_mutex);
1131 for(core::map<u16, RemoteClient*>::Iterator
1132 i = m_clients.getIterator();
1133 i.atEnd() == false; i++)
1135 RemoteClient *client = i.getNode()->getValue();
1137 if(modified_blocks.size() > 0)
1139 // Remove block from sent history
1140 client->SetBlocksNotSent(modified_blocks);
1144 } // interval counter
1147 // Periodically print some info
1149 float &counter = m_print_info_timer;
1155 JMutexAutoLock lock2(m_con_mutex);
1157 for(core::map<u16, RemoteClient*>::Iterator
1158 i = m_clients.getIterator();
1159 i.atEnd() == false; i++)
1161 //u16 peer_id = i.getNode()->getKey();
1162 RemoteClient *client = i.getNode()->getValue();
1163 client->PrintInfo(std::cout);
1171 NOTE: Some of this could be moved to RemoteClient
1175 JMutexAutoLock envlock(m_env_mutex);
1176 JMutexAutoLock conlock(m_con_mutex);
1178 for(core::map<u16, RemoteClient*>::Iterator
1179 i = m_clients.getIterator();
1180 i.atEnd() == false; i++)
1182 RemoteClient *client = i.getNode()->getValue();
1183 Player *player = m_env.getPlayer(client->peer_id);
1185 JMutexAutoLock digmutex(client->m_dig_mutex);
1187 if(client->m_dig_tool_item == -1)
1190 client->m_dig_time_remaining -= dtime;
1192 if(client->m_dig_time_remaining > 0)
1194 client->m_time_from_building.set(0.0);
1198 v3s16 p_under = client->m_dig_position;
1200 // Mandatory parameter; actually used for nothing
1201 core::map<v3s16, MapBlock*> modified_blocks;
1207 // Get material at position
1208 material = m_env.getMap().getNode(p_under).d;
1209 // If it's not diggable, do nothing
1210 if(content_diggable(material) == false)
1212 derr_server<<"Server: Not finishing digging: Node not diggable"
1214 client->m_dig_tool_item = -1;
1218 catch(InvalidPositionException &e)
1220 derr_server<<"Server: Not finishing digging: Node not found"
1222 client->m_dig_tool_item = -1;
1228 SharedBuffer<u8> reply(replysize);
1229 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1230 writeS16(&reply[2], p_under.X);
1231 writeS16(&reply[4], p_under.Y);
1232 writeS16(&reply[6], p_under.Z);
1234 m_con.SendToAll(0, reply, true);
1236 if(g_settings.getBool("creative_mode") == false)
1238 // Add to inventory and send inventory
1239 InventoryItem *item = new MaterialItem(material, 1);
1240 player->inventory.addItem("main", item);
1241 SendInventory(player->peer_id);
1246 (this takes some time so it is done after the quick stuff)
1248 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1254 // Update water pressure around modification
1255 // This also adds it to m_flow_active_nodes if appropriate
1257 MapVoxelManipulator v(&m_env.getMap());
1258 v.m_disable_water_climb =
1259 g_settings.getBool("disable_water_climb");
1261 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1265 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1267 catch(ProcessingLimitException &e)
1269 dstream<<"Processing limit reached (1)"<<std::endl;
1272 v.blitBack(modified_blocks);
1277 // Send object positions
1279 float &counter = m_objectdata_timer;
1281 if(counter >= g_settings.getFloat("objectdata_interval"))
1283 JMutexAutoLock lock1(m_env_mutex);
1284 JMutexAutoLock lock2(m_con_mutex);
1285 SendObjectData(counter);
1291 // Trigger emergethread (it gets somehow gets to a
1292 // non-triggered but bysy state sometimes)
1294 float &counter = m_emergethread_trigger_timer;
1300 m_emergethread.trigger();
1306 float &counter = m_savemap_timer;
1308 if(counter >= g_settings.getFloat("server_map_save_interval"))
1312 JMutexAutoLock lock(m_env_mutex);
1314 // Save only changed parts
1315 m_env.getMap().save(true);
1317 // Delete unused sectors
1318 u32 deleted_count = m_env.getMap().deleteUnusedSectors(
1319 g_settings.getFloat("server_unload_unused_sectors_timeout"));
1320 if(deleted_count > 0)
1322 dout_server<<"Server: Unloaded "<<deleted_count
1323 <<" sectors from memory"<<std::endl;
1329 void Server::Receive()
1331 DSTACK(__FUNCTION_NAME);
1332 u32 data_maxsize = 10000;
1333 Buffer<u8> data(data_maxsize);
1338 JMutexAutoLock conlock(m_con_mutex);
1339 datasize = m_con.Receive(peer_id, *data, data_maxsize);
1342 // This has to be called so that the client list gets synced
1343 // with the peer list of the connection
1344 handlePeerChanges();
1346 ProcessData(*data, datasize, peer_id);
1348 catch(con::InvalidIncomingDataException &e)
1350 derr_server<<"Server::Receive(): "
1351 "InvalidIncomingDataException: what()="
1352 <<e.what()<<std::endl;
1354 catch(con::PeerNotFoundException &e)
1356 //NOTE: This is not needed anymore
1358 // The peer has been disconnected.
1359 // Find the associated player and remove it.
1361 /*JMutexAutoLock envlock(m_env_mutex);
1363 dout_server<<"ServerThread: peer_id="<<peer_id
1364 <<" has apparently closed connection. "
1365 <<"Removing player."<<std::endl;
1367 m_env.removePlayer(peer_id);*/
1371 void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
1373 DSTACK(__FUNCTION_NAME);
1374 // Environment is locked first.
1375 JMutexAutoLock envlock(m_env_mutex);
1376 JMutexAutoLock conlock(m_con_mutex);
1380 peer = m_con.GetPeer(peer_id);
1382 catch(con::PeerNotFoundException &e)
1384 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
1385 <<peer_id<<" not found"<<std::endl;
1389 //u8 peer_ser_ver = peer->serialization_version;
1390 u8 peer_ser_ver = getClient(peer->id)->serialization_version;
1398 ToServerCommand command = (ToServerCommand)readU16(&data[0]);
1400 if(command == TOSERVER_INIT)
1402 // [0] u16 TOSERVER_INIT
1403 // [2] u8 SER_FMT_VER_HIGHEST
1404 // [3] u8[20] player_name
1409 derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
1410 <<peer->id<<std::endl;
1412 // First byte after command is maximum supported
1413 // serialization version
1414 u8 client_max = data[2];
1415 u8 our_max = SER_FMT_VER_HIGHEST;
1416 // Use the highest version supported by both
1417 u8 deployed = core::min_(client_max, our_max);
1418 // If it's lower than the lowest supported, give up.
1419 if(deployed < SER_FMT_VER_LOWEST)
1420 deployed = SER_FMT_VER_INVALID;
1422 //peer->serialization_version = deployed;
1423 getClient(peer->id)->pending_serialization_version = deployed;
1425 if(deployed == SER_FMT_VER_INVALID)
1427 derr_server<<DTIME<<"Server: Cannot negotiate "
1428 "serialization version with peer "
1429 <<peer_id<<std::endl;
1437 Player *player = m_env.getPlayer(peer_id);
1439 // Check if player doesn't exist
1441 throw con::InvalidIncomingDataException
1442 ("Server::ProcessData(): INIT: Player doesn't exist");
1444 // update name if it was supplied
1445 if(datasize >= 20+3)
1448 player->updateName((const char*)&data[3]);
1451 // Now answer with a TOCLIENT_INIT
1453 SharedBuffer<u8> reply(2+1+6);
1454 writeU16(&reply[0], TOCLIENT_INIT);
1455 writeU8(&reply[2], deployed);
1456 writeV3S16(&reply[3], floatToInt(player->getPosition()+v3f(0,BS/2,0)));
1458 m_con.Send(peer_id, 0, reply, true);
1462 if(command == TOSERVER_INIT2)
1464 derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
1465 <<peer->id<<std::endl;
1468 getClient(peer->id)->serialization_version
1469 = getClient(peer->id)->pending_serialization_version;
1472 Send some initialization data
1475 // Send player info to all players
1478 // Send inventory to player
1479 SendInventory(peer->id);
1483 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1484 m_time_of_day.get());
1485 m_con.Send(peer->id, 0, data, true);
1488 // Send information about server to player in chat
1490 std::wostringstream os(std::ios_base::binary);
1493 os<<L"uptime="<<m_uptime.get();
1494 // Information about clients
1496 for(core::map<u16, RemoteClient*>::Iterator
1497 i = m_clients.getIterator();
1498 i.atEnd() == false; i++)
1500 // Get client and check that it is valid
1501 RemoteClient *client = i.getNode()->getValue();
1502 assert(client->peer_id == i.getNode()->getKey());
1503 if(client->serialization_version == SER_FMT_VER_INVALID)
1505 // Get name of player
1506 std::wstring name = L"unknown";
1507 Player *player = m_env.getPlayer(client->peer_id);
1509 name = narrow_to_wide(player->getName());
1510 // Add name to information string
1515 SendChatMessage(peer_id, os.str());
1518 // Send information about joining in chat
1520 std::wstring name = L"unknown";
1521 Player *player = m_env.getPlayer(peer_id);
1523 name = narrow_to_wide(player->getName());
1525 std::wstring message;
1528 message += L" joined game";
1529 BroadcastChatMessage(message);
1535 if(peer_ser_ver == SER_FMT_VER_INVALID)
1537 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
1538 " serialization format invalid or not initialized."
1539 " Skipping incoming command="<<command<<std::endl;
1543 Player *player = m_env.getPlayer(peer_id);
1546 derr_server<<"Server::ProcessData(): Cancelling: "
1547 "No player for peer_id="<<peer_id
1551 if(command == TOSERVER_PLAYERPOS)
1553 if(datasize < 2+12+12+4+4)
1557 v3s32 ps = readV3S32(&data[start+2]);
1558 v3s32 ss = readV3S32(&data[start+2+12]);
1559 f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
1560 f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
1561 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
1562 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
1563 pitch = wrapDegrees(pitch);
1564 yaw = wrapDegrees(yaw);
1565 player->setPosition(position);
1566 player->setSpeed(speed);
1567 player->setPitch(pitch);
1568 player->setYaw(yaw);
1570 /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
1571 <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
1572 <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
1574 else if(command == TOSERVER_GOTBLOCKS)
1587 u16 count = data[2];
1588 for(u16 i=0; i<count; i++)
1590 if((s16)datasize < 2+1+(i+1)*6)
1591 throw con::InvalidIncomingDataException
1592 ("GOTBLOCKS length is too short");
1593 v3s16 p = readV3S16(&data[2+1+i*6]);
1594 /*dstream<<"Server: GOTBLOCKS ("
1595 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1596 RemoteClient *client = getClient(peer_id);
1597 client->GotBlock(p);
1600 else if(command == TOSERVER_DELETEDBLOCKS)
1613 u16 count = data[2];
1614 for(u16 i=0; i<count; i++)
1616 if((s16)datasize < 2+1+(i+1)*6)
1617 throw con::InvalidIncomingDataException
1618 ("DELETEDBLOCKS length is too short");
1619 v3s16 p = readV3S16(&data[2+1+i*6]);
1620 /*dstream<<"Server: DELETEDBLOCKS ("
1621 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1622 RemoteClient *client = getClient(peer_id);
1623 client->SetBlockNotSent(p);
1626 else if(command == TOSERVER_CLICK_OBJECT)
1633 [2] u8 button (0=left, 1=right)
1638 u8 button = readU8(&data[2]);
1640 p.X = readS16(&data[3]);
1641 p.Y = readS16(&data[5]);
1642 p.Z = readS16(&data[7]);
1643 s16 id = readS16(&data[9]);
1644 //u16 item_i = readU16(&data[11]);
1646 MapBlock *block = NULL;
1649 block = m_env.getMap().getBlockNoCreate(p);
1651 catch(InvalidPositionException &e)
1653 derr_server<<"CLICK_OBJECT block not found"<<std::endl;
1657 MapBlockObject *obj = block->getObject(id);
1661 derr_server<<"CLICK_OBJECT object not found"<<std::endl;
1665 //TODO: Check that object is reasonably close
1670 InventoryList *ilist = player->inventory.getList("main");
1671 if(g_settings.getBool("creative_mode") == false && ilist != NULL)
1674 // Skip if inventory has no free space
1675 if(ilist->getUsedSlots() == ilist->getSize())
1677 dout_server<<"Player inventory has no free space"<<std::endl;
1682 Create the inventory item
1684 InventoryItem *item = NULL;
1685 // If it is an item-object, take the item from it
1686 if(obj->getTypeId() == MAPBLOCKOBJECT_TYPE_ITEM)
1688 item = ((ItemObject*)obj)->createInventoryItem();
1690 // Else create an item of the object
1693 item = new MapBlockObjectItem
1694 (obj->getInventoryString());
1697 // Add to inventory and send inventory
1698 ilist->addItem(item);
1699 SendInventory(player->peer_id);
1702 // Remove from block
1703 block->removeObject(id);
1706 else if(command == TOSERVER_GROUND_ACTION)
1714 [3] v3s16 nodepos_undersurface
1715 [9] v3s16 nodepos_abovesurface
1720 2: stop digging (all parameters ignored)
1722 u8 action = readU8(&data[2]);
1724 p_under.X = readS16(&data[3]);
1725 p_under.Y = readS16(&data[5]);
1726 p_under.Z = readS16(&data[7]);
1728 p_over.X = readS16(&data[9]);
1729 p_over.Y = readS16(&data[11]);
1730 p_over.Z = readS16(&data[13]);
1731 u16 item_i = readU16(&data[15]);
1733 //TODO: Check that target is reasonably close
1741 NOTE: This can be used in the future to check if
1742 somebody is cheating, by checking the timing.
1749 else if(action == 2)
1752 RemoteClient *client = getClient(peer->id);
1753 JMutexAutoLock digmutex(client->m_dig_mutex);
1754 client->m_dig_tool_item = -1;
1759 3: Digging completed
1761 else if(action == 3)
1763 // Mandatory parameter; actually used for nothing
1764 core::map<v3s16, MapBlock*> modified_blocks;
1770 // Get material at position
1771 material = m_env.getMap().getNode(p_under).d;
1772 // If it's not diggable, do nothing
1773 if(content_diggable(material) == false)
1775 derr_server<<"Server: Not finishing digging: Node not diggable"
1780 catch(InvalidPositionException &e)
1782 derr_server<<"Server: Not finishing digging: Node not found"
1787 //TODO: Send to only other clients
1790 Send the removal to all other clients
1795 SharedBuffer<u8> reply(replysize);
1796 writeU16(&reply[0], TOCLIENT_REMOVENODE);
1797 writeS16(&reply[2], p_under.X);
1798 writeS16(&reply[4], p_under.Y);
1799 writeS16(&reply[6], p_under.Z);
1801 for(core::map<u16, RemoteClient*>::Iterator
1802 i = m_clients.getIterator();
1803 i.atEnd() == false; i++)
1805 // Get client and check that it is valid
1806 RemoteClient *client = i.getNode()->getValue();
1807 assert(client->peer_id == i.getNode()->getKey());
1808 if(client->serialization_version == SER_FMT_VER_INVALID)
1811 // Don't send if it's the same one
1812 if(peer_id == client->peer_id)
1816 m_con.Send(client->peer_id, 0, reply, true);
1820 Update and send inventory
1823 if(g_settings.getBool("creative_mode") == false)
1828 InventoryList *mlist = player->inventory.getList("main");
1831 InventoryItem *item = mlist->getItem(item_i);
1832 if(item && (std::string)item->getName() == "ToolItem")
1834 ToolItem *titem = (ToolItem*)item;
1835 std::string toolname = titem->getToolName();
1837 // Get digging properties for material and tool
1838 DiggingProperties prop =
1839 getDiggingProperties(material, toolname);
1841 if(prop.diggable == false)
1843 derr_server<<"Server: WARNING: Player digged"
1844 <<" with impossible material + tool"
1845 <<" combination"<<std::endl;
1848 bool weared_out = titem->addWear(prop.wear);
1852 mlist->deleteItem(item_i);
1858 Add digged item to inventory
1860 InventoryItem *item = new MaterialItem(material, 1);
1861 player->inventory.addItem("main", item);
1866 SendInventory(player->peer_id);
1871 (this takes some time so it is done after the quick stuff)
1873 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
1879 // Update water pressure around modification
1880 // This also adds it to m_flow_active_nodes if appropriate
1882 MapVoxelManipulator v(&m_env.getMap());
1883 v.m_disable_water_climb =
1884 g_settings.getBool("disable_water_climb");
1886 VoxelArea area(p_under-v3s16(1,1,1), p_under+v3s16(1,1,1));
1890 v.updateAreaWaterPressure(area, m_flow_active_nodes);
1892 catch(ProcessingLimitException &e)
1894 dstream<<"Processing limit reached (1)"<<std::endl;
1897 v.blitBack(modified_blocks);
1903 else if(action == 1)
1906 InventoryList *ilist = player->inventory.getList("main");
1911 InventoryItem *item = ilist->getItem(item_i);
1913 // If there is no item, it is not possible to add it anywhere
1918 Handle material items
1920 if(std::string("MaterialItem") == item->getName())
1923 // Don't add a node if this is not a free space
1924 MapNode n2 = m_env.getMap().getNode(p_over);
1925 if(content_buildable_to(n2.d) == false)
1928 catch(InvalidPositionException &e)
1930 derr_server<<"Server: Ignoring ADDNODE: Node not found"
1935 // Reset build time counter
1936 getClient(peer->id)->m_time_from_building.set(0.0);
1939 MaterialItem *mitem = (MaterialItem*)item;
1941 n.d = mitem->getMaterial();
1942 if(content_directional(n.d))
1943 n.dir = packDir(p_under - p_over);
1947 u32 replysize = 8 + MapNode::serializedLength(peer_ser_ver);
1948 SharedBuffer<u8> reply(replysize);
1949 writeU16(&reply[0], TOCLIENT_ADDNODE);
1950 writeS16(&reply[2], p_over.X);
1951 writeS16(&reply[4], p_over.Y);
1952 writeS16(&reply[6], p_over.Z);
1953 n.serialize(&reply[8], peer_ser_ver);
1955 m_con.SendToAll(0, reply, true);
1960 InventoryList *ilist = player->inventory.getList("main");
1961 if(g_settings.getBool("creative_mode") == false && ilist)
1963 // Remove from inventory and send inventory
1964 if(mitem->getCount() == 1)
1965 ilist->deleteItem(item_i);
1969 SendInventory(peer_id);
1975 This takes some time so it is done after the quick stuff
1977 core::map<v3s16, MapBlock*> modified_blocks;
1978 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
1984 InventoryList *ilist = player->inventory.getList("main");
1985 if(g_settings.getBool("creative_mode") == false && ilist)
1987 // Remove from inventory and send inventory
1988 if(mitem->getCount() == 1)
1989 ilist->deleteItem(item_i);
1993 SendInventory(peer_id);
1999 This takes some time so it is done after the quick stuff
2001 core::map<v3s16, MapBlock*> modified_blocks;
2002 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
2005 Set the modified blocks unsent for all the clients
2008 //JMutexAutoLock lock2(m_con_mutex);
2010 for(core::map<u16, RemoteClient*>::Iterator
2011 i = m_clients.getIterator();
2012 i.atEnd() == false; i++)
2014 RemoteClient *client = i.getNode()->getValue();
2016 if(modified_blocks.size() > 0)
2018 // Remove block from sent history
2019 client->SetBlocksNotSent(modified_blocks);
2028 // Update water pressure around modification
2029 // This also adds it to m_flow_active_nodes if appropriate
2031 MapVoxelManipulator v(&m_env.getMap());
2032 v.m_disable_water_climb =
2033 g_settings.getBool("disable_water_climb");
2035 VoxelArea area(p_over-v3s16(1,1,1), p_over+v3s16(1,1,1));
2039 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2041 catch(ProcessingLimitException &e)
2043 dstream<<"Processing limit reached (1)"<<std::endl;
2046 v.blitBack(modified_blocks);
2053 v3s16 blockpos = getNodeBlockPos(p_over);
2055 MapBlock *block = NULL;
2058 block = m_env.getMap().getBlockNoCreate(blockpos);
2060 catch(InvalidPositionException &e)
2062 derr_server<<"Error while placing object: "
2063 "block not found"<<std::endl;
2067 v3s16 block_pos_i_on_map = block->getPosRelative();
2068 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map);
2070 v3f pos = intToFloat(p_over);
2071 pos -= block_pos_f_on_map;
2073 /*dout_server<<"pos="
2074 <<"("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
2077 MapBlockObject *obj = NULL;
2080 Handle block object items
2082 if(std::string("MBOItem") == item->getName())
2084 MapBlockObjectItem *oitem = (MapBlockObjectItem*)item;
2086 /*dout_server<<"Trying to place a MapBlockObjectItem: "
2087 "inventorystring=\""
2088 <<oitem->getInventoryString()
2089 <<"\""<<std::endl;*/
2091 obj = oitem->createObject
2092 (pos, player->getYaw(), player->getPitch());
2099 dout_server<<"Placing a miscellaneous item on map"
2102 Create an ItemObject that contains the item.
2104 ItemObject *iobj = new ItemObject(NULL, -1, pos);
2105 std::ostringstream os(std::ios_base::binary);
2106 item->serialize(os);
2107 dout_server<<"Item string is \""<<os.str()<<"\""<<std::endl;
2108 iobj->setItemString(os.str());
2114 derr_server<<"WARNING: item resulted in NULL object, "
2115 <<"not placing onto map"
2120 block->addObject(obj);
2122 dout_server<<"Placed object"<<std::endl;
2124 InventoryList *ilist = player->inventory.getList("main");
2125 if(g_settings.getBool("creative_mode") == false && ilist)
2127 // Remove from inventory and send inventory
2128 ilist->deleteItem(item_i);
2130 SendInventory(peer_id);
2138 Catch invalid actions
2142 derr_server<<"WARNING: Server: Invalid action "
2143 <<action<<std::endl;
2147 else if(command == TOSERVER_RELEASE)
2156 dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
2159 else if(command == TOSERVER_SIGNTEXT)
2168 std::string datastring((char*)&data[2], datasize-2);
2169 std::istringstream is(datastring, std::ios_base::binary);
2172 is.read((char*)buf, 6);
2173 v3s16 blockpos = readV3S16(buf);
2174 is.read((char*)buf, 2);
2175 s16 id = readS16(buf);
2176 is.read((char*)buf, 2);
2177 u16 textlen = readU16(buf);
2179 for(u16 i=0; i<textlen; i++)
2181 is.read((char*)buf, 1);
2182 text += (char)buf[0];
2185 MapBlock *block = NULL;
2188 block = m_env.getMap().getBlockNoCreate(blockpos);
2190 catch(InvalidPositionException &e)
2192 derr_server<<"Error while setting sign text: "
2193 "block not found"<<std::endl;
2197 MapBlockObject *obj = block->getObject(id);
2200 derr_server<<"Error while setting sign text: "
2201 "object not found"<<std::endl;
2205 if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
2207 derr_server<<"Error while setting sign text: "
2208 "object is not a sign"<<std::endl;
2212 ((SignObject*)obj)->setText(text);
2214 obj->getBlock()->setChangedFlag();
2216 else if(command == TOSERVER_INVENTORY_ACTION)
2218 /*// Ignore inventory changes if in creative mode
2219 if(g_settings.getBool("creative_mode") == true)
2221 dstream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode"
2225 // Strip command and create a stream
2226 std::string datastring((char*)&data[2], datasize-2);
2227 dstream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
2228 std::istringstream is(datastring, std::ios_base::binary);
2230 InventoryAction *a = InventoryAction::deSerialize(is);
2234 Handle craftresult specially if not in creative mode
2236 bool disable_action = false;
2237 if(a->getType() == IACTION_MOVE
2238 && g_settings.getBool("creative_mode") == false)
2240 IMoveAction *ma = (IMoveAction*)a;
2241 // Don't allow moving anything to craftresult
2242 if(ma->to_name == "craftresult")
2245 disable_action = true;
2247 // When something is removed from craftresult
2248 if(ma->from_name == "craftresult")
2250 disable_action = true;
2251 // Remove stuff from craft
2252 InventoryList *clist = player->inventory.getList("craft");
2255 u16 count = ma->count;
2258 clist->decrementMaterials(count);
2261 // Feed action to player inventory
2262 a->apply(&player->inventory);
2265 // If something appeared in craftresult, throw it
2267 InventoryList *rlist = player->inventory.getList("craftresult");
2268 InventoryList *mlist = player->inventory.getList("main");
2269 if(rlist && mlist && rlist->getUsedSlots() == 1)
2271 InventoryItem *item1 = rlist->changeItem(0, NULL);
2272 mlist->addItem(item1);
2276 if(disable_action == false)
2278 // Feed action to player inventory
2279 a->apply(&player->inventory);
2284 SendInventory(player->peer_id);
2288 dstream<<"TOSERVER_INVENTORY_ACTION: "
2289 <<"InventoryAction::deSerialize() returned NULL"
2293 else if(command == TOSERVER_CHAT_MESSAGE)
2301 std::string datastring((char*)&data[2], datasize-2);
2302 std::istringstream is(datastring, std::ios_base::binary);
2305 is.read((char*)buf, 2);
2306 u16 len = readU16(buf);
2308 std::wstring message;
2309 for(u16 i=0; i<len; i++)
2311 is.read((char*)buf, 2);
2312 message += (wchar_t)readU16(buf);
2315 // Get player name of this client
2316 std::wstring name = narrow_to_wide(player->getName());
2318 std::wstring line = std::wstring(L"<")+name+L"> "+message;
2320 dstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl;
2323 Send the message to all other clients
2325 for(core::map<u16, RemoteClient*>::Iterator
2326 i = m_clients.getIterator();
2327 i.atEnd() == false; i++)
2329 // Get client and check that it is valid
2330 RemoteClient *client = i.getNode()->getValue();
2331 assert(client->peer_id == i.getNode()->getKey());
2332 if(client->serialization_version == SER_FMT_VER_INVALID)
2335 // Don't send if it's the same one
2336 if(peer_id == client->peer_id)
2339 SendChatMessage(client->peer_id, line);
2344 derr_server<<"WARNING: Server::ProcessData(): Ignoring "
2345 "unknown command "<<command<<std::endl;
2349 catch(SendFailedException &e)
2351 derr_server<<"Server::ProcessData(): SendFailedException: "
2357 /*void Server::Send(u16 peer_id, u16 channelnum,
2358 SharedBuffer<u8> data, bool reliable)
2360 JMutexAutoLock lock(m_con_mutex);
2361 m_con.Send(peer_id, channelnum, data, reliable);
2364 void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
2366 DSTACK(__FUNCTION_NAME);
2368 Create a packet with the block in the right format
2371 std::ostringstream os(std::ios_base::binary);
2372 block->serialize(os, ver);
2373 std::string s = os.str();
2374 SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
2376 u32 replysize = 8 + blockdata.getSize();
2377 SharedBuffer<u8> reply(replysize);
2378 v3s16 p = block->getPos();
2379 writeU16(&reply[0], TOCLIENT_BLOCKDATA);
2380 writeS16(&reply[2], p.X);
2381 writeS16(&reply[4], p.Y);
2382 writeS16(&reply[6], p.Z);
2383 memcpy(&reply[8], *blockdata, blockdata.getSize());
2385 /*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2386 <<": \tpacket size: "<<replysize<<std::endl;*/
2391 m_con.Send(peer_id, 1, reply, true);
2394 core::list<PlayerInfo> Server::getPlayerInfo()
2396 DSTACK(__FUNCTION_NAME);
2397 JMutexAutoLock envlock(m_env_mutex);
2398 JMutexAutoLock conlock(m_con_mutex);
2400 core::list<PlayerInfo> list;
2402 core::list<Player*> players = m_env.getPlayers();
2404 core::list<Player*>::Iterator i;
2405 for(i = players.begin();
2406 i != players.end(); i++)
2410 Player *player = *i;
2412 con::Peer *peer = m_con.GetPeer(player->peer_id);
2414 info.address = peer->address;
2415 info.avg_rtt = peer->avg_rtt;
2417 catch(con::PeerNotFoundException &e)
2419 // Outdated peer info
2421 info.address = Address(0,0,0,0,0);
2425 snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
2426 info.position = player->getPosition();
2428 list.push_back(info);
2434 void Server::peerAdded(con::Peer *peer)
2436 DSTACK(__FUNCTION_NAME);
2437 dout_server<<"Server::peerAdded(): peer->id="
2438 <<peer->id<<std::endl;
2441 c.type = PEER_ADDED;
2442 c.peer_id = peer->id;
2444 m_peer_change_queue.push_back(c);
2447 void Server::deletingPeer(con::Peer *peer, bool timeout)
2449 DSTACK(__FUNCTION_NAME);
2450 dout_server<<"Server::deletingPeer(): peer->id="
2451 <<peer->id<<", timeout="<<timeout<<std::endl;
2454 c.type = PEER_REMOVED;
2455 c.peer_id = peer->id;
2456 c.timeout = timeout;
2457 m_peer_change_queue.push_back(c);
2460 void Server::SendObjectData(float dtime)
2462 DSTACK(__FUNCTION_NAME);
2464 core::map<v3s16, bool> stepped_blocks;
2466 for(core::map<u16, RemoteClient*>::Iterator
2467 i = m_clients.getIterator();
2468 i.atEnd() == false; i++)
2470 u16 peer_id = i.getNode()->getKey();
2471 RemoteClient *client = i.getNode()->getValue();
2472 assert(client->peer_id == peer_id);
2474 if(client->serialization_version == SER_FMT_VER_INVALID)
2477 client->SendObjectData(this, dtime, stepped_blocks);
2481 void Server::SendPlayerInfos()
2483 DSTACK(__FUNCTION_NAME);
2485 //JMutexAutoLock envlock(m_env_mutex);
2487 core::list<Player*> players = m_env.getPlayers();
2489 u32 player_count = players.getSize();
2490 u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
2492 SharedBuffer<u8> data(datasize);
2493 writeU16(&data[0], TOCLIENT_PLAYERINFO);
2496 core::list<Player*>::Iterator i;
2497 for(i = players.begin();
2498 i != players.end(); i++)
2500 Player *player = *i;
2502 /*dstream<<"Server sending player info for player with "
2503 "peer_id="<<player->peer_id<<std::endl;*/
2505 writeU16(&data[start], player->peer_id);
2506 snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
2507 start += 2+PLAYERNAME_SIZE;
2510 //JMutexAutoLock conlock(m_con_mutex);
2513 m_con.SendToAll(0, data, true);
2531 ItemSpec(enum ItemSpecType a_type, std::string a_name):
2537 ItemSpec(enum ItemSpecType a_type, u16 a_num):
2543 enum ItemSpecType type;
2544 // Only other one of these is used
2550 items: a pointer to an array of 9 pointers to items
2551 specs: a pointer to an array of 9 ItemSpecs
2553 bool checkItemCombination(InventoryItem **items, ItemSpec *specs)
2555 u16 items_min_x = 100;
2556 u16 items_max_x = 100;
2557 u16 items_min_y = 100;
2558 u16 items_max_y = 100;
2559 for(u16 y=0; y<3; y++)
2560 for(u16 x=0; x<3; x++)
2562 if(items[y*3 + x] == NULL)
2564 if(items_min_x == 100 || x < items_min_x)
2566 if(items_min_y == 100 || y < items_min_y)
2568 if(items_max_x == 100 || x > items_max_x)
2570 if(items_max_y == 100 || y > items_max_y)
2573 // No items at all, just return false
2574 if(items_min_x == 100)
2577 u16 items_w = items_max_x - items_min_x + 1;
2578 u16 items_h = items_max_y - items_min_y + 1;
2580 u16 specs_min_x = 100;
2581 u16 specs_max_x = 100;
2582 u16 specs_min_y = 100;
2583 u16 specs_max_y = 100;
2584 for(u16 y=0; y<3; y++)
2585 for(u16 x=0; x<3; x++)
2587 if(specs[y*3 + x].type == ITEM_NONE)
2589 if(specs_min_x == 100 || x < specs_min_x)
2591 if(specs_min_y == 100 || y < specs_min_y)
2593 if(specs_max_x == 100 || x > specs_max_x)
2595 if(specs_max_y == 100 || y > specs_max_y)
2598 // No specs at all, just return false
2599 if(specs_min_x == 100)
2602 u16 specs_w = specs_max_x - specs_min_x + 1;
2603 u16 specs_h = specs_max_y - specs_min_y + 1;
2606 if(items_w != specs_w || items_h != specs_h)
2609 for(u16 y=0; y<specs_h; y++)
2610 for(u16 x=0; x<specs_w; x++)
2612 u16 items_x = items_min_x + x;
2613 u16 items_y = items_min_y + y;
2614 u16 specs_x = specs_min_x + x;
2615 u16 specs_y = specs_min_y + y;
2616 InventoryItem *item = items[items_y * 3 + items_x];
2617 ItemSpec &spec = specs[specs_y * 3 + specs_x];
2619 if(spec.type == ITEM_NONE)
2621 // Has to be no item
2627 // There should be an item
2631 std::string itemname = item->getName();
2633 if(spec.type == ITEM_MATERIAL)
2635 if(itemname != "MaterialItem")
2637 MaterialItem *mitem = (MaterialItem*)item;
2638 if(mitem->getMaterial() != spec.num)
2641 else if(spec.type == ITEM_CRAFT)
2643 if(itemname != "CraftItem")
2645 CraftItem *mitem = (CraftItem*)item;
2646 if(mitem->getSubName() != spec.name)
2649 else if(spec.type == ITEM_TOOL)
2651 // Not supported yet
2654 else if(spec.type == ITEM_MBO)
2656 // Not supported yet
2661 // Not supported yet
2669 void Server::SendInventory(u16 peer_id)
2671 DSTACK(__FUNCTION_NAME);
2673 Player* player = m_env.getPlayer(peer_id);
2676 Calculate crafting stuff
2678 if(g_settings.getBool("creative_mode") == false)
2680 InventoryList *clist = player->inventory.getList("craft");
2681 InventoryList *rlist = player->inventory.getList("craftresult");
2684 rlist->clearItems();
2688 InventoryItem *items[9];
2689 for(u16 i=0; i<9; i++)
2691 items[i] = clist->getItem(i);
2700 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
2701 if(checkItemCombination(items, specs))
2703 rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
2712 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2713 if(checkItemCombination(items, specs))
2715 rlist->addItem(new CraftItem("Stick", 4));
2724 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2725 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2726 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2727 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2728 specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2729 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2730 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2731 if(checkItemCombination(items, specs))
2733 rlist->addItem(new MapBlockObjectItem("Sign"));
2742 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COALSTONE);
2743 specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
2744 if(checkItemCombination(items, specs))
2746 rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
2755 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2756 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2757 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
2758 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2759 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2760 if(checkItemCombination(items, specs))
2762 rlist->addItem(new ToolItem("WPick", 0));
2771 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2772 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2773 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_STONE);
2774 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2775 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2776 if(checkItemCombination(items, specs))
2778 rlist->addItem(new ToolItem("STPick", 0));
2787 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2788 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2789 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
2790 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
2791 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
2792 if(checkItemCombination(items, specs))
2794 rlist->addItem(new ToolItem("MesePick", 0));
2799 } // if creative_mode == false
2805 std::ostringstream os;
2806 //os.imbue(std::locale("C"));
2808 player->inventory.serialize(os);
2810 std::string s = os.str();
2812 SharedBuffer<u8> data(s.size()+2);
2813 writeU16(&data[0], TOCLIENT_INVENTORY);
2814 memcpy(&data[2], s.c_str(), s.size());
2817 m_con.Send(peer_id, 0, data, true);
2820 void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
2822 DSTACK(__FUNCTION_NAME);
2824 std::ostringstream os(std::ios_base::binary);
2828 writeU16(buf, TOCLIENT_CHAT_MESSAGE);
2829 os.write((char*)buf, 2);
2832 writeU16(buf, message.size());
2833 os.write((char*)buf, 2);
2836 for(u32 i=0; i<message.size(); i++)
2840 os.write((char*)buf, 2);
2844 std::string s = os.str();
2845 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
2847 m_con.Send(peer_id, 0, data, true);
2850 void Server::BroadcastChatMessage(const std::wstring &message)
2852 for(core::map<u16, RemoteClient*>::Iterator
2853 i = m_clients.getIterator();
2854 i.atEnd() == false; i++)
2856 // Get client and check that it is valid
2857 RemoteClient *client = i.getNode()->getValue();
2858 assert(client->peer_id == i.getNode()->getKey());
2859 if(client->serialization_version == SER_FMT_VER_INVALID)
2862 SendChatMessage(client->peer_id, message);
2866 void Server::SendBlocks(float dtime)
2868 DSTACK(__FUNCTION_NAME);
2870 JMutexAutoLock envlock(m_env_mutex);
2872 core::array<PrioritySortedBlockTransfer> queue;
2874 s32 total_sending = 0;
2876 for(core::map<u16, RemoteClient*>::Iterator
2877 i = m_clients.getIterator();
2878 i.atEnd() == false; i++)
2880 RemoteClient *client = i.getNode()->getValue();
2881 assert(client->peer_id == i.getNode()->getKey());
2883 total_sending += client->SendingCount();
2885 if(client->serialization_version == SER_FMT_VER_INVALID)
2888 client->GetNextBlocks(this, dtime, queue);
2892 // Lowest priority number comes first.
2893 // Lowest is most important.
2896 JMutexAutoLock conlock(m_con_mutex);
2898 for(u32 i=0; i<queue.size(); i++)
2900 //TODO: Calculate limit dynamically
2901 if(total_sending >= g_settings.getS32
2902 ("max_simultaneous_block_sends_server_total"))
2905 PrioritySortedBlockTransfer q = queue[i];
2907 MapBlock *block = NULL;
2910 block = m_env.getMap().getBlockNoCreate(q.pos);
2912 catch(InvalidPositionException &e)
2917 RemoteClient *client = getClient(q.peer_id);
2919 SendBlockNoLock(q.peer_id, block, client->serialization_version);
2921 client->SentBlock(q.pos);
2928 RemoteClient* Server::getClient(u16 peer_id)
2930 DSTACK(__FUNCTION_NAME);
2931 //JMutexAutoLock lock(m_con_mutex);
2932 core::map<u16, RemoteClient*>::Node *n;
2933 n = m_clients.find(peer_id);
2934 // A client should exist for all peers
2936 return n->getValue();
2939 void Server::UpdateBlockWaterPressure(MapBlock *block,
2940 core::map<v3s16, MapBlock*> &modified_blocks)
2942 MapVoxelManipulator v(&m_env.getMap());
2943 v.m_disable_water_climb =
2944 g_settings.getBool("disable_water_climb");
2946 VoxelArea area(block->getPosRelative(),
2947 block->getPosRelative() + v3s16(1,1,1)*(MAP_BLOCKSIZE-1));
2951 v.updateAreaWaterPressure(area, m_flow_active_nodes);
2953 catch(ProcessingLimitException &e)
2955 dstream<<"Processing limit reached (1)"<<std::endl;
2958 v.blitBack(modified_blocks);
2961 void Server::handlePeerChange(PeerChange &c)
2963 JMutexAutoLock envlock(m_env_mutex);
2964 JMutexAutoLock conlock(m_con_mutex);
2966 if(c.type == PEER_ADDED)
2973 core::map<u16, RemoteClient*>::Node *n;
2974 n = m_clients.find(c.peer_id);
2975 // The client shouldn't already exist
2979 RemoteClient *client = new RemoteClient();
2980 client->peer_id = c.peer_id;
2981 m_clients.insert(client->peer_id, client);
2985 Player *player = m_env.getPlayer(c.peer_id);
2987 // The player shouldn't already exist
2988 assert(player == NULL);
2990 player = new ServerRemotePlayer();
2991 player->peer_id = c.peer_id;
2997 // We're going to throw the player to this position
2998 //v2s16 nodepos(29990,29990);
2999 //v2s16 nodepos(9990,9990);
3001 v2s16 sectorpos = getNodeSectorPos(nodepos);
3002 // Get zero sector (it could have been unloaded to disk)
3003 m_env.getMap().emergeSector(sectorpos);
3004 // Get ground height at origin
3005 f32 groundheight = m_env.getMap().getGroundHeight(nodepos, true);
3006 // The sector should have been generated -> groundheight exists
3007 assert(groundheight > GROUNDHEIGHT_VALID_MINVALUE);
3008 // Don't go underwater
3009 if(groundheight < WATER_LEVEL)
3010 groundheight = WATER_LEVEL;
3012 player->setPosition(intToFloat(v3s16(
3019 Add player to environment
3022 m_env.addPlayer(player);
3025 Add stuff to inventory
3028 if(g_settings.getBool("creative_mode"))
3030 // Give some good picks
3032 InventoryItem *item = new ToolItem("STPick", 0);
3033 void* r = player->inventory.addItem("main", item);
3037 InventoryItem *item = new ToolItem("MesePick", 0);
3038 void* r = player->inventory.addItem("main", item);
3045 assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
3048 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
3049 player->inventory.addItem("main", item);
3052 for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
3054 // Skip some materials
3055 if(i == CONTENT_OCEAN || i == CONTENT_TORCH)
3058 InventoryItem *item = new MaterialItem(i, 1);
3059 player->inventory.addItem("main", item);
3063 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3064 void* r = player->inventory.addItem("main", item);
3071 InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
3072 void* r = player->inventory.addItem("main", item);
3076 InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
3077 void* r = player->inventory.addItem("main", item);
3081 InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
3082 void* r = player->inventory.addItem("main", item);
3086 InventoryItem *item = new CraftItem("Stick", 4);
3087 void* r = player->inventory.addItem("main", item);
3091 InventoryItem *item = new ToolItem("WPick", 32000);
3092 void* r = player->inventory.addItem("main", item);
3096 InventoryItem *item = new ToolItem("STPick", 32000);
3097 void* r = player->inventory.addItem("main", item);
3100 /*// Give some lights
3102 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
3103 bool r = player->inventory.addItem("main", item);
3107 for(u16 i=0; i<4; i++)
3109 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3110 bool r = player->inventory.addItem("main", item);
3113 /*// Give some other stuff
3115 InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
3116 bool r = player->inventory.addItem("main", item);
3123 else if(c.type == PEER_REMOVED)
3130 core::map<u16, RemoteClient*>::Node *n;
3131 n = m_clients.find(c.peer_id);
3132 // The client should exist
3135 // Collect information about leaving in chat
3136 std::wstring message;
3138 std::wstring name = L"unknown";
3139 Player *player = m_env.getPlayer(c.peer_id);
3141 name = narrow_to_wide(player->getName());
3145 message += L" left game";
3147 message += L" (timed out)";
3152 m_env.removePlayer(c.peer_id);
3156 delete m_clients[c.peer_id];
3157 m_clients.remove(c.peer_id);
3159 // Send player info to all remaining clients
3162 // Send leave chat message to all remaining clients
3163 BroadcastChatMessage(message);
3172 void Server::handlePeerChanges()
3174 while(m_peer_change_queue.size() > 0)
3176 PeerChange c = m_peer_change_queue.pop_front();
3178 dout_server<<"Server: Handling peer change: "
3179 <<"id="<<c.peer_id<<", timeout="<<c.timeout
3182 handlePeerChange(c);