3 Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser 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.
20 #include "client/client.h"
22 #include "util/base64.h"
23 #include "chatmessage.h"
24 #include "client/clientmedia.h"
27 #include "mapsector.h"
28 #include "client/minimap.h"
29 #include "modchannels.h"
31 #include "serialization.h"
33 #include "util/strfnd.h"
34 #include "client/clientevent.h"
35 #include "client/sound.h"
36 #include "network/clientopcodes.h"
37 #include "network/connection.h"
38 #include "script/scripting_client.h"
39 #include "util/serialize.h"
41 #include "tileanimation.h"
44 void Client::handleCommand_Deprecated(NetworkPacket* pkt)
46 infostream << "Got deprecated command "
47 << toClientCommandTable[pkt->getCommand()].name << " from peer "
48 << pkt->getPeerId() << "!" << std::endl;
51 void Client::handleCommand_Hello(NetworkPacket* pkt)
53 if (pkt->getSize() < 1)
60 std::string username_legacy; // for case insensitivity
61 *pkt >> serialization_ver >> compression_mode >> proto_ver
62 >> auth_mechs >> username_legacy;
64 // Chose an auth method we support
65 AuthMechanism chosen_auth_mechanism = choseAuthMech(auth_mechs);
67 infostream << "Client: TOCLIENT_HELLO received with "
68 << "serialization_ver=" << (u32)serialization_ver
69 << ", auth_mechs=" << auth_mechs
70 << ", proto_ver=" << proto_ver
71 << ", compression_mode=" << compression_mode
72 << ". Doing auth with mech " << chosen_auth_mechanism << std::endl;
74 if (!ser_ver_supported(serialization_ver)) {
75 infostream << "Client: TOCLIENT_HELLO: Server sent "
76 << "unsupported ser_fmt_ver"<< std::endl;
80 m_server_ser_ver = serialization_ver;
81 m_proto_ver = proto_ver;
83 //TODO verify that username_legacy matches sent username, only
84 // differs in casing (make both uppercase and compare)
85 // This is only neccessary though when we actually want to add casing support
87 if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) {
88 // we recieved a TOCLIENT_HELLO while auth was already going on
89 errorstream << "Client: TOCLIENT_HELLO while auth was already going on"
90 << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl;
91 if (m_chosen_auth_mech == AUTH_MECHANISM_SRP ||
92 m_chosen_auth_mech == AUTH_MECHANISM_LEGACY_PASSWORD) {
93 srp_user_delete((SRPUser *) m_auth_data);
98 // Authenticate using that method, or abort if there wasn't any method found
99 if (chosen_auth_mechanism != AUTH_MECHANISM_NONE) {
100 if (chosen_auth_mechanism == AUTH_MECHANISM_FIRST_SRP &&
101 !m_simple_singleplayer_mode &&
102 !getServerAddress().isLocalhost() &&
103 g_settings->getBool("enable_register_confirmation")) {
104 promptConfirmRegistration(chosen_auth_mechanism);
106 startAuth(chosen_auth_mechanism);
109 m_chosen_auth_mech = AUTH_MECHANISM_NONE;
110 m_access_denied = true;
111 m_access_denied_reason = "Unknown";
117 void Client::handleCommand_AuthAccept(NetworkPacket* pkt)
122 *pkt >> playerpos >> m_map_seed >> m_recommended_send_interval
123 >> m_sudo_auth_methods;
125 playerpos -= v3f(0, BS / 2, 0);
127 // Set player position
128 LocalPlayer *player = m_env.getLocalPlayer();
129 assert(player != NULL);
130 player->setPosition(playerpos);
132 infostream << "Client: received map seed: " << m_map_seed << std::endl;
133 infostream << "Client: received recommended send interval "
134 << m_recommended_send_interval<<std::endl;
137 std::string lang = gettext("LANG_CODE");
138 if (lang == "LANG_CODE")
141 NetworkPacket resp_pkt(TOSERVER_INIT2, sizeof(u16) + lang.size());
147 void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt)
151 m_password = m_new_password;
153 verbosestream << "Client: Recieved TOCLIENT_ACCEPT_SUDO_MODE." << std::endl;
155 // send packet to actually set the password
156 startAuth(AUTH_MECHANISM_FIRST_SRP);
159 m_chosen_auth_mech = AUTH_MECHANISM_NONE;
161 void Client::handleCommand_DenySudoMode(NetworkPacket* pkt)
163 ChatMessage *chatMessage = new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
164 L"Password change denied. Password NOT changed.");
165 pushToChatQueue(chatMessage);
166 // reset everything and be sad
170 void Client::handleCommand_AccessDenied(NetworkPacket* pkt)
172 // The server didn't like our password. Note, this needs
173 // to be processed even if the serialisation format has
174 // not been agreed yet, the same as TOCLIENT_INIT.
175 m_access_denied = true;
176 m_access_denied_reason = "Unknown";
178 if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) {
179 // 13/03/15 Legacy code from 0.4.12 and lesser but is still used
180 // in some places of the server code
181 if (pkt->getSize() >= 2) {
182 std::wstring wide_reason;
184 m_access_denied_reason = wide_to_utf8(wide_reason);
189 if (pkt->getSize() < 1)
192 u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA;
194 if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN ||
195 denyCode == SERVER_ACCESSDENIED_CRASH) {
196 *pkt >> m_access_denied_reason;
197 if (m_access_denied_reason.empty()) {
198 m_access_denied_reason = accessDeniedStrings[denyCode];
202 m_access_denied_reconnect = reconnect & 1;
203 } else if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) {
204 *pkt >> m_access_denied_reason;
205 } else if (denyCode < SERVER_ACCESSDENIED_MAX) {
206 m_access_denied_reason = accessDeniedStrings[denyCode];
208 // Allow us to add new error messages to the
209 // protocol without raising the protocol version, if we want to.
210 // Until then (which may be never), this is outside
211 // of the defined protocol.
212 *pkt >> m_access_denied_reason;
213 if (m_access_denied_reason.empty()) {
214 m_access_denied_reason = "Unknown";
219 void Client::handleCommand_RemoveNode(NetworkPacket* pkt)
221 if (pkt->getSize() < 6)
229 void Client::handleCommand_AddNode(NetworkPacket* pkt)
231 if (pkt->getSize() < 6 + MapNode::serializedLength(m_server_ser_ver))
238 n.deSerialize(pkt->getU8Ptr(6), m_server_ser_ver);
240 bool remove_metadata = true;
241 u32 index = 6 + MapNode::serializedLength(m_server_ser_ver);
242 if ((pkt->getSize() >= index + 1) && pkt->getU8(index)) {
243 remove_metadata = false;
246 addNode(p, n, remove_metadata);
249 void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt)
251 if (pkt->getSize() < 1)
254 std::istringstream is(pkt->readLongString(), std::ios::binary);
255 std::stringstream sstr;
256 decompressZlib(is, sstr);
258 NodeMetadataList meta_updates_list(false);
259 meta_updates_list.deSerialize(sstr, m_itemdef, true);
261 Map &map = m_env.getMap();
262 for (NodeMetadataMap::const_iterator i = meta_updates_list.begin();
263 i != meta_updates_list.end(); ++i) {
264 v3s16 pos = i->first;
266 if (map.isValidPosition(pos) &&
267 map.setNodeMetadata(pos, i->second))
268 continue; // Prevent from deleting metadata
270 // Meta couldn't be set, unused metadata
275 void Client::handleCommand_BlockData(NetworkPacket* pkt)
277 // Ignore too small packet
278 if (pkt->getSize() < 6)
284 std::string datastring(pkt->getString(6), pkt->getSize() - 6);
285 std::istringstream istr(datastring, std::ios_base::binary);
291 sector = m_env.getMap().emergeSector(p2d);
293 assert(sector->getPos() == p2d);
295 block = sector->getBlockNoCreateNoEx(p.Y);
298 Update an existing block
300 block->deSerialize(istr, m_server_ser_ver, false);
301 block->deSerializeNetworkSpecific(istr);
307 block = new MapBlock(&m_env.getMap(), p, this);
308 block->deSerialize(istr, m_server_ser_ver, false);
309 block->deSerializeNetworkSpecific(istr);
310 sector->insertBlock(block);
314 ServerMap::saveBlock(block, m_localdb);
318 Add it to mesh update queue and set it to be acknowledged after update.
320 addUpdateMeshTaskWithEdge(p, true);
323 void Client::handleCommand_Inventory(NetworkPacket* pkt)
325 if (pkt->getSize() < 1)
328 std::string datastring(pkt->getString(0), pkt->getSize());
329 std::istringstream is(datastring, std::ios_base::binary);
331 LocalPlayer *player = m_env.getLocalPlayer();
332 assert(player != NULL);
334 player->inventory.deSerialize(is);
336 m_inventory_updated = true;
338 delete m_inventory_from_server;
339 m_inventory_from_server = new Inventory(player->inventory);
340 m_inventory_from_server_age = 0.0;
343 void Client::handleCommand_TimeOfDay(NetworkPacket* pkt)
345 if (pkt->getSize() < 2)
352 time_of_day = time_of_day % 24000;
353 float time_speed = 0;
355 if (pkt->getSize() >= 2 + 4) {
359 // Old message; try to approximate speed of time by ourselves
360 float time_of_day_f = (float)time_of_day / 24000.0f;
361 float tod_diff_f = 0;
363 if (time_of_day_f < 0.2 && m_last_time_of_day_f > 0.8)
364 tod_diff_f = time_of_day_f - m_last_time_of_day_f + 1.0f;
366 tod_diff_f = time_of_day_f - m_last_time_of_day_f;
368 m_last_time_of_day_f = time_of_day_f;
369 float time_diff = m_time_of_day_update_timer;
370 m_time_of_day_update_timer = 0;
372 if (m_time_of_day_set) {
373 time_speed = (3600.0f * 24.0f) * tod_diff_f / time_diff;
374 infostream << "Client: Measured time_of_day speed (old format): "
375 << time_speed << " tod_diff_f=" << tod_diff_f
376 << " time_diff=" << time_diff << std::endl;
380 // Update environment
381 m_env.setTimeOfDay(time_of_day);
382 m_env.setTimeOfDaySpeed(time_speed);
383 m_time_of_day_set = true;
385 u32 dr = m_env.getDayNightRatio();
386 infostream << "Client: time_of_day=" << time_of_day
387 << " time_speed=" << time_speed
388 << " dr=" << dr << std::endl;
391 void Client::handleCommand_ChatMessage(NetworkPacket *pkt)
396 u16 sendername length
402 ChatMessage *chatMessage = new ChatMessage();
403 u8 version, message_type;
404 *pkt >> version >> message_type;
406 if (version != 1 || message_type >= CHATMESSAGE_TYPE_MAX) {
411 *pkt >> chatMessage->sender >> chatMessage->message >> chatMessage->timestamp;
413 chatMessage->type = (ChatMessageType) message_type;
415 // @TODO send this to CSM using ChatMessage object
416 if (moddingEnabled() && m_script->on_receiving_message(
417 wide_to_utf8(chatMessage->message))) {
418 // Message was consumed by CSM and should not be handled by client
421 pushToChatQueue(chatMessage);
425 void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt)
428 u16 count of removed objects
429 for all removed objects {
432 u16 count of added objects
433 for all added objects {
436 u32 initialization data length
437 string initialization data
443 u16 removed_count, added_count, id;
445 // Read removed objects
446 *pkt >> removed_count;
448 for (u16 i = 0; i < removed_count; i++) {
450 m_env.removeActiveObject(id);
453 // Read added objects
456 for (u16 i = 0; i < added_count; i++) {
458 m_env.addActiveObject(id, type, pkt->readLongString());
460 } catch (PacketError &e) {
461 infostream << "handleCommand_ActiveObjectRemoveAdd: " << e.what()
462 << ". The packet is unreliable, ignoring" << std::endl;
466 void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt)
476 std::string datastring(pkt->getString(0), pkt->getSize());
477 std::istringstream is(datastring, std::ios_base::binary);
481 u16 id = readU16(is);
485 std::string message = deSerializeString(is);
487 // Pass on to the environment
488 m_env.processActiveObjectMessage(id, message);
490 } catch (SerializationError &e) {
491 errorstream << "Client::handleCommand_ActiveObjectMessages: "
492 << "caught SerializationError: " << e.what() << std::endl;
496 void Client::handleCommand_Movement(NetworkPacket* pkt)
498 LocalPlayer *player = m_env.getLocalPlayer();
499 assert(player != NULL);
501 float mad, maa, maf, msw, mscr, msf, mscl, msj, lf, lfs, ls, g;
503 *pkt >> mad >> maa >> maf >> msw >> mscr >> msf >> mscl >> msj
504 >> lf >> lfs >> ls >> g;
506 player->movement_acceleration_default = mad * BS;
507 player->movement_acceleration_air = maa * BS;
508 player->movement_acceleration_fast = maf * BS;
509 player->movement_speed_walk = msw * BS;
510 player->movement_speed_crouch = mscr * BS;
511 player->movement_speed_fast = msf * BS;
512 player->movement_speed_climb = mscl * BS;
513 player->movement_speed_jump = msj * BS;
514 player->movement_liquid_fluidity = lf * BS;
515 player->movement_liquid_fluidity_smooth = lfs * BS;
516 player->movement_liquid_sink = ls * BS;
517 player->movement_gravity = g * BS;
520 void Client::handleCommand_HP(NetworkPacket* pkt)
523 LocalPlayer *player = m_env.getLocalPlayer();
524 assert(player != NULL);
526 u16 oldhp = player->hp;
533 if (moddingEnabled()) {
534 m_script->on_hp_modification(hp);
538 // Add to ClientEvent queue
539 ClientEvent *event = new ClientEvent();
540 event->type = CE_PLAYER_DAMAGE;
541 event->player_damage.amount = oldhp - hp;
542 m_client_event_queue.push(event);
546 void Client::handleCommand_Breath(NetworkPacket* pkt)
548 LocalPlayer *player = m_env.getLocalPlayer();
549 assert(player != NULL);
555 player->setBreath(breath);
558 void Client::handleCommand_MovePlayer(NetworkPacket* pkt)
560 LocalPlayer *player = m_env.getLocalPlayer();
561 assert(player != NULL);
566 *pkt >> pos >> pitch >> yaw;
568 player->setPosition(pos);
570 infostream << "Client got TOCLIENT_MOVE_PLAYER"
571 << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")"
572 << " pitch=" << pitch
577 Add to ClientEvent queue.
578 This has to be sent to the main program because otherwise
579 it would just force the pitch and yaw values to whatever
580 the camera points to.
582 ClientEvent *event = new ClientEvent();
583 event->type = CE_PLAYER_FORCE_MOVE;
584 event->player_force_move.pitch = pitch;
585 event->player_force_move.yaw = yaw;
586 m_client_event_queue.push(event);
589 void Client::handleCommand_DeathScreen(NetworkPacket* pkt)
591 bool set_camera_point_target;
592 v3f camera_point_target;
594 *pkt >> set_camera_point_target;
595 *pkt >> camera_point_target;
597 ClientEvent *event = new ClientEvent();
598 event->type = CE_DEATHSCREEN;
599 event->deathscreen.set_camera_point_target = set_camera_point_target;
600 event->deathscreen.camera_point_target_x = camera_point_target.X;
601 event->deathscreen.camera_point_target_y = camera_point_target.Y;
602 event->deathscreen.camera_point_target_z = camera_point_target.Z;
603 m_client_event_queue.push(event);
606 void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
612 infostream << "Client: Received media announcement: packet size: "
613 << pkt->getSize() << std::endl;
615 if (m_media_downloader == NULL ||
616 m_media_downloader->isStarted()) {
617 const char *problem = m_media_downloader ?
618 "we already saw another announcement" :
619 "all media has been received already";
620 errorstream << "Client: Received media announcement but "
622 << " files=" << num_files
623 << " size=" << pkt->getSize() << std::endl;
627 // Mesh update thread must be stopped while
628 // updating content definitions
629 sanity_check(!m_mesh_update_thread.isRunning());
631 for (u16 i = 0; i < num_files; i++) {
632 std::string name, sha1_base64;
634 *pkt >> name >> sha1_base64;
636 std::string sha1_raw = base64_decode(sha1_base64);
637 m_media_downloader->addFile(name, sha1_raw);
646 while(!sf.at_end()) {
647 std::string baseurl = trim(sf.next(","));
648 if (!baseurl.empty())
649 m_media_downloader->addRemoteServer(baseurl);
652 catch(SerializationError& e) {
653 // not supported by server or turned off
656 m_media_downloader->step(this);
659 void Client::handleCommand_Media(NetworkPacket* pkt)
663 u16 total number of file bunches
664 u16 index of this bunch
665 u32 number of files in this bunch
677 *pkt >> num_bunches >> bunch_i >> num_files;
679 infostream << "Client: Received files: bunch " << bunch_i << "/"
680 << num_bunches << " files=" << num_files
681 << " size=" << pkt->getSize() << std::endl;
686 if (!m_media_downloader || !m_media_downloader->isStarted()) {
687 const char *problem = m_media_downloader ?
688 "media has not been requested" :
689 "all media has been received already";
690 errorstream << "Client: Received media but "
692 << " bunch " << bunch_i << "/" << num_bunches
693 << " files=" << num_files
694 << " size=" << pkt->getSize() << std::endl;
698 // Mesh update thread must be stopped while
699 // updating content definitions
700 sanity_check(!m_mesh_update_thread.isRunning());
702 for (u32 i=0; i < num_files; i++) {
707 std::string data = pkt->readLongString();
709 m_media_downloader->conventionalTransferDone(
714 void Client::handleCommand_NodeDef(NetworkPacket* pkt)
716 infostream << "Client: Received node definitions: packet size: "
717 << pkt->getSize() << std::endl;
719 // Mesh update thread must be stopped while
720 // updating content definitions
721 sanity_check(!m_mesh_update_thread.isRunning());
723 // Decompress node definitions
724 std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
725 std::ostringstream tmp_os;
726 decompressZlib(tmp_is, tmp_os);
728 // Deserialize node definitions
729 std::istringstream tmp_is2(tmp_os.str());
730 m_nodedef->deSerialize(tmp_is2);
731 m_nodedef_received = true;
734 void Client::handleCommand_ItemDef(NetworkPacket* pkt)
736 infostream << "Client: Received item definitions: packet size: "
737 << pkt->getSize() << std::endl;
739 // Mesh update thread must be stopped while
740 // updating content definitions
741 sanity_check(!m_mesh_update_thread.isRunning());
743 // Decompress item definitions
744 std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
745 std::ostringstream tmp_os;
746 decompressZlib(tmp_is, tmp_os);
748 // Deserialize node definitions
749 std::istringstream tmp_is2(tmp_os.str());
750 m_itemdef->deSerialize(tmp_is2);
751 m_itemdef_received = true;
754 void Client::handleCommand_PlaySound(NetworkPacket* pkt)
762 [11 + len] (f32 * 3) pos
763 [23 + len] u16 object_id
773 u8 type; // 0=local, 1=positional, 2=object
780 *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop;
785 } catch (PacketError &e) {};
791 client_id = m_sound->playSound(name, loop, gain, fade, pitch);
793 case 1: // positional
794 client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch);
798 ClientActiveObject *cao = m_env.getActiveObject(object_id);
800 pos = cao->getPosition();
801 client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch);
802 // TODO: Set up sound to move with object
809 if (client_id != -1) {
810 m_sounds_server_to_client[server_id] = client_id;
811 m_sounds_client_to_server[client_id] = server_id;
813 m_sounds_to_objects[client_id] = object_id;
817 void Client::handleCommand_StopSound(NetworkPacket* pkt)
823 std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.find(server_id);
824 if (i != m_sounds_server_to_client.end()) {
825 int client_id = i->second;
826 m_sound->stopSound(client_id);
830 void Client::handleCommand_FadeSound(NetworkPacket *pkt)
836 *pkt >> sound_id >> step >> gain;
838 std::unordered_map<s32, int>::const_iterator i =
839 m_sounds_server_to_client.find(sound_id);
841 if (i != m_sounds_server_to_client.end())
842 m_sound->fadeSound(i->second, step, gain);
845 void Client::handleCommand_Privileges(NetworkPacket* pkt)
847 m_privileges.clear();
848 infostream << "Client: Privileges updated: ";
851 *pkt >> num_privileges;
853 for (u16 i = 0; i < num_privileges; i++) {
858 m_privileges.insert(priv);
859 infostream << priv << " ";
861 infostream << std::endl;
864 void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt)
866 LocalPlayer *player = m_env.getLocalPlayer();
867 assert(player != NULL);
869 // Store formspec in LocalPlayer
870 player->inventory_formspec = pkt->readLongString();
873 void Client::handleCommand_DetachedInventory(NetworkPacket* pkt)
876 bool keep_inv = true;
877 *pkt >> name >> keep_inv;
879 infostream << "Client: Detached inventory update: \"" << name
880 << "\", mode=" << (keep_inv ? "update" : "remove") << std::endl;
882 const auto &inv_it = m_detached_inventories.find(name);
884 if (inv_it != m_detached_inventories.end()) {
885 delete inv_it->second;
886 m_detached_inventories.erase(inv_it);
890 Inventory *inv = nullptr;
891 if (inv_it == m_detached_inventories.end()) {
892 inv = new Inventory(m_itemdef);
893 m_detached_inventories[name] = inv;
895 inv = inv_it->second;
898 std::string contents;
900 std::istringstream is(contents, std::ios::binary);
901 inv->deSerialize(is);
904 void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt)
906 std::string formspec = pkt->readLongString();
907 std::string formname;
911 ClientEvent *event = new ClientEvent();
912 event->type = CE_SHOW_FORMSPEC;
913 // pointer is required as event is a struct only!
914 // adding a std:string to a struct isn't possible
915 event->show_formspec.formspec = new std::string(formspec);
916 event->show_formspec.formname = new std::string(formname);
917 m_client_event_queue.push(event);
920 void Client::handleCommand_SpawnParticle(NetworkPacket* pkt)
922 std::string datastring(pkt->getString(0), pkt->getSize());
923 std::istringstream is(datastring, std::ios_base::binary);
925 v3f pos = readV3F32(is);
926 v3f vel = readV3F32(is);
927 v3f acc = readV3F32(is);
928 float expirationtime = readF32(is);
929 float size = readF32(is);
930 bool collisiondetection = readU8(is);
931 std::string texture = deSerializeLongString(is);
933 bool vertical = false;
934 bool collision_removal = false;
935 TileAnimationParams animation;
936 animation.type = TAT_NONE;
938 bool object_collision = false;
940 vertical = readU8(is);
941 collision_removal = readU8(is);
942 animation.deSerialize(is, m_proto_ver);
944 object_collision = readU8(is);
947 ClientEvent *event = new ClientEvent();
948 event->type = CE_SPAWN_PARTICLE;
949 event->spawn_particle.pos = new v3f (pos);
950 event->spawn_particle.vel = new v3f (vel);
951 event->spawn_particle.acc = new v3f (acc);
952 event->spawn_particle.expirationtime = expirationtime;
953 event->spawn_particle.size = size;
954 event->spawn_particle.collisiondetection = collisiondetection;
955 event->spawn_particle.collision_removal = collision_removal;
956 event->spawn_particle.object_collision = object_collision;
957 event->spawn_particle.vertical = vertical;
958 event->spawn_particle.texture = new std::string(texture);
959 event->spawn_particle.animation = animation;
960 event->spawn_particle.glow = glow;
962 m_client_event_queue.push(event);
965 void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
979 bool collisiondetection;
982 *pkt >> amount >> spawntime >> minpos >> maxpos >> minvel >> maxvel
983 >> minacc >> maxacc >> minexptime >> maxexptime >> minsize
984 >> maxsize >> collisiondetection;
986 std::string texture = pkt->readLongString();
990 bool vertical = false;
991 bool collision_removal = false;
993 TileAnimationParams animation;
994 animation.type = TAT_NONE;
996 bool object_collision = false;
999 *pkt >> collision_removal;
1000 *pkt >> attached_id;
1002 // This is horrible but required (why are there two ways to deserialize pkts?)
1003 std::string datastring(pkt->getRemainingString(), pkt->getRemainingBytes());
1004 std::istringstream is(datastring, std::ios_base::binary);
1005 animation.deSerialize(is, m_proto_ver);
1007 object_collision = readU8(is);
1010 ClientEvent *event = new ClientEvent();
1011 event->type = CE_ADD_PARTICLESPAWNER;
1012 event->add_particlespawner.amount = amount;
1013 event->add_particlespawner.spawntime = spawntime;
1014 event->add_particlespawner.minpos = new v3f (minpos);
1015 event->add_particlespawner.maxpos = new v3f (maxpos);
1016 event->add_particlespawner.minvel = new v3f (minvel);
1017 event->add_particlespawner.maxvel = new v3f (maxvel);
1018 event->add_particlespawner.minacc = new v3f (minacc);
1019 event->add_particlespawner.maxacc = new v3f (maxacc);
1020 event->add_particlespawner.minexptime = minexptime;
1021 event->add_particlespawner.maxexptime = maxexptime;
1022 event->add_particlespawner.minsize = minsize;
1023 event->add_particlespawner.maxsize = maxsize;
1024 event->add_particlespawner.collisiondetection = collisiondetection;
1025 event->add_particlespawner.collision_removal = collision_removal;
1026 event->add_particlespawner.object_collision = object_collision;
1027 event->add_particlespawner.attached_id = attached_id;
1028 event->add_particlespawner.vertical = vertical;
1029 event->add_particlespawner.texture = new std::string(texture);
1030 event->add_particlespawner.id = id;
1031 event->add_particlespawner.animation = animation;
1032 event->add_particlespawner.glow = glow;
1034 m_client_event_queue.push(event);
1038 void Client::handleCommand_DeleteParticleSpawner(NetworkPacket* pkt)
1043 ClientEvent *event = new ClientEvent();
1044 event->type = CE_DELETE_PARTICLESPAWNER;
1045 event->delete_particlespawner.id = id;
1047 m_client_event_queue.push(event);
1050 void Client::handleCommand_HudAdd(NetworkPacket* pkt)
1052 std::string datastring(pkt->getString(0), pkt->getSize());
1053 std::istringstream is(datastring, std::ios_base::binary);
1069 *pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item
1070 >> dir >> align >> offset;
1074 catch(SerializationError &e) {};
1078 } catch(SerializationError &e) {};
1080 ClientEvent *event = new ClientEvent();
1081 event->type = CE_HUDADD;
1082 event->hudadd.server_id = server_id;
1083 event->hudadd.type = type;
1084 event->hudadd.pos = new v2f(pos);
1085 event->hudadd.name = new std::string(name);
1086 event->hudadd.scale = new v2f(scale);
1087 event->hudadd.text = new std::string(text);
1088 event->hudadd.number = number;
1089 event->hudadd.item = item;
1090 event->hudadd.dir = dir;
1091 event->hudadd.align = new v2f(align);
1092 event->hudadd.offset = new v2f(offset);
1093 event->hudadd.world_pos = new v3f(world_pos);
1094 event->hudadd.size = new v2s32(size);
1095 m_client_event_queue.push(event);
1098 void Client::handleCommand_HudRemove(NetworkPacket* pkt)
1104 auto i = m_hud_server_to_client.find(server_id);
1105 if (i != m_hud_server_to_client.end()) {
1106 int client_id = i->second;
1107 m_hud_server_to_client.erase(i);
1109 ClientEvent *event = new ClientEvent();
1110 event->type = CE_HUDRM;
1111 event->hudrm.id = client_id;
1112 m_client_event_queue.push(event);
1116 void Client::handleCommand_HudChange(NetworkPacket* pkt)
1126 *pkt >> server_id >> stat;
1128 if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE ||
1129 stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET)
1131 else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT)
1133 else if (stat == HUD_STAT_WORLD_POS)
1135 else if (stat == HUD_STAT_SIZE )
1140 std::unordered_map<u32, u32>::const_iterator i = m_hud_server_to_client.find(server_id);
1141 if (i != m_hud_server_to_client.end()) {
1142 ClientEvent *event = new ClientEvent();
1143 event->type = CE_HUDCHANGE;
1144 event->hudchange.id = i->second;
1145 event->hudchange.stat = (HudElementStat)stat;
1146 event->hudchange.v2fdata = new v2f(v2fdata);
1147 event->hudchange.v3fdata = new v3f(v3fdata);
1148 event->hudchange.sdata = new std::string(sdata);
1149 event->hudchange.data = intdata;
1150 event->hudchange.v2s32data = new v2s32(v2s32data);
1151 m_client_event_queue.push(event);
1155 void Client::handleCommand_HudSetFlags(NetworkPacket* pkt)
1159 *pkt >> flags >> mask;
1161 LocalPlayer *player = m_env.getLocalPlayer();
1162 assert(player != NULL);
1164 bool was_minimap_visible = player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE;
1165 bool was_minimap_radar_visible = player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE;
1167 player->hud_flags &= ~mask;
1168 player->hud_flags |= flags;
1170 m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE);
1171 bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE);
1173 // Hide minimap if it has been disabled by the server
1174 if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible)
1175 // defers a minimap update, therefore only call it if really
1176 // needed, by checking that minimap was visible before
1177 m_minimap->setMinimapMode(MINIMAP_MODE_OFF);
1179 // Switch to surface mode if radar disabled by server
1180 if (m_minimap && m_minimap_radar_disabled_by_server && was_minimap_radar_visible)
1181 m_minimap->setMinimapMode(MINIMAP_MODE_SURFACEx1);
1184 void Client::handleCommand_HudSetParam(NetworkPacket* pkt)
1186 u16 param; std::string value;
1188 *pkt >> param >> value;
1190 LocalPlayer *player = m_env.getLocalPlayer();
1191 assert(player != NULL);
1193 if (param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4) {
1194 s32 hotbar_itemcount = readS32((u8*) value.c_str());
1195 if (hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX)
1196 player->hud_hotbar_itemcount = hotbar_itemcount;
1198 else if (param == HUD_PARAM_HOTBAR_IMAGE) {
1199 // If value not empty verify image exists in texture source
1200 if (!value.empty() && !getTextureSource()->isKnownSourceImage(value)) {
1201 errorstream << "Server sent wrong Hud hotbar image (sent value: '"
1202 << value << "')" << std::endl;
1205 player->hotbar_image = value;
1207 else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) {
1208 // If value not empty verify image exists in texture source
1209 if (!value.empty() && !getTextureSource()->isKnownSourceImage(value)) {
1210 errorstream << "Server sent wrong Hud hotbar selected image (sent value: '"
1211 << value << "')" << std::endl;
1214 player->hotbar_selected_image = value;
1218 void Client::handleCommand_HudSetSky(NetworkPacket* pkt)
1220 std::string datastring(pkt->getString(0), pkt->getSize());
1221 std::istringstream is(datastring, std::ios_base::binary);
1223 video::SColor *bgcolor = new video::SColor(readARGB8(is));
1224 std::string *type = new std::string(deSerializeString(is));
1225 u16 count = readU16(is);
1226 std::vector<std::string> *params = new std::vector<std::string>;
1228 for (size_t i = 0; i < count; i++)
1229 params->push_back(deSerializeString(is));
1233 clouds = readU8(is);
1236 ClientEvent *event = new ClientEvent();
1237 event->type = CE_SET_SKY;
1238 event->set_sky.bgcolor = bgcolor;
1239 event->set_sky.type = type;
1240 event->set_sky.params = params;
1241 event->set_sky.clouds = clouds;
1242 m_client_event_queue.push(event);
1245 void Client::handleCommand_CloudParams(NetworkPacket* pkt)
1248 video::SColor color_bright;
1249 video::SColor color_ambient;
1254 *pkt >> density >> color_bright >> color_ambient
1255 >> height >> thickness >> speed;
1257 ClientEvent *event = new ClientEvent();
1258 event->type = CE_CLOUD_PARAMS;
1259 event->cloud_params.density = density;
1260 // use the underlying u32 representation, because we can't
1261 // use struct members with constructors here, and this way
1262 // we avoid using new() and delete() for no good reason
1263 event->cloud_params.color_bright = color_bright.color;
1264 event->cloud_params.color_ambient = color_ambient.color;
1265 event->cloud_params.height = height;
1266 event->cloud_params.thickness = thickness;
1267 // same here: deconstruct to skip constructor
1268 event->cloud_params.speed_x = speed.X;
1269 event->cloud_params.speed_y = speed.Y;
1270 m_client_event_queue.push(event);
1273 void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt)
1276 u16 day_night_ratio_u;
1278 *pkt >> do_override >> day_night_ratio_u;
1280 float day_night_ratio_f = (float)day_night_ratio_u / 65536;
1282 ClientEvent *event = new ClientEvent();
1283 event->type = CE_OVERRIDE_DAY_NIGHT_RATIO;
1284 event->override_day_night_ratio.do_override = do_override;
1285 event->override_day_night_ratio.ratio_f = day_night_ratio_f;
1286 m_client_event_queue.push(event);
1289 void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt)
1291 LocalPlayer *player = m_env.getLocalPlayer();
1292 assert(player != NULL);
1294 *pkt >> player->local_animations[0];
1295 *pkt >> player->local_animations[1];
1296 *pkt >> player->local_animations[2];
1297 *pkt >> player->local_animations[3];
1298 *pkt >> player->local_animation_speed;
1301 void Client::handleCommand_EyeOffset(NetworkPacket* pkt)
1303 LocalPlayer *player = m_env.getLocalPlayer();
1304 assert(player != NULL);
1306 *pkt >> player->eye_offset_first >> player->eye_offset_third;
1309 void Client::handleCommand_UpdatePlayerList(NetworkPacket* pkt)
1313 *pkt >> type >> num_players;
1314 PlayerListModifer notice_type = (PlayerListModifer) type;
1316 for (u16 i = 0; i < num_players; i++) {
1319 switch (notice_type) {
1320 case PLAYER_LIST_INIT:
1321 case PLAYER_LIST_ADD:
1322 m_env.addPlayerName(name);
1324 case PLAYER_LIST_REMOVE:
1325 m_env.removePlayerName(name);
1331 void Client::handleCommand_SrpBytesSandB(NetworkPacket* pkt)
1333 if (m_chosen_auth_mech != AUTH_MECHANISM_SRP &&
1334 m_chosen_auth_mech != AUTH_MECHANISM_LEGACY_PASSWORD) {
1335 errorstream << "Client: Received SRP S_B login message,"
1336 << " but wasn't supposed to (chosen_mech="
1337 << m_chosen_auth_mech << ")." << std::endl;
1343 SRPUser *usr = (SRPUser *) m_auth_data;
1348 infostream << "Client: Received TOCLIENT_SRP_BYTES_S_B." << std::endl;
1350 srp_user_process_challenge(usr, (const unsigned char *) s.c_str(), s.size(),
1351 (const unsigned char *) B.c_str(), B.size(),
1352 (unsigned char **) &bytes_M, &len_M);
1355 errorstream << "Client: SRP-6a S_B safety check violation!" << std::endl;
1359 NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_M, 0);
1360 resp_pkt << std::string(bytes_M, len_M);
1364 void Client::handleCommand_FormspecPrepend(NetworkPacket *pkt)
1366 LocalPlayer *player = m_env.getLocalPlayer();
1367 assert(player != NULL);
1369 // Store formspec in LocalPlayer
1370 *pkt >> player->formspec_prepend;
1373 void Client::handleCommand_CSMRestrictionFlags(NetworkPacket *pkt)
1375 *pkt >> m_csm_restriction_flags >> m_csm_restriction_noderange;
1377 // Restrictions were received -> load mods if it's enabled
1378 // Note: this should be moved after mods receptions from server instead
1386 void Client::handleCommand_ModChannelMsg(NetworkPacket *pkt)
1388 std::string channel_name, sender, channel_msg;
1389 *pkt >> channel_name >> sender >> channel_msg;
1391 verbosestream << "Mod channel message received from server " << pkt->getPeerId()
1392 << " on channel " << channel_name << ". sender: `" << sender << "`, message: "
1393 << channel_msg << std::endl;
1395 if (!m_modchannel_mgr->channelRegistered(channel_name)) {
1396 verbosestream << "Server sent us messages on unregistered channel "
1397 << channel_name << ", ignoring." << std::endl;
1401 m_script->on_modchannel_message(channel_name, sender, channel_msg);
1404 void Client::handleCommand_ModChannelSignal(NetworkPacket *pkt)
1407 ModChannelSignal signal;
1408 std::string channel;
1410 *pkt >> signal_tmp >> channel;
1412 signal = (ModChannelSignal)signal_tmp;
1414 bool valid_signal = true;
1415 // @TODO: send Signal to Lua API
1417 case MODCHANNEL_SIGNAL_JOIN_OK:
1418 m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE);
1419 infostream << "Server ack our mod channel join on channel `" << channel
1420 << "`, joining." << std::endl;
1422 case MODCHANNEL_SIGNAL_JOIN_FAILURE:
1423 // Unable to join, remove channel
1424 m_modchannel_mgr->leaveChannel(channel, 0);
1425 infostream << "Server refused our mod channel join on channel `" << channel
1426 << "`" << std::endl;
1428 case MODCHANNEL_SIGNAL_LEAVE_OK:
1430 infostream << "Server ack our mod channel leave on channel " << channel
1431 << "`, leaving." << std::endl;
1434 case MODCHANNEL_SIGNAL_LEAVE_FAILURE:
1435 infostream << "Server refused our mod channel leave on channel `" << channel
1436 << "`" << std::endl;
1438 case MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED:
1440 // Generally unused, but ensure we don't do an implementation error
1441 infostream << "Server tells us we sent a message on channel `" << channel
1442 << "` but we are not registered. Message was dropped." << std::endl;
1445 case MODCHANNEL_SIGNAL_SET_STATE: {
1449 if (state == MODCHANNEL_STATE_INIT || state >= MODCHANNEL_STATE_MAX) {
1450 infostream << "Received wrong channel state " << state
1451 << ", ignoring." << std::endl;
1455 m_modchannel_mgr->setChannelState(channel, (ModChannelState) state);
1456 infostream << "Server sets mod channel `" << channel
1457 << "` in read-only mode." << std::endl;
1462 warningstream << "Received unhandled mod channel signal ID "
1463 << signal << ", ignoring." << std::endl;
1465 valid_signal = false;
1469 // If signal is valid, forward it to client side mods
1471 m_script->on_modchannel_signal(channel, signal);