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.
23 #include "clientserver.h"
25 #include "jmutexautolock.h"
27 #include "constants.h"
29 #include "materials.h"
32 #define BLOCK_EMERGE_FLAG_FROMDISK (1<<0)
34 void * ServerThread::Thread()
38 DSTACK(__FUNCTION_NAME);
40 BEGIN_DEBUG_EXCEPTION_HANDLER
45 //TimeTaker timer("AsyncRunStep() + Receive()");
48 //TimeTaker timer("AsyncRunStep()");
49 m_server->AsyncRunStep();
52 //dout_server<<"Running m_server->Receive()"<<std::endl;
55 catch(con::NoIncomingDataException &e)
58 catch(con::PeerNotFoundException &e)
60 dout_server<<"Server: PeerNotFoundException"<<std::endl;
64 END_DEBUG_EXCEPTION_HANDLER
69 void * EmergeThread::Thread()
73 DSTACK(__FUNCTION_NAME);
77 BEGIN_DEBUG_EXCEPTION_HANDLER
80 Get block info from queue, emerge them and send them
83 After queue is empty, exit.
87 QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
91 SharedPtr<QueuedBlockEmerge> q(qptr);
97 Do not generate over-limit
99 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
100 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
101 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
102 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
103 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
104 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
107 //derr_server<<"EmergeThread::Thread(): running"<<std::endl;
109 //TimeTaker timer("block emerge");
112 Try to emerge it from somewhere.
114 If it is only wanted as optional, only loading from disk
119 Check if any peer wants it as non-optional. In that case it
122 Also decrement the emerge queue count in clients.
125 bool optional = true;
128 core::map<u16, u8>::Iterator i;
129 for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++)
131 //u16 peer_id = i.getNode()->getKey();
134 u8 flags = i.getNode()->getValue();
135 if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false)
141 /*dstream<<"EmergeThread: p="
142 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
143 <<"optional="<<optional<<std::endl;*/
145 ServerMap &map = ((ServerMap&)m_server->m_env.getMap());
147 core::map<v3s16, MapBlock*> changed_blocks;
148 core::map<v3s16, MapBlock*> lighting_invalidated_blocks;
150 MapBlock *block = NULL;
151 bool got_block = true;
152 core::map<v3s16, MapBlock*> modified_blocks;
154 bool only_from_disk = false;
157 only_from_disk = true;
159 v2s16 chunkpos = map.sector_to_chunk(p2d);
161 bool generate_chunk = false;
162 if(only_from_disk == false)
164 JMutexAutoLock envlock(m_server->m_env_mutex);
165 if(map.chunkNonVolatile(chunkpos) == false)
166 generate_chunk = true;
173 JMutexAutoLock envlock(m_server->m_env_mutex);
174 map.initChunkMake(data, chunkpos);
180 JMutexAutoLock envlock(m_server->m_env_mutex);
181 map.finishChunkMake(data, changed_blocks);
186 Fetch block from map or generate a single block
189 JMutexAutoLock envlock(m_server->m_env_mutex);
191 // Load sector if it isn't loaded
192 if(map.getSectorNoGenerateNoEx(p2d) == NULL)
193 map.loadSectorFull(p2d);
195 block = map.getBlockNoCreateNoEx(p);
196 if(!block || block->isDummy())
204 // Get, load or create sector
205 ServerMapSector *sector =
206 (ServerMapSector*)map.createSector(p2d);
208 block = map.generateBlock(p, block, sector, changed_blocks,
209 lighting_invalidated_blocks);
216 if(block->getLightingExpired()){
217 lighting_invalidated_blocks[block->getPos()] = block;
221 // TODO: Some additional checking and lighting updating,
226 JMutexAutoLock envlock(m_server->m_env_mutex);
231 Collect a list of blocks that have been modified in
232 addition to the fetched one.
235 if(lighting_invalidated_blocks.size() > 0)
237 /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
238 <<" blocks"<<std::endl;*/
240 // 50-100ms for single block generation
241 //TimeTaker timer("** EmergeThread updateLighting");
243 // Update lighting without locking the environment mutex,
244 // add modified blocks to changed blocks
245 map.updateLighting(lighting_invalidated_blocks, modified_blocks);
248 // Add all from changed_blocks to modified_blocks
249 for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator();
250 i.atEnd() == false; i++)
252 MapBlock *block = i.getNode()->getValue();
253 modified_blocks.insert(block->getPos(), block);
256 // If we got no block, there should be no invalidated blocks
259 assert(lighting_invalidated_blocks.size() == 0);
265 Set sent status of modified blocks on clients
268 // NOTE: Server's clients are also behind the connection mutex
269 JMutexAutoLock lock(m_server->m_con_mutex);
272 Add the originally fetched block to the modified list
276 modified_blocks.insert(p, block);
280 Set the modified blocks unsent for all the clients
283 for(core::map<u16, RemoteClient*>::Iterator
284 i = m_server->m_clients.getIterator();
285 i.atEnd() == false; i++)
287 RemoteClient *client = i.getNode()->getValue();
289 if(modified_blocks.size() > 0)
291 // Remove block from sent history
292 client->SetBlocksNotSent(modified_blocks);
298 END_DEBUG_EXCEPTION_HANDLER
303 void RemoteClient::GetNextBlocks(Server *server, float dtime,
304 core::array<PrioritySortedBlockTransfer> &dest)
306 DSTACK(__FUNCTION_NAME);
309 m_nearest_unsent_reset_timer += dtime;
311 // Won't send anything if already sending
312 if(m_blocks_sending.size() >= g_settings.getU16
313 ("max_simultaneous_block_sends_per_client"))
315 //dstream<<"Not sending any blocks, Queue full."<<std::endl;
319 Player *player = server->m_env.getPlayer(peer_id);
321 assert(player != NULL);
323 v3f playerpos = player->getPosition();
324 v3f playerspeed = player->getSpeed();
326 v3s16 center_nodepos = floatToInt(playerpos, BS);
328 v3s16 center = getNodeBlockPos(center_nodepos);
330 // Camera position and direction
332 playerpos + v3f(0, BS+BS/2, 0);
333 v3f camera_dir = v3f(0,0,1);
334 camera_dir.rotateYZBy(player->getPitch());
335 camera_dir.rotateXZBy(player->getYaw());
338 Get the starting value of the block finder radius.
340 s16 last_nearest_unsent_d;
343 if(m_last_center != center)
345 m_nearest_unsent_d = 0;
346 m_last_center = center;
349 /*dstream<<"m_nearest_unsent_reset_timer="
350 <<m_nearest_unsent_reset_timer<<std::endl;*/
351 if(m_nearest_unsent_reset_timer > 5.0)
353 m_nearest_unsent_reset_timer = 0;
354 m_nearest_unsent_d = 0;
355 //dstream<<"Resetting m_nearest_unsent_d"<<std::endl;
358 last_nearest_unsent_d = m_nearest_unsent_d;
360 d_start = m_nearest_unsent_d;
362 u16 maximum_simultaneous_block_sends_setting = g_settings.getU16
363 ("max_simultaneous_block_sends_per_client");
364 u16 maximum_simultaneous_block_sends =
365 maximum_simultaneous_block_sends_setting;
368 Check the time from last addNode/removeNode.
370 Decrease send rate if player is building stuff.
372 m_time_from_building += dtime;
373 if(m_time_from_building < g_settings.getFloat(
374 "full_block_send_enable_min_time_from_building"))
376 maximum_simultaneous_block_sends
377 = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
380 u32 num_blocks_selected = m_blocks_sending.size();
383 next time d will be continued from the d from which the nearest
384 unsent block was found this time.
386 This is because not necessarily any of the blocks found this
387 time are actually sent.
389 s32 new_nearest_unsent_d = -1;
391 s16 d_max = g_settings.getS16("max_block_send_distance");
392 s16 d_max_gen = g_settings.getS16("max_block_generate_distance");
394 //dstream<<"Starting from "<<d_start<<std::endl;
396 for(s16 d = d_start; d <= d_max; d++)
398 //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
401 If m_nearest_unsent_d was changed by the EmergeThread
402 (it can change it to 0 through SetBlockNotSent),
404 Else update m_nearest_unsent_d
406 if(m_nearest_unsent_d != last_nearest_unsent_d)
408 d = m_nearest_unsent_d;
409 last_nearest_unsent_d = m_nearest_unsent_d;
413 Get the border/face dot coordinates of a "d-radiused"
416 core::list<v3s16> list;
417 getFacePositions(list, d);
419 core::list<v3s16>::Iterator li;
420 for(li=list.begin(); li!=list.end(); li++)
422 v3s16 p = *li + center;
426 - Don't allow too many simultaneous transfers
427 - EXCEPT when the blocks are very close
429 Also, don't send blocks that are already flying.
432 u16 maximum_simultaneous_block_sends_now =
433 maximum_simultaneous_block_sends;
435 if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
437 maximum_simultaneous_block_sends_now =
438 maximum_simultaneous_block_sends_setting;
441 // Limit is dynamically lowered when building
442 if(num_blocks_selected
443 >= maximum_simultaneous_block_sends_now)
445 /*dstream<<"Not sending more blocks. Queue full. "
446 <<m_blocks_sending.size()
451 if(m_blocks_sending.find(p) != NULL)
457 if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
458 || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
459 || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
460 || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
461 || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
462 || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
465 // If this is true, inexistent block will be made from scratch
466 bool generate = d <= d_max_gen;
469 /*// Limit the generating area vertically to 2/3
470 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
473 // Limit the send area vertically to 2/3
474 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
480 If block is far away, don't generate it unless it is
483 NOTE: We can't know the ground level this way with the
489 MapSector *sector = NULL;
492 sector = server->m_env.getMap().getSectorNoGenerate(p2d);
494 catch(InvalidPositionException &e)
500 // Get center ground height in nodes
501 f32 gh = sector->getGroundHeight(
502 v2s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2));
503 // Block center y in nodes
504 f32 y = (f32)(p.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE/2);
505 // If differs a lot, don't generate
506 if(fabs(gh - y) > MAP_BLOCKSIZE*2)
513 Don't generate or send if not in sight
516 if(isBlockInSight(p, camera_pos, camera_dir, 10000*BS) == false)
522 Don't send already sent blocks
525 if(m_blocks_sent.find(p) != NULL)
530 Check if map has this block
532 MapBlock *block = server->m_env.getMap().getBlockNoCreateNoEx(p);
534 bool surely_not_found_on_disk = false;
535 bool block_is_invalid = false;
540 surely_not_found_on_disk = true;
543 if(block->isValid() == false)
545 block_is_invalid = true;
548 /*if(block->isFullyGenerated() == false)
550 block_is_invalid = true;
554 ServerMap *map = (ServerMap*)(&server->m_env.getMap());
555 v2s16 chunkpos = map->sector_to_chunk(p2d);
556 if(map->chunkNonVolatile(chunkpos) == false)
557 block_is_invalid = true;
561 If block has been marked to not exist on disk (dummy)
562 and generating new ones is not wanted, skip block.
564 if(generate == false && surely_not_found_on_disk == true)
571 Record the lowest d from which a a block has been
572 found being not sent and possibly to exist
574 if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d)
576 new_nearest_unsent_d = d;
580 Add inexistent block to emerge queue.
582 if(block == NULL || surely_not_found_on_disk || block_is_invalid)
584 //TODO: Get value from somewhere
585 // Allow only one block in emerge queue
586 //if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
587 if(server->m_emerge_queue.peerItemCount(peer_id) < 2)
589 //dstream<<"Adding block to emerge queue"<<std::endl;
591 // Add it to the emerge queue and trigger the thread
594 if(generate == false)
595 flags |= BLOCK_EMERGE_FLAG_FROMDISK;
597 server->m_emerge_queue.addBlock(peer_id, p, flags);
598 server->m_emergethread.trigger();
606 Add block to send queue
609 PrioritySortedBlockTransfer q((float)d, p, peer_id);
613 num_blocks_selected += 1;
618 if(new_nearest_unsent_d != -1)
620 m_nearest_unsent_d = new_nearest_unsent_d;
624 void RemoteClient::SendObjectData(
627 core::map<v3s16, bool> &stepped_blocks
630 DSTACK(__FUNCTION_NAME);
632 // Can't send anything without knowing version
633 if(serialization_version == SER_FMT_VER_INVALID)
635 dstream<<"RemoteClient::SendObjectData(): Not sending, no version."
641 Send a TOCLIENT_OBJECTDATA packet.
645 u16 number of player positions
656 std::ostringstream os(std::ios_base::binary);
660 writeU16(buf, TOCLIENT_OBJECTDATA);
661 os.write((char*)buf, 2);
664 Get and write player data
667 // Get connected players
668 core::list<Player*> players = server->m_env.getPlayers(true);
670 // Write player count
671 u16 playercount = players.size();
672 writeU16(buf, playercount);
673 os.write((char*)buf, 2);
675 core::list<Player*>::Iterator i;
676 for(i = players.begin();
677 i != players.end(); i++)
681 v3f pf = player->getPosition();
682 v3f sf = player->getSpeed();
684 v3s32 position_i(pf.X*100, pf.Y*100, pf.Z*100);
685 v3s32 speed_i (sf.X*100, sf.Y*100, sf.Z*100);
686 s32 pitch_i (player->getPitch() * 100);
687 s32 yaw_i (player->getYaw() * 100);
689 writeU16(buf, player->peer_id);
690 os.write((char*)buf, 2);
691 writeV3S32(buf, position_i);
692 os.write((char*)buf, 12);
693 writeV3S32(buf, speed_i);
694 os.write((char*)buf, 12);
695 writeS32(buf, pitch_i);
696 os.write((char*)buf, 4);
697 writeS32(buf, yaw_i);
698 os.write((char*)buf, 4);
702 Get and write object data
708 For making players to be able to build to their nearby
709 environment (building is not possible on blocks that are not
712 - Add blocks to emerge queue if they are not found
714 SUGGESTION: These could be ignored from the backside of the player
717 Player *player = server->m_env.getPlayer(peer_id);
721 v3f playerpos = player->getPosition();
722 v3f playerspeed = player->getSpeed();
724 v3s16 center_nodepos = floatToInt(playerpos, BS);
725 v3s16 center = getNodeBlockPos(center_nodepos);
727 s16 d_max = g_settings.getS16("active_object_range");
729 // Number of blocks whose objects were written to bos
732 std::ostringstream bos(std::ios_base::binary);
734 for(s16 d = 0; d <= d_max; d++)
736 core::list<v3s16> list;
737 getFacePositions(list, d);
739 core::list<v3s16>::Iterator li;
740 for(li=list.begin(); li!=list.end(); li++)
742 v3s16 p = *li + center;
745 Ignore blocks that haven't been sent to the client
748 if(m_blocks_sent.find(p) == NULL)
752 // Try stepping block and add it to a send queue
757 MapBlock *block = server->m_env.getMap().getBlockNoCreate(p);
760 Step block if not in stepped_blocks and add to stepped_blocks.
762 if(stepped_blocks.find(p) == NULL)
764 block->stepObjects(dtime, true, server->getDayNightRatio());
765 stepped_blocks.insert(p, true);
766 block->setChangedFlag();
769 // Skip block if there are no objects
770 if(block->getObjectCount() == 0)
779 bos.write((char*)buf, 6);
782 block->serializeObjects(bos, serialization_version);
787 Stop collecting objects if data is already too big
789 // Sum of player and object data sizes
790 s32 sum = (s32)os.tellp() + 2 + (s32)bos.tellp();
791 // break out if data too big
792 if(sum > MAX_OBJECTDATA_SIZE)
794 goto skip_subsequent;
798 catch(InvalidPositionException &e)
801 // Add it to the emerge queue and trigger the thread.
802 // Fetch the block only if it is on disk.
804 // Grab and increment counter
805 /*SharedPtr<JMutexAutoLock> lock
806 (m_num_blocks_in_emerge_queue.getLock());
807 m_num_blocks_in_emerge_queue.m_value++;*/
809 // Add to queue as an anonymous fetch from disk
810 u8 flags = BLOCK_EMERGE_FLAG_FROMDISK;
811 server->m_emerge_queue.addBlock(0, p, flags);
812 server->m_emergethread.trigger();
820 writeU16(buf, blockcount);
821 os.write((char*)buf, 2);
823 // Write block objects
830 //dstream<<"Server: Sending object data to "<<peer_id<<std::endl;
833 std::string s = os.str();
834 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
835 // Send as unreliable
836 server->m_con.Send(peer_id, 0, data, false);
839 void RemoteClient::GotBlock(v3s16 p)
841 if(m_blocks_sending.find(p) != NULL)
842 m_blocks_sending.remove(p);
845 /*dstream<<"RemoteClient::GotBlock(): Didn't find in"
846 " m_blocks_sending"<<std::endl;*/
847 m_excess_gotblocks++;
849 m_blocks_sent.insert(p, true);
852 void RemoteClient::SentBlock(v3s16 p)
854 if(m_blocks_sending.find(p) == NULL)
855 m_blocks_sending.insert(p, 0.0);
857 dstream<<"RemoteClient::SentBlock(): Sent block"
858 " already in m_blocks_sending"<<std::endl;
861 void RemoteClient::SetBlockNotSent(v3s16 p)
863 m_nearest_unsent_d = 0;
865 if(m_blocks_sending.find(p) != NULL)
866 m_blocks_sending.remove(p);
867 if(m_blocks_sent.find(p) != NULL)
868 m_blocks_sent.remove(p);
871 void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
873 m_nearest_unsent_d = 0;
875 for(core::map<v3s16, MapBlock*>::Iterator
876 i = blocks.getIterator();
877 i.atEnd()==false; i++)
879 v3s16 p = i.getNode()->getKey();
881 if(m_blocks_sending.find(p) != NULL)
882 m_blocks_sending.remove(p);
883 if(m_blocks_sent.find(p) != NULL)
884 m_blocks_sent.remove(p);
892 PlayerInfo::PlayerInfo()
898 void PlayerInfo::PrintLine(std::ostream *s)
901 (*s)<<"\""<<name<<"\" ("
902 <<(position.X/10)<<","<<(position.Y/10)
903 <<","<<(position.Z/10)<<") ";
905 (*s)<<" avg_rtt="<<avg_rtt;
909 u32 PIChecksum(core::list<PlayerInfo> &l)
911 core::list<PlayerInfo>::Iterator i;
914 for(i=l.begin(); i!=l.end(); i++)
916 checksum += a * (i->id+1);
917 checksum ^= 0x435aafcd;
928 std::string mapsavedir
930 m_env(new ServerMap(mapsavedir), this),
931 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
933 m_emergethread(this),
936 m_time_of_day_send_timer(0),
938 m_mapsavedir(mapsavedir),
939 m_shutdown_requested(false),
940 m_ignore_map_edit_events(false),
941 m_ignore_map_edit_events_peer_id(0)
943 m_liquid_transform_timer = 0.0;
944 m_print_info_timer = 0.0;
945 m_objectdata_timer = 0.0;
946 m_emergethread_trigger_timer = 0.0;
947 m_savemap_timer = 0.0;
951 m_step_dtime_mutex.Init();
954 m_env.getMap().addEventReceiver(this);
957 m_env.deSerializePlayers(m_mapsavedir);
963 Send shutdown message
966 JMutexAutoLock conlock(m_con_mutex);
968 std::wstring line = L"*** Server shutting down";
971 Send the message to clients
973 for(core::map<u16, RemoteClient*>::Iterator
974 i = m_clients.getIterator();
975 i.atEnd() == false; i++)
977 // Get client and check that it is valid
978 RemoteClient *client = i.getNode()->getValue();
979 assert(client->peer_id == i.getNode()->getKey());
980 if(client->serialization_version == SER_FMT_VER_INVALID)
983 SendChatMessage(client->peer_id, line);
990 m_env.serializePlayers(m_mapsavedir);
1001 JMutexAutoLock clientslock(m_con_mutex);
1003 for(core::map<u16, RemoteClient*>::Iterator
1004 i = m_clients.getIterator();
1005 i.atEnd() == false; i++)
1008 // NOTE: These are removed by env destructor
1010 u16 peer_id = i.getNode()->getKey();
1011 JMutexAutoLock envlock(m_env_mutex);
1012 m_env.removePlayer(peer_id);
1016 delete i.getNode()->getValue();
1021 void Server::start(unsigned short port)
1023 DSTACK(__FUNCTION_NAME);
1024 // Stop thread if already running
1027 // Initialize connection
1028 m_con.setTimeoutMs(30);
1032 m_thread.setRun(true);
1035 dout_server<<"Server: Started on port "<<port<<std::endl;
1040 DSTACK(__FUNCTION_NAME);
1042 // Stop threads (set run=false first so both start stopping)
1043 m_thread.setRun(false);
1044 m_emergethread.setRun(false);
1046 m_emergethread.stop();
1048 dout_server<<"Server: Threads stopped"<<std::endl;
1050 dout_server<<"Server: Saving players"<<std::endl;
1052 // FIXME: Apparently this does not do anything here
1053 //m_env.serializePlayers(m_mapsavedir);
1056 void Server::step(float dtime)
1058 DSTACK(__FUNCTION_NAME);
1063 JMutexAutoLock lock(m_step_dtime_mutex);
1064 m_step_dtime += dtime;
1068 void Server::AsyncRunStep()
1070 DSTACK(__FUNCTION_NAME);
1074 JMutexAutoLock lock1(m_step_dtime_mutex);
1075 dtime = m_step_dtime;
1078 // Send blocks to clients
1084 //dstream<<"Server steps "<<dtime<<std::endl;
1085 //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
1088 JMutexAutoLock lock1(m_step_dtime_mutex);
1089 m_step_dtime -= dtime;
1096 m_uptime.set(m_uptime.get() + dtime);
1100 Update m_time_of_day
1103 m_time_counter += dtime;
1104 f32 speed = g_settings.getFloat("time_speed") * 24000./(24.*3600);
1105 u32 units = (u32)(m_time_counter*speed);
1106 m_time_counter -= (f32)units / speed;
1107 m_time_of_day.set((m_time_of_day.get() + units) % 24000);
1109 //dstream<<"Server: m_time_of_day = "<<m_time_of_day.get()<<std::endl;
1112 Send to clients at constant intervals
1115 m_time_of_day_send_timer -= dtime;
1116 if(m_time_of_day_send_timer < 0.0)
1118 m_time_of_day_send_timer = g_settings.getFloat("time_send_interval");
1120 //JMutexAutoLock envlock(m_env_mutex);
1121 JMutexAutoLock conlock(m_con_mutex);
1123 for(core::map<u16, RemoteClient*>::Iterator
1124 i = m_clients.getIterator();
1125 i.atEnd() == false; i++)
1127 RemoteClient *client = i.getNode()->getValue();
1128 //Player *player = m_env.getPlayer(client->peer_id);
1130 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1131 m_time_of_day.get());
1133 m_con.Send(client->peer_id, 0, data, true);
1139 // Process connection's timeouts
1140 JMutexAutoLock lock2(m_con_mutex);
1141 m_con.RunTimeouts(dtime);
1145 // This has to be called so that the client list gets synced
1146 // with the peer list of the connection
1147 handlePeerChanges();
1152 // This also runs Map's timers
1153 JMutexAutoLock lock(m_env_mutex);
1164 m_liquid_transform_timer += dtime;
1165 if(m_liquid_transform_timer >= 1.00)
1167 m_liquid_transform_timer -= 1.00;
1169 JMutexAutoLock lock(m_env_mutex);
1171 core::map<v3s16, MapBlock*> modified_blocks;
1172 m_env.getMap().transformLiquids(modified_blocks);
1177 core::map<v3s16, MapBlock*> lighting_modified_blocks;
1178 ServerMap &map = ((ServerMap&)m_env.getMap());
1179 map.updateLighting(modified_blocks, lighting_modified_blocks);
1181 // Add blocks modified by lighting to modified_blocks
1182 for(core::map<v3s16, MapBlock*>::Iterator
1183 i = lighting_modified_blocks.getIterator();
1184 i.atEnd() == false; i++)
1186 MapBlock *block = i.getNode()->getValue();
1187 modified_blocks.insert(block->getPos(), block);
1191 Set the modified blocks unsent for all the clients
1194 JMutexAutoLock lock2(m_con_mutex);
1196 for(core::map<u16, RemoteClient*>::Iterator
1197 i = m_clients.getIterator();
1198 i.atEnd() == false; i++)
1200 RemoteClient *client = i.getNode()->getValue();
1202 if(modified_blocks.size() > 0)
1204 // Remove block from sent history
1205 client->SetBlocksNotSent(modified_blocks);
1210 // Periodically print some info
1212 float &counter = m_print_info_timer;
1218 JMutexAutoLock lock2(m_con_mutex);
1220 for(core::map<u16, RemoteClient*>::Iterator
1221 i = m_clients.getIterator();
1222 i.atEnd() == false; i++)
1224 //u16 peer_id = i.getNode()->getKey();
1225 RemoteClient *client = i.getNode()->getValue();
1226 Player *player = m_env.getPlayer(client->peer_id);
1229 std::cout<<player->getName()<<"\t";
1230 client->PrintInfo(std::cout);
1235 //if(g_settings.getBool("enable_experimental"))
1239 Check added and deleted active objects
1242 //dstream<<"Server: Checking added and deleted active objects"<<std::endl;
1244 JMutexAutoLock envlock(m_env_mutex);
1245 JMutexAutoLock conlock(m_con_mutex);
1247 // Radius inside which objects are active
1250 for(core::map<u16, RemoteClient*>::Iterator
1251 i = m_clients.getIterator();
1252 i.atEnd() == false; i++)
1254 RemoteClient *client = i.getNode()->getValue();
1255 Player *player = m_env.getPlayer(client->peer_id);
1258 dstream<<"WARNING: "<<__FUNCTION_NAME<<": Client "<<client->peer_id
1259 <<" has no associated player"<<std::endl;
1262 v3s16 pos = floatToInt(player->getPosition(), BS);
1264 core::map<u16, bool> removed_objects;
1265 core::map<u16, bool> added_objects;
1266 m_env.getRemovedActiveObjects(pos, radius,
1267 client->m_known_objects, removed_objects);
1268 m_env.getAddedActiveObjects(pos, radius,
1269 client->m_known_objects, added_objects);
1271 // Ignore if nothing happened
1272 if(removed_objects.size() == 0 && added_objects.size() == 0)
1274 //dstream<<"INFO: active objects: none changed"<<std::endl;
1278 std::string data_buffer;
1282 // Handle removed objects
1283 writeU16((u8*)buf, removed_objects.size());
1284 data_buffer.append(buf, 2);
1285 for(core::map<u16, bool>::Iterator
1286 i = removed_objects.getIterator();
1287 i.atEnd()==false; i++)
1290 u16 id = i.getNode()->getKey();
1291 ServerActiveObject* obj = m_env.getActiveObject(id);
1293 // Add to data buffer for sending
1294 writeU16((u8*)buf, i.getNode()->getKey());
1295 data_buffer.append(buf, 2);
1297 // Remove from known objects
1298 client->m_known_objects.remove(i.getNode()->getKey());
1300 if(obj && obj->m_known_by_count > 0)
1301 obj->m_known_by_count--;
1304 // Handle added objects
1305 writeU16((u8*)buf, added_objects.size());
1306 data_buffer.append(buf, 2);
1307 for(core::map<u16, bool>::Iterator
1308 i = added_objects.getIterator();
1309 i.atEnd()==false; i++)
1312 u16 id = i.getNode()->getKey();
1313 ServerActiveObject* obj = m_env.getActiveObject(id);
1316 u8 type = ACTIVEOBJECT_TYPE_INVALID;
1318 dstream<<"WARNING: "<<__FUNCTION_NAME
1319 <<": NULL object"<<std::endl;
1321 type = obj->getType();
1323 // Add to data buffer for sending
1324 writeU16((u8*)buf, id);
1325 data_buffer.append(buf, 2);
1326 writeU8((u8*)buf, type);
1327 data_buffer.append(buf, 1);
1330 data_buffer.append(serializeLongString(
1331 obj->getClientInitializationData()));
1333 data_buffer.append(serializeLongString(""));
1335 // Add to known objects
1336 client->m_known_objects.insert(i.getNode()->getKey(), false);
1339 obj->m_known_by_count++;
1343 SharedBuffer<u8> reply(2 + data_buffer.size());
1344 writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD);
1345 memcpy((char*)&reply[2], data_buffer.c_str(),
1346 data_buffer.size());
1348 m_con.Send(client->peer_id, 0, reply, true);
1350 dstream<<"INFO: Server: Sent object remove/add: "
1351 <<removed_objects.size()<<" removed, "
1352 <<added_objects.size()<<" added, "
1353 <<"packet size is "<<reply.getSize()<<std::endl;
1358 Collect a list of all the objects known by the clients
1359 and report it back to the environment.
1362 core::map<u16, bool> all_known_objects;
1364 for(core::map<u16, RemoteClient*>::Iterator
1365 i = m_clients.getIterator();
1366 i.atEnd() == false; i++)
1368 RemoteClient *client = i.getNode()->getValue();
1369 // Go through all known objects of client
1370 for(core::map<u16, bool>::Iterator
1371 i = client->m_known_objects.getIterator();
1372 i.atEnd()==false; i++)
1374 u16 id = i.getNode()->getKey();
1375 all_known_objects[id] = true;
1379 m_env.setKnownActiveObjects(whatever);
1385 Send object messages
1388 JMutexAutoLock envlock(m_env_mutex);
1389 JMutexAutoLock conlock(m_con_mutex);
1392 // Value = data sent by object
1393 core::map<u16, core::list<ActiveObjectMessage>* > buffered_messages;
1395 // Get active object messages from environment
1398 ActiveObjectMessage aom = m_env.getActiveObjectMessage();
1402 core::list<ActiveObjectMessage>* message_list = NULL;
1403 core::map<u16, core::list<ActiveObjectMessage>* >::Node *n;
1404 n = buffered_messages.find(aom.id);
1407 message_list = new core::list<ActiveObjectMessage>;
1408 buffered_messages.insert(aom.id, message_list);
1412 message_list = n->getValue();
1414 message_list->push_back(aom);
1417 // Route data to every client
1418 for(core::map<u16, RemoteClient*>::Iterator
1419 i = m_clients.getIterator();
1420 i.atEnd()==false; i++)
1422 RemoteClient *client = i.getNode()->getValue();
1423 std::string reliable_data;
1424 std::string unreliable_data;
1425 // Go through all objects in message buffer
1426 for(core::map<u16, core::list<ActiveObjectMessage>* >::Iterator
1427 j = buffered_messages.getIterator();
1428 j.atEnd()==false; j++)
1430 // If object is not known by client, skip it
1431 u16 id = j.getNode()->getKey();
1432 if(client->m_known_objects.find(id) == NULL)
1434 // Get message list of object
1435 core::list<ActiveObjectMessage>* list = j.getNode()->getValue();
1436 // Go through every message
1437 for(core::list<ActiveObjectMessage>::Iterator
1438 k = list->begin(); k != list->end(); k++)
1440 // Compose the full new data with header
1441 ActiveObjectMessage aom = *k;
1442 std::string new_data;
1445 writeU16((u8*)&buf[0], aom.id);
1446 new_data.append(buf, 2);
1448 new_data += serializeString(aom.datastring);
1449 // Add data to buffer
1451 reliable_data += new_data;
1453 unreliable_data += new_data;
1457 reliable_data and unreliable_data are now ready.
1460 if(reliable_data.size() > 0)
1462 SharedBuffer<u8> reply(2 + reliable_data.size());
1463 writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES);
1464 memcpy((char*)&reply[2], reliable_data.c_str(),
1465 reliable_data.size());
1467 m_con.Send(client->peer_id, 0, reply, true);
1469 if(unreliable_data.size() > 0)
1471 SharedBuffer<u8> reply(2 + unreliable_data.size());
1472 writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES);
1473 memcpy((char*)&reply[2], unreliable_data.c_str(),
1474 unreliable_data.size());
1475 // Send as unreliable
1476 m_con.Send(client->peer_id, 0, reply, false);
1479 /*if(reliable_data.size() > 0 || unreliable_data.size() > 0)
1481 dstream<<"INFO: Server: Size of object message data: "
1482 <<"reliable: "<<reliable_data.size()
1483 <<", unreliable: "<<unreliable_data.size()
1488 // Clear buffered_messages
1489 for(core::map<u16, core::list<ActiveObjectMessage>* >::Iterator
1490 i = buffered_messages.getIterator();
1491 i.atEnd()==false; i++)
1493 delete i.getNode()->getValue();
1497 } // enable_experimental
1500 Send queued-for-sending map edit events.
1503 while(m_unsent_map_edit_queue.size() != 0)
1505 MapEditEvent* event = m_unsent_map_edit_queue.pop_front();
1507 if(event->type == MEET_ADDNODE)
1509 dstream<<"Server: MEET_ADDNODE"<<std::endl;
1510 sendAddNode(event->p, event->n, event->already_known_by_peer);
1512 else if(event->type == MEET_REMOVENODE)
1514 dstream<<"Server: MEET_REMOVENODE"<<std::endl;
1515 sendRemoveNode(event->p, event->already_known_by_peer);
1517 else if(event->type == MEET_OTHER)
1519 dstream<<"WARNING: Server: MEET_OTHER not implemented"
1524 dstream<<"WARNING: Server: Unknown MapEditEvent "
1525 <<((u32)event->type)<<std::endl;
1533 Send object positions
1534 TODO: Get rid of MapBlockObjects
1537 float &counter = m_objectdata_timer;
1539 if(counter >= g_settings.getFloat("objectdata_interval"))
1541 JMutexAutoLock lock1(m_env_mutex);
1542 JMutexAutoLock lock2(m_con_mutex);
1543 SendObjectData(counter);
1553 JMutexAutoLock envlock(m_env_mutex);
1554 JMutexAutoLock conlock(m_con_mutex);
1556 core::map<v3s16, MapBlock*> changed_blocks;
1557 m_env.getMap().nodeMetadataStep(dtime, changed_blocks);
1559 for(core::map<v3s16, MapBlock*>::Iterator
1560 i = changed_blocks.getIterator();
1561 i.atEnd() == false; i++)
1563 MapBlock *block = i.getNode()->getValue();
1565 for(core::map<u16, RemoteClient*>::Iterator
1566 i = m_clients.getIterator();
1567 i.atEnd()==false; i++)
1569 RemoteClient *client = i.getNode()->getValue();
1570 client->SetBlockNotSent(block->getPos());
1576 Trigger emergethread (it somehow gets to a non-triggered but
1577 bysy state sometimes)
1580 float &counter = m_emergethread_trigger_timer;
1586 m_emergethread.trigger();
1592 float &counter = m_savemap_timer;
1594 if(counter >= g_settings.getFloat("server_map_save_interval"))
1598 JMutexAutoLock lock(m_env_mutex);
1600 if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == true)
1602 // Save only changed parts
1603 m_env.getMap().save(true);
1605 // Delete unused sectors
1606 u32 deleted_count = m_env.getMap().deleteUnusedSectors(
1607 g_settings.getFloat("server_unload_unused_sectors_timeout"));
1608 if(deleted_count > 0)
1610 dout_server<<"Server: Unloaded "<<deleted_count
1611 <<" sectors from memory"<<std::endl;
1615 m_env.serializePlayers(m_mapsavedir);
1621 void Server::Receive()
1623 DSTACK(__FUNCTION_NAME);
1624 u32 data_maxsize = 10000;
1625 Buffer<u8> data(data_maxsize);
1630 JMutexAutoLock conlock(m_con_mutex);
1631 datasize = m_con.Receive(peer_id, *data, data_maxsize);
1634 // This has to be called so that the client list gets synced
1635 // with the peer list of the connection
1636 handlePeerChanges();
1638 ProcessData(*data, datasize, peer_id);
1640 catch(con::InvalidIncomingDataException &e)
1642 derr_server<<"Server::Receive(): "
1643 "InvalidIncomingDataException: what()="
1644 <<e.what()<<std::endl;
1646 catch(con::PeerNotFoundException &e)
1648 //NOTE: This is not needed anymore
1650 // The peer has been disconnected.
1651 // Find the associated player and remove it.
1653 /*JMutexAutoLock envlock(m_env_mutex);
1655 dout_server<<"ServerThread: peer_id="<<peer_id
1656 <<" has apparently closed connection. "
1657 <<"Removing player."<<std::endl;
1659 m_env.removePlayer(peer_id);*/
1663 void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
1665 DSTACK(__FUNCTION_NAME);
1666 // Environment is locked first.
1667 JMutexAutoLock envlock(m_env_mutex);
1668 JMutexAutoLock conlock(m_con_mutex);
1672 peer = m_con.GetPeer(peer_id);
1674 catch(con::PeerNotFoundException &e)
1676 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: peer "
1677 <<peer_id<<" not found"<<std::endl;
1681 u8 peer_ser_ver = getClient(peer->id)->serialization_version;
1689 ToServerCommand command = (ToServerCommand)readU16(&data[0]);
1691 if(command == TOSERVER_INIT)
1693 // [0] u16 TOSERVER_INIT
1694 // [2] u8 SER_FMT_VER_HIGHEST
1695 // [3] u8[20] player_name
1700 derr_server<<DTIME<<"Server: Got TOSERVER_INIT from "
1701 <<peer->id<<std::endl;
1703 // First byte after command is maximum supported
1704 // serialization version
1705 u8 client_max = data[2];
1706 u8 our_max = SER_FMT_VER_HIGHEST;
1707 // Use the highest version supported by both
1708 u8 deployed = core::min_(client_max, our_max);
1709 // If it's lower than the lowest supported, give up.
1710 if(deployed < SER_FMT_VER_LOWEST)
1711 deployed = SER_FMT_VER_INVALID;
1713 //peer->serialization_version = deployed;
1714 getClient(peer->id)->pending_serialization_version = deployed;
1716 if(deployed == SER_FMT_VER_INVALID)
1718 derr_server<<DTIME<<"Server: Cannot negotiate "
1719 "serialization version with peer "
1720 <<peer_id<<std::endl;
1729 const u32 playername_size = 20;
1730 char playername[playername_size];
1731 for(u32 i=0; i<playername_size-1; i++)
1733 playername[i] = data[3+i];
1735 playername[playername_size-1] = 0;
1738 Player *player = emergePlayer(playername, "", peer_id);
1739 //Player *player = m_env.getPlayer(peer_id);
1742 // DEBUG: Test serialization
1743 std::ostringstream test_os;
1744 player->serialize(test_os);
1745 dstream<<"Player serialization test: \""<<test_os.str()
1747 std::istringstream test_is(test_os.str());
1748 player->deSerialize(test_is);
1751 // If failed, cancel
1754 derr_server<<DTIME<<"Server: peer_id="<<peer_id
1755 <<": failed to emerge player"<<std::endl;
1760 // If a client is already connected to the player, cancel
1761 if(player->peer_id != 0)
1763 derr_server<<DTIME<<"Server: peer_id="<<peer_id
1764 <<" tried to connect to "
1765 "an already connected player (peer_id="
1766 <<player->peer_id<<")"<<std::endl;
1769 // Set client of player
1770 player->peer_id = peer_id;
1773 // Check if player doesn't exist
1775 throw con::InvalidIncomingDataException
1776 ("Server::ProcessData(): INIT: Player doesn't exist");
1778 /*// update name if it was supplied
1779 if(datasize >= 20+3)
1782 player->updateName((const char*)&data[3]);
1785 // Now answer with a TOCLIENT_INIT
1787 SharedBuffer<u8> reply(2+1+6+8);
1788 writeU16(&reply[0], TOCLIENT_INIT);
1789 writeU8(&reply[2], deployed);
1790 writeV3S16(&reply[2+1], floatToInt(player->getPosition()+v3f(0,BS/2,0), BS));
1791 //writeU64(&reply[2+1+6], m_env.getServerMap().getSeed());
1794 m_con.Send(peer_id, 0, reply, true);
1798 if(command == TOSERVER_INIT2)
1800 derr_server<<DTIME<<"Server: Got TOSERVER_INIT2 from "
1801 <<peer->id<<std::endl;
1804 getClient(peer->id)->serialization_version
1805 = getClient(peer->id)->pending_serialization_version;
1808 Send some initialization data
1811 // Send player info to all players
1814 // Send inventory to player
1815 SendInventory(peer->id);
1819 SharedBuffer<u8> data = makePacket_TOCLIENT_TIME_OF_DAY(
1820 m_time_of_day.get());
1821 m_con.Send(peer->id, 0, data, true);
1824 // Send information about server to player in chat
1825 SendChatMessage(peer_id, getStatusString());
1827 // Send information about joining in chat
1829 std::wstring name = L"unknown";
1830 Player *player = m_env.getPlayer(peer_id);
1832 name = narrow_to_wide(player->getName());
1834 std::wstring message;
1837 message += L" joined game";
1838 BroadcastChatMessage(message);
1844 if(peer_ser_ver == SER_FMT_VER_INVALID)
1846 derr_server<<DTIME<<"Server::ProcessData(): Cancelling: Peer"
1847 " serialization format invalid or not initialized."
1848 " Skipping incoming command="<<command<<std::endl;
1852 Player *player = m_env.getPlayer(peer_id);
1855 derr_server<<"Server::ProcessData(): Cancelling: "
1856 "No player for peer_id="<<peer_id
1860 if(command == TOSERVER_PLAYERPOS)
1862 if(datasize < 2+12+12+4+4)
1866 v3s32 ps = readV3S32(&data[start+2]);
1867 v3s32 ss = readV3S32(&data[start+2+12]);
1868 f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0;
1869 f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0;
1870 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
1871 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
1872 pitch = wrapDegrees(pitch);
1873 yaw = wrapDegrees(yaw);
1874 player->setPosition(position);
1875 player->setSpeed(speed);
1876 player->setPitch(pitch);
1877 player->setYaw(yaw);
1879 /*dout_server<<"Server::ProcessData(): Moved player "<<peer_id<<" to "
1880 <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")"
1881 <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/
1883 else if(command == TOSERVER_GOTBLOCKS)
1896 u16 count = data[2];
1897 for(u16 i=0; i<count; i++)
1899 if((s16)datasize < 2+1+(i+1)*6)
1900 throw con::InvalidIncomingDataException
1901 ("GOTBLOCKS length is too short");
1902 v3s16 p = readV3S16(&data[2+1+i*6]);
1903 /*dstream<<"Server: GOTBLOCKS ("
1904 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1905 RemoteClient *client = getClient(peer_id);
1906 client->GotBlock(p);
1909 else if(command == TOSERVER_DELETEDBLOCKS)
1922 u16 count = data[2];
1923 for(u16 i=0; i<count; i++)
1925 if((s16)datasize < 2+1+(i+1)*6)
1926 throw con::InvalidIncomingDataException
1927 ("DELETEDBLOCKS length is too short");
1928 v3s16 p = readV3S16(&data[2+1+i*6]);
1929 /*dstream<<"Server: DELETEDBLOCKS ("
1930 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
1931 RemoteClient *client = getClient(peer_id);
1932 client->SetBlockNotSent(p);
1935 else if(command == TOSERVER_CLICK_OBJECT)
1942 [2] u8 button (0=left, 1=right)
1947 u8 button = readU8(&data[2]);
1949 p.X = readS16(&data[3]);
1950 p.Y = readS16(&data[5]);
1951 p.Z = readS16(&data[7]);
1952 s16 id = readS16(&data[9]);
1953 //u16 item_i = readU16(&data[11]);
1955 MapBlock *block = NULL;
1958 block = m_env.getMap().getBlockNoCreate(p);
1960 catch(InvalidPositionException &e)
1962 derr_server<<"CLICK_OBJECT block not found"<<std::endl;
1966 MapBlockObject *obj = block->getObject(id);
1970 derr_server<<"CLICK_OBJECT object not found"<<std::endl;
1974 //TODO: Check that object is reasonably close
1979 InventoryList *ilist = player->inventory.getList("main");
1980 if(g_settings.getBool("creative_mode") == false && ilist != NULL)
1983 // Skip if inventory has no free space
1984 if(ilist->getUsedSlots() == ilist->getSize())
1986 dout_server<<"Player inventory has no free space"<<std::endl;
1991 Create the inventory item
1993 InventoryItem *item = NULL;
1994 // If it is an item-object, take the item from it
1995 if(obj->getTypeId() == MAPBLOCKOBJECT_TYPE_ITEM)
1997 item = ((ItemObject*)obj)->createInventoryItem();
1999 // Else create an item of the object
2002 item = new MapBlockObjectItem
2003 (obj->getInventoryString());
2006 // Add to inventory and send inventory
2007 ilist->addItem(item);
2008 SendInventory(player->peer_id);
2011 // Remove from block
2012 block->removeObject(id);
2015 else if(command == TOSERVER_CLICK_ACTIVEOBJECT)
2023 [2] u8 button (0=left, 1=right)
2027 u8 button = readU8(&data[2]);
2028 u16 id = readS16(&data[3]);
2029 //u16 item_i = readU16(&data[11]);
2031 ServerActiveObject *obj = m_env.getActiveObject(id);
2035 derr_server<<"Server: CLICK_ACTIVEOBJECT: object not found"
2040 //TODO: Check that object is reasonably close
2042 // Left click, pick object up (usually)
2045 InventoryList *ilist = player->inventory.getList("main");
2046 if(g_settings.getBool("creative_mode") == false && ilist != NULL)
2049 // Skip if inventory has no free space
2050 if(ilist->getUsedSlots() == ilist->getSize())
2052 dout_server<<"Player inventory has no free space"<<std::endl;
2056 // Skip if object has been removed
2061 Create the inventory item
2063 InventoryItem *item = obj->createPickedUpItem();
2067 // Add to inventory and send inventory
2068 ilist->addItem(item);
2069 SendInventory(player->peer_id);
2071 // Remove object from environment
2072 obj->m_removed = true;
2077 else if(command == TOSERVER_GROUND_ACTION)
2085 [3] v3s16 nodepos_undersurface
2086 [9] v3s16 nodepos_abovesurface
2091 2: stop digging (all parameters ignored)
2092 3: digging completed
2094 u8 action = readU8(&data[2]);
2096 p_under.X = readS16(&data[3]);
2097 p_under.Y = readS16(&data[5]);
2098 p_under.Z = readS16(&data[7]);
2100 p_over.X = readS16(&data[9]);
2101 p_over.Y = readS16(&data[11]);
2102 p_over.Z = readS16(&data[13]);
2103 u16 item_i = readU16(&data[15]);
2105 //TODO: Check that target is reasonably close
2113 NOTE: This can be used in the future to check if
2114 somebody is cheating, by checking the timing.
2121 else if(action == 2)
2124 RemoteClient *client = getClient(peer->id);
2125 JMutexAutoLock digmutex(client->m_dig_mutex);
2126 client->m_dig_tool_item = -1;
2131 3: Digging completed
2133 else if(action == 3)
2135 // Mandatory parameter; actually used for nothing
2136 core::map<v3s16, MapBlock*> modified_blocks;
2139 u8 mineral = MINERAL_NONE;
2141 bool cannot_remove_node = false;
2145 MapNode n = m_env.getMap().getNode(p_under);
2147 mineral = n.getMineral();
2148 // Get material at position
2150 // If not yet cancelled
2151 if(cannot_remove_node == false)
2153 // If it's not diggable, do nothing
2154 if(content_diggable(material) == false)
2156 derr_server<<"Server: Not finishing digging: "
2157 <<"Node not diggable"
2159 cannot_remove_node = true;
2162 // If not yet cancelled
2163 if(cannot_remove_node == false)
2165 // Get node metadata
2166 NodeMetadata *meta = m_env.getMap().getNodeMetadata(p_under);
2167 if(meta && meta->nodeRemovalDisabled() == true)
2169 derr_server<<"Server: Not finishing digging: "
2170 <<"Node metadata disables removal"
2172 cannot_remove_node = true;
2176 catch(InvalidPositionException &e)
2178 derr_server<<"Server: Not finishing digging: Node not found."
2179 <<" Adding block to emerge queue."
2181 m_emerge_queue.addBlock(peer_id,
2182 getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK);
2183 cannot_remove_node = true;
2187 If node can't be removed, set block to be re-sent to
2190 if(cannot_remove_node)
2192 derr_server<<"Server: Not finishing digging."<<std::endl;
2194 // Client probably has wrong data.
2195 // Set block not sent, so that client will get
2197 dstream<<"Client "<<peer_id<<" tried to dig "
2198 <<"node; but node cannot be removed."
2199 <<" setting MapBlock not sent."<<std::endl;
2200 RemoteClient *client = getClient(peer_id);
2201 v3s16 blockpos = getNodeBlockPos(p_under);
2202 client->SetBlockNotSent(blockpos);
2208 Send the removal to all other clients.
2209 - If other player is close, send REMOVENODE
2210 - Otherwise set blocks not sent
2212 core::list<u16> far_players;
2213 sendRemoveNode(p_under, peer_id, &far_players, 100);
2216 Update and send inventory
2219 if(g_settings.getBool("creative_mode") == false)
2224 InventoryList *mlist = player->inventory.getList("main");
2227 InventoryItem *item = mlist->getItem(item_i);
2228 if(item && (std::string)item->getName() == "ToolItem")
2230 ToolItem *titem = (ToolItem*)item;
2231 std::string toolname = titem->getToolName();
2233 // Get digging properties for material and tool
2234 DiggingProperties prop =
2235 getDiggingProperties(material, toolname);
2237 if(prop.diggable == false)
2239 derr_server<<"Server: WARNING: Player digged"
2240 <<" with impossible material + tool"
2241 <<" combination"<<std::endl;
2244 bool weared_out = titem->addWear(prop.wear);
2248 mlist->deleteItem(item_i);
2254 Add dug item to inventory
2257 InventoryItem *item = NULL;
2259 if(mineral != MINERAL_NONE)
2260 item = getDiggedMineralItem(mineral);
2265 std::string &dug_s = content_features(material).dug_item;
2268 std::istringstream is(dug_s, std::ios::binary);
2269 item = InventoryItem::deSerialize(is);
2275 // Add a item to inventory
2276 player->inventory.addItem("main", item);
2279 SendInventory(player->peer_id);
2285 (this takes some time so it is done after the quick stuff)
2287 m_ignore_map_edit_events = true;
2288 m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
2289 m_ignore_map_edit_events = false;
2292 Set blocks not sent to far players
2294 for(core::list<u16>::Iterator
2295 i = far_players.begin();
2296 i != far_players.end(); i++)
2299 RemoteClient *client = getClient(peer_id);
2302 client->SetBlocksNotSent(modified_blocks);
2309 else if(action == 1)
2312 InventoryList *ilist = player->inventory.getList("main");
2317 InventoryItem *item = ilist->getItem(item_i);
2319 // If there is no item, it is not possible to add it anywhere
2324 Handle material items
2326 if(std::string("MaterialItem") == item->getName())
2329 // Don't add a node if this is not a free space
2330 MapNode n2 = m_env.getMap().getNode(p_over);
2331 if(content_buildable_to(n2.d) == false)
2333 // Client probably has wrong data.
2334 // Set block not sent, so that client will get
2336 dstream<<"Client "<<peer_id<<" tried to place"
2337 <<" node in invalid position; setting"
2338 <<" MapBlock not sent."<<std::endl;
2339 RemoteClient *client = getClient(peer_id);
2340 v3s16 blockpos = getNodeBlockPos(p_over);
2341 client->SetBlockNotSent(blockpos);
2345 catch(InvalidPositionException &e)
2347 derr_server<<"Server: Ignoring ADDNODE: Node not found"
2348 <<" Adding block to emerge queue."
2350 m_emerge_queue.addBlock(peer_id,
2351 getNodeBlockPos(p_over), BLOCK_EMERGE_FLAG_FROMDISK);
2355 // Reset build time counter
2356 getClient(peer->id)->m_time_from_building = 0.0;
2359 MaterialItem *mitem = (MaterialItem*)item;
2361 n.d = mitem->getMaterial();
2362 if(content_features(n.d).wall_mounted)
2363 n.dir = packDir(p_under - p_over);
2368 core::list<u16> far_players;
2369 sendAddNode(p_over, n, 0, &far_players, 100);
2374 InventoryList *ilist = player->inventory.getList("main");
2375 if(g_settings.getBool("creative_mode") == false && ilist)
2377 // Remove from inventory and send inventory
2378 if(mitem->getCount() == 1)
2379 ilist->deleteItem(item_i);
2383 SendInventory(peer_id);
2389 This takes some time so it is done after the quick stuff
2391 core::map<v3s16, MapBlock*> modified_blocks;
2392 m_ignore_map_edit_events = true;
2393 m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
2394 m_ignore_map_edit_events = false;
2397 Set blocks not sent to far players
2399 for(core::list<u16>::Iterator
2400 i = far_players.begin();
2401 i != far_players.end(); i++)
2404 RemoteClient *client = getClient(peer_id);
2407 client->SetBlocksNotSent(modified_blocks);
2411 Calculate special events
2414 /*if(n.d == CONTENT_MESE)
2417 for(s16 z=-1; z<=1; z++)
2418 for(s16 y=-1; y<=1; y++)
2419 for(s16 x=-1; x<=1; x++)
2426 Place other item (not a block)
2430 v3s16 blockpos = getNodeBlockPos(p_over);
2433 Check that the block is loaded so that the item
2434 can properly be added to the static list too
2436 MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(blockpos);
2439 derr_server<<"Error while placing object: "
2440 "block not found"<<std::endl;
2444 dout_server<<"Placing a miscellaneous item on map"
2447 // Calculate a position for it
2448 v3f pos = intToFloat(p_over, BS);
2450 pos.Y -= BS*0.25; // let it drop a bit
2452 pos.X += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
2453 pos.Z += BS*0.2*(float)myrand_range(-1000,1000)/1000.0;
2458 ServerActiveObject *obj = item->createSAO(&m_env, 0, pos);
2462 derr_server<<"WARNING: item resulted in NULL object, "
2463 <<"not placing onto map"
2468 // Add the object to the environment
2469 m_env.addActiveObject(obj);
2471 dout_server<<"Placed object"<<std::endl;
2473 if(g_settings.getBool("creative_mode") == false)
2475 // Delete the right amount of items from the slot
2476 u16 dropcount = item->getDropCount();
2478 // Delete item if all gone
2479 if(item->getCount() <= dropcount)
2481 if(item->getCount() < dropcount)
2482 dstream<<"WARNING: Server: dropped more items"
2483 <<" than the slot contains"<<std::endl;
2485 InventoryList *ilist = player->inventory.getList("main");
2487 // Remove from inventory and send inventory
2488 ilist->deleteItem(item_i);
2490 // Else decrement it
2492 item->remove(dropcount);
2495 SendInventory(peer_id);
2503 Catch invalid actions
2507 derr_server<<"WARNING: Server: Invalid action "
2508 <<action<<std::endl;
2512 else if(command == TOSERVER_RELEASE)
2521 dstream<<"TOSERVER_RELEASE ignored"<<std::endl;
2524 else if(command == TOSERVER_SIGNTEXT)
2533 std::string datastring((char*)&data[2], datasize-2);
2534 std::istringstream is(datastring, std::ios_base::binary);
2537 is.read((char*)buf, 6);
2538 v3s16 blockpos = readV3S16(buf);
2539 is.read((char*)buf, 2);
2540 s16 id = readS16(buf);
2541 is.read((char*)buf, 2);
2542 u16 textlen = readU16(buf);
2544 for(u16 i=0; i<textlen; i++)
2546 is.read((char*)buf, 1);
2547 text += (char)buf[0];
2550 MapBlock *block = NULL;
2553 block = m_env.getMap().getBlockNoCreate(blockpos);
2555 catch(InvalidPositionException &e)
2557 derr_server<<"Error while setting sign text: "
2558 "block not found"<<std::endl;
2562 MapBlockObject *obj = block->getObject(id);
2565 derr_server<<"Error while setting sign text: "
2566 "object not found"<<std::endl;
2570 if(obj->getTypeId() != MAPBLOCKOBJECT_TYPE_SIGN)
2572 derr_server<<"Error while setting sign text: "
2573 "object is not a sign"<<std::endl;
2577 ((SignObject*)obj)->setText(text);
2579 obj->getBlock()->setChangedFlag();
2581 else if(command == TOSERVER_SIGNNODETEXT)
2589 std::string datastring((char*)&data[2], datasize-2);
2590 std::istringstream is(datastring, std::ios_base::binary);
2593 is.read((char*)buf, 6);
2594 v3s16 p = readV3S16(buf);
2595 is.read((char*)buf, 2);
2596 u16 textlen = readU16(buf);
2598 for(u16 i=0; i<textlen; i++)
2600 is.read((char*)buf, 1);
2601 text += (char)buf[0];
2604 NodeMetadata *meta = m_env.getMap().getNodeMetadata(p);
2607 if(meta->typeId() != CONTENT_SIGN_WALL)
2609 SignNodeMetadata *signmeta = (SignNodeMetadata*)meta;
2610 signmeta->setText(text);
2612 v3s16 blockpos = getNodeBlockPos(p);
2613 MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(blockpos);
2616 block->setChangedFlag();
2619 for(core::map<u16, RemoteClient*>::Iterator
2620 i = m_clients.getIterator();
2621 i.atEnd()==false; i++)
2623 RemoteClient *client = i.getNode()->getValue();
2624 client->SetBlockNotSent(blockpos);
2627 else if(command == TOSERVER_INVENTORY_ACTION)
2629 /*// Ignore inventory changes if in creative mode
2630 if(g_settings.getBool("creative_mode") == true)
2632 dstream<<"TOSERVER_INVENTORY_ACTION: ignoring in creative mode"
2636 // Strip command and create a stream
2637 std::string datastring((char*)&data[2], datasize-2);
2638 dstream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl;
2639 std::istringstream is(datastring, std::ios_base::binary);
2641 InventoryAction *a = InventoryAction::deSerialize(is);
2646 c.current_player = player;
2649 Handle craftresult specially if not in creative mode
2651 bool disable_action = false;
2652 if(a->getType() == IACTION_MOVE
2653 && g_settings.getBool("creative_mode") == false)
2655 IMoveAction *ma = (IMoveAction*)a;
2656 if(ma->to_inv == "current_player" &&
2657 ma->from_inv == "current_player")
2659 InventoryList *rlist = player->inventory.getList("craftresult");
2661 InventoryList *clist = player->inventory.getList("craft");
2663 InventoryList *mlist = player->inventory.getList("main");
2666 Craftresult is no longer preview if something
2669 if(ma->to_list == "craftresult"
2670 && ma->from_list != "craftresult")
2672 // If it currently is a preview, remove
2674 if(player->craftresult_is_preview)
2676 rlist->deleteItem(0);
2678 player->craftresult_is_preview = false;
2681 Crafting takes place if this condition is true.
2683 if(player->craftresult_is_preview &&
2684 ma->from_list == "craftresult")
2686 player->craftresult_is_preview = false;
2687 clist->decrementMaterials(1);
2690 If the craftresult is placed on itself, move it to
2691 main inventory instead of doing the action
2693 if(ma->to_list == "craftresult"
2694 && ma->from_list == "craftresult")
2696 disable_action = true;
2698 InventoryItem *item1 = rlist->changeItem(0, NULL);
2699 mlist->addItem(item1);
2704 if(disable_action == false)
2706 // Feed action to player inventory
2714 SendInventory(player->peer_id);
2719 dstream<<"TOSERVER_INVENTORY_ACTION: "
2720 <<"InventoryAction::deSerialize() returned NULL"
2724 else if(command == TOSERVER_CHAT_MESSAGE)
2732 std::string datastring((char*)&data[2], datasize-2);
2733 std::istringstream is(datastring, std::ios_base::binary);
2736 is.read((char*)buf, 2);
2737 u16 len = readU16(buf);
2739 std::wstring message;
2740 for(u16 i=0; i<len; i++)
2742 is.read((char*)buf, 2);
2743 message += (wchar_t)readU16(buf);
2746 // Get player name of this client
2747 std::wstring name = narrow_to_wide(player->getName());
2749 // Line to send to players
2751 // Whether to send to the player that sent the line
2752 bool send_to_sender = false;
2753 // Whether to send to other players
2754 bool send_to_others = false;
2757 std::wstring commandprefix = L"/#";
2758 if(message.substr(0, commandprefix.size()) == commandprefix)
2760 line += L"Server: ";
2762 message = message.substr(commandprefix.size());
2763 // Get player name as narrow string
2764 std::string name_s = player->getName();
2765 // Convert message to narrow string
2766 std::string message_s = wide_to_narrow(message);
2767 // Operator is the single name defined in config.
2768 std::string operator_name = g_settings.get("name");
2769 bool is_operator = (operator_name != "" &&
2770 wide_to_narrow(name) == operator_name);
2771 bool valid_command = false;
2772 if(message_s == "help")
2774 line += L"-!- Available commands: ";
2778 line += L"shutdown setting ";
2783 send_to_sender = true;
2784 valid_command = true;
2786 else if(message_s == "status")
2788 line = getStatusString();
2789 send_to_sender = true;
2790 valid_command = true;
2792 else if(is_operator)
2794 if(message_s == "shutdown")
2796 dstream<<DTIME<<" Server: Operator requested shutdown."
2798 m_shutdown_requested.set(true);
2800 line += L"*** Server shutting down (operator request)";
2801 send_to_sender = true;
2802 valid_command = true;
2804 else if(message_s.substr(0,8) == "setting ")
2806 std::string confline = message_s.substr(8);
2807 g_settings.parseConfigLine(confline);
2808 line += L"-!- Setting changed.";
2809 send_to_sender = true;
2810 valid_command = true;
2814 if(valid_command == false)
2816 line += L"-!- Invalid command: " + message;
2817 send_to_sender = true;
2828 send_to_others = true;
2833 dstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl;
2836 Send the message to clients
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)
2849 bool sender_selected = (peer_id == client->peer_id);
2850 if(sender_selected == true && send_to_sender == false)
2852 if(sender_selected == false && send_to_others == false)
2855 SendChatMessage(client->peer_id, line);
2861 derr_server<<"WARNING: Server::ProcessData(): Ignoring "
2862 "unknown command "<<command<<std::endl;
2866 catch(SendFailedException &e)
2868 derr_server<<"Server::ProcessData(): SendFailedException: "
2874 void Server::onMapEditEvent(MapEditEvent *event)
2876 dstream<<"Server::onMapEditEvent()"<<std::endl;
2877 if(m_ignore_map_edit_events)
2879 MapEditEvent *e = event->clone();
2880 m_unsent_map_edit_queue.push_back(e);
2883 Inventory* Server::getInventory(InventoryContext *c, std::string id)
2885 if(id == "current_player")
2887 assert(c->current_player);
2888 return &(c->current_player->inventory);
2892 std::string id0 = fn.next(":");
2894 if(id0 == "nodemeta")
2897 p.X = stoi(fn.next(","));
2898 p.Y = stoi(fn.next(","));
2899 p.Z = stoi(fn.next(","));
2900 NodeMetadata *meta = m_env.getMap().getNodeMetadata(p);
2902 return meta->getInventory();
2903 dstream<<"nodemeta at ("<<p.X<<","<<p.Y<<","<<p.Z<<"): "
2904 <<"no metadata found"<<std::endl;
2908 dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
2911 void Server::inventoryModified(InventoryContext *c, std::string id)
2913 if(id == "current_player")
2915 assert(c->current_player);
2917 SendInventory(c->current_player->peer_id);
2922 std::string id0 = fn.next(":");
2924 if(id0 == "nodemeta")
2927 p.X = stoi(fn.next(","));
2928 p.Y = stoi(fn.next(","));
2929 p.Z = stoi(fn.next(","));
2930 v3s16 blockpos = getNodeBlockPos(p);
2932 NodeMetadata *meta = m_env.getMap().getNodeMetadata(p);
2934 meta->inventoryModified();
2936 for(core::map<u16, RemoteClient*>::Iterator
2937 i = m_clients.getIterator();
2938 i.atEnd()==false; i++)
2940 RemoteClient *client = i.getNode()->getValue();
2941 client->SetBlockNotSent(blockpos);
2947 dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
2950 core::list<PlayerInfo> Server::getPlayerInfo()
2952 DSTACK(__FUNCTION_NAME);
2953 JMutexAutoLock envlock(m_env_mutex);
2954 JMutexAutoLock conlock(m_con_mutex);
2956 core::list<PlayerInfo> list;
2958 core::list<Player*> players = m_env.getPlayers();
2960 core::list<Player*>::Iterator i;
2961 for(i = players.begin();
2962 i != players.end(); i++)
2966 Player *player = *i;
2969 con::Peer *peer = m_con.GetPeer(player->peer_id);
2970 // Copy info from peer to info struct
2972 info.address = peer->address;
2973 info.avg_rtt = peer->avg_rtt;
2975 catch(con::PeerNotFoundException &e)
2977 // Set dummy peer info
2979 info.address = Address(0,0,0,0,0);
2983 snprintf(info.name, PLAYERNAME_SIZE, "%s", player->getName());
2984 info.position = player->getPosition();
2986 list.push_back(info);
2993 void Server::peerAdded(con::Peer *peer)
2995 DSTACK(__FUNCTION_NAME);
2996 dout_server<<"Server::peerAdded(): peer->id="
2997 <<peer->id<<std::endl;
3000 c.type = PEER_ADDED;
3001 c.peer_id = peer->id;
3003 m_peer_change_queue.push_back(c);
3006 void Server::deletingPeer(con::Peer *peer, bool timeout)
3008 DSTACK(__FUNCTION_NAME);
3009 dout_server<<"Server::deletingPeer(): peer->id="
3010 <<peer->id<<", timeout="<<timeout<<std::endl;
3013 c.type = PEER_REMOVED;
3014 c.peer_id = peer->id;
3015 c.timeout = timeout;
3016 m_peer_change_queue.push_back(c);
3019 void Server::SendObjectData(float dtime)
3021 DSTACK(__FUNCTION_NAME);
3023 core::map<v3s16, bool> stepped_blocks;
3025 for(core::map<u16, RemoteClient*>::Iterator
3026 i = m_clients.getIterator();
3027 i.atEnd() == false; i++)
3029 u16 peer_id = i.getNode()->getKey();
3030 RemoteClient *client = i.getNode()->getValue();
3031 assert(client->peer_id == peer_id);
3033 if(client->serialization_version == SER_FMT_VER_INVALID)
3036 client->SendObjectData(this, dtime, stepped_blocks);
3040 void Server::SendPlayerInfos()
3042 DSTACK(__FUNCTION_NAME);
3044 //JMutexAutoLock envlock(m_env_mutex);
3046 // Get connected players
3047 core::list<Player*> players = m_env.getPlayers(true);
3049 u32 player_count = players.getSize();
3050 u32 datasize = 2+(2+PLAYERNAME_SIZE)*player_count;
3052 SharedBuffer<u8> data(datasize);
3053 writeU16(&data[0], TOCLIENT_PLAYERINFO);
3056 core::list<Player*>::Iterator i;
3057 for(i = players.begin();
3058 i != players.end(); i++)
3060 Player *player = *i;
3062 /*dstream<<"Server sending player info for player with "
3063 "peer_id="<<player->peer_id<<std::endl;*/
3065 writeU16(&data[start], player->peer_id);
3066 memset((char*)&data[start+2], 0, PLAYERNAME_SIZE);
3067 snprintf((char*)&data[start+2], PLAYERNAME_SIZE, "%s", player->getName());
3068 start += 2+PLAYERNAME_SIZE;
3071 //JMutexAutoLock conlock(m_con_mutex);
3074 m_con.SendToAll(0, data, true);
3077 void Server::SendInventory(u16 peer_id)
3079 DSTACK(__FUNCTION_NAME);
3081 Player* player = m_env.getPlayer(peer_id);
3085 Calculate crafting stuff
3087 if(g_settings.getBool("creative_mode") == false)
3089 InventoryList *clist = player->inventory.getList("craft");
3090 InventoryList *rlist = player->inventory.getList("craftresult");
3092 if(rlist->getUsedSlots() == 0)
3093 player->craftresult_is_preview = true;
3095 if(rlist && player->craftresult_is_preview)
3097 rlist->clearItems();
3099 if(clist && rlist && player->craftresult_is_preview)
3101 InventoryItem *items[9];
3102 for(u16 i=0; i<9; i++)
3104 items[i] = clist->getItem(i);
3113 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_TREE);
3114 if(checkItemCombination(items, specs))
3116 rlist->addItem(new MaterialItem(CONTENT_WOOD, 4));
3125 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3126 if(checkItemCombination(items, specs))
3128 rlist->addItem(new CraftItem("Stick", 4));
3137 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3138 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3139 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3140 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3141 specs[4] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3142 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3143 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3144 if(checkItemCombination(items, specs))
3146 //rlist->addItem(new MapBlockObjectItem("Sign"));
3147 rlist->addItem(new MaterialItem(CONTENT_SIGN_WALL, 1));
3156 specs[0] = ItemSpec(ITEM_CRAFT, "lump_of_coal");
3157 specs[3] = ItemSpec(ITEM_CRAFT, "Stick");
3158 if(checkItemCombination(items, specs))
3160 rlist->addItem(new MaterialItem(CONTENT_TORCH, 4));
3169 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3170 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3171 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3172 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3173 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3174 if(checkItemCombination(items, specs))
3176 rlist->addItem(new ToolItem("WPick", 0));
3185 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3186 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3187 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3188 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3189 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3190 if(checkItemCombination(items, specs))
3192 rlist->addItem(new ToolItem("STPick", 0));
3201 specs[0] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3202 specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3203 specs[2] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3204 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3205 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3206 if(checkItemCombination(items, specs))
3208 rlist->addItem(new ToolItem("SteelPick", 0));
3217 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
3218 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
3219 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_MESE);
3220 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3221 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3222 if(checkItemCombination(items, specs))
3224 rlist->addItem(new ToolItem("MesePick", 0));
3233 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3234 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3235 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3236 if(checkItemCombination(items, specs))
3238 rlist->addItem(new ToolItem("WShovel", 0));
3247 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3248 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3249 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3250 if(checkItemCombination(items, specs))
3252 rlist->addItem(new ToolItem("STShovel", 0));
3261 specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3262 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3263 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3264 if(checkItemCombination(items, specs))
3266 rlist->addItem(new ToolItem("SteelShovel", 0));
3275 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3276 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3277 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3278 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3279 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3280 if(checkItemCombination(items, specs))
3282 rlist->addItem(new ToolItem("WAxe", 0));
3291 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3292 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3293 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3294 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3295 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3296 if(checkItemCombination(items, specs))
3298 rlist->addItem(new ToolItem("STAxe", 0));
3307 specs[0] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3308 specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3309 specs[3] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3310 specs[4] = ItemSpec(ITEM_CRAFT, "Stick");
3311 specs[7] = ItemSpec(ITEM_CRAFT, "Stick");
3312 if(checkItemCombination(items, specs))
3314 rlist->addItem(new ToolItem("SteelAxe", 0));
3323 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3324 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3325 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3326 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3327 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3328 specs[6] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3329 specs[7] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3330 specs[8] = ItemSpec(ITEM_MATERIAL, CONTENT_WOOD);
3331 if(checkItemCombination(items, specs))
3333 rlist->addItem(new MaterialItem(CONTENT_CHEST, 1));
3342 specs[0] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3343 specs[1] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3344 specs[2] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3345 specs[3] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3346 specs[5] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3347 specs[6] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3348 specs[7] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3349 specs[8] = ItemSpec(ITEM_MATERIAL, CONTENT_COBBLE);
3350 if(checkItemCombination(items, specs))
3352 rlist->addItem(new MaterialItem(CONTENT_FURNACE, 1));
3361 specs[0] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3362 specs[1] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3363 specs[2] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3364 specs[3] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3365 specs[4] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3366 specs[5] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3367 specs[6] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3368 specs[7] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3369 specs[8] = ItemSpec(ITEM_CRAFT, "steel_ingot");
3370 if(checkItemCombination(items, specs))
3372 rlist->addItem(new MaterialItem(CONTENT_STEEL, 1));
3378 } // if creative_mode == false
3384 std::ostringstream os;
3385 //os.imbue(std::locale("C"));
3387 player->inventory.serialize(os);
3389 std::string s = os.str();
3391 SharedBuffer<u8> data(s.size()+2);
3392 writeU16(&data[0], TOCLIENT_INVENTORY);
3393 memcpy(&data[2], s.c_str(), s.size());
3396 m_con.Send(peer_id, 0, data, true);
3399 void Server::SendChatMessage(u16 peer_id, const std::wstring &message)
3401 DSTACK(__FUNCTION_NAME);
3403 std::ostringstream os(std::ios_base::binary);
3407 writeU16(buf, TOCLIENT_CHAT_MESSAGE);
3408 os.write((char*)buf, 2);
3411 writeU16(buf, message.size());
3412 os.write((char*)buf, 2);
3415 for(u32 i=0; i<message.size(); i++)
3419 os.write((char*)buf, 2);
3423 std::string s = os.str();
3424 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
3426 m_con.Send(peer_id, 0, data, true);
3429 void Server::BroadcastChatMessage(const std::wstring &message)
3431 for(core::map<u16, RemoteClient*>::Iterator
3432 i = m_clients.getIterator();
3433 i.atEnd() == false; i++)
3435 // Get client and check that it is valid
3436 RemoteClient *client = i.getNode()->getValue();
3437 assert(client->peer_id == i.getNode()->getKey());
3438 if(client->serialization_version == SER_FMT_VER_INVALID)
3441 SendChatMessage(client->peer_id, message);
3445 void Server::sendRemoveNode(v3s16 p, u16 ignore_id,
3446 core::list<u16> *far_players, float far_d_nodes)
3448 float maxd = far_d_nodes*BS;
3449 v3f p_f = intToFloat(p, BS);
3453 SharedBuffer<u8> reply(replysize);
3454 writeU16(&reply[0], TOCLIENT_REMOVENODE);
3455 writeS16(&reply[2], p.X);
3456 writeS16(&reply[4], p.Y);
3457 writeS16(&reply[6], p.Z);
3459 for(core::map<u16, RemoteClient*>::Iterator
3460 i = m_clients.getIterator();
3461 i.atEnd() == false; i++)
3463 // Get client and check that it is valid
3464 RemoteClient *client = i.getNode()->getValue();
3465 assert(client->peer_id == i.getNode()->getKey());
3466 if(client->serialization_version == SER_FMT_VER_INVALID)
3469 // Don't send if it's the same one
3470 if(client->peer_id == ignore_id)
3476 Player *player = m_env.getPlayer(client->peer_id);
3479 // If player is far away, only set modified blocks not sent
3480 v3f player_pos = player->getPosition();
3481 if(player_pos.getDistanceFrom(p_f) > maxd)
3483 far_players->push_back(client->peer_id);
3490 m_con.Send(client->peer_id, 0, reply, true);
3494 void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id,
3495 core::list<u16> *far_players, float far_d_nodes)
3497 float maxd = far_d_nodes*BS;
3498 v3f p_f = intToFloat(p, BS);
3500 for(core::map<u16, RemoteClient*>::Iterator
3501 i = m_clients.getIterator();
3502 i.atEnd() == false; i++)
3504 // Get client and check that it is valid
3505 RemoteClient *client = i.getNode()->getValue();
3506 assert(client->peer_id == i.getNode()->getKey());
3507 if(client->serialization_version == SER_FMT_VER_INVALID)
3510 // Don't send if it's the same one
3511 if(client->peer_id == ignore_id)
3517 Player *player = m_env.getPlayer(client->peer_id);
3520 // If player is far away, only set modified blocks not sent
3521 v3f player_pos = player->getPosition();
3522 if(player_pos.getDistanceFrom(p_f) > maxd)
3524 far_players->push_back(client->peer_id);
3531 u32 replysize = 8 + MapNode::serializedLength(client->serialization_version);
3532 SharedBuffer<u8> reply(replysize);
3533 writeU16(&reply[0], TOCLIENT_ADDNODE);
3534 writeS16(&reply[2], p.X);
3535 writeS16(&reply[4], p.Y);
3536 writeS16(&reply[6], p.Z);
3537 n.serialize(&reply[8], client->serialization_version);
3540 m_con.Send(client->peer_id, 0, reply, true);
3544 void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver)
3546 DSTACK(__FUNCTION_NAME);
3548 Create a packet with the block in the right format
3551 std::ostringstream os(std::ios_base::binary);
3552 block->serialize(os, ver);
3553 std::string s = os.str();
3554 SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size());
3556 u32 replysize = 8 + blockdata.getSize();
3557 SharedBuffer<u8> reply(replysize);
3558 v3s16 p = block->getPos();
3559 writeU16(&reply[0], TOCLIENT_BLOCKDATA);
3560 writeS16(&reply[2], p.X);
3561 writeS16(&reply[4], p.Y);
3562 writeS16(&reply[6], p.Z);
3563 memcpy(&reply[8], *blockdata, blockdata.getSize());
3565 /*dstream<<"Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")"
3566 <<": \tpacket size: "<<replysize<<std::endl;*/
3571 m_con.Send(peer_id, 1, reply, true);
3574 void Server::SendBlocks(float dtime)
3576 DSTACK(__FUNCTION_NAME);
3578 JMutexAutoLock envlock(m_env_mutex);
3579 JMutexAutoLock conlock(m_con_mutex);
3581 //TimeTaker timer("Server::SendBlocks");
3583 core::array<PrioritySortedBlockTransfer> queue;
3585 s32 total_sending = 0;
3587 for(core::map<u16, RemoteClient*>::Iterator
3588 i = m_clients.getIterator();
3589 i.atEnd() == false; i++)
3591 RemoteClient *client = i.getNode()->getValue();
3592 assert(client->peer_id == i.getNode()->getKey());
3594 total_sending += client->SendingCount();
3596 if(client->serialization_version == SER_FMT_VER_INVALID)
3599 client->GetNextBlocks(this, dtime, queue);
3603 // Lowest priority number comes first.
3604 // Lowest is most important.
3607 for(u32 i=0; i<queue.size(); i++)
3609 //TODO: Calculate limit dynamically
3610 if(total_sending >= g_settings.getS32
3611 ("max_simultaneous_block_sends_server_total"))
3614 PrioritySortedBlockTransfer q = queue[i];
3616 MapBlock *block = NULL;
3619 block = m_env.getMap().getBlockNoCreate(q.pos);
3621 catch(InvalidPositionException &e)
3626 RemoteClient *client = getClient(q.peer_id);
3628 SendBlockNoLock(q.peer_id, block, client->serialization_version);
3630 client->SentBlock(q.pos);
3637 RemoteClient* Server::getClient(u16 peer_id)
3639 DSTACK(__FUNCTION_NAME);
3640 //JMutexAutoLock lock(m_con_mutex);
3641 core::map<u16, RemoteClient*>::Node *n;
3642 n = m_clients.find(peer_id);
3643 // A client should exist for all peers
3645 return n->getValue();
3648 std::wstring Server::getStatusString()
3650 std::wostringstream os(std::ios_base::binary);
3653 os<<L"uptime="<<m_uptime.get();
3654 // Information about clients
3656 for(core::map<u16, RemoteClient*>::Iterator
3657 i = m_clients.getIterator();
3658 i.atEnd() == false; i++)
3660 // Get client and check that it is valid
3661 RemoteClient *client = i.getNode()->getValue();
3662 assert(client->peer_id == i.getNode()->getKey());
3663 if(client->serialization_version == SER_FMT_VER_INVALID)
3666 Player *player = m_env.getPlayer(client->peer_id);
3667 // Get name of player
3668 std::wstring name = L"unknown";
3670 name = narrow_to_wide(player->getName());
3671 // Add name to information string
3675 if(((ServerMap*)(&m_env.getMap()))->isSavingEnabled() == false)
3676 os<<" WARNING: Map saving is disabled."<<std::endl;
3681 void setCreativeInventory(Player *player)
3683 player->resetInventory();
3685 // Give some good picks
3687 InventoryItem *item = new ToolItem("STPick", 0);
3688 void* r = player->inventory.addItem("main", item);
3692 InventoryItem *item = new ToolItem("MesePick", 0);
3693 void* r = player->inventory.addItem("main", item);
3701 // CONTENT_IGNORE-terminated list
3702 u8 material_items[] = {
3711 CONTENT_WATERSOURCE,
3719 u8 *mip = material_items;
3720 for(u16 i=0; i<PLAYER_INVENTORY_SIZE; i++)
3722 if(*mip == CONTENT_IGNORE)
3725 InventoryItem *item = new MaterialItem(*mip, 1);
3726 player->inventory.addItem("main", item);
3732 assert(USEFUL_CONTENT_COUNT <= PLAYER_INVENTORY_SIZE);
3735 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 1);
3736 player->inventory.addItem("main", item);
3739 for(u16 i=0; i<USEFUL_CONTENT_COUNT; i++)
3741 // Skip some materials
3742 if(i == CONTENT_WATER || i == CONTENT_TORCH
3743 || i == CONTENT_COALSTONE)
3746 InventoryItem *item = new MaterialItem(i, 1);
3747 player->inventory.addItem("main", item);
3753 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3754 void* r = player->inventory.addItem("main", item);
3759 Player *Server::emergePlayer(const char *name, const char *password,
3763 Try to get an existing player
3765 Player *player = m_env.getPlayer(name);
3768 // If player is already connected, cancel
3769 if(player->peer_id != 0)
3771 dstream<<"emergePlayer(): Player already connected"<<std::endl;
3776 player->peer_id = peer_id;
3778 // Reset inventory to creative if in creative mode
3779 if(g_settings.getBool("creative_mode"))
3781 setCreativeInventory(player);
3788 If player with the wanted peer_id already exists, cancel.
3790 if(m_env.getPlayer(peer_id) != NULL)
3792 dstream<<"emergePlayer(): Player with wrong name but same"
3793 " peer_id already exists"<<std::endl;
3801 player = new ServerRemotePlayer();
3802 //player->peer_id = c.peer_id;
3803 //player->peer_id = PEER_ID_INEXISTENT;
3804 player->peer_id = peer_id;
3805 player->updateName(name);
3811 dstream<<"Server: Finding spawn place for player \""
3812 <<player->getName()<<"\""<<std::endl;
3816 player->setPosition(intToFloat(v3s16(
3823 s16 groundheight = 0;
3825 // Try to find a good place a few times
3826 for(s32 i=0; i<1000; i++)
3829 // We're going to try to throw the player to this position
3830 nodepos = v2s16(-range + (myrand()%(range*2)),
3831 -range + (myrand()%(range*2)));
3832 v2s16 sectorpos = getNodeSectorPos(nodepos);
3833 // Get sector (NOTE: Don't get because it's slow)
3834 //m_env.getMap().emergeSector(sectorpos);
3835 // Get ground height at point (fallbacks to heightmap function)
3836 groundheight = m_env.getServerMap().findGroundLevel(nodepos);
3837 // Don't go underwater
3838 if(groundheight < WATER_LEVEL)
3840 //dstream<<"-> Underwater"<<std::endl;
3843 // Don't go to high places
3844 if(groundheight > WATER_LEVEL + 4)
3846 //dstream<<"-> Underwater"<<std::endl;
3850 // Found a good place
3851 dstream<<"Searched through "<<i<<" places."<<std::endl;
3856 // If no suitable place was not found, go above water at least.
3857 if(groundheight < WATER_LEVEL)
3858 groundheight = WATER_LEVEL;
3860 player->setPosition(intToFloat(v3s16(
3862 groundheight + 5, // Accomodate mud
3868 Add player to environment
3871 m_env.addPlayer(player);
3874 Add stuff to inventory
3877 if(g_settings.getBool("creative_mode"))
3879 setCreativeInventory(player);
3884 InventoryItem *item = new ToolItem("WPick", 32000);
3885 void* r = player->inventory.addItem("main", item);
3889 InventoryItem *item = new MaterialItem(CONTENT_MESE, 6);
3890 void* r = player->inventory.addItem("main", item);
3894 InventoryItem *item = new MaterialItem(CONTENT_COALSTONE, 6);
3895 void* r = player->inventory.addItem("main", item);
3899 InventoryItem *item = new MaterialItem(CONTENT_WOOD, 6);
3900 void* r = player->inventory.addItem("main", item);
3904 InventoryItem *item = new CraftItem("Stick", 4);
3905 void* r = player->inventory.addItem("main", item);
3909 InventoryItem *item = new ToolItem("WPick", 32000);
3910 void* r = player->inventory.addItem("main", item);
3914 InventoryItem *item = new ToolItem("STPick", 32000);
3915 void* r = player->inventory.addItem("main", item);
3918 /*// Give some lights
3920 InventoryItem *item = new MaterialItem(CONTENT_TORCH, 999);
3921 bool r = player->inventory.addItem("main", item);
3925 for(u16 i=0; i<4; i++)
3927 InventoryItem *item = new MapBlockObjectItem("Sign Example text");
3928 bool r = player->inventory.addItem("main", item);
3931 /*// Give some other stuff
3933 InventoryItem *item = new MaterialItem(CONTENT_TREE, 999);
3934 bool r = player->inventory.addItem("main", item);
3941 } // create new player
3944 void Server::handlePeerChange(PeerChange &c)
3946 JMutexAutoLock envlock(m_env_mutex);
3947 JMutexAutoLock conlock(m_con_mutex);
3949 if(c.type == PEER_ADDED)
3956 core::map<u16, RemoteClient*>::Node *n;
3957 n = m_clients.find(c.peer_id);
3958 // The client shouldn't already exist
3962 RemoteClient *client = new RemoteClient();
3963 client->peer_id = c.peer_id;
3964 m_clients.insert(client->peer_id, client);
3967 else if(c.type == PEER_REMOVED)
3974 core::map<u16, RemoteClient*>::Node *n;
3975 n = m_clients.find(c.peer_id);
3976 // The client should exist
3979 // Collect information about leaving in chat
3980 std::wstring message;
3982 std::wstring name = L"unknown";
3983 Player *player = m_env.getPlayer(c.peer_id);
3985 name = narrow_to_wide(player->getName());
3989 message += L" left game";
3991 message += L" (timed out)";
3996 m_env.removePlayer(c.peer_id);
3999 // Set player client disconnected
4001 Player *player = m_env.getPlayer(c.peer_id);
4003 player->peer_id = 0;
4007 delete m_clients[c.peer_id];
4008 m_clients.remove(c.peer_id);
4010 // Send player info to all remaining clients
4013 // Send leave chat message to all remaining clients
4014 BroadcastChatMessage(message);
4023 void Server::handlePeerChanges()
4025 while(m_peer_change_queue.size() > 0)
4027 PeerChange c = m_peer_change_queue.pop_front();
4029 dout_server<<"Server: Handling peer change: "
4030 <<"id="<<c.peer_id<<", timeout="<<c.timeout
4033 handlePeerChange(c);
4037 void dedicated_server_loop(Server &server, bool &kill)
4039 DSTACK(__FUNCTION_NAME);
4041 std::cout<<DTIME<<std::endl;
4042 std::cout<<"========================"<<std::endl;
4043 std::cout<<"Running dedicated server"<<std::endl;
4044 std::cout<<"========================"<<std::endl;
4045 std::cout<<std::endl;
4049 // This is kind of a hack but can be done like this
4050 // because server.step() is very light
4054 if(server.getShutdownRequested() || kill)
4056 std::cout<<DTIME<<" dedicated_server_loop(): Quitting."<<std::endl;
4060 static int counter = 0;
4066 core::list<PlayerInfo> list = server.getPlayerInfo();
4067 core::list<PlayerInfo>::Iterator i;
4068 static u32 sum_old = 0;
4069 u32 sum = PIChecksum(list);
4072 std::cout<<DTIME<<"Player info:"<<std::endl;
4073 for(i=list.begin(); i!=list.end(); i++)
4075 i->PrintLine(&std::cout);