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,
72 MapDrawControl &control):
73 m_mesh_update_thread(),
75 new ClientMap(this, control,
76 device->getSceneManager()->getRootSceneNode(),
77 device->getSceneManager(), 666),
78 device->getSceneManager()
80 m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
82 camera_position(0,0,0),
83 camera_direction(0,0,1),
84 m_server_ser_ver(SER_FMT_VER_INVALID),
85 m_inventory_updated(false),
89 m_access_denied(false)
91 m_packetcounter_timer = 0.0;
92 m_delete_unused_sectors_timer = 0.0;
93 m_connection_reinit_timer = 0.0;
94 m_avg_rtt_timer = 0.0;
95 m_playerpos_send_timer = 0.0;
96 m_ignore_damage_timer = 0.0;
101 m_mesh_update_thread.Start();
107 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
109 Player *player = new LocalPlayer();
111 player->updateName(playername);
113 m_env.addPlayer(player);
115 // Initialize player in the inventory context
116 m_inventory_context.current_player = player;
123 //JMutexAutoLock conlock(m_con_mutex); //bulk comment-out
127 m_mesh_update_thread.setRun(false);
128 while(m_mesh_update_thread.IsRunning())
132 void Client::connect(Address address)
134 DSTACK(__FUNCTION_NAME);
135 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
136 m_con.setTimeoutMs(0);
137 m_con.Connect(address);
140 bool Client::connectedAndInitialized()
142 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
144 if(m_con.Connected() == false)
147 if(m_server_ser_ver == SER_FMT_VER_INVALID)
153 void Client::step(float dtime)
155 DSTACK(__FUNCTION_NAME);
161 if(m_ignore_damage_timer > dtime)
162 m_ignore_damage_timer -= dtime;
164 m_ignore_damage_timer = 0.0;
166 //dstream<<"Client steps "<<dtime<<std::endl;
169 //TimeTaker timer("ReceiveAll()", m_device);
175 //TimeTaker timer("m_con_mutex + m_con.RunTimeouts()", m_device);
177 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
178 m_con.RunTimeouts(dtime);
185 float &counter = m_packetcounter_timer;
191 dout_client<<"Client packetcounter (20s):"<<std::endl;
192 m_packetcounter.print(dout_client);
193 m_packetcounter.clear();
199 Delete unused sectors
201 NOTE: This jams the game for a while because deleting sectors
205 float &counter = m_delete_unused_sectors_timer;
213 //JMutexAutoLock lock(m_env_mutex); //bulk comment-out
215 core::list<v3s16> deleted_blocks;
217 float delete_unused_sectors_timeout =
218 g_settings.getFloat("client_delete_unused_sectors_timeout");
220 // Delete sector blocks
221 /*u32 num = m_env.getMap().deleteUnusedSectors
222 (delete_unused_sectors_timeout,
223 true, &deleted_blocks);*/
225 // Delete whole sectors
226 u32 num = m_env.getMap().deleteUnusedSectors
227 (delete_unused_sectors_timeout,
228 false, &deleted_blocks);
232 /*dstream<<DTIME<<"Client: Deleted blocks of "<<num
233 <<" unused sectors"<<std::endl;*/
234 dstream<<DTIME<<"Client: Deleted "<<num
235 <<" unused sectors"<<std::endl;
241 // Env is locked so con can be locked.
242 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
244 core::list<v3s16>::Iterator i = deleted_blocks.begin();
245 core::list<v3s16> sendlist;
248 if(sendlist.size() == 255 || i == deleted_blocks.end())
250 if(sendlist.size() == 0)
259 u32 replysize = 2+1+6*sendlist.size();
260 SharedBuffer<u8> reply(replysize);
261 writeU16(&reply[0], TOSERVER_DELETEDBLOCKS);
262 reply[2] = sendlist.size();
264 for(core::list<v3s16>::Iterator
265 j = sendlist.begin();
266 j != sendlist.end(); j++)
268 writeV3S16(&reply[2+1+6*k], *j);
271 m_con.Send(PEER_ID_SERVER, 1, reply, true);
273 if(i == deleted_blocks.end())
279 sendlist.push_back(*i);
286 bool connected = connectedAndInitialized();
288 if(connected == false)
290 float &counter = m_connection_reinit_timer;
296 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
298 Player *myplayer = m_env.getLocalPlayer();
299 assert(myplayer != NULL);
301 // Send TOSERVER_INIT
302 // [0] u16 TOSERVER_INIT
303 // [2] u8 SER_FMT_VER_HIGHEST
304 // [3] u8[20] player_name
305 // [23] u8[28] password
306 SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE+PASSWORD_SIZE);
307 writeU16(&data[0], TOSERVER_INIT);
308 writeU8(&data[2], SER_FMT_VER_HIGHEST);
309 memset((char*)&data[3], 0, PLAYERNAME_SIZE);
310 snprintf((char*)&data[3], PLAYERNAME_SIZE, "%s", myplayer->getName());
311 snprintf((char*)&data[23], PASSWORD_SIZE, "%s", m_password.c_str());
313 // Send as unreliable
314 Send(0, data, false);
317 // Not connected, return
322 Do stuff if connected
330 //JMutexAutoLock lock(m_env_mutex); //bulk comment-out
332 // Control local player (0ms)
333 LocalPlayer *player = m_env.getLocalPlayer();
334 assert(player != NULL);
335 player->applyControl(dtime);
337 //TimeTaker envtimer("env step", m_device);
341 // Step active blocks
342 for(core::map<v3s16, bool>::Iterator
343 i = m_active_blocks.getIterator();
344 i.atEnd() == false; i++)
346 v3s16 p = i.getNode()->getKey();
348 MapBlock *block = NULL;
351 block = m_env.getMap().getBlockNoCreate(p);
352 block->stepObjects(dtime, false, m_env.getDayNightRatio());
354 catch(InvalidPositionException &e)
364 ClientEnvEvent event = m_env.getClientEvent();
365 if(event.type == CEE_NONE)
369 else if(event.type == CEE_PLAYER_DAMAGE)
371 if(m_ignore_damage_timer <= 0)
373 u8 damage = event.player_damage.amount;
376 // Add to ClientEvent queue
378 event.type = CE_PLAYER_DAMAGE;
379 event.player_damage.amount = damage;
380 m_client_event_queue.push_back(event);
390 float &counter = m_avg_rtt_timer;
395 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
396 // connectedAndInitialized() is true, peer exists.
397 con::Peer *peer = m_con.GetPeer(PEER_ID_SERVER);
398 dstream<<DTIME<<"Client: avg_rtt="<<peer->avg_rtt<<std::endl;
403 Send player position to server
406 float &counter = m_playerpos_send_timer;
416 Replace updated meshes
419 //JMutexAutoLock lock(m_env_mutex); //bulk comment-out
421 //TimeTaker timer("** Processing mesh update result queue");
424 /*dstream<<"Mesh update result queue size is "
425 <<m_mesh_update_thread.m_queue_out.size()
428 while(m_mesh_update_thread.m_queue_out.size() > 0)
430 MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_front();
431 MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
434 block->replaceMesh(r.mesh);
436 if(r.ack_block_to_server)
438 /*dstream<<"Client: ACK block ("<<r.p.X<<","<<r.p.Y
439 <<","<<r.p.Z<<")"<<std::endl;*/
450 u32 replysize = 2+1+6;
451 SharedBuffer<u8> reply(replysize);
452 writeU16(&reply[0], TOSERVER_GOTBLOCKS);
454 writeV3S16(&reply[3], r.p);
456 m_con.Send(PEER_ID_SERVER, 1, reply, true);
462 // Virtual methods from con::PeerHandler
463 void Client::peerAdded(con::Peer *peer)
465 derr_client<<"Client::peerAdded(): peer->id="
466 <<peer->id<<std::endl;
468 void Client::deletingPeer(con::Peer *peer, bool timeout)
470 derr_client<<"Client::deletingPeer(): "
471 "Server Peer is getting deleted "
472 <<"(timeout="<<timeout<<")"<<std::endl;
475 void Client::ReceiveAll()
477 DSTACK(__FUNCTION_NAME);
483 catch(con::NoIncomingDataException &e)
487 catch(con::InvalidIncomingDataException &e)
489 dout_client<<DTIME<<"Client::ReceiveAll(): "
490 "InvalidIncomingDataException: what()="
491 <<e.what()<<std::endl;
496 void Client::Receive()
498 DSTACK(__FUNCTION_NAME);
499 u32 data_maxsize = 200000;
500 Buffer<u8> data(data_maxsize);
504 //TimeTaker t1("con mutex and receive", m_device);
505 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
506 datasize = m_con.Receive(sender_peer_id, *data, data_maxsize);
508 //TimeTaker t1("ProcessData", m_device);
509 ProcessData(*data, datasize, sender_peer_id);
513 sender_peer_id given to this shall be quaranteed to be a valid peer
515 void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
517 DSTACK(__FUNCTION_NAME);
519 // Ignore packets that don't even fit a command
522 m_packetcounter.add(60000);
526 ToClientCommand command = (ToClientCommand)readU16(&data[0]);
528 //dstream<<"Client: received command="<<command<<std::endl;
529 m_packetcounter.add((u16)command);
532 If this check is removed, be sure to change the queue
533 system to know the ids
535 if(sender_peer_id != PEER_ID_SERVER)
537 dout_client<<DTIME<<"Client::ProcessData(): Discarding data not "
538 "coming from server: peer_id="<<sender_peer_id
545 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
546 // All data is coming from the server
547 // PeerNotFoundException is handled by caller.
548 peer = m_con.GetPeer(PEER_ID_SERVER);
551 u8 ser_version = m_server_ser_ver;
553 //dstream<<"Client received command="<<(int)command<<std::endl;
555 if(command == TOCLIENT_INIT)
560 u8 deployed = data[2];
562 dout_client<<DTIME<<"Client: TOCLIENT_INIT received with "
563 "deployed="<<((int)deployed&0xff)<<std::endl;
565 if(deployed < SER_FMT_VER_LOWEST
566 || deployed > SER_FMT_VER_HIGHEST)
568 derr_client<<DTIME<<"Client: TOCLIENT_INIT: Server sent "
569 <<"unsupported ser_fmt_ver"<<std::endl;
573 m_server_ser_ver = deployed;
575 // Get player position
576 v3s16 playerpos_s16(0, BS*2+BS*20, 0);
577 if(datasize >= 2+1+6)
578 playerpos_s16 = readV3S16(&data[2+1]);
579 v3f playerpos_f = intToFloat(playerpos_s16, BS) - v3f(0, BS/2, 0);
582 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
584 // Set player position
585 Player *player = m_env.getLocalPlayer();
586 assert(player != NULL);
587 player->setPosition(playerpos_f);
590 if(datasize >= 2+1+6+8)
593 m_map_seed = readU64(&data[2+1+6]);
594 dstream<<"Client: received map seed: "<<m_map_seed<<std::endl;
599 SharedBuffer<u8> reply(replysize);
600 writeU16(&reply[0], TOSERVER_INIT2);
602 m_con.Send(PEER_ID_SERVER, 1, reply, true);
607 if(command == TOCLIENT_ACCESS_DENIED)
609 // The server didn't like our password. Note, this needs
610 // to be processed even if the serialisation format has
611 // not been agreed yet, the same as TOCLIENT_INIT.
612 m_access_denied = true;
616 if(ser_version == SER_FMT_VER_INVALID)
618 dout_client<<DTIME<<"WARNING: Client: Server serialization"
619 " format invalid or not initialized."
620 " Skipping incoming command="<<command<<std::endl;
624 // Just here to avoid putting the two if's together when
625 // making some copypasta
628 if(command == TOCLIENT_REMOVENODE)
633 p.X = readS16(&data[2]);
634 p.Y = readS16(&data[4]);
635 p.Z = readS16(&data[6]);
637 //TimeTaker t1("TOCLIENT_REMOVENODE");
639 // This will clear the cracking animation after digging
640 ((ClientMap&)m_env.getMap()).clearTempMod(p);
644 else if(command == TOCLIENT_ADDNODE)
646 if(datasize < 8 + MapNode::serializedLength(ser_version))
650 p.X = readS16(&data[2]);
651 p.Y = readS16(&data[4]);
652 p.Z = readS16(&data[6]);
654 //TimeTaker t1("TOCLIENT_ADDNODE");
657 n.deSerialize(&data[8], ser_version);
661 else if(command == TOCLIENT_BLOCKDATA)
663 // Ignore too small packet
668 p.X = readS16(&data[2]);
669 p.Y = readS16(&data[4]);
670 p.Z = readS16(&data[6]);
672 /*dout_client<<DTIME<<"Client: Thread: BLOCKDATA for ("
673 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
674 /*dstream<<DTIME<<"Client: Thread: BLOCKDATA for ("
675 <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
677 std::string datastring((char*)&data[8], datasize-8);
678 std::istringstream istr(datastring, std::ios_base::binary);
684 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
687 sector = m_env.getMap().emergeSector(p2d);
689 v2s16 sp = sector->getPos();
692 dstream<<"ERROR: Got sector with getPos()="
693 <<"("<<sp.X<<","<<sp.Y<<"), tried to get"
694 <<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
698 //assert(sector->getPos() == p2d);
700 //TimeTaker timer("MapBlock deSerialize");
704 block = sector->getBlockNoCreate(p.Y);
706 Update an existing block
708 //dstream<<"Updating"<<std::endl;
709 block->deSerialize(istr, ser_version);
710 //block->setChangedFlag();
712 catch(InvalidPositionException &e)
717 //dstream<<"Creating new"<<std::endl;
718 block = new MapBlock(&m_env.getMap(), p);
719 block->deSerialize(istr, ser_version);
720 sector->insertBlock(block);
721 //block->setChangedFlag();
725 mod.type = NODEMOD_CHANGECONTENT;
726 mod.param = CONTENT_MESE;
727 block->setTempMod(v3s16(8,10,8), mod);
728 block->setTempMod(v3s16(8,9,8), mod);
729 block->setTempMod(v3s16(8,8,8), mod);
730 block->setTempMod(v3s16(8,7,8), mod);
731 block->setTempMod(v3s16(8,6,8), mod);*/
735 Well, this is a dumb way to do it, they should just
736 be drawn as separate objects. But the looks of them
737 can be tested this way.
742 mod.type = NODEMOD_CHANGECONTENT;
743 mod.param = CONTENT_CLOUD;
746 for(p2.X=3; p2.X<=13; p2.X++)
747 for(p2.Z=3; p2.Z<=13; p2.Z++)
749 block->setTempMod(p2, mod);
767 u32 replysize = 2+1+6;
768 SharedBuffer<u8> reply(replysize);
769 writeU16(&reply[0], TOSERVER_GOTBLOCKS);
771 writeV3S16(&reply[3], p);
773 m_con.Send(PEER_ID_SERVER, 1, reply, true);
777 Update Mesh of this block and blocks at x-, y- and z-.
778 Environment should not be locked as it interlocks with the
779 main thread, from which is will want to retrieve textures.
782 //m_env.getClientMap().updateMeshes(block->getPos(), getDayNightRatio());
784 addUpdateMeshTaskWithEdge(p, true);
786 else if(command == TOCLIENT_PLAYERPOS)
788 dstream<<"WARNING: Received deprecated TOCLIENT_PLAYERPOS"
792 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
793 our_peer_id = m_con.GetPeerID();
795 // Cancel if we don't have a peer id
796 if(our_peer_id == PEER_ID_INEXISTENT){
797 dout_client<<DTIME<<"TOCLIENT_PLAYERPOS cancelled: "
804 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
806 u32 player_size = 2+12+12+4+4;
808 u32 player_count = (datasize-2) / player_size;
810 for(u32 i=0; i<player_count; i++)
812 u16 peer_id = readU16(&data[start]);
814 Player *player = m_env.getPlayer(peer_id);
816 // Skip if player doesn't exist
819 start += player_size;
823 // Skip if player is local player
824 if(player->isLocal())
826 start += player_size;
830 v3s32 ps = readV3S32(&data[start+2]);
831 v3s32 ss = readV3S32(&data[start+2+12]);
832 s32 pitch_i = readS32(&data[start+2+12+12]);
833 s32 yaw_i = readS32(&data[start+2+12+12+4]);
834 /*dstream<<"Client: got "
835 <<"pitch_i="<<pitch_i
836 <<" yaw_i="<<yaw_i<<std::endl;*/
837 f32 pitch = (f32)pitch_i / 100.0;
838 f32 yaw = (f32)yaw_i / 100.0;
839 v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.);
840 v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.);
841 player->setPosition(position);
842 player->setSpeed(speed);
843 player->setPitch(pitch);
846 /*dstream<<"Client: player "<<peer_id
848 <<" yaw="<<yaw<<std::endl;*/
850 start += player_size;
854 else if(command == TOCLIENT_PLAYERINFO)
858 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
859 our_peer_id = m_con.GetPeerID();
861 // Cancel if we don't have a peer id
862 if(our_peer_id == PEER_ID_INEXISTENT){
863 dout_client<<DTIME<<"TOCLIENT_PLAYERINFO cancelled: "
869 //dstream<<DTIME<<"Client: Server reports players:"<<std::endl;
872 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
874 u32 item_size = 2+PLAYERNAME_SIZE;
875 u32 player_count = (datasize-2) / item_size;
878 core::list<u16> players_alive;
879 for(u32 i=0; i<player_count; i++)
881 // Make sure the name ends in '\0'
882 data[start+2+20-1] = 0;
884 u16 peer_id = readU16(&data[start]);
886 players_alive.push_back(peer_id);
888 /*dstream<<DTIME<<"peer_id="<<peer_id
889 <<" name="<<((char*)&data[start+2])<<std::endl;*/
891 // Don't update the info of the local player
892 if(peer_id == our_peer_id)
898 Player *player = m_env.getPlayer(peer_id);
900 // Create a player if it doesn't exist
903 player = new RemotePlayer(
904 m_device->getSceneManager()->getRootSceneNode(),
907 player->peer_id = peer_id;
908 m_env.addPlayer(player);
909 dout_client<<DTIME<<"Client: Adding new player "
910 <<peer_id<<std::endl;
913 player->updateName((char*)&data[start+2]);
919 Remove those players from the environment that
920 weren't listed by the server.
922 //dstream<<DTIME<<"Removing dead players"<<std::endl;
923 core::list<Player*> players = m_env.getPlayers();
924 core::list<Player*>::Iterator ip;
925 for(ip=players.begin(); ip!=players.end(); ip++)
927 // Ingore local player
931 // Warn about a special case
932 if((*ip)->peer_id == 0)
934 dstream<<DTIME<<"WARNING: Client: Removing "
935 "dead player with id=0"<<std::endl;
938 bool is_alive = false;
939 core::list<u16>::Iterator i;
940 for(i=players_alive.begin(); i!=players_alive.end(); i++)
942 if((*ip)->peer_id == *i)
948 /*dstream<<DTIME<<"peer_id="<<((*ip)->peer_id)
949 <<" is_alive="<<is_alive<<std::endl;*/
952 dstream<<DTIME<<"Removing dead player "<<(*ip)->peer_id
954 m_env.removePlayer((*ip)->peer_id);
958 else if(command == TOCLIENT_SECTORMETA)
963 [3...] v2s16 pos + sector metadata
968 //dstream<<"Client received TOCLIENT_SECTORMETA"<<std::endl;
971 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
973 std::string datastring((char*)&data[2], datasize-2);
974 std::istringstream is(datastring, std::ios_base::binary);
978 is.read((char*)buf, 1);
979 u16 sector_count = readU8(buf);
981 //dstream<<"sector_count="<<sector_count<<std::endl;
983 for(u16 i=0; i<sector_count; i++)
986 is.read((char*)buf, 4);
987 v2s16 pos = readV2S16(buf);
988 /*dstream<<"Client: deserializing sector at "
989 <<"("<<pos.X<<","<<pos.Y<<")"<<std::endl;*/
991 assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
992 ((ClientMap&)m_env.getMap()).deSerializeSector(pos, is);
996 else if(command == TOCLIENT_INVENTORY)
1001 //TimeTaker t1("Parsing TOCLIENT_INVENTORY", m_device);
1004 //TimeTaker t2("mutex locking", m_device);
1005 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1008 //TimeTaker t3("istringstream init", m_device);
1009 std::string datastring((char*)&data[2], datasize-2);
1010 std::istringstream is(datastring, std::ios_base::binary);
1013 //m_env.printPlayers(dstream);
1015 //TimeTaker t4("player get", m_device);
1016 Player *player = m_env.getLocalPlayer();
1017 assert(player != NULL);
1020 //TimeTaker t1("inventory.deSerialize()", m_device);
1021 player->inventory.deSerialize(is);
1024 m_inventory_updated = true;
1026 //dstream<<"Client got player inventory:"<<std::endl;
1027 //player->inventory.print(dstream);
1031 else if(command == TOCLIENT_OBJECTDATA)
1034 // Strip command word and create a stringstream
1035 std::string datastring((char*)&data[2], datasize-2);
1036 std::istringstream is(datastring, std::ios_base::binary);
1040 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1048 is.read((char*)buf, 2);
1049 u16 playercount = readU16(buf);
1051 for(u16 i=0; i<playercount; i++)
1053 is.read((char*)buf, 2);
1054 u16 peer_id = readU16(buf);
1055 is.read((char*)buf, 12);
1056 v3s32 p_i = readV3S32(buf);
1057 is.read((char*)buf, 12);
1058 v3s32 s_i = readV3S32(buf);
1059 is.read((char*)buf, 4);
1060 s32 pitch_i = readS32(buf);
1061 is.read((char*)buf, 4);
1062 s32 yaw_i = readS32(buf);
1064 Player *player = m_env.getPlayer(peer_id);
1066 // Skip if player doesn't exist
1072 // Skip if player is local player
1073 if(player->isLocal())
1078 f32 pitch = (f32)pitch_i / 100.0;
1079 f32 yaw = (f32)yaw_i / 100.0;
1080 v3f position((f32)p_i.X/100., (f32)p_i.Y/100., (f32)p_i.Z/100.);
1081 v3f speed((f32)s_i.X/100., (f32)s_i.Y/100., (f32)s_i.Z/100.);
1083 player->setPosition(position);
1084 player->setSpeed(speed);
1085 player->setPitch(pitch);
1086 player->setYaw(yaw);
1093 // Read active block count
1094 is.read((char*)buf, 2);
1095 u16 blockcount = readU16(buf);
1097 // Initialize delete queue with all active blocks
1098 core::map<v3s16, bool> abs_to_delete;
1099 for(core::map<v3s16, bool>::Iterator
1100 i = m_active_blocks.getIterator();
1101 i.atEnd() == false; i++)
1103 v3s16 p = i.getNode()->getKey();
1104 /*dstream<<"adding "
1105 <<"("<<p.x<<","<<p.y<<","<<p.z<<") "
1106 <<" to abs_to_delete"
1108 abs_to_delete.insert(p, true);
1111 /*dstream<<"Initial delete queue size: "<<abs_to_delete.size()
1114 for(u16 i=0; i<blockcount; i++)
1117 is.read((char*)buf, 6);
1118 v3s16 p = readV3S16(buf);
1119 // Get block from somewhere
1120 MapBlock *block = NULL;
1122 block = m_env.getMap().getBlockNoCreate(p);
1124 catch(InvalidPositionException &e)
1126 //TODO: Create a dummy block?
1130 dstream<<"WARNING: "
1131 <<"Could not get block at blockpos "
1132 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
1133 <<"in TOCLIENT_OBJECTDATA. Ignoring "
1134 <<"following block object data."
1139 /*dstream<<"Client updating objects for block "
1140 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1143 // Insert to active block list
1144 m_active_blocks.insert(p, true);
1146 // Remove from deletion queue
1147 if(abs_to_delete.find(p) != NULL)
1148 abs_to_delete.remove(p);
1151 Update objects of block
1153 NOTE: Be sure this is done in the main thread.
1155 block->updateObjects(is, m_server_ser_ver,
1156 m_device->getSceneManager(), m_env.getDayNightRatio());
1159 /*dstream<<"Final delete queue size: "<<abs_to_delete.size()
1162 // Delete objects of blocks in delete queue
1163 for(core::map<v3s16, bool>::Iterator
1164 i = abs_to_delete.getIterator();
1165 i.atEnd() == false; i++)
1167 v3s16 p = i.getNode()->getKey();
1170 MapBlock *block = m_env.getMap().getBlockNoCreate(p);
1173 block->clearObjects();
1174 // Remove from active blocks list
1175 m_active_blocks.remove(p);
1177 catch(InvalidPositionException &e)
1179 dstream<<"WARNAING: Client: "
1180 <<"Couldn't clear objects of active->inactive"
1182 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1183 <<" because block was not found"
1191 else if(command == TOCLIENT_TIME_OF_DAY)
1196 u16 time = readU16(&data[2]);
1197 time = time % 24000;
1198 m_time_of_day = time;
1199 //dstream<<"Client: time="<<time<<std::endl;
1209 u32 dr = time_to_daynight_ratio(m_time_of_day);
1211 dstream<<"Client: time_of_day="<<m_time_of_day
1215 if(dr != m_env.getDayNightRatio())
1217 dout_client<<DTIME<<"Client: changing day-night ratio"<<std::endl;
1218 m_env.setDayNightRatio(dr);
1219 m_env.expireMeshes(true);
1224 else if(command == TOCLIENT_CHAT_MESSAGE)
1232 std::string datastring((char*)&data[2], datasize-2);
1233 std::istringstream is(datastring, std::ios_base::binary);
1236 is.read((char*)buf, 2);
1237 u16 len = readU16(buf);
1239 std::wstring message;
1240 for(u16 i=0; i<len; i++)
1242 is.read((char*)buf, 2);
1243 message += (wchar_t)readU16(buf);
1246 /*dstream<<"Client received chat message: "
1247 <<wide_to_narrow(message)<<std::endl;*/
1249 m_chat_queue.push_back(message);
1251 else if(command == TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD)
1253 //if(g_settings.getBool("enable_experimental"))
1257 u16 count of removed objects
1258 for all removed objects {
1261 u16 count of added objects
1262 for all added objects {
1265 u16 initialization data length
1266 string initialization data
1271 // Get all data except the command number
1272 std::string datastring((char*)&data[2], datasize-2);
1273 // Throw them in an istringstream
1274 std::istringstream is(datastring, std::ios_base::binary);
1278 // Read removed objects
1280 u16 removed_count = readU16((u8*)buf);
1281 for(u16 i=0; i<removed_count; i++)
1284 u16 id = readU16((u8*)buf);
1287 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1288 m_env.removeActiveObject(id);
1292 // Read added objects
1294 u16 added_count = readU16((u8*)buf);
1295 for(u16 i=0; i<added_count; i++)
1298 u16 id = readU16((u8*)buf);
1300 u8 type = readU8((u8*)buf);
1301 std::string data = deSerializeLongString(is);
1304 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1305 m_env.addActiveObject(id, type, data);
1310 else if(command == TOCLIENT_ACTIVE_OBJECT_MESSAGES)
1312 //if(g_settings.getBool("enable_experimental"))
1324 // Get all data except the command number
1325 std::string datastring((char*)&data[2], datasize-2);
1326 // Throw them in an istringstream
1327 std::istringstream is(datastring, std::ios_base::binary);
1329 while(is.eof() == false)
1333 u16 id = readU16((u8*)buf);
1337 u16 message_size = readU16((u8*)buf);
1338 std::string message;
1339 message.reserve(message_size);
1340 for(u16 i=0; i<message_size; i++)
1343 message.append(buf, 1);
1345 // Pass on to the environment
1347 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1348 m_env.processActiveObjectMessage(id, message);
1353 else if(command == TOCLIENT_HP)
1355 std::string datastring((char*)&data[2], datasize-2);
1356 std::istringstream is(datastring, std::ios_base::binary);
1357 Player *player = m_env.getLocalPlayer();
1358 assert(player != NULL);
1362 else if(command == TOCLIENT_MOVE_PLAYER)
1364 std::string datastring((char*)&data[2], datasize-2);
1365 std::istringstream is(datastring, std::ios_base::binary);
1366 Player *player = m_env.getLocalPlayer();
1367 assert(player != NULL);
1368 v3f pos = readV3F1000(is);
1369 f32 pitch = readF1000(is);
1370 f32 yaw = readF1000(is);
1371 player->setPosition(pos);
1372 /*player->setPitch(pitch);
1373 player->setYaw(yaw);*/
1375 dstream<<"Client got TOCLIENT_MOVE_PLAYER"
1376 <<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"
1382 Add to ClientEvent queue.
1383 This has to be sent to the main program because otherwise
1384 it would just force the pitch and yaw values to whatever
1385 the camera points to.
1388 event.type = CE_PLAYER_FORCE_MOVE;
1389 event.player_force_move.pitch = pitch;
1390 event.player_force_move.yaw = yaw;
1391 m_client_event_queue.push_back(event);
1393 // Ignore damage for a few seconds, so that the player doesn't
1394 // get damage from falling on ground
1395 m_ignore_damage_timer = 3.0;
1399 dout_client<<DTIME<<"WARNING: Client: Ignoring unknown command "
1400 <<command<<std::endl;
1404 void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)
1406 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
1407 m_con.Send(PEER_ID_SERVER, channelnum, data, reliable);
1410 void Client::groundAction(u8 action, v3s16 nodepos_undersurface,
1411 v3s16 nodepos_oversurface, u16 item)
1413 if(connectedAndInitialized() == false){
1414 dout_client<<DTIME<<"Client::groundAction() "
1415 "cancelled (not connected)"
1424 [3] v3s16 nodepos_undersurface
1425 [9] v3s16 nodepos_abovesurface
1430 2: stop digging (all parameters ignored)
1431 3: digging completed
1433 u8 datasize = 2 + 1 + 6 + 6 + 2;
1434 SharedBuffer<u8> data(datasize);
1435 writeU16(&data[0], TOSERVER_GROUND_ACTION);
1436 writeU8(&data[2], action);
1437 writeV3S16(&data[3], nodepos_undersurface);
1438 writeV3S16(&data[9], nodepos_oversurface);
1439 writeU16(&data[15], item);
1440 Send(0, data, true);
1443 void Client::clickObject(u8 button, v3s16 blockpos, s16 id, u16 item)
1445 if(connectedAndInitialized() == false){
1446 dout_client<<DTIME<<"Client::clickObject() "
1447 "cancelled (not connected)"
1453 [0] u16 command=TOSERVER_CLICK_OBJECT
1454 [2] u8 button (0=left, 1=right)
1459 u8 datasize = 2 + 1 + 6 + 2 + 2;
1460 SharedBuffer<u8> data(datasize);
1461 writeU16(&data[0], TOSERVER_CLICK_OBJECT);
1462 writeU8(&data[2], button);
1463 writeV3S16(&data[3], blockpos);
1464 writeS16(&data[9], id);
1465 writeU16(&data[11], item);
1466 Send(0, data, true);
1469 void Client::clickActiveObject(u8 button, u16 id, u16 item)
1471 if(connectedAndInitialized() == false){
1472 dout_client<<DTIME<<"Client::clickActiveObject() "
1473 "cancelled (not connected)"
1481 [2] u8 button (0=left, 1=right)
1485 u8 datasize = 2 + 1 + 6 + 2 + 2;
1486 SharedBuffer<u8> data(datasize);
1487 writeU16(&data[0], TOSERVER_CLICK_ACTIVEOBJECT);
1488 writeU8(&data[2], button);
1489 writeU16(&data[3], id);
1490 writeU16(&data[5], item);
1491 Send(0, data, true);
1494 void Client::sendSignText(v3s16 blockpos, s16 id, std::string text)
1503 std::ostringstream os(std::ios_base::binary);
1507 writeU16(buf, TOSERVER_SIGNTEXT);
1508 os.write((char*)buf, 2);
1511 writeV3S16(buf, blockpos);
1512 os.write((char*)buf, 6);
1516 os.write((char*)buf, 2);
1518 u16 textlen = text.size();
1519 // Write text length
1520 writeS16(buf, textlen);
1521 os.write((char*)buf, 2);
1524 os.write((char*)text.c_str(), textlen);
1527 std::string s = os.str();
1528 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1530 Send(0, data, true);
1533 void Client::sendSignNodeText(v3s16 p, std::string text)
1541 std::ostringstream os(std::ios_base::binary);
1545 writeU16(buf, TOSERVER_SIGNNODETEXT);
1546 os.write((char*)buf, 2);
1550 os.write((char*)buf, 6);
1552 u16 textlen = text.size();
1553 // Write text length
1554 writeS16(buf, textlen);
1555 os.write((char*)buf, 2);
1558 os.write((char*)text.c_str(), textlen);
1561 std::string s = os.str();
1562 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1564 Send(0, data, true);
1567 void Client::sendInventoryAction(InventoryAction *a)
1569 std::ostringstream os(std::ios_base::binary);
1573 writeU16(buf, TOSERVER_INVENTORY_ACTION);
1574 os.write((char*)buf, 2);
1579 std::string s = os.str();
1580 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1582 Send(0, data, true);
1585 void Client::sendChatMessage(const std::wstring &message)
1587 std::ostringstream os(std::ios_base::binary);
1591 writeU16(buf, TOSERVER_CHAT_MESSAGE);
1592 os.write((char*)buf, 2);
1595 writeU16(buf, message.size());
1596 os.write((char*)buf, 2);
1599 for(u32 i=0; i<message.size(); i++)
1603 os.write((char*)buf, 2);
1607 std::string s = os.str();
1608 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1610 Send(0, data, true);
1613 void Client::sendDamage(u8 damage)
1615 DSTACK(__FUNCTION_NAME);
1616 std::ostringstream os(std::ios_base::binary);
1618 writeU16(os, TOSERVER_DAMAGE);
1619 writeU8(os, damage);
1622 std::string s = os.str();
1623 SharedBuffer<u8> data((u8*)s.c_str(), s.size());
1625 Send(0, data, true);
1628 void Client::sendPlayerPos()
1630 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1632 Player *myplayer = m_env.getLocalPlayer();
1633 if(myplayer == NULL)
1638 //JMutexAutoLock lock(m_con_mutex); //bulk comment-out
1639 our_peer_id = m_con.GetPeerID();
1642 // Set peer id if not set already
1643 if(myplayer->peer_id == PEER_ID_INEXISTENT)
1644 myplayer->peer_id = our_peer_id;
1645 // Check that an existing peer_id is the same as the connection's
1646 assert(myplayer->peer_id == our_peer_id);
1648 v3f pf = myplayer->getPosition();
1649 v3s32 position(pf.X*100, pf.Y*100, pf.Z*100);
1650 v3f sf = myplayer->getSpeed();
1651 v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100);
1652 s32 pitch = myplayer->getPitch() * 100;
1653 s32 yaw = myplayer->getYaw() * 100;
1658 [2] v3s32 position*100
1659 [2+12] v3s32 speed*100
1660 [2+12+12] s32 pitch*100
1661 [2+12+12+4] s32 yaw*100
1664 SharedBuffer<u8> data(2+12+12+4+4);
1665 writeU16(&data[0], TOSERVER_PLAYERPOS);
1666 writeV3S32(&data[2], position);
1667 writeV3S32(&data[2+12], speed);
1668 writeS32(&data[2+12+12], pitch);
1669 writeS32(&data[2+12+12+4], yaw);
1671 // Send as unreliable
1672 Send(0, data, false);
1675 void Client::removeNode(v3s16 p)
1677 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1679 core::map<v3s16, MapBlock*> modified_blocks;
1683 //TimeTaker t("removeNodeAndUpdate", m_device);
1684 m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
1686 catch(InvalidPositionException &e)
1690 for(core::map<v3s16, MapBlock * >::Iterator
1691 i = modified_blocks.getIterator();
1692 i.atEnd() == false; i++)
1694 v3s16 p = i.getNode()->getKey();
1695 //m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
1696 addUpdateMeshTaskWithEdge(p);
1700 void Client::addNode(v3s16 p, MapNode n)
1702 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1704 TimeTaker timer1("Client::addNode()");
1706 core::map<v3s16, MapBlock*> modified_blocks;
1710 TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
1711 m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
1713 catch(InvalidPositionException &e)
1716 //TimeTaker timer2("Client::addNode(): updateMeshes");
1718 for(core::map<v3s16, MapBlock * >::Iterator
1719 i = modified_blocks.getIterator();
1720 i.atEnd() == false; i++)
1722 v3s16 p = i.getNode()->getKey();
1723 //m_env.getClientMap().updateMeshes(p, m_env.getDayNightRatio());
1724 addUpdateMeshTaskWithEdge(p);
1728 void Client::updateCamera(v3f pos, v3f dir)
1730 m_env.getClientMap().updateCamera(pos, dir);
1731 camera_position = pos;
1732 camera_direction = dir;
1735 MapNode Client::getNode(v3s16 p)
1737 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1738 return m_env.getMap().getNode(p);
1741 NodeMetadata* Client::getNodeMetadata(v3s16 p)
1743 return m_env.getMap().getNodeMetadata(p);
1746 v3f Client::getPlayerPosition()
1748 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1749 LocalPlayer *player = m_env.getLocalPlayer();
1750 assert(player != NULL);
1751 return player->getPosition();
1754 void Client::setPlayerControl(PlayerControl &control)
1756 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1757 LocalPlayer *player = m_env.getLocalPlayer();
1758 assert(player != NULL);
1759 player->control = control;
1762 // Returns true if the inventory of the local player has been
1763 // updated from the server. If it is true, it is set to false.
1764 bool Client::getLocalInventoryUpdated()
1766 // m_inventory_updated is behind envlock
1767 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1768 bool updated = m_inventory_updated;
1769 m_inventory_updated = false;
1773 // Copies the inventory of the local player to parameter
1774 void Client::getLocalInventory(Inventory &dst)
1776 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1777 Player *player = m_env.getLocalPlayer();
1778 assert(player != NULL);
1779 dst = player->inventory;
1782 InventoryContext *Client::getInventoryContext()
1784 return &m_inventory_context;
1787 Inventory* Client::getInventory(InventoryContext *c, std::string id)
1789 if(id == "current_player")
1791 assert(c->current_player);
1792 return &(c->current_player->inventory);
1796 std::string id0 = fn.next(":");
1798 if(id0 == "nodemeta")
1801 p.X = stoi(fn.next(","));
1802 p.Y = stoi(fn.next(","));
1803 p.Z = stoi(fn.next(","));
1804 NodeMetadata* meta = getNodeMetadata(p);
1806 return meta->getInventory();
1807 dstream<<"nodemeta at ("<<p.X<<","<<p.Y<<","<<p.Z<<"): "
1808 <<"no metadata found"<<std::endl;
1812 dstream<<__FUNCTION_NAME<<": unknown id "<<id<<std::endl;
1815 void Client::inventoryAction(InventoryAction *a)
1817 sendInventoryAction(a);
1820 MapBlockObject * Client::getSelectedObject(
1822 v3f from_pos_f_on_map,
1823 core::line3d<f32> shootline_on_map
1826 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1828 core::array<DistanceSortedObject> objects;
1830 for(core::map<v3s16, bool>::Iterator
1831 i = m_active_blocks.getIterator();
1832 i.atEnd() == false; i++)
1834 v3s16 p = i.getNode()->getKey();
1836 MapBlock *block = NULL;
1839 block = m_env.getMap().getBlockNoCreate(p);
1841 catch(InvalidPositionException &e)
1846 // Calculate from_pos relative to block
1847 v3s16 block_pos_i_on_map = block->getPosRelative();
1848 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
1849 v3f from_pos_f_on_block = from_pos_f_on_map - block_pos_f_on_map;
1851 block->getObjects(from_pos_f_on_block, max_d, objects);
1852 //block->getPseudoObjects(from_pos_f_on_block, max_d, objects);
1855 //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
1858 // After this, the closest object is the first in the array.
1861 for(u32 i=0; i<objects.size(); i++)
1863 MapBlockObject *obj = objects[i].obj;
1864 MapBlock *block = obj->getBlock();
1866 // Calculate shootline relative to block
1867 v3s16 block_pos_i_on_map = block->getPosRelative();
1868 v3f block_pos_f_on_map = intToFloat(block_pos_i_on_map, BS);
1869 core::line3d<f32> shootline_on_block(
1870 shootline_on_map.start - block_pos_f_on_map,
1871 shootline_on_map.end - block_pos_f_on_map
1874 if(obj->isSelected(shootline_on_block))
1876 //dstream<<"Returning selected object"<<std::endl;
1881 //dstream<<"No object selected; returning NULL."<<std::endl;
1885 ClientActiveObject * Client::getSelectedActiveObject(
1887 v3f from_pos_f_on_map,
1888 core::line3d<f32> shootline_on_map
1891 core::array<DistanceSortedActiveObject> objects;
1893 m_env.getActiveObjects(from_pos_f_on_map, max_d, objects);
1895 //dstream<<"Collected "<<objects.size()<<" nearby objects"<<std::endl;
1898 // After this, the closest object is the first in the array.
1901 for(u32 i=0; i<objects.size(); i++)
1903 ClientActiveObject *obj = objects[i].obj;
1905 core::aabbox3d<f32> *selection_box = obj->getSelectionBox();
1906 if(selection_box == NULL)
1909 v3f pos = obj->getPosition();
1911 core::aabbox3d<f32> offsetted_box(
1912 selection_box->MinEdge + pos,
1913 selection_box->MaxEdge + pos
1916 if(offsetted_box.intersectsWithLine(shootline_on_map))
1918 //dstream<<"Returning selected object"<<std::endl;
1923 //dstream<<"No object selected; returning NULL."<<std::endl;
1927 void Client::printDebugInfo(std::ostream &os)
1929 //JMutexAutoLock lock1(m_fetchblock_mutex);
1930 /*JMutexAutoLock lock2(m_incoming_queue_mutex);
1932 os<<"m_incoming_queue.getSize()="<<m_incoming_queue.getSize()
1933 //<<", m_fetchblock_history.size()="<<m_fetchblock_history.size()
1934 //<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
1938 /*s32 Client::getDayNightIndex()
1940 assert(m_daynight_i >= 0 && m_daynight_i < DAYNIGHT_CACHE_COUNT);
1941 return m_daynight_i;
1944 u32 Client::getDayNightRatio()
1946 //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
1947 return m_env.getDayNightRatio();
1952 Player *player = m_env.getLocalPlayer();
1953 assert(player != NULL);
1957 void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
1959 /*dstream<<"Client::addUpdateMeshTask(): "
1960 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
1963 MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
1968 Create a task to update the mesh of the block
1971 MeshMakeData *data = new MeshMakeData;
1974 //TimeTaker timer("data fill");
1976 data->fill(getDayNightRatio(), b);
1980 //while(m_mesh_update_thread.m_queue_in.size() > 0) sleep_ms(10);
1982 // Add task to queue
1983 m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server);
1985 /*dstream<<"Mesh update input queue size is "
1986 <<m_mesh_update_thread.m_queue_in.size()
1990 // Temporary test: make mesh directly in here
1992 //TimeTaker timer("make mesh");
1994 scene::SMesh *mesh_new = NULL;
1995 mesh_new = makeMapBlockMesh(data);
1996 b->replaceMesh(mesh_new);
2001 b->setMeshExpired(false);
2004 void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server)
2008 dstream<<"Client::addUpdateMeshTaskWithEdge(): "
2009 <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
2014 v3s16 p = blockpos + v3s16(0,0,0);
2015 //MapBlock *b = m_env.getMap().getBlockNoCreate(p);
2016 addUpdateMeshTask(p, ack_to_server);
2018 catch(InvalidPositionException &e){}
2021 v3s16 p = blockpos + v3s16(-1,0,0);
2022 addUpdateMeshTask(p);
2024 catch(InvalidPositionException &e){}
2026 v3s16 p = blockpos + v3s16(0,-1,0);
2027 addUpdateMeshTask(p);
2029 catch(InvalidPositionException &e){}
2031 v3s16 p = blockpos + v3s16(0,0,-1);
2032 addUpdateMeshTask(p);
2034 catch(InvalidPositionException &e){}
2037 ClientEvent Client::getClientEvent()
2039 if(m_client_event_queue.size() == 0)
2042 event.type = CE_NONE;
2045 return m_client_event_queue.pop_front();