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"
24 #include "jmutexautolock.h"
29 void * MeshUpdateThread::Thread()
33 DSTACK(__FUNCTION_NAME);
35 BEGIN_DEBUG_EXCEPTION_HANDLER
39 QueuedMeshUpdate *q = m_queue_in.pop();
46 scene::SMesh *mesh_new = NULL;
47 mesh_new = makeMapBlockMesh(q->data);
52 r.ack_block_to_server = q->ack_block_to_server;
54 /*dstream<<"MeshUpdateThread: Processed "
55 <<"("<<q->p.X<<","<<q->p.Y<<","<<q->p.Z<<")"
58 m_queue_out.push_back(r);
63 END_DEBUG_EXCEPTION_HANDLER
69 IrrlichtDevice *device,
70 const char *playername,
71 MapDrawControl &control):
72 m_mesh_update_thread(),
74 new ClientMap(this, control,
75 device->getSceneManager()->getRootSceneNode(),
76 device->getSceneManager(), 666),
77 device->getSceneManager()
79 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
81 camera_position(0,0,0),
82 camera_direction(0,0,1),
83 m_server_ser_ver(SER_FMT_VER_INVALID),
84 m_inventory_updated(false),
88 m_packetcounter_timer = 0.0;
89 m_delete_unused_sectors_timer = 0.0;
90 m_connection_reinit_timer = 0.0;
91 m_avg_rtt_timer = 0.0;
92 m_playerpos_send_timer = 0.0;
93 m_ignore_damage_timer = 0.0;
98 m_mesh_update_thread.Start();
104 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
106 Player *player = new LocalPlayer();
108 player->updateName(playername);
110 m_env.addPlayer(player);
112 // Initialize player in the inventory context
113 m_inventory_context.current_player = player;
120 //JMutexAutoLock conlock(m_con_mutex); //bulk comment-out
124 m_mesh_update_thread.setRun(false);
125 while(m_mesh_update_thread.IsRunning())
129 void Client::connect(Address address)
131 DSTACK(__FUNCTION_NAME);
132 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
133 m_con.setTimeoutMs(0);
134 m_con.Connect(address);
137 bool Client::connectedAndInitialized()
139 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
141 if(m_con.Connected() == false)
144 if(m_server_ser_ver == SER_FMT_VER_INVALID)
150 void Client::step(float dtime)
152 DSTACK(__FUNCTION_NAME);
158 if(m_ignore_damage_timer > dtime)
159 m_ignore_damage_timer -= dtime;
161 m_ignore_damage_timer = 0.0;
163 //dstream<<"Client steps "<<dtime<<std::endl;
166 //TimeTaker timer("ReceiveAll()", m_device);
172 //TimeTaker timer("m_con_mutex + m_con.RunTimeouts()", m_device);
174 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
175 m_con.RunTimeouts(dtime);
182 float &counter = m_packetcounter_timer;
188 dout_client<<"Client packetcounter (20s):"<<std::endl;
189 m_packetcounter.print(dout_client);
190 m_packetcounter.clear();
196 Delete unused sectors
198 NOTE: This jams the game for a while because deleting sectors
202 float &counter = m_delete_unused_sectors_timer;
210 //JMutexAutoLock lock(m_env_mutex); //bulk comment-out
212 core::list<v3s16> deleted_blocks;
214 float delete_unused_sectors_timeout =
215 g_settings.getFloat("client_delete_unused_sectors_timeout");
217 // Delete sector blocks
218 /*u32 num = m_env.getMap().deleteUnusedSectors
219 (delete_unused_sectors_timeout,
220 true, &deleted_blocks);*/
222 // Delete whole sectors
223 u32 num = m_env.getMap().deleteUnusedSectors
224 (delete_unused_sectors_timeout,
225 false, &deleted_blocks);
229 /*dstream<<DTIME<<"Client: Deleted blocks of "<<num
230 <<" unused sectors"<<std::endl;*/
231 dstream<<DTIME<<"Client: Deleted "<<num
232 <<" unused sectors"<<std::endl;
238 // Env is locked so con can be locked.
239 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
241 core::list<v3s16>::Iterator i = deleted_blocks.begin();
242 core::list<v3s16> sendlist;
245 if(sendlist.size() == 255 || i == deleted_blocks.end())
247 if(sendlist.size() == 0)
256 u32 replysize = 2+1+6*sendlist.size();
257 SharedBuffer<u8> reply(replysize);
258 writeU16(&reply[0], TOSERVER_DELETEDBLOCKS);
259 reply[2] = sendlist.size();
261 for(core::list<v3s16>::Iterator
262 j = sendlist.begin();
263 j != sendlist.end(); j++)
265 writeV3S16(&reply[2+1+6*k], *j);
268 m_con.Send(PEER_ID_SERVER, 1, reply, true);
270 if(i == deleted_blocks.end())
276 sendlist.push_back(*i);
283 bool connected = connectedAndInitialized();
285 if(connected == false)
287 float &counter = m_connection_reinit_timer;
293 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
295 Player *myplayer = m_env.getLocalPlayer();
296 assert(myplayer != NULL);
298 // Send TOSERVER_INIT
299 // [0] u16 TOSERVER_INIT
300 // [2] u8 SER_FMT_VER_HIGHEST
301 // [3] u8[20] player_name
302 SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE);
303 writeU16(&data[0], TOSERVER_INIT);
304 writeU8(&data[2], SER_FMT_VER_HIGHEST);
305 memset((char*)&data[3], 0, PLAYERNAME_SIZE);
306 snprintf((char*)&data[3], PLAYERNAME_SIZE, "%s", myplayer->getName());
307 // Send as unreliable
308 Send(0, data, false);
311 // Not connected, return
316 Do stuff if connected
324 //JMutexAutoLock lock(m_env_mutex); //bulk comment-out
326 // Control local player (0ms)
327 LocalPlayer *player = m_env.getLocalPlayer();
328 assert(player != NULL);
329 player->applyControl(dtime);
331 //TimeTaker envtimer("env step", m_device);
335 // Step active blocks
336 for(core::map<v3s16, bool>::Iterator
337 i = m_active_blocks.getIterator();
338 i.atEnd() == false; i++)
340 v3s16 p = i.getNode()->getKey();
342 MapBlock *block = NULL;
345 block = m_env.getMap().getBlockNoCreate(p);
346 block->stepObjects(dtime, false, m_env.getDayNightRatio());
348 catch(InvalidPositionException &e)
358 ClientEnvEvent event = m_env.getClientEvent();
359 if(event.type == CEE_NONE)
363 else if(event.type == CEE_PLAYER_DAMAGE)
365 if(m_ignore_damage_timer <= 0)
367 u8 damage = event.player_damage.amount;
370 // Add to ClientEvent queue
372 event.type = CE_PLAYER_DAMAGE;
373 event.player_damage.amount = damage;
374 m_client_event_queue.push_back(event);
384 float &counter = m_avg_rtt_timer;
389 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
390 // connectedAndInitialized() is true, peer exists.
391 con::Peer *peer = m_con.GetPeer(PEER_ID_SERVER);
392 dstream<<DTIME<<"Client: avg_rtt="<<peer->avg_rtt<<std::endl;
397 Send player position to server
400 float &counter = m_playerpos_send_timer;
410 Replace updated meshes
413 //JMutexAutoLock lock(m_env_mutex); //bulk comment-out
415 //TimeTaker timer("** Processing mesh update result queue");
418 /*dstream<<"Mesh update result queue size is "
419 <<m_mesh_update_thread.m_queue_out.size()
422 while(m_mesh_update_thread.m_queue_out.size() > 0)
424 MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_front();
425 MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
428 block->replaceMesh(r.mesh);
430 if(r.ack_block_to_server)
432 /*dstream<<"Client: ACK block ("<<r.p.X<<","<<r.p.Y
433 <<","<<r.p.Z<<")"<<std::endl;*/
444 u32 replysize = 2+1+6;
445 SharedBuffer<u8> reply(replysize);
446 writeU16(&reply[0], TOSERVER_GOTBLOCKS);
448 writeV3S16(&reply[3], r.p);
450 m_con.Send(PEER_ID_SERVER, 1, reply, true);
456 // Virtual methods from con::PeerHandler
457 void Client::peerAdded(con::Peer *peer)
459 derr_client<<"Client::peerAdded(): peer->id="
460 <<peer->id<<std::endl;
462 void Client::deletingPeer(con::Peer *peer, bool timeout)
464 derr_client<<"Client::deletingPeer(): "
465 "Server Peer is getting deleted "
466 <<"(timeout="<<timeout<<")"<<std::endl;
469 void Client::ReceiveAll()
471 DSTACK(__FUNCTION_NAME);
477 catch(con::NoIncomingDataException &e)
481 catch(con::InvalidIncomingDataException &e)
483 dout_client<<DTIME<<"Client::ReceiveAll(): "
484 "InvalidIncomingDataException: what()="
485 <<e.what()<<std::endl;
490 void Client::Receive()
492 DSTACK(__FUNCTION_NAME);
493 u32 data_maxsize = 200000;
494 Buffer<u8> data(data_maxsize);
498 //TimeTaker t1("con mutex and receive", m_device);
499 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
500 datasize = m_con.Receive(sender_peer_id, *data, data_maxsize);
502 //TimeTaker t1("ProcessData", m_device);
503 ProcessData(*data, datasize, sender_peer_id);
507 sender_peer_id given to this shall be quaranteed to be a valid peer
509 void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
511 DSTACK(__FUNCTION_NAME);
513 // Ignore packets that don't even fit a command
516 m_packetcounter.add(60000);
520 ToClientCommand command = (ToClientCommand)readU16(&data[0]);
522 //dstream<<"Client: received command="<<command<<std::endl;
523 m_packetcounter.add((u16)command);
526 If this check is removed, be sure to change the queue
527 system to know the ids
529 if(sender_peer_id != PEER_ID_SERVER)
531 dout_client<<DTIME<<"Client::ProcessData(): Discarding data not "
532 "coming from server: peer_id="<<sender_peer_id
539 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
540 // All data is coming from the server
541 // PeerNotFoundException is handled by caller.
542 peer = m_con.GetPeer(PEER_ID_SERVER);
545 u8 ser_version = m_server_ser_ver;
547 //dstream<<"Client received command="<<(int)command<<std::endl;
549 if(command == TOCLIENT_INIT)
554 u8 deployed = data[2];
556 dout_client<<DTIME<<"Client: TOCLIENT_INIT received with "
557 "deployed="<<((int)deployed&0xff)<<std::endl;
559 if(deployed < SER_FMT_VER_LOWEST
560 || deployed > SER_FMT_VER_HIGHEST)
562 derr_client<<DTIME<<"Client: TOCLIENT_INIT: Server sent "
563 <<"unsupported ser_fmt_ver"<<std::endl;
567 m_server_ser_ver = deployed;
569 // Get player position
570 v3s16 playerpos_s16(0, BS*2+BS*20, 0);
571 if(datasize >= 2+1+6)
572 playerpos_s16 = readV3S16(&data[2+1]);
573 v3f playerpos_f = intToFloat(playerpos_s16, BS) - v3f(0, BS/2, 0);
576 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
578 // Set player position
579 Player *player = m_env.getLocalPlayer();
580 assert(player != NULL);
581 player->setPosition(playerpos_f);
584 if(datasize >= 2+1+6+8)
587 m_map_seed = readU64(&data[2+1+6]);
588 dstream<<"Client: received map seed: "<<m_map_seed<<std::endl;
593 SharedBuffer<u8> reply(replysize);
594 writeU16(&reply[0], TOSERVER_INIT2);
596 m_con.Send(PEER_ID_SERVER, 1, reply, true);
601 if(ser_version == SER_FMT_VER_INVALID)
603 dout_client<<DTIME<<"WARNING: Client: Server serialization"
604 " format invalid or not initialized."
605 " Skipping incoming command="<<command<<std::endl;
609 // Just here to avoid putting the two if's together when
610 // making some copypasta
613 if(command == TOCLIENT_REMOVENODE)
618 p.X = readS16(&data[2]);
619 p.Y = readS16(&data[4]);
620 p.Z = readS16(&data[6]);
622 //TimeTaker t1("TOCLIENT_REMOVENODE");
624 // This will clear the cracking animation after digging
625 ((ClientMap&)m_env.getMap()).clearTempMod(p);
629 else if(command == TOCLIENT_ADDNODE)
631 if(datasize < 8 + MapNode::serializedLength(ser_version))
635 p.X = readS16(&data[2]);
636 p.Y = readS16(&data[4]);
637 p.Z = readS16(&data[6]);
639 //TimeTaker t1("TOCLIENT_ADDNODE");
642 n.deSerialize(&data[8], ser_version);
646 else if(command == TOCLIENT_BLOCKDATA)
648 // Ignore too small packet
653 p.X = readS16(&data[2]);
654 p.Y = readS16(&data[4]);
655 p.Z = readS16(&data[6]);
657 /*dout_client<<DTIME<<"Client: Thread: BLOCKDATA for ("
658 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
659 /*dstream<<DTIME<<"Client: Thread: BLOCKDATA for ("
660 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
662 std::string datastring((char*)&data[8], datasize-8);
663 std::istringstream istr(datastring, std::ios_base::binary);
669 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
672 sector = m_env.getMap().emergeSector(p2d);
674 v2s16 sp = sector->getPos();
677 dstream<<"ERROR: Got sector with getPos()="
678 <<"("<<sp.X<<","<<sp.Y<<"), tried to get"
679 <<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
683 //assert(sector->getPos() == p2d);
685 //TimeTaker timer("MapBlock deSerialize");
689 block = sector->getBlockNoCreate(p.Y);
691 Update an existing block
693 //dstream<<"Updating"<<std::endl;
694 block->deSerialize(istr, ser_version);
695 //block->setChangedFlag();
697 catch(InvalidPositionException &e)
702 //dstream<<"Creating new"<<std::endl;
703 block = new MapBlock(&m_env.getMap(), p);
704 block->deSerialize(istr, ser_version);
705 sector->insertBlock(block);
706 //block->setChangedFlag();
710 mod.type = NODEMOD_CHANGECONTENT;
711 mod.param = CONTENT_MESE;
712 block->setTempMod(v3s16(8,10,8), mod);
713 block->setTempMod(v3s16(8,9,8), mod);
714 block->setTempMod(v3s16(8,8,8), mod);
715 block->setTempMod(v3s16(8,7,8), mod);
716 block->setTempMod(v3s16(8,6,8), mod);*/
720 Well, this is a dumb way to do it, they should just
721 be drawn as separate objects. But the looks of them
722 can be tested this way.
727 mod.type = NODEMOD_CHANGECONTENT;
728 mod.param = CONTENT_CLOUD;
731 for(p2.X=3; p2.X<=13; p2.X++)
732 for(p2.Z=3; p2.Z<=13; p2.Z++)
734 block->setTempMod(p2, mod);
752 u32 replysize = 2+1+6;
753 SharedBuffer<u8> reply(replysize);
754 writeU16(&reply[0], TOSERVER_GOTBLOCKS);
756 writeV3S16(&reply[3], p);
758 m_con.Send(PEER_ID_SERVER, 1, reply, true);
762 Update Mesh of this block and blocks at x-, y- and z-.
763 Environment should not be locked as it interlocks with the
764 main thread, from which is will want to retrieve textures.
767 //m_env.getClientMap().updateMeshes(block->getPos(), getDayNightRatio());
769 addUpdateMeshTaskWithEdge(p, true);
771 else if(command == TOCLIENT_PLAYERPOS)
773 dstream<<"WARNING: Received deprecated TOCLIENT_PLAYERPOS"
777 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
778 our_peer_id = m_con.GetPeerID();
780 // Cancel if we don't have a peer id
781 if(our_peer_id == PEER_ID_INEXISTENT){
782 dout_client<<DTIME<<"TOCLIENT_PLAYERPOS cancelled: "
789 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
791 u32 player_size = 2+12+12+4+4;
793 u32 player_count = (datasize-2) / player_size;
795 for(u32 i=0; i<player_count; i++)
797 u16 peer_id = readU16(&data[start]);
799 Player *player = m_env.getPlayer(peer_id);
801 // Skip if player doesn't exist
804 start += player_size;
808 // Skip if player is local player
809 if(player->isLocal())
811 start += player_size;
815 v3s32 ps = readV3S32(&data[start+2]);
816 v3s32 ss = readV3S32(&data[start+2+12]);
817 s32 pitch_i = readS32(&data[start+2+12+12]);
818 s32 yaw_i = readS32(&data[start+2+12+12+4]);
819 /*dstream<<"Client: got "
820 <<"pitch_i="<<pitch_i
821 <<" yaw_i="<<yaw_i<<std::endl;*/
822 f32 pitch = (f32)pitch_i / 100.0;
823 f32 yaw = (f32)yaw_i / 100.0;
824 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
825 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
826 player->setPosition(position);
827 player->setSpeed(speed);
828 player->setPitch(pitch);
831 /*dstream<<"Client: player "<<peer_id
833 <<" yaw="<<yaw<<std::endl;*/
835 start += player_size;
839 else if(command == TOCLIENT_PLAYERINFO)
843 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
844 our_peer_id = m_con.GetPeerID();
846 // Cancel if we don't have a peer id
847 if(our_peer_id == PEER_ID_INEXISTENT){
848 dout_client<<DTIME<<"TOCLIENT_PLAYERINFO cancelled: "
854 //dstream<<DTIME<<"Client: Server reports players:"<<std::endl;
857 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
859 u32 item_size = 2+PLAYERNAME_SIZE;
860 u32 player_count = (datasize-2) / item_size;
863 core::list<u16> players_alive;
864 for(u32 i=0; i<player_count; i++)
866 // Make sure the name ends in '\0'
867 data[start+2+20-1] = 0;
869 u16 peer_id = readU16(&data[start]);
871 players_alive.push_back(peer_id);
873 /*dstream<<DTIME<<"peer_id="<<peer_id
874 <<" name="<<((char*)&data[start+2])<<std::endl;*/
876 // Don't update the info of the local player
877 if(peer_id == our_peer_id)
883 Player *player = m_env.getPlayer(peer_id);
885 // Create a player if it doesn't exist
888 player = new RemotePlayer(
889 m_device->getSceneManager()->getRootSceneNode(),
892 player->peer_id = peer_id;
893 m_env.addPlayer(player);
894 dout_client<<DTIME<<"Client: Adding new player "
895 <<peer_id<<std::endl;
898 player->updateName((char*)&data[start+2]);
904 Remove those players from the environment that
905 weren't listed by the server.
907 //dstream<<DTIME<<"Removing dead players"<<std::endl;
908 core::list<Player*> players = m_env.getPlayers();
909 core::list<Player*>::Iterator ip;
910 for(ip=players.begin(); ip!=players.end(); ip++)
912 // Ingore local player
916 // Warn about a special case
917 if((*ip)->peer_id == 0)
919 dstream<<DTIME<<"WARNING: Client: Removing "
920 "dead player with id=0"<<std::endl;
923 bool is_alive = false;
924 core::list<u16>::Iterator i;
925 for(i=players_alive.begin(); i!=players_alive.end(); i++)
927 if((*ip)->peer_id == *i)
933 /*dstream<<DTIME<<"peer_id="<<((*ip)->peer_id)
934 <<" is_alive="<<is_alive<<std::endl;*/
937 dstream<<DTIME<<"Removing dead player "<<(*ip)->peer_id
939 m_env.removePlayer((*ip)->peer_id);
943 else if(command == TOCLIENT_SECTORMETA)
948 [3...] v2s16 pos + sector metadata
953 //dstream<<"Client received TOCLIENT_SECTORMETA"<<std::endl;
956 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
958 std::string datastring((char*)&data[2], datasize-2);
959 std::istringstream is(datastring, std::ios_base::binary);
963 is.read((char*)buf, 1);
964 u16 sector_count = readU8(buf);
966 //dstream<<"sector_count="<<sector_count<<std::endl;
968 for(u16 i=0; i<sector_count; i++)
971 is.read((char*)buf, 4);
972 v2s16 pos = readV2S16(buf);
973 /*dstream<<"Client: deserializing sector at "
974 <<"("<<pos.X<<","<<pos.Y<<")"<<std::endl;*/
976 assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
977 ((ClientMap&)m_env.getMap()).deSerializeSector(pos, is);
981 else if(command == TOCLIENT_INVENTORY)
986 //TimeTaker t1("Parsing TOCLIENT_INVENTORY", m_device);
989 //TimeTaker t2("mutex locking", m_device);
990 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
993 //TimeTaker t3("istringstream init", m_device);
994 std::string datastring((char*)&data[2], datasize-2);
995 std::istringstream is(datastring, std::ios_base::binary);
998 //m_env.printPlayers(dstream);
1000 //TimeTaker t4("player get", m_device);
1001 Player *player = m_env.getLocalPlayer();
1002 assert(player != NULL);
1005 //TimeTaker t1("inventory.deSerialize()", m_device);
1006 player->inventory.deSerialize(is);
1009 m_inventory_updated = true;
1011 //dstream<<"Client got player inventory:"<<std::endl;
1012 //player->inventory.print(dstream);
1016 else if(command == TOCLIENT_OBJECTDATA)
1019 // Strip command word and create a stringstream
1020 std::string datastring((char*)&data[2], datasize-2);
1021 std::istringstream is(datastring, std::ios_base::binary);
1025 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1033 is.read((char*)buf, 2);
1034 u16 playercount = readU16(buf);
1036 for(u16 i=0; i<playercount; i++)
1038 is.read((char*)buf, 2);
1039 u16 peer_id = readU16(buf);
1040 is.read((char*)buf, 12);
1041 v3s32 p_i = readV3S32(buf);
1042 is.read((char*)buf, 12);
1043 v3s32 s_i = readV3S32(buf);
1044 is.read((char*)buf, 4);
1045 s32 pitch_i = readS32(buf);
1046 is.read((char*)buf, 4);
1047 s32 yaw_i = readS32(buf);
1049 Player *player = m_env.getPlayer(peer_id);
1051 // Skip if player doesn't exist
1057 // Skip if player is local player
1058 if(player->isLocal())
1063 f32 pitch = (f32)pitch_i / 100.0;
1064 f32 yaw = (f32)yaw_i / 100.0;
1065 v3f position((f32)p_i.X/100., (f32)p_i.Y/100., (f32)p_i.Z/100.);
1066 v3f speed((f32)s_i.X/100., (f32)s_i.Y/100., (f32)s_i.Z/100.);
1068 player->setPosition(position);
1069 player->setSpeed(speed);
1070 player->setPitch(pitch);
1071 player->setYaw(yaw);
1078 // Read active block count
1079 is.read((char*)buf, 2);
1080 u16 blockcount = readU16(buf);
1082 // Initialize delete queue with all active blocks
1083 core::map<v3s16, bool> abs_to_delete;
1084 for(core::map<v3s16, bool>::Iterator
1085 i = m_active_blocks.getIterator();
1086 i.atEnd() == false; i++)
1088 v3s16 p = i.getNode()->getKey();
1089 /*dstream<<"adding "
1090 <<"("<<p.x<<","<<p.y<<","<<p.z<<") "
1091 <<" to abs_to_delete"
1093 abs_to_delete.insert(p, true);
1096 /*dstream<<"Initial delete queue size: "<<abs_to_delete.size()
1099 for(u16 i=0; i<blockcount; i++)
1102 is.read((char*)buf, 6);
1103 v3s16 p = readV3S16(buf);
1104 // Get block from somewhere
1105 MapBlock *block = NULL;
1107 block = m_env.getMap().getBlockNoCreate(p);
1109 catch(InvalidPositionException &e)
1111 //TODO: Create a dummy block?
1115 dstream<<"WARNING: "
1116 <<"Could not get block at blockpos "
1117 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
1118 <<"in TOCLIENT_OBJECTDATA. Ignoring "
1119 <<"following block object data."
1124 /*dstream<<"Client updating objects for block "
1125 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1128 // Insert to active block list
1129 m_active_blocks.insert(p, true);
1131 // Remove from deletion queue
1132 if(abs_to_delete.find(p) != NULL)
1133 abs_to_delete.remove(p);
1136 Update objects of block
1138 NOTE: Be sure this is done in the main thread.
1140 block->updateObjects(is, m_server_ser_ver,
1141 m_device->getSceneManager(), m_env.getDayNightRatio());
1144 /*dstream<<"Final delete queue size: "<<abs_to_delete.size()
1147 // Delete objects of blocks in delete queue
1148 for(core::map<v3s16, bool>::Iterator
1149 i = abs_to_delete.getIterator();
1150 i.atEnd() == false; i++)
1152 v3s16 p = i.getNode()->getKey();
1155 MapBlock *block = m_env.getMap().getBlockNoCreate(p);
1158 block->clearObjects();
1159 // Remove from active blocks list
1160 m_active_blocks.remove(p);
1162 catch(InvalidPositionException &e)
1164 dstream<<"WARNAING: Client: "
1165 <<"Couldn't clear objects of active->inactive"
1167 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1168 <<" because block was not found"
1176 else if(command == TOCLIENT_TIME_OF_DAY)
1181 u16 time = readU16(&data[2]);
1182 time = time % 24000;
1183 m_time_of_day = time;
1184 //dstream<<"Client: time="<<time<<std::endl;
1194 u32 dr = time_to_daynight_ratio(m_time_of_day);
1196 dstream<<"Client: time_of_day="<<m_time_of_day
1200 if(dr != m_env.getDayNightRatio())
1202 dout_client<<DTIME<<"Client: changing day-night ratio"<<std::endl;
1203 m_env.setDayNightRatio(dr);
1204 m_env.expireMeshes(true);
1209 else if(command == TOCLIENT_CHAT_MESSAGE)
1217 std::string datastring((char*)&data[2], datasize-2);
1218 std::istringstream is(datastring, std::ios_base::binary);
1221 is.read((char*)buf, 2);
1222 u16 len = readU16(buf);
1224 std::wstring message;
1225 for(u16 i=0; i<len; i++)
1227 is.read((char*)buf, 2);
1228 message += (wchar_t)readU16(buf);
1231 /*dstream<<"Client received chat message: "
1232 <<wide_to_narrow(message)<<std::endl;*/
1234 m_chat_queue.push_back(message);
1236 else if(command == TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD)
1238 //if(g_settings.getBool("enable_experimental"))
1242 u16 count of removed objects
1243 for all removed objects {
1246 u16 count of added objects
1247 for all added objects {
1250 u16 initialization data length
1251 string initialization data
1256 // Get all data except the command number
1257 std::string datastring((char*)&data[2], datasize-2);
1258 // Throw them in an istringstream
1259 std::istringstream is(datastring, std::ios_base::binary);
1263 // Read removed objects
1265 u16 removed_count = readU16((u8*)buf);
1266 for(u16 i=0; i<removed_count; i++)
1269 u16 id = readU16((u8*)buf);
1272 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1273 m_env.removeActiveObject(id);
1277 // Read added objects
1279 u16 added_count = readU16((u8*)buf);
1280 for(u16 i=0; i<added_count; i++)
1283 u16 id = readU16((u8*)buf);
1285 u8 type = readU8((u8*)buf);
1286 std::string data = deSerializeLongString(is);
1289 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1290 m_env.addActiveObject(id, type, data);
1295 else if(command == TOCLIENT_ACTIVE_OBJECT_MESSAGES)
1297 //if(g_settings.getBool("enable_experimental"))
1309 // Get all data except the command number
1310 std::string datastring((char*)&data[2], datasize-2);
1311 // Throw them in an istringstream
1312 std::istringstream is(datastring, std::ios_base::binary);
1314 while(is.eof() == false)
1318 u16 id = readU16((u8*)buf);
1322 u16 message_size = readU16((u8*)buf);
1323 std::string message;
1324 message.reserve(message_size);
1325 for(u16 i=0; i<message_size; i++)
1328 message.append(buf, 1);
1330 // Pass on to the environment
1332 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1333 m_env.processActiveObjectMessage(id, message);
1338 else if(command == TOCLIENT_HP)
1340 std::string datastring((char*)&data[2], datasize-2);
1341 std::istringstream is(datastring, std::ios_base::binary);
1342 Player *player = m_env.getLocalPlayer();
1343 assert(player != NULL);
1347 else if(command == TOCLIENT_MOVE_PLAYER)
1349 std::string datastring((char*)&data[2], datasize-2);
1350 std::istringstream is(datastring, std::ios_base::binary);
1351 Player *player = m_env.getLocalPlayer();
1352 assert(player != NULL);
1353 v3f pos = readV3F1000(is);
1354 f32 pitch = readF1000(is);
1355 f32 yaw = readF1000(is);
1356 player->setPosition(pos);
1357 /*player->setPitch(pitch);
1358 player->setYaw(yaw);*/
1360 dstream<<"Client got TOCLIENT_MOVE_PLAYER"
1361 <<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
1367 Add to ClientEvent queue.
1368 This has to be sent to the main program because otherwise
1369 it would just force the pitch and yaw values to whatever
1370 the camera points to.
1373 event.type = CE_PLAYER_FORCE_MOVE;
1374 event.player_force_move.pitch = pitch;
1375 event.player_force_move.yaw = yaw;
1376 m_client_event_queue.push_back(event);
1378 // Ignore damage for a few seconds, so that the player doesn't
1379 // get damage from falling on ground
1380 m_ignore_damage_timer = 3.0;
1384 dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
1385 <<command<<std::endl;
1389 void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
1391 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
1392 m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
1395 void Client::groundAction(u8 action, v3s16 nodepos_undersurface,
1396 v3s16 nodepos_oversurface, u16 item)
1398 if(connectedAndInitialized() == false){
1399 dout_client<<DTIME<<"Client::groundAction() "
1400 "cancelled (not connected)"
1409 [3] v3s16 nodepos_undersurface
1410 [9] v3s16 nodepos_abovesurface
1415 2: stop digging (all parameters ignored)
1416 3: digging completed
1418 u8 datasize = 2 + 1 + 6 + 6 + 2;
1419 SharedBuffer<u8> data(datasize);
1420 writeU16(&data[0], TOSERVER_GROUND_ACTION);
1421 writeU8(&data[2], action);
1422 writeV3S16(&data[3], nodepos_undersurface);
1423 writeV3S16(&data[9], nodepos_oversurface);
1424 writeU16(&data[15], item);
1425 Send(0, data, true);
1428 void Client::clickObject(u8 button, v3s16 blockpos, s16 id, u16 item)
1430 if(connectedAndInitialized() == false){
1431 dout_client<<DTIME<<"Client::clickObject() "
1432 "cancelled (not connected)"
1438 [0] u16 command=TOSERVER_CLICK_OBJECT
1439 [2] u8 button (0=left, 1=right)
1444 u8 datasize = 2 + 1 + 6 + 2 + 2;
1445 SharedBuffer<u8> data(datasize);
1446 writeU16(&data[0], TOSERVER_CLICK_OBJECT);
1447 writeU8(&data[2], button);
1448 writeV3S16(&data[3], blockpos);
1449 writeS16(&data[9], id);
1450 writeU16(&data[11], item);
1451 Send(0, data, true);
1454 void Client::clickActiveObject(u8 button, u16 id, u16 item)
1456 if(connectedAndInitialized() == false){
1457 dout_client<<DTIME<<"Client::clickActiveObject() "
1458 "cancelled (not connected)"
1466 [2] u8 button (0=left, 1=right)
1470 u8 datasize = 2 + 1 + 6 + 2 + 2;
1471 SharedBuffer<u8> data(datasize);
1472 writeU16(&data[0], TOSERVER_CLICK_ACTIVEOBJECT);
1473 writeU8(&data[2], button);
1474 writeU16(&data[3], id);
1475 writeU16(&data[5], item);
1476 Send(0, data, true);
1479 void Client::sendSignText(v3s16 blockpos, s16 id, std::string text)
1488 std::ostringstream os(std::ios_base::binary);
1492 writeU16(buf, TOSERVER_SIGNTEXT);
1493 os.write((char*)buf, 2);
1496 writeV3S16(buf, blockpos);
1497 os.write((char*)buf, 6);
1501 os.write((char*)buf, 2);
1503 u16 textlen = text.size();
1504 // Write text length
1505 writeS16(buf, textlen);
1506 os.write((char*)buf, 2);
1509 os.write((char*)text.c_str(), textlen);
1512 std::string s = os.str();
1513 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1515 Send(0, data, true);
1518 void Client::sendSignNodeText(v3s16 p, std::string text)
1526 std::ostringstream os(std::ios_base::binary);
1530 writeU16(buf, TOSERVER_SIGNNODETEXT);
1531 os.write((char*)buf, 2);
1535 os.write((char*)buf, 6);
1537 u16 textlen = text.size();
1538 // Write text length
1539 writeS16(buf, textlen);
1540 os.write((char*)buf, 2);
1543 os.write((char*)text.c_str(), textlen);
1546 std::string s = os.str();
1547 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1549 Send(0, data, true);
1552 void Client::sendInventoryAction(InventoryAction *a)
1554 std::ostringstream os(std::ios_base::binary);
1558 writeU16(buf, TOSERVER_INVENTORY_ACTION);
1559 os.write((char*)buf, 2);
1564 std::string s = os.str();
1565 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1567 Send(0, data, true);
1570 void Client::sendChatMessage(const std::wstring &message)
1572 std::ostringstream os(std::ios_base::binary);
1576 writeU16(buf, TOSERVER_CHAT_MESSAGE);
1577 os.write((char*)buf, 2);
1580 writeU16(buf, message.size());
1581 os.write((char*)buf, 2);
1584 for(u32 i=0; i<message.size(); i++)
1588 os.write((char*)buf, 2);
1592 std::string s = os.str();
1593 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1595 Send(0, data, true);
1598 void Client::sendDamage(u8 damage)
1600 DSTACK(__FUNCTION_NAME);
1601 std::ostringstream os(std::ios_base::binary);
1603 writeU16(os, TOSERVER_DAMAGE);
1604 writeU8(os, damage);
1607 std::string s = os.str();
1608 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1610 Send(0, data, true);
1613 void Client::sendPlayerPos()
1615 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1617 Player *myplayer = m_env.getLocalPlayer();
1618 if(myplayer == NULL)
1623 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
1624 our_peer_id = m_con.GetPeerID();
1627 // Set peer id if not set already
1628 if(myplayer->peer_id == PEER_ID_INEXISTENT)
1629 myplayer->peer_id = our_peer_id;
1630 // Check that an existing peer_id is the same as the connection's
1631 assert(myplayer->peer_id == our_peer_id);
1633 v3f pf = myplayer->getPosition();
1634 v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
1635 v3f sf = myplayer->getSpeed();
1636 v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
1637 s32 pitch = myplayer->getPitch() * 100;
1638 s32 yaw = myplayer->getYaw() * 100;
1643 [2] v3s32 position*100
1644 [2+12] v3s32 speed*100
1645 [2+12+12] s32 pitch*100
1646 [2+12+12+4] s32 yaw*100
1649 SharedBuffer<u8> data(2+12+12+4+4);
1650 writeU16(&data[0], TOSERVER_PLAYERPOS);
1651 writeV3S32(&data[2], position);
1652 writeV3S32(&data[2+12], speed);
1653 writeS32(&data[2+12+12], pitch);
1654 writeS32(&data[2+12+12+4], yaw);
1656 // Send as unreliable
1657 Send(0, data, false);
1660 void Client::removeNode(v3s16 p)
1662 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1664 core::map<v3s16, MapBlock*> modified_blocks;
1668 //TimeTaker t("removeNodeAndUpdate", m_device);
1669 m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
1671 catch(InvalidPositionException &e)
1675 for(core::map<v3s16, MapBlock * >::Iterator
1676 i = modified_blocks.getIterator();
1677 i.atEnd() == false; i++)
1679 v3s16 p = i.getNode()->getKey();
1680 //m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
1681 addUpdateMeshTaskWithEdge(p);
1685 void Client::addNode(v3s16 p, MapNode n)
1687 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1689 TimeTaker timer1("Client::addNode()");
1691 core::map<v3s16, MapBlock*> modified_blocks;
1695 TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
1696 m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
1698 catch(InvalidPositionException &e)
1701 //TimeTaker timer2("Client::addNode(): updateMeshes");
1703 for(core::map<v3s16, MapBlock * >::Iterator
1704 i = modified_blocks.getIterator();
1705 i.atEnd() == false; i++)
1707 v3s16 p = i.getNode()->getKey();
1708 //m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
1709 addUpdateMeshTaskWithEdge(p);
1713 void Client::updateCamera(v3f pos, v3f dir)
1715 m_env.getClientMap().updateCamera(pos, dir);
1716 camera_position = pos;
1717 camera_direction = dir;
1720 MapNode Client::getNode(v3s16 p)
1722 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1723 return m_env.getMap().getNode(p);
1726 NodeMetadata* Client::getNodeMetadata(v3s16 p)
1728 return m_env.getMap().getNodeMetadata(p);
1731 v3f Client::getPlayerPosition()
1733 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1734 LocalPlayer *player = m_env.getLocalPlayer();
1735 assert(player != NULL);
1736 return player->getPosition();
1739 void Client::setPlayerControl(PlayerControl &control)
1741 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1742 LocalPlayer *player = m_env.getLocalPlayer();
1743 assert(player != NULL);
1744 player->control = control;
1747 // Returns true if the inventory of the local player has been
1748 // updated from the server. If it is true, it is set to false.
1749 bool Client::getLocalInventoryUpdated()
1751 // m_inventory_updated is behind envlock
1752 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1753 bool updated = m_inventory_updated;
1754 m_inventory_updated = false;
1758 // Copies the inventory of the local player to parameter
1759 void Client::getLocalInventory(Inventory &dst)
1761 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1762 Player *player = m_env.getLocalPlayer();
1763 assert(player != NULL);
1764 dst = player->inventory;
1767 InventoryContext *Client::getInventoryContext()
1769 return &m_inventory_context;
1772 Inventory* Client::getInventory(InventoryContext *c, std::string id)
1774 if(id == "current_player")
1776 assert(c->current_player);
1777 return &(c->current_player->inventory);
1781 std::string id0 = fn.next(":");
1783 if(id0 == "nodemeta")
1786 p.X = stoi(fn.next(","));
1787 p.Y = stoi(fn.next(","));
1788 p.Z = stoi(fn.next(","));
1789 NodeMetadata* meta = getNodeMetadata(p);
1791 return meta->getInventory();
1792 dstream<<"nodemeta at ("<<p.X<<","<<p.Y<<","<<p.Z<<"): "
1793 <<"no metadata found"<<std::endl;
1797 dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
1800 void Client::inventoryAction(InventoryAction *a)
1802 sendInventoryAction(a);
1805 MapBlockObject * Client::getSelectedObject(
1807 v3f from_pos_f_on_map,
1808 core::line3d<f32> shootline_on_map
1811 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1813 core::array<DistanceSortedObject> objects;
1815 for(core::map<v3s16, bool>::Iterator
1816 i = m_active_blocks.getIterator();
1817 i.atEnd() == false; i++)
1819 v3s16 p = i.getNode()->getKey();
1821 MapBlock *block = NULL;
1824 block = m_env.getMap().getBlockNoCreate(p);
1826 catch(InvalidPositionException &e)
1831 // Calculate from_pos relative to block
1832 v3s16 block_pos_i_on_map = block->getPosRelative();
1833 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
1834 v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
1836 block->getObjects(from_pos_f_on_block, max_d, objects);
1837 //block->getPseudoObjects(from_pos_f_on_block, max_d, objects);
1840 //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
1843 // After this, the closest object is the first in the array.
1846 for(u32 i=0; i<objects.size(); i++)
1848 MapBlockObject *obj = objects[i].obj;
1849 MapBlock *block = obj->getBlock();
1851 // Calculate shootline relative to block
1852 v3s16 block_pos_i_on_map = block->getPosRelative();
1853 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
1854 core::line3d<f32> shootline_on_block(
1855 shootline_on_map.start - block_pos_f_on_map,
1856 shootline_on_map.end - block_pos_f_on_map
1859 if(obj->isSelected(shootline_on_block))
1861 //dstream<<"Returning selected object"<<std::endl;
1866 //dstream<<"No object selected; returning NULL."<<std::endl;
1870 ClientActiveObject * Client::getSelectedActiveObject(
1872 v3f from_pos_f_on_map,
1873 core::line3d<f32> shootline_on_map
1876 core::array<DistanceSortedActiveObject> objects;
1878 m_env.getActiveObjects(from_pos_f_on_map, max_d, objects);
1880 //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
1883 // After this, the closest object is the first in the array.
1886 for(u32 i=0; i<objects.size(); i++)
1888 ClientActiveObject *obj = objects[i].obj;
1890 core::aabbox3d<f32> *selection_box = obj->getSelectionBox();
1891 if(selection_box == NULL)
1894 v3f pos = obj->getPosition();
1896 core::aabbox3d<f32> offsetted_box(
1897 selection_box->MinEdge + pos,
1898 selection_box->MaxEdge + pos
1901 if(offsetted_box.intersectsWithLine(shootline_on_map))
1903 //dstream<<"Returning selected object"<<std::endl;
1908 //dstream<<"No object selected; returning NULL."<<std::endl;
1912 void Client::printDebugInfo(std::ostream &os)
1914 //JMutexAutoLock lock1(m_fetchblock_mutex);
1915 /*JMutexAutoLock lock2(m_incoming_queue_mutex);
1917 os<<"m_incoming_queue.getSize()="<<m_incoming_queue.getSize()
1918 //<<", m_fetchblock_history.size()="<<m_fetchblock_history.size()
1919 //<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
1923 /*s32 Client::getDayNightIndex()
1925 assert(m_daynight_i >= 0 && m_daynight_i < DAYNIGHT_CACHE_COUNT);
1926 return m_daynight_i;
1929 u32 Client::getDayNightRatio()
1931 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1932 return m_env.getDayNightRatio();
1937 Player *player = m_env.getLocalPlayer();
1938 assert(player != NULL);
1942 void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
1944 /*dstream<<"Client::addUpdateMeshTask(): "
1945 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1948 MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
1953 Create a task to update the mesh of the block
1956 MeshMakeData *data = new MeshMakeData;
1959 //TimeTaker timer("data fill");
1961 data->fill(getDayNightRatio(), b);
1965 //while(m_mesh_update_thread.m_queue_in.size() > 0) sleep_ms(10);
1967 // Add task to queue
1968 m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server);
1970 /*dstream<<"Mesh update input queue size is "
1971 <<m_mesh_update_thread.m_queue_in.size()
1975 // Temporary test: make mesh directly in here
1977 //TimeTaker timer("make mesh");
1979 scene::SMesh *mesh_new = NULL;
1980 mesh_new = makeMapBlockMesh(data);
1981 b->replaceMesh(mesh_new);
1986 b->setMeshExpired(false);
1989 void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server)
1993 dstream<<"Client::addUpdateMeshTaskWithEdge(): "
1994 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1999 v3s16 p = blockpos + v3s16(0,0,0);
2000 //MapBlock *b = m_env.getMap().getBlockNoCreate(p);
2001 addUpdateMeshTask(p, ack_to_server);
2003 catch(InvalidPositionException &e){}
2006 v3s16 p = blockpos + v3s16(-1,0,0);
2007 addUpdateMeshTask(p);
2009 catch(InvalidPositionException &e){}
2011 v3s16 p = blockpos + v3s16(0,-1,0);
2012 addUpdateMeshTask(p);
2014 catch(InvalidPositionException &e){}
2016 v3s16 p = blockpos + v3s16(0,0,-1);
2017 addUpdateMeshTask(p);
2019 catch(InvalidPositionException &e){}
2022 ClientEvent Client::getClientEvent()
2024 if(m_client_event_queue.size() == 0)
2027 event.type = CE_NONE;
2030 return m_client_event_queue.pop_front();