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 // Execute fast commands straight away
551 if(command == TOCLIENT_INIT)
556 u8 deployed = data[2];
558 dout_client<<DTIME<<"Client: TOCLIENT_INIT received with "
559 "deployed="<<((int)deployed&0xff)<<std::endl;
561 if(deployed < SER_FMT_VER_LOWEST
562 || deployed > SER_FMT_VER_HIGHEST)
564 derr_client<<DTIME<<"Client: TOCLIENT_INIT: Server sent "
565 <<"unsupported ser_fmt_ver"<<std::endl;
569 m_server_ser_ver = deployed;
571 // Get player position
572 v3s16 playerpos_s16(0, BS*2+BS*20, 0);
573 if(datasize >= 2+1+6)
574 playerpos_s16 = readV3S16(&data[2+1]);
575 v3f playerpos_f = intToFloat(playerpos_s16, BS) - v3f(0, BS/2, 0);
578 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
580 // Set player position
581 Player *player = m_env.getLocalPlayer();
582 assert(player != NULL);
583 player->setPosition(playerpos_f);
586 if(datasize >= 2+1+6+8)
589 m_map_seed = readU64(&data[2+1+6]);
590 dstream<<"Client: received map seed: "<<m_map_seed<<std::endl;
595 SharedBuffer<u8> reply(replysize);
596 writeU16(&reply[0], TOSERVER_INIT2);
598 m_con.Send(PEER_ID_SERVER, 1, reply, true);
603 if(ser_version == SER_FMT_VER_INVALID)
605 dout_client<<DTIME<<"WARNING: Client: Server serialization"
606 " format invalid or not initialized."
607 " Skipping incoming command="<<command<<std::endl;
611 // Just here to avoid putting the two if's together when
612 // making some copypasta
615 if(command == TOCLIENT_REMOVENODE)
620 p.X = readS16(&data[2]);
621 p.Y = readS16(&data[4]);
622 p.Z = readS16(&data[6]);
624 //TimeTaker t1("TOCLIENT_REMOVENODE");
626 // This will clear the cracking animation after digging
627 ((ClientMap&)m_env.getMap()).clearTempMod(p);
631 else if(command == TOCLIENT_ADDNODE)
633 if(datasize < 8 + MapNode::serializedLength(ser_version))
637 p.X = readS16(&data[2]);
638 p.Y = readS16(&data[4]);
639 p.Z = readS16(&data[6]);
641 //TimeTaker t1("TOCLIENT_ADDNODE");
644 n.deSerialize(&data[8], ser_version);
648 else if(command == TOCLIENT_BLOCKDATA)
650 // Ignore too small packet
655 p.X = readS16(&data[2]);
656 p.Y = readS16(&data[4]);
657 p.Z = readS16(&data[6]);
659 /*dout_client<<DTIME<<"Client: Thread: BLOCKDATA for ("
660 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
661 /*dstream<<DTIME<<"Client: Thread: BLOCKDATA for ("
662 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
664 std::string datastring((char*)&data[8], datasize-8);
665 std::istringstream istr(datastring, std::ios_base::binary);
671 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
674 sector = m_env.getMap().emergeSector(p2d);
676 v2s16 sp = sector->getPos();
679 dstream<<"ERROR: Got sector with getPos()="
680 <<"("<<sp.X<<","<<sp.Y<<"), tried to get"
681 <<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
685 //assert(sector->getPos() == p2d);
687 //TimeTaker timer("MapBlock deSerialize");
691 block = sector->getBlockNoCreate(p.Y);
693 Update an existing block
695 //dstream<<"Updating"<<std::endl;
696 block->deSerialize(istr, ser_version);
697 //block->setChangedFlag();
699 catch(InvalidPositionException &e)
704 //dstream<<"Creating new"<<std::endl;
705 block = new MapBlock(&m_env.getMap(), p);
706 block->deSerialize(istr, ser_version);
707 sector->insertBlock(block);
708 //block->setChangedFlag();
712 mod.type = NODEMOD_CHANGECONTENT;
713 mod.param = CONTENT_MESE;
714 block->setTempMod(v3s16(8,10,8), mod);
715 block->setTempMod(v3s16(8,9,8), mod);
716 block->setTempMod(v3s16(8,8,8), mod);
717 block->setTempMod(v3s16(8,7,8), mod);
718 block->setTempMod(v3s16(8,6,8), mod);*/
722 Well, this is a dumb way to do it, they should just
723 be drawn as separate objects. But the looks of them
724 can be tested this way.
729 mod.type = NODEMOD_CHANGECONTENT;
730 mod.param = CONTENT_CLOUD;
733 for(p2.X=3; p2.X<=13; p2.X++)
734 for(p2.Z=3; p2.Z<=13; p2.Z++)
736 block->setTempMod(p2, mod);
754 u32 replysize = 2+1+6;
755 SharedBuffer<u8> reply(replysize);
756 writeU16(&reply[0], TOSERVER_GOTBLOCKS);
758 writeV3S16(&reply[3], p);
760 m_con.Send(PEER_ID_SERVER, 1, reply, true);
764 Update Mesh of this block and blocks at x-, y- and z-.
765 Environment should not be locked as it interlocks with the
766 main thread, from which is will want to retrieve textures.
769 //m_env.getClientMap().updateMeshes(block->getPos(), getDayNightRatio());
771 addUpdateMeshTaskWithEdge(p, true);
773 else if(command == TOCLIENT_PLAYERPOS)
775 dstream<<"WARNING: Received deprecated TOCLIENT_PLAYERPOS"
779 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
780 our_peer_id = m_con.GetPeerID();
782 // Cancel if we don't have a peer id
783 if(our_peer_id == PEER_ID_INEXISTENT){
784 dout_client<<DTIME<<"TOCLIENT_PLAYERPOS cancelled: "
791 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
793 u32 player_size = 2+12+12+4+4;
795 u32 player_count = (datasize-2) / player_size;
797 for(u32 i=0; i<player_count; i++)
799 u16 peer_id = readU16(&data[start]);
801 Player *player = m_env.getPlayer(peer_id);
803 // Skip if player doesn't exist
806 start += player_size;
810 // Skip if player is local player
811 if(player->isLocal())
813 start += player_size;
817 v3s32 ps = readV3S32(&data[start+2]);
818 v3s32 ss = readV3S32(&data[start+2+12]);
819 s32 pitch_i = readS32(&data[start+2+12+12]);
820 s32 yaw_i = readS32(&data[start+2+12+12+4]);
821 /*dstream<<"Client: got "
822 <<"pitch_i="<<pitch_i
823 <<" yaw_i="<<yaw_i<<std::endl;*/
824 f32 pitch = (f32)pitch_i / 100.0;
825 f32 yaw = (f32)yaw_i / 100.0;
826 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
827 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
828 player->setPosition(position);
829 player->setSpeed(speed);
830 player->setPitch(pitch);
833 /*dstream<<"Client: player "<<peer_id
835 <<" yaw="<<yaw<<std::endl;*/
837 start += player_size;
841 else if(command == TOCLIENT_PLAYERINFO)
845 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
846 our_peer_id = m_con.GetPeerID();
848 // Cancel if we don't have a peer id
849 if(our_peer_id == PEER_ID_INEXISTENT){
850 dout_client<<DTIME<<"TOCLIENT_PLAYERINFO cancelled: "
856 //dstream<<DTIME<<"Client: Server reports players:"<<std::endl;
859 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
861 u32 item_size = 2+PLAYERNAME_SIZE;
862 u32 player_count = (datasize-2) / item_size;
865 core::list<u16> players_alive;
866 for(u32 i=0; i<player_count; i++)
868 // Make sure the name ends in '\0'
869 data[start+2+20-1] = 0;
871 u16 peer_id = readU16(&data[start]);
873 players_alive.push_back(peer_id);
875 /*dstream<<DTIME<<"peer_id="<<peer_id
876 <<" name="<<((char*)&data[start+2])<<std::endl;*/
878 // Don't update the info of the local player
879 if(peer_id == our_peer_id)
885 Player *player = m_env.getPlayer(peer_id);
887 // Create a player if it doesn't exist
890 player = new RemotePlayer(
891 m_device->getSceneManager()->getRootSceneNode(),
894 player->peer_id = peer_id;
895 m_env.addPlayer(player);
896 dout_client<<DTIME<<"Client: Adding new player "
897 <<peer_id<<std::endl;
900 player->updateName((char*)&data[start+2]);
906 Remove those players from the environment that
907 weren't listed by the server.
909 //dstream<<DTIME<<"Removing dead players"<<std::endl;
910 core::list<Player*> players = m_env.getPlayers();
911 core::list<Player*>::Iterator ip;
912 for(ip=players.begin(); ip!=players.end(); ip++)
914 // Ingore local player
918 // Warn about a special case
919 if((*ip)->peer_id == 0)
921 dstream<<DTIME<<"WARNING: Client: Removing "
922 "dead player with id=0"<<std::endl;
925 bool is_alive = false;
926 core::list<u16>::Iterator i;
927 for(i=players_alive.begin(); i!=players_alive.end(); i++)
929 if((*ip)->peer_id == *i)
935 /*dstream<<DTIME<<"peer_id="<<((*ip)->peer_id)
936 <<" is_alive="<<is_alive<<std::endl;*/
939 dstream<<DTIME<<"Removing dead player "<<(*ip)->peer_id
941 m_env.removePlayer((*ip)->peer_id);
945 else if(command == TOCLIENT_SECTORMETA)
950 [3...] v2s16 pos + sector metadata
955 //dstream<<"Client received TOCLIENT_SECTORMETA"<<std::endl;
958 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
960 std::string datastring((char*)&data[2], datasize-2);
961 std::istringstream is(datastring, std::ios_base::binary);
965 is.read((char*)buf, 1);
966 u16 sector_count = readU8(buf);
968 //dstream<<"sector_count="<<sector_count<<std::endl;
970 for(u16 i=0; i<sector_count; i++)
973 is.read((char*)buf, 4);
974 v2s16 pos = readV2S16(buf);
975 /*dstream<<"Client: deserializing sector at "
976 <<"("<<pos.X<<","<<pos.Y<<")"<<std::endl;*/
978 assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
979 ((ClientMap&)m_env.getMap()).deSerializeSector(pos, is);
983 else if(command == TOCLIENT_INVENTORY)
988 //TimeTaker t1("Parsing TOCLIENT_INVENTORY", m_device);
991 //TimeTaker t2("mutex locking", m_device);
992 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
995 //TimeTaker t3("istringstream init", m_device);
996 std::string datastring((char*)&data[2], datasize-2);
997 std::istringstream is(datastring, std::ios_base::binary);
1000 //m_env.printPlayers(dstream);
1002 //TimeTaker t4("player get", m_device);
1003 Player *player = m_env.getLocalPlayer();
1004 assert(player != NULL);
1007 //TimeTaker t1("inventory.deSerialize()", m_device);
1008 player->inventory.deSerialize(is);
1011 m_inventory_updated = true;
1013 //dstream<<"Client got player inventory:"<<std::endl;
1014 //player->inventory.print(dstream);
1018 else if(command == TOCLIENT_OBJECTDATA)
1021 // Strip command word and create a stringstream
1022 std::string datastring((char*)&data[2], datasize-2);
1023 std::istringstream is(datastring, std::ios_base::binary);
1027 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1035 is.read((char*)buf, 2);
1036 u16 playercount = readU16(buf);
1038 for(u16 i=0; i<playercount; i++)
1040 is.read((char*)buf, 2);
1041 u16 peer_id = readU16(buf);
1042 is.read((char*)buf, 12);
1043 v3s32 p_i = readV3S32(buf);
1044 is.read((char*)buf, 12);
1045 v3s32 s_i = readV3S32(buf);
1046 is.read((char*)buf, 4);
1047 s32 pitch_i = readS32(buf);
1048 is.read((char*)buf, 4);
1049 s32 yaw_i = readS32(buf);
1051 Player *player = m_env.getPlayer(peer_id);
1053 // Skip if player doesn't exist
1059 // Skip if player is local player
1060 if(player->isLocal())
1065 f32 pitch = (f32)pitch_i / 100.0;
1066 f32 yaw = (f32)yaw_i / 100.0;
1067 v3f position((f32)p_i.X/100., (f32)p_i.Y/100., (f32)p_i.Z/100.);
1068 v3f speed((f32)s_i.X/100., (f32)s_i.Y/100., (f32)s_i.Z/100.);
1070 player->setPosition(position);
1071 player->setSpeed(speed);
1072 player->setPitch(pitch);
1073 player->setYaw(yaw);
1080 // Read active block count
1081 is.read((char*)buf, 2);
1082 u16 blockcount = readU16(buf);
1084 // Initialize delete queue with all active blocks
1085 core::map<v3s16, bool> abs_to_delete;
1086 for(core::map<v3s16, bool>::Iterator
1087 i = m_active_blocks.getIterator();
1088 i.atEnd() == false; i++)
1090 v3s16 p = i.getNode()->getKey();
1091 /*dstream<<"adding "
1092 <<"("<<p.x<<","<<p.y<<","<<p.z<<") "
1093 <<" to abs_to_delete"
1095 abs_to_delete.insert(p, true);
1098 /*dstream<<"Initial delete queue size: "<<abs_to_delete.size()
1101 for(u16 i=0; i<blockcount; i++)
1104 is.read((char*)buf, 6);
1105 v3s16 p = readV3S16(buf);
1106 // Get block from somewhere
1107 MapBlock *block = NULL;
1109 block = m_env.getMap().getBlockNoCreate(p);
1111 catch(InvalidPositionException &e)
1113 //TODO: Create a dummy block?
1117 dstream<<"WARNING: "
1118 <<"Could not get block at blockpos "
1119 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
1120 <<"in TOCLIENT_OBJECTDATA. Ignoring "
1121 <<"following block object data."
1126 /*dstream<<"Client updating objects for block "
1127 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1130 // Insert to active block list
1131 m_active_blocks.insert(p, true);
1133 // Remove from deletion queue
1134 if(abs_to_delete.find(p) != NULL)
1135 abs_to_delete.remove(p);
1138 Update objects of block
1140 NOTE: Be sure this is done in the main thread.
1142 block->updateObjects(is, m_server_ser_ver,
1143 m_device->getSceneManager(), m_env.getDayNightRatio());
1146 /*dstream<<"Final delete queue size: "<<abs_to_delete.size()
1149 // Delete objects of blocks in delete queue
1150 for(core::map<v3s16, bool>::Iterator
1151 i = abs_to_delete.getIterator();
1152 i.atEnd() == false; i++)
1154 v3s16 p = i.getNode()->getKey();
1157 MapBlock *block = m_env.getMap().getBlockNoCreate(p);
1160 block->clearObjects();
1161 // Remove from active blocks list
1162 m_active_blocks.remove(p);
1164 catch(InvalidPositionException &e)
1166 dstream<<"WARNAING: Client: "
1167 <<"Couldn't clear objects of active->inactive"
1169 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1170 <<" because block was not found"
1178 else if(command == TOCLIENT_TIME_OF_DAY)
1183 u16 time = readU16(&data[2]);
1184 time = time % 24000;
1185 m_time_of_day = time;
1186 //dstream<<"Client: time="<<time<<std::endl;
1196 u32 dr = time_to_daynight_ratio(m_time_of_day);
1198 dstream<<"Client: time_of_day="<<m_time_of_day
1202 if(dr != m_env.getDayNightRatio())
1204 dout_client<<DTIME<<"Client: changing day-night ratio"<<std::endl;
1205 m_env.setDayNightRatio(dr);
1206 m_env.expireMeshes(true);
1211 else if(command == TOCLIENT_CHAT_MESSAGE)
1219 std::string datastring((char*)&data[2], datasize-2);
1220 std::istringstream is(datastring, std::ios_base::binary);
1223 is.read((char*)buf, 2);
1224 u16 len = readU16(buf);
1226 std::wstring message;
1227 for(u16 i=0; i<len; i++)
1229 is.read((char*)buf, 2);
1230 message += (wchar_t)readU16(buf);
1233 /*dstream<<"Client received chat message: "
1234 <<wide_to_narrow(message)<<std::endl;*/
1236 m_chat_queue.push_back(message);
1238 else if(command == TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD)
1240 //if(g_settings.getBool("enable_experimental"))
1244 u16 count of removed objects
1245 for all removed objects {
1248 u16 count of added objects
1249 for all added objects {
1252 u16 initialization data length
1253 string initialization data
1258 // Get all data except the command number
1259 std::string datastring((char*)&data[2], datasize-2);
1260 // Throw them in an istringstream
1261 std::istringstream is(datastring, std::ios_base::binary);
1265 // Read removed objects
1267 u16 removed_count = readU16((u8*)buf);
1268 for(u16 i=0; i<removed_count; i++)
1271 u16 id = readU16((u8*)buf);
1274 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1275 m_env.removeActiveObject(id);
1279 // Read added objects
1281 u16 added_count = readU16((u8*)buf);
1282 for(u16 i=0; i<added_count; i++)
1285 u16 id = readU16((u8*)buf);
1287 u8 type = readU8((u8*)buf);
1288 std::string data = deSerializeLongString(is);
1291 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1292 m_env.addActiveObject(id, type, data);
1297 else if(command == TOCLIENT_ACTIVE_OBJECT_MESSAGES)
1299 //if(g_settings.getBool("enable_experimental"))
1311 // Get all data except the command number
1312 std::string datastring((char*)&data[2], datasize-2);
1313 // Throw them in an istringstream
1314 std::istringstream is(datastring, std::ios_base::binary);
1316 while(is.eof() == false)
1320 u16 id = readU16((u8*)buf);
1324 u16 message_size = readU16((u8*)buf);
1325 std::string message;
1326 message.reserve(message_size);
1327 for(u16 i=0; i<message_size; i++)
1330 message.append(buf, 1);
1332 // Pass on to the environment
1334 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1335 m_env.processActiveObjectMessage(id, message);
1340 else if(command == TOCLIENT_HP)
1342 std::string datastring((char*)&data[2], datasize-2);
1343 std::istringstream is(datastring, std::ios_base::binary);
1344 Player *player = m_env.getLocalPlayer();
1345 assert(player != NULL);
1349 else if(command == TOCLIENT_MOVE_PLAYER)
1351 std::string datastring((char*)&data[2], datasize-2);
1352 std::istringstream is(datastring, std::ios_base::binary);
1353 Player *player = m_env.getLocalPlayer();
1354 assert(player != NULL);
1355 v3f pos = readV3F1000(is);
1356 f32 pitch = readF1000(is);
1357 f32 yaw = readF1000(is);
1358 player->setPosition(pos);
1359 /*player->setPitch(pitch);
1360 player->setYaw(yaw);*/
1362 dstream<<"Client got TOCLIENT_MOVE_PLAYER"
1363 <<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
1369 Add to ClientEvent queue.
1370 This has to be sent to the main program because otherwise
1371 it would just force the pitch and yaw values to whatever
1372 the camera points to.
1375 event.type = CE_PLAYER_FORCE_MOVE;
1376 event.player_force_move.pitch = pitch;
1377 event.player_force_move.yaw = yaw;
1378 m_client_event_queue.push_back(event);
1380 // Ignore damage for a few seconds, so that the player doesn't
1381 // get damage from falling on ground
1382 m_ignore_damage_timer = 3.0;
1386 dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
1387 <<command<<std::endl;
1391 void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
1393 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
1394 m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
1397 void Client::groundAction(u8 action, v3s16 nodepos_undersurface,
1398 v3s16 nodepos_oversurface, u16 item)
1400 if(connectedAndInitialized() == false){
1401 dout_client<<DTIME<<"Client::groundAction() "
1402 "cancelled (not connected)"
1411 [3] v3s16 nodepos_undersurface
1412 [9] v3s16 nodepos_abovesurface
1417 2: stop digging (all parameters ignored)
1418 3: digging completed
1420 u8 datasize = 2 + 1 + 6 + 6 + 2;
1421 SharedBuffer<u8> data(datasize);
1422 writeU16(&data[0], TOSERVER_GROUND_ACTION);
1423 writeU8(&data[2], action);
1424 writeV3S16(&data[3], nodepos_undersurface);
1425 writeV3S16(&data[9], nodepos_oversurface);
1426 writeU16(&data[15], item);
1427 Send(0, data, true);
1430 void Client::clickObject(u8 button, v3s16 blockpos, s16 id, u16 item)
1432 if(connectedAndInitialized() == false){
1433 dout_client<<DTIME<<"Client::clickObject() "
1434 "cancelled (not connected)"
1440 [0] u16 command=TOSERVER_CLICK_OBJECT
1441 [2] u8 button (0=left, 1=right)
1446 u8 datasize = 2 + 1 + 6 + 2 + 2;
1447 SharedBuffer<u8> data(datasize);
1448 writeU16(&data[0], TOSERVER_CLICK_OBJECT);
1449 writeU8(&data[2], button);
1450 writeV3S16(&data[3], blockpos);
1451 writeS16(&data[9], id);
1452 writeU16(&data[11], item);
1453 Send(0, data, true);
1456 void Client::clickActiveObject(u8 button, u16 id, u16 item)
1458 if(connectedAndInitialized() == false){
1459 dout_client<<DTIME<<"Client::clickActiveObject() "
1460 "cancelled (not connected)"
1468 [2] u8 button (0=left, 1=right)
1472 u8 datasize = 2 + 1 + 6 + 2 + 2;
1473 SharedBuffer<u8> data(datasize);
1474 writeU16(&data[0], TOSERVER_CLICK_ACTIVEOBJECT);
1475 writeU8(&data[2], button);
1476 writeU16(&data[3], id);
1477 writeU16(&data[5], item);
1478 Send(0, data, true);
1481 void Client::sendSignText(v3s16 blockpos, s16 id, std::string text)
1490 std::ostringstream os(std::ios_base::binary);
1494 writeU16(buf, TOSERVER_SIGNTEXT);
1495 os.write((char*)buf, 2);
1498 writeV3S16(buf, blockpos);
1499 os.write((char*)buf, 6);
1503 os.write((char*)buf, 2);
1505 u16 textlen = text.size();
1506 // Write text length
1507 writeS16(buf, textlen);
1508 os.write((char*)buf, 2);
1511 os.write((char*)text.c_str(), textlen);
1514 std::string s = os.str();
1515 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1517 Send(0, data, true);
1520 void Client::sendSignNodeText(v3s16 p, std::string text)
1528 std::ostringstream os(std::ios_base::binary);
1532 writeU16(buf, TOSERVER_SIGNNODETEXT);
1533 os.write((char*)buf, 2);
1537 os.write((char*)buf, 6);
1539 u16 textlen = text.size();
1540 // Write text length
1541 writeS16(buf, textlen);
1542 os.write((char*)buf, 2);
1545 os.write((char*)text.c_str(), textlen);
1548 std::string s = os.str();
1549 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1551 Send(0, data, true);
1554 void Client::sendInventoryAction(InventoryAction *a)
1556 std::ostringstream os(std::ios_base::binary);
1560 writeU16(buf, TOSERVER_INVENTORY_ACTION);
1561 os.write((char*)buf, 2);
1566 std::string s = os.str();
1567 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1569 Send(0, data, true);
1572 void Client::sendChatMessage(const std::wstring &message)
1574 std::ostringstream os(std::ios_base::binary);
1578 writeU16(buf, TOSERVER_CHAT_MESSAGE);
1579 os.write((char*)buf, 2);
1582 writeU16(buf, message.size());
1583 os.write((char*)buf, 2);
1586 for(u32 i=0; i<message.size(); i++)
1590 os.write((char*)buf, 2);
1594 std::string s = os.str();
1595 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1597 Send(0, data, true);
1600 void Client::sendDamage(u8 damage)
1602 DSTACK(__FUNCTION_NAME);
1603 std::ostringstream os(std::ios_base::binary);
1605 writeU16(os, TOSERVER_DAMAGE);
1606 writeU8(os, damage);
1609 std::string s = os.str();
1610 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1612 Send(0, data, true);
1615 void Client::sendPlayerPos()
1617 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1619 Player *myplayer = m_env.getLocalPlayer();
1620 if(myplayer == NULL)
1625 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
1626 our_peer_id = m_con.GetPeerID();
1629 // Set peer id if not set already
1630 if(myplayer->peer_id == PEER_ID_INEXISTENT)
1631 myplayer->peer_id = our_peer_id;
1632 // Check that an existing peer_id is the same as the connection's
1633 assert(myplayer->peer_id == our_peer_id);
1635 v3f pf = myplayer->getPosition();
1636 v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
1637 v3f sf = myplayer->getSpeed();
1638 v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
1639 s32 pitch = myplayer->getPitch() * 100;
1640 s32 yaw = myplayer->getYaw() * 100;
1645 [2] v3s32 position*100
1646 [2+12] v3s32 speed*100
1647 [2+12+12] s32 pitch*100
1648 [2+12+12+4] s32 yaw*100
1651 SharedBuffer<u8> data(2+12+12+4+4);
1652 writeU16(&data[0], TOSERVER_PLAYERPOS);
1653 writeV3S32(&data[2], position);
1654 writeV3S32(&data[2+12], speed);
1655 writeS32(&data[2+12+12], pitch);
1656 writeS32(&data[2+12+12+4], yaw);
1658 // Send as unreliable
1659 Send(0, data, false);
1662 void Client::removeNode(v3s16 p)
1664 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1666 core::map<v3s16, MapBlock*> modified_blocks;
1670 //TimeTaker t("removeNodeAndUpdate", m_device);
1671 m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
1673 catch(InvalidPositionException &e)
1677 for(core::map<v3s16, MapBlock * >::Iterator
1678 i = modified_blocks.getIterator();
1679 i.atEnd() == false; i++)
1681 v3s16 p = i.getNode()->getKey();
1682 //m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
1683 addUpdateMeshTaskWithEdge(p);
1687 void Client::addNode(v3s16 p, MapNode n)
1689 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1691 TimeTaker timer1("Client::addNode()");
1693 core::map<v3s16, MapBlock*> modified_blocks;
1697 TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
1698 m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
1700 catch(InvalidPositionException &e)
1703 //TimeTaker timer2("Client::addNode(): updateMeshes");
1705 for(core::map<v3s16, MapBlock * >::Iterator
1706 i = modified_blocks.getIterator();
1707 i.atEnd() == false; i++)
1709 v3s16 p = i.getNode()->getKey();
1710 //m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
1711 addUpdateMeshTaskWithEdge(p);
1715 void Client::updateCamera(v3f pos, v3f dir)
1717 m_env.getClientMap().updateCamera(pos, dir);
1718 camera_position = pos;
1719 camera_direction = dir;
1722 MapNode Client::getNode(v3s16 p)
1724 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1725 return m_env.getMap().getNode(p);
1728 NodeMetadata* Client::getNodeMetadata(v3s16 p)
1730 return m_env.getMap().getNodeMetadata(p);
1733 v3f Client::getPlayerPosition()
1735 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1736 LocalPlayer *player = m_env.getLocalPlayer();
1737 assert(player != NULL);
1738 return player->getPosition();
1741 void Client::setPlayerControl(PlayerControl &control)
1743 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1744 LocalPlayer *player = m_env.getLocalPlayer();
1745 assert(player != NULL);
1746 player->control = control;
1749 // Returns true if the inventory of the local player has been
1750 // updated from the server. If it is true, it is set to false.
1751 bool Client::getLocalInventoryUpdated()
1753 // m_inventory_updated is behind envlock
1754 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1755 bool updated = m_inventory_updated;
1756 m_inventory_updated = false;
1760 // Copies the inventory of the local player to parameter
1761 void Client::getLocalInventory(Inventory &dst)
1763 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1764 Player *player = m_env.getLocalPlayer();
1765 assert(player != NULL);
1766 dst = player->inventory;
1769 InventoryContext *Client::getInventoryContext()
1771 return &m_inventory_context;
1774 Inventory* Client::getInventory(InventoryContext *c, std::string id)
1776 if(id == "current_player")
1778 assert(c->current_player);
1779 return &(c->current_player->inventory);
1783 std::string id0 = fn.next(":");
1785 if(id0 == "nodemeta")
1788 p.X = stoi(fn.next(","));
1789 p.Y = stoi(fn.next(","));
1790 p.Z = stoi(fn.next(","));
1791 NodeMetadata* meta = getNodeMetadata(p);
1793 return meta->getInventory();
1794 dstream<<"nodemeta at ("<<p.X<<","<<p.Y<<","<<p.Z<<"): "
1795 <<"no metadata found"<<std::endl;
1799 dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
1802 void Client::inventoryAction(InventoryAction *a)
1804 sendInventoryAction(a);
1807 MapBlockObject * Client::getSelectedObject(
1809 v3f from_pos_f_on_map,
1810 core::line3d<f32> shootline_on_map
1813 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1815 core::array<DistanceSortedObject> objects;
1817 for(core::map<v3s16, bool>::Iterator
1818 i = m_active_blocks.getIterator();
1819 i.atEnd() == false; i++)
1821 v3s16 p = i.getNode()->getKey();
1823 MapBlock *block = NULL;
1826 block = m_env.getMap().getBlockNoCreate(p);
1828 catch(InvalidPositionException &e)
1833 // Calculate from_pos relative to block
1834 v3s16 block_pos_i_on_map = block->getPosRelative();
1835 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
1836 v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
1838 block->getObjects(from_pos_f_on_block, max_d, objects);
1839 //block->getPseudoObjects(from_pos_f_on_block, max_d, objects);
1842 //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
1845 // After this, the closest object is the first in the array.
1848 for(u32 i=0; i<objects.size(); i++)
1850 MapBlockObject *obj = objects[i].obj;
1851 MapBlock *block = obj->getBlock();
1853 // Calculate shootline relative to block
1854 v3s16 block_pos_i_on_map = block->getPosRelative();
1855 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
1856 core::line3d<f32> shootline_on_block(
1857 shootline_on_map.start - block_pos_f_on_map,
1858 shootline_on_map.end - block_pos_f_on_map
1861 if(obj->isSelected(shootline_on_block))
1863 //dstream<<"Returning selected object"<<std::endl;
1868 //dstream<<"No object selected; returning NULL."<<std::endl;
1872 ClientActiveObject * Client::getSelectedActiveObject(
1874 v3f from_pos_f_on_map,
1875 core::line3d<f32> shootline_on_map
1878 core::array<DistanceSortedActiveObject> objects;
1880 m_env.getActiveObjects(from_pos_f_on_map, max_d, objects);
1882 //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
1885 // After this, the closest object is the first in the array.
1888 for(u32 i=0; i<objects.size(); i++)
1890 ClientActiveObject *obj = objects[i].obj;
1892 core::aabbox3d<f32> *selection_box = obj->getSelectionBox();
1893 if(selection_box == NULL)
1896 v3f pos = obj->getPosition();
1898 core::aabbox3d<f32> offsetted_box(
1899 selection_box->MinEdge + pos,
1900 selection_box->MaxEdge + pos
1903 if(offsetted_box.intersectsWithLine(shootline_on_map))
1905 //dstream<<"Returning selected object"<<std::endl;
1910 //dstream<<"No object selected; returning NULL."<<std::endl;
1914 void Client::printDebugInfo(std::ostream &os)
1916 //JMutexAutoLock lock1(m_fetchblock_mutex);
1917 /*JMutexAutoLock lock2(m_incoming_queue_mutex);
1919 os<<"m_incoming_queue.getSize()="<<m_incoming_queue.getSize()
1920 //<<", m_fetchblock_history.size()="<<m_fetchblock_history.size()
1921 //<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
1925 /*s32 Client::getDayNightIndex()
1927 assert(m_daynight_i >= 0 && m_daynight_i < DAYNIGHT_CACHE_COUNT);
1928 return m_daynight_i;
1931 u32 Client::getDayNightRatio()
1933 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1934 return m_env.getDayNightRatio();
1939 Player *player = m_env.getLocalPlayer();
1940 assert(player != NULL);
1944 void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
1946 /*dstream<<"Client::addUpdateMeshTask(): "
1947 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1950 MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
1955 Create a task to update the mesh of the block
1958 MeshMakeData *data = new MeshMakeData;
1961 //TimeTaker timer("data fill");
1963 data->fill(getDayNightRatio(), b);
1967 //while(m_mesh_update_thread.m_queue_in.size() > 0) sleep_ms(10);
1969 // Add task to queue
1970 m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server);
1972 /*dstream<<"Mesh update input queue size is "
1973 <<m_mesh_update_thread.m_queue_in.size()
1977 // Temporary test: make mesh directly in here
1979 //TimeTaker timer("make mesh");
1981 scene::SMesh *mesh_new = NULL;
1982 mesh_new = makeMapBlockMesh(data);
1983 b->replaceMesh(mesh_new);
1988 b->setMeshExpired(false);
1991 void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server)
1995 dstream<<"Client::addUpdateMeshTaskWithEdge(): "
1996 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2001 v3s16 p = blockpos + v3s16(0,0,0);
2002 //MapBlock *b = m_env.getMap().getBlockNoCreate(p);
2003 addUpdateMeshTask(p, ack_to_server);
2005 catch(InvalidPositionException &e){}
2008 v3s16 p = blockpos + v3s16(-1,0,0);
2009 addUpdateMeshTask(p);
2011 catch(InvalidPositionException &e){}
2013 v3s16 p = blockpos + v3s16(0,-1,0);
2014 addUpdateMeshTask(p);
2016 catch(InvalidPositionException &e){}
2018 v3s16 p = blockpos + v3s16(0,0,-1);
2019 addUpdateMeshTask(p);
2021 catch(InvalidPositionException &e){}
2024 ClientEvent Client::getClientEvent()
2026 if(m_client_event_queue.size() == 0)
2029 event.type = CE_NONE;
2032 return m_client_event_queue.pop_front();